using System; using System.Collections.Generic; using System.Linq; using CounterDrone.Core.Algorithms; using CounterDrone.Core.Models; using CounterDrone.Core.Services; using SQLite; namespace CounterDrone.Core.Simulation { /// 仿真引擎 — 纯执行器,接收发射计划按时间表执行 public class SimulationEngine { private readonly IScenarioService _scenarioService; private readonly FrameDataStore _frameStore; private readonly IDamageModel _damageModel; private readonly IPathProvider _paths; public SimulationState State { get; private set; } = SimulationState.Idle; public int FrameIndex { get; private set; } public float SimulationTime { get; private set; } public float TimeScale { get; set; } = 1f; /// 是否录制逐帧数据(回放用)。默认开启。 public bool RecordFrames { get; set; } = true; private float _tickInterval; public IReadOnlyList Drones => _drones; public IReadOnlyList Clouds => _clouds; public IReadOnlyList Platforms => _platforms; public IReadOnlyList Munitions => _munitions; public IReadOnlyList DetectionEntities => _detectionEntities; public IReadOnlyList Events => _allEvents; public event Action? OnMunitionLaunched; public event Action? OnCloudGenerated; public event Action? OnDroneDestroyed; public event Action? OnDroneReachedTarget; public event Action? OnZoneIntruded; public event Action? OnTargetDetected; public event Action? OnSimulationEnded; private List _drones = new(); private List _platforms = new(); private List _detectionEntities = new(); private List _munitions = new(); private List _clouds = new(); private List _zones = new(); private CombatScene _scene = new(); private float _disperseHeight; private AmmunitionSpec _ammoSpec = new(); private int _tickRate = 20; private readonly List _fireSchedule = new(); private int _nextFireIndex; private bool _anyThreatDetected; private float _detectionTime; private readonly List _allEvents = new(); private string _scenarioId = string.Empty; private readonly IDefensePlanner _planner; private int _entityCounter; // ── GC 优化:预分配复用池 ── private readonly List _snapshotPool = new(); private readonly List _frameEventPool = new(); private readonly SimulationFrameResult _frameResultPool = new(); private readonly List _removalIndices = new(); private readonly List _flyingDrones = new(); public SimulationEngine(IScenarioService scenarioService, FrameDataStore frameStore, IDamageModel damageModel, IPathProvider paths, IDefensePlanner planner) { _scenarioService = scenarioService; _frameStore = frameStore; _damageModel = damageModel; _paths = paths; _planner = planner; } public void SetFireSchedule(List schedule) { _fireSchedule.Clear(); _fireSchedule.AddRange(schedule.OrderBy(f => f.FireTime)); _nextFireIndex = 0; } public void Initialize(string scenarioId) { _scenarioId = scenarioId; var config = _scenarioService.GetScenarioDetail(scenarioId); if (config == null) throw new InvalidOperationException("scenario not found"); _scene = config.Scene; _disperseHeight = (float)config.Cloud.DisperseHeight; _tickRate = config.Info.TickRate; _tickInterval = 1.0f / _tickRate; using (var ammoDb = new SQLiteConnection(_paths.GetMainDbPath())) { _ammoSpec = ammoDb.Table() .FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType); } if (_ammoSpec == null || _ammoSpec.Id == null) _ammoSpec = DefaultData.Load(_paths).Ammunition .First(a => a.AerosolType == config.Cloud.AerosolType); _drones.Clear(); foreach (var target in config.Targets) { var route = config.Routes.FirstOrDefault(r => r.WaveId == target.WaveId); var wps = config.WaypointGroups.GetValueOrDefault(target.WaveId, new List()); if (route == null || wps.Count == 0) continue; var mode = (FormationMode)route.FormationMode; var lateralSpacing = (float)route.LateralSpacing; var longSpacing = (float)route.LongitudinalSpacing; int latCount = route.LateralCount ?? target.Quantity; int longCount = route.LongitudinalCount ?? 1; for (int i = 0; i < target.Quantity; i++) { int latIdx = i % latCount; int longIdx = i / latCount; _drones.Add(new DroneEntity($"drone_{++_entityCounter}", target.WaveId, target, wps, latIdx, lateralSpacing, longIdx, longSpacing, mode, route.LateralAxis, route.LongitudinalAxis)); } } // 平台 ID 从 1 开始,确保与报告映射一致 _entityCounter = 0; _platforms.Clear(); foreach (var equip in config.Equipment) if (equip.EquipmentRole != (int)EquipmentRole.Detection) for (int i = 0; i < equip.Quantity; i++) { int gunCount = equip.GunCount ?? 1; int chPerGun = equip.ChannelsPerGun ?? 1; for (int g = 0; g < gunCount; g++) for (int ch = 0; ch < chPerGun; ch++) _platforms.Add(new PlatformEntity($"plat_{++_entityCounter}", equip)); } _zones.Clear(); foreach (var z in config.ControlZones) _zones.Add(new ControlZoneEntity(z.Id, z.Name, z.VerticesJson, (float)z.MinAltitude, (float)z.MaxAltitude)); // 探测设备运行时实体 _detectionEntities.Clear(); var detectionSrcs = BuildDetectionSources(config); int detIdx = 0; foreach (var src in detectionSrcs) _detectionEntities.Add(new DetectionEntity($"det_{++detIdx}", src)); _munitions.Clear(); _clouds.Clear(); _allEvents.Clear(); FrameIndex = 0; SimulationTime = 0; _entityCounter = 0; _nextFireIndex = 0; // 自动从 Planner 生成发射计划 if (_fireSchedule.Count == 0) { var fireUnits = BuildFireUnits(config); var threats = BuildDroneWaves(config); if (fireUnits.Count > 0 && threats.Count > 0) { var detectionSources = BuildDetectionSources(config); var result = _planner.Plan(fireUnits, threats, _scene, detectionSources); SetFireSchedule(result.Best.MergedSchedule); if (_fireSchedule.Count == 0) _allEvents.Add(new SimEvent { Type = SimEventType.PlanningFailed, OccurredAt = 0, Description = result.Best.Summary, }); } } if (RecordFrames) _frameStore.BeginRecording(_scenarioId); _anyThreatDetected = false; State = SimulationState.Running; } public SimulationFrameResult Tick(float deltaTime) { if (State != SimulationState.Running) { _frameResultPool.State = State; _frameResultPool.FrameIndex = FrameIndex; _frameResultPool.SimulationTime = SimulationTime; _frameResultPool.EntitySnapshots = null!; _frameResultPool.NewEvents = null!; return _frameResultPool; } var scaledDt = deltaTime * TimeScale; SimulationTime += scaledDt; FrameIndex++; _frameEventPool.Clear(); // 1. 按发射计划执行(以探测时刻为时间起点) if (_anyThreatDetected) { while (_nextFireIndex < _fireSchedule.Count && _fireSchedule[_nextFireIndex].FireTime + _detectionTime <= SimulationTime) { var fe = _fireSchedule[_nextFireIndex++]; float launchTime = fe.FireTime + _detectionTime; var platform = _platforms.Count > fe.PlatformIndex ? _platforms[fe.PlatformIndex] : null; if (platform == null) continue; if (platform.PlatformType == Models.PlatformType.AirBased && platform.Ready) { // 空基从当前位置直接发射(与地基相同) platform.Release(); var m = new MunitionEntity($"mun_{++_entityCounter}", Models.PlatformType.GroundBased, platform.AerosolType ?? Models.AerosolType.InertGas, platform.PosX, platform.PosY, platform.PosZ, fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, launchTime, fe.LaunchAngle, fe.FlightDuration); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); _frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"空基发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" }); } else if (platform.Ready) { platform.Release(); var m = new MunitionEntity($"mun_{++_entityCounter}", platform.PlatformType ?? Models.PlatformType.GroundBased, platform.AerosolType ?? Models.AerosolType.InertGas, platform.PosX, platform.PosY, platform.PosZ, fe.TargetX, fe.TargetY, fe.TargetZ, fe.MuzzleVelocity, _disperseHeight, launchTime, fe.LaunchAngle, fe.FlightDuration); _munitions.Add(m); OnMunitionLaunched?.Invoke(m); _frameEventPool.Add(new SimEvent { Type = SimEventType.MunitionLaunched, OccurredAt = launchTime, SourceId = platform.Id, Description = $"计划发射 {m.Id} @({platform.PosX:F0},{platform.PosY:F0},{platform.PosZ:F0})→({fe.TargetX:F0},{fe.TargetY:F0},{fe.TargetZ:F0})" }); } } } // 2. 弹药飞行 → 云团生成(手动遍历+移除索引,避免 ToList()) _removalIndices.Clear(); for (int i = 0; i < _munitions.Count; i++) { var m = _munitions[i]; m.Update(scaledDt); if (m.HasArrived) { var disp = AlgorithmFactory.Create(); disp.Initialize(_ammoSpec, _scene, new Algorithms.Vector3(m.PosX, m.PosY, m.PosZ), m.ArrivalTime); var cloud = new CloudEntity($"cloud_{++_entityCounter}", m.AerosolType, disp, m.ArrivalTime); _clouds.Add(cloud); OnCloudGenerated?.Invoke(cloud); _frameEventPool.Add(new SimEvent { Type = SimEventType.CloudGenerated, OccurredAt = m.ArrivalTime, Description = $"云团生成 @({m.PosX:F0},{m.PosY:F0},{m.PosZ:F0})" }); _removalIndices.Add(i); } } for (int i = _removalIndices.Count - 1; i >= 0; i--) _munitions.RemoveAt(_removalIndices[i]); // 3. 云团演化(手动遍历+移除索引) _removalIndices.Clear(); for (int i = 0; i < _clouds.Count; i++) { var c = _clouds[i]; c.Tick(scaledDt, (float)_scene.WindSpeed, (WindDirection)_scene.WindDirection); if (c.IsDissipated) _removalIndices.Add(i); } for (int i = _removalIndices.Count - 1; i >= 0; i--) _clouds.RemoveAt(_removalIndices[i]); // 4. 无人机:缓存飞行状态列表,避免多次 Where _flyingDrones.Clear(); for (int i = 0; i < _drones.Count; i++) if (_drones[i].Status == DroneStatus.Flying) _flyingDrones.Add(_drones[i]); for (int i = 0; i < _flyingDrones.Count; i++) _flyingDrones[i].SavePreviousPosition(); for (int i = 0; i < _flyingDrones.Count; i++) { _flyingDrones[i].Update(scaledDt); if (_flyingDrones[i].Status == DroneStatus.ReachedTarget) { OnDroneReachedTarget?.Invoke(_flyingDrones[i]); _frameEventPool.Add(new SimEvent { Type = SimEventType.DroneReachedTarget, OccurredAt = SimulationTime, SourceId = _flyingDrones[i].Id, Description = "到达攻击目标" }); } } // 5. 实时探测扫描(决策 1/2:离开回退、融合取最早/同刻取精度高) if (_detectionEntities.Count > 0) { float visibility = (float)_scene.Visibility; for (int di = 0; di < _flyingDrones.Count; di++) { var drone = _flyingDrones[di]; DetectionEntity? bestDetector = null; float bestAccuracy = float.MaxValue; for (int ei = 0; ei < _detectionEntities.Count; ei++) { var det = _detectionEntities[ei]; bool inRange = det.IsInRange(drone.PosX, drone.PosY, drone.PosZ, visibility, out _); bool? stateChange = det.UpdateState(drone.Id, inRange); if (stateChange == true) { // 决策 2:首次发现,同 tick 取精度最高(Accuracy 最小) if (bestDetector == null || det.Source.Accuracy < bestAccuracy) { bestDetector = det; bestAccuracy = det.Source.Accuracy; } } } if (bestDetector != null) { if (!_anyThreatDetected) { _anyThreatDetected = true; _detectionTime = SimulationTime; } OnTargetDetected?.Invoke(drone, bestDetector); _frameEventPool.Add(new SimEvent { Type = SimEventType.TargetDetected, OccurredAt = SimulationTime, SourceId = bestDetector.Id, TargetId = drone.Id, Description = $"探测设备 {bestDetector.Id} 发现 drone {drone.Id} @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})", }); } } } // 6. 毁伤判定:积分路径段在云内的时间(快速排斥:先检查包围盒) var (wvx, wvy, wvz) = Kinematics.WindToVector((WindDirection)_scene.WindDirection, (float)_scene.WindSpeed); float cloudDx = wvx * scaledDt, cloudDy = wvy * scaledDt, cloudDz = wvz * scaledDt; float effectiveConcentration = (float)_ammoSpec.EffectiveConcentration; for (int di = 0; di < _flyingDrones.Count; di++) { var drone = _flyingDrones[di]; for (int ci = 0; ci < _clouds.Count; ci++) { var cloud = _clouds[ci]; if (cloud.Dispersion.CoreDensity < effectiveConcentration) continue; // 快速排斥:检查无人机与云团中心的距离是否在 2×有效半径内 float effR = cloud.Dispersion.EffectiveRadius; float ccx = cloud.Dispersion.Center.X, ccy = cloud.Dispersion.Center.Y, ccz = cloud.Dispersion.Center.Z; float dx = drone.PosX - ccx, dy = drone.PosY - ccy, dz = drone.PosZ - ccz; float distSq = dx * dx + dy * dy + dz * dz; if (distSq > effR * effR * 4) continue; // 距离 > 2R,不可能穿云 float inCloudDistance = DamageAssessment.PathInSphere( drone.PrevX - (ccx - cloudDx), drone.PrevY - (ccy - cloudDy), drone.PrevZ - (ccz - cloudDz), drone.PosX - ccx, drone.PosY - ccy, drone.PosZ - ccz, 0f, 0f, 0f, effR); if (inCloudDistance > 0) { float exposureIncrement = inCloudDistance / (drone.CruiseSpeed / 3.6f); drone.ExposureTime += exposureIncrement; var dmg = _damageModel.CalculateDamage(drone.DroneType, drone.PowerType, cloud.AerosolType, cloud.Dispersion.CoreDensity, drone.ExposureTime, exposureIncrement); drone.ApplyDamage(dmg); if (drone.Status == DroneStatus.Destroyed) { OnDroneDestroyed?.Invoke(drone); _frameEventPool.Add(new SimEvent { Type = SimEventType.DroneDestroyed, OccurredAt = SimulationTime, TargetId = drone.Id, Description = $"无人机 {drone.Id} 被摧毁 @({drone.PosX:F0},{drone.PosY:F0},{drone.PosZ:F0})" }); break; } } } } // 7. 平台冷却 for (int i = 0; i < _platforms.Count; i++) _platforms[i].Update(scaledDt); // 8. 管控区域 for (int di = 0; di < _flyingDrones.Count; di++) { var drone = _flyingDrones[di]; for (int zi = 0; zi < _zones.Count; zi++) { if (_zones[zi].ContainsPoint(drone.PosX, drone.PosY, drone.PosZ)) { drone.MarkZoneIntruded(); OnZoneIntruded?.Invoke(drone, _zones[zi]); _frameEventPool.Add(new SimEvent { Type = SimEventType.ZoneIntruded, OccurredAt = SimulationTime, SourceId = drone.Id, TargetId = _zones[zi].Id, Description = $"侵入 {_zones[zi].Name}" }); } } } // 9. 结束判定 bool allDone = true; for (int i = 0; i < _drones.Count; i++) if (_drones[i].Status == DroneStatus.Flying) { allDone = false; break; } if (allDone) { State = SimulationState.Completed; OnSimulationEnded?.Invoke(); _frameEventPool.Add(new SimEvent { Type = SimEventType.SimulationEnd, OccurredAt = SimulationTime }); } _allEvents.AddRange(_frameEventPool); if (RecordFrames) { var snapshots = CollectSnapshots(); _frameStore.RecordFrame(FrameIndex, SimulationTime, snapshots); _frameResultPool.EntitySnapshots = snapshots; } else { _frameResultPool.EntitySnapshots = null!; } _frameResultPool.NewEvents = _frameEventPool; _frameResultPool.State = State; _frameResultPool.FrameIndex = FrameIndex; _frameResultPool.SimulationTime = SimulationTime; return _frameResultPool; } public void Pause() { if (State == SimulationState.Running) State = SimulationState.Paused; } public void Resume() { if (State == SimulationState.Paused) State = SimulationState.Running; } public void Stop() { State = SimulationState.Stopped; _frameStore.Flush(); } private static List BuildFireUnits(ScenarioConfig config) { var units = new List(); foreach (var eq in config.Equipment.Where(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform)) { int qty = Math.Max(1, eq.Quantity); int gunCount = eq.GunCount ?? 1; int chPerGun = eq.ChannelsPerGun ?? 1; int channels = gunCount * chPerGun; for (int i = 0; i < qty; i++) { units.Add(new FireUnit { Id = $"fu{units.Count}", Type = (PlatformType)(eq.PlatformType ?? (int)PlatformType.GroundBased), Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ), GunCount = gunCount, ChannelsPerGun = chPerGun, ChannelInterval = (float)(eq.ChannelInterval ?? 1), CruiseSpeed = (float)(eq.CruiseSpeed ?? 0f), ReleaseAltitude = (float)(eq.ReleaseAltitude ?? 0f), MuzzleVelocity = (float)(eq.MuzzleVelocity ?? 0f), TotalMunitions = eq.MunitionCount ?? channels, AmmoTypes = new List { (AerosolType)(eq.AerosolType ?? 0) }, Cooldown = (float)eq.Cooldown, // 探测能力(火力单元自带探测,激活原死字段) RadarRange = (float)(eq.RadarRange ?? 0f), EORange = (float)(eq.EORange ?? 0f), IRRange = (float)(eq.IRRange ?? 0f), }); } } return units; } /// 构建统一信息网络的探测源列表。 /// 独立探测设备(EquipmentRole.Detection)+ 火力单元自带探测能力。 private static List BuildDetectionSources(ScenarioConfig config) { var sources = new List(); foreach (var eq in config.Equipment) { // 只要有任何探测字段非 null 且 > 0,就是有效探测源(无论是否独立探测设备) bool hasDetection = (eq.RadarRange ?? 0) > 0 || (eq.EORange ?? 0) > 0 || (eq.IRRange ?? 0) > 0; if (!hasDetection) continue; int qty = Math.Max(1, eq.Quantity); for (int i = 0; i < qty; i++) { sources.Add(new DetectionSource { Position = new Algorithms.Vector3((float)eq.PositionX + i * 50, (float)eq.PositionY, (float)eq.PositionZ), RadarRange = (float)(eq.RadarRange ?? 0f), EORange = (float)(eq.EORange ?? 0f), IRRange = (float)(eq.IRRange ?? 0f), Accuracy = (float)(eq.DetectionAccuracy ?? 0f), // 3D 球冠几何:缺失时退化为无限制(等价 2D),保证存量数据平滑过渡 MinElevation = (float)(eq.MinElevation ?? float.MaxValue), MaxElevation = (float)(eq.MaxElevation ?? float.MaxValue), MinDetectAlt = (float)(eq.MinDetectAlt ?? float.MaxValue), MaxDetectAlt = (float)(eq.MaxDetectAlt ?? float.MaxValue), }); } } return sources; } private static List BuildDroneWaves(ScenarioConfig config) { var groups = new List(); foreach (var t in config.Targets) { var route = config.Routes.FirstOrDefault(r => r.WaveId == t.WaveId); var wps = config.WaypointGroups.GetValueOrDefault(t.WaveId, new List()); groups.Add(new DroneWave { WaveId = t.WaveId, Target = t, Route = route ?? new RoutePlan(), Waypoints = wps, }); } return groups; } private List CollectSnapshots() { _snapshotPool.Clear(); for (int i = 0; i < _drones.Count; i++) { var d = _drones[i]; float vx = d.PosX - d.PrevX, vy = d.PosY - d.PrevY, vz = d.PosZ - d.PrevZ; _snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.Drone, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ, VelX = vx, VelY = vy, VelZ = vz, DamageStage = (int)_damageModel.GetDamageStage(1 - d.Hp), Hp = d.Hp }); } for (int i = 0; i < _munitions.Count; i++) { var m = _munitions[i]; var (vx, vy, vz) = m.Velocity; _snapshotPool.Add(new EntitySnapshot { EntityId = m.Id, EntityType = Models.EntityType.Munition, PosX = m.PosX, PosY = m.PosY, PosZ = m.PosZ, VelX = vx, VelY = vy, VelZ = vz }); } for (int i = 0; i < _platforms.Count; i++) { var p = _platforms[i]; var (vx, vy, vz) = p.CurrentVelocity; _snapshotPool.Add(new EntitySnapshot { EntityId = p.Id, EntityType = Models.EntityType.Platform, PosX = p.PosX, PosY = p.PosY, PosZ = p.PosZ, VelX = vx, VelY = vy, VelZ = vz, PlatformStateStr = p.State.ToString() }); } for (int i = 0; i < _detectionEntities.Count; i++) { var d = _detectionEntities[i]; _snapshotPool.Add(new EntitySnapshot { EntityId = d.Id, EntityType = Models.EntityType.DetectionEquip, PosX = d.PosX, PosY = d.PosY, PosZ = d.PosZ }); } for (int i = 0; i < _clouds.Count; i++) { var c = _clouds[i]; _snapshotPool.Add(new EntitySnapshot { EntityId = c.Id, EntityType = Models.EntityType.Cloud, PosX = c.PosX, PosY = c.PosY, PosZ = c.PosZ, CloudRadius = c.Radius, CloudOpacity = c.Dispersion.Particles.Opacity, CloudPhase = c.Phase, CloudElapsed = c.Elapsed }); } return _snapshotPool; } } }