fix: 自由路径姿态改为平面旋转(只绕垂直轴yaw,不绕前进轴滚动)

用户反馈:自由路径动画起点物体沿前进轴旋转90°(侧滚),
期望像地面路径一样只转向前进方向、仅绕垂直轴旋转。

根因:TryCreateFreePathRotationForFrame/AtStart 用了
CanonicalRailPoseBuilder(完整三维姿态,forward=3D切线),
会绕前进轴滚动。改为 CanonicalPlanarPoseBuilder
(forward=切线水平投影,up=世界up,只转yaw)。
撤销此前 ShouldPreservePathRotationForFrames 让 Free 保留姿态的改动。
同步更新 FreePathTests 断言(斜路径投影水平、纯垂直无水平方向返回false)。
257 单测全过。
This commit is contained in:
tian 2026-07-31 16:50:35 +08:00
parent 4b6c3852cc
commit 84f67dbd79
2 changed files with 32 additions and 45 deletions

View File

@ -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上升物体只绕垂直轴转 yawforward 投影到水平面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=+Zup 应正交(水平)
Assert.IsTrue(Math.Abs(Vector3.Dot(forward, Vector3.UnitZ)) > 0.99f);
Assert.IsFalse(ok);
}
// === 自由路径几何中心对齐语义 ===

View File

@ -4938,8 +4938,9 @@ namespace NavisworksTransport.Core.Animation
}
/// <summary>
/// 自由路径姿态方案1 —— forward=路径切线up=世界up投影正交化。
/// 复用 CanonicalRailPoseBuilder 的三维姿态计算(不依赖轨道特殊语义)。
/// 自由路径姿态(平面):像地面路径一样,物体转向前进方向的水平投影,
/// 但只绕垂直轴up 轴旋转yaw不绕前进轴滚动。
/// 复用 CanonicalPlanarPoseBuilderforward=路径水平投影up=世界up
/// </summary>
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
}
/// <summary>
/// 自由路径起点姿态:以首段方向为切线世界up正交化方案1)。
/// 自由路径起点姿态:以首段方向水平投影为前向只绕垂直轴旋转yaw)。
/// </summary>
private bool TryCreateFreePathRotationAtStart(out Rotation3D rotation)
{