diff --git a/src/Core/PathPlanningManager.cs b/src/Core/PathPlanningManager.cs index 9f42b3e..0140374 100644 --- a/src/Core/PathPlanningManager.cs +++ b/src/Core/PathPlanningManager.cs @@ -3453,20 +3453,21 @@ namespace NavisworksTransport double layerZ = layer.Z; double passableHeight = layer.PassableHeight.GetSpan(); - // 判断高度是否足够车辆通行 - bool isHeightSufficient = passableHeight >= vehicleHeightInModelUnits; + // 判断该层是否可通行(考虑膨胀影响和高度限制) + bool isLayerWalkable = layer.IsWalkable; // 膨胀影响 + bool isHeightSufficient = passableHeight >= vehicleHeightInModelUnits; // 高度限制 - // 调试:输出高度不足的层 - if (!isHeightSufficient && multiLayerCells < 5) // 只输出前5个网格的详情 + // 调试:输出不可通行的层 + if ((!isLayerWalkable || !isHeightSufficient) && multiLayerCells < 5) // 只输出前5个网格的详情 { - LogManager.Debug($"[高度判断] 网格({x},{y}) Layer Z={layerZ:F2}, PassableH={passableHeight:F2}, 车辆H={vehicleHeightInModelUnits:F2}, 高度足够={isHeightSufficient}"); + LogManager.Debug($"[层通行性] 网格({x},{y}) Layer Z={layerZ:F2}, IsWalkable={isLayerWalkable}, PassableH={passableHeight:F2}, 车辆H={vehicleHeightInModelUnits:F2}, 高度足够={isHeightSufficient}"); } PathRoute targetRoute = null; string gridTypeName = ""; - // 根据高度是否足够和网格类型决定渲染方式 - if (isHeightSufficient) + // 根据层可通行性、高度是否足够和网格类型决定渲染方式 + if (isLayerWalkable && isHeightSufficient) { // 高度足够 - 按原类型渲染 if (cell.CellType == CategoryAttributeManager.LogisticsElementType.通道) @@ -3499,11 +3500,25 @@ namespace NavisworksTransport } else { - // 高度不足 - 渲染为障碍物(灰色) + // 层不可通行(被膨胀影响或高度不足)- 渲染为障碍物(灰色) if (_showObstacleGrid) { targetRoute = obstacleRoute; - gridTypeName = "障碍_高度不足"; // 必须包含"障碍"关键词才能渲染为灰色 + + // 区分不可通行的原因 + if (!isLayerWalkable && !isHeightSufficient) + { + gridTypeName = "障碍_膨胀+高度不足"; + } + else if (!isLayerWalkable) + { + gridTypeName = "障碍_膨胀影响"; + } + else + { + gridTypeName = "障碍_高度不足"; + } + heightInsufficientLayers++; } } diff --git a/src/PathPlanning/AutoPathFinder.cs b/src/PathPlanning/AutoPathFinder.cs index 2a70a1d..50dcfe8 100644 --- a/src/PathPlanning/AutoPathFinder.cs +++ b/src/PathPlanning/AutoPathFinder.cs @@ -168,6 +168,10 @@ namespace NavisworksTransport.PathPlanning { var layer = cell.HeightLayers[li]; + // 检查该层是否可通行(膨胀影响) + if (!layer.IsWalkable) + continue; + // 楼梯/电梯区域跳过层0(下方楼板,不是可行走表面) if (li == 0 && (layer.Type == CategoryAttributeManager.LogisticsElementType.楼梯 || layer.Type == CategoryAttributeManager.LogisticsElementType.电梯)) diff --git a/src/PathPlanning/GridMap.cs b/src/PathPlanning/GridMap.cs index 6c8cc23..eaab45a 100644 --- a/src/PathPlanning/GridMap.cs +++ b/src/PathPlanning/GridMap.cs @@ -851,6 +851,19 @@ namespace NavisworksTransport.PathPlanning /// public CategoryAttributeManager.LogisticsElementType Type { get; set; } + /// + /// 是否是边界层(用于膨胀计算) + /// 边界定义:当前网格层数 > 邻居网格层数,且层间高度差超过阈值 + /// + public bool IsBoundary { get; set; } + + /// + /// 该层是否可通行(用于多层膨胀) + /// true: 该层可以通行 + /// false: 该层被膨胀影响,不可通行 + /// + public bool IsWalkable { get; set; } + /// /// 构造函数 /// @@ -861,6 +874,8 @@ namespace NavisworksTransport.PathPlanning SourceItem = sourceItem; SpeedLimit = speedLimit; Type = type; + IsBoundary = false; // 默认不是边界 + IsWalkable = true; // 默认可通行,通过膨胀算法修改 } } diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index ab0c8f3..fe951e5 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -29,6 +29,13 @@ namespace NavisworksTransport.PathPlanning /// public class GridMapGenerator { + /// + /// 边界检测的最大高度差(米) + /// 与AutoPathFinder中的MAX_HEIGHT_DIFF_METERS保持一致 + /// 用于判断层间是否构成边界(超过此阈值则标记为边界) + /// + private const double MAX_HEIGHT_DIFF_METERS = 0.35; + private readonly CategoryAttributeManager _categoryManager; private readonly ChannelBasedGridBuilder _channelBuilder; @@ -145,6 +152,13 @@ namespace NavisworksTransport.PathPlanning ProcessDoorElements(channelCoverage.GridMap, allTraversableItems); LogManager.Info($"【阶段2.6完成】门元素处理后网格统计: {channelCoverage.GridMap.GetStatistics()}"); + // 2.7. 标记边界层(用于膨胀计算) + LogManager.Info("【生成网格地图】步骤2.7: 标记边界层"); + double maxHeightDiffInModelUnits = MAX_HEIGHT_DIFF_METERS * metersToModelUnitsConversionFactor; + LogManager.Info($"【生成网格地图】边界高度差阈值: {MAX_HEIGHT_DIFF_METERS}米 = {maxHeightDiffInModelUnits:F2}模型单位"); + MarkBoundaryLayers(channelCoverage.GridMap, maxHeightDiffInModelUnits); + LogManager.Info($"【阶段2.7完成】边界层标记后网格统计: {channelCoverage.GridMap.GetStatistics()}"); + // 3. 应用车辆尺寸膨胀(如果需要) if (vehicleRadius > 0 || safetyMargin > 0) { @@ -410,6 +424,111 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"【高度约束设置】其中楼梯/电梯下方层: {stairBottomLayersProcessed} 个(使用实际可通行高度)"); } + /// + /// 标记边界层(用于膨胀计算) + /// 边界定义:当前网格层数 > 邻居网格层数(梯度≥1),且层间高度差超过阈值 + /// + /// 网格地图 + /// 最大允许高度差(模型单位) + private void MarkBoundaryLayers(GridMap gridMap, double maxHeightDiffInModelUnits) + { + LogManager.Info($"【边界检测】开始标记边界层,高度差阈值: {maxHeightDiffInModelUnits:F2}模型单位"); + + int totalBoundaryLayers = 0; + int totalCheckedCells = 0; + + // 4个方向:上下左右 + int[] dx = { 0, 0, -1, 1 }; + int[] dy = { -1, 1, 0, 0 }; + string[] dirNames = { "下", "上", "左", "右" }; + + 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 == null || cell.HeightLayers.Count == 0) + continue; + + totalCheckedCells++; + int currentLayerCount = cell.HeightLayers.Count; + + // 检查4个方向的邻居 + for (int dir = 0; dir < 4; dir++) + { + int nx = x + dx[dir]; + int ny = y + dy[dir]; + + // 边界检查 + if (nx < 0 || nx >= gridMap.Width || ny < 0 || ny >= gridMap.Height) + continue; + + var neighbor = gridMap.Cells[nx, ny]; + int neighborLayerCount = (neighbor.HeightLayers != null) ? neighbor.HeightLayers.Count : 0; + + // 层数梯度检测:当前层数 > 邻居层数 + if (currentLayerCount <= neighborLayerCount) + continue; + + int gradient = currentLayerCount - neighborLayerCount; + + // 检查每个梯度层是否构成边界 + // 例如:当前2层,邻居1层,梯度=1,检查当前Layer[1]是否是边界 + // 例如:当前3层,邻居1层,梯度=2,检查Layer[1]和Layer[2] + for (int layerOffset = 1; layerOffset <= gradient; layerOffset++) + { + int currentLayerIndex = neighborLayerCount + layerOffset - 1; + + if (currentLayerIndex >= currentLayerCount) + continue; + + var currentLayer = cell.HeightLayers[currentLayerIndex]; + + // 获取对比层的Z值 + double currentZ = currentLayer.Z; + double neighborZ = 0; + + if (neighborLayerCount > 0) + { + // 邻居有层:取邻居最高层的Z值 + neighborZ = neighbor.HeightLayers[neighborLayerCount - 1].Z; + } + else + { + // 邻居无层(Unknown):使用网格底部Z值 + neighborZ = gridMap.Bounds.Min.Z; + } + + // 计算层间高度差 + double heightDiff = Math.Abs(currentZ - neighborZ); + + // 高度差判定 + if (heightDiff > maxHeightDiffInModelUnits) + { + // 高度差超过阈值,标记为边界 + if (!currentLayer.IsBoundary) + { + currentLayer.IsBoundary = true; + cell.HeightLayers[currentLayerIndex] = currentLayer; + totalBoundaryLayers++; + + LogManager.Debug($"【边界检测】网格({x},{y}) Layer{currentLayerIndex} 标记为边界," + + $"方向={dirNames[dir]}, 当前Z={currentZ:F2}, 邻居Z={neighborZ:F2}, " + + $"高度差={heightDiff:F2}, 梯度={gradient}"); + } + } + } + } + + gridMap.Cells[x, y] = cell; + } + } + + LogManager.Info($"【边界检测】完成,检查了 {totalCheckedCells} 个网格,标记了 {totalBoundaryLayers} 个边界层"); + } + /// /// 从物流元素集合中过滤出门类型的元素 /// @@ -699,13 +818,222 @@ namespace NavisworksTransport.PathPlanning } /// - /// 优化的车辆膨胀算法 - 使用正确的8连通距离变换方法 - /// 时间复杂度: O(n*m) 而不是 O(n*m*r^2) + /// 多层膨胀算法 - 按高度层分别处理膨胀 + /// 核心原则:边界层只影响同层索引的邻居层 /// private void ApplyVehicleInflationOptimized(GridMap gridMap, int inflationRadius) { var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + LogManager.Info($"【多层膨胀】开始按层处理膨胀,网格大小: {gridMap.Width}x{gridMap.Height}, 膨胀半径: {inflationRadius}"); + + // 统计每个层索引的处理情况 + var layerStats = new Dictionary(); + int maxLayerIndex = 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.HeightLayers != null && cell.HeightLayers.Count > 0) + { + maxLayerIndex = Math.Max(maxLayerIndex, cell.HeightLayers.Count - 1); + } + } + } + + LogManager.Info($"【多层膨胀】检测到最大层索引: {maxLayerIndex}"); + + // 第二步:对每个层索引分别处理膨胀 + for (int layerIndex = 0; layerIndex <= maxLayerIndex; layerIndex++) + { + int boundaryCount = 0; + int inflatedCount = 0; + + // 为这一层创建距离图 + var distanceMap = new double[gridMap.Width, gridMap.Height]; + const double SQRT2 = 1.414213562373095; + + // 初始化:标记障碍物、Unknown和边界层 + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + + // 默认为无穷大 + distanceMap[x, y] = double.MaxValue; + + // 1. 整个网格是障碍物 → 距离为0 + if (!cell.IsWalkable) + { + distanceMap[x, y] = 0.0; + continue; + } + + // 2. Unknown网格 → 距离为0 + if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown) + { + distanceMap[x, y] = 0.0; + continue; + } + + // 3. 该层索引存在且是边界 → 距离为0 + if (cell.HeightLayers != null && layerIndex < cell.HeightLayers.Count) + { + var layer = cell.HeightLayers[layerIndex]; + if (layer.IsBoundary) + { + distanceMap[x, y] = 0.0; + boundaryCount++; + } + } + } + } + + if (boundaryCount == 0) + { + LogManager.Debug($"【多层膨胀】Layer{layerIndex} 无边界,跳过"); + continue; + } + + LogManager.Info($"【多层膨胀】Layer{layerIndex} 开始膨胀,边界数量: {boundaryCount}"); + + // 距离变换 - 正向扫描 + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + if (distanceMap[x, y] == double.MaxValue) + { + double minDist = double.MaxValue; + + if (x > 0) + minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1.0); + if (y > 0) + minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1.0); + if (x > 0 && y > 0) + minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + SQRT2); + if (x > 0 && y < gridMap.Height - 1) + minDist = Math.Min(minDist, distanceMap[x - 1, y + 1] + SQRT2); + + if (minDist != double.MaxValue) + distanceMap[x, y] = minDist; + } + } + } + + // 距离变换 - 反向扫描 + for (int x = gridMap.Width - 1; x >= 0; x--) + { + for (int y = gridMap.Height - 1; y >= 0; y--) + { + if (distanceMap[x, y] != 0.0) + { + double minDist = distanceMap[x, y]; + + if (x < gridMap.Width - 1) + minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1.0); + if (y < gridMap.Height - 1) + minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1.0); + if (x < gridMap.Width - 1 && y < gridMap.Height - 1) + minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + SQRT2); + if (x < gridMap.Width - 1 && y > 0) + minDist = Math.Min(minDist, distanceMap[x + 1, y - 1] + SQRT2); + + distanceMap[x, y] = minDist; + } + } + } + + // 应用膨胀结果:设置该层的IsWalkable + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + + // 检查是否需要膨胀这一层 + if (cell.HeightLayers != null && layerIndex < cell.HeightLayers.Count) + { + var layer = cell.HeightLayers[layerIndex]; + + // 门保护:不膨胀门类型的层 + if (layer.Type == CategoryAttributeManager.LogisticsElementType.门) + continue; + + // 膨胀条件:距离 < 膨胀半径 + // 边界网格距离=0,算在膨胀半径内 + // 例如:半径=1 → 只膨胀边界本身(距离0) + // 半径=2 → 膨胀边界+向内1格(距离0,1) + // 半径=3 → 膨胀边界+向内2格(距离0,1,2) + bool shouldInflate = false; + + if (distanceMap[x, y] < inflationRadius) + { + shouldInflate = true; + } + + if (shouldInflate) + { + // 设置该层为不可通行 + layer.IsWalkable = false; + cell.HeightLayers[layerIndex] = layer; + inflatedCount++; + } + } + + gridMap.Cells[x, y] = cell; + } + } + + layerStats[layerIndex] = (boundaryCount, inflatedCount); + LogManager.Info($"【多层膨胀】Layer{layerIndex} 完成,边界: {boundaryCount}, 膨胀: {inflatedCount}"); + } + + // 第三步:更新GridCell.IsWalkable(至少有一层可通行) + int cellsBecameUnwalkable = 0; + for (int x = 0; x < gridMap.Width; x++) + { + for (int y = 0; y < gridMap.Height; y++) + { + var cell = gridMap.Cells[x, y]; + bool wasWalkable = cell.IsWalkable; + + // 检查是否至少有一层可通行 + bool hasWalkableLayer = false; + if (cell.HeightLayers != null && cell.HeightLayers.Count > 0) + { + hasWalkableLayer = cell.HeightLayers.Any(layer => layer.IsWalkable); + } + + cell.IsWalkable = hasWalkableLayer; + gridMap.Cells[x, y] = cell; + + if (wasWalkable && !hasWalkableLayer) + cellsBecameUnwalkable++; + } + } + + stopwatch.Stop(); + LogManager.Info($"【多层膨胀】完成,耗时: {stopwatch.ElapsedMilliseconds}ms"); + LogManager.Info($"【多层膨胀】网格变为不可通行: {cellsBecameUnwalkable}个"); + + foreach (var kvp in layerStats.OrderBy(k => k.Key)) + { + LogManager.Info($"【多层膨胀】Layer{kvp.Key}: 边界{kvp.Value.boundary}个, 膨胀{kvp.Value.inflated}层"); + } + } + + /// + /// 旧的2D膨胀算法(已废弃,保留用于参考) + /// + private void ApplyVehicleInflationOptimized_Old2D(GridMap gridMap, int inflationRadius) + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + LogManager.Info($"【8连通膨胀】使用正确的8连通距离变换算法,网格大小: {gridMap.Width}x{gridMap.Height}"); // 使用浮点数距离矩阵支持√2对角线距离 @@ -715,6 +1043,7 @@ namespace NavisworksTransport.PathPlanning // 初始化距离矩阵 int obstacleCount = 0; int unknownCount = 0; + int boundaryCount = 0; for (int x = 0; x < gridMap.Width; x++) { @@ -722,7 +1051,7 @@ namespace NavisworksTransport.PathPlanning { var cell = gridMap.Cells[x, y]; - // 障碍物和Unknown网格作为膨胀源点 + // 膨胀源点:1.障碍物 2.Unknown网格 3.边界层网格 if (!cell.IsWalkable) { distanceMap[x, y] = 0.0; @@ -733,6 +1062,12 @@ namespace NavisworksTransport.PathPlanning distanceMap[x, y] = 0.0; unknownCount++; } + else if (cell.HeightLayers != null && cell.HeightLayers.Any(layer => layer.IsBoundary)) + { + // 包含边界层的网格作为膨胀源点 + distanceMap[x, y] = 0.0; + boundaryCount++; + } else { distanceMap[x, y] = double.MaxValue; @@ -740,7 +1075,7 @@ namespace NavisworksTransport.PathPlanning } } - LogManager.Info($"【8连通膨胀】膨胀源点统计 - 障碍物: {obstacleCount}, Unknown: {unknownCount}"); + LogManager.Info($"【8连通膨胀】膨胀源点统计 - 障碍物: {obstacleCount}, Unknown: {unknownCount}, 边界: {boundaryCount}"); // 使用标准的距离变换算法 - 正向扫描 for (int x = 0; x < gridMap.Width; x++)