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) {