fix: 自由路径姿态改为平面旋转(只绕垂直轴yaw,不绕前进轴滚动)
用户反馈:自由路径动画起点物体沿前进轴旋转90°(侧滚), 期望像地面路径一样只转向前进方向、仅绕垂直轴旋转。 根因:TryCreateFreePathRotationForFrame/AtStart 用了 CanonicalRailPoseBuilder(完整三维姿态,forward=3D切线), 会绕前进轴滚动。改为 CanonicalPlanarPoseBuilder (forward=切线水平投影,up=世界up,只转yaw)。 撤销此前 ShouldPreservePathRotationForFrames 让 Free 保留姿态的改动。 同步更新 FreePathTests 断言(斜路径投影水平、纯垂直无水平方向返回false)。 257 单测全过。
This commit is contained in:
parent
4b6c3852cc
commit
84f67dbd79
@ -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);
|
||||
}
|
||||
|
||||
// === 自由路径几何中心对齐语义 ===
|
||||
|
||||
@ -4938,8 +4938,9 @@ namespace NavisworksTransport.Core.Animation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自由路径姿态:方案1 —— forward=路径切线,up=世界up投影正交化。
|
||||
/// 复用 CanonicalRailPoseBuilder 的三维姿态计算(不依赖轨道特殊语义)。
|
||||
/// 自由路径姿态(平面):像地面路径一样,物体转向前进方向的水平投影,
|
||||
/// 但只绕垂直轴(up 轴)旋转(yaw),不绕前进轴滚动。
|
||||
/// 复用 CanonicalPlanarPoseBuilder(forward=路径水平投影,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)
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user