NavisworksTransport/UnitTests/Core/PathCurveEngineTests.cs

518 lines
19 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 System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.UnitTests.Core
{
/// <summary>
/// PathCurveEngine 核心算法测试
/// 测试路径曲线化算法的正确性
/// </summary>
[TestClass]
public class PathCurveEngineTests
{
#region CalculateFillet
[TestMethod]
public void CalculateFillet_RightAngleTurn_ReturnsValidArc()
{
// Arrange - 创建90度直角转弯
var pPrev = new Point3D(0, 10, 0); // 上方点
var pCurr = new Point3D(0, 0, 0); // 转弯点
var pNext = new Point3D(10, 0, 0); // 右方点
double turnRadius = 2.0;
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.AreEqual(turnRadius, trajectory.RequestedRadius, 0.01, "请求半径应该正确");
Assert.IsTrue(trajectory.ActualRadius > 0, "实际半径应该大于0");
Assert.IsTrue(trajectory.DeflectionAngle > 0, "偏转角应该大于0");
Assert.IsTrue(trajectory.ArcLength > 0, "圆弧长度应该大于0");
// 验证切点位置
Assert.IsTrue(trajectory.Ts.Y > 0, "进入切点应该在转弯点上方");
Assert.IsTrue(trajectory.Ts.X == 0, "进入切点X坐标应该为0");
Assert.IsTrue(trajectory.Te.X > 0, "退出切点应该在转弯点右侧");
Assert.IsTrue(trajectory.Te.Y == 0, "退出切点Y坐标应该为0");
}
[TestMethod]
public void CalculateFillet_AcuteAngleTurn_ReturnsValidArc()
{
// Arrange - 创建锐角转弯约60度
var pPrev = new Point3D(0, 10, 0);
var pCurr = new Point3D(0, 0, 0);
var pNext = new Point3D(8.66, 5, 0); // 60度方向
double turnRadius = 2.0;
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.IsTrue(trajectory.ActualRadius > 0, "实际半径应该大于0");
Assert.IsTrue(trajectory.DeflectionAngle > 0.5 && trajectory.DeflectionAngle < 1.5, "偏转角应该在60度左右");
}
[TestMethod]
public void CalculateFillet_ObtuseAngleTurn_ReturnsValidArc()
{
// Arrange - 创建钝角转弯约120度
var pPrev = new Point3D(0, 10, 0);
var pCurr = new Point3D(0, 0, 0);
var pNext = new Point3D(8.66, -5, 0); // 120度方向
double turnRadius = 2.0;
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.IsTrue(trajectory.ActualRadius > 0, "实际半径应该大于0");
Assert.IsTrue(trajectory.DeflectionAngle > 1.5 && trajectory.DeflectionAngle < 2.5, "偏转角应该在120度左右");
}
[TestMethod]
public void CalculateFillet_CollinearPoints_ReturnsInvalidArc()
{
// Arrange - 创建共线点(几乎直线)
var pPrev = new Point3D(0, 10, 0);
var pCurr = new Point3D(0, 0, 0);
var pNext = new Point3D(0, -10, 0); // 同一直线
double turnRadius = 2.0;
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.AreEqual(0, trajectory.ActualRadius, 0.01, "实际半径应该为0");
Assert.AreEqual(0, trajectory.ArcLength, 0.01, "圆弧长度应该为0");
Assert.AreEqual(pCurr, trajectory.Ts, "切点应该与转弯点相同");
}
[TestMethod]
public void CalculateFillet_SafetyTruncation_AdjustsRadius()
{
// Arrange - 创建短边场景,需要安全截断
var pPrev = new Point3D(0, 1, 0); // 短边
var pCurr = new Point3D(0, 0, 0);
var pNext = new Point3D(1, 0, 0); // 短边
double turnRadius = 2.0; // 半径大于边长
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.IsTrue(trajectory.ActualRadius < turnRadius, "实际半径应该小于请求半径");
Assert.IsTrue(trajectory.ActualRadius > 0, "实际半径应该大于0");
}
[TestMethod]
public void CalculateFillet_3DTurn_ReturnsValidArc()
{
// Arrange - 创建3D空间中的转弯
var pPrev = new Point3D(0, 10, 0);
var pCurr = new Point3D(0, 0, 0);
var pNext = new Point3D(10, 0, 5); // 有Z轴变化
double turnRadius = 2.0;
// Act
var trajectory = PathCurveEngine.CalculateFillet(pPrev, pCurr, pNext, turnRadius);
// Assert
Assert.IsNotNull(trajectory, "轨迹不应该为null");
Assert.IsTrue(trajectory.ActualRadius > 0, "实际半径应该大于0");
Assert.IsTrue(trajectory.ArcCenter.Z != 0, "圆心Z坐标应该不为0");
}
#endregion
#region SampleArc
[TestMethod]
public void SampleArc_WithValidTrajectory_ReturnsSampledPoints()
{
// Arrange
var trajectory = new ArcTrajectory
{
Ts = new Point3D(0, 2, 0),
Te = new Point3D(2, 0, 0),
ArcCenter = new Point3D(0, 0, 0),
ActualRadius = 2.0,
DeflectionAngle = Math.PI / 2, // 90度
ArcLength = Math.PI // 半圆周长
};
double samplingStep = 0.5;
// Act
var sampledPoints = PathCurveEngine.SampleArc(trajectory, samplingStep);
// Assert
Assert.IsNotNull(sampledPoints, "采样点列表不应该为null");
Assert.IsTrue(sampledPoints.Count >= 2, "采样点数量应该至少为2");
Assert.AreEqual(trajectory.Ts, sampledPoints.First(), "第一个点应该是进入切点");
Assert.AreEqual(trajectory.Te, sampledPoints.Last(), "最后一个点应该是退出切点");
}
[TestMethod]
public void SampleArc_SmallArcLength_ReturnsTwoPoints()
{
// Arrange
var trajectory = new ArcTrajectory
{
Ts = new Point3D(0, 0.1, 0),
Te = new Point3D(0.1, 0, 0),
ArcCenter = new Point3D(0, 0, 0),
ActualRadius = 0.1,
DeflectionAngle = 0.1,
ArcLength = 0.01 // 非常小的圆弧
};
double samplingStep = 0.05;
// Act
var sampledPoints = PathCurveEngine.SampleArc(trajectory, samplingStep);
// Assert
Assert.IsNotNull(sampledPoints, "采样点列表不应该为null");
Assert.AreEqual(2, sampledPoints.Count, "小圆弧应该返回2个点");
}
[TestMethod]
public void SampleArc_SamplingStep0_05_ReturnsCorrectCount()
{
// Arrange
var trajectory = new ArcTrajectory
{
Ts = new Point3D(0, 2, 0),
Te = new Point3D(2, 0, 0),
ArcCenter = new Point3D(0, 0, 0),
ActualRadius = 2.0,
DeflectionAngle = Math.PI / 2,
ArcLength = Math.PI
};
double samplingStep = 0.05;
// Act
var sampledPoints = PathCurveEngine.SampleArc(trajectory, samplingStep);
// Assert
int expectedCount = (int)Math.Ceiling(Math.PI / 0.05) + 1;
Assert.IsTrue(sampledPoints.Count >= expectedCount - 2 && sampledPoints.Count <= expectedCount + 2,
$"采样点数量应该在 {expectedCount - 2} 到 {expectedCount + 2} 之间");
}
#endregion
#region ApplyCurvatureToRoute
[TestMethod]
public void ApplyCurvatureToRoute_SimplePath_GeneratesEdges()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "测试路径",
TurnRadius = 2.0
};
route.Points = new List<PathPoint>
{
new PathPoint(new Point3D(0, 0, 0), "起点", PathPointType.StartPoint),
new PathPoint(new Point3D(10, 0, 0), "点2", PathPointType.WayPoint),
new PathPoint(new Point3D(10, 10, 0), "终点", PathPointType.EndPoint)
};
route.Points[0].Index = 0;
route.Points[1].Index = 1;
route.Points[2].Index = 2;
double samplingStep = 0.5;
// Act
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
// Assert
Assert.IsTrue(route.IsCurved, "路径应该被标记为已曲线化");
Assert.IsTrue(route.Edges.Count > 0, "应该生成路径边");
Assert.IsTrue(route.TotalLength > 0, "路径总长度应该大于0");
// 验证每个边都有采样点
foreach (var edge in route.Edges)
{
Assert.IsNotNull(edge.SampledPoints, "每个边都应该有采样点");
Assert.IsTrue(edge.SampledPoints.Count > 0, "采样点数量应该大于0");
}
}
[TestMethod]
public void ApplyCurvatureToRoute_LShapedPath_GeneratesArcEdge()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "L型路径",
TurnRadius = 1.5
};
route.Points = new List<PathPoint>
{
new PathPoint(new Point3D(0, 10, 0), "起点", PathPointType.StartPoint),
new PathPoint(new Point3D(0, 0, 0), "转弯点", PathPointType.WayPoint),
new PathPoint(new Point3D(10, 0, 0), "终点", PathPointType.EndPoint)
};
route.Points[0].Index = 0;
route.Points[1].Index = 1;
route.Points[2].Index = 2;
double samplingStep = 0.5;
// Act
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
// Assert
Assert.IsTrue(route.IsCurved, "路径应该被标记为已曲线化");
Assert.AreEqual(2, route.Edges.Count, "L型路径应该生成2条边");
// 第一条边是直线段
Assert.AreEqual(PathSegmentType.Straight, route.Edges[0].SegmentType, "第一条边应该是直线段");
// 第二条边包含圆弧
Assert.AreEqual(PathSegmentType.Arc, route.Edges[1].SegmentType, "第二条边应该是圆弧段");
Assert.IsNotNull(route.Edges[1].Trajectory, "圆弧边应该有轨迹数据");
Assert.IsTrue(route.Edges[1].Trajectory.ActualRadius > 0, "实际半径应该大于0");
}
[TestMethod]
public void ApplyCurvatureToRoute_ZShapedPath_GeneratesMultipleArcEdges()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "Z型路径",
TurnRadius = 1.5
};
route.Points = new List<PathPoint>
{
new PathPoint(new Point3D(0, 10, 0), "起点", PathPointType.StartPoint),
new PathPoint(new Point3D(0, 0, 0), "转弯点1", PathPointType.WayPoint),
new PathPoint(new Point3D(10, 0, 0), "转弯点2", PathPointType.WayPoint),
new PathPoint(new Point3D(10, 10, 0), "终点", PathPointType.EndPoint)
};
route.Points[0].Index = 0;
route.Points[1].Index = 1;
route.Points[2].Index = 2;
route.Points[3].Index = 3;
double samplingStep = 0.5;
// Act
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
// Assert
Assert.IsTrue(route.IsCurved, "路径应该被标记为已曲线化");
Assert.AreEqual(3, route.Edges.Count, "Z型路径应该生成3条边");
// 应该有两条圆弧边
int arcEdgeCount = route.Edges.Count(e => e.SegmentType == PathSegmentType.Arc);
Assert.IsTrue(arcEdgeCount >= 1, "应该至少有一条圆弧边");
}
[TestMethod]
public void ApplyCurvatureToRoute_NullRoute_DoesNotThrow()
{
// Arrange
PathRoute route = null;
double samplingStep = 0.5;
// Act & Assert
try
{
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
Assert.Fail("应该抛出ArgumentNullException");
}
catch (ArgumentNullException)
{
// Expected
}
}
[TestMethod]
public void ApplyCurvatureToRoute_LessThanTwoPoints_DoesNotCurve()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "单点路径",
TurnRadius = 2.0
};
route.Points = new List<PathPoint>
{
new PathPoint(new Point3D(0, 0, 0), "起点", PathPointType.StartPoint)
};
route.Points[0].Index = 0;
double samplingStep = 0.5;
// Act
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
// Assert
Assert.IsFalse(route.IsCurved, "少于2个点的路径不应该被曲线化");
Assert.AreEqual(0, route.Edges.Count, "不应该生成任何边");
}
#endregion
#region RecalculateRouteLength
[TestMethod]
public void RecalculateRouteLength_StraightEdges_CalculatesCorrectly()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "测试路径"
};
route.Edges = new List<PathEdge>
{
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 10.0,
SampledPoints = new List<Point3D>()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 5.0,
SampledPoints = new List<Point3D>()
}
};
// Act
PathCurveEngine.RecalculateRouteLength(route);
// Assert
Assert.AreEqual(15.0, route.TotalLength, 0.01, "路径总长度应该等于所有边长度之和");
}
[TestMethod]
public void RecalculateRouteLength_MixedEdgeTypes_CalculatesCorrectly()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "测试路径"
};
route.Edges = new List<PathEdge>
{
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 10.0,
SampledPoints = new List<Point3D>()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Arc,
PhysicalLength = Math.PI, // 半圆
SampledPoints = new List<Point3D>()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 5.0,
SampledPoints = new List<Point3D>()
}
};
// Act
PathCurveEngine.RecalculateRouteLength(route);
// Assert
double expectedLength = 10.0 + Math.PI + 5.0;
Assert.AreEqual(expectedLength, route.TotalLength, 0.01, "路径总长度应该正确计算");
}
[TestMethod]
public void RecalculateRouteLength_NullRoute_DoesNotThrow()
{
// Arrange
PathRoute route = null;
// Act
PathCurveEngine.RecalculateRouteLength(route);
// Assert - 不应该抛出异常
Assert.IsTrue(true, "null路径不应该导致异常");
}
[TestMethod]
public void RecalculateRouteLength_EmptyEdges_SetsZeroLength()
{
// Arrange
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "空路径",
Edges = new List<PathEdge>()
};
// Act
PathCurveEngine.RecalculateRouteLength(route);
// Assert
Assert.AreEqual(0, route.TotalLength, 0.01, "空边的路径长度应该为0");
}
#endregion
#region
[TestMethod]
public void FullWorkflow_CreateCurvePath_CalculatesCorrectLength()
{
// Arrange - 创建一个包含转弯的路径
var route = new PathRoute
{
Id = Guid.NewGuid().ToString(),
Name = "完整测试路径",
TurnRadius = 2.0
};
route.Points = new List<PathPoint>
{
new PathPoint(new Point3D(0, 20, 0), "起点", PathPointType.StartPoint),
new PathPoint(new Point3D(0, 0, 0), "转弯点", PathPointType.WayPoint),
new PathPoint(new Point3D(20, 0, 0), "终点", PathPointType.EndPoint)
};
route.Points[0].Index = 0;
route.Points[1].Index = 1;
route.Points[2].Index = 2;
double samplingStep = 0.5;
// Act - 应用曲线化
PathCurveEngine.ApplyCurvatureToRoute(route, samplingStep);
// Assert
Assert.IsTrue(route.IsCurved, "路径应该被曲线化");
Assert.IsTrue(route.TotalLength > 0, "路径总长度应该大于0");
// 验证路径总长度小于直线距离(因为圆弧比直线短)
double straightDistance = 20 + 20; // 两条直线段
Assert.IsTrue(route.TotalLength < straightDistance, "曲线化后的路径长度应该小于直线距离");
}
#endregion
}
}