diff --git a/doc/working/2025-01-08-multi-layer-gridmap-solution.md b/doc/working/2025-01-08-multi-layer-gridmap-solution.md new file mode 100644 index 0000000..7999407 --- /dev/null +++ b/doc/working/2025-01-08-multi-layer-gridmap-solution.md @@ -0,0 +1,194 @@ +# 多高度层GridMap解决方案 + +**日期**: 2025-01-08 +**问题**: 楼梯与楼板在同一(x,y)坐标重叠时,2D网格投影导致互相覆盖 +**目标**: 支持多高度层,同时保留RoyT.AStar高性能算法 + +--- + +## 问题分析 + +### 场景描述 +- **楼梯**: 倾斜通道,在(x,y)位置有不同Z高度 +- **楼梯下方楼板**: 水平通道,在相同(x,y)位置也可通行 +- **当前问题**: 两者投影到同一2D网格坐标(x,y),后处理的覆盖前处理的 + +### 核心矛盾 +- 2D网格投影系统无法处理垂直空间层叠 +- 起点在楼面(Z=3.0m),终点在楼梯(Z=5.0m)的路径无法正确规划 + +--- + +## 解决方案 + +### 核心思路 +1. **保留RoyT.AStar**: 2D A*算法不变,继续产生最优2D路径 +2. **扩展数据结构**: GridCell支持多个HeightLayer +3. **投影改为追加**: 通道投影不覆盖,而是添加新层 +4. **路径后处理选层**: A*产生2D路径后,为每个路径点智能选择高度层 + +### 关键洞察 +- **2D路径已经最优**: RoyT.AStar产生的2D网格序列`[(x1,y1), (x2,y2), ..., (xn,yn)]`不需要改变 +- **只需选Z坐标**: 为每个2D点选择正确的高度层,形成3D路径 +- **基于高度趋势**: 根据当前位置到终点的剩余高度变化,选择最合适的层 + +--- + +## 技术设计 + +### 1. 数据结构扩展 + +#### HeightLayer结构 +```csharp +public struct HeightLayer +{ + public double Z; // 该层的Z坐标 + public HeightInterval PassableHeight; // 可通行高度范围(用于车辆检查) + public ModelItem SourceItem; // 来源模型项 + public double SpeedLimit; // 限速 + public CategoryAttributeManager.LogisticsElementType Type; // 层类型 +} +``` + +#### GridCell扩展 +```csharp +public struct GridCell +{ + // ... 保留所有现有字段 ... + + // 新增:多高度层支持 + public List HeightLayers { get; set; } +} +``` + +### 2. 通道投影改为追加模式 + +**文件**: `src/PathPlanning/ChannelBasedGridBuilder.cs` +**方法**: `RasterizeTriangleToGrid()` + +**修改前**: +```csharp +var cell = GridCellBuilder.Channel(preciseWorldPosition, channel, channelSpeedLimit); +gridMap.PlaceCell(gridPos, cell); // 覆盖 +``` + +**修改后**: +```csharp +var layer = new HeightLayer +{ + Z = gridCenterHeight, + PassableHeight = new HeightInterval(0, 0), // 初始化,后续设置 + SourceItem = channel, + SpeedLimit = channelSpeedLimit, + Type = CategoryAttributeManager.GetLogisticsElementType(channel) +}; +gridMap.AddHeightLayer(gridPos, layer); // 追加 +``` + +### 3. PassableHeight计算适配 + +**保留现有逻辑**: `SetChannelPassableHeights()` 已经很完善 +**修改点**: 从设置单层GridCell改为设置多个HeightLayer + +```csharp +// 为每个高度层设置PassableHeight +if (cell.HeightLayers != null && cell.HeightLayers.Any()) +{ + for (int i = 0; i < cell.HeightLayers.Count; i++) + { + var layer = cell.HeightLayers[i]; + layer.PassableHeight = new HeightInterval(0, scanHeightInModelUnits); + cell.HeightLayers[i] = layer; + } +} +``` + +### 4. 路径后处理智能选层 + +**核心算法**: `AssignLayerBased3DCoordinates()` + +**选层策略**: +``` +1. 起点锁定: 选择包含start.Z的层 +2. 终点锁定: 选择包含end.Z的层 +3. 中间点选层: + - 计算剩余高度变化: remainingHeight = end.Z - current.Z + - 计算期望每步变化: desiredChange = remainingHeight / remainingSteps + - 目标Z: targetZ = prevZ + desiredChange + - 选择最接近targetZ且净空足够的层 +``` + +**示例执行**: +``` +场景: 起点楼面(3.0m) → 终点楼梯(5.0m) +2D路径: [(0,0), (1,1), (2,2), (3,3)] + +赋值过程: +1. (0,0): Z=3.0 (起点锁定楼面层) +2. (1,1): remainingHeight=2.0, steps=2, desiredChange=1.0 + targetZ=4.0, 选择楼梯层3.5m (更接近4.0) +3. (2,2): remainingHeight=1.5, steps=1, desiredChange=1.5 + targetZ=5.0, 选择楼梯层4.5m +4. (3,3): Z=5.0 (终点锁定楼梯层) + +最终路径: [(0,0,3.0), (1,1,3.5), (2,2,4.5), (3,3,5.0)] +✅ 平滑爬升! +``` + +--- + +## 实施计划 + +### 步骤1: 扩展GridCell支持多层 +- **文件**: `src/PathPlanning/GridMap.cs` +- **内容**: 新增HeightLayer结构、扩展GridCell、添加AddHeightLayer()和FindLayerContainingZ()方法 + +### 步骤2: 通道投影改为追加模式 +- **文件**: `src/PathPlanning/ChannelBasedGridBuilder.cs` +- **内容**: 修改RasterizeTriangleToGrid(),从PlaceCell改为AddHeightLayer + +### 步骤3: 适配PassableHeight计算 +- **文件**: `src/PathPlanning/GridMapGenerator.cs` +- **内容**: 修改SetChannelPassableHeights()支持多层 + +### 步骤4: 实现智能选层算法 +- **文件**: `src/PathPlanning/AutoPathFinder.cs` +- **内容**: 新增AssignLayerBased3DCoordinates()和SelectBestLayer()方法,替换现有路径后处理 + +### 步骤5: 测试验证 +- 起点楼面 → 终点楼梯(验证爬升) +- 起点楼梯 → 终点楼面(验证下降) +- 楼梯下方通行(验证多层并存) + +--- + +## 关键约束 + +1. **保留RoyT.AStar**: 不修改A*算法,保持高性能 +2. **不考虑向后兼容**: 直接改为多层架构 +3. **保留现有PassableHeight逻辑**: 只适配到多层结构 +4. **最小化改动**: 只修改必要的数据结构和投影逻辑 + +--- + +## 预期效果 + +- ✅ 解决楼梯-楼板重叠问题 +- ✅ 自动选择最优高度层 +- ✅ 平滑的高度过渡 +- ✅ 保持RoyT.AStar高性能 +- ✅ 代码改动最小化 + +--- + +## 文件修改清单 + +1. `src/PathPlanning/GridMap.cs` - 数据结构扩展 +2. `src/PathPlanning/ChannelBasedGridBuilder.cs` - 投影逻辑修改 +3. `src/PathPlanning/GridMapGenerator.cs` - PassableHeight计算适配 +4. `src/PathPlanning/AutoPathFinder.cs` - 路径后处理智能选层 + +--- + +**状态**: 设计完成,待实施 +**预计工作量**: 4个文件,约200行代码修改 diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 1768a80..ff32e03 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -105,8 +105,8 @@ namespace NavisworksTransport.PathPlanning .Select(point => (point, gridMap.WorldToGrid(point))) .ToList(); - // 6. 根据网格点通行高度区间对路径点Z坐标调整 - var enhancedPath = ApplyGridHeightConstraints(pathWithGridCoords, gridMap, vehicleHeight); + // 6. 根据网格点通行高度区间对路径点Z坐标调整(传递原始坐标以保留用户指定的Z值) + var enhancedPath = ApplyGridHeightConstraints(pathWithGridCoords, gridMap, vehicleHeight, originalStart, originalEnd); // 7. 验证路径中所有点的高度约束(直接使用原始网格坐标,因为网格位置未变) ValidatePathHeightConstraints(pathWithGridCoords, gridMap, vehicleHeight); @@ -214,34 +214,24 @@ namespace NavisworksTransport.PathPlanning } // 检查基本可通行性和高度约束 - bool isPassable = cell.IsWalkable; + bool isPassable = IsPassableWithHeight(cell, vehicleHeight); - if (isPassable) + if (cell.IsWalkable && !isPassable) { - if (cell.PassableHeight.MaxZ > cell.PassableHeight.MinZ) - { - // vehicleHeight已经是模型单位,直接比较即可 - bool heightOk = cell.PassableHeight.GetSpan() >= vehicleHeight; - if (!heightOk) - { - heightConstrainedCells++; + // 本来可通行但被高度约束排除 + heightConstrainedCells++; - // 详细日志:记录被高度约束排除的单元格(仅记录前10个避免日志过多) - if (heightConstrainedCells <= 10) - { - LogManager.Info($"[A*高度约束] 单元格({x},{y})被高度约束排除:车辆高度{vehicleHeight:F2}模型单位,可用区间:{cell.PassableHeight.MinZ:F2}-{cell.PassableHeight.MaxZ:F2}(跨度{cell.PassableHeight.GetSpan():F2})"); - } - else if (heightConstrainedCells == 11) - { - LogManager.Info($"[A*高度约束] 还有更多单元格被高度约束排除,不再详细记录..."); - } - } - isPassable = heightOk; - } - else + // 详细日志:记录被高度约束排除的单元格(仅记录前10个避免日志过多) + if (heightConstrainedCells <= 10) { - // 没有高度信息的网格直接标记为不可通行 - isPassable = false; + var layerInfo = cell.HeightLayers != null && cell.HeightLayers.Count > 0 + ? $"有{cell.HeightLayers.Count}个高度层" + : "无高度层"; + LogManager.Info($"[A*高度约束] 单元格({x},{y})被高度约束排除:车辆高度{vehicleHeight:F2}模型单位,{layerInfo}"); + } + else if (heightConstrainedCells == 11) + { + LogManager.Info($"[A*高度约束] 还有更多单元格被高度约束排除,不再详细记录..."); } } @@ -255,11 +245,7 @@ namespace NavisworksTransport.PathPlanning if (x + 1 < gridMap.Width) { var rightCell = gridMap.Cells[x + 1, y]; - bool rightPassable = rightCell.IsWalkable; - if (rightPassable && rightCell.PassableHeight.MaxZ > rightCell.PassableHeight.MinZ) - { - rightPassable = rightCell.PassableHeight.GetSpan() >= vehicleHeight; - } + bool rightPassable = IsPassableWithHeight(rightCell, vehicleHeight); if (rightPassable) { var rightPos = new GridPosition(x + 1, y); @@ -273,11 +259,7 @@ namespace NavisworksTransport.PathPlanning if (y + 1 < gridMap.Height) { var bottomCell = gridMap.Cells[x, y + 1]; - bool bottomPassable = bottomCell.IsWalkable; - if (bottomPassable && bottomCell.PassableHeight.MaxZ > bottomCell.PassableHeight.MinZ) - { - bottomPassable = bottomCell.PassableHeight.GetSpan() >= vehicleHeight; - } + bool bottomPassable = IsPassableWithHeight(bottomCell, vehicleHeight); if (bottomPassable) { var bottomPos = new GridPosition(x, y + 1); @@ -291,11 +273,7 @@ namespace NavisworksTransport.PathPlanning if (x - 1 >= 0) { var leftCell = gridMap.Cells[x - 1, y]; - bool leftPassable = leftCell.IsWalkable; - if (leftPassable && leftCell.PassableHeight.MaxZ > leftCell.PassableHeight.MinZ) - { - leftPassable = leftCell.PassableHeight.GetSpan() >= vehicleHeight; - } + bool leftPassable = IsPassableWithHeight(leftCell, vehicleHeight); if (leftPassable) { var leftPos = new GridPosition(x - 1, y); @@ -309,11 +287,7 @@ namespace NavisworksTransport.PathPlanning if (y - 1 >= 0) { var topCell = gridMap.Cells[x, y - 1]; - bool topPassable = topCell.IsWalkable; - if (topPassable && topCell.PassableHeight.MaxZ > topCell.PassableHeight.MinZ) - { - topPassable = topCell.PassableHeight.GetSpan() >= vehicleHeight; - } + bool topPassable = IsPassableWithHeight(topCell, vehicleHeight); if (topPassable) { var topPos = new GridPosition(x, y - 1); @@ -430,16 +404,10 @@ namespace NavisworksTransport.PathPlanning var cell = gridMap.Cells[x, y]; // 检查基本可通行性和高度约束 - bool isPassable = cell.IsWalkable; - if (isPassable && cell.PassableHeight.MaxZ > cell.PassableHeight.MinZ) + bool isPassable = IsPassableWithHeight(cell, vehicleHeight); + if (cell.IsWalkable && !isPassable) { - // vehicleHeight已经是模型单位,直接比较即可 - bool heightOk = cell.PassableHeight.GetSpan() >= vehicleHeight; - if (!heightOk) - { - heightConstrainedCells++; - isPassable = false; - } + heightConstrainedCells++; } if (isPassable) @@ -581,10 +549,15 @@ namespace NavisworksTransport.PathPlanning if (!cell.IsWalkable) return false; - if (cell.PassableHeight.MaxZ > cell.PassableHeight.MinZ) + // 检查是否有任何高度层满足车辆高度要求 + if (cell.HeightLayers != null && cell.HeightLayers.Count > 0) { - // vehicleHeight已经是模型单位,直接比较即可 - return cell.PassableHeight.GetSpan() >= vehicleHeight; + foreach (var layer in cell.HeightLayers) + { + if (layer.PassableHeight.GetSpan() >= vehicleHeight) + return true; + } + return false; } return false; @@ -1210,23 +1183,32 @@ namespace NavisworksTransport.PathPlanning return false; } - // 检查高度约束 - if (cell.PassableHeight.MaxZ > cell.PassableHeight.MinZ) + // 检查高度层约束 + if (cell.HeightLayers != null && cell.HeightLayers.Count > 0) { - bool spanOk = cell.PassableHeight.GetSpan() >= vehicleHeight; - // 将绝对坐标转换为相对于网格底面的坐标 - double relativeZ = point.Z - cell.WorldPosition.Z; - bool containsHeight = cell.PassableHeight.Contains(relativeZ); + // 查找包含指定Z坐标且满足车辆高度的层 + var matchingLayer = gridMap.FindLayerContainingZ(gridPos, point.Z, tolerance: 0.5); - if (spanOk && containsHeight) + if (matchingLayer.HasValue) { - return true; + bool heightOk = matchingLayer.Value.PassableHeight.GetSpan() >= vehicleHeight; + if (heightOk) + { + return true; + } + else + { + LogManager.Warning($"[高度检查] ❌ 找到匹配Z={point.Z:F2}的层Z={matchingLayer.Value.Z:F2},但高度不足:车辆高度{vehicleHeight:F2},层高度跨度{matchingLayer.Value.PassableHeight.GetSpan():F2}"); + } + } + else + { + LogManager.Warning($"[高度检查] ❌ 单元格({gridX}, {gridY})有{cell.HeightLayers.Count}个高度层,但无法找到包含Z={point.Z:F2}的层"); } - - LogManager.Warning($"[高度检查] ❌ 高度区间不满足要求:({gridX}, {gridY}) - PassableHeight=[{cell.PassableHeight.MinZ:F2}, {cell.PassableHeight.MaxZ:F2}], VehicleHeight={vehicleHeight:F2}, RelativeZ={relativeZ:F2}, SpanOK={spanOk}, ContainsHeight={containsHeight}, 原因:高度约束不满足"); return false; } + LogManager.Warning($"[高度检查] ❌ 单元格({gridX}, {gridY})没有高度层信息"); return false; } catch (Exception ex) @@ -1242,16 +1224,35 @@ namespace NavisworksTransport.PathPlanning /// 路径点和对应的网格坐标 /// 网格地图 /// 车辆高度(模型单位) + /// 用户指定的原始起点(保留原始Z坐标) + /// 用户指定的原始终点(保留原始Z坐标) /// 调整后的路径 - private List ApplyGridHeightConstraints(List<(Point3D point, GridPoint2D gridPos)> pathWithGridCoords, GridMap gridMap, double vehicleHeight) + private List ApplyGridHeightConstraints(List<(Point3D point, GridPoint2D gridPos)> pathWithGridCoords, GridMap gridMap, double vehicleHeight, Point3D originalStart, Point3D originalEnd) { - LogManager.Info($"[应用高度区间] 开始调整,路径点数: {pathWithGridCoords.Count}"); + LogManager.Info($"[智能选层] 开始为路径点选择最优高度层,路径点数: {pathWithGridCoords.Count}"); + + if (pathWithGridCoords.Count == 0) + return new List(); var adjustedPath = new List(); - int adjustedCount = 0; - foreach (var (point, gridPos) in pathWithGridCoords) + // 1. 使用用户指定的原始起点和终点Z坐标(而不是从转换后的路径中提取) + double startZ = originalStart.Z; + double endZ = originalEnd.Z; + + // 对比日志:显示原始坐标与路径坐标的差异 + var pathStartZ = pathWithGridCoords[0].point.Z; + var pathEndZ = pathWithGridCoords[pathWithGridCoords.Count - 1].point.Z; + + LogManager.Info($"[智能选层] 原始起点Z={startZ:F3}, 原始终点Z={endZ:F3}"); + LogManager.Info($"[智能选层] 路径起点Z={pathStartZ:F3}, 路径终点Z={pathEndZ:F3}"); + LogManager.Info($"[智能选层] 使用原始Z坐标进行智能选层,高度变化={endZ - startZ:F3}"); + + // 2. 为每个路径点选择高度层 + for (int i = 0; i < pathWithGridCoords.Count; i++) { + var (point, gridPos) = pathWithGridCoords[i]; + // 检查网格位置有效性 if (!gridMap.IsValidGridPosition(gridPos)) { @@ -1261,25 +1262,133 @@ namespace NavisworksTransport.PathPlanning var cell = gridMap.Cells[gridPos.X, gridPos.Y]; - // 检查单个高度区间是否满足车辆高度要求 - if (cell.PassableHeight.GetSpan() >= vehicleHeight) + // 如果没有高度层,使用原始点 + if (cell.HeightLayers == null || cell.HeightLayers.Count == 0) { - // 使用高度区间的底部作为最优Z坐标 - var optimalZ = cell.WorldPosition.Z + cell.PassableHeight.MinZ; - adjustedPath.Add(new Point3D(point.X, point.Y, optimalZ)); + adjustedPath.Add(point); + continue; + } + + // 起点和终点:锁定到包含其Z坐标的层 + if (i == 0) + { + var selectedLayer = SelectBestLayer(cell.HeightLayers, startZ, vehicleHeight, startZ, endZ, i, pathWithGridCoords.Count, true); + if (selectedLayer.HasValue) + { + adjustedPath.Add(new Point3D(point.X, point.Y, selectedLayer.Value.Z)); + LogManager.Debug($"[智能选层] 起点({gridPos.X},{gridPos.Y}) Z={selectedLayer.Value.Z:F3}"); + } + else + { + adjustedPath.Add(point); + } + } + else if (i == pathWithGridCoords.Count - 1) + { + var selectedLayer = SelectBestLayer(cell.HeightLayers, endZ, vehicleHeight, startZ, endZ, i, pathWithGridCoords.Count, true); + if (selectedLayer.HasValue) + { + adjustedPath.Add(new Point3D(point.X, point.Y, selectedLayer.Value.Z)); + LogManager.Debug($"[智能选层] 终点({gridPos.X},{gridPos.Y}) Z={selectedLayer.Value.Z:F3}"); + } + else + { + adjustedPath.Add(point); + } } else { - // 高度不足,保持原始点 - adjustedPath.Add(point); + // 中间点:根据高度趋势选择最佳层 + var prevZ = adjustedPath[i - 1].Z; + var selectedLayer = SelectBestLayer(cell.HeightLayers, prevZ, vehicleHeight, startZ, endZ, i, pathWithGridCoords.Count, false); + + if (selectedLayer.HasValue) + { + adjustedPath.Add(new Point3D(point.X, point.Y, selectedLayer.Value.Z)); + } + else + { + adjustedPath.Add(point); + } } - adjustedCount++; } - LogManager.Info($"[应用高度区间] 完成,调整了 {adjustedCount}/{pathWithGridCoords.Count} 个点"); + LogManager.Info($"[智能选层] 完成,调整了 {adjustedPath.Count}/{pathWithGridCoords.Count} 个点"); return adjustedPath; } + /// + /// 为路径点选择最佳高度层 + /// + /// 可用的高度层列表 + /// 参考Z坐标(起点/终点为目标Z,中间点为前一点Z) + /// 车辆高度 + /// 路径起点Z + /// 路径终点Z + /// 当前点索引 + /// 总点数 + /// 是否精确匹配(起点/终点使用) + /// 选中的高度层,如果没有合适的返回null + private HeightLayer? SelectBestLayer(List layers, double referenceZ, double vehicleHeight, + double startZ, double endZ, int currentIndex, int totalPoints, bool exactMatch) + { + if (layers == null || layers.Count == 0) + return null; + + // 只有一层,直接返回 + if (layers.Count == 1) + { + var layer = layers[0]; + if (layer.PassableHeight.GetSpan() >= vehicleHeight) + return layer; + return null; + } + + // 确定目标Z坐标 + double targetZ; + if (exactMatch) + { + // 精确匹配:直接使用参考Z + targetZ = referenceZ; + } + else + { + // 中间点:根据高度趋势计算目标Z + int remainingSteps = totalPoints - currentIndex - 1; + if (remainingSteps <= 0) + { + // 没有剩余步骤,使用参考Z + targetZ = referenceZ; + } + else + { + // 计算期望的高度变化率 + double remainingHeight = endZ - referenceZ; + double desiredChange = remainingHeight / remainingSteps; + targetZ = referenceZ + desiredChange; + } + } + + // 选择最接近目标Z且满足车辆高度要求的层 + HeightLayer? bestLayer = null; + double minDistance = double.MaxValue; + + foreach (var layer in layers) + { + if (layer.PassableHeight.GetSpan() < vehicleHeight) + continue; + + double distance = Math.Abs(layer.Z - targetZ); + if (distance < minDistance) + { + minDistance = distance; + bestLayer = layer; + } + } + + return bestLayer; + } + /// /// 将A*返回的米坐标转换为网格坐标(统一方法) /// @@ -1605,16 +1714,10 @@ namespace NavisworksTransport.PathPlanning var cell = gridMap.Cells[x, y]; // 检查基本可通行性和高度约束 - bool isPassable = cell.IsWalkable; - if (isPassable && cell.PassableHeight.MaxZ > cell.PassableHeight.MinZ) + bool isPassable = IsPassableWithHeight(cell, vehicleHeight); + if (cell.IsWalkable && !isPassable) { - // vehicleHeight已经是模型单位,直接比较即可 - bool heightOk = cell.PassableHeight.GetSpan() >= vehicleHeight; - if (!heightOk) - { - heightConstrainedCells++; - isPassable = false; - } + heightConstrainedCells++; } if (isPassable) diff --git a/src/PathPlanning/ChannelBasedGridBuilder.cs b/src/PathPlanning/ChannelBasedGridBuilder.cs index 0dc867f..d5a0420 100644 --- a/src/PathPlanning/ChannelBasedGridBuilder.cs +++ b/src/PathPlanning/ChannelBasedGridBuilder.cs @@ -274,12 +274,18 @@ namespace NavisworksTransport.PathPlanning // 计算网格中心点在三角形平面上的精确高度 double gridCenterHeight = GetHeightAtPoint(triangle, worldPos.X, worldPos.Y); - // 使用精确的世界坐标位置创建通道GridCell - var preciseWorldPosition = new Point3D(worldPos.X, worldPos.Y, gridCenterHeight); - var cell = GridCellBuilder.Channel(preciseWorldPosition, channel, channelSpeedLimit); + // 创建高度层(追加模式,不覆盖) + var channelType = CategoryAttributeManager.GetLogisticsElementType(channel); + var layer = new HeightLayer( + z: gridCenterHeight, + passableHeight: new HeightInterval(0, 0), // 初始化,后续由SetChannelPassableHeights设置 + sourceItem: channel, + speedLimit: channelSpeedLimit, + type: channelType + ); - // 一次性放置完整配置的GridCell - gridMap.PlaceCell(gridPos, cell); + // 追加高度层(支持同一网格多个高度) + gridMap.AddHeightLayer(gridPos, layer); } } } diff --git a/src/PathPlanning/GridCellBuilder.cs b/src/PathPlanning/GridCellBuilder.cs index 844561c..c1d19dc 100644 --- a/src/PathPlanning/GridCellBuilder.cs +++ b/src/PathPlanning/GridCellBuilder.cs @@ -28,7 +28,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = channel, SpeedLimit = speedLimit, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = cost // 会在PlaceCell时由ApplyCellValidationRules处理 }; } @@ -52,7 +52,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = door, SpeedLimit = speedLimit, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = cost }; } @@ -74,7 +74,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = obstacle, SpeedLimit = 0, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = double.MaxValue // 障碍物成本最大 }; } @@ -98,7 +98,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = elevator, SpeedLimit = speedLimit, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = cost }; } @@ -122,7 +122,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = stairs, SpeedLimit = speedLimit, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = cost }; } @@ -143,7 +143,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = worldPosition, RelatedModelItem = null, SpeedLimit = 0, - PassableHeight = new HeightInterval(), + HeightLayers = new System.Collections.Generic.List(), Cost = double.MaxValue }; } diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs index 5ec512b..828be01 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -163,7 +163,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = false, CellType = CategoryAttributeManager.LogisticsElementType.Unknown, // 修改:默认为未知/空洞类型 IsInChannel = false, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = ChannelType.Other, WorldPosition = worldPos }; @@ -257,7 +257,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = isWalkable, CellType = cellType, IsInChannel = cellType == CategoryAttributeManager.LogisticsElementType.通道, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = cellType == CategoryAttributeManager.LogisticsElementType.通道 ? ChannelType.Corridor : ChannelType.Other, WorldPosition = worldPos, SpeedLimit = speedLimit @@ -286,6 +286,88 @@ namespace NavisworksTransport.PathPlanning Cells[gridPosition.X, gridPosition.Y] = cell; } + /// + /// 为指定网格坐标添加高度层(追加模式,不覆盖) + /// + /// 网格坐标 + /// 要添加的高度层 + public void AddHeightLayer(GridPoint2D gridPosition, HeightLayer layer) + { + if (!IsValidGridPosition(gridPosition)) + return; + + var cell = Cells[gridPosition.X, gridPosition.Y]; + + // 确保HeightLayers列表已初始化 + if (cell.HeightLayers == null) + { + cell.HeightLayers = new List(); + } + + // 添加新的高度层 + cell.HeightLayers.Add(layer); + + // 更新单元格的基本属性(使用第一个层的属性) + if (cell.HeightLayers.Count == 1) + { + cell.IsWalkable = true; + cell.CellType = layer.Type; + cell.IsInChannel = true; + cell.RelatedModelItem = layer.SourceItem; + cell.SpeedLimit = layer.SpeedLimit; + cell.Cost = cell.GetCost(); + } + + Cells[gridPosition.X, gridPosition.Y] = cell; + } + + /// + /// 查找包含指定Z坐标的高度层 + /// + /// 网格坐标 + /// Z坐标(米) + /// 容差(米),默认0.1m + /// 符合条件的高度层,如果没有则返回null + public HeightLayer? FindLayerContainingZ(GridPoint2D gridPosition, double z, double tolerance = 0.1) + { + if (!IsValidGridPosition(gridPosition)) + return null; + + var cell = Cells[gridPosition.X, gridPosition.Y]; + if (cell.HeightLayers == null || cell.HeightLayers.Count == 0) + return null; + + // 查找Z坐标最接近的层 + HeightLayer? bestLayer = null; + double minDistance = double.MaxValue; + + foreach (var layer in cell.HeightLayers) + { + double distance = Math.Abs(layer.Z - z); + if (distance < minDistance && distance <= tolerance) + { + minDistance = distance; + bestLayer = layer; + } + } + + return bestLayer; + } + + /// + /// 获取指定网格位置的所有高度层 + /// + /// 网格坐标 + /// 高度层列表,如果没有则返回空列表 + public List GetHeightLayers(GridPoint2D gridPosition) + { + if (!IsValidGridPosition(gridPosition)) + return new List(); + + var cell = Cells[gridPosition.X, gridPosition.Y]; + return cell.HeightLayers ?? new List(); + } + /// /// 应用GridCell验证规则 /// 确保Unknown和障碍物类型永远不可通行,并设置合适的成本 @@ -323,8 +405,16 @@ namespace NavisworksTransport.PathPlanning if (!cell.IsWalkable) return false; - // 检查是否有任何高度区间包含指定高度 - return (cell.PassableHeight.MaxZ - cell.PassableHeight.MinZ) >= height; + // 检查是否有任何高度层满足高度要求 + if (cell.HeightLayers != null && cell.HeightLayers.Count > 0) + { + foreach (var layer in cell.HeightLayers) + { + if (layer.PassableHeight.GetSpan() >= height) + return true; + } + } + return false; } /// @@ -594,9 +684,9 @@ namespace NavisworksTransport.PathPlanning public bool IsInChannel { get; set; } /// - /// 可通行高度区间列表 - 支持2.5D路径规划 + /// 多高度层列表 - 支持同一网格坐标多个高度层(如楼梯下方通道) /// - public HeightInterval PassableHeight { get; set; } + public List HeightLayers { get; set; } /// /// 通道类型 - 用于区分不同类型的通道 @@ -661,7 +751,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = false, CellType = CategoryAttributeManager.LogisticsElementType.障碍物, IsInChannel = false, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = ChannelType.Other, SpeedLimit = 0 }; @@ -680,7 +770,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = true, CellType = CategoryAttributeManager.LogisticsElementType.楼板, IsInChannel = false, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = ChannelType.Other, SpeedLimit = 0 }; @@ -700,7 +790,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = isOpen, CellType = CategoryAttributeManager.LogisticsElementType.门, IsInChannel = isOpen, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = ChannelType.Other, SpeedLimit = 0 }; @@ -720,7 +810,7 @@ namespace NavisworksTransport.PathPlanning IsWalkable = true, CellType = CategoryAttributeManager.LogisticsElementType.通道, IsInChannel = true, - PassableHeight = new HeightInterval(), + HeightLayers = new List(), ChannelType = channelType, SpeedLimit = 0 }; @@ -729,6 +819,49 @@ namespace NavisworksTransport.PathPlanning } } + /// + /// 高度层结构体 + /// 用于支持多高度层的网格单元格 + /// + public struct HeightLayer + { + /// + /// 该层的Z坐标(米) + /// + public double Z { get; set; } + + /// + /// 可通行高度范围(用于车辆检查) + /// + public HeightInterval PassableHeight { get; set; } + + /// + /// 来源模型项 + /// + public ModelItem SourceItem { get; set; } + + /// + /// 限速(米/秒),0表示未设置限速 + /// + public double SpeedLimit { get; set; } + + /// + /// 层类型 + /// + public CategoryAttributeManager.LogisticsElementType Type { get; set; } + + /// + /// 构造函数 + /// + public HeightLayer(double z, HeightInterval passableHeight, ModelItem sourceItem, double speedLimit, CategoryAttributeManager.LogisticsElementType type) + { + Z = z; + PassableHeight = passableHeight; + SourceItem = sourceItem; + SpeedLimit = speedLimit; + Type = type; + } + } /// /// 高度区间结构体 - 用于2.5D路径规划 diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index 66db9d7..379b43d 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -309,11 +309,8 @@ namespace NavisworksTransport.PathPlanning var cell = GridCellBuilder.Door(preciseWorldPosition, doorItem, doorSpeedLimit); LogManager.Info($"[门网格创建] 位置({preciseWorldPosition.X:F2},{preciseWorldPosition.Y:F2},{preciseWorldPosition.Z:F2}) 限速: {cell.SpeedLimit:F2}m/s"); - // 设置门的高度范围 - if (doorHeight > 0) - { - cell.PassableHeight = new HeightInterval(0, doorHeight); - } + // 多层架构:PassableHeight现在在HeightLayer中设置 + // 门的高度范围由AddHeightLayer时设置 // 一次性放置完整配置的GridCell gridMap.PlaceCell(gridPos, cell); @@ -348,21 +345,31 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"【高度约束设置】开始为所有可通行网格设置高度约束,扫描高度: {scanHeightInModelUnits:F2}模型单位"); int processedCount = 0; + int layerCount = 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) + if (cell.IsWalkable && cell.HeightLayers != null && cell.HeightLayers.Count > 0) { - cell.PassableHeight = new HeightInterval(0, scanHeightInModelUnits); + // 为每个高度层设置PassableHeight + for (int i = 0; i < cell.HeightLayers.Count; i++) + { + var layer = cell.HeightLayers[i]; + layer.PassableHeight = new HeightInterval(0, scanHeightInModelUnits); + cell.HeightLayers[i] = layer; + layerCount++; + } + gridMap.Cells[x, y] = cell; processedCount++; } } } - LogManager.Info($"【高度约束设置】完成,已处理 {processedCount} 个可通行网格"); + LogManager.Info($"【高度约束设置】完成,已处理 {processedCount} 个网格,{layerCount} 个高度层"); } /// @@ -492,8 +499,8 @@ namespace NavisworksTransport.PathPlanning // 更新网格单元格的高度区间信息 var cell = gridMap.Cells[gridX, gridY]; - // 直接设置高度区间 - cell.PassableHeight = interval; + // 多层架构:高度区间在HeightLayer中设置,这里不再需要 + // cell.PassableHeight = interval; // 关键修复:只处理通道单元格的高度信息集成 // 非通道单元格不应该通过高度扫描而改变其类型或可通行性 @@ -516,7 +523,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = point, RelatedModelItem = cell.RelatedModelItem, // 保持原有关联 SpeedLimit = cell.SpeedLimit, // 保持原有限速 - PassableHeight = interval, + HeightLayers = cell.HeightLayers, // 保持高度层 Cost = 1.0 }; @@ -538,7 +545,7 @@ namespace NavisworksTransport.PathPlanning WorldPosition = point, RelatedModelItem = cell.RelatedModelItem, // 保持原有关联 SpeedLimit = cell.SpeedLimit, // 保持原有限速 - PassableHeight = interval, + HeightLayers = cell.HeightLayers, // 保持高度层 Cost = double.MaxValue }; @@ -554,9 +561,10 @@ namespace NavisworksTransport.PathPlanning // 这种情况理论上不应该发生,因为只对通道单元格进行扫描 LogManager.Warning($"[通道2.5D模式] 警告:非通道单元格 ({gridX},{gridY}) 收到高度数据,类型:{cell.CellType}, IsInChannel:{cell.IsInChannel}"); - // 仍然更新高度信息,但保持原有状态 + // 仍然更新世界位置,但保持原有状态 cell.WorldPosition = point; - cell.PassableHeight = interval; + // 多层架构:高度区间在HeightLayer中设置 + // cell.PassableHeight = interval; gridMap.Cells[gridX, gridY] = cell; } updatedCells++; @@ -1311,12 +1319,9 @@ namespace NavisworksTransport.PathPlanning cell.IsInChannel = false; cell.RelatedModelItem = update.Item; - // 记录高度信息 - var groundHeight = cell.WorldPosition.Z; - cell.PassableHeight = new HeightInterval( - update.BoundingBox.Min.Z - groundHeight, - update.BoundingBox.Max.Z - groundHeight - ); + // 多层架构:障碍物不需要PassableHeight + // var groundHeight = cell.WorldPosition.Z; + // cell.PassableHeight = new HeightInterval(...) gridMap.Cells[update.X, update.Y] = cell; updatedCells++;