feat: 路径积分毁伤判定(DamageAssessment.PathInSphere)
- 替换离散 ContainsPoint 为连续路径积分 - DroneEntity 新增 PrevX/Y/Z + SavePreviousPosition - DamageAssessment 独立模块 - Piston WindSpeed=0 验证:暴露4.52s,击毁
This commit is contained in:
parent
1f092e8647
commit
a3bf6720c6
@ -67,10 +67,12 @@ namespace CounterDrone.Core.Algorithms
|
||||
return TimeToReach(r);
|
||||
}
|
||||
|
||||
/// <summary>覆盖指定距离需要的云团数量(间距=2R)</summary>
|
||||
public int RoundsNeeded(float requiredCoverage)
|
||||
/// <summary>覆盖指定距离需要的云团数量</summary>
|
||||
/// <param name="requiredCoverage">需要覆盖的距离 (m)</param>
|
||||
/// <param name="effectiveRadius">使用哪个半径值(null=默认TurbulentRadius)</param>
|
||||
public int RoundsNeeded(float requiredCoverage, float? effectiveRadius = null)
|
||||
{
|
||||
float R = TurbulentRadius;
|
||||
float R = effectiveRadius ?? TurbulentRadius;
|
||||
float spacing = 2f * R;
|
||||
return Math.Max(1, (int)Math.Ceiling(requiredCoverage / spacing));
|
||||
}
|
||||
|
||||
41
src/CounterDrone.Core/Algorithms/DamageAssessment.cs
Normal file
41
src/CounterDrone.Core/Algorithms/DamageAssessment.cs
Normal file
@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using CounterDrone.Core.Models;
|
||||
|
||||
namespace CounterDrone.Core.Algorithms
|
||||
{
|
||||
/// <summary>毁伤评估——路径积分计算无人机穿过云团的暴露时间</summary>
|
||||
public static class DamageAssessment
|
||||
{
|
||||
/// <summary>线段 (p1→p2) 在球体内的长度,换算为路径长度 (m)</summary>
|
||||
/// <returns>球体内的路径长度 (m),0 表示未进入</returns>
|
||||
public static float PathInSphere(
|
||||
float p1x, float p1y, float p1z,
|
||||
float p2x, float p2y, float p2z,
|
||||
float cx, float cy, float cz, float radius)
|
||||
{
|
||||
float dx = p2x - p1x, dy = p2y - p1y, dz = p2z - p1z;
|
||||
float segLen = (float)Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
if (segLen < 0.0001f) return 0f;
|
||||
|
||||
float fx = p1x - cx, fy = p1y - cy, fz = p1z - cz;
|
||||
|
||||
// |p1 + t*d - c|² = r² → a*t² + 2b*t + c = 0
|
||||
float a = dx * dx + dy * dy + dz * dz;
|
||||
float b = dx * fx + dy * fy + dz * fz;
|
||||
float c = fx * fx + fy * fy + fz * fz - radius * radius;
|
||||
|
||||
float disc = b * b - a * c;
|
||||
if (disc <= 0) return 0f;
|
||||
|
||||
float sqrtDisc = (float)Math.Sqrt(disc);
|
||||
float t1 = (-b - sqrtDisc) / a;
|
||||
float t2 = (-b + sqrtDisc) / a;
|
||||
|
||||
t1 = Math.Max(0f, t1);
|
||||
t2 = Math.Min(1f, t2);
|
||||
if (t1 >= t2) return 0f;
|
||||
|
||||
return (t2 - t1) * segLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -396,7 +396,12 @@ namespace CounterDrone.Core.Algorithms
|
||||
|
||||
var mid = ThreatMidpoint(threat);
|
||||
var cloudModel = new CloudExpansionModel(ammo, env);
|
||||
float expansionTime = cloudModel.TimeToReach(cloudModel.TurbulentRadius);
|
||||
// 云龄 = expansionTime,expansionTime = TimeToReach(RadiusAt(30s)) ≈ 30s
|
||||
// 考虑弹间间隔,最后云的龄 = expansionTime - (rounds-1)*stagger
|
||||
// 用保守值:取 expansionTime 的 90%(实际间距导致龄差 ~3s)
|
||||
float effectiveAge = cloudModel.TimeToReach(cloudModel.TurbulentRadius) * 0.9f;
|
||||
float effectiveR = cloudModel.RadiusAt(effectiveAge);
|
||||
float expansionTime = cloudModel.TimeToReach(effectiveR);
|
||||
|
||||
// 密度检查:云团在膨胀时间后密度是否仍达标
|
||||
float densityAtPassage = cloudModel.DensityAt(expansionTime);
|
||||
|
||||
@ -21,6 +21,10 @@ namespace CounterDrone.Core.Simulation
|
||||
public float Hp { get; private set; } = 1.0f;
|
||||
public DroneStatus Status { get; private set; } = DroneStatus.Flying;
|
||||
public float ExposureTime { get; set; }
|
||||
/// <summary>上一帧位置(路径积分用)</summary>
|
||||
public float PrevX { get; private set; }
|
||||
public float PrevY { get; private set; }
|
||||
public float PrevZ { get; private set; }
|
||||
public float FormationOffsetX { get; set; }
|
||||
public float FormationOffsetY { get; set; }
|
||||
|
||||
@ -90,6 +94,11 @@ namespace CounterDrone.Core.Simulation
|
||||
}
|
||||
}
|
||||
|
||||
public void SavePreviousPosition()
|
||||
{
|
||||
PrevX = PosX; PrevY = PosY; PrevZ = PosZ;
|
||||
}
|
||||
|
||||
public void Update(float deltaTime, float windSpeed, WindDirection windDir)
|
||||
{
|
||||
if (Status != DroneStatus.Flying) return;
|
||||
|
||||
@ -242,32 +242,12 @@ namespace CounterDrone.Core.Simulation
|
||||
if (c.IsDissipated) _clouds.Remove(c);
|
||||
}
|
||||
|
||||
// 4. 毁伤判定
|
||||
// 4. 毁伤判定(路径积分替代点采样)
|
||||
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
|
||||
{
|
||||
foreach (var cloud in _clouds)
|
||||
{
|
||||
var dx = drone.PosX - cloud.Dispersion.Center.X;
|
||||
var dy = drone.PosY - cloud.Dispersion.Center.Y;
|
||||
var dz = drone.PosZ - cloud.Dispersion.Center.Z;
|
||||
var dist = (float)System.Math.Sqrt(dx * dx + dy * dy + dz * dz);
|
||||
bool inside = dist <= cloud.Dispersion.EffectiveRadius;
|
||||
_hitLog.AppendLine($"{drone.PosX:F1},{drone.PosY:F1},{cloud.Dispersion.Center.X:F1},{cloud.Dispersion.Center.Y:F1},{dist:F1},{cloud.Dispersion.EffectiveRadius:F1},{inside}");
|
||||
if (inside && cloud.Dispersion.CoreDensity >= (float)_ammoSpec.EffectiveConcentration)
|
||||
{
|
||||
drone.ExposureTime += scaledDt;
|
||||
var dmg = _damageModel.CalculateDamage(drone.TargetType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, scaledDt);
|
||||
drone.ApplyDamage(dmg);
|
||||
if (drone.Status == DroneStatus.Destroyed)
|
||||
{
|
||||
OnDroneDestroyed?.Invoke(drone);
|
||||
frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁" });
|
||||
}
|
||||
}
|
||||
}
|
||||
// 保存上一帧位置用于积分
|
||||
drone.SavePreviousPosition();
|
||||
}
|
||||
if (_hitLog.Length > 0 && SimulationTime >= 120)
|
||||
System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\hit_log.csv", _hitLog.ToString());
|
||||
|
||||
// 5. 无人机移动
|
||||
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
|
||||
@ -280,6 +260,31 @@ namespace CounterDrone.Core.Simulation
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 毁伤判定:积分路径段在云内的时间
|
||||
foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying))
|
||||
{
|
||||
foreach (var cloud in _clouds)
|
||||
{
|
||||
float inCloudTime = DamageAssessment.PathInSphere(
|
||||
drone.PrevX, drone.PrevY, drone.PrevZ,
|
||||
drone.PosX, drone.PosY, drone.PosZ,
|
||||
cloud.Dispersion.Center.X, cloud.Dispersion.Center.Y, cloud.Dispersion.Center.Z,
|
||||
cloud.Dispersion.EffectiveRadius);
|
||||
if (inCloudTime > 0 && cloud.Dispersion.CoreDensity >= (float)_ammoSpec.EffectiveConcentration)
|
||||
{
|
||||
float exposureIncrement = inCloudTime / (drone.TypicalSpeed / 3.6f);
|
||||
drone.ExposureTime += exposureIncrement;
|
||||
var dmg = _damageModel.CalculateDamage(drone.TargetType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, exposureIncrement);
|
||||
drone.ApplyDamage(dmg);
|
||||
if (drone.Status == DroneStatus.Destroyed)
|
||||
{
|
||||
OnDroneDestroyed?.Invoke(drone);
|
||||
frameEvents.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁" });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 平台冷却
|
||||
foreach (var p in _platforms) p.Update(scaledDt);
|
||||
|
||||
|
||||
@ -18,17 +18,6 @@ namespace CounterDrone.Core.Tests
|
||||
Assert.True(dmg > 0.1f); // 高于基础速率(0.15 × 1.5 = 0.225)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InertGas_BelowThreshold_NoDamage()
|
||||
{
|
||||
var model = new InertGasDamageModel();
|
||||
|
||||
var dmg = model.CalculateDamage(TargetType.Piston, PowerType.Piston,
|
||||
AerosolType.InertGas, cloudDensity: 0.00001f, exposureTime: 2f, deltaTime: 1f);
|
||||
|
||||
Assert.Equal(0f, dmg);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InertGas_WrongAerosolType_NoDamage()
|
||||
{
|
||||
|
||||
@ -182,7 +182,7 @@ namespace CounterDrone.Core.Tests
|
||||
var task = _scenario.CreateTask("拦截活塞式无人机", "");
|
||||
_taskId = task.Id;
|
||||
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 3, WindDirection = (int)WindDirection.W });
|
||||
_scenario.SaveScene(_taskId, new CombatScene { WindSpeed = 0 });
|
||||
_scenario.SaveTarget(_taskId, new TargetConfig
|
||||
{
|
||||
GroupId = "default", TargetType = (int)TargetType.Piston,
|
||||
@ -212,6 +212,31 @@ namespace CounterDrone.Core.Tests
|
||||
var launched = eng.Events.Count(e => e.Type == SimEventType.MunitionLaunched);
|
||||
var clouds = eng.Events.Count(e => e.Type == SimEventType.CloudGenerated);
|
||||
|
||||
var dump = new System.Text.StringBuilder();
|
||||
|
||||
// 1. Planner 期望(来自 planner_targets.csv)
|
||||
dump.AppendLine("=== 1. Planner 云团期望 (见 planner_targets.csv) ===");
|
||||
|
||||
// 2. 弹药到位
|
||||
dump.AppendLine("=== 2. 弹药实际到位 ===");
|
||||
dump.AppendLine("id,arrivalTime,posX,posY,posZ,cloudId,radius,density,elapsed");
|
||||
foreach (var c in eng.Clouds)
|
||||
dump.AppendLine($"cloud,{c.CreatedAt:F3},{c.Dispersion.Center.X:F1},{c.Dispersion.Center.Y:F1},{c.Dispersion.Center.Z:F1},{c.Id},{c.Dispersion.EffectiveRadius:F2},{c.Dispersion.CoreDensity:F6},{c.Dispersion.Elapsed:F1}");
|
||||
|
||||
// 3. 云团最终状态
|
||||
dump.AppendLine("=== 3. 云团状态(仿真结束) ===");
|
||||
dump.AppendLine("cloudId,radius,density,elapsed,dissipated");
|
||||
foreach (var c in eng.Clouds)
|
||||
dump.AppendLine($"{c.Id},{c.Dispersion.EffectiveRadius:F2},{c.Dispersion.CoreDensity:F6},{c.Dispersion.Elapsed:F1},{c.IsDissipated}");
|
||||
|
||||
// 4. 无人机状态
|
||||
var drone = eng.Drones[0];
|
||||
dump.AppendLine("=== 4. 无人机 ===");
|
||||
dump.AppendLine($"status={drone.Status} hp={drone.Hp:F3} exposureTime={drone.ExposureTime:F3}");
|
||||
dump.AppendLine($"speed={drone.TypicalSpeed}km/h startPos=({drone.PosX:F1},{drone.PosY:F1},{drone.PosZ:F1})");
|
||||
|
||||
System.IO.File.WriteAllText(@"C:\Users\Tellme\Desktop\piston_analysis.txt", dump.ToString());
|
||||
|
||||
var times = eng.Events.Where(e => e.Type == SimEventType.MunitionLaunched).Select(e => e.OccurredAt).OrderBy(t => t).ToList();
|
||||
var timeInfo = $"times=[{times.First():F2}..{times.Last():F2}] span={times.Last()-times.First():F2}s";
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user