From 84f67dbd79bad2481aa1f7ac585efb5d3fe9aedb Mon Sep 17 00:00:00 2001
From: tian <11429339@qq.com>
Date: Fri, 31 Jul 2026 16:50:35 +0800
Subject: [PATCH] =?UTF-8?q?fix:=20=E8=87=AA=E7=94=B1=E8=B7=AF=E5=BE=84?=
=?UTF-8?q?=E5=A7=BF=E6=80=81=E6=94=B9=E4=B8=BA=E5=B9=B3=E9=9D=A2=E6=97=8B?=
=?UTF-8?q?=E8=BD=AC=EF=BC=88=E5=8F=AA=E7=BB=95=E5=9E=82=E7=9B=B4=E8=BD=B4?=
=?UTF-8?q?yaw=EF=BC=8C=E4=B8=8D=E7=BB=95=E5=89=8D=E8=BF=9B=E8=BD=B4?=
=?UTF-8?q?=E6=BB=9A=E5=8A=A8=EF=BC=89?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
用户反馈:自由路径动画起点物体沿前进轴旋转90°(侧滚),
期望像地面路径一样只转向前进方向、仅绕垂直轴旋转。
根因:TryCreateFreePathRotationForFrame/AtStart 用了
CanonicalRailPoseBuilder(完整三维姿态,forward=3D切线),
会绕前进轴滚动。改为 CanonicalPlanarPoseBuilder
(forward=切线水平投影,up=世界up,只转yaw)。
撤销此前 ShouldPreservePathRotationForFrames 让 Free 保留姿态的改动。
同步更新 FreePathTests 断言(斜路径投影水平、纯垂直无水平方向返回false)。
257 单测全过。
---
UnitTests/Core/FreePathTests.cs | 54 ++++++++--------------
src/Core/Animation/PathAnimationManager.cs | 23 +++++----
2 files changed, 32 insertions(+), 45 deletions(-)
diff --git a/UnitTests/Core/FreePathTests.cs b/UnitTests/Core/FreePathTests.cs
index 8685da4..b7be899 100644
--- a/UnitTests/Core/FreePathTests.cs
+++ b/UnitTests/Core/FreePathTests.cs
@@ -47,52 +47,36 @@ namespace NavisworksTransport.UnitTests.Core
}
[TestMethod]
- public void FreePathRotation_SlopedPath_ShouldKeepForwardAlongTangent_UpOrthogonalized()
+ public void FreePathRotation_SlopedPath_ShouldProjectForwardToHorizontal_UpStaysWorldUp()
{
- // 斜路径(沿X上升):forward 应沿切线,up 应正交且接近世界Z
- bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
- new Vector3(0, 0, 0),
- new Vector3(1, 0, 0.5f),
- new Vector3(2, 0, 1),
+ // 斜路径(沿X上升):物体只绕垂直轴转 yaw,forward 投影到水平面,up 恒为世界Z(不侧滚)
+ bool ok = CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
+ new Vector3(2, 0, 1), // 切线(含垂直分量)
Vector3.UnitZ,
- out Vector3 forward,
- out Vector3 lateral,
- out Vector3 up);
+ ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
+ LocalEulerRotationCorrection.Zero,
+ out Quaternion rotation);
Assert.IsTrue(ok);
- Assert.AreEqual(1.0, forward.Length(), 1e-6);
- Assert.AreEqual(1.0, up.Length(), 1e-6);
- Assert.AreEqual(1.0, lateral.Length(), 1e-6);
- // 正交性
- Assert.AreEqual(0.0, Vector3.Dot(forward, up), 1e-6);
- Assert.AreEqual(0.0, Vector3.Dot(forward, lateral), 1e-6);
- Assert.AreEqual(0.0, Vector3.Dot(lateral, up), 1e-6);
- // 切线方向沿X上升((2,0,1)/√5 ≈ (0.894,0,0.447),X为主)
- Assert.IsTrue(Vector3.Dot(forward, Vector3.UnitX) > 0.80f);
- // up 为世界Z在 forward 上的投影正交化,随坡度倾斜约26.6°,
- // Dot(up, Z) ≈ cos(26.6°) ≈ 0.894,仍以上方为主
- Assert.IsTrue(Vector3.Dot(up, Vector3.UnitZ) > 0.80f);
+ Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(rotation);
+ // forward = 切线水平投影 = (1,0,0) → 局部X映射到世界X
+ AssertColumn(linear, 0, 1, 0, 0);
+ // up = 世界Z(不侧滚,关键:斜路径也不倾斜)
+ AssertColumn(linear, 2, 0, 0, 1);
}
[TestMethod]
- public void FreePathRotation_VerticalPath_ShouldStillProduceValidFrame()
+ public void FreePathRotation_PureVerticalPath_ShouldFail_NoHorizontalDirection()
{
- // 纯垂直路径(沿Z上升):forward 应能生成,up 退化为水平(无世界up投影时用X)
- bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
- new Vector3(0, 0, 0),
- new Vector3(0, 0, 1),
+ // 纯垂直路径无水平分量,无法确定 yaw,应返回 false(保持当前姿态)
+ bool ok = CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
new Vector3(0, 0, 2),
Vector3.UnitZ,
- out Vector3 forward,
- out Vector3 lateral,
- out Vector3 up);
+ ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
+ LocalEulerRotationCorrection.Zero,
+ out _);
- Assert.IsTrue(ok);
- Assert.AreEqual(1.0, forward.Length(), 1e-6);
- Assert.AreEqual(1.0, up.Length(), 1e-6);
- Assert.AreEqual(0.0, Vector3.Dot(forward, up), 1e-6);
- // 纯垂直时 forward=+Z,up 应正交(水平)
- Assert.IsTrue(Math.Abs(Vector3.Dot(forward, Vector3.UnitZ)) > 0.99f);
+ Assert.IsFalse(ok);
}
// === 自由路径几何中心对齐语义 ===
diff --git a/src/Core/Animation/PathAnimationManager.cs b/src/Core/Animation/PathAnimationManager.cs
index ec2cab0..e1f64ce 100644
--- a/src/Core/Animation/PathAnimationManager.cs
+++ b/src/Core/Animation/PathAnimationManager.cs
@@ -4938,8 +4938,9 @@ namespace NavisworksTransport.Core.Animation
}
///
- /// 自由路径姿态:方案1 —— forward=路径切线,up=世界up投影正交化。
- /// 复用 CanonicalRailPoseBuilder 的三维姿态计算(不依赖轨道特殊语义)。
+ /// 自由路径姿态(平面):像地面路径一样,物体转向前进方向的水平投影,
+ /// 但只绕垂直轴(up 轴)旋转(yaw),不绕前进轴滚动。
+ /// 复用 CanonicalPlanarPoseBuilder(forward=路径水平投影,up=世界up)。
///
private bool TryCreateFreePathRotationForFrame(Point3D previousPoint, Point3D currentPoint, Point3D nextPoint, out Rotation3D rotation)
{
@@ -4952,15 +4953,17 @@ namespace NavisworksTransport.Core.Animation
Vector3 canonicalCurrent = ToNumerics(adapter.ToCanonicalPoint(currentPoint));
Vector3 canonicalNext = ToNumerics(adapter.ToCanonicalPoint(nextPoint));
- Quaternion correctionQuaternion = adapter.CreateCanonicalRotationCorrection(_objectRotationCorrection);
- if (!CanonicalRailPoseBuilder.TryCreateQuaternion(
- canonicalPrevious,
- canonicalCurrent,
- canonicalNext,
+ Vector3 canonicalForward = canonicalNext - canonicalPrevious;
+ if (canonicalForward.LengthSquared() < 1e-6f)
+ {
+ canonicalForward = canonicalNext - canonicalCurrent;
+ }
+
+ if (!CanonicalPlanarPoseBuilder.TryCreateQuaternionFromForward(
+ canonicalForward,
HostCoordinateAdapter.CanonicalUpVector3,
convention,
- null, // preferredNormal=null → 使用 canonicalUp(世界up)作为参考up,即方案1
- correctionQuaternion,
+ _objectRotationCorrection,
out var canonicalRotation))
{
return false;
@@ -4971,7 +4974,7 @@ namespace NavisworksTransport.Core.Animation
}
///
- /// 自由路径起点姿态:以首段方向为切线,世界up正交化(方案1)。
+ /// 自由路径起点姿态:以首段方向水平投影为前向,只绕垂直轴旋转(yaw)。
///
private bool TryCreateFreePathRotationAtStart(out Rotation3D rotation)
{