diff --git a/ThreatSource/src/Guidance/BasicGuidanceSystem.cs b/ThreatSource/src/Guidance/BaseGuidanceSystem.cs similarity index 97% rename from ThreatSource/src/Guidance/BasicGuidanceSystem.cs rename to ThreatSource/src/Guidance/BaseGuidanceSystem.cs index 40f6d63..1816f9d 100644 --- a/ThreatSource/src/Guidance/BasicGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/BaseGuidanceSystem.cs @@ -19,7 +19,7 @@ namespace ThreatSource.Guidance /// - 事件发布 /// 是其他具体制导系统的基类 /// - public class BasicGuidanceSystem : SimulationElement, IGuidanceSystem, IJammable + public class BaseGuidanceSystem : SimulationElement, IGuidanceSystem, IJammable { /// /// 干扰处理组件 @@ -104,7 +104,7 @@ namespace ThreatSource.Guidance /// - 配置制导参数 /// - 初始化干扰处理 /// - public BasicGuidanceSystem(string id, double maxAcceleration, double proportionalNavigationCoefficient, ISimulationManager simulationManager) + public BaseGuidanceSystem(string id, double maxAcceleration, double proportionalNavigationCoefficient, ISimulationManager simulationManager) : base(id, new MotionParameters(), simulationManager) { HasGuidance = false; diff --git a/ThreatSource/src/Guidance/InfraredCommandGuidanceSystem.cs b/ThreatSource/src/Guidance/InfraredCommandGuidanceSystem.cs index 07985ae..b84a588 100644 --- a/ThreatSource/src/Guidance/InfraredCommandGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/InfraredCommandGuidanceSystem.cs @@ -15,7 +15,7 @@ namespace ThreatSource.Guidance /// - 制导加速度生成 /// 用于实现红外指令制导的导弹控制 /// - public class InfraredCommandGuidanceSystem : BasicGuidanceSystem + public class InfraredCommandGuidanceSystem : BaseGuidanceSystem { /// /// 跟踪器到导弹的方向向量 (使用可空类型) diff --git a/ThreatSource/src/Guidance/InfraredImagingGuidanceSystem.cs b/ThreatSource/src/Guidance/InfraredImagingGuidanceSystem.cs index 3c08125..dfd1ebf 100644 --- a/ThreatSource/src/Guidance/InfraredImagingGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/InfraredImagingGuidanceSystem.cs @@ -19,7 +19,7 @@ namespace ThreatSource.Guidance /// - 比例导引控制 /// 用于实现自主寻的制导 /// - public class InfraredImagingGuidanceSystem : BasicGuidanceSystem + public class InfraredImagingGuidanceSystem : BaseGuidanceSystem { /// /// 工作模式枚举 diff --git a/ThreatSource/src/Guidance/LaserBeamRiderGuidanceSystem.cs b/ThreatSource/src/Guidance/LaserBeamRiderGuidanceSystem.cs index 0e1339d..d855b16 100644 --- a/ThreatSource/src/Guidance/LaserBeamRiderGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/LaserBeamRiderGuidanceSystem.cs @@ -17,7 +17,7 @@ namespace ThreatSource.Guidance /// - 非线性增益控制 /// 用于实现高精度的制导控制 /// - public class LaserBeamRiderGuidanceSystem : BasicGuidanceSystem + public class LaserBeamRiderGuidanceSystem : BaseGuidanceSystem { /// /// 获取或设置激光源位置 (使用可空类型) diff --git a/ThreatSource/src/Guidance/LaserSemiActiveGuidanceSystem.cs b/ThreatSource/src/Guidance/LaserSemiActiveGuidanceSystem.cs index 3e8120f..56dd626 100644 --- a/ThreatSource/src/Guidance/LaserSemiActiveGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/LaserSemiActiveGuidanceSystem.cs @@ -19,7 +19,7 @@ namespace ThreatSource.Guidance /// - 比例导引控制 /// 用于实现精确制导打击 /// - public class LaserSemiActiveGuidanceSystem : BasicGuidanceSystem + public class LaserSemiActiveGuidanceSystem : BaseGuidanceSystem { private readonly LaserSemiActiveGuidanceConfig config; diff --git a/ThreatSource/src/Guidance/MillimeterWaveGuidanceSystem.cs b/ThreatSource/src/Guidance/MillimeterWaveGuidanceSystem.cs index f06eb97..d426e03 100644 --- a/ThreatSource/src/Guidance/MillimeterWaveGuidanceSystem.cs +++ b/ThreatSource/src/Guidance/MillimeterWaveGuidanceSystem.cs @@ -19,7 +19,7 @@ namespace ThreatSource.Guidance /// - 比例导引控制 /// 用于实现全天候制导打击 /// - public class MillimeterWaveGuidanceSystem : BasicGuidanceSystem + public class MillimeterWaveGuidanceSystem : BaseGuidanceSystem { /// /// 工作模式枚举 diff --git a/ThreatSource/src/Indicator/BaseIndicator.cs b/ThreatSource/src/Indicator/BaseIndicator.cs index 00f63a8..61d3170 100644 --- a/ThreatSource/src/Indicator/BaseIndicator.cs +++ b/ThreatSource/src/Indicator/BaseIndicator.cs @@ -2,6 +2,11 @@ using ThreatSource.Utils; using ThreatSource.Jammer; using ThreatSource.Simulation; using ThreatSource.Jammable; +using ThreatSource.Equipment; +using System.Collections.Generic; +using System; +using System.Diagnostics; +using System.Linq; namespace ThreatSource.Indicator { @@ -17,11 +22,21 @@ namespace ThreatSource.Indicator /// public abstract class BaseIndicator : SimulationElement, IIndicator, IJammable { + /// + /// 视觉遮挡阈值 (0.0 - 1.0) + /// + protected const double TargetObscurationThreshold = 0.8; + /// /// 干扰处理组件 /// protected readonly JammableComponent _jammingComponent; + /// + /// 获取目标是否被烟幕遮挡 (由任何烟幕引起) + /// + protected bool IsTargetObscured { get; private set; } = false; + /// /// 获取设备支持的干扰类型 /// @@ -141,16 +156,19 @@ namespace ThreatSource.Indicator /// /// 更新过程: /// - 检查激活状态 - /// - 更新干扰状态 + /// - 更新干扰状态 (包括JammableComponent和烟幕遮挡) + /// - 更新最后已知目标方向(如果未被遮挡) /// - 处理指示器特定逻辑 /// public override void Update(double deltaTime) { - // 更新干扰状态 _jammingComponent.UpdateJammingStatus(deltaTime); if (IsActive) { + // Always check obscuration status in each update cycle + CheckOverallObscuration(); + UpdateIndicator(deltaTime); } } @@ -177,5 +195,82 @@ namespace ThreatSource.Indicator /// 包括位置、姿态、目标关联和干扰状态等 /// public abstract IndicatorRunningState GetRunningState(); + + + /// + /// 检查所有活动的烟幕,确定目标是否被任何一个遮挡 + /// + private void CheckOverallObscuration() + { + bool currentlyObscured = false; + if (string.IsNullOrEmpty(TargetId)) + { + IsTargetObscured = false; + return; + } + + var target = SimulationManager.GetEntityById(TargetId) as BaseEquipment; + if (target == null) + { + IsTargetObscured = false; + return; + } + + var activeSmokeGrenades = SimulationManager.GetEntitiesByType() // Get all smoke grenades + .Where(sg => sg.IsActive); // Filter for active ones + + if (!activeSmokeGrenades.Any()) + { + IsTargetObscured = false; // No active smoke, not obscured + return; + } + + Vector3D observerPos = this.Position; + Vector3D targetCenter = target.Position; + Orientation targetOrient = target.Orientation; + Vector3D targetDims = new(target.Properties.Length, target.Properties.Height, target.Properties.Width); + + foreach (var smokeGrenade in activeSmokeGrenades) + { + if (smokeGrenade == null) continue; + + try + { + Vector3D smokeCenter = smokeGrenade.Position; + Orientation smokeOrient = smokeGrenade.Orientation; + Vector3D smokeDims; + if (smokeGrenade.config.SmokeType == Jammer.SmokeScreenType.Cloud) + { + smokeDims = new(smokeGrenade.config.CloudDiameter, smokeGrenade.config.Thickness, smokeGrenade.config.CloudDiameter); + } + else + { + smokeDims = new(smokeGrenade.config.WallWidth, smokeGrenade.config.WallHeight, smokeGrenade.config.Thickness); + } + + double ratio = ObscurationUtils.CalculateProjectedOverlapRatio( + observerPos, + smokeCenter, smokeDims, smokeOrient, + targetCenter, targetDims, targetOrient + ); + + if (ratio >= TargetObscurationThreshold) + { + currentlyObscured = true; + break; // Found an obscuring smoke, no need to check others + } + } + catch (Exception ex) + { + Debug.WriteLine($"Error checking obscuration by smoke {smokeGrenade.Id} for indicator {Id}: {ex.Message}"); + } + } + + if (IsTargetObscured != currentlyObscured) + { + IsTargetObscured = currentlyObscured; + Debug.WriteLine($"Indicator {Id}: Overall obscuration status updated. IsTargetObscured: {IsTargetObscured}"); + } + } } } \ No newline at end of file diff --git a/ThreatSource/src/Indicator/InfraredTracker.cs b/ThreatSource/src/Indicator/InfraredTracker.cs index 26a1363..6416375 100644 --- a/ThreatSource/src/Indicator/InfraredTracker.cs +++ b/ThreatSource/src/Indicator/InfraredTracker.cs @@ -83,22 +83,24 @@ namespace ThreatSource.Indicator /// 时间步长,单位:秒 /// /// 实现红外测角仪特定的更新逻辑: - /// - 只有在未被干扰时才执行跟踪 + /// - 只有在未被电子干扰且目标未被烟幕遮挡时才执行跟踪 /// - 更新跟踪状态 /// - 处理目标跟踪 /// protected override void UpdateIndicator(double deltaTime) { - // 只有在未被干扰时才执行跟踪 - if (!IsJammed) + // 检查电子干扰和烟幕遮挡 + if (IsJammed || IsTargetObscured) { - UpdateTracking(); - } - else - { - // 在干扰状态下,确保停止跟踪 + // 在干扰或遮挡状态下,确保停止跟踪 StopTracking(); + // 可以选择性地在此处添加日志或状态更新 + if(IsTargetObscured) Debug.WriteLine($"InfraredTracker {Id}: Target obscured by smoke, stopping tracking."); + return; // 不执行后续更新逻辑 } + + // 未被干扰且未被遮挡,执行正常跟踪 + UpdateTracking(); } /// @@ -119,7 +121,12 @@ namespace ThreatSource.Indicator var missile = SimulationManager.GetEntityById(MissileId); if (target is not SimulationElement targetElement || - missile is not InfraredCommandGuidedMissile missileElement) return; + missile is not InfraredCommandGuidedMissile missileElement) + { + // 如果目标或导弹无效,停止跟踪 + StopTracking(); + return; + } IsTracking = true; @@ -132,7 +139,8 @@ namespace ThreatSource.Indicator // 计算测角仪到导弹的向量 Vector3D trackerToMissile = missileElement.Position - Position; - // 计算测角仪到目标的向量 + // 计算测角仪到目标的向量 (使用最后已知方向,以防目标瞬间无效) + // Note: This uses the current target position. If obscured, UpdateTracking shouldn't be called. Vector3D trackerToTarget = targetElement.Position - Position; // 发送制导指令事件 @@ -307,6 +315,7 @@ namespace ThreatSource.Indicator /// - 工作类型和状态 /// - 位置和姿态信息 /// - 激活状态 + /// - 干扰状态 (包括电子干扰和烟幕遮挡) /// public override IndicatorRunningState GetRunningState() { @@ -319,7 +328,7 @@ namespace ThreatSource.Indicator Position = Position, Orientation = Orientation, IsActive = IsActive, - JammingState = IsJammed ? JammingState.Jammed : JammingState.Normal + JammingState = (IsJammed || IsTargetObscured) ? JammingState.Jammed : JammingState.Normal }; } @@ -334,17 +343,21 @@ namespace ThreatSource.Indicator /// - 目标关联状态 /// - 工作状态 /// - 跟踪状态 - /// - 干扰状态 + /// - 干扰/遮挡状态 /// public override string GetStatus() { + string jammingStatusString = "正常"; + if (IsJammed) jammingStatusString = "受电子干扰"; + if (IsTargetObscured) jammingStatusString = IsJammed ? "受电子干扰和烟幕遮挡" : "目标被烟幕遮挡"; + return $"红外测角仪 {Id}:\n" + $" 位置: {Position}\n" + $" 跟踪导弹: {MissileId ?? "无"}\n" + $" 目标: {TargetId ?? "无"}\n" + $" 状态: {(IsActive ? "活动" : "已销毁")}\n" + $" 跟踪状态: {(IsTracking ? "跟踪中" : "未跟踪")}\n" + - $" 干扰状态: {(IsJammed ? "受干扰" : "正常")}\n"; + $" 干扰/遮挡状态: {jammingStatusString}\n"; } /// diff --git a/ThreatSource/src/Indicator/LaserBeamRider.cs b/ThreatSource/src/Indicator/LaserBeamRider.cs index b9795ea..178c8ec 100644 --- a/ThreatSource/src/Indicator/LaserBeamRider.cs +++ b/ThreatSource/src/Indicator/LaserBeamRider.cs @@ -168,21 +168,52 @@ namespace ThreatSource.Indicator /// 时间步长,单位:秒 /// /// 更新过程: - /// - 检查干扰状态 - /// - 更新激光指向 - /// - 发布状态更新事件 + /// - 检查电子干扰和烟幕遮挡状态 + /// - 更新激光指向 (仅当未被干扰/遮挡时) + /// - 发布状态更新事件 (仅当未被干扰/遮挡且方向更新时) /// protected override void UpdateIndicator(double deltaTime) { - if (IsBeamOn && !IsJammed) + // 检查电子干扰和烟幕遮挡 + if (IsJammed || IsTargetObscured) { - // 更新驾束仪的激光指向 + // 如果被干扰或遮挡,不更新激光方向,也不发布事件 + // 激光束本身不一定关闭,除非干扰处理逻辑明确要求(HandleJammingApplied) + // 或者产品设计要求在遮挡时关闭光束(可以添加到 StopBeamIllumination 调用) + if(IsTargetObscured) Debug.WriteLine($"LaserBeamRider {Id}: Target obscured by smoke, direction update skipped."); + return; // 不执行后续更新逻辑 + } + + // 未被干扰/遮挡,且光束开启,则更新方向并发布事件 + if (IsBeamOn) + { + // 更新驾束仪的激光指向 (使用基类维护的 LastKnownTargetDirection) + // We rely on BaseIndicator.UpdateTargetDirectionIfNotObscured() + // to keep LastKnownTargetDirection updated when not obscured. + // If it becomes obscured, UpdateIndicator stops calling this section, + // and LaserDirection retains the value from the last successful update. + + // Alternative: Directly use LastKnownTargetDirection if always desired when not obscured. + // LaserDirection = LastKnownTargetDirection; + + // Original logic: Recalculate based on current target position if needed + // This check happens *after* the IsObscured check, so target should be visible. if (TargetId != null && SimulationManager.GetEntityById(TargetId) is SimulationElement target) { Vector3D targetPosition = target.Position; - LaserDirection = (targetPosition - Position).Normalize(); - Debug.WriteLine($"激光驾束仪 {Id} 更新激光指向: {LaserDirection}"); - PublishLaserBeamEvent(); + Vector3D newDirection = (targetPosition - Position).Normalize(); + // Only update and publish if the direction actually changes significantly (optional optimization) + if ((newDirection - LaserDirection).MagnitudeSquared() > 1e-9) + { + LaserDirection = newDirection; + Debug.WriteLine($"激光驾束仪 {Id} 更新激光指向: {LaserDirection}"); + PublishLaserBeamEvent(); // Publish event only when direction is updated + } + } + else + { + // Target lost or invalid, maybe stop beam? + // StopBeamIllumination(); } } } @@ -201,14 +232,13 @@ namespace ThreatSource.Indicator { if (!IsActive) { - IsActive = true; - // 先订阅干扰事件 + // IsActive = true; // Base class handles IsActive now SimulationManager.SubscribeToEvent(OnLaserJamming); Debug.WriteLine($"激光驾束仪 {Id} 已激活"); - // 激光驾束仪开始照射 StartBeamIllumination(); + base.Activate(); // Call base Activate LAST to ensure subscriptions are set up before potential events } - base.Activate(); + // base.Activate(); // Should be called within the if block } /// @@ -225,16 +255,16 @@ namespace ThreatSource.Indicator { if (IsActive) { - IsActive = false; + // IsActive = false; // Base class handles IsActive now if (IsBeamOn) { StopBeamIllumination(); } - // 取消订阅干扰事件 SimulationManager.UnsubscribeFromEvent(OnLaserJamming); + base.Deactivate(); // Call base Deactivate FIRST to handle unsubscriptions before local cleanup Debug.WriteLine($"激光驾束仪 {Id} 已停用"); } - base.Deactivate(); + // base.Deactivate(); // Should be called within the if block } /// @@ -352,7 +382,7 @@ namespace ThreatSource.Indicator /// - 工作类型和状态 /// - 位置和姿态信息 /// - 激活状态 - /// - 干扰状态 + /// - 干扰/遮挡状态 /// public override IndicatorRunningState GetRunningState() { @@ -365,7 +395,7 @@ namespace ThreatSource.Indicator Position = Position, Orientation = Orientation, IsActive = IsActive, - JammingState = IsJammed ? JammingState.Jammed : JammingState.Normal + JammingState = (IsJammed || IsTargetObscured) ? JammingState.Jammed : JammingState.Normal }; } @@ -379,15 +409,20 @@ namespace ThreatSource.Indicator /// - 激光参数(功率、发散角等) /// - 工作状态(激活、照射等) /// - 制导范围参数 + /// - 干扰/遮挡状态 /// public override string GetStatus() { + string jammingStatusString = "正常"; + if (IsJammed) jammingStatusString = "受电子干扰"; + if (IsTargetObscured) jammingStatusString = IsJammed ? "受电子干扰和烟幕遮挡" : "目标被烟幕遮挡"; + return $"激光驾束仪 {Id}:\n" + $" 位置: {Position}\n" + $" 方向: {LaserDirection}\n" + $" 激活状态: {(IsActive ? "激活" : "未激活")}\n" + $" 激光束状态: {(IsBeamOn ? "开启" : "关闭")}\n" + - $" 干扰状态: {(IsJammed ? "受干扰" : "正常")}\n" + + $" 干扰/遮挡状态: {jammingStatusString}\n" + $" 激光功率: {LaserPower} W\n" + $" 发散角: {BeamDivergence} rad\n" + $" 控制场直径: {ControlFieldDiameter} m\n" + diff --git a/ThreatSource/src/Indicator/LaserDesignator.cs b/ThreatSource/src/Indicator/LaserDesignator.cs index 33c2ede..386b800 100644 --- a/ThreatSource/src/Indicator/LaserDesignator.cs +++ b/ThreatSource/src/Indicator/LaserDesignator.cs @@ -127,19 +127,33 @@ namespace ThreatSource.Indicator /// /// 更新过程: /// - 检查激活状态 - /// - 检查干扰状态 + /// - 检查电子干扰和烟幕遮挡状态 /// - 更新照射状态 - /// - 发布状态事件 + /// - 更新指示器朝向 (仅当未被干扰/遮挡时) + /// - 发布状态事件 (仅当未被干扰/遮挡且照射开启时) /// protected override void UpdateIndicator(double deltaTime) { - if (!IsJammed) + // 检查电子干扰和烟幕遮挡 + if (IsJammed || IsTargetObscured) { - UpdateOrientation(); - if (IsIlluminationOn) - { - PublishIlluminationUpdateEvent(); - } + // 如果被干扰或遮挡,不更新朝向,也不发布更新事件 + // 激光照射本身不一定停止,除非干扰处理逻辑明确要求(HandleJammingApplied) + // 或设计要求在遮挡时停止照射 (可以添加到 StopLaserIllumination 调用) + if(IsTargetObscured) Debug.WriteLine($"LaserDesignator {Id}: Target obscured by smoke, orientation update skipped."); + // Still need to publish *if* illumination is on but orientation is frozen? + // Current plan: Do not publish update event if obscured/jammed. + return; // 不执行后续更新逻辑 + } + + // 未被干扰/遮挡,执行正常更新 + // 更新朝向 + UpdateOrientation(); // This now happens only if not jammed/obscured + + // 如果正在照射,发布更新事件 + if (IsIlluminationOn) + { + PublishIlluminationUpdateEvent(); } } @@ -151,6 +165,9 @@ namespace ThreatSource.Indicator if (target != null) { Vector3D direction = (target.Position - Position).Normalize(); + // Check if direction is valid before calculating angles + if (direction.MagnitudeSquared() < 1e-9) return; + double yaw = Math.PI + Math.Atan2(direction.Z, direction.X); double pitch = -Math.Asin(direction.Y); Orientation = new Orientation(yaw, pitch, 0); @@ -274,13 +291,12 @@ namespace ThreatSource.Indicator { if (!IsActive) { - IsActive = true; - // 先订阅事件 + // IsActive = true; // Base class handles IsActive SimulationManager.SubscribeToEvent(OnLaserJamming); - // 再开始照射 StartLaserIllumination(); + base.Activate(); // Call base Activate LAST } - base.Activate(); + // base.Activate(); // Should be inside the if block } /// @@ -298,11 +314,12 @@ namespace ThreatSource.Indicator { if (IsActive) { - IsActive = false; + // IsActive = false; // Base class handles IsActive StopLaserIllumination(); SimulationManager.UnsubscribeFromEvent(OnLaserJamming); + base.Deactivate(); // Call base Deactivate FIRST } - base.Deactivate(); + // base.Deactivate(); // Should be inside the if block } /// @@ -376,7 +393,7 @@ namespace ThreatSource.Indicator /// - 工作类型和状态 /// - 位置和姿态信息 /// - 激活状态 - /// - 干扰状态 + /// - 干扰/遮挡状态 /// public override IndicatorRunningState GetRunningState() { @@ -389,7 +406,7 @@ namespace ThreatSource.Indicator Position = Position, Orientation = Orientation, IsActive = IsActive, - JammingState = IsJammed ? JammingState.Jammed : JammingState.Normal + JammingState = (IsJammed || IsTargetObscured) ? JammingState.Jammed : JammingState.Normal }; } @@ -403,11 +420,15 @@ namespace ThreatSource.Indicator /// - 位置信息 /// - 目标关联状态 /// - 工作状态 - /// - 干扰状态 + /// - 干扰/遮挡状态 /// - 激光参数 /// public override string GetStatus() { + string jammingStatusString = "正常"; + if (IsJammed) jammingStatusString = "受电子干扰"; + if (IsTargetObscured) jammingStatusString = IsJammed ? "受电子干扰和烟幕遮挡" : "目标被烟幕遮挡"; + return $"激光目标指示器 {Id}:\n" + $" 位置: {Position}\n" + $" 目标: {TargetId}\n" + @@ -415,7 +436,7 @@ namespace ThreatSource.Indicator $" 朝向: {Orientation}\n" + $" 激活状态: {(IsActive ? "激活" : "未激活")}\n" + $" 照射状态: {(IsIlluminationOn ? "正在照射" : "未照射")}\n" + - $" 干扰状态: {(IsJammed ? "受干扰" : "正常")}\n" + + $" 干扰/遮挡状态: {jammingStatusString}\n" + $" 编码状态: {(LaserCodeConfig != null ? "启用" : "禁用")}\n" + $" 激光功率: {LaserPower:E} W"; } diff --git a/ThreatSource/src/Utils/ObscurationUtils.cs b/ThreatSource/src/Utils/ObscurationUtils.cs new file mode 100644 index 0000000..c48e12f --- /dev/null +++ b/ThreatSource/src/Utils/ObscurationUtils.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ThreatSource.Simulation; // Assuming SimulationElement, Vector3D, Orientation are here or accessible + +namespace ThreatSource.Utils +{ + /// + /// Provides utility methods for calculating visual obscuration between simulation elements. + /// Assumes objects can be represented by an oriented bounding box (OBB). + /// + public static class ObscurationUtils + { + // Represents a 2D Axis-Aligned Bounding Box + private struct Rect + { + public double MinU, MinV, MaxU, MaxV; + public double Width => MaxU - MinU; + public double Height => MaxV - MinV; + public bool IsValid => Width >= 0 && Height >= 0; + } + + /// + /// Calculates the ratio of the background object's projected area that is obscured by the foreground object, + /// as seen from the observer's position. Both objects are treated as Oriented Bounding Boxes (OBBs). + /// + /// The position of the observer. + /// World position of the foreground object's center. + /// Dimensions (e.g., Length, Height, Width) of the foreground object aligned with its local axes. + /// Orientation of the foreground object. + /// World position of the background object's center. + /// Dimensions of the background object aligned with its local axes. + /// Orientation of the background object. + /// A ratio from 0.0 (no obscuration) to 1.0 (fully obscured). + public static double CalculateProjectedOverlapRatio( + Vector3D observerPos, + Vector3D foregroundCenter, + Vector3D foregroundDims, + Orientation foregroundOrient, + Vector3D backgroundCenter, + Vector3D backgroundDims, + Orientation backgroundOrient) + { + // --- Input Validation --- + if (backgroundDims.X <= 0 || backgroundDims.Y <= 0 || backgroundDims.Z <= 0 || + foregroundDims.X <= 0 || foregroundDims.Y <= 0 || foregroundDims.Z <= 0) + { + // Invalid dimensions + System.Diagnostics.Debug.WriteLine("Warning: Invalid dimensions for obscuration calculation."); + return 0.0; + } + + // --- Define Projection Plane --- + Vector3D los = (backgroundCenter - observerPos).Normalize(); + if (los.MagnitudeSquared() < 1e-9) return 1.0; // Observer at background object center, fully obscured conceptually + + // Create basis vectors for the 2D projection plane (perpendicular to Line of Sight) + Vector3D upApprox = (Math.Abs(Vector3D.DotProduct(los, Vector3D.UnitY)) < 0.99) ? Vector3D.UnitY : Vector3D.UnitX; + Vector3D uAxis = Vector3D.CrossProduct(upApprox, los).Normalize(); + Vector3D vAxis = Vector3D.CrossProduct(los, uAxis).Normalize(); + + if (uAxis.MagnitudeSquared() < 1e-9 || vAxis.MagnitudeSquared() < 1e-9) + { + System.Diagnostics.Debug.WriteLine("Warning: Failed to create projection basis vectors."); + return 0.0; + } + + // --- Project Objects and Get 2D AABBs --- + Rect backgroundRect = CalculateProjectedAABB(observerPos, backgroundCenter, backgroundDims, backgroundOrient, uAxis, vAxis); + Rect foregroundRect = CalculateProjectedAABB(observerPos, foregroundCenter, foregroundDims, foregroundOrient, uAxis, vAxis); + + if (!backgroundRect.IsValid) + { + return 0.0; // Background projection has no area + } + + // --- Calculate Intersection and Ratio --- + double backgroundArea = backgroundRect.Width * backgroundRect.Height; + if (backgroundArea < 1e-9) + { + // If background has no projected area, it cannot be obscured. + // Or consider it fully obscured if foreground *does* have area? + // Safest is 0 obscuration if background isn't visible. + return 0.0; + } + + double intersectionArea = CalculateRectangleIntersectionArea(backgroundRect, foregroundRect); + + return Math.Clamp(intersectionArea / backgroundArea, 0.0, 1.0); + } + + + // === Geometric Helper Methods === + + /// + /// Calculates the 8 world-space corners of an Oriented Bounding Box (OBB). + /// + private static List GetWorldOBBCorners(Vector3D center, Vector3D dimensions, Orientation orientation) + { + Vector3D halfDim = dimensions * 0.5; + // Assuming Orientation class can provide basis vectors or a rotation matrix. + // Using a hypothetical GetBasisVectors() returning [localX, localY, localZ] in world space. + // TODO: Replace with actual Orientation method. + (Vector3D xAxis, Vector3D yAxis, Vector3D zAxis) = GetOrientationBasisVectors(orientation); + + var corners = new List(8); + corners.Add(center - xAxis * halfDim.X - yAxis * halfDim.Y - zAxis * halfDim.Z); + corners.Add(center + xAxis * halfDim.X - yAxis * halfDim.Y - zAxis * halfDim.Z); + corners.Add(center + xAxis * halfDim.X + yAxis * halfDim.Y - zAxis * halfDim.Z); + corners.Add(center - xAxis * halfDim.X + yAxis * halfDim.Y - zAxis * halfDim.Z); + corners.Add(center - xAxis * halfDim.X - yAxis * halfDim.Y + zAxis * halfDim.Z); + corners.Add(center + xAxis * halfDim.X - yAxis * halfDim.Y + zAxis * halfDim.Z); + corners.Add(center + xAxis * halfDim.X + yAxis * halfDim.Y + zAxis * halfDim.Z); + corners.Add(center - xAxis * halfDim.X + yAxis * halfDim.Y + zAxis * halfDim.Z); + return corners; + } + + // Placeholder for getting Orientation axes + private static (Vector3D, Vector3D, Vector3D) GetOrientationBasisVectors(Orientation orientation) + { + // This needs to be implemented based on the actual Orientation class definition. + // Example: If Orientation has ToRotationMatrix() returning a 3x3 matrix: + // var matrix = orientation.ToRotationMatrix(); + // return (matrix.GetColumn(0), matrix.GetColumn(1), matrix.GetColumn(2)); + + // Example: If Orientation directly stores axes: + // return (orientation.XAxis, orientation.YAxis, orientation.ZAxis); + + // Fallback: Assume identity orientation if method not available + System.Diagnostics.Debug.WriteLine("Warning: Using identity orientation basis vectors."); + return (Vector3D.UnitX, Vector3D.UnitY, Vector3D.UnitZ); + } + + /// + /// Calculates the 2D Axis-Aligned Bounding Box (AABB) of an object's projected corners. + /// + private static Rect CalculateProjectedAABB( + Vector3D observerPos, + Vector3D objectCenter, + Vector3D objectDimensions, + Orientation objectOrientation, + Vector3D uAxis, // Projection plane U axis (world space) + Vector3D vAxis) // Projection plane V axis (world space) + { + List worldCorners = GetWorldOBBCorners(objectCenter, objectDimensions, objectOrientation); + + double minU = double.MaxValue, maxU = double.MinValue; + double minV = double.MaxValue, maxV = double.MinValue; + + foreach (var corner in worldCorners) + { + Vector3D observerToCorner = corner - observerPos; + double u = Vector3D.DotProduct(observerToCorner, uAxis); + double v = Vector3D.DotProduct(observerToCorner, vAxis); + + minU = Math.Min(minU, u); + maxU = Math.Max(maxU, u); + minV = Math.Min(minV, v); + maxV = Math.Max(maxV, v); + } + + if (minU > maxU || minV > maxV) + { + // No valid projection (e.g., object is behind observer or has zero size) + return new Rect { MinU = 0, MaxU = -1, MinV = 0, MaxV = -1 }; // Indicate invalid rect + } + + return new Rect { MinU = minU, MinV = minV, MaxU = maxU, MaxV = maxV }; + } + + /// + /// Calculates the intersection area of two 2D AABB rectangles. + /// + private static double CalculateRectangleIntersectionArea(Rect r1, Rect r2) + { + if (!r1.IsValid || !r2.IsValid) return 0; + + double xOverlap = Math.Max(0, Math.Min(r1.MaxU, r2.MaxU) - Math.Max(r1.MinU, r2.MinU)); + double yOverlap = Math.Max(0, Math.Min(r1.MaxV, r2.MaxV) - Math.Max(r1.MinV, r2.MinV)); + + return xOverlap * yOverlap; + } + } +} \ No newline at end of file