diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 551525e..9263755 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -158,7 +158,8 @@ "Bash(git restore:*)", "Read(//c/Program Files/Autodesk/Navisworks Manage 2026/**)", "Bash(where:*)", - "Read(//c/Users/Tellme/Pictures/Screenshots/**)" + "Read(//c/Users/Tellme/Pictures/Screenshots/**)", + "Bash(./deploy-plugin.bat)" ], "deny": [], "additionalDirectories": [ diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index e8395d4..ed9a3e3 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -43,21 +43,464 @@ namespace NavisworksTransport.PathPlanning /// /// 自动路径查找器 /// 基于A*算法的路径规划核心类 - /// 支持传统模式和2.5D高度约束的路径规划 + /// 支持传统模式和真3D图路径规划 /// public class AutoPathFinder { /// - /// 2.5D路径规划(使用通道覆盖数据、高度约束和路径策略) + /// 水平边连接允许的最大高度差(米) + /// 足以支持台阶连接(约0.15米)和地面到楼梯底部的连接(约0.3米) + /// 使用时需转换为模型单位 + /// + private const double MAX_HEIGHT_DIFF_METERS = 0.35; + + /// + /// 3D节点信息(存储每个A*节点对应的3D坐标和类型) + /// + private class Node3DInfo + { + public int X { get; set; } + public int Y { get; set; } + public int LayerIndex { get; set; } + public double Z { get; set; } + public CategoryAttributeManager.LogisticsElementType CellType { get; set; } + } + + /// + /// 3D图构建结果 + /// + private class Graph3DResult + { + /// + /// 节点字典:(x, y, layerIndex) -> A*节点 + /// + public Dictionary<(int x, int y, int layerIndex), Roy_T.AStar.Graphs.Node> Nodes { get; set; } + + /// + /// 节点信息字典:A*节点 -> 3D信息 + /// + public Dictionary NodeInfo { get; set; } + + /// + /// 所有节点列表(用于查找最近节点) + /// + public List AllNodes { get; set; } + + /// + /// 最大速度 + /// + public Velocity MaxVelocity { get; set; } + } + + /// + /// 真3D路径规划(使用通道覆盖数据、高度约束和路径策略) /// - /// 起点(世界坐标) - /// 终点(世界坐标) - /// 网格地图 - /// 通道覆盖数据 - /// 车辆高度(模型单位) - /// 路径规划策略 - /// 路径查找结果 public PathFindingResult FindPath(Point3D start, Point3D end, GridMap gridMap, ChannelCoverage channelCoverage, double vehicleHeight, PathStrategy strategy = PathStrategy.Shortest) + { + try + { + LogManager.Info("[3D路径规划] 开始执行"); + + // 构建3D图 + var graph3D = BuildGraphWithStrategy(gridMap, channelCoverage, vehicleHeight, start, end, strategy); + + // 执行A*搜索 + var pathResult = ExecuteAStarOnGraph(start, end, gridMap, graph3D); + + // 优化路径 + pathResult = OptimizePath(pathResult, gridMap); + + return pathResult; + } + catch (Exception ex) + { + LogManager.Error($"[3D路径规划] 发生异常: {ex.Message}\n{ex.StackTrace}"); + return new PathFindingResult + { + PathPoints = new List { start, end }, + IsComplete = false, + OriginalEndPoint = end, + ActualEndPoint = start, + CompletionPercentage = 0 + }; + } + } + + /// + /// 构建3D图(每个(x,y,layerIndex)作为独立节点) + /// + private Graph3DResult BuildGraph3D(GridMap gridMap, ChannelCoverage channelCoverage, double vehicleHeight, Point3D startPos, Point3D endPos) + { + LogManager.Info("[3D图构建] 开始构建真3D图"); + + var nodes = new Dictionary<(int x, int y, int layerIndex), Roy_T.AStar.Graphs.Node>(); + var nodeInfo = new Dictionary(); + var allNodes = new List(); + + double baseSpeed = 5.0; // km/h + float maxSpeed = (float)baseSpeed; + + // 将米转换为模型单位 + double maxHeightDiffInModelUnits = MAX_HEIGHT_DIFF_METERS * UnitsConverter.GetMetersToUnitsConversionFactor(); + LogManager.Info($"[3D图构建] 高度差阈值: {MAX_HEIGHT_DIFF_METERS}米 = {maxHeightDiffInModelUnits:F2}模型单位"); + + // === 阶段1:创建所有3D节点 === + int totalNodesCreated = 0; + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + if (!cell.IsWalkable || cell.HeightLayers.Count == 0) + continue; + + // 为每个高度层创建独立节点 + for (int li = 0; li < cell.HeightLayers.Count; li++) + { + var layer = cell.HeightLayers[li]; + + // 楼梯/电梯区域跳过层0(下方楼板,不是可行走表面) + if (li == 0 && (layer.Type == CategoryAttributeManager.LogisticsElementType.楼梯 || + layer.Type == CategoryAttributeManager.LogisticsElementType.电梯)) + { + continue; + } + + if (layer.PassableHeight.GetSpan() < vehicleHeight) + continue; + + // 使用2D位置(用于A*启发式) + var position = new Position(x, y); + var node = new Roy_T.AStar.Graphs.Node(position); + + nodes[(x, y, li)] = node; + nodeInfo[node] = new Node3DInfo + { + X = x, + Y = y, + LayerIndex = li, + Z = layer.Z, + CellType = layer.Type + }; + allNodes.Add(node); + totalNodesCreated++; + } + } + } + + LogManager.Info($"[3D图构建] 阶段1完成:创建了 {totalNodesCreated} 个3D节点"); + + // === 阶段2:创建水平边(同层的相邻节点,高度差在阈值内)=== + var directions = new[] { (1, 0), (-1, 0), (0, 1), (0, -1) }; + int horizontalEdges = 0; + + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + if (!cell.IsWalkable || cell.HeightLayers.Count == 0) + continue; + + for (int li = 0; li < cell.HeightLayers.Count; li++) + { + var currentLayer = cell.HeightLayers[li]; + if (currentLayer.PassableHeight.GetSpan() < vehicleHeight) + continue; + + if (!nodes.TryGetValue((x, y, li), out var currentNode)) + continue; + + double currentZ = currentLayer.Z; + + foreach (var (dx, dy) in directions) + { + int nx = x + dx; + int ny = y + dy; + + if (nx < 0 || nx >= gridMap.Width || ny < 0 || ny >= gridMap.Height) + continue; + + var neighborCell = gridMap.Cells[nx, ny]; + if (!neighborCell.IsWalkable) + continue; + + // 查找邻居中所有满足高度约束的层 + for (int nli = 0; nli < neighborCell.HeightLayers.Count; nli++) + { + var neighborLayer = neighborCell.HeightLayers[nli]; + if (neighborLayer.PassableHeight.GetSpan() < vehicleHeight) + continue; + + double heightDiff = Math.Abs(neighborLayer.Z - currentZ); + if (heightDiff > maxHeightDiffInModelUnits) + continue; + + if (!nodes.TryGetValue((nx, ny, nli), out var neighborNode)) + continue; + + float speed = CalculateSpeedByHeightDiff(heightDiff, baseSpeed, maxHeightDiffInModelUnits); + maxSpeed = Math.Max(maxSpeed, speed); + var velocity = Velocity.FromKilometersPerHour(speed); + + currentNode.Connect(neighborNode, velocity); + horizontalEdges++; + } + } + } + } + } + + LogManager.Info($"[3D图构建] 阶段2完成:创建了 {horizontalEdges} 条水平边"); + + // === 阶段3:创建垂直边(仅在楼梯/电梯内部的相邻层)=== + int verticalEdges = 0; + + for (int x2 = 0; x2 < gridMap.Width; x2++) + { + for (int y2 = 0; y2 < gridMap.Height; y2++) + { + var cell = gridMap.Cells[x2, y2]; + + // 检查该网格是否有任何楼梯或电梯层(允许垂直移动) + bool hasStairsOrElevator = cell.HeightLayers.Any(layer => + layer.Type == CategoryAttributeManager.LogisticsElementType.楼梯 || + layer.Type == CategoryAttributeManager.LogisticsElementType.电梯); + + if (!hasStairsOrElevator || cell.HeightLayers.Count < 2) + continue; + + // 连接同一(x,y)位置的不同高度层(相邻层之间) + for (int li = 0; li < cell.HeightLayers.Count - 1; li++) + { + var layer1 = cell.HeightLayers[li]; + var layer2 = cell.HeightLayers[li + 1]; + + // 垂直边只连接相同类型的层(楼梯到楼梯,电梯到电梯) + // 不允许从通道跳到楼梯,必须通过水平边进入楼梯 + if (layer1.Type != layer2.Type) + continue; + + // 垂直边只在楼梯/电梯类型之间创建 + if (layer1.Type != CategoryAttributeManager.LogisticsElementType.楼梯 && + layer1.Type != CategoryAttributeManager.LogisticsElementType.电梯) + continue; + + if (layer1.PassableHeight.GetSpan() < vehicleHeight || + layer2.PassableHeight.GetSpan() < vehicleHeight) + continue; + + double heightDiff = Math.Abs(layer2.Z - layer1.Z); + if (heightDiff > maxHeightDiffInModelUnits) + continue; + + if (!nodes.TryGetValue((x2, y2, li), out var node1) || + !nodes.TryGetValue((x2, y2, li + 1), out var node2)) + continue; + + // 垂直移动速度:电梯较快,楼梯较慢 + float verticalSpeed = layer1.Type == CategoryAttributeManager.LogisticsElementType.电梯 + ? (float)(baseSpeed * 0.5) + : (float)(baseSpeed * 0.3); + maxSpeed = Math.Max(maxSpeed, verticalSpeed); + var velocity = Velocity.FromKilometersPerHour(verticalSpeed); + + // 双向连接 + node1.Connect(node2, velocity); + node2.Connect(node1, velocity); + verticalEdges += 2; + } + } + } + + LogManager.Info($"[3D图构建] 阶段3完成:创建了 {verticalEdges} 条垂直边(双向)"); + LogManager.Info($"[3D图构建] 总结:{totalNodesCreated}个节点,{horizontalEdges + verticalEdges}条边"); + + return new Graph3DResult + { + Nodes = nodes, + NodeInfo = nodeInfo, + AllNodes = allNodes, + MaxVelocity = Velocity.FromKilometersPerHour(maxSpeed) + }; + } + + /// + /// 根据高度差计算速度(高度差单位:模型单位) + /// + /// 高度差(模型单位) + /// 基础速度(km/h) + /// 最大允许高度差(模型单位,对应0.5米) + private float CalculateSpeedByHeightDiff(double heightDiff, double baseSpeed, double maxHeightDiff) + { + // 计算相对高度差比例 + double heightRatio = heightDiff / maxHeightDiff; + + if (heightRatio < 0.06) return (float)baseSpeed; // 平地 (<3cm,即<0.06*0.5m) + if (heightRatio <= 0.5) return (float)(baseSpeed * 0.8); // 缓坡 (≤0.25m) + if (heightRatio <= 1.0) return (float)(baseSpeed * 0.5); // 楼梯 (≤0.5m) + return (float)(baseSpeed * 0.1); // 过陡 (>0.5m) + } + + /// + /// 根据策略构建图 + /// + private Graph3DResult BuildGraphWithStrategy(GridMap gridMap, ChannelCoverage channelCoverage, double vehicleHeight, Point3D startPos, Point3D endPos, PathStrategy strategy) + { + return BuildGraph3D(gridMap, channelCoverage, vehicleHeight, startPos, endPos); + } + + /// + /// 在3D图上执行A*搜索 + /// + private PathFindingResult ExecuteAStarOnGraph(Point3D start, Point3D end, GridMap gridMap, Graph3DResult graph3D) + { + var startGridPos = gridMap.WorldToGrid(start); + var endGridPos = gridMap.WorldToGrid(end); + + // 找到起点和终点最近的3D节点 + var startNode = FindClosest3DNode(graph3D, startGridPos, start.Z); + var endNode = FindClosest3DNode(graph3D, endGridPos, end.Z); + + if (startNode == null || endNode == null) + { + LogManager.Error($"[A*执行-3D] 无法找到起点或终点节点"); + return new PathFindingResult + { + PathPoints = new List { start, end }, + IsComplete = false, + OriginalEndPoint = end, + ActualEndPoint = start, + CompletionPercentage = 0 + }; + } + + var startInfo = graph3D.NodeInfo[startNode]; + var endInfo = graph3D.NodeInfo[endNode]; + LogManager.Info($"[A*执行-3D] 起点映射: ({startGridPos.X},{startGridPos.Y},Z={start.Z:F2}) -> 节点({startInfo.X},{startInfo.Y},层{startInfo.LayerIndex},Z={startInfo.Z:F2})"); + LogManager.Info($"[A*执行-3D] 终点映射: ({endGridPos.X},{endGridPos.Y},Z={end.Z:F2}) -> 节点({endInfo.X},{endInfo.Y},层{endInfo.LayerIndex},Z={endInfo.Z:F2})"); + + // 执行A* + var pathfinder = new Roy_T.AStar.Paths.PathFinder(); + var astarPath = pathfinder.FindPath(startNode, endNode, graph3D.MaxVelocity); + + if (astarPath.Edges.Count == 0) + { + LogManager.Warning($"[A*执行-3D] 未找到路径"); + return new PathFindingResult + { + PathPoints = new List { start, end }, + IsComplete = false, + OriginalEndPoint = end, + ActualEndPoint = start, + CompletionPercentage = 0 + }; + } + + // 转换为世界坐标路径 + var path3D = ConvertGraphPathToWorld(astarPath, graph3D, gridMap, start, end); + + double completionPercentage = 100.0; + bool isComplete = true; + Point3D actualEnd = path3D.Count > 0 ? path3D[path3D.Count - 1] : start; + + LogManager.Info($"[3D路径规划] 路径生成完成,包含 {path3D.Count} 个路径点,完成度: {completionPercentage:F1}%"); + + return new PathFindingResult + { + PathPoints = path3D, + IsComplete = isComplete, + OriginalEndPoint = end, + ActualEndPoint = actualEnd, + CompletionPercentage = completionPercentage + }; + } + + /// + /// 找到最接近指定位置和高度的3D节点 + /// + private Roy_T.AStar.Graphs.Node FindClosest3DNode(Graph3DResult graph3D, GridPoint2D gridPos, double targetZ) + { + Roy_T.AStar.Graphs.Node closestNode = null; + double minDist = double.MaxValue; + + foreach (var node in graph3D.AllNodes) + { + var info = graph3D.NodeInfo[node]; + if (info.X == gridPos.X && info.Y == gridPos.Y) + { + double heightDiff = Math.Abs(info.Z - targetZ); + if (heightDiff < minDist) + { + minDist = heightDiff; + closestNode = node; + } + } + } + + return closestNode; + } + + /// + /// 将图路径转换为世界坐标路径 + /// + private List ConvertGraphPathToWorld(Roy_T.AStar.Paths.Path astarPath, Graph3DResult graph3D, GridMap gridMap, Point3D originalStart, Point3D originalEnd) + { + var path3D = new List(); + path3D.Add(originalStart); + + int pointIndex = 0; + LogManager.Info($"[路径点{pointIndex}] 原始起点: ({originalStart.X:F2}, {originalStart.Y:F2}, Z={originalStart.Z:F2})"); + pointIndex++; + + // 添加第一条边的起点(如果与originalStart不同) + if (astarPath.Edges.Count > 0) + { + var firstEdge = astarPath.Edges[0]; + var firstNode = firstEdge.Start as Roy_T.AStar.Graphs.Node; + if (firstNode != null && graph3D.NodeInfo.ContainsKey(firstNode)) + { + var firstInfo = graph3D.NodeInfo[firstNode]; + var firstPos = gridMap.GridToWorld2D(new GridPoint2D(firstInfo.X, firstInfo.Y)); + var firstPoint = new Point3D(firstPos.X, firstPos.Y, firstInfo.Z); + + // 如果与起点高度不同,添加这个节点(避免直接跳跃) + if (Math.Abs(firstPoint.Z - originalStart.Z) > 0.01) + { + path3D.Add(firstPoint); + LogManager.Info($"[路径点{pointIndex}] A*起点: 网格({firstInfo.X},{firstInfo.Y}), 层{firstInfo.LayerIndex}, Z={firstInfo.Z:F2}, 类型={firstInfo.CellType}"); + pointIndex++; + } + } + } + + // 添加所有A*路径节点 + foreach (var edge in astarPath.Edges) + { + var endNode = edge.End as Roy_T.AStar.Graphs.Node; + if (endNode == null || !graph3D.NodeInfo.ContainsKey(endNode)) + continue; + + var nodeInfo = graph3D.NodeInfo[endNode]; + var worldPos = gridMap.GridToWorld2D(new GridPoint2D(nodeInfo.X, nodeInfo.Y)); + path3D.Add(new Point3D(worldPos.X, worldPos.Y, nodeInfo.Z)); + + LogManager.Info($"[路径点{pointIndex}] 网格({nodeInfo.X},{nodeInfo.Y}), 层{nodeInfo.LayerIndex}, Z={nodeInfo.Z:F2}, 类型={nodeInfo.CellType}"); + pointIndex++; + } + + path3D.Add(originalEnd); + LogManager.Info($"[路径点{pointIndex}] 原始终点: ({originalEnd.X:F2}, {originalEnd.Y:F2}, Z={originalEnd.Z:F2})"); + LogManager.Info($"[路径转换完成] 总共 {path3D.Count} 个路径点"); + + return path3D; + } + + + /// + /// 2.5D路径规划(使用通道覆盖数据、高度约束和路径策略)[已废弃,保留作为备份] + /// + private PathFindingResult FindPath_Legacy_2_5D(Point3D start, Point3D end, GridMap gridMap, ChannelCoverage channelCoverage, double vehicleHeight, PathStrategy strategy = PathStrategy.Shortest) { try { @@ -944,21 +1387,13 @@ namespace NavisworksTransport.PathPlanning var distance = Math.Sqrt(Math.Pow(endPoint.X - startPoint.X, 2) + Math.Pow(endPoint.Y - startPoint.Y, 2)); - // 🔥 新增:在尝试连接远点之前,先检查是否会跳过高度变化点 - bool canSkip = true; - const double heightTolerance = 1e-6; // 与PathOptimizer保持一致 + // 🔥 关键修复:检查起点和终点的高度差,如果超过阈值就不能跳过 + // 这样可以防止从地面直接跳到楼梯上层 + double totalHeightDiff = Math.Abs(endPoint.Z - startPoint.Z); + const double maxAllowedHeightDiffMeters = 0.3; // 0.3米,允许小台阶但不允许跨越整个楼梯 + double maxAllowedHeightDiffInModelUnits = maxAllowedHeightDiffMeters * UnitsConverter.GetMetersToUnitsConversionFactor(); - for (int checkIdx = currentIndex + 1; checkIdx < testIndex; checkIdx++) - { - // 检查被跳过的点是否有高度变化 - if (Math.Abs(optimizedPath[checkIdx].Z - optimizedPath[checkIdx-1].Z) > heightTolerance || - (checkIdx + 1 < optimizedPath.Count && - Math.Abs(optimizedPath[checkIdx+1].Z - optimizedPath[checkIdx].Z) > heightTolerance)) - { - canSkip = false; - break; - } - } + bool canSkip = totalHeightDiff <= maxAllowedHeightDiffInModelUnits; if (canSkip && IsDirectPathClear(startPoint, endPoint, gridMap)) { @@ -997,6 +1432,14 @@ namespace NavisworksTransport.PathPlanning int reducedPoints = optimizedPath.Count - diagonalOptimizedPath.Count; LogManager.Info($"[斜线优化] 斜线优化完成,点数变化: {optimizedPath.Count} -> {diagonalOptimizedPath.Count} (减少{reducedPoints}个点)"); + // 打印斜线优化后的路径点详情 + LogManager.Info($"[斜线优化后路径详情] 共 {diagonalOptimizedPath.Count} 个点:"); + for (int i = 0; i < diagonalOptimizedPath.Count; i++) + { + var pt = diagonalOptimizedPath[i]; + LogManager.Info($" [斜线优化后点{i}] 位置=({pt.X:F2}, {pt.Y:F2}, Z={pt.Z:F2})"); + } + return diagonalOptimizedPath; } catch (Exception ex) @@ -1851,6 +2294,7 @@ namespace NavisworksTransport.PathPlanning tempRoute.Points.Add(pathPoint); } + // 使用基于网格的路径优化算法 // 使用基于网格的路径优化算法 if (gridMap != null) { @@ -1874,6 +2318,8 @@ namespace NavisworksTransport.PathPlanning pathResult.PathPoints = optimizedPoints; // 🔥 步骤2:在现有优化基础上应用专门的斜线优化 + // 2D场景:高度差为0,不影响原有逻辑 + // 3D场景:通过高度差检查保护楼梯路径 var diagonalOptimizedPoints = ApplyDiagonalOptimization(optimizedPoints, gridMap); if (diagonalOptimizedPoints.Count != optimizedPoints.Count) @@ -1892,6 +2338,14 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"[路径生成] 未提供网格地图,跳过路径优化,点数: {pathResult.PathPoints.Count}"); } + // 打印最终返回给用户的路径点详情 + LogManager.Info($"[最终路径详情] 共 {pathResult.PathPoints.Count} 个点:"); + for (int i = 0; i < pathResult.PathPoints.Count; i++) + { + var pt = pathResult.PathPoints[i]; + LogManager.Info($" [最终点{i}] 位置=({pt.X:F2}, {pt.Y:F2}, Z={pt.Z:F2})"); + } + return pathResult; } catch (Exception ex) diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index d5a0420..baaac6f 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -60,17 +60,31 @@ namespace NavisworksTransport.PathPlanning { LogManager.Info("[通道网格构建器] 开始构建通道覆盖网格"); - // 1. 获取所有通道物品 - var channelItems = CategoryAttributeManager.GetLogisticsItemsByType( + // 1. 获取所有通行相关的物品(通道 + 楼梯 + 电梯) + var channelItems = new List(); + + // 获取通道 + var channels = CategoryAttributeManager.GetLogisticsItemsByType( CategoryAttributeManager.LogisticsElementType.通道); - + channelItems.AddRange(channels); + + // 获取楼梯 + var stairs = CategoryAttributeManager.GetLogisticsItemsByType( + CategoryAttributeManager.LogisticsElementType.楼梯); + channelItems.AddRange(stairs); + + // 获取电梯 + var elevators = CategoryAttributeManager.GetLogisticsItemsByType( + CategoryAttributeManager.LogisticsElementType.电梯); + channelItems.AddRange(elevators); + if (!channelItems.Any()) { - LogManager.Warning("[通道网格构建器] 未找到任何通道物品"); + LogManager.Warning("[通道网格构建器] 未找到任何通行物品(通道/楼梯/电梯)"); return CreateEmptyChannelCoverage(); } - LogManager.Info($"[通道网格构建器] 找到 {channelItems.Count} 个通道物品"); + LogManager.Info($"[通道网格构建器] 找到通行物品: 通道{channels.Count}个, 楼梯{stairs.Count}个, 电梯{elevators.Count}个, 总计{channelItems.Count}个"); // 2. 计算通道总边界 var totalBounds = BoundingBoxGeometryUtils.CalculateTotalBounds(channelItems); diff --git a/src/PathPlanning/PathOptimizer.cs b/src/PathPlanning/PathOptimizer.cs index 645dc0f..0257364 100644 --- a/src/PathPlanning/PathOptimizer.cs +++ b/src/PathPlanning/PathOptimizer.cs @@ -151,13 +151,19 @@ namespace NavisworksTransport.PathPlanning var worldPath = points.Select(p => p.Position).ToList(); var gridPath = worldPath.Select(p => gridMap.WorldToGrid(p)).ToList(); - // 步骤1:先去除重复点,但保护限速边界 + // 步骤1:先去除重复点,但保护限速边界和多层高度 var dedupedGridPath = new List(); var dedupedPoints = new List(); for (int i = 0; i < gridPath.Count; i++) { - // 🔥 关键修复:即使位置相同,如果限速不同也不能去重 - bool shouldKeep = i == 0 || !gridPath[i].Equals(gridPath[i-1]) || HasSpeedLimitChange(points[i-1], points[i]); + // 🔥 关键修复:即使网格坐标相同,如果限速不同或高度不同也不能去重 + // 2D场景:hasHeightChange为false,不影响原有逻辑 + // 3D场景:保留同网格不同高度的点 + bool hasGridChange = i == 0 || !gridPath[i].Equals(gridPath[i-1]); + bool hasSpeedChange = i > 0 && HasSpeedLimitChange(points[i-1], points[i]); + bool hasHeightChange = i > 0 && Math.Abs(points[i].Position.Z - points[i-1].Position.Z) > 1e-6; + + bool shouldKeep = hasGridChange || hasSpeedChange || hasHeightChange; if (shouldKeep) { @@ -165,9 +171,12 @@ namespace NavisworksTransport.PathPlanning dedupedPoints.Add(points[i]); } } + LogManager.Info($"[路径优化] 去重完成:{gridPath.Count} -> {dedupedPoints.Count} 个点"); - // 步骤2:基于去重后的网格路径进行方向优化 - var simplified = new List { dedupedGridPath[0] }; + // 步骤2:基于去重后的路径进行方向优化 + // 🔥 关键修复:不使用网格坐标,直接使用索引来跟踪保留的点 + // 这样可以避免同一网格坐标的不同高度点被混淆 + var simplifiedIndices = new List { 0 }; // 保留起点索引 // 获取初始方向(归一化) int initialDx = dedupedGridPath[1].X - dedupedGridPath[0].X; @@ -177,7 +186,8 @@ namespace NavisworksTransport.PathPlanning initialDy == 0 ? 0 : Math.Sign(initialDy) ); - // 简化路径:只保留转弯点、高度变化点和限速变化点(使用归一化方向) + // 简化路径:只保留转弯点、高度变化点和限速变化点 + bool forceKeepNext = false; for (int i = 2; i < dedupedGridPath.Count; i++) { // 计算当前线段的位移 @@ -190,14 +200,14 @@ namespace NavisworksTransport.PathPlanning dy == 0 ? 0 : Math.Sign(dy) ); - // 检查高度变化 + // 检查高度变化(2D场景下始终为false,不影响原有逻辑) bool hasHeightChange = HasHeightChange( dedupedPoints[i-2], dedupedPoints[i-1], dedupedPoints[i] ); - // 🔥 关键修复:检查限速变化 + // 检查限速变化 bool hasSpeedLimitChange = HasSpeedLimitChange( dedupedPoints[i-2], dedupedPoints[i-1], @@ -205,37 +215,36 @@ namespace NavisworksTransport.PathPlanning ); // 保留转折点、高度变化点或限速变化点 - if (!currentDirection.Equals(prevDirection) || hasHeightChange || hasSpeedLimitChange) - { - simplified.Add(dedupedGridPath[i - 1]); - prevDirection = currentDirection; + bool shouldKeep = !currentDirection.Equals(prevDirection) || hasHeightChange || hasSpeedLimitChange || forceKeepNext; - if (hasHeightChange) - { - LogManager.Debug($"[网格路径优化] 保留高度变化点 {i-1}"); - } - if (hasSpeedLimitChange) - { - LogManager.Debug($"[网格路径优化] 保留限速变化点 {i-1}"); - } + if (shouldKeep) + { + simplifiedIndices.Add(i - 1); // 保留索引而不是网格坐标 + prevDirection = currentDirection; } + + // 🔥 如果当前检测到高度变化,下一个点也必须保留 + forceKeepNext = hasHeightChange; } - // 添加终点 - simplified.Add(dedupedGridPath[dedupedGridPath.Count - 1]); + // 添加终点索引 + simplifiedIndices.Add(dedupedGridPath.Count - 1); - LogManager.Info($"[网格路径优化] 最终优化完成:{gridPath.Count} -> {dedupedGridPath.Count}(去重) -> {simplified.Count}(简化) 个点"); + LogManager.Info($"[网格路径优化] 简化完成:{dedupedGridPath.Count} -> {simplifiedIndices.Count} 个点"); - // 步骤3:转换回PathPoint格式 + // 步骤3:根据索引提取PathPoint var simplifiedPoints = new List(); - foreach (var gridPoint in simplified) + foreach (int index in simplifiedIndices) { - // 在去重后的路径中找到对应的点 - int originalIndex = dedupedGridPath.FindIndex(g => g.Equals(gridPoint)); - if (originalIndex >= 0) - { - simplifiedPoints.Add(dedupedPoints[originalIndex]); - } + simplifiedPoints.Add(dedupedPoints[index]); + } + + // 打印优化后的路径点详情 + LogManager.Info($"[优化后路径详情] 共 {simplifiedPoints.Count} 个点:"); + for (int i = 0; i < simplifiedPoints.Count; i++) + { + var pt = simplifiedPoints[i]; + LogManager.Info($" [优化后点{i}] 位置=({pt.Position.X:F2}, {pt.Position.Y:F2}, Z={pt.Position.Z:F2}), 限速={pt.SpeedLimit:F1}"); } return simplifiedPoints;