From df1885a35241a1250e07cf240a2e2d33c5bb08b7 Mon Sep 17 00:00:00 2001 From: tian <11429339@qq.com> Date: Fri, 10 Oct 2025 23:51:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E4=BA=86=E8=86=A8=E8=83=80?= =?UTF-8?q?=E7=AE=97=E6=B3=95=EF=BC=8C=E6=8A=8A=E9=9A=9C=E7=A2=8D=E7=89=A9?= =?UTF-8?q?=E5=92=8C=E8=BE=B9=E7=95=8C=E5=88=86=E5=BC=80=E8=AE=A1=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/PathPlanning/GridMapGenerator.cs | 392 ++++++++++++++++----------- 1 file changed, 240 insertions(+), 152 deletions(-) diff --git a/src/PathPlanning/GridMapGenerator.cs b/src/PathPlanning/GridMapGenerator.cs index 7a32550..7a92cff 100644 --- a/src/PathPlanning/GridMapGenerator.cs +++ b/src/PathPlanning/GridMapGenerator.cs @@ -846,8 +846,8 @@ namespace NavisworksTransport.PathPlanning } /// - /// 多层膨胀算法 - 按高度层分别处理膨胀 - /// 核心原则:边界层只影响同层索引的邻居层 + /// 多层膨胀算法 - 分两次处理:障碍物膨胀 + 边界膨胀 + /// 核心原则:障碍物从外围膨胀(不含本身),边界从边界本身膨胀(包含边界) /// private void ApplyVehicleInflationOptimized(GridMap gridMap, int inflationRadius) { @@ -855,8 +855,12 @@ namespace NavisworksTransport.PathPlanning LogManager.Info($"【多层膨胀】开始按层处理膨胀,网格大小: {gridMap.Width}x{gridMap.Height}, 膨胀半径: {inflationRadius}"); + // 计算高度差阈值(用于楼梯口保护) + double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units); + double maxHeightDiffInModelUnits = MAX_HEIGHT_DIFF_METERS * metersToModelUnitsConversionFactor; + // 统计每个层索引的处理情况 - var layerStats = new Dictionary(); + var layerStats = new Dictionary(); int maxLayerIndex = 0; // 第一步:找出最大层索引 @@ -877,169 +881,253 @@ namespace NavisworksTransport.PathPlanning // 第二步:对每个层索引分别处理膨胀 for (int layerIndex = 0; layerIndex <= maxLayerIndex; layerIndex++) { - int boundaryCount = 0; - int inflatedCount = 0; + // 步骤1:障碍物膨胀(不包括障碍物本身) + int obstacleInflated = InflateObstacles(gridMap, layerIndex, inflationRadius); - // 为这一层创建距离图 - var distanceMap = new double[gridMap.Width, gridMap.Height]; - const double SQRT2 = 1.414213562373095; + // 步骤2:边界膨胀(包括边界本身) + var (boundaryCount, boundaryInflated) = InflateBoundaries(gridMap, layerIndex, inflationRadius, maxHeightDiffInModelUnits); - // 初始化:标记障碍物、Unknown和边界层 - for (int x = 0; x < gridMap.Width; x++) + if (boundaryCount > 0 || obstacleInflated > 0) { - for (int y = 0; y < gridMap.Height; y++) - { - var cell = gridMap.Cells[x, y]; - - // 默认为无穷大 - distanceMap[x, y] = double.MaxValue; - - // 1. Unknown网格 → 距离为0(整个网格无法通行) - if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown) - { - distanceMap[x, y] = 0.0; - continue; - } - - // 2. 检查该层索引是否存在 - if (cell.HeightLayers != null && layerIndex < cell.HeightLayers.Count) - { - var layer = cell.HeightLayers[layerIndex]; - - // 2a. 该层不可通行 → 距离为0(障碍物层) - if (!layer.IsWalkable) - { - distanceMap[x, y] = 0.0; - continue; - } - - // 2b. 该层是边界 → 距离为0 - if (layer.IsBoundary) - { - distanceMap[x, y] = 0.0; - boundaryCount++; - } - } - else - { - // 3. 该层索引不存在(网格没有这一层)→ 设置为-1,不参与距离变换 - distanceMap[x, y] = -1.0; - } - } + layerStats[layerIndex] = (obstacleInflated, boundaryCount, boundaryInflated); + LogManager.Info($"【多层膨胀】Layer{layerIndex} 完成 - 障碍物膨胀: {obstacleInflated}层, 边界: {boundaryCount}个, 边界膨胀: {boundaryInflated}层"); } - - 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; - - // 从邻居计算距离,跳过 < 0 的邻居(没有该层的网格) - if (x > 0 && distanceMap[x - 1, y] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1.0); - if (y > 0 && distanceMap[x, y - 1] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1.0); - if (x > 0 && y > 0 && distanceMap[x - 1, y - 1] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + SQRT2); - if (x > 0 && y < gridMap.Height - 1 && distanceMap[x - 1, y + 1] >= 0.0) - 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) // 排除边界(0)和没有该层(-1) - { - double minDist = distanceMap[x, y]; - - // 从邻居计算距离,跳过 < 0 的邻居(没有该层的网格) - if (x < gridMap.Width - 1 && distanceMap[x + 1, y] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1.0); - if (y < gridMap.Height - 1 && distanceMap[x, y + 1] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1.0); - if (x < gridMap.Width - 1 && y < gridMap.Height - 1 && distanceMap[x + 1, y + 1] >= 0.0) - minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + SQRT2); - if (x < gridMap.Width - 1 && y > 0 && distanceMap[x + 1, y - 1] >= 0.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(包括边界)且距离<半径 - // 例如:半径=2 → 膨胀距离0、1的网格(总共2格) - // 半径=3 → 膨胀距离0、1、2的网格(总共3格) - bool shouldInflate = false; - - if (distanceMap[x, y] >= 0.0 && 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已移除,通行性由HeightLayer.IsWalkable决定 - // 不再需要更新网格级别的IsWalkable - stopwatch.Stop(); LogManager.Info($"【多层膨胀】完成,耗时: {stopwatch.ElapsedMilliseconds}ms"); foreach (var kvp in layerStats.OrderBy(k => k.Key)) { - LogManager.Info($"【多层膨胀】Layer{kvp.Key}: 边界{kvp.Value.boundary}个, 膨胀{kvp.Value.inflated}层"); + LogManager.Info($"【多层膨胀】Layer{kvp.Key}: 障碍物膨胀{kvp.Value.obstacleInflated}层, 边界{kvp.Value.boundaryCount}个, 边界膨胀{kvp.Value.boundaryInflated}层"); } } + /// + /// 距离变换算法 - 8连通距离计算 + /// + private void DistanceTransform(double[,] distanceMap, int width, int height) + { + const double SQRT2 = 1.414213562373095; + + // 正向扫描(从左上到右下) + for (int x = 0; x < width; x++) + { + for (int y = 0; y < height; y++) + { + if (distanceMap[x, y] == double.MaxValue) + { + double minDist = double.MaxValue; + + // 从邻居计算距离,跳过 < 0 的邻居(没有该层的网格) + if (x > 0 && distanceMap[x - 1, y] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1.0); + if (y > 0 && distanceMap[x, y - 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1.0); + if (x > 0 && y > 0 && distanceMap[x - 1, y - 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + SQRT2); + if (x > 0 && y < height - 1 && distanceMap[x - 1, y + 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x - 1, y + 1] + SQRT2); + + if (minDist != double.MaxValue) + distanceMap[x, y] = minDist; + } + } + } + + // 反向扫描(从右下到左上) + for (int x = width - 1; x >= 0; x--) + { + for (int y = height - 1; y >= 0; y--) + { + if (distanceMap[x, y] > 0.0) // 排除distance=0和没有该层(-1) + { + double minDist = distanceMap[x, y]; + + // 从邻居计算距离,跳过 < 0 的邻居(没有该层的网格) + if (x < width - 1 && distanceMap[x + 1, y] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1.0); + if (y < height - 1 && distanceMap[x, y + 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1.0); + if (x < width - 1 && y < height - 1 && distanceMap[x + 1, y + 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + SQRT2); + if (x < width - 1 && y > 0 && distanceMap[x + 1, y - 1] >= 0.0) + minDist = Math.Min(minDist, distanceMap[x + 1, y - 1] + SQRT2); + + distanceMap[x, y] = minDist; + } + } + } + } + + /// + /// 障碍物膨胀 - 从障碍物外围开始膨胀,不包括障碍物本身 + /// + private int InflateObstacles(GridMap gridMap, int layerIndex, int inflationRadius) + { + int inflatedCount = 0; + var distanceMap = new double[gridMap.Width, gridMap.Height]; + + // 初始化:只标记障碍物 + 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; + + // Unknown网格 → distance=0(整个网格无法通行) + if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown) + { + distanceMap[x, y] = 0.0; + continue; + } + + // 检查该层索引是否存在 + if (cell.HeightLayers != null && layerIndex < cell.HeightLayers.Count) + { + var layer = cell.HeightLayers[layerIndex]; + + // 该层不可通行 → distance=0(障碍物层) + if (!layer.IsWalkable) + { + distanceMap[x, y] = 0.0; + } + } + else + { + // 该层索引不存在 → distance=-1,不参与距离变换 + distanceMap[x, y] = -1.0; + } + } + } + + // 距离变换 + DistanceTransform(distanceMap, gridMap.Width, gridMap.Height); + + // 应用膨胀:distance > 0 && distance <= inflationRadius(不包括障碍物本身) + 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; + + // 障碍物膨胀条件:不包括障碍物本身(distance=0) + // 例如:半径=2 → 膨胀distance=1,2(障碍物外围2格) + if (distanceMap[x, y] > 0.0 && distanceMap[x, y] <= inflationRadius) + { + layer.IsWalkable = false; + cell.HeightLayers[layerIndex] = layer; + inflatedCount++; + } + } + + gridMap.Cells[x, y] = cell; + } + } + + return inflatedCount; + } + + /// + /// 边界膨胀 - 从边界网格本身开始膨胀,包括边界网格 + /// + private (int boundaryCount, int inflatedCount) InflateBoundaries(GridMap gridMap, int layerIndex, int inflationRadius, double maxHeightDiffInModelUnits) + { + int boundaryCount = 0; + int inflatedCount = 0; + var distanceMap = new double[gridMap.Width, gridMap.Height]; + + // 初始化:只标记边界 + 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; + + // 检查该层索引是否存在 + if (cell.HeightLayers != null && layerIndex < cell.HeightLayers.Count) + { + var layer = cell.HeightLayers[layerIndex]; + + // 该层是边界 → distance=0 + if (layer.IsBoundary) + { + distanceMap[x, y] = 0.0; + boundaryCount++; + } + } + else + { + // 该层索引不存在 → distance=-1,不参与距离变换 + distanceMap[x, y] = -1.0; + } + } + } + + if (boundaryCount == 0) + { + return (0, 0); // 无边界,跳过 + } + + // 距离变换 + DistanceTransform(distanceMap, gridMap.Width, gridMap.Height); + + // 应用膨胀:distance >= 0 && distance < inflationRadius(包括边界本身) + 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; + + // 楼梯口保护:Layer[0]且存在Layer[1]且高度差小于阈值 + if (layerIndex == 0 && cell.HeightLayers.Count > 1) + { + var layer0 = cell.HeightLayers[0]; + var layer1 = cell.HeightLayers[1]; + double heightDiff = Math.Abs(layer1.Z - layer0.Z); + + if (heightDiff <= maxHeightDiffInModelUnits) + { + // 这是楼梯口:Layer[0]不膨胀,保持通行以便进入楼梯 + continue; + } + } + + // 边界膨胀条件:包括边界本身(distance=0) + // 例如:半径=2 → 膨胀distance=0,1(边界+向外1格) + if (distanceMap[x, y] >= 0.0 && distanceMap[x, y] < inflationRadius) + { + layer.IsWalkable = false; + cell.HeightLayers[layerIndex] = layer; + inflatedCount++; + } + } + + gridMap.Cells[x, y] = cell; + } + } + + return (boundaryCount, inflatedCount); + } + /// /// 旧的2D膨胀算法(已废弃,保留用于参考) ///