部分修改了烟幕对指示器的影响

This commit is contained in:
Tian jianyong 2025-04-22 18:16:04 +08:00
parent 6e990f65a4
commit 5a480a9969
11 changed files with 406 additions and 58 deletions

View File

@ -19,7 +19,7 @@ namespace ThreatSource.Guidance
/// - 事件发布
/// 是其他具体制导系统的基类
/// </remarks>
public class BasicGuidanceSystem : SimulationElement, IGuidanceSystem, IJammable
public class BaseGuidanceSystem : SimulationElement, IGuidanceSystem, IJammable
{
/// <summary>
/// 干扰处理组件
@ -104,7 +104,7 @@ namespace ThreatSource.Guidance
/// - 配置制导参数
/// - 初始化干扰处理
/// </remarks>
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;

View File

@ -15,7 +15,7 @@ namespace ThreatSource.Guidance
/// - 制导加速度生成
/// 用于实现红外指令制导的导弹控制
/// </remarks>
public class InfraredCommandGuidanceSystem : BasicGuidanceSystem
public class InfraredCommandGuidanceSystem : BaseGuidanceSystem
{
/// <summary>
/// 跟踪器到导弹的方向向量 (使用可空类型)

View File

@ -19,7 +19,7 @@ namespace ThreatSource.Guidance
/// - 比例导引控制
/// 用于实现自主寻的制导
/// </remarks>
public class InfraredImagingGuidanceSystem : BasicGuidanceSystem
public class InfraredImagingGuidanceSystem : BaseGuidanceSystem
{
/// <summary>
/// 工作模式枚举

View File

@ -17,7 +17,7 @@ namespace ThreatSource.Guidance
/// - 非线性增益控制
/// 用于实现高精度的制导控制
/// </remarks>
public class LaserBeamRiderGuidanceSystem : BasicGuidanceSystem
public class LaserBeamRiderGuidanceSystem : BaseGuidanceSystem
{
/// <summary>
/// 获取或设置激光源位置 (使用可空类型)

View File

@ -19,7 +19,7 @@ namespace ThreatSource.Guidance
/// - 比例导引控制
/// 用于实现精确制导打击
/// </remarks>
public class LaserSemiActiveGuidanceSystem : BasicGuidanceSystem
public class LaserSemiActiveGuidanceSystem : BaseGuidanceSystem
{
private readonly LaserSemiActiveGuidanceConfig config;

View File

@ -19,7 +19,7 @@ namespace ThreatSource.Guidance
/// - 比例导引控制
/// 用于实现全天候制导打击
/// </remarks>
public class MillimeterWaveGuidanceSystem : BasicGuidanceSystem
public class MillimeterWaveGuidanceSystem : BaseGuidanceSystem
{
/// <summary>
/// 工作模式枚举

View File

@ -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
/// </remarks>
public abstract class BaseIndicator : SimulationElement, IIndicator, IJammable
{
/// <summary>
/// 视觉遮挡阈值 (0.0 - 1.0)
/// </summary>
protected const double TargetObscurationThreshold = 0.8;
/// <summary>
/// 干扰处理组件
/// </summary>
protected readonly JammableComponent _jammingComponent;
/// <summary>
/// 获取目标是否被烟幕遮挡 (由任何烟幕引起)
/// </summary>
protected bool IsTargetObscured { get; private set; } = false;
/// <summary>
/// 获取设备支持的干扰类型
/// </summary>
@ -141,16 +156,19 @@ namespace ThreatSource.Indicator
/// <remarks>
/// 更新过程:
/// - 检查激活状态
/// - 更新干扰状态
/// - 更新干扰状态 (包括JammableComponent和烟幕遮挡)
/// - 更新最后已知目标方向(如果未被遮挡)
/// - 处理指示器特定逻辑
/// </remarks>
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
/// 包括位置、姿态、目标关联和干扰状态等
/// </remarks>
public abstract IndicatorRunningState GetRunningState();
/// <summary>
/// 检查所有活动的烟幕,确定目标是否被任何一个遮挡
/// </summary>
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<SmokeGrenade>() // 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}");
}
}
}
}

View File

@ -83,22 +83,24 @@ namespace ThreatSource.Indicator
/// <param name="deltaTime">时间步长,单位:秒</param>
/// <remarks>
/// 实现红外测角仪特定的更新逻辑:
/// - 只有在未被干扰时才执行跟踪
/// - 只有在未被电子干扰且目标未被烟幕遮挡时才执行跟踪
/// - 更新跟踪状态
/// - 处理目标跟踪
/// </remarks>
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();
}
/// <summary>
@ -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
/// - 工作类型和状态
/// - 位置和姿态信息
/// - 激活状态
/// - 干扰状态 (包括电子干扰和烟幕遮挡)
/// </remarks>
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
/// - 目标关联状态
/// - 工作状态
/// - 跟踪状态
/// - 干扰状态
/// - 干扰/遮挡状态
/// </remarks>
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";
}
/// <summary>

View File

@ -168,21 +168,52 @@ namespace ThreatSource.Indicator
/// <param name="deltaTime">时间步长,单位:秒</param>
/// <remarks>
/// 更新过程:
/// - 检查干扰状态
/// - 更新激光指向
/// - 发布状态更新事件
/// - 检查电子干扰和烟幕遮挡状态
/// - 更新激光指向 (仅当未被干扰/遮挡时)
/// - 发布状态更新事件 (仅当未被干扰/遮挡且方向更新时)
/// </remarks>
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<LaserJammingEvent>(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
}
/// <summary>
@ -225,16 +255,16 @@ namespace ThreatSource.Indicator
{
if (IsActive)
{
IsActive = false;
// IsActive = false; // Base class handles IsActive now
if (IsBeamOn)
{
StopBeamIllumination();
}
// 取消订阅干扰事件
SimulationManager.UnsubscribeFromEvent<LaserJammingEvent>(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
}
/// <summary>
@ -352,7 +382,7 @@ namespace ThreatSource.Indicator
/// - 工作类型和状态
/// - 位置和姿态信息
/// - 激活状态
/// - 干扰状态
/// - 干扰/遮挡状态
/// </remarks>
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
/// - 激光参数(功率、发散角等)
/// - 工作状态(激活、照射等)
/// - 制导范围参数
/// - 干扰/遮挡状态
/// </remarks>
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" +

View File

@ -127,19 +127,33 @@ namespace ThreatSource.Indicator
/// <remarks>
/// 更新过程:
/// - 检查激活状态
/// - 检查干扰状态
/// - 检查电子干扰和烟幕遮挡状态
/// - 更新照射状态
/// - 发布状态事件
/// - 更新指示器朝向 (仅当未被干扰/遮挡时)
/// - 发布状态事件 (仅当未被干扰/遮挡且照射开启时)
/// </remarks>
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<LaserJammingEvent>(OnLaserJamming);
// 再开始照射
StartLaserIllumination();
base.Activate(); // Call base Activate LAST
}
base.Activate();
// base.Activate(); // Should be inside the if block
}
/// <summary>
@ -298,11 +314,12 @@ namespace ThreatSource.Indicator
{
if (IsActive)
{
IsActive = false;
// IsActive = false; // Base class handles IsActive
StopLaserIllumination();
SimulationManager.UnsubscribeFromEvent<LaserJammingEvent>(OnLaserJamming);
base.Deactivate(); // Call base Deactivate FIRST
}
base.Deactivate();
// base.Deactivate(); // Should be inside the if block
}
/// <summary>
@ -376,7 +393,7 @@ namespace ThreatSource.Indicator
/// - 工作类型和状态
/// - 位置和姿态信息
/// - 激活状态
/// - 干扰状态
/// - 干扰/遮挡状态
/// </remarks>
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
/// - 位置信息
/// - 目标关联状态
/// - 工作状态
/// - 干扰状态
/// - 干扰/遮挡状态
/// - 激光参数
/// </remarks>
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";
}

View File

@ -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
{
/// <summary>
/// Provides utility methods for calculating visual obscuration between simulation elements.
/// Assumes objects can be represented by an oriented bounding box (OBB).
/// </summary>
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;
}
/// <summary>
/// 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).
/// </summary>
/// <param name="observerPos">The position of the observer.</param>
/// <param name="foregroundCenter">World position of the foreground object's center.</param>
/// <param name="foregroundDims">Dimensions (e.g., Length, Height, Width) of the foreground object aligned with its local axes.</param>
/// <param name="foregroundOrient">Orientation of the foreground object.</param>
/// <param name="backgroundCenter">World position of the background object's center.</param>
/// <param name="backgroundDims">Dimensions of the background object aligned with its local axes.</param>
/// <param name="backgroundOrient">Orientation of the background object.</param>
/// <returns>A ratio from 0.0 (no obscuration) to 1.0 (fully obscured).</returns>
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 ===
/// <summary>
/// Calculates the 8 world-space corners of an Oriented Bounding Box (OBB).
/// </summary>
private static List<Vector3D> 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<Vector3D>(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);
}
/// <summary>
/// Calculates the 2D Axis-Aligned Bounding Box (AABB) of an object's projected corners.
/// </summary>
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<Vector3D> 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 };
}
/// <summary>
/// Calculates the intersection area of two 2D AABB rectangles.
/// </summary>
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;
}
}
}