NavisworksTransport/UnitTests/Core/FreePathTests.cs
tian 002618f371 feat: 自由路径(Free Path)一级路径类型
- PathType 新增 Free=3,显示'自由'
- 动画:统一包围盒中心对齐,方案1姿态(forward=切线,up=世界up正交化)
  复用 CanonicalRailPoseBuilder,新增 TryCreateFreePathRotationForFrame/AtStart
- 渲染:通行空间垂直偏移0(路径点即物体中心)
- 路径管理器:StartCreatingNewRoute/FinishEditing 支持 Free
- UI:PathEditingView 新增'自由'按钮 + NewFreePathCommand
- 数据库:枚举存 int 无需 schema 变更
- 迁移:加载时 IsTwoPointVertical 吊装路径 → Free
- 单测:新增 FreePathTests(6个全通过)

注:CornerTurnCheckHelperTests 6个失败为分支起点已存在的 pre-existing 问题,与本次无关
2026-07-31 16:02:25 +08:00

138 lines
5.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using NavisworksTransport.Utils.CoordinateSystem;
using System;
using System.Numerics;
namespace NavisworksTransport.UnitTests.Core
{
[TestClass]
public class FreePathTests
{
// === PathType 枚举 ===
[TestMethod]
public void PathType_Free_ShouldHaveValue3()
{
Assert.AreEqual(3, (int)PathType.Free);
}
[TestMethod]
public void PathType_Free_GetDisplayName_ShouldReturnFree()
{
Assert.AreEqual("自由", PathType.Free.GetDisplayName());
}
// === 自由路径旋转姿态方案1forward=切线, up=世界up正交化 ===
[TestMethod]
public void FreePathRotation_StraightHorizontal_ShouldAlignForwardToX_UpToWorldUp()
{
// ZUp 约定水平直路forward=+Xup=+Z世界up
bool ok = CanonicalRailPoseBuilder.TryCreateQuaternion(
new Vector3(0, 0, 0),
new Vector3(1, 0, 0),
new Vector3(2, 0, 0),
Vector3.UnitZ,
ModelAxisConvention.CreateDefaultForHost(CoordinateSystemType.ZUp),
null, // preferredNormal=null → 使用世界up
Quaternion.Identity,
out Quaternion rotation);
Assert.IsTrue(ok);
Matrix4x4 linear = Matrix4x4.CreateFromQuaternion(rotation);
// 局部 X → 世界 Xforward=切线)
AssertColumn(linear, 0, 1, 0, 0);
// 局部 Z → 世界 Zup=世界up
AssertColumn(linear, 2, 0, 0, 1);
}
[TestMethod]
public void FreePathRotation_SlopedPath_ShouldKeepForwardAlongTangent_UpOrthogonalized()
{
// 斜路径沿X上升forward 应沿切线up 应正交且接近世界Z
bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
new Vector3(0, 0, 0),
new Vector3(1, 0, 0.5f),
new Vector3(2, 0, 1),
Vector3.UnitZ,
out Vector3 forward,
out Vector3 lateral,
out Vector3 up);
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 方向主导)
Assert.IsTrue(Vector3.Dot(forward, Vector3.UnitX) > 0.99f);
// up 接近世界 Z投影正交化
Assert.IsTrue(Vector3.Dot(up, Vector3.UnitZ) > 0.95f);
}
[TestMethod]
public void FreePathRotation_VerticalPath_ShouldStillProduceValidFrame()
{
// 纯垂直路径沿Z上升forward 应能生成up 退化为水平无世界up投影时用X
bool ok = CanonicalRailPoseBuilder.TryCreateBasis(
new Vector3(0, 0, 0),
new Vector3(0, 0, 1),
new Vector3(0, 0, 2),
Vector3.UnitZ,
out Vector3 forward,
out Vector3 lateral,
out Vector3 up);
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);
}
// === 自由路径几何中心对齐语义 ===
[TestMethod]
public void FreePath_CenterAlignment_ShouldNotApplyHeightOffset()
{
// 自由路径路径点即物体中心ResolveGroundTrackedCenter 的半高偏移不应用于自由路径
// 这里验证 CanonicalTrackedPositionResolver 的语义不影响自由路径
var resolver = CanonicalTrackedPositionResolver.ResolveGroundTrackedCenter(
new Vector3(5, 5, 5),
Vector3.UnitZ,
2.0);
// Ground 语义:中心上移半高
Assert.AreEqual(6.0, resolver.Z, 1e-6);
}
private static void AssertColumn(Matrix4x4 m, int col, float x, float y, float z)
{
switch (col)
{
case 0:
Assert.AreEqual(x, m.M11, 1e-5f);
Assert.AreEqual(y, m.M21, 1e-5f);
Assert.AreEqual(z, m.M31, 1e-5f);
break;
case 1:
Assert.AreEqual(x, m.M12, 1e-5f);
Assert.AreEqual(y, m.M22, 1e-5f);
Assert.AreEqual(z, m.M32, 1e-5f);
break;
case 2:
Assert.AreEqual(x, m.M13, 1e-5f);
Assert.AreEqual(y, m.M23, 1e-5f);
Assert.AreEqual(z, m.M33, 1e-5f);
break;
default:
Assert.Fail("无效列");
break;
}
}
}
}