无人物流车转弯路径曲线化功能 - 实施方案 1. 目标概述 实现基于圆弧过渡(Arc Fillet)的路径曲线化功能,替代现有的直线连接方式,确保仿真系统能准确检测物体转弯时的扫掠路径(Swept Path)碰撞。 核心原理 控制点与物理点分离:用户操作的 PathPoint 作为控制点,实际物理路径由 PathEdge 表示 圆弧过渡法:在相邻路径段转折处插入切圆弧,使用进入切点 Ts 和退出切点 Te 连接 安全截断:当计算的切线长度超过线段长度45%时,自动缩减半径,防止圆弧过大 2. 数据结构改动 2.1 新增数据模型 [NEW] PathSegmentType 枚举 /// /// 路径段类型 /// public enum PathSegmentType { /// /// 直线段 /// Straight, /// /// 圆弧段 /// Arc } [NEW] ArcTrajectory 类 /// /// 圆弧轨迹数据 /// [Serializable] public class ArcTrajectory { /// /// 进入切点 /// public Point3D Ts { get; set; } /// /// 退出切点 /// public Point3D Te { get; set; } /// /// 圆心位置 /// public Point3D ArcCenter { get; set; } /// /// 请求半径(配置的转向半径) /// public double RequestedRadius { get; set; } /// /// 实际半径(安全截断后) /// public double ActualRadius { get; set; } /// /// 偏转角(弧度) /// public double DeflectionAngle { get; set; } /// /// 圆弧长度(米) /// public double ArcLength { get; set; } } [NEW] PathEdge 类 /// /// 路径边 - 连接两个连续控制点的物理路径段 /// [Serializable] public class PathEdge { /// /// 边唯一标识符 /// public string Id { get; set; } /// /// 起始控制点ID /// public string StartPointId { get; set; } /// /// 结束控制点ID /// public string EndPointId { get; set; } /// /// 路径段类型 /// public PathSegmentType SegmentType { get; set; } /// /// 圆弧轨迹数据(仅当 SegmentType == Arc 时有效) /// public ArcTrajectory Trajectory { get; set; } /// /// 边的物理长度(米) /// 直线段:两点间距离;圆弧段:直线段长度 + 圆弧长度 /// public double PhysicalLength { get; set; } /// /// 采样点序列(用于碰撞检测和动画) /// [XmlIgnore] public List SampledPoints { get; set; } } 2.2 修改现有数据模型 [MODIFY] PathPoint.cs 新增属性: /// /// 自定义转向半径(米),用于局部急转弯场景 /// 为 null 时使用全局默认值 /// public double? CustomTurnRadius { get; set; } [MODIFY] PathRoute.cs 新增属性: /// /// 路径边集合 - 存储物理路径段 /// public List Edges { get; set; } = new List(); /// /// 默认转向半径(米) /// public double TurnRadius { get; set; } = 1.5; /// /// 是否已曲线化 /// public bool IsCurved { get; set; } = false; 修改方法: UpdateTotalLength() : 综合计算所有 Edges 的物理长度 Clone() : 需要深拷贝 Edges 集合 2.3 数据库表结构扩展 [MODIFY] PathDatabase.cs 新增表 PathEdges: CREATE TABLE IF NOT EXISTS PathEdges ( Id TEXT PRIMARY KEY, RouteId TEXT NOT NULL, StartPointId TEXT NOT NULL, EndPointId TEXT NOT NULL, SegmentType INTEGER NOT NULL, -- 0=Straight, 1=Arc PhysicalLength REAL NOT NULL, -- 圆弧轨迹数据 (仅 SegmentType=1 时有效) Ts_X REAL, Ts_Y REAL, Ts_Z REAL, Te_X REAL, Te_Y REAL, Te_Z REAL, ArcCenter_X REAL, ArcCenter_Y REAL, ArcCenter_Z REAL, RequestedRadius REAL, ActualRadius REAL, DeflectionAngle REAL, ArcLength REAL, FOREIGN KEY (RouteId) REFERENCES PathRoutes(Id) ON DELETE CASCADE ); 修改表 PathRoutes : ALTER TABLE PathRoutes ADD COLUMN TurnRadius REAL DEFAULT 1.5; ALTER TABLE PathRoutes ADD COLUMN IsCurved INTEGER DEFAULT 0; 修改表 PathPoints : ALTER TABLE PathPoints ADD COLUMN CustomTurnRadius REAL; 3. 核心算法实现 3.1 PathCurveEngine 类 位置: src/Core/PathCurveEngine.cs (新建) 3.1.1 CalculateFillet 方法 /// /// 计算圆弧切点和轨迹参数 /// /// 前一个控制点 /// 当前控制点 /// 下一个控制点 /// 转向半径(米) /// 圆弧轨迹数据 public static ArcTrajectory CalculateFillet( Point3D pPrev, Point3D pCurr, Point3D pNext, double turnRadius) { // 1. 计算单位向量 Vector3D v1 = (pPrev - pCurr).Normalize(); Vector3D v2 = (pNext - pCurr).Normalize(); // 2. 计算夹角 α double cosAlpha = Vector3D.DotProduct(v1, v2); double angleRad = Math.Acos(Math.Clamp(cosAlpha, -1.0, 1.0)); // 3. 计算切线长 Lt = R / tan(α/2) double Lt = turnRadius / Math.Tan(angleRad / 2.0); // 4. 安全截断检查 double seg1Length = (pPrev - pCurr).Length; double seg2Length = (pNext - pCurr).Length; double maxAllowedLt = Math.Min(seg1Length, seg2Length) * 0.45; double actualRadius = turnRadius; if (Lt > maxAllowedLt) { Lt = maxAllowedLt; actualRadius = Lt * Math.Tan(angleRad / 2.0); } // 5. 计算切点 Point3D ts = pCurr + v1 * Lt; Point3D te = pCurr + v2 * Lt; // 6. 计算圆心 (使用角平分线方向) Vector3D bisector = (v1 + v2).Normalize(); double distToCenter = actualRadius / Math.Sin(angleRad / 2.0); Point3D arcCenter = pCurr + bisector * distToCenter; // 7. 计算圆弧长度 double arcLength = actualRadius * angleRad; return new ArcTrajectory { Ts = ts, Te = te, ArcCenter = arcCenter, RequestedRadius = turnRadius, ActualRadius = actualRadius, DeflectionAngle = angleRad, ArcLength = arcLength }; } 3.1.2 SampleArc 方法 /// /// 圆弧采样为离散点序列 /// /// 圆弧轨迹 /// 采样步长(米) /// 采样点列表 public static List SampleArc( ArcTrajectory trajectory, double samplingStep) { var points = new List(); // 根据采样步长计算采样点数量 int sampleCount = Math.Max(2, (int)Math.Ceiling(trajectory.ArcLength / samplingStep)); // 计算起始和结束向量 Vector3D startVec = (trajectory.Ts - trajectory.ArcCenter).Normalize(); Vector3D endVec = (trajectory.Te - trajectory.ArcCenter).Normalize(); // 计算旋转轴(叉乘) Vector3D rotationAxis = Vector3D.CrossProduct(startVec, endVec).Normalize(); // 等角度插值 for (int i = 0; i <= sampleCount; i++) { double t = i / (double)sampleCount; double theta = t * trajectory.DeflectionAngle; // Rodrigues旋转公式 Point3D sampledPoint = RotatePointAroundAxis( trajectory.Ts, trajectory.ArcCenter, rotationAxis, theta ); points.Add(sampledPoint); } return points; } 3.1.3 ApplyCurvatureToRoute 方法 /// /// 对路径应用曲线化处理 /// /// 待处理的路径 /// 采样步长(米) public static void ApplyCurvatureToRoute(PathRoute route, double samplingStep) { route.Edges.Clear(); var sortedPoints = route.GetSortedPoints(); if (sortedPoints.Count < 2) { route.IsCurved = false; return; } for (int i = 0; i < sortedPoints.Count - 1; i++) { var p1 = sortedPoints[i]; var p2 = sortedPoints[i + 1]; // 判断是否为转折点(需要圆弧过渡) bool needsArc = (i > 0 && i < sortedPoints.Count - 2); if (needsArc) { var p0 = sortedPoints[i - 1]; var p2Next = sortedPoints[i + 2]; // 获取转向半径(优先使用自定义值) double radius = p1.CustomTurnRadius ?? route.TurnRadius; // 计算圆弧 var arcTraj = CalculateFillet(p0, p1, p2, radius); // 创建Edge(包含直线段 + 圆弧段) var edge = BuildEdgeWithArc(p1, p2, arcTraj, samplingStep); route.Edges.Add(edge); } else { // 直线边 var edge = BuildStraightEdge(p1, p2, samplingStep); route.Edges.Add(edge); } } route.IsCurved = true; route.RecalculateLength(); } 4. 配置文件扩展 [MODIFY] SystemConfig.cs 在 PathEditingConfig 类中新增: /// /// 默认转向半径(米) /// 推荐值:1.0-2.0 /// public double DefaultTurnRadius { get; set; } = 1.5; /// /// 圆弧采样步长(米) /// 推荐值:0.02-0.1 /// public double ArcSamplingStep { get; set; } = 0.05; 配置文件示例 (TOML) [PathEditing] CellSizeMeters = 0.5 MaxHeightDiffMeters = 0.35 ObjectLengthMeters = 1.0 ObjectWidthMeters = 1.0 ObjectHeightMeters = 2.0 SafetyMarginMeters = 0.05 # 路径曲线化配置 DefaultTurnRadius = 1.5 ArcSamplingStep = 0.05 1. 业务流程集成 5.1 路径编辑完成流程 [MODIFY] PathPlanningManager.cs - FinishEditing 方法 public bool FinishEditing() { try { if (_currentRoute == null) return false; // **新增:应用曲线化** if (ConfigManager.Instance.Current.PathEditing.EnablePathCurving) { double samplingStep = ConfigManager.Instance.Current.PathEditing.ArcSamplingStep; _currentRoute.TurnRadius = ConfigManager.Instance.Current.PathEditing.DefaultTurnRadius; PathCurveEngine.ApplyCurvatureToRoute(_currentRoute, samplingStep); LogManager.Info($"路径曲线化完成: {_currentRoute.Name}, 边数: {_currentRoute.Edges.Count}"); } // 保存到数据库(包含Edges) SaveRouteToDatabase(_currentRoute); // ... 现有逻辑 ... } catch (Exception ex) { LogManager.Error($"完成编辑失败: {ex.Message}"); return false; } } 5.2 数据持久化 [MODIFY] PathDatabase.cs 新增方法: SavePathEdges(string routeId, List edges) LoadPathEdges(string routeId) : List DeletePathEdges(string routeId) 修改方法: SavePathRoute(PathRoute route) : 同时保存 Edges GetAllPathRoutes() : 同时加载 Edges DeletePathRoute(string routeId) : 级联删除 Edges 5.3 导出格式扩展 [MODIFY] PathDataManager.cs DELMIA XML 格式: 1.5 CSV 格式(扁平化采样点): RouteId,EdgeIndex,SegmentType,PointIndex,X,Y,Z route_001,0,Arc,0,10.5,5.2,0.0 route_001,0,Arc,1,10.52,5.25,0.0 ... 6. 碰撞检测与动画集成 6.1 碰撞检测 [MODIFY] PathAnimationManager.cs - 碰撞检测逻辑 // 从 Edges 获取采样点而非直接使用 Points foreach (var edge in route.Edges) { if (edge.SampledPoints == null || edge.SampledPoints.Count == 0) { // 懒加载:若采样点未生成,现场生成 edge.SampledPoints = GenerateSampledPoints(edge, arcSamplingStep); } foreach (var point in edge.SampledPoints) { // 执行OBB碰撞检测 ... } } 6.2 动画生成 使用 edge.SampledPoints 代替原始控制点生成动画帧序列。 1. 验证计划 7.1 单元测试 新建测试文件: test/Core/PathCurveEngineTests.cs 测试用例: 基础几何计算 验证90度直角转弯的圆弧计算 验证锐角/钝角场景 验证3D空间旋转正确性 安全截断 验证半径过大时自动缩减 边界条件:最小夹角(~0°)和最大夹角(~180°) 采样精度 验证采样点数量符合步长要求 验证采样点均匀分布在圆弧上 运行命令(需确认实际测试框架): # 假设使用 xUnit dotnet test NavisworksTransport.Tests --filter "FullyQualifiedName~PathCurveEngine" 7.2 集成测试 场景测试路径: 创建包含3个控制点的简单L型路径 设置转向半径 = 1.5m 完成编辑触发曲线化 验证: Edges 数量= 2 第一个Edge为直线段,第二个Edge包含圆弧 TotalLength 正确(直线段 + 圆弧段) 重新加载路径验证持久化 7.3 手动验证 IMPORTANT 以下步骤需要用户在Navisworks中手动验证 测试步骤: 打开Navisworks,加载测试模型 进入路径规划模式,手工添加4个控制点形成Z字形路径 在配置文件中设置: DefaultTurnRadius = 2.0 ArcSamplingStep = 0.05 EnablePathCurving = true 完成编辑,观察路径可视化是否显示平滑曲线(而非折线) 运行碰撞检测,检查转弯处的采样点密度 导出DELMIA XML,用文本编辑器验证圆弧轨迹参数存在 预期结果: 路径在转折点处显示圆弧过渡 碰撞检测能捕获内轮差区域的碰撞 XML包含完整的 节点 8. 风险与注意事项 8.1 性能影响 圆弧采样:每个转折点生成 ArcLength / SamplingStep 个采样点 缓解措施:默认步长0.05m平衡精度与性能,支持配置调整 8.2 数据兼容性 旧版路径数据库无 Edges 表 迁移策略: 检测表是否存在,不存在则创建 加载旧路径时,Edges为空视为"未曲线化",用户完成编辑时自动曲线化 8.3 极端场景 非常小的转向半径 + 锐角:可能导致无法满足几何约束 处理:最小半径限制0.3m,警告日志记录 9. 实施顺序建议 ✅ 数据结构 → 新增类和修改现有模型 ✅ 配置管理 → 扩展SystemConfig和TOML ✅ 核心算法 → PathCurveEngine + 单元测试 ✅ 数据库 → 表结构变更 + CRUD方法 ✅ 业务集成 → FinishEditing + 数据持久化 ✅ 导出扩展 → XML/CSV格式支持 ✅ 碰撞检测 → 使用Edges采样点 ✅ 验证测试 → 集成测试 + 手动验证 10. 后续优化方向 支持Clothoid曲线(回旋曲线)以实现更平滑的加速度变化 UI可视化:实时预览圆弧位置 性能优化:采样点缓存机制 物流车转弯路径曲线化功能实施任务规划 总体目标 实现基于圆弧过渡(Arc Fillet)的路径曲线化功能,替代现有的直线连接方式,满足仿真系统对物理真实性的要求。 实施阶段 [ ] 阶段1: 数据结构设计与扩展 设计 Edge(边)数据结构 PathEdge 类定义 PathSegmentType 枚举(直线/圆弧) ArcTrajectory 圆弧轨迹数据 扩展 PathPoint 支持局部半径覆盖 新增 CustomTurnRadius 属性 重构 PathRoute 类 新增 Edges 集合 新增 TurnRadius 全局转向半径 更新长度计算逻辑 更新数据库表结构 设计 Edges 表结构 设计数据迁移策略 [ ] 阶段2: 曲线化算法实现 创建 PathCurveEngine 核心算法类 CalculateFillet 方法(圆弧切点计算) 安全截断逻辑实现 SampleArc 方法(圆弧采样) 实现路径曲线化主流程 ApplyCurvatureToRoute 方法 BuildEdgesFromPoints 方法 单元测试 基础几何计算测试 边界条件测试 [ ] 阶段3: 配置管理 添加曲线化相关配置项 DefaultTurnRadius (默认转向半径) ArcSamplingStep (圆弧采样步长,默认0.05m) EnablePathCurving (启用开关) 更新配置文件读取/保存逻辑 [ ] 阶段4: 集成到现有业务流程 修改 PathRoute 长度计算 直线段 + 圆弧段综合计算 集成到路径编辑完成流程 FinishEditing 触发曲线化 更新数据持久化 PathDatabase 保存/加载 Edges PathDataManager 导出格式扩展 [ ] 阶段5: 碰撞检测与动画集成 碰撞检测使用曲线路径 采样点序列生成 集成到现有碰撞检测 PathAnimationManager 支持曲线路径 使用 Edges 生成动画帧 [ ] 阶段6: DELMIA 导出扩展 XML Tag Group 格式支持圆弧 导出 Ts, Te, ArcCenter CSV 格式导出采样点序列 [ ] 阶段7: 测试与验证 功能测试 性能测试 边界场景测试