diff --git a/src/CounterDrone.Core/Simulation/ControlZone.cs b/src/CounterDrone.Core/Simulation/ControlZone.cs
index d8e4c7a..b7188cb 100644
--- a/src/CounterDrone.Core/Simulation/ControlZone.cs
+++ b/src/CounterDrone.Core/Simulation/ControlZone.cs
@@ -13,6 +13,9 @@ namespace CounterDrone.Core.Simulation
public float MinAltitude { get; }
public float MaxAltitude { get; }
+ /// 预缓存的水平投影顶点 (X,Z),ContainsPoint 每帧高频调用时避免重复分配数组
+ private readonly (float X, float Z)[] _vertices2D;
+
public struct Vector3 { public float X { get; set; } public float Y { get; set; } public float Z { get; set; } }
public ControlZoneEntity(string id, string name, string verticesJson, float minAlt, float maxAlt)
@@ -22,17 +25,15 @@ namespace CounterDrone.Core.Simulation
MinAltitude = minAlt;
MaxAltitude = maxAlt;
Vertices = JsonSerializer.Deserialize>(verticesJson) ?? new();
+ _vertices2D = new (float X, float Z)[Vertices.Count];
+ for (int i = 0; i < Vertices.Count; i++)
+ _vertices2D[i] = (Vertices[i].X, Vertices[i].Z);
}
public bool ContainsPoint(float x, float y, float z)
{
if (y < MinAltitude || y > MaxAltitude) return false;
-
- var vertices2D = new (float X, float Z)[Vertices.Count];
- for (int i = 0; i < Vertices.Count; i++)
- vertices2D[i] = (Vertices[i].X, Vertices[i].Z);
-
- return Kinematics.PointInPolygon(x, z, vertices2D);
+ return Kinematics.PointInPolygon(x, z, _vertices2D);
}
}
}
diff --git a/src/CounterDrone.Core/Simulation/DroneEntity.cs b/src/CounterDrone.Core/Simulation/DroneEntity.cs
index bf1b862..de95616 100644
--- a/src/CounterDrone.Core/Simulation/DroneEntity.cs
+++ b/src/CounterDrone.Core/Simulation/DroneEntity.cs
@@ -26,6 +26,13 @@ namespace CounterDrone.Core.Simulation
public float PrevZ { get; private set; }
/// 沿航路已飞行弧长(米),由 RouteGeometry 驱动
private float _traveledArc;
+ /// 航路总弧长(米),构造时一次性算好(航路静态,无需每帧重算)
+ private readonly float _totalArc;
+ /// 每段段长(米),_segLen[i] = Route[i]→Route[i+1]
+ private readonly float[] _segLen;
+ /// 每段终点累积弧长(米),_cumArc[i] = sum(_segLen[0..i])。
+ /// 查找弧长 s 所在段:最大的 i 使 _cumArc[i] >= s。
+ private readonly float[] _cumArc;
public DroneEntity(string id, string waveId, TargetConfig config, List route,
int formationIndex, float lateralSpacing, int longitudinalIndex, float longitudinalSpacing, FormationMode mode)
@@ -74,6 +81,22 @@ namespace CounterDrone.Core.Simulation
PosY = (float)offsetRoute[0].PosY;
PosZ = (float)offsetRoute[0].PosZ;
}
+
+ // 预计算航路几何缓存:航路在构造后静态不变,避免每帧重算总弧长与各段段长
+ int segCount = Math.Max(0, offsetRoute.Count - 1);
+ _segLen = new float[segCount];
+ _cumArc = new float[segCount];
+ float accum = 0f;
+ for (int i = 0; i < segCount; i++)
+ {
+ float sl = Kinematics.Distance3D(
+ (float)offsetRoute[i].PosX, (float)offsetRoute[i].PosY, (float)offsetRoute[i].PosZ,
+ (float)offsetRoute[i + 1].PosX, (float)offsetRoute[i + 1].PosY, (float)offsetRoute[i + 1].PosZ);
+ _segLen[i] = sl;
+ accum += sl;
+ _cumArc[i] = accum;
+ }
+ _totalArc = accum;
}
public void SavePreviousPosition()
@@ -89,8 +112,8 @@ namespace CounterDrone.Core.Simulation
// 运动模型统一在 RouteGeometry:弧长推进 + 位置查询。
// 无人机严格按航路匀速飞行。无人机不受风偏影响(真实无人机有飞控修正航向),
// 但云团受风偏影响(见 SimulationEngine 毁伤判定的云团参考系修正)。
- float totalLen = RouteGeometry.TotalLength(Route);
- if (totalLen <= 0f)
+ // 航路几何在构造时已缓存为 _totalArc / _segLen / _cumArc,此处不再重算。
+ if (_totalArc <= 0f)
{
Status = DroneStatus.ReachedTarget;
return;
@@ -99,7 +122,7 @@ namespace CounterDrone.Core.Simulation
float speedMs = TypicalSpeed / 3.6f;
_traveledArc += speedMs * deltaTime;
- if (_traveledArc >= totalLen)
+ if (_traveledArc >= _totalArc)
{
var end = Route[^1];
PosX = (float)end.PosX;
@@ -109,10 +132,19 @@ namespace CounterDrone.Core.Simulation
return;
}
- var (x, y, z) = RouteGeometry.PositionAt(Route, _traveledArc);
- PosX = x;
- PosY = y;
- PosZ = z;
+ // 在缓存的累积弧长数组中定位所在段(线性扫描:航点数通常 ≤10)
+ int segIdx = 0;
+ while (segIdx < _cumArc.Length - 1 && _traveledArc > _cumArc[segIdx])
+ segIdx++;
+
+ float segStartArc = segIdx > 0 ? _cumArc[segIdx - 1] : 0f;
+ float segLen = _segLen[segIdx];
+ float t = segLen > 0.0001f ? (_traveledArc - segStartArc) / segLen : 0f;
+ var a = Route[segIdx];
+ var b = Route[segIdx + 1];
+ PosX = (float)(a.PosX + (b.PosX - a.PosX) * t);
+ PosY = (float)(a.PosY + (b.PosY - a.PosY) * t);
+ PosZ = (float)(a.PosZ + (b.PosZ - a.PosZ) * t);
}
public void ApplyDamage(float damage)
diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.deps.json.meta b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.deps.json.meta
new file mode 100644
index 0000000..2fc0548
--- /dev/null
+++ b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.deps.json.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 73afbe638ef585a4ba07c649d294a417
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll
index e63c440..f05fc78 100644
Binary files a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll and b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.dll differ
diff --git a/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb.meta b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb.meta
new file mode 100644
index 0000000..0c525d2
--- /dev/null
+++ b/src/Unity/Assets/Plugins/CounterDrone.Core/CounterDrone.Core.pdb.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 59b41f7ef22cc3747990cebf8bdb28b3
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs b/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
index 629bbe1..526ae0c 100644
--- a/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/DispersionModelTests.cs
@@ -54,8 +54,14 @@ namespace CounterDrone.Core.Tests
public void Dissipates_AfterMaxDuration()
{
var m = new GaussianPuffDispersion();
- var a = Ammo();
- a.MaxDuration = 2;
+ var a = new AmmunitionSpec
+ {
+ Id = "test", AerosolType = (int)AerosolType.InertGas,
+ InitialRadius = 3.8, CoreDensity = 1.5, EdgeDensity = 0.1,
+ InitialTemperature = 1800, BuoyancyFactor = 0.3,
+ EffectiveConcentration = 0.0001, MaxRadius = 100, MaxDuration = 2,
+ SourceStrength = 10, BurstChargeKg = 1.5, TurbulentExpansionK = 3,
+ };
m.Initialize(a, new CombatScene { WindSpeed = 0 }, new Algorithms.Vector3(0, 0, 0), 0f);
m.Tick(3f, 0f, WindDirection.N);
Assert.True(m.IsDissipated);
diff --git a/test/unit/CounterDrone.Core.Tests/DroneEntityTests.cs b/test/unit/CounterDrone.Core.Tests/DroneEntityTests.cs
index 9f19c51..f867f3a 100644
--- a/test/unit/CounterDrone.Core.Tests/DroneEntityTests.cs
+++ b/test/unit/CounterDrone.Core.Tests/DroneEntityTests.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using CounterDrone.Core.Algorithms;
using CounterDrone.Core.Models;
using CounterDrone.Core.Simulation;
using Xunit;
@@ -163,5 +164,38 @@ namespace CounterDrone.Core.Tests
Assert.Equal(100f, drone.PosZ, 1);
Assert.Equal(DroneStatus.ReachedTarget, drone.Status);
}
+
+ // ═══════════════════════════════════════
+ // 弧长缓存一致性:多段航路上非整段边界处的位置必须与 RouteGeometry.PositionAt 精确一致
+ // 防止 Update() 内段长缓存引入的数值偏差
+ // ═══════════════════════════════════════
+
+ [Fact]
+ public void MultiWaypoint_SubSegmentPositions_MatchGeometry()
+ {
+ // 不规则三段折线,段长各异(50, 120, 80),避免整段边界凑巧对齐
+ var route = new List
+ {
+ new() { PosX = 0, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 },
+ new() { PosX = 50, PosY = 300, PosZ = 0, Altitude = 300, Speed = 60 }, // 段1: 50m
+ new() { PosX = 50, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 }, // 段2: 120m
+ new() { PosX = 130, PosY = 300, PosZ = 120, Altitude = 300, Speed = 60 }, // 段3: 80m
+ };
+ const float speed = 60f;
+ float speedMs = speed / 3.6f;
+
+ // 取若干非整段边界的弧长采样点,验证每一步位置 = RouteGeometry.PositionAt
+ float[] sampleArcs = { 17f, 50f, 73f, 170f, 200f, 249f };
+ foreach (var arc in sampleArcs)
+ {
+ var drone = new DroneEntity("d", "g", CreateConfig(speed), route,
+ 0, 0, 0, 0, FormationMode.Single);
+ drone.Update(arc / speedMs);
+ var (ex, ey, ez) = RouteGeometry.PositionAt(route, arc);
+ Assert.True(System.Math.Abs(drone.PosX - ex) < 0.5f, $"arc={arc}: X {drone.PosX} vs {ex}");
+ Assert.True(System.Math.Abs(drone.PosY - ey) < 0.5f, $"arc={arc}: Y {drone.PosY} vs {ey}");
+ Assert.True(System.Math.Abs(drone.PosZ - ez) < 0.5f, $"arc={arc}: Z {drone.PosZ} vs {ez}");
+ }
+ }
}
}