using Microsoft.VisualStudio.TestTools.UnitTesting; using NavisworksTransport.Core; using NavisworksTransport.PathPlanning; using NavisworksTransport.Utils.CoordinateSystem; using System.Numerics; using System.Reflection; using System.Collections.Generic; using Autodesk.Navisworks.Api; namespace NavisworksTransport.UnitTests.CoordinateSystem { [TestClass] public class AutoPathPlanningCoordinateSemanticsTests { [TestMethod] public void YUp_HostPlanarGridHelper_CreateHostPoint_ShouldApplyElevationOnHostY() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); Vector3 point = HostPlanarGridHelper.CreateHostPoint3(2.0, 3.0, 14.83, adapter); AssertPoint(point, 2.0, 14.83, 3.0); } [TestMethod] public void ZUp_HostPlanarGridHelper_CreateHostPoint_ShouldApplyElevationOnHostZ() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.ZUp); Vector3 point = HostPlanarGridHelper.CreateHostPoint3(2.0, 3.0, 6.25, adapter); AssertPoint(point, 2.0, 3.0, 6.25); } [TestMethod] public void YUp_HostPlanarGridHelper_GetAndSetElevation_ShouldUseHostY() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); var hostPoint = new Vector3(4.0f, 15.0f, 6.0f); double elevation = HostPlanarGridHelper.GetElevation3(hostPoint, adapter); Vector3 adjusted = HostPlanarGridHelper.SetElevation3(hostPoint, 20.0, adapter); Assert.AreEqual(15.0, elevation, 1e-6); AssertPoint(adjusted, 4.0, 20.0, 6.0); } [TestMethod] public void YUp_HostPlanarGridHelper_GetHorizontalCoords_ShouldProjectToHostXZPlane() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); var hostPoint = new Vector3(8.0f, 14.83f, -5.35f); (double h1, double h2) = HostPlanarGridHelper.GetHorizontalCoords3(hostPoint, adapter); Assert.AreEqual(8.0, h1, 1e-6); Assert.AreEqual(-5.35, h2, 1e-6); } [TestMethod] public void YUp_HostPlanarGridHelper_HorizontalRange_ShouldMatchHostXZPlane() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); var hostMin = new Vector3(-10.0f, 2.0f, -30.0f); var hostMax = new Vector3(40.0f, 12.0f, 5.0f); (double min1, double max1, double min2, double max2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter); Assert.AreEqual(-10.0, min1, 1e-6); Assert.AreEqual(40.0, max1, 1e-6); Assert.AreEqual(-30.0, min2, 1e-6); Assert.AreEqual(5.0, max2, 1e-6); } [TestMethod] public void YUp_GridLikeWorldPointConstruction_ShouldWriteElevationToHostY() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); double originH1 = -210.0; double originH2 = -10.0; double cellSize = 1.0; Vector3 worldPoint = HostPlanarGridHelper.CreateHostPoint3( originH1 + 4 * cellSize, originH2 + 6 * cellSize, 14.83, adapter); Assert.AreEqual(-206.0, worldPoint.X, 1e-6); Assert.AreEqual(14.83, worldPoint.Y, 1e-6); Assert.AreEqual(-4.0, worldPoint.Z, 1e-6); } [TestMethod] public void YUp_GridLikeWorldToGrid_ShouldReadHostXZAsPlanarAxes() { var adapter = new HostCoordinateAdapter(CoordinateSystemType.YUp); var origin = new Vector3(-210.0f, 14.83f, -10.0f); var endPoint = new Vector3(-169.20f, 14.83f, -5.35f); const double cellSize = 1.0; (double pointH1, double pointH2) = HostPlanarGridHelper.GetHorizontalCoords3(endPoint, adapter); (double originH1, double originH2) = HostPlanarGridHelper.GetHorizontalCoords3(origin, adapter); int gridX = (int)System.Math.Round((pointH1 - originH1) / cellSize); int gridY = (int)System.Math.Round((pointH2 - originH2) / cellSize); Assert.AreEqual(41, gridX); Assert.AreEqual(5, gridY); } [TestMethod] public void ZUp_GridMap_CellPlanarBounds_ShouldMatchNearestNodeRoundSemantics() { var bounds = GridMap.CalculateNearestNodePlanarBounds(2.0, 3.0, 1.0); Assert.AreEqual(1.5, bounds.minH1, 1e-9); Assert.AreEqual(2.5, bounds.maxH1, 1e-9); Assert.AreEqual(2.5, bounds.minH2, 1e-9); Assert.AreEqual(3.5, bounds.maxH2, 1e-9); } [TestMethod] public void ZUp_GridMap_SegmentIntersection_ShouldDetectInteriorNotBoundaryTouch() { var bounds = GridMap.CalculateNearestNodePlanarBounds(2.0, 3.0, 1.0); bool crossesInterior = GridMap.TryGetSegmentRectangleInteriorIntersection( 1.75, 3.0, 2.25, 3.0, bounds.minH1, bounds.maxH1, bounds.minH2, bounds.maxH2, 1.0, out double enterT, out double exitT); Assert.IsTrue(crossesInterior); Assert.IsTrue(enterT >= 0.0 && enterT <= exitT && exitT <= 1.0); bool onlyTouchesBoundary = GridMap.TryGetSegmentRectangleInteriorIntersection( 1.5, 2.0, 1.5, 4.0, bounds.minH1, bounds.maxH1, bounds.minH2, bounds.maxH2, 1.0, out _, out _); Assert.IsFalse(onlyTouchesBoundary, "贴着归属边界行走不应被当成穿过格子内部。"); } [TestMethod] public void YUp_GridMapGenerator_GetObstacleElevationRange_ShouldUseHostYInsteadOfWorldZ() { var generator = new GridMapGenerator(); var method = typeof(GridMapGenerator).GetMethod( "GetObstacleElevationRange", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 GetObstacleElevationRange 私有方法。"); var elevationRange = ((double min, double max))method.Invoke(generator, new object[] { -200.0, 10.0, -30.0, -198.0, 30.0, -10.0, CoordinateSystemType.YUp }); Assert.AreEqual(10.0, elevationRange.min, 1e-6, "YUp 下障碍物最小高程应来自宿主 Y。"); Assert.AreEqual(30.0, elevationRange.max, 1e-6, "YUp 下障碍物最大高程应来自宿主 Y。"); } [TestMethod] public void YUp_GridMapGenerator_CalculateBoundingBoxGridCoverage_ShouldProjectToHostXZPlane() { var generator = new GridMapGenerator(); var method = typeof(GridMapGenerator).GetMethod( "CalculateGridCoverageFromWorldExtents", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(int), typeof(int), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 CalculateGridCoverageFromWorldExtents 私有方法。"); var coveredCells = (List<(int x, int y)>)method.Invoke(generator, new object[] { -200.0, 10.0, -30.0, -198.0, 30.0, -10.0, -210.0, 14.83, -80.0, 200, 200, 1.0, CoordinateSystemType.YUp }); Assert.AreEqual(63, coveredCells.Count, "YUp 下障碍物包围盒应按宿主 XZ 平面覆盖 3x21 个网格。"); CollectionAssert.Contains(coveredCells, (10, 50)); CollectionAssert.Contains(coveredCells, (12, 70)); } [TestMethod] public void YUp_GridMapGenerator_CalculateDoorOpeningCoverage_ShouldProjectToHostXZPlane() { var generator = new GridMapGenerator(); var method = typeof(GridMapGenerator).GetMethod( "CalculateDoorOpeningCoverageFromWorldExtents", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(int), typeof(int), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 CalculateDoorOpeningCoverageFromWorldExtents 私有方法。"); var coveredCells = (List<(int x, int y)>)method.Invoke(generator, new object[] { 10.0, 14.0, 20.0, 12.0, 16.0, 30.0, 4.0, 0.0, 0.0, 0.0, 100, 100, 1.0, CoordinateSystemType.YUp }); Assert.AreEqual(15, coveredCells.Count, "YUp 下门开口应按宿主 XZ 平面覆盖 3x5 个网格。"); CollectionAssert.Contains(coveredCells, (10, 23)); CollectionAssert.Contains(coveredCells, (12, 27)); } [TestMethod] public void YUp_PathPlanningManager_CalculateOptimalGridSize_ShouldUseHostXZHorizontalRange() { var method = typeof(PathPlanningManager).GetMethod( "CalculateOptimalGridSizeFromBounds", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 CalculateOptimalGridSizeFromBounds 私有方法。"); var gridSize = (double)method.Invoke(null, new object[] { -10.0, 100.0, -300.0, 90.0, 120.0, 400.0, CoordinateSystemType.YUp }); Assert.AreEqual(2.0, gridSize, 1e-6, "YUp 下网格大小应基于宿主 XZ 平面最大跨度 700 计算。"); } [TestMethod] public void YUp_ChannelHeightDetector_GetBoundsElevationRange_ShouldUseHostY() { var method = typeof(ChannelHeightDetector).GetMethod( "GetBoundsElevationRange", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 GetBoundsElevationRange 私有方法。"); var elevationRange = ((double min, double max))method.Invoke(null, new object[] { -10.0, 14.83, -30.0, 20.0, 18.50, 40.0, CoordinateSystemType.YUp }); Assert.AreEqual(14.83, elevationRange.min, 1e-6); Assert.AreEqual(18.50, elevationRange.max, 1e-6); } [TestMethod] public void YUp_ChannelHeightDetector_CreateHeightProfileSample_ShouldUseHostXZPlaneAndHostYElevation() { var method = typeof(ChannelHeightDetector).GetMethod( "CreateHeightProfileSamplePoint3", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType), typeof(double) }, null); Assert.IsNotNull(method, "未找到 CreateHeightProfileSamplePoint3 私有方法。"); var samplePoint = (Vector3)method.Invoke(null, new object[] { -10.0, 14.83, -30.0, 20.0, 18.50, 40.0, CoordinateSystemType.YUp, 0.5 }); Assert.AreEqual(5.0, samplePoint.X, 1e-6, "YUp 下采样点第一水平轴应沿宿主 X 插值。"); Assert.AreEqual(14.83, samplePoint.Y, 1e-6, "YUp 下采样点高程应落在宿主底面 Y。"); Assert.AreEqual(5.0, samplePoint.Z, 1e-6, "YUp 下采样点第二水平轴应沿宿主 Z 插值。"); } [TestMethod] public void YUp_ChannelHeightDetector_GetPickedSurfaceElevation_ShouldUseHostYInsteadOfWorldZ() { var method = typeof(ChannelHeightDetector).GetMethod( "GetPickedSurfaceElevation", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 GetPickedSurfaceElevation 私有方法。"); var elevation = (double)method.Invoke(null, new object[] { 11.0, 22.0, 33.0, CoordinateSystemType.YUp }); Assert.AreEqual(22.0, elevation, 1e-6, "YUp 下拾取表面点的高程应来自宿主 Y。"); } [TestMethod] public void YUp_GridMapGenerator_ResolveBoundsMinElevation_ShouldUseHostYInsteadOfWorldZ() { var method = typeof(GridMapGenerator).GetMethod( "ResolveBoundsMinElevation", BindingFlags.Static | BindingFlags.NonPublic, null, new[] { typeof(double), typeof(double), typeof(double), typeof(CoordinateSystemType) }, null); Assert.IsNotNull(method, "未找到 ResolveBoundsMinElevation 私有方法。"); var elevation = (double)method.Invoke(null, new object[] { 0.0, 12.5, -100.0, CoordinateSystemType.YUp }); Assert.AreEqual(12.5, elevation, 1e-6, "YUp 下边界最小高程应来自宿主 Y,而不是 bounds.Min.Z。"); } private static void AssertPoint(Vector3 actual, double x, double y, double z) { Assert.AreEqual(x, actual.X, 1e-6); Assert.AreEqual(y, actual.Y, 1e-6); Assert.AreEqual(z, actual.Z, 1e-6); } } }