| | | 1 | | using System; |
| | | 2 | | using System.Collections.Generic; |
| | | 3 | | using System.Linq; |
| | | 4 | | using System.Text.Json; |
| | | 5 | | using CounterDrone.Core.Algorithms; |
| | | 6 | | using CounterDrone.Core.Models; |
| | | 7 | | using CounterDrone.Core.Services; |
| | | 8 | | using SQLite; |
| | | 9 | | |
| | | 10 | | namespace CounterDrone.Core.Simulation |
| | | 11 | | { |
| | | 12 | | /// <summary>发射计划中的单次事件</summary> |
| | | 13 | | public class FireEvent |
| | | 14 | | { |
| | | 15 | | public float FireTime; |
| | | 16 | | public int PlatformIndex; // 第几个发射平台(0-based) |
| | | 17 | | public float TargetX, TargetY, TargetZ; |
| | | 18 | | public float MuzzleVelocity; |
| | | 19 | | } |
| | | 20 | | |
| | | 21 | | /// <summary>仿真引擎 — 纯执行器,不包含任何决策逻辑</summary> |
| | | 22 | | public class SimulationEngine |
| | | 23 | | { |
| | | 24 | | private readonly IScenarioService _scenarioService; |
| | | 25 | | private readonly FrameDataStore _frameStore; |
| | | 26 | | private readonly IDamageModel _damageModel; |
| | | 27 | | private readonly IPathProvider _paths; |
| | | 28 | | |
| | 11142 | 29 | | public SimulationState State { get; private set; } = SimulationState.Idle; |
| | 22189 | 30 | | public int FrameIndex { get; private set; } |
| | 23559 | 31 | | public float SimulationTime { get; private set; } |
| | | 32 | | /// <summary>时间倍速:>1 加速仿真(仅测试或快速推演用)</summary> |
| | 5563 | 33 | | public float TimeScale { get; set; } = 1f; |
| | | 34 | | private float _tickInterval; |
| | | 35 | | |
| | 12 | 36 | | private List<DroneEntity> _drones = new(); |
| | 12 | 37 | | private List<PlatformEntity> _platforms = new(); |
| | 12 | 38 | | private List<MunitionEntity> _munitions = new(); |
| | 12 | 39 | | private List<CloudEntity> _clouds = new(); |
| | 12 | 40 | | private List<ControlZoneEntity> _zones = new(); |
| | | 41 | | |
| | 12 | 42 | | private CombatScene _scene = new(); |
| | 12 | 43 | | private AmmunitionSpec _ammoSpec = new(); |
| | 12 | 44 | | private int _tickRate = 20; |
| | | 45 | | |
| | | 46 | | // 发射计划:算法预计算,引擎按时间表执行 |
| | 12 | 47 | | private readonly List<FireEvent> _fireSchedule = new(); |
| | | 48 | | private int _nextFireIndex; |
| | | 49 | | |
| | 12 | 50 | | private readonly List<SimEvent> _allEvents = new(); |
| | 12 | 51 | | private SQLiteConnection _frameDb = null!; |
| | 12 | 52 | | private string _taskId = string.Empty; |
| | | 53 | | private int _entityCounter; |
| | | 54 | | |
| | 9 | 55 | | public IReadOnlyList<DroneEntity> Drones => _drones; |
| | 2 | 56 | | public IReadOnlyList<CloudEntity> Clouds => _clouds; |
| | 0 | 57 | | public IReadOnlyList<MunitionEntity> Munitions => _munitions; |
| | 8 | 58 | | public IReadOnlyList<SimEvent> Events => _allEvents; |
| | | 59 | | |
| | 12 | 60 | | public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore, |
| | 12 | 61 | | IDamageModel damageModel, IPathProvider paths) |
| | 12 | 62 | | { |
| | 12 | 63 | | _scenarioService = scenarioService; |
| | 12 | 64 | | _frameStore = frameStore; |
| | 12 | 65 | | _damageModel = damageModel; |
| | 12 | 66 | | _paths = paths; |
| | 12 | 67 | | } |
| | | 68 | | |
| | | 69 | | /// <summary>设置发射计划(来自推荐算法),在 Initialize 之前调用</summary> |
| | | 70 | | public void SetFireSchedule(List<FireEvent> schedule) |
| | 2 | 71 | | { |
| | 2 | 72 | | _fireSchedule.Clear(); |
| | 13 | 73 | | _fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime)); |
| | 2 | 74 | | _nextFireIndex = 0; |
| | 2 | 75 | | } |
| | | 76 | | |
| | | 77 | | public void Initialize(string taskId) |
| | 12 | 78 | | { |
| | 12 | 79 | | _taskId = taskId; |
| | 12 | 80 | | var config = _scenarioService.GetTaskDetail(taskId); |
| | 12 | 81 | | if (config == null) throw new InvalidOperationException("Task not found"); |
| | | 82 | | |
| | 12 | 83 | | _scene = config.Scene; |
| | 12 | 84 | | _tickRate = config.Task.TickRate; |
| | 12 | 85 | | _tickInterval = 1.0f / _tickRate; |
| | | 86 | | |
| | 12 | 87 | | using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath())) |
| | 12 | 88 | | { |
| | 12 | 89 | | _ammoSpec = ammoDb.Table<AmmunitionSpec>() |
| | 12 | 90 | | .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType) |
| | 12 | 91 | | ?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, DispersionRateBase = 5, MaxRadius = |
| | 12 | 92 | | } |
| | | 93 | | |
| | | 94 | | // 无人机 |
| | 12 | 95 | | _drones.Clear(); |
| | 60 | 96 | | foreach (var target in config.Targets) |
| | 12 | 97 | | { |
| | 12 | 98 | | var mode = (FormationMode)config.Route.FormationMode; |
| | 12 | 99 | | var spacing = (float)config.Route.FormationSpacing; |
| | 48 | 100 | | for (int i = 0; i < target.Quantity; i++) |
| | 12 | 101 | | _drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.GroupId, |
| | 12 | 102 | | target, config.Waypoints, i, spacing, mode)); |
| | 12 | 103 | | } |
| | | 104 | | |
| | | 105 | | // 装备 |
| | 12 | 106 | | _platforms.Clear(); |
| | 72 | 107 | | foreach (var equip in config.Equipment) |
| | 18 | 108 | | { |
| | 18 | 109 | | if (equip.EquipmentRole != (int)EquipmentRole.Detection) |
| | 16 | 110 | | { |
| | 64 | 111 | | for (int i = 0; i < equip.Quantity; i++) |
| | 16 | 112 | | _platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip)); |
| | 16 | 113 | | } |
| | 18 | 114 | | } |
| | | 115 | | |
| | | 116 | | // 管控区域 |
| | 12 | 117 | | _zones.Clear(); |
| | 38 | 118 | | foreach (var z in config.ControlZones) |
| | 1 | 119 | | _zones.Add(new ControlZoneEntity(z.Id, z.Name, z.VerticesJson, |
| | 1 | 120 | | (float)z.MinAltitude, (float)z.MaxAltitude)); |
| | | 121 | | |
| | 12 | 122 | | _munitions.Clear(); |
| | 12 | 123 | | _clouds.Clear(); |
| | 12 | 124 | | _allEvents.Clear(); |
| | 12 | 125 | | FrameIndex = 0; |
| | 12 | 126 | | SimulationTime = 0; |
| | 12 | 127 | | _entityCounter = 0; |
| | 12 | 128 | | _nextFireIndex = 0; |
| | | 129 | | |
| | 12 | 130 | | _frameDb = _frameStore.CreateOrOpen(_taskId); |
| | 12 | 131 | | State = SimulationState.Running; |
| | 12 | 132 | | } |
| | | 133 | | |
| | | 134 | | public SimulationFrameResult Tick(float deltaTime) |
| | 5545 | 135 | | { |
| | 5545 | 136 | | if (State != SimulationState.Running) |
| | 1 | 137 | | return new SimulationFrameResult { State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTi |
| | | 138 | | |
| | 5544 | 139 | | var scaledDt = deltaTime * TimeScale; |
| | 5544 | 140 | | SimulationTime += scaledDt; |
| | 5544 | 141 | | FrameIndex++; |
| | 5544 | 142 | | var frameEvents = new List<SimEvent>(); |
| | | 143 | | |
| | | 144 | | // 1. 按发射计划执行 |
| | 5555 | 145 | | while (_nextFireIndex < _fireSchedule.Count && |
| | 5555 | 146 | | _fireSchedule[_nextFireIndex].FireTime <= SimulationTime) |
| | 11 | 147 | | { |
| | 11 | 148 | | var fe = _fireSchedule[_nextFireIndex++]; |
| | 11 | 149 | | var platform = _platforms.Count > fe.PlatformIndex |
| | 11 | 150 | | ? _platforms[fe.PlatformIndex] : null; |
| | 11 | 151 | | if (platform != null && platform.Ready) |
| | 11 | 152 | | { |
| | 11 | 153 | | platform.Fire(); |
| | 11 | 154 | | var m = new MunitionEntity($"mun_{++_entityCounter}", |
| | 11 | 155 | | platform.PlatformType ?? Models.PlatformType.GroundBased, |
| | 11 | 156 | | platform.AerosolType ?? Models.AerosolType.InertGas, |
| | 11 | 157 | | platform.PosX, platform.PosY, platform.PosZ, |
| | 11 | 158 | | fe.TargetX, fe.TargetY, fe.TargetZ, |
| | 11 | 159 | | fe.MuzzleVelocity, |
| | 11 | 160 | | (float)_ammoSpec.InitialRadius > 0 ? 300f : 300f); |
| | 11 | 161 | | _munitions.Add(m); |
| | 11 | 162 | | frameEvents.Add(new SimEvent |
| | 11 | 163 | | { |
| | 11 | 164 | | Type = SimEventType.MunitionLaunched, OccurredAt = SimulationTime, |
| | 11 | 165 | | SourceId = platform.Id, Description = $"计划发射 {m.Id}", |
| | 11 | 166 | | }); |
| | 11 | 167 | | } |
| | 11 | 168 | | } |
| | | 169 | | |
| | | 170 | | // 2. 弹药飞行 → 云团生成 |
| | 17710 | 171 | | foreach (var m in _munitions.ToList()) |
| | 539 | 172 | | { |
| | 539 | 173 | | m.Update(scaledDt); |
| | 539 | 174 | | if (m.HasArrived) |
| | 11 | 175 | | { |
| | 11 | 176 | | var disp = AlgorithmFactory.Create<ICloudDispersionModel>(); |
| | 11 | 177 | | disp.Initialize(_ammoSpec, _scene, |
| | 11 | 178 | | new Algorithms.Vector3(m.TargetX, m.TargetY, m.TargetZ), SimulationTime); |
| | 11 | 179 | | _clouds.Add(new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, SimulationTime)); |
| | 11 | 180 | | _munitions.Remove(m); |
| | 11 | 181 | | frameEvents.Add(new SimEvent |
| | 11 | 182 | | { |
| | 11 | 183 | | Type = SimEventType.CloudGenerated, OccurredAt = SimulationTime, |
| | 11 | 184 | | Description = $"云团生成", |
| | 11 | 185 | | }); |
| | 11 | 186 | | } |
| | 539 | 187 | | } |
| | | 188 | | |
| | | 189 | | // 3. 云团演化 |
| | 17360 | 190 | | foreach (var c in _clouds.ToList()) |
| | 364 | 191 | | { |
| | 364 | 192 | | c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection); |
| | 364 | 193 | | if (c.IsDissipated) _clouds.Remove(c); |
| | 364 | 194 | | } |
| | | 195 | | |
| | | 196 | | // 4. 毁伤判定 |
| | 33264 | 197 | | foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) |
| | 5544 | 198 | | { |
| | 17360 | 199 | | foreach (var cloud in _clouds) |
| | 364 | 200 | | { |
| | 364 | 201 | | if (cloud.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ)) |
| | 25 | 202 | | { |
| | 25 | 203 | | drone.ExposureTime += scaledDt; |
| | 25 | 204 | | var dmg = _damageModel.CalculateDamage( |
| | 25 | 205 | | drone.TargetType, drone.PowerType, cloud.AerosolType, |
| | 25 | 206 | | cloud.Dispersion.CoreDensity, drone.ExposureTime, scaledDt); |
| | 25 | 207 | | drone.ApplyDamage(dmg); |
| | 25 | 208 | | if (drone.Status == DroneStatus.Destroyed) |
| | 3 | 209 | | frameEvents.Add(new SimEvent |
| | 3 | 210 | | { |
| | 3 | 211 | | Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, |
| | 3 | 212 | | TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁", |
| | 3 | 213 | | }); |
| | 25 | 214 | | } |
| | 364 | 215 | | } |
| | 5544 | 216 | | } |
| | | 217 | | |
| | | 218 | | // 5. 无人机移动 |
| | 33260 | 219 | | foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) |
| | 5542 | 220 | | { |
| | 5542 | 221 | | drone.Update(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection); |
| | 5542 | 222 | | if (drone.Status == DroneStatus.ReachedTarget) |
| | 4 | 223 | | frameEvents.Add(new SimEvent |
| | 4 | 224 | | { |
| | 4 | 225 | | Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, |
| | 4 | 226 | | SourceId = drone.Id, Description = "到达攻击目标", |
| | 4 | 227 | | }); |
| | 5542 | 228 | | } |
| | | 229 | | |
| | | 230 | | // 6. 平台冷却 |
| | 40164 | 231 | | foreach (var p in _platforms) p.Update(scaledDt); |
| | | 232 | | |
| | | 233 | | // 7. 管控区域 |
| | 33252 | 234 | | foreach (var drone in _drones.Where(d => d.Status == DroneStatus.Flying)) |
| | 5538 | 235 | | { |
| | 16758 | 236 | | foreach (var zone in _zones) |
| | 72 | 237 | | { |
| | 72 | 238 | | if (zone.ContainsPoint(drone.PosX, drone.PosY, drone.PosZ)) |
| | 1 | 239 | | { |
| | 1 | 240 | | drone.MarkZoneIntruded(); |
| | 1 | 241 | | frameEvents.Add(new SimEvent |
| | 1 | 242 | | { |
| | 1 | 243 | | Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime, |
| | 1 | 244 | | SourceId = drone.Id, TargetId = zone.Id, Description = $"侵入 {zone.Name}", |
| | 1 | 245 | | }); |
| | 1 | 246 | | } |
| | 72 | 247 | | } |
| | 5538 | 248 | | } |
| | | 249 | | |
| | | 250 | | // 8. 结束判定 |
| | 11088 | 251 | | if (_drones.All(d => d.Status != DroneStatus.Flying)) |
| | 7 | 252 | | { |
| | 7 | 253 | | State = SimulationState.Completed; |
| | 7 | 254 | | frameEvents.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime }); |
| | 7 | 255 | | } |
| | | 256 | | |
| | 5544 | 257 | | _allEvents.AddRange(frameEvents); |
| | | 258 | | |
| | | 259 | | // 录制帧 |
| | 5544 | 260 | | var snapshots = CollectSnapshots(); |
| | 5544 | 261 | | _frameStore.WriteFrame(_frameDb, _taskId, FrameIndex, SimulationTime, snapshots); |
| | 5544 | 262 | | _frameDb.Commit(); |
| | | 263 | | |
| | 5544 | 264 | | return new SimulationFrameResult |
| | 5544 | 265 | | { |
| | 5544 | 266 | | EntitySnapshots = snapshots, NewEvents = frameEvents, |
| | 5544 | 267 | | State = State, FrameIndex = FrameIndex, SimulationTime = SimulationTime, |
| | 5544 | 268 | | }; |
| | 5545 | 269 | | } |
| | | 270 | | |
| | 4 | 271 | | public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; } |
| | 4 | 272 | | public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; } |
| | 52 | 273 | | public void Stop() { State = SimulationState.Stopped; _frameDb?.Close(); } |
| | | 274 | | |
| | | 275 | | private List<EntitySnapshot> CollectSnapshots() |
| | 5544 | 276 | | { |
| | 5544 | 277 | | var list = new List<EntitySnapshot>(); |
| | 27720 | 278 | | foreach (var d in _drones) |
| | 5544 | 279 | | { |
| | 5544 | 280 | | var stage = _damageModel.GetDamageStage(1 - d.Hp); |
| | 5544 | 281 | | list.Add(new EntitySnapshot |
| | 5544 | 282 | | { |
| | 5544 | 283 | | EntityId = d.Id, EntityType = Models.EntityType.Drone, |
| | 5544 | 284 | | PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, |
| | 5544 | 285 | | StateData = JsonSerializer.Serialize(new { damageStage = (int)stage, hp = d.Hp }), |
| | 5544 | 286 | | }); |
| | 5544 | 287 | | } |
| | 17360 | 288 | | foreach (var c in _clouds) |
| | 364 | 289 | | list.Add(new EntitySnapshot |
| | 364 | 290 | | { |
| | 364 | 291 | | EntityId = c.Id, EntityType = Models.EntityType.Cloud, |
| | 364 | 292 | | PosX = c.Dispersion.Center.X, PosY = c.Dispersion.Center.Y, PosZ = c.Dispersion.Center.Z, |
| | 364 | 293 | | StateData = JsonSerializer.Serialize(new { radius = c.Dispersion.Radius, opacity = c.Dispersion.Part |
| | 364 | 294 | | }); |
| | 5544 | 295 | | return list; |
| | 5544 | 296 | | } |
| | | 297 | | } |
| | | 298 | | } |