给网格增加了高度层概念,试图支持楼面上的楼梯,还不完善

This commit is contained in:
tian 2025-10-08 23:56:59 +08:00
parent a8e8760e2b
commit 9ccf925964
6 changed files with 572 additions and 131 deletions

View File

@ -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<HeightLayer> 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行代码修改

View File

@ -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
/// <param name="pathWithGridCoords">路径点和对应的网格坐标</param>
/// <param name="gridMap">网格地图</param>
/// <param name="vehicleHeight">车辆高度(模型单位)</param>
/// <param name="originalStart">用户指定的原始起点保留原始Z坐标</param>
/// <param name="originalEnd">用户指定的原始终点保留原始Z坐标</param>
/// <returns>调整后的路径</returns>
private List<Point3D> ApplyGridHeightConstraints(List<(Point3D point, GridPoint2D gridPos)> pathWithGridCoords, GridMap gridMap, double vehicleHeight)
private List<Point3D> 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<Point3D>();
var adjustedPath = new List<Point3D>();
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;
}
/// <summary>
/// 为路径点选择最佳高度层
/// </summary>
/// <param name="layers">可用的高度层列表</param>
/// <param name="referenceZ">参考Z坐标起点/终点为目标Z中间点为前一点Z</param>
/// <param name="vehicleHeight">车辆高度</param>
/// <param name="startZ">路径起点Z</param>
/// <param name="endZ">路径终点Z</param>
/// <param name="currentIndex">当前点索引</param>
/// <param name="totalPoints">总点数</param>
/// <param name="exactMatch">是否精确匹配(起点/终点使用)</param>
/// <returns>选中的高度层如果没有合适的返回null</returns>
private HeightLayer? SelectBestLayer(List<HeightLayer> 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;
}
/// <summary>
/// 将A*返回的米坐标转换为网格坐标(统一方法)
/// </summary>
@ -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)

View File

@ -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);
}
}
}

View File

@ -28,7 +28,7 @@ namespace NavisworksTransport.PathPlanning
WorldPosition = worldPosition,
RelatedModelItem = channel,
SpeedLimit = speedLimit,
PassableHeight = new HeightInterval(),
HeightLayers = new System.Collections.Generic.List<HeightLayer>(),
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<HeightLayer>(),
Cost = cost
};
}
@ -74,7 +74,7 @@ namespace NavisworksTransport.PathPlanning
WorldPosition = worldPosition,
RelatedModelItem = obstacle,
SpeedLimit = 0,
PassableHeight = new HeightInterval(),
HeightLayers = new System.Collections.Generic.List<HeightLayer>(),
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<HeightLayer>(),
Cost = cost
};
}
@ -122,7 +122,7 @@ namespace NavisworksTransport.PathPlanning
WorldPosition = worldPosition,
RelatedModelItem = stairs,
SpeedLimit = speedLimit,
PassableHeight = new HeightInterval(),
HeightLayers = new System.Collections.Generic.List<HeightLayer>(),
Cost = cost
};
}
@ -143,7 +143,7 @@ namespace NavisworksTransport.PathPlanning
WorldPosition = worldPosition,
RelatedModelItem = null,
SpeedLimit = 0,
PassableHeight = new HeightInterval(),
HeightLayers = new System.Collections.Generic.List<HeightLayer>(),
Cost = double.MaxValue
};
}

View File

@ -163,7 +163,7 @@ namespace NavisworksTransport.PathPlanning
IsWalkable = false,
CellType = CategoryAttributeManager.LogisticsElementType.Unknown, // 修改:默认为未知/空洞类型
IsInChannel = false,
PassableHeight = new HeightInterval(),
HeightLayers = new List<HeightLayer>(),
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<HeightLayer>(),
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;
}
/// <summary>
/// 为指定网格坐标添加高度层(追加模式,不覆盖)
/// </summary>
/// <param name="gridPosition">网格坐标</param>
/// <param name="layer">要添加的高度层</param>
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<HeightLayer>();
}
// 添加新的高度层
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;
}
/// <summary>
/// 查找包含指定Z坐标的高度层
/// </summary>
/// <param name="gridPosition">网格坐标</param>
/// <param name="z">Z坐标</param>
/// <param name="tolerance">容差默认0.1m</param>
/// <returns>符合条件的高度层如果没有则返回null</returns>
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;
}
/// <summary>
/// 获取指定网格位置的所有高度层
/// </summary>
/// <param name="gridPosition">网格坐标</param>
/// <returns>高度层列表,如果没有则返回空列表</returns>
public List<HeightLayer> GetHeightLayers(GridPoint2D gridPosition)
{
if (!IsValidGridPosition(gridPosition))
return new List<HeightLayer>();
var cell = Cells[gridPosition.X, gridPosition.Y];
return cell.HeightLayers ?? new List<HeightLayer>();
}
/// <summary>
/// 应用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;
}
/// <summary>
@ -594,9 +684,9 @@ namespace NavisworksTransport.PathPlanning
public bool IsInChannel { get; set; }
/// <summary>
/// 可通行高度区间列表 - 支持2.5D路径规划
/// 多高度层列表 - 支持同一网格坐标多个高度层(如楼梯下方通道)
/// </summary>
public HeightInterval PassableHeight { get; set; }
public List<HeightLayer> HeightLayers { get; set; }
/// <summary>
/// 通道类型 - 用于区分不同类型的通道
@ -661,7 +751,7 @@ namespace NavisworksTransport.PathPlanning
IsWalkable = false,
CellType = CategoryAttributeManager.LogisticsElementType.,
IsInChannel = false,
PassableHeight = new HeightInterval(),
HeightLayers = new List<HeightLayer>(),
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<HeightLayer>(),
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<HeightLayer>(),
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<HeightLayer>(),
ChannelType = channelType,
SpeedLimit = 0
};
@ -729,6 +819,49 @@ namespace NavisworksTransport.PathPlanning
}
}
/// <summary>
/// 高度层结构体
/// 用于支持多高度层的网格单元格
/// </summary>
public struct HeightLayer
{
/// <summary>
/// 该层的Z坐标
/// </summary>
public double Z { get; set; }
/// <summary>
/// 可通行高度范围(用于车辆检查)
/// </summary>
public HeightInterval PassableHeight { get; set; }
/// <summary>
/// 来源模型项
/// </summary>
public ModelItem SourceItem { get; set; }
/// <summary>
/// 限速(米/秒0表示未设置限速
/// </summary>
public double SpeedLimit { get; set; }
/// <summary>
/// 层类型
/// </summary>
public CategoryAttributeManager.LogisticsElementType Type { get; set; }
/// <summary>
/// 构造函数
/// </summary>
public HeightLayer(double z, HeightInterval passableHeight, ModelItem sourceItem, double speedLimit, CategoryAttributeManager.LogisticsElementType type)
{
Z = z;
PassableHeight = passableHeight;
SourceItem = sourceItem;
SpeedLimit = speedLimit;
Type = type;
}
}
/// <summary>
/// 高度区间结构体 - 用于2.5D路径规划

View File

@ -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} 个高度层");
}
/// <summary>
@ -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++;