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); } } }