将方向从X-Y-Z改为Z-Y-X

This commit is contained in:
Tian jianyong 2025-05-10 18:12:44 +08:00
parent c0c8c4b3a6
commit d872c8ece3
13 changed files with 361 additions and 262 deletions

View File

@ -9,7 +9,8 @@ alwaysApply: true
# 坐标系约定
- 采用右手坐标系
- Y 轴为垂直轴
- Y 轴向上
- 逆时针旋转为正方向
# 运行命令
- 在运行命令前,先运行 pwd 命令,了解当前目录

View File

@ -32,7 +32,7 @@ namespace ThreatSource.Tests.Jamming
var tankInitialMotion = new MotionParameters
{
Position = new Vector3D(100, 0, 100),
Orientation = new Orientation(Math.PI/4, 0, 0),
Orientation = new Orientation(-Math.PI/2, Math.PI/4, 0),
InitialSpeed = 0
};
var tankProperties = new EquipmentProperties
@ -63,7 +63,7 @@ namespace ThreatSource.Tests.Jamming
var submunitionInitialMotion = new MotionParameters
{
Position = new Vector3D(20, 150, 0),
Orientation = new Orientation(0, -Math.PI/4, 0),
Orientation = new Orientation(-Math.PI/2, -Math.PI/4, 0),
InitialSpeed = 20
};
@ -332,7 +332,7 @@ namespace ThreatSource.Tests.Jamming
var tank5InitialMotion = new MotionParameters
{
Position = new Vector3D(100, 0, 100),
Orientation = new Orientation(Math.PI/4, 0, 0),
Orientation = new Orientation(-Math.PI/2, Math.PI/4, 0),
InitialSpeed = 0
};
var tankProperties = new EquipmentProperties
@ -358,7 +358,7 @@ namespace ThreatSource.Tests.Jamming
var submunitionInitialMotion = new MotionParameters
{
Position = new Vector3D(0, 400, 0),
Orientation = new Orientation(0, -Math.PI/4, 0),
Orientation = new Orientation(-Math.PI/2, -Math.PI/4, 0),
InitialSpeed = 100
};
@ -442,7 +442,7 @@ namespace ThreatSource.Tests.Jamming
var tank6InitialMotion = new MotionParameters
{
Position = new Vector3D(100, 0, 100),
Orientation = new Orientation(Math.PI/4, 0, 0),
Orientation = new Orientation(-Math.PI/2, Math.PI/4, 0),
InitialSpeed = 0
};
var tankProperties = new EquipmentProperties
@ -469,7 +469,7 @@ namespace ThreatSource.Tests.Jamming
var submunitionInitialMotion = new MotionParameters
{
Position = new Vector3D(0, 300, 0),
Orientation = new Orientation(0, -Math.PI/2, 0), // 垂直向下
Orientation = new Orientation(0.0, -Math.PI/2, 0.0), // Vertically downwards (-Z along world -Y)
InitialSpeed = 40 // 设置为减速阶段末速度
};

View File

@ -30,7 +30,7 @@ namespace ThreatSource.Tests.Missile
var tankInitialMotion = new MotionParameters
{
Position = new Vector3D(100, 0, 100),
Orientation = new Orientation(Math.PI/4, 0, 0), // 目标朝向45度
Orientation = new Orientation(-Math.PI/2, Math.PI/4, 0), // 目标朝向45度 (原Yaw=PI/4)
InitialSpeed = 0
};
@ -67,7 +67,7 @@ namespace ThreatSource.Tests.Missile
_missileInitialMotion = new MotionParameters
{
Position = new Vector3D(3000, 0, 100),
Orientation = new Orientation(Math.PI, 0, 0),
Orientation = new Orientation(Math.PI/2, 0.0, 0.0), // 原Yaw=PI
InitialSpeed = 800
};

View File

@ -30,7 +30,7 @@ namespace ThreatSource.Tests.Missile
var tankInitialMotion = new MotionParameters
{
Position = new Vector3D(100, 0, 100), // 目标位置
Orientation = new Orientation(Math.PI/4, 0, 0), // 目标朝向45度
Orientation = new Orientation(-Math.PI/2, Math.PI/4, 0), // 目标朝向45度 (Original Yaw=PI/4)
InitialSpeed = 0
};
@ -66,7 +66,7 @@ namespace ThreatSource.Tests.Missile
_submunitionInitialMotion = new MotionParameters
{
Position = new Vector3D(1100, 400, 100), // 在目标前方1000米高度400米
Orientation = new Orientation(0, -Math.PI/4, 0), // 向下45度
Orientation = new Orientation(-Math.PI/2, -Math.PI/4, 0), // 向下45度 (Original Pitch=-PI/4, Yaw=0)
InitialSpeed = 200
};

View File

@ -249,7 +249,7 @@ namespace ThreatSource.Tests.Utils
// 背景 (目标: Tank_1)
Vector3D backgroundCenter = new Vector3D(0.0, 1.2, 0.0);
Vector3D backgroundDims = new Vector3D(3.5, 2.4, 10.0); // W=3.5, H=2.4, L=10.0
Orientation backgroundOrient = new Orientation(Math.PI, 0.0, 0.0); // Yaw=180 deg
Orientation backgroundOrient = new Orientation(Math.PI/2, 0.0, 0.0); // Yaw=180 deg (原Yaw=PI)
// 前景 (烟幕: SG_2)
Vector3D foregroundCenter = new Vector3D(50.0, 5.0, 0.0);

View File

@ -34,7 +34,7 @@
"focusedSpotDiameter": 0.006,
"reflectionCoefficient": 0.2,
"targetReflectiveArea": 1.0,
"lockThreshold": 1e-10,
"lockThreshold": 1e-7,
"spotOffsetSensitivity": 0.05,
"jammingResistanceThreshold": 1e-5,
"transmitterEfficiency": 0.85,

View File

@ -853,7 +853,7 @@ namespace ThreatSource.Guidance
totalTransmittance *= transmittanceForThisSmoke; // 叠加衰减效应(透过率相乘)
// 如果透过率已经很低,可以提前退出以优化
if (totalTransmittance < 0.001)
if (totalTransmittance < 0.005)
{
return 0.0;
}

View File

@ -316,8 +316,7 @@ namespace ThreatSource.Indicator
// 5. 遍历烟幕检查遮挡
foreach (var jammerId in activeJammerIds)
{
var smokeGrenade = SimulationManager.GetEntityById(jammerId) as SmokeGrenade;
if (smokeGrenade == null)
if (SimulationManager.GetEntityById(jammerId) is not SmokeGrenade smokeGrenade || smokeGrenade.config == null)
{
continue;
}
@ -327,13 +326,20 @@ namespace ThreatSource.Indicator
Vector3D smokeCenter = smokeGrenade.Position;
Orientation smokeOrient = smokeGrenade.Orientation;
Vector3D smokeDims;
if (smokeGrenade.config.SmokeType == SmokeScreenType.Cloud)
{
smokeDims = new(smokeGrenade.config.Thickness, smokeGrenade.config.CloudDiameter, smokeGrenade.config.CloudDiameter);
// 对于云状烟幕 (近似为圆柱体):
// Diameter (X, Z axes), Thickness (Y axis)
// 使用配置值,确保顺序正确
smokeDims = new(smokeGrenade.config.CloudDiameter, smokeGrenade.config.Thickness, smokeGrenade.config.CloudDiameter);
}
else // Wall
{
smokeDims = new(smokeGrenade.config.Thickness, smokeGrenade.config.WallHeight, smokeGrenade.config.WallWidth);
// 对于墙状烟幕:
// WallWidth (X axis), WallHeight (Y axis), Thickness (Z axis)
// 使用配置值,确保顺序正确
smokeDims = new(smokeGrenade.config.WallWidth, smokeGrenade.config.WallHeight, smokeGrenade.config.Thickness);
}
double ratio = ObscurationUtils.CalculateProjectedOverlapRatio(

View File

@ -1,5 +1,6 @@
using ThreatSource.Utils;
using ThreatSource.Simulation;
using System.Numerics;
namespace ThreatSource.Jammer
{
@ -164,239 +165,237 @@ namespace ThreatSource.Jammer
/// <returns>穿过的烟雾厚度如果没有穿过烟雾则返回0</returns>
public double GetSmokeThicknessOnLine(Vector3D observerPosition, Vector3D targetPosition)
{
if (!IsActive || !IsJamming) return 0;
if (!IsActive || !IsJamming || CurrentParameters == null) return 0;
// 计算视线向量
Vector3D lineDirection = (targetPosition - observerPosition).Normalize();
double lineLength = (targetPosition - observerPosition).Magnitude();
Vector3D lineDirection = (targetPosition - observerPosition); // 非单位向量
double lineLength = lineDirection.Magnitude();
if (lineLength < 1e-9) return 0.0; //起点终点重合
Vector3D unitLineDirection = lineDirection / lineLength; // 单位向量
if (config.SmokeType == SmokeScreenType.Cloud)
{
// 对于云状烟幕,计算视线与圆柱体的交点
return CalculateCloudIntersection(observerPosition, lineDirection, lineLength);
return CalculateCloudIntersection(observerPosition, unitLineDirection, lineLength);
}
else // SmokeScreenType.Wall
{
// 对于墙状烟幕,计算视线与平面的交点
return CalculateWallIntersection(observerPosition, lineDirection, lineLength);
// 对于墙状烟幕,调用新的OBB相交方法
return CalculateRayOBBIntersectionLength(observerPosition, unitLineDirection, lineLength);
}
}
/// <summary>
/// 计算视线与云状烟幕的交点并返回穿过的厚度
/// 计算射线与云状烟幕(视为圆柱体)的相交长度。
/// </summary>
/// <param name="origin">射线起点(世界坐标)。</param>
/// <param name="direction">射线方向(世界坐标,单位向量)。</param>
/// <param name="maxDistance">射线的最大有效长度。</param>
/// <returns>射线在烟幕中穿行的长度;如果不相交或烟幕无效,则返回 0。</returns>
private double CalculateCloudIntersection(Vector3D origin, Vector3D direction, double maxDistance)
{
if (!IsActive || !IsJamming || CurrentParameters == null) return 0;
// 将射线起点转换到烟幕的局部坐标系(烟幕中心为原点)
Vector3D localOrigin = origin - Position;
double currentThickness = CurrentParameters.SmokeThickness ?? config.Thickness;
double halfHeight = currentThickness / 2.0;
double lineStartY = localOrigin.Y; // For early exit check if desired, or remove for pure general approach
double lineEndY = localOrigin.Y + direction.Y * maxDistance; // For early exit check
double currentThickness = CurrentParameters.SmokeThickness ?? config.Thickness; // 当前烟幕高度/厚度
double halfHeight = currentThickness / 2.0; // 半高
// Optional: Early Y-axis exit. Can be removed if the general calculation below is preferred to handle all cases.
if ((lineStartY > halfHeight && lineEndY > halfHeight) ||
(lineStartY < -halfHeight && lineEndY < -halfHeight))
{
// This check is valid as an optimization. If removed, the logic below should still yield 0.
// return 0;
}
// 1. Calculate t-interval for Y slab [t_y_min_inf, t_y_max_inf]
// 1. 计算射线与Y轴定义的平板无限延伸的上下界的相交t值区间 [t_y_min_inf, t_y_max_inf]
// t值表示沿射线方向的距离参数
double t_y_min_inf, t_y_max_inf;
if (Math.Abs(direction.Y) < 1e-9) // Horizontal line
if (Math.Abs(direction.Y) < 1e-9) // 射线平行于XZ平面 (近似水平)
{
// 如果水平射线在圆柱体的高度范围内
if (Math.Abs(localOrigin.Y) <= halfHeight)
{
// 则它在Y方向上与无限平板的相交是无限的
t_y_min_inf = double.NegativeInfinity; t_y_max_inf = double.PositiveInfinity;
}
else
{
return 0; // Horizontal line outside Y-bounds of cylinder
// 水平射线在圆柱体的高度范围之外,不相交
return 0;
}
}
else // Non-horizontal line
else // 射线不平行于XZ平面 (有垂直分量)
{
double ty1 = (-halfHeight - localOrigin.Y) / direction.Y;
double ty2 = (halfHeight - localOrigin.Y) / direction.Y;
t_y_min_inf = Math.Min(ty1, ty2);
t_y_max_inf = Math.Max(ty1, ty2);
// 计算射线与上下两个无限平面的交点t值
double ty1 = (-halfHeight - localOrigin.Y) / direction.Y; // 交于 y = -halfHeight 平面
double ty2 = (halfHeight - localOrigin.Y) / direction.Y; // 交于 y = +halfHeight 平面
t_y_min_inf = Math.Min(ty1, ty2); // 取较小的t作为进入点
t_y_max_inf = Math.Max(ty1, ty2); // 取较大的t作为退出点
}
// 2. Calculate t-interval for XZ infinite cylinder [t_xz_min_inf, t_xz_max_inf]
// 2. 计算射线与XZ平面上无限长圆柱半径为CloudDiameter/2的相交t值区间 [t_xz_min_inf, t_xz_max_inf]
double t_xz_min_inf, t_xz_max_inf;
double radius = config.CloudDiameter / 2.0;
double radius_sq = radius * radius;
// 使用 CurrentParameters 中的 SmokeDiameter (如果存在),否则使用 config 中的 CloudDiameter
double radius = (CurrentParameters.SmokeDiameter ?? config.CloudDiameter) / 2.0; // 当前烟幕云的半径
double radius_sq = radius * radius; // 半径的平方
// 射线方程 P(t) = localOrigin + t * direction
// 圆柱方程 (localOrigin.X + t*direction.X)^2 + (localOrigin.Z + t*direction.Z)^2 = radius_sq
// 展开后得到 At^2 + Bt + C = 0 形式的二次方程,其中 t 是我们要解的
// A = direction.X^2 + direction.Z^2
// B = 2 * (localOrigin.X * direction.X + localOrigin.Z * direction.Z)
// C = localOrigin.X^2 + localOrigin.Z^2 - radius_sq
double A_xz = direction.X * direction.X + direction.Z * direction.Z;
double B_xz = 2 * (localOrigin.X * direction.X + localOrigin.Z * direction.Z);
double C_xz = localOrigin.X * localOrigin.X + localOrigin.Z * localOrigin.Z - radius_sq;
if (Math.Abs(A_xz) < 1e-9) // Line's XZ projection is a point (line is ~vertical)
if (Math.Abs(A_xz) < 1e-9) // 射线的XZ投影是一个点 (即射线近似垂直于XZ平面)
{
if (C_xz <= 1e-9) // localOrigin.XZ is inside or on the disk (C_xz = d^2 - r^2 <= 0)
// 如果这个点在圆盘内部或边界上 (C_xz <= 0 意味着 localOrigin的XZ投影到圆心的距离平方 <= radius_sq)
if (C_xz <= 1e-9)
{
// 射线在XZ方向上与无限圆柱的相交是无限的
t_xz_min_inf = double.NegativeInfinity; t_xz_max_inf = double.PositiveInfinity;
}
else
{
return 0; // Vertical line's XZ proj. is outside disk
// 垂直射线在圆盘之外,不相交
return 0;
}
}
else // Line's XZ projection is a line
else // 射线的XZ投影是一条线
{
double discriminant_xz = B_xz * B_xz - 4 * A_xz * C_xz;
if (discriminant_xz < -1e-9) { return 0; } // No XZ intersection (allow small tolerance for negative discriminant)
if (discriminant_xz < 0) discriminant_xz = 0; // Clamp if slightly negative due to precision
double discriminant_xz = B_xz * B_xz - 4 * A_xz * C_xz; // 二次方程的判别式
if (discriminant_xz < -1e-9) { return 0; } // 判别式为负,射线不与无限圆柱相交 (允许微小负值容差)
if (discriminant_xz < 0) discriminant_xz = 0; // 如果因精度问题略小于0则修正为0
double sqrt_discriminant_xz = Math.Sqrt(discriminant_xz);
// 求解二次方程得到两个t值
double txz1 = (-B_xz - sqrt_discriminant_xz) / (2 * A_xz);
double txz2 = (-B_xz + sqrt_discriminant_xz) / (2 * A_xz);
t_xz_min_inf = Math.Min(txz1, txz2);
t_xz_max_inf = Math.Max(txz1, txz2);
t_xz_min_inf = Math.Min(txz1, txz2); // 取较小的t作为进入点
t_xz_max_inf = Math.Max(txz1, txz2); // 取较大的t作为退出点
}
// 3. Intersect the t-intervals for the infinite shapes
double t_overall_min_inf = Math.Max(t_y_min_inf, t_xz_min_inf);
double t_overall_max_inf = Math.Min(t_y_max_inf, t_xz_max_inf);
// 3. 合并两个无限形状Y轴平板和XZ无限圆柱的t值区间得到射线与无限高、指定半径的圆柱体的相交t区间
double t_overall_min_inf = Math.Max(t_y_min_inf, t_xz_min_inf); // 取各区间最小值的最大者
double t_overall_max_inf = Math.Min(t_y_max_inf, t_xz_max_inf); // 取各区间最大值的最小者
// 如果合并后的区间无效 (例如 min > max),则表示不相交
if (t_overall_min_inf >= t_overall_max_inf)
{
return 0; // No common t-interval for intersection with infinite cylinder bounded by height
return 0;
}
// 4. Clip with the line segment's own extent [0, maxDistance]
double final_entry_t = Math.Max(0, t_overall_min_inf);
double final_exit_t = Math.Min(maxDistance, t_overall_max_inf);
// 4. 将无限圆柱的相交区间与射线的实际有限长度 [0, maxDistance] 进行裁剪
// 确保交点在射线的有效范围内
double final_entry_t = Math.Max(0, t_overall_min_inf); // 进入点不能早于射线起点 (t=0)
double final_exit_t = Math.Min(maxDistance, t_overall_max_inf); // 退出点不能超出射线的最大长度
// 如果裁剪后的区间无效,则表示实际射线段不与圆柱体相交
if (final_entry_t >= final_exit_t)
{
return 0; // Intersection is outside the actual line segment
return 0;
}
// 返回射线在圆柱体内部穿行的长度
return final_exit_t - final_entry_t;
}
/// <summary>
/// 计算视线与墙状烟幕的交点并返回穿过的厚度
/// </summary>
private double CalculateWallIntersection(Vector3D origin, Vector3D direction, double maxDistance)
private (Vector3D obbAxisX, Vector3D obbAxisY, Vector3D obbAxisZ) GetWallOBBAxes()
{
if (!IsActive || !IsJamming || CurrentParameters == null) return 0;
// 将视线转换到烟幕局部坐标系
Vector3D localOrigin = origin - Position;
// 获取墙面法向量(与烟幕朝向一致)
Vector3D wallNormal = Orientation.ToVector();
// 计算视线与墙面的夹角余弦
double cosAngle = Vector3D.DotProduct(direction, wallNormal);
if (Math.Abs(cosAngle) < 1e-6)
{
return 0; // 视线平行于墙面
}
// 调用 ObscurationUtils 来获取基于当前烟幕朝向的局部坐标轴在世界空间中的表示
// GetOrientationBasisVectors 返回 (Right, Up, Forward)
// Right -> local X axis
// Up -> local Y axis
// Forward -> local -Z axis (which is the wall's normal / primary orientation vector)
var (localX, localY, localNegativeZ) = ObscurationUtils.GetOrientationBasisVectors(this.Orientation);
// 计算观察点到墙面中心的距离
double distToWallCenter = Vector3D.DotProduct(localOrigin, wallNormal);
double currentThickness = CurrentParameters.SmokeThickness ?? config.Thickness;
// 如果观察点距离墙面中心的距离超过厚度的一半,且视线方向背向墙面,则不可能相交
if (Math.Abs(distToWallCenter) > currentThickness / 2 &&
Math.Sign(distToWallCenter) == Math.Sign(cosAngle))
{
return 0;
}
// 计算视线与中心平面的交点参数
double t = -Vector3D.DotProduct(localOrigin, wallNormal) / cosAngle;
if (t < 0 || t > maxDistance)
{
return 0; // 交点不在视线段上
}
// 计算交点位置
Vector3D intersection = localOrigin + direction * t;
// 检查交点是否在墙面范围内
// 1. 检查高度
double currentHeight = CurrentParameters.SmokeWallHeight ?? config.WallHeight;
if (Math.Abs(intersection.Y) > currentHeight / 2)
{
return 0;
}
// 2. 检查宽度(沿墙面方向)
Vector3D wallParallel = Vector3D.CrossProduct(wallNormal, Vector3D.UnitY);
double distAlongWall = Math.Abs(Vector3D.DotProduct(intersection, wallParallel));
double currentWidth = CurrentParameters.SmokeWallWidth ?? config.WallWidth;
if (distAlongWall > currentWidth / 2)
{
return 0;
}
// 3. 检查交点到墙面中心的距离是否在厚度范围内
double distFromWallCenter = Math.Abs(Vector3D.DotProduct(intersection, wallNormal));
if (distFromWallCenter > currentThickness / 2)
{
return 0;
}
// 如果交点在墙面范围内,返回穿过的厚度
return currentThickness / Math.Abs(cosAngle);
// 对于墙状烟幕:
// obbAxisX (宽度方向) 对应局部X轴
// obbAxisY (高度方向) 对应局部Y轴
// obbAxisZ (厚度/法线方向) 对应局部-Z轴
return (localX, localY, localNegativeZ);
}
/// <summary>
/// 判断给定点是否在烟幕范围内(内部辅助方法)
/// </summary>
private bool IsPointInSmoke(Vector3D point)
private double CalculateRayOBBIntersectionLength(
Vector3D rayOrigin_world,
Vector3D rayDirection_world, // 必须是单位向量
double maxDistance)
{
if (!IsActive || !IsJamming) return false;
// 计算相对位置
Vector3D relativePos = point - Position;
if (config.SmokeType == SmokeScreenType.Cloud)
if (!IsActive || !IsJamming || CurrentParameters == null) return 0.0;
Vector3D obbCenter_world = Position;
Vector3D obbDimensions = new(
CurrentParameters.SmokeWallWidth ?? config.WallWidth,
CurrentParameters.SmokeWallHeight ?? config.WallHeight,
CurrentParameters.SmokeThickness ?? config.Thickness
);
if (obbDimensions.X <= 0 || obbDimensions.Y <= 0 || obbDimensions.Z <= 0) return 0.0;
(Vector3D obbAxisX_w, Vector3D obbAxisY_w, Vector3D obbAxisZ_w) = GetWallOBBAxes();
Vector3D rayOriginRelativeToOBBCenter_w = rayOrigin_world - obbCenter_world;
Vector3D rayOrigin_local = new(
Vector3D.DotProduct(rayOriginRelativeToOBBCenter_w, obbAxisX_w),
Vector3D.DotProduct(rayOriginRelativeToOBBCenter_w, obbAxisY_w),
Vector3D.DotProduct(rayOriginRelativeToOBBCenter_w, obbAxisZ_w)
);
Vector3D rayDirection_local = new Vector3D(
Vector3D.DotProduct(rayDirection_world, obbAxisX_w),
Vector3D.DotProduct(rayDirection_world, obbAxisY_w),
Vector3D.DotProduct(rayDirection_world, obbAxisZ_w)
);
Vector3D halfDims = obbDimensions * 0.5;
double tNear = 0.0;
double tFar = maxDistance;
// --- X Slab ---
if (Math.Abs(rayDirection_local.X) < 1e-9)
{
// 对于云状烟幕,视为圆柱体
// 计算垂直方向的距离(烟幕厚度)
double heightComponent = Math.Abs(relativePos.Y);
// 检查是否在烟幕厚度范围内
if (heightComponent > config.Thickness)
{
return false;
}
// 计算水平面内的位置
Vector3D planePos = new(relativePos.X, 0, relativePos.Z);
double radiusComponent = planePos.Magnitude();
// 检查径向距离是否在圆柱体范围内
return radiusComponent <= config.CloudDiameter / 2;
if (rayOrigin_local.X < -halfDims.X || rayOrigin_local.X > halfDims.X) return 0.0;
}
else // SmokeScreenType.Wall
else
{
// 对于墙状烟幕,需要检查:
// 1. 高度是否在范围内
if (Math.Abs(relativePos.Y) > config.WallHeight / 2)
{
return false;
}
// 2. 计算点到墙面的距离
double distAlongNormal = Math.Abs(Vector3D.DotProduct(relativePos, Orientation.ToVector()));
if (distAlongNormal > config.Thickness)
{
return false;
}
// 3. 计算沿墙面方向的投影距离
Vector3D wallParallel = Vector3D.CrossProduct(Orientation.ToVector(), Vector3D.UnitY);
double distAlongWall = Math.Abs(Vector3D.DotProduct(relativePos, wallParallel));
return distAlongWall <= config.WallWidth / 2;
double t1x = (-halfDims.X - rayOrigin_local.X) / rayDirection_local.X;
double t2x = (halfDims.X - rayOrigin_local.X) / rayDirection_local.X;
if (t1x > t2x) { double temp = t1x; t1x = t2x; t2x = temp; }
tNear = Math.Max(tNear, t1x);
tFar = Math.Min(tFar, t2x);
if (tNear >= tFar) return 0.0;
}
// --- Y Slab ---
if (Math.Abs(rayDirection_local.Y) < 1e-9)
{
if (rayOrigin_local.Y < -halfDims.Y || rayOrigin_local.Y > halfDims.Y) return 0.0;
}
else
{
double t1y = (-halfDims.Y - rayOrigin_local.Y) / rayDirection_local.Y;
double t2y = (halfDims.Y - rayOrigin_local.Y) / rayDirection_local.Y;
if (t1y > t2y) { double temp = t1y; t1y = t2y; t2y = temp; }
tNear = Math.Max(tNear, t1y);
tFar = Math.Min(tFar, t2y);
if (tNear >= tFar) return 0.0;
}
// --- Z Slab ---
if (Math.Abs(rayDirection_local.Z) < 1e-9)
{
if (rayOrigin_local.Z < -halfDims.Z || rayOrigin_local.Z > halfDims.Z) return 0.0;
}
else
{
double t1z = (-halfDims.Z - rayOrigin_local.Z) / rayDirection_local.Z;
double t2z = (halfDims.Z - rayOrigin_local.Z) / rayDirection_local.Z;
if (t1z > t2z) { double temp = t1z; t1z = t2z; t2z = temp; }
tNear = Math.Max(tNear, t1z);
tFar = Math.Min(tFar, t2z);
if (tNear >= tFar) return 0.0;
}
if (tFar <= 1e-9) return 0.0;
double intersectionLength = tFar - tNear;
return (intersectionLength > 1e-9) ? intersectionLength : 0.0;
}
}
}

View File

@ -278,7 +278,13 @@ namespace ThreatSource.Utils
return rotatedVector;
}
private static Matrix4x4 CalculateRotationMatrix(Orientation orientation, double misalignmentAngle)
/// <summary>
/// 计算旋转矩阵
/// </summary>
/// <param name="orientation">方向</param>
/// <param name="misalignmentAngle">失调角</param>
/// <returns>旋转矩阵</returns>
public static Matrix4x4 CalculateRotationMatrix(Orientation orientation, double misalignmentAngle)
{
// 创建旋转矩阵
Matrix4x4 yawRotation = Matrix4x4.CreateRotationY((float)orientation.Yaw);
@ -298,6 +304,20 @@ namespace ThreatSource.Utils
return new Vector3D(x, y, z);
}
/// <summary>
/// 使用矩阵变换方向向量(不应用平移)
/// </summary>
/// <param name="direction">要变换的方向向量</param>
/// <param name="matrix">变换矩阵</param>
/// <returns>变换后的方向向量</returns>
public static Vector3D TransformDirection(Vector3D direction, Matrix4x4 matrix)
{
double x = direction.X * matrix.M11 + direction.Y * matrix.M21 + direction.Z * matrix.M31;
double y = direction.X * matrix.M12 + direction.Y * matrix.M22 + direction.Z * matrix.M32;
double z = direction.X * matrix.M13 + direction.Y * matrix.M23 + direction.Z * matrix.M33;
return new Vector3D(x, y, z);
}
/// <summary>
/// 计算两个向量之间的角度
/// </summary>
@ -398,55 +418,46 @@ namespace ThreatSource.Utils
throw new NotImplementedException();
}
/// <summary>
/// 根据给定的方向向量创建方向
/// </summary>
/// <param name="direction">方向向量</param>
/// <returns>对应的方向</returns>
public static Orientation LookAt(Vector3D direction)
{
double yaw = Math.Atan2(direction.Z, direction.X);
double pitch = Math.Atan2(direction.Y, Math.Sqrt(direction.X * direction.X + direction.Z * direction.Z));
return new Orientation(yaw, pitch, 0);
}
/// <summary>
/// 将方向转换为单位向量
/// </summary>
/// <returns>对应的单位向量</returns>
public readonly Vector3D ToVector()
{
double cosYaw = Math.Cos(Yaw);
double sinYaw = Math.Sin(Yaw);
// Yaw: 绕 Y 轴 (Up), Pitch: 绕 X 轴 (Right)
// -Z Forward convention
// X_forward = -cos(Pitch) * sin(Yaw)
// Y_forward = sin(Pitch)
// Z_forward = -cos(Pitch) * cos(Yaw)
double cosPitch = Math.Cos(Pitch);
double sinPitch = Math.Sin(Pitch);
double cosYaw = Math.Cos(Yaw);
double sinYaw = Math.Sin(Yaw);
return new Vector3D(
cosYaw * cosPitch,
sinPitch,
sinYaw * cosPitch
);
double x = -cosPitch * sinYaw;
double y = sinPitch;
double z = -cosPitch * cosYaw;
return new Vector3D(x, y, z);
}
/// <summary>
/// 从向量创建方向
/// 从向量创建方向 (基于向量代表期望的 -Z 前向)
/// </summary>
/// <param name="vector">输入向量</param>
/// <returns>对应的方向</returns>
public static Orientation FromVector(Vector3D vector)
/// <param name="worldForwardVector">输入的世界前向单位向量</param>
/// <returns>对应的方向 (RHS/CCW 欧拉角)</returns>
public static Orientation FromVector(Vector3D worldForwardVector)
{
Vector3D normalized = vector.Normalize();
double pitch = Math.Asin(normalized.Y);
double yaw;
if (Math.Abs(normalized.Y) > 0.9999) // 接近垂直
{
yaw = 0; // 或者保持之前的偏航角
}
else
{
yaw = Math.Atan2(normalized.Z, normalized.X);
}
return new Orientation(yaw, pitch, 0);
Vector3D fwd = worldForwardVector.Normalize();
// Pitch = arcsin(fwd.Y)
// Yaw = atan2(-fwd.X, -fwd.Z)
double pitch = Math.Asin(fwd.Y); // Pitch 范围 [-PI/2, PI/2]
double yaw = Math.Atan2(-fwd.X, -fwd.Z); // Yaw 范围 [-PI, PI]
// Gimbal lock: 当 pitch 为 +/-PI/2 时fwd.X 和 fwd.Z 理论上为0。
// Math.Atan2(0,0) 通常返回0这对于偏航角来说是一个合理的约定。
// 此时,滚转角 Roll 和偏航角 Yaw 会指向同一旋转自由度。我们约定 Roll 为0。
return new Orientation(yaw, pitch, 0); // Roll 设为 0
}
}

View File

@ -1,4 +1,5 @@
using System.Diagnostics;
using System.Numerics;
namespace ThreatSource.Utils
{
@ -115,46 +116,34 @@ namespace ThreatSource.Utils
return corners;
}
private static (Vector3D xAxis, Vector3D yAxis, Vector3D zAxis) GetOrientationBasisVectors(Orientation orientation)
/// <summary>
/// 计算定向包围盒 (OBB) 的局部轴向量。
/// </summary>
/// <param name="orientation">对象的朝向。</param>
/// <returns>一个包含局部 X、Y、Z 轴向量的元组。</returns>
public static (Vector3D xAxis, Vector3D yAxis, Vector3D zAxis) GetOrientationBasisVectors(Orientation orientation)
{
// 1. 获取局部 X 轴 (根据 Common.cs 的实现, ToVector 代表局部 X)
Vector3D localX = orientation.ToVector().Normalize();
// 1. 使用 Orientation 的 Yaw, Pitch, Roll (已是 RHS/CCW) 构建旋转矩阵。
// 我们假设 ObscurationUtils 不需要额外的 misalignmentAngle所以设为 0.0。
// 旋转顺序 Y * X * Z (Yaw, Pitch, Roll)
Matrix4x4 rotationMatrix = Vector3D.CalculateRotationMatrix(orientation, 0.0);
// 2. 定义世界向上方向 (通常是 Y 轴)
Vector3D worldUp = Vector3D.UnitY;
// 2. 变换标准基向量以获得世界坐标中的局部轴。
// 使用 TransformDirection 来确保不应用平移。
// 检查 localX 是否几乎与 worldUp 平行 (指向正上或正下)
if (Math.Abs(Vector3D.DotProduct(localX, worldUp)) > 0.999)
{
worldUp = Vector3D.UnitX;
if(Math.Abs(Vector3D.DotProduct(localX, worldUp)) > 0.999)
{
worldUp = Vector3D.UnitZ;
}
}
// 物体的局部右向 (+X_local) 在世界坐标中的表示
Vector3D worldX_local = Vector3D.TransformDirection(Vector3D.UnitX, rotationMatrix);
// 3. 计算局部 Z 轴 (前向) = localX x worldUp
Vector3D localZ = Vector3D.CrossProduct(localX, worldUp).Normalize();
// 物体的局部向上 (+Y_local) 在世界坐标中的表示
Vector3D worldY_local = Vector3D.TransformDirection(Vector3D.UnitY, rotationMatrix);
// 物体的局部前向 (-Z_local) 在世界坐标中的表示
// 因为我们的约定是 -Z 为前,所以变换 -Vector3D.UnitZ
Vector3D worldNegativeZ_local = Vector3D.TransformDirection(-Vector3D.UnitZ, rotationMatrix);
// 4. 再次检查叉积结果是否有效 (防止 localX 与选择的 worldUp 平行)
if (localZ.MagnitudeSquared() < 1e-9)
{
Vector3D alternativeAux = (worldUp == Vector3D.UnitY) ? Vector3D.UnitX : Vector3D.UnitY;
if(alternativeAux == worldUp) alternativeAux = Vector3D.UnitZ;
localZ = Vector3D.CrossProduct(localX, alternativeAux).Normalize();
if (localZ.MagnitudeSquared() < 1e-9) // 极端情况
{
return (Vector3D.UnitX, Vector3D.UnitY, Vector3D.UnitZ);
}
}
// 5. 计算局部 Y 轴 (向上) = localZ x localX
Vector3D localY = Vector3D.CrossProduct(localZ, localX).Normalize();
// 返回正确的轴顺序 (Right, Up, Forward)
return (localX, localY, localZ);
// 确保返回的轴是归一化的 (TransformDirection 不保证归一化尽管输入是单位向量且旋转矩阵是正交的结果长度应为1)
// 为安全起见,可以添加 .Normalize()。
return (worldX_local.Normalize(), worldY_local.Normalize(), worldNegativeZ_local.Normalize());
}
/// <summary>

View File

@ -0,0 +1,93 @@
# Orientation 系统重构方案 (RHS, CCW)
## 1. 引言
本文档概述了项目内部 `Orientation` 系统重构的最终方案。目标是建立一个清晰、一致且技术上合理的坐标系和旋转约定,主要目的是简化与标准数学库(如 `System.Numerics.Matrix4x4`)的集成,并遵守项目规范。
## 2. 选定的坐标系和旋转约定
经过讨论和评估各种替代方案后,项目将采用以下约定:
* **坐标系类型**: 右手坐标系 (RHS)。
* **主轴定义**:
* **Y 轴**: 指向**上** (例如,世界坐标中的 `(0,1,0)`)。
* **X 轴**: 指向**右** (例如,世界坐标中的 `(1,0,0)`)。
* **Z 轴**: 指向**外/后** (例如,世界坐标中的 `(0,0,1)`,相对于典型的朝前摄像机视图)。
* **物体前向**: 物体的"前向"将定义为指向世界坐标系或其局部参考系的**负 Z 轴 (-Z)** (例如,未旋转且与世界轴对齐时为 `(0,0,-1)`)。
* **旋转正方向**: 当沿着正轴朝向原点观察时(标准右手定则),为**逆时针 (CCW)**。
* **存储在 `Orientation` 中的欧拉角 (`Yaw`, `Pitch`, `Roll`)**:
* 这些角度将直接表示基于 RHS 和 CCW 约定的旋转。
* **正角度增量的影响**:
* **正 `Yaw` (绕 Y 轴, CCW)**: 物体的前方 (其 -Z 方向) 向其**左侧**偏转。
* **正 `Pitch` (绕 X 轴, CCW)**: 物体的前方 (其 -Z 方向) **向上**倾斜。
* **正 `Roll` (绕 Z 轴, CCW)**: 物体的顶部 (其 Y 方向) 向其**左侧**倾斜 (左滚)。
* *注意: 用户和开发者需要适应这些标准的泰特-布莱恩角效应,其中定义的偏航、俯仰和滚转的正旋转可能分别对应于向左、向上和向左滚动的运动。像"右转"这样的操作将需要一个负的 Yaw 角。*
## 3. `Orientation` 类 (`Common.cs`) 的核心更改
### 3.1. `ToVector()` 方法
此方法计算对象在世界空间中的前向向量 (即其局部 -Z 轴经其姿态变换后的向量)。
* 输入: 使用存储的 RHS/CCW `Yaw``Pitch` 角 (Roll 不改变主前向向量的方向,仅改变其围绕该向量的姿态)。
* 默认前向 (Yaw=0, Pitch=0, Roll=0 时): `(0,0,-1)`
* 公式 (RHS, Y-Up, -Z Forward, CCW 欧拉角 Yaw, Pitch):
* `X_forward = -cos(Pitch) * sin(Yaw)`
* `Y_forward = sin(Pitch)`
* `Z_forward = -cos(Pitch) * cos(Yaw)`
返回的 `Vector3D(X_forward, Y_forward, Z_forward)` 是表示对象前向的单位向量。
### 3.2. `FromVector(Vector3D worldForwardVector)` / `LookAt(Vector3D worldTargetDirection)` 方法
此方法计算使对象朝向给定 `worldForwardVector` 所需的 RHS/CCW 欧拉角。
* 输入: `worldForwardVector` (世界空间中的单位向量)。
* 计算出的欧拉角 (RHS/CCW):
* `Pitch = Math.Asin(worldForwardVector.Y)`
* `Yaw = Math.Atan2(-worldForwardVector.X, -worldForwardVector.Z)`
* 这些计算出的 `Yaw``Pitch` 值直接存储在 `Orientation` 实例中。Roll 通常设置为 0。
## 4. 与 `System.Numerics.Matrix4x4` 的交互
* 存储在 `Orientation` 中的 `Yaw`、`Pitch` 和 `Roll` 角 (由于是 RHS/CCW) 可以**直接传递**给 `System.Numerics.Matrix4x4.CreateRotationY()`、`CreateRotationX()` 和 `CreateRotationZ()`
* 由于约定一致,此阶段不需要进行角度符号反转或复杂的左右手性转换。
* `Vector3D.CalculateRotationMatrix()` 中的示例:
```csharp
// orientation.Yaw, orientation.Pitch, orientation.Roll 已经是 RHS/CCW
Matrix4x4 yawRotation = Matrix4x4.CreateRotationY((float)orientation.Yaw);
Matrix4x4 pitchRotation = Matrix4x4.CreateRotationX((float)orientation.Pitch);
Matrix4x4 rollRotation = Matrix4x4.CreateRotationZ((float)orientation.Roll);
// 按照标准矩阵乘法顺序组合它们例如yaw * pitch * roll
```
## 5. `ObscurationUtils.GetOrientationBasisVectors()` 的更改
此方法应返回对象在世界坐标系中的局部轴 `(RightVector, UpVector, ForwardVector)`
* `forwardVec_world = orientation.ToVector().Normalize()` (这已经是正确的前向向量)。
* 如果使用基于矩阵的方法:
1. 从 `CalculateRotationMatrix` 获取组合旋转矩阵 `M` (它已经是表示姿态的正确 RHS 矩阵)。
2. `RightVector_world = M.MultiplyVector(Vector3D.UnitX)` (其中 `Vector3D.UnitX``(1,0,0)`)
3. `UpVector_world = M.MultiplyVector(Vector3D.UnitY)` (其中 `Vector3D.UnitY``(0,1,0)`)
4. `ForwardVector_world = M.MultiplyVector(-Vector3D.UnitZ)` (其中 `-Vector3D.UnitZ``(0,0,-1)`,表示局部前向)
* 返回这些向量 (归一化)。
* 或者,使用叉积 (假设 `forwardVec_world` 已知且 `Vector3D.UnitY` 是全局向上参考,如果对象有明显的翻滚,则需小心):
1. `RightVector_world = Vector3D.CrossProduct(Vector3D.UnitY, forwardVec_world).Normalize()`
2. `UpVector_world = Vector3D.CrossProduct(forwardVec_world, RightVector_world).Normalize()`
* 这种叉积方法更简单,但如果由于翻滚导致对象的局部"向上"与世界"向上"显著不同,则其鲁棒性较差。通常首选矩阵方法以确保准确性。
## 6. 主要影响和必要的适配
* **审查和修改 `new Orientation(yaw, pitch, roll)` 实例**: 这将是工作量最大的部分。所有使用特定欧拉角值直接实例化 `Orientation` 的现有代码都必须重新审查。需要理解这些角度的原始意图 (基于旧的 X-Forward 系统),并且必须计算新的 RHS/CCW 角度,以便为新的 -Z 前向约定实现相同的世界空间姿态。
* 示例: `new Orientation(0,0,0)` 现在将表示"朝向 -Z 世界轴",而不是"朝向 +X 世界轴"。
* **更新单元测试和集成测试**: 所有断言对象姿态、`ToVector()` 结果或任何依赖于姿态的行为的测试,都需要更新以反映新的 -Z 前向约定和 RHS/CCW 欧拉角效应。
## 7. 此方案的优势
* **简化与 `System.Numerics.Matrix4x4` 的集成**: 这是主要的好处。数学运算变得更直接,更不容易出错。
* **遵守项目规则**: 完全符合"右手坐标系Y-Up"规则。
* **与行业标准对齐**: RHS 与 CCW 旋转是许多 3D 图形 API 和物理引擎中的常见标准。
* **核心数学的清晰性**: 减少了在核心变换逻辑中进行左右手性或旋转方向的临时转换的需求。
## 8. 用户/开发者的适配
* 创建或解释 `Orientation` 欧拉角的用户和开发者必须理解,根据标准的 RHS/CCW 约定,正角度现在会导致向左偏航、向上俯仰和向左翻滚。需要"右转"、"向下俯仰"或"向右翻滚"的操作将涉及负角度值。
## 9. 潜在的改进点
在 Vector3D.Rotate 方法中,它调用了 ApplyRotationMatrix。根据其上下文Rotate 一个向量),如果这个向量代表的是一个方向而不是一个点,那么它也应该使用我们新创建的 TransformDirection 方法或者有类似的逻辑来避免平移。不过,这超出了我们当前对 Orientation 和 ObscurationUtils 的直接修改范围,可以作为一个后续的潜在改进点来记录。

View File

@ -192,8 +192,8 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(0, 1.2, 0),
Orientation = new Orientation(Math.PI/2, 0.0, 0.0),
InitialSpeed = 2.0
Orientation = new Orientation(0.0, 0.0, 0.0),
InitialSpeed = 1.0
};
string targetId = "Tank_1";
var target = _threatSourceFactory.CreateTarget(targetId, "mbt_001", motionParameters);
@ -210,7 +210,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(0, 0, 50),
Orientation = new Orientation(Math.PI, 0, 0),
Orientation = new Orientation(Math.PI/2, 0.0, 0.0),
InitialSpeed = 0.0
};
string laserDecoyId = "LDY_1";
@ -231,7 +231,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(50, 5, 0),
Orientation = Orientation.FromVector(Vector3D.UnitX),
Orientation = new Orientation(Math.PI/2, 0.0, 0.0),
InitialSpeed = 0.0
};
string smokeGrenadeId = "SG_1";
@ -264,7 +264,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParametersTop = new MotionParameters
{
Position = new Vector3D(0, 10, 0),
Orientation = Orientation.FromVector(Vector3D.UnitY),
Orientation = new Orientation(0, Math.PI/2, 0.0),
InitialSpeed = 0.0
};
smokeGrenadeId = "SG_4";
@ -284,8 +284,8 @@ namespace ThreatSource.Tools.MissileSimulation
{
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 10, 20),
Orientation = new Orientation(Math.PI, -0.01, 0),
Position = new Vector3D(2000, 10, 10),
Orientation = new Orientation(Math.PI/2, -0.01, 0),
InitialSpeed = 700
};
string missileId = "LSGM_1";
@ -303,7 +303,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 10, 10),
Orientation = new Orientation(Math.PI, -0.01, 0.0),
Orientation = new Orientation(Math.PI/2, -0.01, 0.0),
InitialSpeed = 300
};
string missileId = "LBRM_1";
@ -321,7 +321,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(3000, 0, 20),
Orientation = new Orientation(Math.PI, 0, 0),
Orientation = new Orientation(Math.PI/2, 0.0, 0.0),
InitialSpeed = 800
};
@ -341,7 +341,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0),
Orientation = new Orientation(Math.PI/2, 0.01, 0),
InitialSpeed = 300
};
@ -360,7 +360,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 10, 0),
Orientation = new Orientation(Math.PI, -0.001, 0),
Orientation = new Orientation(Math.PI/2, -0.001, 0),
InitialSpeed = 300
};
string missileId = "ITGM_1";
@ -378,7 +378,7 @@ namespace ThreatSource.Tools.MissileSimulation
var motionParameters = new MotionParameters
{
Position = new Vector3D(2000, 1, 20),
Orientation = new Orientation(Math.PI, 0.01, 0),
Orientation = new Orientation(Math.PI/2, 0.01, 0),
InitialSpeed = 300
};
string missileId = "MMWG_1";
@ -398,7 +398,7 @@ namespace ThreatSource.Tools.MissileSimulation
var laserDesignatorLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 1, 100),
Orientation = new Orientation(Math.PI, 0.01, 0),
Orientation = new Orientation(Math.PI/2, 0.01, 0),
InitialSpeed = 0
};
var laserDesignator = _threatSourceFactory.CreateIndicator(laserDesignatorId, "ld_001", "Tank_1", "LSGM_1", laserDesignatorLaunchParams);
@ -411,7 +411,7 @@ namespace ThreatSource.Tools.MissileSimulation
var laserBeamRiderLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 1, 10),
Orientation = new Orientation(Math.PI, 0, 0),
Orientation = new Orientation(Math.PI/2, 0, 0),
InitialSpeed = 0
};
var laserBeamRider = _threatSourceFactory.CreateIndicator(laserBeamRiderId, "br_001", "Tank_1", "LBRM_1", laserBeamRiderLaunchParams);
@ -424,7 +424,7 @@ namespace ThreatSource.Tools.MissileSimulation
var infraredTrackerLaunchParams = new MotionParameters
{
Position = new Vector3D(2100, 1, 0),
Orientation = new Orientation(Math.PI, 0, 0),
Orientation = new Orientation(Math.PI/2, 0, 0),
InitialSpeed = 0
};
var infraredTracker = _threatSourceFactory.CreateIndicator(infraredTrackerId, "ir_001", "Tank_1", "ITGM_1", infraredTrackerLaunchParams);