using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Autodesk.Navisworks.Api;
namespace NavisworksTransport.UnitTests.Core
{
///
/// PathCurveEngine 核心算法测试
/// 测试路径曲线化算法的正确性
///
[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
{
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
{
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
{
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
{
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
{
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 10.0,
SampledPoints = new List()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 5.0,
SampledPoints = new List()
}
};
// 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
{
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 10.0,
SampledPoints = new List()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Arc,
PhysicalLength = Math.PI, // 半圆
SampledPoints = new List()
},
new PathEdge
{
Id = Guid.NewGuid().ToString(),
SegmentType = PathSegmentType.Straight,
PhysicalLength = 5.0,
SampledPoints = new List()
}
};
// 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()
};
// 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
{
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
}
}