diff --git a/.cursor/rules/threat-source-rule.mdc b/.cursor/rules/threat-source-rule.mdc index 9b9f528..f07ab18 100644 --- a/.cursor/rules/threat-source-rule.mdc +++ b/.cursor/rules/threat-source-rule.mdc @@ -10,7 +10,4 @@ alwaysApply: true - Y 轴为垂直轴 # 运行命令 -- 在运行命令前,先运行 pwd 命令,了解当前目录 - -# 测试 -- 项目使用 MSTest 测试框架,使用 MSTest 的方式运行测试 \ No newline at end of file +- 在运行命令前,先运行 pwd 命令,了解当前目录 \ No newline at end of file diff --git a/ThreatSource.Tests/data/indicators/ir_trackers/ir_001.json b/ThreatSource.Tests/data/indicators/ir_trackers/ir_001.json new file mode 100644 index 0000000..779c49e --- /dev/null +++ b/ThreatSource.Tests/data/indicators/ir_trackers/ir_001.json @@ -0,0 +1,13 @@ +{ + "name": { + "zh": "红外测角仪-001", + "en": "IR Tracker-001" + }, + "type": "InfraredTracker", + "infraredTrackerConfig": { + "maxTrackingRange": 10000.0, + "fieldOfView": 0.2, + "angleMeasurementAccuracy": 0.001, + "updateFrequency": 10.0 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/indicators/laser_beamriders/br_001.json b/ThreatSource.Tests/data/indicators/laser_beamriders/br_001.json new file mode 100644 index 0000000..b19de60 --- /dev/null +++ b/ThreatSource.Tests/data/indicators/laser_beamriders/br_001.json @@ -0,0 +1,18 @@ +{ + "name": { + "zh": "激光驾束仪-001", + "en": "Laser Beam Rider-001" + }, + "type": "LaserBeamRider", + "beamRiderConfig": { + "laserPower": 1000, + "laserWavelength": 1.06, + "controlFieldDiameter": 6.0, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1010 + } + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/indicators/laser_designators/ld_001.json b/ThreatSource.Tests/data/indicators/laser_designators/ld_001.json new file mode 100644 index 0000000..6c1c1b8 --- /dev/null +++ b/ThreatSource.Tests/data/indicators/laser_designators/ld_001.json @@ -0,0 +1,18 @@ +{ + "name": { + "zh": "前视激光指示器-001", + "en": "Forward Looking Laser Designator-001" + }, + "type": "LaserDesignator", + "designatorConfig": { + "laserPower": 5000, + "laserWavelength": 1.06, + "laserDivergenceAngle": 0.0003, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1010 + } + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/jammers/decoy_jammers/laser_decoy.json b/ThreatSource.Tests/data/jammers/decoy_jammers/laser_decoy.json new file mode 100644 index 0000000..9dad44c --- /dev/null +++ b/ThreatSource.Tests/data/jammers/decoy_jammers/laser_decoy.json @@ -0,0 +1,14 @@ +{ + "name": { + "zh": "激光诱偏目标", + "en": "Laser Decoy" + }, + "type": "LaserDecoy", + "LaserDecoyConfig": { + "decoyPower": 25.0, + "decoyLaserDivergenceAngle": 0.001, + "reflectiveArea": 1.2, + "reflectionCoefficient": 0.8, + "lifeTime": 60.0 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/jammers/smoke_grenades/surround.json b/ThreatSource.Tests/data/jammers/smoke_grenades/surround.json new file mode 100644 index 0000000..af6a9ed --- /dev/null +++ b/ThreatSource.Tests/data/jammers/smoke_grenades/surround.json @@ -0,0 +1,18 @@ +{ + "name": { + "zh": "周边烟幕弹", + "en": "Surround Smoke Grenade" + }, + "type": "SmokeGrenade", + "SmokeGrenadeConfig": { + "smokeType": "Wall", + "concentration": 0.2, + "duration": 60.0, + "wallWidth": 50.0, + "wallHeight": 10.0, + "cloudDiameter": 0.0, + "thickness": 5.0, + "formationDelay": 1.5, + "expansionRate": 5.0 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/jammers/smoke_grenades/top.json b/ThreatSource.Tests/data/jammers/smoke_grenades/top.json new file mode 100644 index 0000000..7abc495 --- /dev/null +++ b/ThreatSource.Tests/data/jammers/smoke_grenades/top.json @@ -0,0 +1,18 @@ +{ + "name": { + "zh": "顶部烟幕弹", + "en": "Top Smoke Grenade" + }, + "type": "SmokeGrenade", + "SmokeGrenadeConfig": { + "smokeType": "Cloud", + "concentration": 0.2, + "duration": 60.0, + "wallWidth": 0.0, + "wallHeight": 0.0, + "cloudDiameter": 20.0, + "thickness": 5.0, + "formationDelay": 1.5, + "expansionRate": 5.0 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/ir_command/irc_001.json b/ThreatSource.Tests/data/missiles/ir_command/irc_001.json new file mode 100644 index 0000000..a6eaf72 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/ir_command/irc_001.json @@ -0,0 +1,27 @@ +{ + "name": { + "zh": "红外指令导引导弹-001", + "en": "IR Command Guided Missile-001" + }, + "type": "InfraredCommandGuidance", + "properties": { + "maxSpeed": 300.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 5000.0, + "maxAcceleration": 100.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 5.0, + "mass": 23.5, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 0.0, + "trackerSensitivity": null, + "commandLatency": null, + "irSignature": null + }, + "infraredCommandGuidanceConfig": { + "jammingResistanceThreshold": 1e-3 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/ir_imaging/itg_001.json b/ThreatSource.Tests/data/missiles/ir_imaging/itg_001.json new file mode 100644 index 0000000..ee48218 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/ir_imaging/itg_001.json @@ -0,0 +1,34 @@ +{ + "name": { + "zh": "红外成像末制导导弹-001", + "en": "IR Imaging Terminal Guided Missile-001" + }, + "type": "InfraredImagingTerminalGuidance", + "properties": { + "maxSpeed": 250.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 5000.0, + "maxAcceleration": 100.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 5.0, + "mass": 25.0, + "explosionRadius": 5.5, + "hitProbability": 0.9, + "selfDestructHeight": 0.0 + }, + "infraredImagingGuidanceConfig": { + "maxDetectionRange": 1000, + "searchFieldOfView": 0.209, + "trackFieldOfView": 0.052, + "imageWidth": 640, + "imageHeight": 512, + "backgroundIntensity": 0.01, + "searchRecognitionProbability": 0.6, + "trackRecognitionProbability": 0.8, + "targetLostTolerance": 0.2, + "lockConfirmationTime": 0.3, + "jammingResistanceThreshold": 1e-3 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/laser_beam_rider/hj10.json b/ThreatSource.Tests/data/missiles/laser_beam_rider/hj10.json new file mode 100644 index 0000000..b47d6c6 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/laser_beam_rider/hj10.json @@ -0,0 +1,41 @@ +{ + "name": { + "zh": "红箭-10", + "en": "HJ-10" + }, + "type": "LaserBeamRiderGuidance", + "properties": { + "maxSpeed": 300.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 5000.0, + "maxAcceleration": 100.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 5.0, + "mass": 24.5, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 0.0, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1010 + }, + "IsCodeEnabled": true, + "IsCodeMatchRequired": true + } + }, + "LaserBeamRiderGuidanceConfig": { + "minDetectablePower": 1e-10, + "detectorDiameter": 0.03, + "controlFieldDiameter": 20.0, + "proportionalGain": 30.0, + "integralGain": 0.05, + "derivativeGain": 5.0, + "nonlinearGain": 0.5, + "maxGuidanceAcceleration": 50.0, + "lowPassFilterCoefficient": 0.2, + "jammingResistanceThreshold": 1e-3 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/laser_beam_rider/kornet.json b/ThreatSource.Tests/data/missiles/laser_beam_rider/kornet.json new file mode 100644 index 0000000..2ff9740 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/laser_beam_rider/kornet.json @@ -0,0 +1,27 @@ +{ + "name": { + "zh": "科尔内特", + "en": "Kornet" + }, + "type": "LaserBeamRiderGuidance", + "properties": { + "maxSpeed": 300.0, + "maxFlightTime": 70.0, + "maxFlightDistance": 5500.0, + "maxAcceleration": 110.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 12.0, + "cruiseTime": 5.0, + "mass": 27.0, + "explosionRadius": 5.5, + "hitProbability": 0.9, + "selfDestructHeight": 10.0, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1100 + } + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/laser_beam_rider/tow.json b/ThreatSource.Tests/data/missiles/laser_beam_rider/tow.json new file mode 100644 index 0000000..3c4f39f --- /dev/null +++ b/ThreatSource.Tests/data/missiles/laser_beam_rider/tow.json @@ -0,0 +1,27 @@ +{ + "name": { + "zh": "陶式导弹", + "en": "TOW" + }, + "type": "LaserBeamRiderGuidance", + "properties": { + "maxSpeed": 278.0, + "maxFlightTime": 65.0, + "maxFlightDistance": 4500.0, + "maxAcceleration": 100.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 10.0, + "cruiseTime": 5.0, + "mass": 22.6, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 10.0, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1110 + } + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/laser_semi_active/lsgm_001.json b/ThreatSource.Tests/data/missiles/laser_semi_active/lsgm_001.json new file mode 100644 index 0000000..2c85ffb --- /dev/null +++ b/ThreatSource.Tests/data/missiles/laser_semi_active/lsgm_001.json @@ -0,0 +1,43 @@ +{ + "name": { + "zh": "激光半主动制导导弹-001", + "en": "Laser Semi-Active Guidance Missile-001" + }, + "type": "LaserSemiActiveGuidance", + "properties": { + "maxSpeed": 800.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 4000.0, + "maxAcceleration": 50.0, + "proportionalNavigationCoefficient": 2.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 5.0, + "mass": 22.0, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 0.0, + "laserCodeConfig": { + "code": { + "codeType": "PRF", + "codeValue": 1010 + }, + "IsCodeEnabled": true, + "IsCodeMatchRequired": true + } + }, + "laserSemiActiveGuidanceConfig": { + "fieldOfViewAngle": 30.0, + "lensDiameter": 0.1, + "sensorDiameter": 0.03, + "focusedSpotDiameter": 0.006, + "reflectionCoefficient": 0.2, + "targetReflectiveArea": 1.0, + "lockThreshold": 1e-12, + "spotOffsetSensitivity": 0.05, + "jammingResistanceThreshold": 1e-3, + "transmitterEfficiency": 0.85, + "receiverEfficiency": 0.8, + "laserWavelength": 1.06 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/mmw/mmw_001.json b/ThreatSource.Tests/data/missiles/mmw/mmw_001.json new file mode 100644 index 0000000..799b230 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/mmw/mmw_001.json @@ -0,0 +1,56 @@ +{ + "name": { + "zh": "毫米波末制导导弹-001", + "en": "Millimeter Wave Terminal Guided Missile-001" + }, + "type": "MillimeterWaveTerminalGuidance", + "properties": { + "maxSpeed": 250.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 8000.0, + "maxAcceleration": 100.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 4.0, + "mass": 28.0, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 0.0 + }, + "millimeterWaveGuidanceConfig": { + "maxDetectionRange": 5000.0, + "fieldOfViewAngle": 45.0, + "targetRecognitionProbability": 0.95, + "waveFrequency": 94e9, + "pulseDuration": 1e-6, + + "searchBeamWidth": 4.0, + "trackBeamWidth": 2.0, + "lockBeamWidth": 1.0, + + "scanAngularSpeedDeg": 360.0, + "scanRadiusGrowthRateDeg": 22.5, + + "recognitionSNRThreshold": -25.0, + "lockSNRThreshold": -10.0, + + "targetLostTolerance": 0.2, + "lockConfirmationTime": 0.3, + + "pulseRepetitionFrequency": 1e-4, + "transmitPower": 0.3, + + "dopplerVelocityResolution": 1.0, + "maxMeasurableVelocity": 1000.0, + + "antennaGainDB": 23.0, + "noiseFigureDB": 7.0, + "systemLossDB": 6.0, + "monopulseSensitivity": 1, + + "yawControlEffectiveness": 120.0, + "pitchControlEffectiveness": 150.0, + "jammingResistanceThreshold": 1e-3 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/missiles/terminal_sensitive/tsm_001.json b/ThreatSource.Tests/data/missiles/terminal_sensitive/tsm_001.json new file mode 100644 index 0000000..0de6168 --- /dev/null +++ b/ThreatSource.Tests/data/missiles/terminal_sensitive/tsm_001.json @@ -0,0 +1,51 @@ +{ + "name": { + "zh": "末敏导弹-001", + "en": "Terminal Sensitive Missile-001" + }, + "type": "TerminalSensitiveMissile", + "properties": { + "maxSpeed": 1000.0, + "maxFlightTime": 100.0, + "maxFlightDistance": 5000.0, + "maxAcceleration": 200.0, + "proportionalNavigationCoefficient": 3.0, + "launchAcceleration": 100.0, + "maxEngineBurnTime": 0.1, + "cruiseTime": 5.0, + "mass": 50.0, + "explosionRadius": 5.0, + "selfDestructHeight": 0.0 + }, + "submunitionProperties": { + "maxSpeed": 2000.0, + "maxFlightTime": 60.0, + "maxFlightDistance": 2000.0, + "maxAcceleration": 300.0, + "proportionalNavigationCoefficient": 4.0, + "launchAcceleration": 10.0, + "maxEngineBurnTime": 0.1, + "mass": 10.0, + "explosionRadius": 5.0, + "hitProbability": 0.9, + "selfDestructHeight": 20.0 + }, + "submunitionCount": 1, + "submunitionConfig": { + "separationHeight": 1000.0, + "separationDistance": 1000.0, + "submunitionSeparationAngle": 45.0, + "separationRange": 50.0, + "decelerationAcceleration": 250.0, + "decelerationEndSpeed": 50.0, + "parachuteDeploymentHeight": 400.0, + "parachuteDeceleration": 140.0, + "stableScanHeight": 200.0, + "verticalDeclineSpeed": 10.0, + "spiralRotationSpeed": 25.13, + "scanAngle": 30.0, + "targetDetectionDistance": 150.0, + "selfDestructHeight": 20.0, + "attackSpeed": 200.0 + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/sensors/laser_warners/lw_001.json b/ThreatSource.Tests/data/sensors/laser_warners/lw_001.json new file mode 100644 index 0000000..7bdd997 --- /dev/null +++ b/ThreatSource.Tests/data/sensors/laser_warners/lw_001.json @@ -0,0 +1,32 @@ +{ + "name": { + "zh": "激光告警接收机-001", + "en": "Laser Warning Receiver-001" + }, + "type": "LaserWarner", + "properties": { + "detectionRange": 5000.0, + "wavelengthRange": { + "min": 0.4, + "max": 1.7 + }, + "fieldOfView": 360.0, + "responseTime": 0.001, + "sensitivity": -60.0, + "angularResolution": 7.5, + "falseAlarmRate": 1e-6, + "detectionProbability": 0.95, + "spectralBands": [ + { + "name": "Band1", + "minWavelength": 0.4, + "maxWavelength": 0.7 + }, + { + "name": "Band2", + "minWavelength": 0.7, + "maxWavelength": 1.7 + } + ] + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/targets/apcs/apc_001.json b/ThreatSource.Tests/data/targets/apcs/apc_001.json new file mode 100644 index 0000000..506011b --- /dev/null +++ b/ThreatSource.Tests/data/targets/apcs/apc_001.json @@ -0,0 +1,34 @@ +{ + "name": { + "zh": "装甲运兵车-001", + "en": "Armored Personnel Carrier-001" + }, + "type": "APC", + "mass": 25000.0, + "length": 7.0, + "width": 3.2, + "height": 2.8, + "maxSpeed": 80.0, + "armorThickness": 400.0, + + "radarCrossSection": 12.0, + "infraredRadiationIntensity": 2000.0, + "ultravioletRadiationIntensity": 12.0, + "millimeterWaveRadiationIntensity": 8.0, + "millimeterWaveRadiationTemperature": 350.0, + "laserReflectivity": 0.25, + + "thermalPattern": { + "description": "3x3 matrix representing side view temperature distribution (°C)", + "static": [ + [35, 40, 65], + [30, 35, 70], + [40, 40, 45] + ], + "moving": [ + [40, 45, 70], + [35, 40, 75], + [50, 50, 55] + ] + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/targets/helicopters/heli_001.json b/ThreatSource.Tests/data/targets/helicopters/heli_001.json new file mode 100644 index 0000000..c3c6b97 --- /dev/null +++ b/ThreatSource.Tests/data/targets/helicopters/heli_001.json @@ -0,0 +1,34 @@ +{ + "name": { + "zh": "武装直升机-001", + "en": "Attack Helicopter-001" + }, + "type": "Helicopter", + "mass": 10000.0, + "length": 17.0, + "width": 3.0, + "height": 4.5, + "maxSpeed": 280.0, + "armorThickness": 150.0, + + "radarCrossSection": 8.0, + "infraredRadiationIntensity": 3000.0, + "ultravioletRadiationIntensity": 20.0, + "millimeterWaveRadiationIntensity": 6.0, + "millimeterWaveRadiationTemperature": 450.0, + "laserReflectivity": 0.2, + + "thermalPattern": { + "description": "3x3 matrix representing side view temperature distribution (°C)", + "static": [ + [85, 110, 80], + [35, 45, 40], + [30, 35, 30] + ], + "moving": [ + [90, 115, 85], + [40, 50, 45], + [35, 40, 35] + ] + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/targets/tanks/mbt_001.json b/ThreatSource.Tests/data/targets/tanks/mbt_001.json new file mode 100644 index 0000000..979429b --- /dev/null +++ b/ThreatSource.Tests/data/targets/tanks/mbt_001.json @@ -0,0 +1,34 @@ +{ + "name": { + "zh": "主战坦克-001", + "en": "Main Battle Tank-001" + }, + "type": "Tank", + "properties": { + "mass": 50000.0, + "length": 10.0, + "width": 3.5, + "height": 2.4, + "maxSpeed": 70.0, + "armorThickness": 800.0, + "radarCrossSection": 15.0, + "infraredRadiationIntensity": 2500.0, + "ultravioletRadiationIntensity": 15.0, + "millimeterWaveRadiationIntensity": 10.0, + "millimeterWaveRadiationTemperature": 400.0, + "laserReflectivity": 0.3, + "thermalPattern": { + "description": "3x3 matrix representing side view temperature distribution (°C)", + "static": [ + [40, 45, 80], + [35, 40, 90], + [50, 50, 60] + ], + "moving": [ + [45, 50, 85], + [40, 45, 95], + [65, 65, 75] + ] + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/heavy_fog.json b/ThreatSource.Tests/data/weathers/heavy_fog.json new file mode 100644 index 0000000..ca03b1e --- /dev/null +++ b/ThreatSource.Tests/data/weathers/heavy_fog.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "浓雾", + "en": "Heavy Fog" + }, + "type": "Fog", + "temperature": 8.0, + "relativeHumidity": 99.0, + "visibility": 0.1, + "precipitation": 0.0, + "cO2Concentration": 415.0, + "pressure": 998.0, + "windSpeed": 0.0, + "windDirection": 0.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/heavy_rain.json b/ThreatSource.Tests/data/weathers/heavy_rain.json new file mode 100644 index 0000000..f7c99e2 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/heavy_rain.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "大雨", + "en": "Heavy Rain" + }, + "type": "Rain", + "temperature": 16.0, + "relativeHumidity": 95.0, + "visibility": 2.0, + "precipitation": 25.0, + "cO2Concentration": 415.0, + "pressure": 985.0, + "windSpeed": 8.0, + "windDirection": 180.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/heavy_snow.json b/ThreatSource.Tests/data/weathers/heavy_snow.json new file mode 100644 index 0000000..3a929d1 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/heavy_snow.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "大雪", + "en": "Heavy Snow" + }, + "type": "Snow", + "temperature": -7.0, + "relativeHumidity": 85.0, + "visibility": 1.0, + "precipitation": 8.0, + "cO2Concentration": 410.0, + "pressure": 990.0, + "windSpeed": 6.0, + "windDirection": 270.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/light_fog.json b/ThreatSource.Tests/data/weathers/light_fog.json new file mode 100644 index 0000000..693e6f5 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/light_fog.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "轻雾", + "en": "Light Fog" + }, + "type": "Fog", + "temperature": 12.0, + "relativeHumidity": 95.0, + "visibility": 0.8, + "precipitation": 0.0, + "cO2Concentration": 415.0, + "pressure": 1000.0, + "windSpeed": 0.5, + "windDirection": 20.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/light_rain.json b/ThreatSource.Tests/data/weathers/light_rain.json new file mode 100644 index 0000000..38bcfb5 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/light_rain.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "小雨", + "en": "Light Rain" + }, + "type": "Rain", + "temperature": 20.0, + "relativeHumidity": 80.0, + "visibility": 6.0, + "precipitation": 5.0, + "cO2Concentration": 415.0, + "pressure": 1005.0, + "windSpeed": 3.0, + "windDirection": 180.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/light_snow.json b/ThreatSource.Tests/data/weathers/light_snow.json new file mode 100644 index 0000000..bb656e0 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/light_snow.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "小雪", + "en": "Light Snow" + }, + "type": "Snow", + "temperature": -3.0, + "relativeHumidity": 75.0, + "visibility": 4.0, + "precipitation": 2.0, + "cO2Concentration": 410.0, + "pressure": 1002.0, + "windSpeed": 3.0, + "windDirection": 270.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/medium_fog.json b/ThreatSource.Tests/data/weathers/medium_fog.json new file mode 100644 index 0000000..5236375 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/medium_fog.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "中雾", + "en": "Medium Fog" + }, + "type": "Fog", + "temperature": 10.0, + "relativeHumidity": 98.0, + "visibility": 0.4, + "precipitation": 0.0, + "cO2Concentration": 415.0, + "pressure": 1000.0, + "windSpeed": 0.2, + "windDirection": 10.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/medium_rain.json b/ThreatSource.Tests/data/weathers/medium_rain.json new file mode 100644 index 0000000..4fe1d6f --- /dev/null +++ b/ThreatSource.Tests/data/weathers/medium_rain.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "中雨", + "en": "Medium Rain" + }, + "type": "Rain", + "temperature": 18.0, + "relativeHumidity": 90.0, + "visibility": 4.0, + "precipitation": 15.0, + "cO2Concentration": 415.0, + "pressure": 995.0, + "windSpeed": 6.0, + "windDirection": 180.0 +} diff --git a/ThreatSource.Tests/data/weathers/medium_snow.json b/ThreatSource.Tests/data/weathers/medium_snow.json new file mode 100644 index 0000000..1b43fe4 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/medium_snow.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "中雪", + "en": "Medium Snow" + }, + "type": "Snow", + "temperature": -5.0, + "relativeHumidity": 80.0, + "visibility": 2.0, + "precipitation": 4.0, + "cO2Concentration": 410.0, + "pressure": 1000.0, + "windSpeed": 4.0, + "windDirection": 270.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/sandstorm.json b/ThreatSource.Tests/data/weathers/sandstorm.json new file mode 100644 index 0000000..992bec2 --- /dev/null +++ b/ThreatSource.Tests/data/weathers/sandstorm.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "沙尘", + "en": "Sandstorm" + }, + "type": "Dust", + "temperature": 30.0, + "relativeHumidity": 25.0, + "visibility": 1.5, + "precipitation": 0.0, + "cO2Concentration": 430.0, + "pressure": 1005.0, + "windSpeed": 12.0, + "windDirection": 315.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/data/weathers/sunny.json b/ThreatSource.Tests/data/weathers/sunny.json new file mode 100644 index 0000000..6dbbffa --- /dev/null +++ b/ThreatSource.Tests/data/weathers/sunny.json @@ -0,0 +1,15 @@ +{ + "name": { + "zh": "晴天", + "en": "Sunny" + }, + "type": "Clear", + "temperature": 25.0, + "relativeHumidity": 45.0, + "visibility": 15.0, + "precipitation": 0.0, + "cO2Concentration": 415.0, + "pressure": 1018.0, + "windSpeed": 1.2, + "windDirection": 90.0 +} \ No newline at end of file diff --git a/ThreatSource.Tests/src/Jamming/SmokeGrenadeTests.cs b/ThreatSource.Tests/src/Jamming/SmokeGrenadeTests.cs new file mode 100644 index 0000000..2b9afe1 --- /dev/null +++ b/ThreatSource.Tests/src/Jamming/SmokeGrenadeTests.cs @@ -0,0 +1,293 @@ +using System; +using Xunit; +using ThreatSource.Jammer; +using ThreatSource.Utils; +using ThreatSource.Simulation; +using ThreatSource.Tests.Simulation; +using ThreatSource.Data; +using AirTransmission; +using System.IO; + +namespace ThreatSource.Tests.Jamming +{ + public class SmokeGrenadeTests + { + private readonly TestSimulationAdapter _simulationAdapter; + private readonly ISimulationManager _simulationManager; + private readonly ThreatSourceFactory _threatSourceFactory; + private readonly ThreatSourceDataManager _dataManager; + + public SmokeGrenadeTests() + { + // 创建模拟管理器和适配器 + _simulationManager = new SimulationManager(); + _simulationAdapter = new TestSimulationAdapter(_simulationManager); + _simulationManager.SetSimulationAdapter(_simulationAdapter); + + // 创建数据管理器和工厂 + string baseDir = AppDomain.CurrentDomain.BaseDirectory; + string projectRoot = Path.GetFullPath(Path.Combine(baseDir, "../../../..")); + string dataPath = Path.Combine(projectRoot, "ThreatSource/data"); + + _dataManager = new ThreatSourceDataManager(dataPath); + _threatSourceFactory = new ThreatSourceFactory(_dataManager, _simulationManager); + + // 显示可用的天气类型 + Console.WriteLine("可用的天气类型:"); + foreach (var weatherType in _dataManager.GetAvailableWeathers()) + { + Console.WriteLine($" - {weatherType}"); + } + + // 创建并设置天气 + var weather = _threatSourceFactory.CreateWeather("sunny"); + _simulationManager.SetWeather(weather); + Console.WriteLine("已添加并设置晴天天气环境"); + } + + [Fact] + public void Initialize_WithWallSmokeConfig_SetsCorrectParameters() + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Wall, + Concentration = 0.2, + Duration = 60.0, + WallWidth = 50.0, + WallHeight = 10.0, + Thickness = 5.0, + ExpansionRate = 5.0, + FormationDelay = 1.5 + }; + + // Act + var smokeGrenade = new SmokeGrenade("test_wall", config, new MotionParameters(), _simulationManager); + + // Assert + Assert.Equal(SmokeScreenType.Wall, config.SmokeType); + Assert.Equal(50.0, config.WallWidth); + Assert.Equal(10.0, config.WallHeight); + Assert.False(smokeGrenade.IsActive); + } + + [Fact] + public void Initialize_WithCloudSmokeConfig_SetsCorrectParameters() + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Cloud, + Concentration = 0.2, + Duration = 60.0, + CloudDiameter = 20.0, + Thickness = 5.0, + ExpansionRate = 5.0, + FormationDelay = 1.5 + }; + + // Act + var smokeGrenade = new SmokeGrenade("test_cloud", config, new MotionParameters(), _simulationManager); + + // Assert + Assert.Equal(SmokeScreenType.Cloud, config.SmokeType); + Assert.Equal(20.0, config.CloudDiameter); + Assert.False(smokeGrenade.IsActive); + } + + [Fact] + public void Activate_SetsIsActiveTrue() + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Wall, + Concentration = 0.2, + Duration = 60.0 + }; + var smokeGrenade = new SmokeGrenade("test", config, new MotionParameters(), _simulationManager); + + // Act + smokeGrenade.Activate(); + + // Assert + Assert.True(smokeGrenade.IsActive); + } + + [Theory] + [InlineData(0)] // 初始时刻 + [InlineData(30)] // 中间时刻 + [InlineData(60)] // 结束时刻 + public void Update_ConcentrationDecaysOverTime(double elapsedTime) + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Wall, + Concentration = 0.2, + Duration = 60.0, + WallWidth = 50.0, + WallHeight = 10.0, + Thickness = 5.0 + }; + + var motionParams = new MotionParameters + { + Position = Vector3D.Zero, + Orientation = Orientation.FromVector(Vector3D.UnitZ) // 使用Z轴单位向量设置朝向 + }; + + var smokeGrenade = new SmokeGrenade("test", config, motionParams, _simulationManager); + smokeGrenade.Activate(); + + // Act + for (int i = 0; i < elapsedTime; i++) + { + smokeGrenade.Update(1.0); // 每次更新1秒 + } + + // 计算预期浓度(使用半衰期公式) + double expectedConcentration = config.Concentration * Math.Exp(-Math.Log(2) * elapsedTime / config.Duration); + + // 获取实际浓度(通过测试一个穿过烟幕中心的视线) + Vector3D observer = new(0, 0, -10); + Vector3D target = new(0, 0, 10); + double thickness = smokeGrenade.GetSmokeThicknessOnLine(observer, target); + + // Assert + if (elapsedTime >= 60) + { + Assert.False(smokeGrenade.IsActive); // 到达持续时间后应该自动禁用 + Assert.Equal(0, thickness); // 禁用后厚度应该为0 + } + else + { + Assert.True(thickness > 0); // 未到持续时间时应该有烟幕 + } + } + + [Theory] + [InlineData(0, 0)] // 无风 + [InlineData(5, 0)] // 北风 + [InlineData(5, 90)] // 东风 + [InlineData(5, 180)] // 南风 + [InlineData(5, 270)] // 西风 + public void Update_SmokePositionChangesWithWind(double windSpeed, double windDirection) + { + // Arrange + var weather = _threatSourceFactory.CreateWeather("sunny"); + weather.WindSpeed = windSpeed; + weather.WindDirection = windDirection; + _simulationManager.SetWeather(weather); + + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Cloud, + Concentration = 0.2, + Duration = 60.0, + CloudDiameter = 20.0 + }; + var initialPosition = new Vector3D(0, 0, 0); + var motionParams = new MotionParameters { Position = initialPosition }; + var smokeGrenade = new SmokeGrenade("test", config, motionParams, _simulationManager); + smokeGrenade.Activate(); + + // Act + double updateTime = 10.0; // 更新10秒 + smokeGrenade.Update(updateTime); + + // 计算预期位移 + double windDirectionRad = windDirection * Math.PI / 180.0; + double expectedX = windSpeed * updateTime * Math.Sin(windDirectionRad); + double expectedZ = windSpeed * updateTime * Math.Cos(windDirectionRad); + + // Assert + Assert.Equal(expectedX, smokeGrenade.Position.X, 2); // 允许2米的误差 + Assert.Equal(expectedZ, smokeGrenade.Position.Z, 2); + } + + [Theory] + [InlineData(0, 0, 10, true)] // 正对墙面中心 + [InlineData(20, 0, 10, true)] // 斜向墙面但在宽度范围内 + [InlineData(-50, 0, -10, false)] // 在墙外(观察者和目标都在墙外) + [InlineData(0, 15, 0, false)] // 高度超出范围 + public void GetSmokeThicknessOnLine_WallSmoke_ReturnsCorrectThickness( + double observerX, double observerY, double expectedThickness, bool shouldIntersect) + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Wall, + WallWidth = 50.0, + WallHeight = 10.0, + Thickness = 5.0 + }; + + // 设置烟墙朝向为Z轴正方向 + var motionParams = new MotionParameters + { + Position = Vector3D.Zero, + Orientation = Orientation.FromVector(Vector3D.UnitZ) // 使用Z轴单位向量设置朝向 + }; + + var smokeGrenade = new SmokeGrenade("test", config, motionParams, _simulationManager); + smokeGrenade.Activate(); + + Vector3D observer = new(observerX, observerY, -10); + Vector3D target = new(observerX, observerY, 10); + + // Act + double thickness = smokeGrenade.GetSmokeThicknessOnLine(observer, target); + + // Assert + if (shouldIntersect) + { + Assert.True(thickness > 0); + Assert.True(thickness >= config.Thickness); // 斜向穿透时厚度会更大 + } + else + { + Assert.Equal(0, thickness); + } + } + + [Theory] + [InlineData(0, 0, true)] // 穿过云中心 + [InlineData(8, 0, true)] // 穿过云边缘 + [InlineData(15, 0, false)] // 未穿过云 + [InlineData(0, 10, false)] // 高度超出范围 + public void GetSmokeThicknessOnLine_CloudSmoke_ReturnsCorrectThickness( + double offsetX, double offsetY, bool shouldIntersect) + { + // Arrange + var config = new SmokeGrenadeConfig + { + SmokeType = SmokeScreenType.Cloud, + CloudDiameter = 20.0, + Thickness = 5.0 + }; + var smokeGrenade = new SmokeGrenade("test", config, new MotionParameters(), _simulationManager); + smokeGrenade.Activate(); + + Vector3D observer = new(offsetX, offsetY, -10); + Vector3D target = new(offsetX, offsetY, 10); + + // Act + double thickness = smokeGrenade.GetSmokeThicknessOnLine(observer, target); + + // Assert + if (shouldIntersect) + { + Assert.True(thickness > 0); + if (offsetX == 0) // 中心穿透 + { + Assert.Equal(config.CloudDiameter, thickness, 1); // 允许1米误差 + } + } + else + { + Assert.Equal(0, thickness); + } + } + } +} \ No newline at end of file diff --git a/ThreatSource.Tests/src/Simulation/TestSimulationAdapter.cs b/ThreatSource.Tests/src/Simulation/TestSimulationAdapter.cs index a381001..a150aba 100644 --- a/ThreatSource.Tests/src/Simulation/TestSimulationAdapter.cs +++ b/ThreatSource.Tests/src/Simulation/TestSimulationAdapter.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using ThreatSource.Simulation; -using ThreatSource.Utils; +using AirTransmission; namespace ThreatSource.Tests.Simulation { @@ -20,13 +20,15 @@ namespace ThreatSource.Tests.Simulation private Dictionary _environmentData = new(); // 当前仿真时间 private double _currentTime = 0; + // 当前天气 + private Weather? _currentWeather; private readonly object _eventLock = new object(); // 添加事件声明 public event EventHandler? EventPublished; /// - /// 构造函数,初始化测试适配器 + /// 带仿真管理器的构造函数 /// /// 仿真管理器 public TestSimulationAdapter(ISimulationManager simulationManager) @@ -34,6 +36,21 @@ namespace ThreatSource.Tests.Simulation _simulationManager = simulationManager ?? throw new ArgumentNullException(nameof(simulationManager)); } + /// + /// 获取当前天气 + /// + public Weather? CurrentWeather => _currentWeather; + + /// + /// 设置当前天气 + /// + /// 天气条件 + public void SetWeather(Weather weather) + { + _currentWeather = weather; + Console.WriteLine($"已设置天气:{weather.Type}"); + } + /// /// 获取实体 /// diff --git a/ThreatSource/src/Data/ThreatSourceDataManager.cs b/ThreatSource/src/Data/ThreatSourceDataManager.cs index 9088b85..69be0a1 100644 --- a/ThreatSource/src/Data/ThreatSourceDataManager.cs +++ b/ThreatSource/src/Data/ThreatSourceDataManager.cs @@ -73,28 +73,16 @@ namespace ThreatSource.Data try { var jsonContent = File.ReadAllText(file); - Console.WriteLine($"读取导弹文件内容:{jsonContent}"); - Console.WriteLine($"序列化选项:"); - Console.WriteLine($"- PropertyNameCaseInsensitive: {_jsonOptions.PropertyNameCaseInsensitive}"); - Console.WriteLine($"- WriteIndented: {_jsonOptions.WriteIndented}"); - Console.WriteLine($"- PropertyNamingPolicy: {_jsonOptions.PropertyNamingPolicy}"); var data = JsonSerializer.Deserialize(jsonContent, _jsonOptions); if (data != null) { string model = Path.GetFileNameWithoutExtension(file); _missiles[model] = data; - Console.WriteLine($"已加载导弹数据:{model}"); - Console.WriteLine($"导弹类型:{data.Type}"); - Console.WriteLine($"毫米波导引配置:{(data.MillimeterWaveGuidanceConfig != null ? "已加载" : "未加载")}"); - if (data.MillimeterWaveGuidanceConfig != null) + if (data.MillimeterWaveGuidanceConfig != null) { var config = data.MillimeterWaveGuidanceConfig; - Console.WriteLine($"- 最大探测范围:{config.MaxDetectionRange}"); - Console.WriteLine($"- 视场角:{config.FieldOfViewAngle}"); - Console.WriteLine($"- 目标识别概率:{config.TargetRecognitionProbability}"); } - //Console.WriteLine($"读取到的完整数据:{JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true })}"); } } catch (Exception ex) @@ -121,16 +109,13 @@ namespace ThreatSource.Data try { var jsonContent = File.ReadAllText(file); - //Console.WriteLine($"读取指示器文件内容:{jsonContent}"); var data = JsonSerializer.Deserialize(jsonContent, _jsonOptions); if (data != null) { string model = Path.GetFileNameWithoutExtension(file); _indicators[model] = data; - Console.WriteLine($"已加载指示器数据:{model}"); - //Console.WriteLine($"读取到的数据:{JsonSerializer.Serialize(data, _jsonOptions)}"); - } + } } catch (Exception ex) { @@ -156,15 +141,12 @@ namespace ThreatSource.Data try { var jsonContent = File.ReadAllText(file); - //Console.WriteLine($"读取传感器文件内容:{jsonContent}"); var data = JsonSerializer.Deserialize(jsonContent, _jsonOptions); if (data != null) { string model = Path.GetFileNameWithoutExtension(file); _sensors[model] = data; - Console.WriteLine($"已加载传感器数据:{model}"); - //Console.WriteLine($"读取到的数据:{JsonSerializer.Serialize(data, _jsonOptions)}"); } } catch (Exception ex) @@ -191,15 +173,12 @@ namespace ThreatSource.Data try { var jsonContent = File.ReadAllText(file); - //Console.WriteLine($"读取目标文件内容:{jsonContent}"); var data = JsonSerializer.Deserialize(jsonContent, _jsonOptions); if (data != null) { string model = Path.GetFileNameWithoutExtension(file); _targets[model] = data; - Console.WriteLine($"正在加载目标数据:{file}"); - //Console.WriteLine($"读取到的数据:{JsonSerializer.Serialize(data, _jsonOptions)}"); } } catch (Exception ex) @@ -232,7 +211,6 @@ namespace ThreatSource.Data { string model = Path.GetFileNameWithoutExtension(file); _weathers[model] = data; - Console.WriteLine($"已加载天气数据:{model}"); } } catch (Exception ex) @@ -264,7 +242,6 @@ namespace ThreatSource.Data { string model = Path.GetFileNameWithoutExtension(file); _jammers[model] = data; - Console.WriteLine($"已加载干扰机数据:{model}"); } } catch (Exception ex) diff --git a/ThreatSource/src/Jammer/JammingParameters.cs b/ThreatSource/src/Jammer/JammingParameters.cs index ba7749d..dda9330 100644 --- a/ThreatSource/src/Jammer/JammingParameters.cs +++ b/ThreatSource/src/Jammer/JammingParameters.cs @@ -89,6 +89,31 @@ namespace ThreatSource.Jammer /// 烟幕厚度,单位:米 /// public double? SmokeThickness { get; set; } + + /// + /// 烟幕直径,单位:米 + /// + public double? SmokeDiameter { get; set; } + + /// + /// 烟幕墙宽,单位:米 + /// + public double? SmokeWallWidth { get; set; } + + /// + /// 烟幕墙高,单位:米 + /// + public double? SmokeWallHeight { get; set; } + + /// + /// 烟幕形成延迟,单位:秒 + /// + public double? FormationDelay { get; set; } + + /// + /// 烟幕扩散速率,单位:米/秒 + /// + public double? ExpansionRate { get; set; } } /// diff --git a/ThreatSource/src/Jammer/SmokeGrenade.cs b/ThreatSource/src/Jammer/SmokeGrenade.cs index 7e9246d..f35e392 100644 --- a/ThreatSource/src/Jammer/SmokeGrenade.cs +++ b/ThreatSource/src/Jammer/SmokeGrenade.cs @@ -51,6 +51,7 @@ namespace ThreatSource.Jammer public override void Activate() { IsActive = true; + _elapsedTime = 0; if (!IsJamming) { var parameters = CreateJammingParameters(); @@ -86,17 +87,73 @@ namespace ThreatSource.Jammer StartTime = DateTime.UtcNow, SmokeType = config.SmokeType, SmokeConcentration = config.Concentration, - SmokeThickness = config.Thickness + SmokeThickness = config.Thickness, + SmokeDiameter = config.CloudDiameter, + SmokeWallWidth = config.WallWidth, + SmokeWallHeight = config.WallHeight, + FormationDelay = config.FormationDelay, + ExpansionRate = config.ExpansionRate }; } + private double _elapsedTime = 0; + /// /// 更新烟幕效果 /// protected override void UpdateJamming(double deltaTime) { - // 发布烟幕状态更新事件 - PublishSmokeEvent(); + var weather = SimulationManager?.CurrentWeather; + if (weather != null && IsActive && CurrentParameters != null) + { + // 更新累计时间 + _elapsedTime += deltaTime; + + // 风力移动部分 + double windSpeed = weather.WindSpeed; + double windDirectionDeg = weather.WindDirection; + double windDirectionRad = windDirectionDeg * Math.PI / 180.0; + + Vector3D windVector = new( + Math.Sin(windDirectionRad), + 0, + Math.Cos(windDirectionRad) + ); + + // 烟雾位置随风移动 + Position += windVector * windSpeed * deltaTime; + + // 计算浓度衰减 + double decayRate = Math.Log(2) / config.Duration; + CurrentParameters.SmokeConcentration *= Math.Exp(-decayRate * deltaTime); + + // 计算扩散 + double expansionAmount = config.ExpansionRate * deltaTime; + CurrentParameters.SmokeThickness = config.Thickness + expansionAmount; + + if (config.SmokeType == SmokeScreenType.Cloud) + { + CurrentParameters.SmokeDiameter = config.CloudDiameter + expansionAmount; + CurrentParameters.SmokeWallWidth = null; + CurrentParameters.SmokeWallHeight = null; + } + else // Wall + { + CurrentParameters.SmokeWallWidth = config.WallWidth + expansionAmount; + CurrentParameters.SmokeWallHeight = config.WallHeight + expansionAmount; + CurrentParameters.SmokeDiameter = null; + } + + // 发布烟幕状态更新事件 + PublishSmokeEvent(); + + // 检查是否需要停止烟幕效果 + if (_elapsedTime >= config.Duration || + CurrentParameters.SmokeConcentration < config.Concentration * 0.1) + { + Deactivate(); + } + } } /// @@ -150,13 +207,157 @@ namespace ThreatSource.Jammer PublishEvent(evt); } - /// - /// 判断给定点是否在烟幕范围内 + /// 计算观察者到目标的视线穿过烟雾的厚度 /// - /// 要检测的点 - /// 点是否在烟幕范围内 - public bool IsPointInSmoke(Vector3D point) + /// 观察者位置 + /// 目标位置 + /// 穿过的烟雾厚度(米),如果没有穿过烟雾则返回0 + public double GetSmokeThicknessOnLine(Vector3D observerPosition, Vector3D targetPosition) + { + if (!IsActive || !IsJamming) return 0; + + // 计算视线向量 + Vector3D lineDirection = (targetPosition - observerPosition).Normalize(); + double lineLength = (targetPosition - observerPosition).Magnitude(); + + if (config.SmokeType == SmokeScreenType.Cloud) + { + // 对于云状烟幕,计算视线与圆柱体的交点 + return CalculateCloudIntersection(observerPosition, lineDirection, lineLength); + } + else // SmokeScreenType.Wall + { + // 对于墙状烟幕,计算视线与平面的交点 + return CalculateWallIntersection(observerPosition, lineDirection, lineLength); + } + } + + /// + /// 计算视线与云状烟幕的交点并返回穿过的厚度 + /// + private double CalculateCloudIntersection(Vector3D origin, Vector3D direction, double maxDistance) + { + if (!IsActive || !IsJamming || CurrentParameters == null) return 0; + + // 将视线转换到烟幕局部坐标系 + Vector3D localOrigin = origin - Position; + + // 检查高度约束 + double currentThickness = CurrentParameters.SmokeThickness ?? config.Thickness; + if (Math.Abs(localOrigin.Y) > currentThickness && + Math.Abs(localOrigin.Y + direction.Y * maxDistance) > currentThickness) + { + return 0; + } + + // 计算视线与圆柱体的交点 + // 将问题简化为2D平面上的圆与线段相交 + Vector3D originXZ = new Vector3D(localOrigin.X, 0, localOrigin.Z); + Vector3D directionXZ = new Vector3D(direction.X, 0, direction.Z).Normalize(); + + double radius = config.CloudDiameter / 2; + double a = Vector3D.DotProduct(directionXZ, directionXZ); + double b = 2 * Vector3D.DotProduct(originXZ, directionXZ); + double c = Vector3D.DotProduct(originXZ, originXZ) - radius * radius; + + double discriminant = b * b - 4 * a * c; + if (discriminant < 0) + { + return 0; // 没有交点 + } + + // 计算交点参数 + double t1 = (-b - Math.Sqrt(discriminant)) / (2 * a); + double t2 = (-b + Math.Sqrt(discriminant)) / (2 * a); + + // 检查交点是否在视线段上 + if (t2 < 0 || t1 > maxDistance) + { + return 0; + } + + // 限制交点在视线段范围内 + t1 = Math.Max(0, t1); + t2 = Math.Min(maxDistance, t2); + + // 计算实际穿过的厚度 + return (t2 - t1); + } + + /// + /// 计算视线与墙状烟幕的交点并返回穿过的厚度 + /// + private double CalculateWallIntersection(Vector3D origin, Vector3D direction, double maxDistance) + { + if (!IsActive || !IsJamming || CurrentParameters == null) return 0; + + // 将视线转换到烟幕局部坐标系 + Vector3D localOrigin = origin - Position; + + // 获取墙面法向量(与烟幕朝向一致) + Vector3D wallNormal = Orientation.ToVector(); + + // 计算视线与墙面的夹角余弦 + double cosAngle = Vector3D.DotProduct(direction, wallNormal); + if (Math.Abs(cosAngle) < 1e-6) + { + return 0; // 视线平行于墙面 + } + + // 计算观察点到墙面中心的距离 + double distToWallCenter = Vector3D.DotProduct(localOrigin, wallNormal); + double currentThickness = CurrentParameters.SmokeThickness ?? config.Thickness; + + // 如果观察点距离墙面中心的距离超过厚度的一半,且视线方向背向墙面,则不可能相交 + if (Math.Abs(distToWallCenter) > currentThickness / 2 && + Math.Sign(distToWallCenter) == Math.Sign(cosAngle)) + { + return 0; + } + + // 计算视线与中心平面的交点参数 + double t = -Vector3D.DotProduct(localOrigin, wallNormal) / cosAngle; + if (t < 0 || t > maxDistance) + { + return 0; // 交点不在视线段上 + } + + // 计算交点位置 + Vector3D intersection = localOrigin + direction * t; + + // 检查交点是否在墙面范围内 + // 1. 检查高度 + double currentHeight = CurrentParameters.SmokeWallHeight ?? config.WallHeight; + if (Math.Abs(intersection.Y) > currentHeight / 2) + { + return 0; + } + + // 2. 检查宽度(沿墙面方向) + Vector3D wallParallel = Vector3D.CrossProduct(wallNormal, Vector3D.UnitY); + double distAlongWall = Math.Abs(Vector3D.DotProduct(intersection, wallParallel)); + double currentWidth = CurrentParameters.SmokeWallWidth ?? config.WallWidth; + if (distAlongWall > currentWidth / 2) + { + return 0; + } + + // 3. 检查交点到墙面中心的距离是否在厚度范围内 + double distFromWallCenter = Math.Abs(Vector3D.DotProduct(intersection, wallNormal)); + if (distFromWallCenter > currentThickness / 2) + { + return 0; + } + + // 如果交点在墙面范围内,返回穿过的厚度 + return currentThickness / Math.Abs(cosAngle); + } + + /// + /// 判断给定点是否在烟幕范围内(内部辅助方法) + /// + private bool IsPointInSmoke(Vector3D point) { if (!IsActive || !IsJamming) return false; @@ -177,7 +378,7 @@ namespace ThreatSource.Jammer } // 计算水平面内的位置 - Vector3D planePos = new Vector3D(relativePos.X, 0, relativePos.Z); + Vector3D planePos = new(relativePos.X, 0, relativePos.Z); double radiusComponent = planePos.Magnitude(); // 检查径向距离是否在圆柱体范围内