using System;
using System.Numerics;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.Utils
{
///
/// Rail 路径姿态辅助工具。
/// LegacySuspensionPoint 模式下保持世界 Z 偏移;
/// RailCenterLine 模式下按路径切线推导局部法向,支持斜轨的轨上/轨下偏移。
///
public static class RailPathPoseHelper
{
private const double TangentEpsilon = 1e-9;
private static bool _rotationConstructorConventionLogged;
///
/// 获取 Rail 参考点到构件对接点的有符号偏移(模型单位)。
/// LegacySuspensionPoint 模式下,路径点本身就是构件对接点,因此偏移为 0。
///
public static double GetAnchorOffset(PathRoute route)
{
if (route == null || route.PathType != PathType.Rail)
{
return 0.0;
}
if (route.RailPathDefinitionMode == RailPathDefinitionMode.LegacySuspensionPoint)
{
return 0.0;
}
return route.RailMountMode == RailMountMode.OverRail
? route.RailReferenceToAnchorOffset
: -route.RailReferenceToAnchorOffset;
}
///
/// 计算构件底面中心相对于 Rail 参考点的 Z 偏移(模型单位)。
///
public static double GetBottomZOffset(PathRoute route, double objectHeight)
{
double anchorOffset = GetAnchorOffset(route);
if (route == null || route.PathType != PathType.Rail)
{
return 0.0;
}
if (PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode))
{
return anchorOffset - objectHeight;
}
return anchorOffset;
}
///
/// 计算通行空间中心相对于 Rail 参考点的 Z 偏移(模型单位)。
/// 顶面对接时,通行空间中心位于对接点下方半高;
/// 底面对接时,通行空间中心位于对接点上方半高。
///
public static double GetObjectSpaceCenterZOffset(PathRoute route, double objectSpaceHeight)
{
double anchorOffset = GetAnchorOffset(route);
if (route == null || route.PathType != PathType.Rail)
{
return 0.0;
}
if (PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode))
{
return anchorOffset - objectSpaceHeight / 2.0;
}
return anchorOffset + objectSpaceHeight / 2.0;
}
///
/// 根据 Rail 路径参考点计算构件底面中心位置。
///
public static Point3D ResolveBottomPosition(PathRoute route, Point3D referencePoint, double objectHeight)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePoint = adapter.ToCanonicalPoint(referencePoint);
var canonicalUp = HostCoordinateAdapter.CanonicalUp;
double bottomOffset = GetBottomOffsetMagnitude(route, objectHeight);
var canonicalBottomPoint = new Point3D(
canonicalReferencePoint.X + canonicalUp.X * bottomOffset,
canonicalReferencePoint.Y + canonicalUp.Y * bottomOffset,
canonicalReferencePoint.Z + canonicalUp.Z * bottomOffset);
return adapter.FromCanonicalPoint(canonicalBottomPoint);
}
///
/// 根据 Rail 路径参考点和相邻点计算构件底面中心位置。
/// RailCenterLine 模式下沿轨道局部法向偏移,适用于斜轨。
///
public static Point3D ResolveBottomPosition(
PathRoute route,
Point3D referencePoint,
Point3D previousPoint,
Point3D nextPoint,
double objectHeight)
{
if (route == null || route.PathType != PathType.Rail)
{
return referencePoint;
}
if (route.RailPathDefinitionMode == RailPathDefinitionMode.LegacySuspensionPoint)
{
return ResolveBottomPosition(route, referencePoint, objectHeight);
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePoint = adapter.ToCanonicalPoint(referencePoint);
var normal = ResolveRailNormal(previousPoint, referencePoint, nextPoint);
double bottomOffset = GetBottomOffsetMagnitude(route, objectHeight);
var canonicalBottomPoint = new Point3D(
canonicalReferencePoint.X + normal.X * bottomOffset,
canonicalReferencePoint.Y + normal.Y * bottomOffset,
canonicalReferencePoint.Z + normal.Z * bottomOffset);
return adapter.FromCanonicalPoint(canonicalBottomPoint);
}
///
/// 根据 Rail 路径参考点和相邻点计算通行空间中心位置。
///
public static Point3D ResolveObjectSpaceCenterPosition(
PathRoute route,
Point3D referencePoint,
Point3D previousPoint,
Point3D nextPoint,
double objectSpaceHeight)
{
if (route == null || route.PathType != PathType.Rail)
{
return referencePoint;
}
double centerOffset = GetObjectSpaceCenterOffsetMagnitude(route, objectSpaceHeight);
if (route.RailPathDefinitionMode == RailPathDefinitionMode.LegacySuspensionPoint)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePoint = adapter.ToCanonicalPoint(referencePoint);
var canonicalCenterPoint = new Point3D(
canonicalReferencePoint.X,
canonicalReferencePoint.Y,
canonicalReferencePoint.Z + centerOffset);
return adapter.FromCanonicalPoint(canonicalCenterPoint);
}
var adapterForRail = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalReferencePointForRail = adapterForRail.ToCanonicalPoint(referencePoint);
var normal = ResolveRailNormal(previousPoint, referencePoint, nextPoint);
var canonicalCenterPointForRail = new Point3D(
canonicalReferencePointForRail.X + normal.X * centerOffset,
canonicalReferencePointForRail.Y + normal.Y * centerOffset,
canonicalReferencePointForRail.Z + normal.Z * centerOffset);
return adapterForRail.FromCanonicalPoint(canonicalCenterPointForRail);
}
///
/// 根据轨道路径切向和法向创建 Rail 构件完整三维姿态。
/// 约定:构件本地 X 轴为前进方向,本地 Z 轴为上方向。
/// 返回值是可直接用于 Transform3D 的线性矩阵。
///
public static bool TryCreateRailLinearTransform(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out Matrix3 linearTransform)
{
return TryCreateRailLinearTransform(
previousPoint,
currentPoint,
nextPoint,
ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
out linearTransform);
}
///
/// 根据轨道路径切向和法向创建 Rail 构件完整三维姿态。
/// 通过模型局部轴约定显式指定本地哪个轴代表 forward/up。
///
public static bool TryCreateRailLinearTransform(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
ModelAxisConvention axisConvention,
out Matrix3 linearTransform)
{
linearTransform = null;
if (axisConvention == null)
{
throw new ArgumentNullException(nameof(axisConvention));
}
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalPreviousPoint = ToNumerics(adapter.ToCanonicalPoint(previousPoint));
var canonicalCurrentPoint = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
var canonicalNextPoint = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
if (!CanonicalRailPoseBuilder.TryCreateBasis(
canonicalPreviousPoint,
canonicalCurrentPoint,
canonicalNextPoint,
HostCoordinateAdapter.CanonicalUpVector3,
out var canonicalForward,
out var canonicalLateral,
out var canonicalUp))
{
return false;
}
Quaternion canonicalRotation = axisConvention.CreateQuaternion(canonicalForward, canonicalUp);
Matrix4x4 canonicalLinear = Matrix4x4.CreateFromQuaternion(canonicalRotation);
var hostXAxis = Normalize(adapter.FromCanonicalVector(ToNavVector(new Vector3(canonicalLinear.M11, canonicalLinear.M21, canonicalLinear.M31))));
var hostYAxis = Normalize(adapter.FromCanonicalVector(ToNavVector(new Vector3(canonicalLinear.M12, canonicalLinear.M22, canonicalLinear.M32))));
var hostZAxis = Normalize(adapter.FromCanonicalVector(ToNavVector(new Vector3(canonicalLinear.M13, canonicalLinear.M23, canonicalLinear.M33))));
// Navisworks 在 Transform3D(Matrix3, ...) 中按列读取局部基向量:
// 第 1 列 = 本地 X 轴在世界中的方向
// 第 2 列 = 本地 Y 轴在世界中的方向
// 第 3 列 = 本地 Z 轴在世界中的方向
linearTransform = new Matrix3(
hostXAxis.X, hostYAxis.X, hostZAxis.X,
hostXAxis.Y, hostYAxis.Y, hostZAxis.Y,
hostXAxis.Z, hostYAxis.Z, hostZAxis.Z);
LogManager.Info(
$"[Rail姿态] Canonical切向=({canonicalForward.X:F4},{canonicalForward.Y:F4},{canonicalForward.Z:F4}), " +
$"Canonical侧向=({canonicalLateral.X:F4},{canonicalLateral.Y:F4},{canonicalLateral.Z:F4}), " +
$"Canonical法向=({canonicalUp.X:F4},{canonicalUp.Y:F4},{canonicalUp.Z:F4}), " +
$"Host法向=({hostZAxis.X:F4},{hostZAxis.Y:F4},{hostZAxis.Z:F4}), " +
$"模型Forward={axisConvention.ForwardAxis}, 模型Up={axisConvention.UpAxis}");
return true;
}
///
/// 根据轨道路径切向和法向创建 Rail 构件完整三维旋转。
/// 约定:构件本地 X 轴为前进方向,本地 Z 轴为上方向。
///
public static bool TryCreateRailRotation(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
out Rotation3D rotation)
{
return TryCreateRailRotation(
previousPoint,
currentPoint,
nextPoint,
ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
out rotation);
}
public static bool TryCreateRailRotation(
Point3D previousPoint,
Point3D currentPoint,
Point3D nextPoint,
ModelAxisConvention axisConvention,
out Rotation3D rotation)
{
rotation = Rotation3D.Identity;
LogRotationConstructorConventionOnce();
if (!TryCreateRailLinearTransform(previousPoint, currentPoint, nextPoint, axisConvention, out var linearTransform))
{
return false;
}
rotation = CreateRotationFromLinearTransform(linearTransform);
var verifiedLinear = new Transform3D(rotation).Linear;
LogManager.Info(
$"[Rail旋转验证] X=({verifiedLinear.Get(0, 0):F4},{verifiedLinear.Get(1, 0):F4},{verifiedLinear.Get(2, 0):F4}), " +
$"Y=({verifiedLinear.Get(0, 1):F4},{verifiedLinear.Get(1, 1):F4},{verifiedLinear.Get(2, 1):F4}), " +
$"Z=({verifiedLinear.Get(0, 2):F4},{verifiedLinear.Get(1, 2):F4},{verifiedLinear.Get(2, 2):F4})");
return true;
}
private static Rotation3D CreateRotationFromLinearTransform(Matrix3 linearTransform)
{
double m00 = linearTransform.Get(0, 0);
double m01 = linearTransform.Get(0, 1);
double m02 = linearTransform.Get(0, 2);
double m10 = linearTransform.Get(1, 0);
double m11 = linearTransform.Get(1, 1);
double m12 = linearTransform.Get(1, 2);
double m20 = linearTransform.Get(2, 0);
double m21 = linearTransform.Get(2, 1);
double m22 = linearTransform.Get(2, 2);
double qx;
double qy;
double qz;
double qw;
double trace = m00 + m11 + m22;
if (trace > 0.0)
{
double s = Math.Sqrt(trace + 1.0) * 2.0;
qw = 0.25 * s;
qx = (m21 - m12) / s;
qy = (m02 - m20) / s;
qz = (m10 - m01) / s;
}
else if (m00 > m11 && m00 > m22)
{
double s = Math.Sqrt(1.0 + m00 - m11 - m22) * 2.0;
qw = (m21 - m12) / s;
qx = 0.25 * s;
qy = (m01 + m10) / s;
qz = (m02 + m20) / s;
}
else if (m11 > m22)
{
double s = Math.Sqrt(1.0 + m11 - m00 - m22) * 2.0;
qw = (m02 - m20) / s;
qx = (m01 + m10) / s;
qy = 0.25 * s;
qz = (m12 + m21) / s;
}
else
{
double s = Math.Sqrt(1.0 + m22 - m00 - m11) * 2.0;
qw = (m10 - m01) / s;
qx = (m02 + m20) / s;
qy = (m12 + m21) / s;
qz = 0.25 * s;
}
var rotation = new Rotation3D(qx, qy, qz, qw);
double error = CalculateLinearError(new Transform3D(rotation).Linear, linearTransform);
LogManager.Info(
$"[Rail旋转验证] quaternion候选=({qx:F4},{qy:F4},{qz:F4},{qw:F4}), 误差={error:F6}");
return rotation;
}
private static void LogRotationConstructorConventionOnce()
{
if (_rotationConstructorConventionLogged)
{
return;
}
_rotationConstructorConventionLogged = true;
LogKnownAxisRotation("X90", new UnitVector3D(1, 0, 0), Math.PI / 2.0);
LogKnownAxisRotation("Y90", new UnitVector3D(0, 1, 0), Math.PI / 2.0);
LogKnownAxisRotation("Z90", new UnitVector3D(0, 0, 1), Math.PI / 2.0);
}
private static void LogKnownAxisRotation(string name, UnitVector3D axis, double angle)
{
Rotation3D axisAngleRotation = new Rotation3D(axis, angle);
Rotation3D reconstructed = new Rotation3D(
axisAngleRotation.A,
axisAngleRotation.B,
axisAngleRotation.C,
axisAngleRotation.D);
Matrix3 linear = new Transform3D(reconstructed).Linear;
LogManager.Info(
$"[Rotation构造测试] {name}: " +
$"A={axisAngleRotation.A:F6}, B={axisAngleRotation.B:F6}, C={axisAngleRotation.C:F6}, D={axisAngleRotation.D:F6}, " +
$"LinearX=({linear.Get(0, 0):F4},{linear.Get(1, 0):F4},{linear.Get(2, 0):F4}), " +
$"LinearY=({linear.Get(0, 1):F4},{linear.Get(1, 1):F4},{linear.Get(2, 1):F4}), " +
$"LinearZ=({linear.Get(0, 2):F4},{linear.Get(1, 2):F4},{linear.Get(2, 2):F4})");
}
private static double CalculateLinearError(Matrix3 actual, Matrix3 expected)
{
double error = 0.0;
for (int row = 0; row < 3; row++)
{
for (int column = 0; column < 3; column++)
{
double delta = actual.Get(row, column) - expected.Get(row, column);
error += delta * delta;
}
}
return Math.Sqrt(error);
}
private static double GetBottomOffsetMagnitude(PathRoute route, double objectHeight)
{
double anchorOffset = GetAnchorOffset(route);
return PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
? anchorOffset - objectHeight
: anchorOffset;
}
private static double GetObjectSpaceCenterOffsetMagnitude(PathRoute route, double objectSpaceHeight)
{
double anchorOffset = GetAnchorOffset(route);
return PathRoute.IsTopPayloadAnchorForMountMode(route.RailMountMode)
? anchorOffset - objectSpaceHeight / 2.0
: anchorOffset + objectSpaceHeight / 2.0;
}
private static Vector3D ResolveRailNormal(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint)
{
var tangent = ResolveTangent(previousPoint, currentPoint, nextPoint);
double tangentLengthSquared = tangent.X * tangent.X + tangent.Y * tangent.Y + tangent.Z * tangent.Z;
if (tangentLengthSquared < TangentEpsilon)
{
return HostCoordinateAdapter.CanonicalUp;
}
tangent = new Vector3D(
tangent.X / Math.Sqrt(tangentLengthSquared),
tangent.Y / Math.Sqrt(tangentLengthSquared),
tangent.Z / Math.Sqrt(tangentLengthSquared));
var worldUp = HostCoordinateAdapter.CanonicalUp;
double projection = worldUp.X * tangent.X + worldUp.Y * tangent.Y + worldUp.Z * tangent.Z;
var normal = new Vector3D(
worldUp.X - projection * tangent.X,
worldUp.Y - projection * tangent.Y,
worldUp.Z - projection * tangent.Z);
double normalLengthSquared = normal.X * normal.X + normal.Y * normal.Y + normal.Z * normal.Z;
if (normalLengthSquared < TangentEpsilon)
{
return HostCoordinateAdapter.CanonicalUp;
}
double normalLength = Math.Sqrt(normalLengthSquared);
return new Vector3D(
normal.X / normalLength,
normal.Y / normalLength,
normal.Z / normalLength);
}
private static Vector3D ResolveTangent(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint)
{
var adapter = CoordinateSystemManager.Instance.CreateHostAdapter();
var canonicalPreviousPoint = adapter.ToCanonicalPoint(previousPoint);
var canonicalCurrentPoint = adapter.ToCanonicalPoint(currentPoint);
var canonicalNextPoint = adapter.ToCanonicalPoint(nextPoint);
var tangent = new Vector3D(
canonicalNextPoint.X - canonicalPreviousPoint.X,
canonicalNextPoint.Y - canonicalPreviousPoint.Y,
canonicalNextPoint.Z - canonicalPreviousPoint.Z);
double tangentLengthSquared = tangent.X * tangent.X + tangent.Y * tangent.Y + tangent.Z * tangent.Z;
if (tangentLengthSquared < TangentEpsilon)
{
tangent = new Vector3D(
canonicalNextPoint.X - canonicalCurrentPoint.X,
canonicalNextPoint.Y - canonicalCurrentPoint.Y,
canonicalNextPoint.Z - canonicalCurrentPoint.Z);
}
return tangent;
}
private static Vector3D Normalize(Vector3D vector)
{
double lengthSquared = vector.X * vector.X + vector.Y * vector.Y + vector.Z * vector.Z;
if (lengthSquared < TangentEpsilon)
{
return new Vector3D(0, 0, 0);
}
double length = Math.Sqrt(lengthSquared);
return new Vector3D(vector.X / length, vector.Y / length, vector.Z / length);
}
private static Vector3D Cross(Vector3D a, Vector3D b)
{
return new Vector3D(
a.Y * b.Z - a.Z * b.Y,
a.Z * b.X - a.X * b.Z,
a.X * b.Y - a.Y * b.X);
}
private static Vector3 ToNumerics(Point3D point)
{
return new Vector3((float)point.X, (float)point.Y, (float)point.Z);
}
private static Vector3D ToNavVector(Vector3 vector)
{
return new Vector3D(vector.X, vector.Y, vector.Z);
}
}
}