1844 lines
91 KiB
C#
1844 lines
91 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using System.Numerics;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisworksTransport.Utils;
|
||
using NavisworksTransport.Core.Config;
|
||
using NavisworksTransport.Utils.CoordinateSystem;
|
||
|
||
namespace NavisworksTransport.PathPlanning
|
||
{
|
||
/// <summary>
|
||
/// 网格生成模式
|
||
/// </summary>
|
||
public enum GridGenerationMode
|
||
{
|
||
/// <summary>
|
||
/// 通道基础2.5D模式 - 使用通道优先策略和垂直扫描的高性能模式
|
||
/// </summary>
|
||
ChannelBased2_5D,
|
||
|
||
/// <summary>
|
||
/// 边界框基础2.5D模式 - 使用直接包围盒遍历的优化算法
|
||
/// </summary>
|
||
BoundingBoxBased2_5D
|
||
}
|
||
|
||
/// <summary>
|
||
/// 网格地图生成器
|
||
/// 负责将BIM模型转换为路径规划可用的网格地图
|
||
/// 支持通道基础2.5D模式
|
||
/// </summary>
|
||
public class GridMapGenerator
|
||
{
|
||
/// <summary>
|
||
/// 获取边界检测的最大高度差(米)
|
||
/// 从配置文件读取,与AutoPathFinder保持一致
|
||
/// 用于判断层间是否构成边界(超过此阈值则标记为边界)
|
||
/// </summary>
|
||
private double GetMaxHeightDiffMeters()
|
||
{
|
||
return ConfigManager.Instance.Current.PathEditing.MaxHeightDiffMeters;
|
||
}
|
||
|
||
private readonly CategoryAttributeManager _categoryManager;
|
||
private readonly ChannelBasedGridBuilder _channelBuilder;
|
||
|
||
/// <summary>
|
||
/// 构造函数
|
||
/// </summary>
|
||
public GridMapGenerator()
|
||
{
|
||
_categoryManager = new CategoryAttributeManager();
|
||
_channelBuilder = new ChannelBasedGridBuilder();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 基于包围盒遍历的网格地图生成方法(优化版)
|
||
/// </summary>
|
||
/// <param name="document">Navisworks文档</param>
|
||
/// <param name="bounds">扫描边界</param>
|
||
/// <param name="cellSize">网格单元大小(米)</param>
|
||
/// <param name="objectRadius">物体半径(米)</param>
|
||
/// <param name="safetyMargin">安全间隙(米)</param>
|
||
/// <param name="planningStartPoint">规划起点</param>
|
||
/// <param name="planningEndPoint">规划终点</param>
|
||
/// <param name="objectHeight">物体高度(米)</param>
|
||
/// <returns>生成的网格地图</returns>
|
||
public GridMap GenerateFromBIM(BoundingBox3D bounds, double cellSize, double objectRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double objectHeight)
|
||
{
|
||
try
|
||
{
|
||
// 第一步:统一转换所有米制参数为模型单位
|
||
double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
|
||
double cellSizeInModelUnits = cellSize * metersToModelUnitsConversionFactor;
|
||
double objectRadiusInModelUnits = objectRadius * metersToModelUnitsConversionFactor;
|
||
double safetyMarginInModelUnits = safetyMargin * metersToModelUnitsConversionFactor;
|
||
double objectHeightInModelUnits = objectHeight * metersToModelUnitsConversionFactor;
|
||
double scanHeightInModelUnits = objectHeightInModelUnits + safetyMarginInModelUnits; // 扫描高度 = 物体高度 + 安全间隙
|
||
double totalInflationRadiusInModelUnits = objectRadiusInModelUnits + safetyMarginInModelUnits; // 膨胀半径 = 物体半径 + 安全间隙
|
||
|
||
LogManager.Info($"【生成网格地图】参数单位转换完成 (转换系数: {metersToModelUnitsConversionFactor:F2}):");
|
||
LogManager.Info($" 网格大小: {cellSize}米 → {cellSizeInModelUnits:F2}模型单位");
|
||
LogManager.Info($" 物体半径: {objectRadius}米 → {objectRadiusInModelUnits:F2}模型单位");
|
||
LogManager.Info($" 安全间隙: {safetyMargin}米 → {safetyMarginInModelUnits:F2}模型单位");
|
||
LogManager.Info($" 物体高度: {objectHeight}米 → {objectHeightInModelUnits:F2}模型单位");
|
||
LogManager.Info($" 扫描高度: {scanHeightInModelUnits:F2}模型单位");
|
||
LogManager.Info($" 膨胀半径: {totalInflationRadiusInModelUnits:F2}模型单位");
|
||
|
||
// 生成缓存键(使用转换后的模型单位参数确保一致性)
|
||
var cacheKey = GridMapCacheKey.CreateFrom(bounds, cellSizeInModelUnits, objectRadiusInModelUnits, safetyMarginInModelUnits, objectHeightInModelUnits);
|
||
|
||
// 尝试从缓存获取
|
||
var cachedGridMap = GlobalGridMapCache.Instance.Get(cacheKey, 5000); // 预估生成需要5秒
|
||
if (cachedGridMap != null)
|
||
{
|
||
LogManager.Info($"【GridMap缓存】使用缓存网格地图 - {cacheKey}");
|
||
LogManager.Info($"【GridMap缓存】缓存GridMap统计: {cachedGridMap.GetStatistics()}");
|
||
|
||
// 更新规划起终点信息(这些信息可能会变化,不影响核心网格结构)
|
||
if (planningStartPoint != default(Point3D) && planningEndPoint != default(Point3D))
|
||
{
|
||
cachedGridMap.PlanningStartPoint = planningStartPoint;
|
||
cachedGridMap.PlanningEndPoint = planningEndPoint;
|
||
cachedGridMap.HasPlanningPoints = true;
|
||
}
|
||
|
||
return cachedGridMap;
|
||
}
|
||
|
||
// 缓存未命中,生成新的GridMap
|
||
LogManager.Info("【生成网格地图】缓存未命中,开始生成新网格地图");
|
||
LogManager.Info($"【生成网格地图】缓存键: {cacheKey}");
|
||
var startTime = DateTime.Now;
|
||
|
||
// 1. 使用通道构建器构建基础通道覆盖网格
|
||
LogManager.Info("【生成网格地图】步骤1: 构建通道覆盖网格");
|
||
var channelCoverage = _channelBuilder.BuildChannelCoverage(cellSizeInModelUnits);
|
||
|
||
LogManager.Info($"【生成网格地图】通道覆盖构建完成: {channelCoverage.GetStatistics()}");
|
||
LogManager.Info($"【阶段1完成】通道覆盖网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
|
||
// 1.5. 使用智能收集策略收集所有通道相关节点
|
||
LogManager.Info("【生成网格地图】步骤1.5: 智能收集通道相关节点");
|
||
|
||
// 直接获取所有可通行的物流模型项
|
||
var allTraversableItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
|
||
LogManager.Info($"【生成网格地图】直接获取到 {allTraversableItems.Count} 个可通行物流元素");
|
||
|
||
// 智能收集可通行模型相关节点(包括父节点、同级节点等)
|
||
var traversableRelatedItems = CollectRelatedItems(allTraversableItems.ToList());
|
||
LogManager.Info($"【生成网格地图】智能收集到 {traversableRelatedItems.Count} 个可通行元素相关节点");
|
||
|
||
// 1.6. 批量获取所有"无关项"(不参与网格生成的大型构件)
|
||
LogManager.Info("【生成网格地图】步骤1.6: 批量获取无关项并收集相关节点");
|
||
var irrelevantItems = CategoryAttributeManager.GetLogisticsItemsByType(
|
||
"无关项");
|
||
LogManager.Info($"【生成网格地图】直接获取到 {irrelevantItems.Count} 个无关项");
|
||
|
||
// 智能收集无关项相关节点(包括子节点等)
|
||
var irrelevantRelatedItems = CollectRelatedItems(irrelevantItems);
|
||
LogManager.Info($"【生成网格地图】智能收集到 {irrelevantRelatedItems.Count} 个无关项相关节点(将被排除)");
|
||
|
||
// 2. 先设置PassableHeights,确保障碍物高度检查时有正确的高度范围
|
||
LogManager.Info("【生成网格地图】步骤2: 为可通行网格设置高度约束");
|
||
SetChannelPassableHeights(channelCoverage.GridMap, scanHeightInModelUnits);
|
||
LogManager.Info($"【阶段2完成】高度约束设置后网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
|
||
// 2.5. 处理障碍物(包围盒检测) - 使用高性能优化版本
|
||
LogManager.Info("【生成网格地图】步骤2.5: 高性能包围盒遍历处理障碍物");
|
||
ProcessObstaclesWithBoundingBox(channelCoverage.GridMap, traversableRelatedItems, scanHeightInModelUnits, channelCoverage, irrelevantRelatedItems);
|
||
|
||
LogManager.Info($"【阶段2.5完成】障碍物处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
|
||
// 2.6. 单独处理门元素,设置为可通行,并设置通行高度
|
||
LogManager.Info("【生成网格地图】步骤2.6: 处理门元素");
|
||
ProcessDoorElements(channelCoverage.GridMap, allTraversableItems);
|
||
LogManager.Info($"【阶段2.6完成】门元素处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
|
||
// 2.7. 标记边界层(用于膨胀计算)
|
||
LogManager.Info("【生成网格地图】步骤2.7: 标记边界层");
|
||
double maxHeightDiff = ConfigManager.Instance.Current.PathEditing.MaxHeightDiff;
|
||
LogManager.Info($"【生成网格地图】边界高度差阈值: {ConfigManager.Instance.Current.PathEditing.MaxHeightDiffMeters}米 = {maxHeightDiff:F2}模型单位");
|
||
MarkBoundaryLayers(channelCoverage.GridMap, maxHeightDiff);
|
||
LogManager.Info($"【阶段2.7完成】边界层标记后网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
|
||
// 3. 应用物体尺寸膨胀
|
||
if (objectRadius > 0 || safetyMargin > 0)
|
||
{
|
||
LogManager.Info("【生成网格地图】步骤3: 应用物体膨胀");
|
||
ApplyObjectInflation(channelCoverage.GridMap, totalInflationRadiusInModelUnits);
|
||
LogManager.Info($"【生成网格地图】物体膨胀完成,膨胀半径: {totalInflationRadiusInModelUnits:F2}模型单位");
|
||
LogManager.Info($"【阶段3完成】物体膨胀后网格统计: {channelCoverage.GridMap.GetStatistics()}");
|
||
}
|
||
|
||
// 4. 设置规划起点和终点信息
|
||
if (planningStartPoint != default(Point3D) && planningEndPoint != default(Point3D))
|
||
{
|
||
channelCoverage.GridMap.PlanningStartPoint = planningStartPoint;
|
||
channelCoverage.GridMap.PlanningEndPoint = planningEndPoint;
|
||
channelCoverage.GridMap.HasPlanningPoints = true;
|
||
}
|
||
|
||
// 将通道数据设置到GridMap中
|
||
if (traversableRelatedItems != null && traversableRelatedItems.Any())
|
||
{
|
||
channelCoverage.GridMap.ChannelItems = traversableRelatedItems.ToList();
|
||
}
|
||
else if (channelCoverage.ChannelItems != null && channelCoverage.ChannelItems.Any())
|
||
{
|
||
channelCoverage.GridMap.ChannelItems = new List<ModelItem>(channelCoverage.ChannelItems);
|
||
}
|
||
|
||
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
|
||
LogManager.Info($"【生成网格地图】网格地图生成完成: {channelCoverage.GridMap.GetStatistics()}");
|
||
LogManager.Info($"【生成网格地图】总耗时: {elapsed:F1}ms");
|
||
|
||
// 将生成的GridMap添加到缓存
|
||
GlobalGridMapCache.Instance.Put(cacheKey, channelCoverage.GridMap);
|
||
LogManager.Info($"【GridMap缓存】网格地图已缓存,缓存键: {cacheKey}");
|
||
|
||
return channelCoverage.GridMap;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"【生成网格地图】生成失败: {ex.Message}");
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取GridMap缓存统计信息
|
||
/// </summary>
|
||
/// <returns>缓存统计报告</returns>
|
||
public static string GetCacheStatistics()
|
||
{
|
||
return GlobalGridMapCache.Instance.Statistics.GenerateReport();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取GridMap缓存详细统计报告
|
||
/// </summary>
|
||
/// <returns>详细统计报告</returns>
|
||
public static string GetDetailedCacheReport()
|
||
{
|
||
return GlobalGridMapCache.Instance.GenerateDetailedReport();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除GridMap缓存
|
||
/// </summary>
|
||
public static void ClearCache()
|
||
{
|
||
GlobalGridMapCache.Instance.Clear();
|
||
LogManager.Info("[GridMap缓存] 手动清除所有缓存");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理门元素,将其设置为可通行网格
|
||
/// </summary>
|
||
private void ProcessDoorElements(GridMap gridMap, ICollection<ModelItem> allTraversableItems)
|
||
{
|
||
try
|
||
{
|
||
// 过滤出门类型的元素
|
||
var doorItems = FilterDoorItems(allTraversableItems);
|
||
LogManager.Info($"【门元素处理】找到 {doorItems.Count} 个门元素");
|
||
|
||
if (!doorItems.Any())
|
||
{
|
||
LogManager.Info("【门元素处理】没有找到门元素,跳过处理");
|
||
return;
|
||
}
|
||
|
||
int processedCount = 0;
|
||
int skippedCount = 0;
|
||
foreach (var doorItem in doorItems)
|
||
{
|
||
try
|
||
{
|
||
// 获取门元素的包围盒
|
||
var bbox = doorItem.BoundingBox();
|
||
if (bbox == null)
|
||
{
|
||
LogManager.Debug($"【门元素处理】门元素无有效包围盒,跳过: {doorItem.DisplayName}");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 使用限宽计算门的实际开口区域
|
||
var coveredCells = CalculateDoorOpeningCoverage(doorItem, bbox, gridMap);
|
||
|
||
if (!coveredCells.Any())
|
||
{
|
||
LogManager.Warning($"【门元素处理】门元素 {doorItem.DisplayName} 无法计算有效开口区域,跳过处理");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 获取门的限高属性
|
||
string heightLimitStr = CategoryAttributeManager.GetLogisticsPropertyValue(doorItem, CategoryAttributeManager.LogisticsProperties.HEIGHT_LIMIT);
|
||
double configuredHeight = CategoryAttributeManager.ParseLogisticsLimitValue(heightLimitStr, "限高");
|
||
|
||
var (doorBottomElevation, doorTopElevation) = GetObstacleElevationRange(
|
||
bbox.Min.X, bbox.Min.Y, bbox.Min.Z,
|
||
bbox.Max.X, bbox.Max.Y, bbox.Max.Z,
|
||
gridMap.CoordinateSystemType);
|
||
|
||
// 计算门的实际物理高度
|
||
double actualDoorHeight = doorTopElevation - doorBottomElevation;
|
||
|
||
double doorHeight;
|
||
if (configuredHeight <= 0)
|
||
{
|
||
// 没有限高配置,使用实际门高
|
||
doorHeight = actualDoorHeight;
|
||
LogManager.Debug($"【门元素处理】门元素 {doorItem.DisplayName} 未设置限高,使用包围盒高度: {doorHeight:F2}");
|
||
}
|
||
else
|
||
{
|
||
// 转换限高到模型单位
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
|
||
double configuredHeightInModelUnits = configuredHeight * metersToModelUnits;
|
||
|
||
// 取实际门高和配置限高的最小值
|
||
doorHeight = Math.Min(actualDoorHeight, configuredHeightInModelUnits);
|
||
LogManager.Debug($"【门元素处理】门元素 {doorItem.DisplayName} 实际高度: {actualDoorHeight:F2}, 限高:{configuredHeightInModelUnits:F2}, 有效高度: {doorHeight:F2}");
|
||
}
|
||
|
||
// 获取门的限速
|
||
double doorSpeedLimit = CategoryAttributeManager.GetSpeedLimit(doorItem);
|
||
LogManager.Info($"[门网格限速] 门物品 '{doorItem.DisplayName}' 限速: {doorSpeedLimit:F2}m/s");
|
||
|
||
// 设置开口区域的网格为可通行的门类型
|
||
foreach (var (x, y) in coveredCells)
|
||
{
|
||
var gridPos = new GridPoint2D(x, y);
|
||
|
||
// 计算门网格的精确世界坐标
|
||
var world2D = gridMap.GridToWorld2D(gridPos);
|
||
var preciseWorldPosition = gridMap.SetWorldElevation(world2D, doorBottomElevation);
|
||
|
||
// 使用GridCellBuilder创建完整配置的门GridCell
|
||
var cell = GridCellBuilder.Door(preciseWorldPosition, doorItem, doorSpeedLimit);
|
||
LogManager.Info($"[门网格创建] 位置({preciseWorldPosition.X:F2},{preciseWorldPosition.Y:F2},{preciseWorldPosition.Z:F2}) 限速: {cell.SpeedLimit:F2}m/s");
|
||
|
||
// 多层架构:PassableHeight现在在HeightLayer中设置
|
||
// 门的高度范围由AddHeightLayer时设置
|
||
|
||
// 一次性放置完整配置的GridCell
|
||
gridMap.PlaceCell(gridPos, cell);
|
||
LogManager.Info($"[门网格放置] 网格坐标({gridPos.X},{gridPos.Y}) 最终限速: {gridMap.GetCell(gridPos)?.SpeedLimit:F2}m/s");
|
||
|
||
// 为门添加可通行的高度层
|
||
var doorLayer = new HeightLayer
|
||
{
|
||
Type = "门",
|
||
Z = doorBottomElevation,
|
||
PassableHeight = new HeightInterval(doorBottomElevation, doorBottomElevation + doorHeight),
|
||
IsWalkable = true, // 关键:门是可通行的
|
||
SpeedLimit = doorSpeedLimit,
|
||
SourceItem = doorItem,
|
||
IsBoundary = false
|
||
};
|
||
gridMap.AddHeightLayer(gridPos, doorLayer);
|
||
LogManager.Info($"[门高度层添加] 网格({gridPos.X},{gridPos.Y}) 高度范围: [{doorBottomElevation:F2}, {doorBottomElevation + doorHeight:F2}], 可通行高度: {doorHeight:F2}");
|
||
}
|
||
|
||
processedCount++;
|
||
LogManager.Debug($"【门元素处理】已处理门元素: {doorItem.DisplayName},开口区域覆盖网格数量: {coveredCells.Count}");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"【门元素处理】处理门元素失败: {doorItem.DisplayName}, 错误: {ex.Message}");
|
||
skippedCount++;
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"【门元素处理】完成,已处理 {processedCount} 个门元素,跳过 {skippedCount} 个");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"【门元素处理】处理失败: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 为所有可通行网格设置PassableHeights确保高度约束检查
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <param name="scanHeightInModelUnits">扫描高度(模型单位)</param>
|
||
private void SetChannelPassableHeights(GridMap gridMap, double scanHeightInModelUnits)
|
||
{
|
||
LogManager.Info($"【高度约束设置】开始为所有可通行网格设置高度约束,扫描高度: {scanHeightInModelUnits:F2}模型单位");
|
||
|
||
int processedCount = 0;
|
||
int layerCount = 0;
|
||
int stairBottomLayersProcessed = 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.HasAnyWalkableLayer())
|
||
{
|
||
// 检查是否是楼梯或电梯网格(有多层且包含楼梯/电梯层)
|
||
bool hasStairsOrElevator = cell.HeightLayers.Any(layer =>
|
||
layer.Type == "楼梯" ||
|
||
layer.Type == "电梯");
|
||
|
||
// 为每个高度层设置PassableHeight
|
||
for (int i = 0; i < cell.HeightLayers.Count; i++)
|
||
{
|
||
var layer = cell.HeightLayers[i];
|
||
|
||
// 特殊处理:楼梯/电梯的第0层(下方通道层)
|
||
if (hasStairsOrElevator && i == 0 && cell.HeightLayers.Count >= 2)
|
||
{
|
||
// Layer 0是楼梯下方的通道层
|
||
// PassableHeight应该是从层顶到上方楼梯底面的距离
|
||
var upperLayer = cell.HeightLayers[1]; // 楼梯层
|
||
double bottomLayerTop = layer.Z; // 下层顶部
|
||
double upperLayerBottom = upperLayer.Z; // 上层底部
|
||
|
||
// 计算实际可通行高度
|
||
double actualPassableHeight = upperLayerBottom - bottomLayerTop;
|
||
|
||
if (actualPassableHeight > 0)
|
||
{
|
||
layer.PassableHeight = new HeightInterval(0, actualPassableHeight);
|
||
stairBottomLayersProcessed++;
|
||
}
|
||
else
|
||
{
|
||
// 如果计算出负值或0,使用默认值
|
||
layer.PassableHeight = new HeightInterval(0, scanHeightInModelUnits);
|
||
LogManager.Warning($"【楼梯下方高度】网格({x},{y}) Layer{i} 计算高度异常: {actualPassableHeight:F2},使用默认值");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 普通层或楼梯层本身,使用标准扫描高度
|
||
layer.PassableHeight = new HeightInterval(0, scanHeightInModelUnits);
|
||
}
|
||
|
||
cell.HeightLayers[i] = layer;
|
||
layerCount++;
|
||
}
|
||
|
||
gridMap.Cells[x, y] = cell;
|
||
processedCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"【高度约束设置】完成,已处理 {processedCount} 个网格,{layerCount} 个高度层");
|
||
LogManager.Info($"【高度约束设置】其中楼梯/电梯下方层: {stairBottomLayersProcessed} 个(使用实际可通行高度)");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 标记边界层(用于膨胀计算)
|
||
/// 边界定义:当前网格层数 > 邻居网格层数(梯度≥1),且层间高度差超过阈值
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <param name="maxHeightDiffInModelUnits">最大允许高度差(模型单位)</param>
|
||
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.HasAnyWalkableLayer())
|
||
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];
|
||
|
||
// 情况1:网格地图边界 - 标记当前网格所有层为边界
|
||
if (nx < 0 || nx >= gridMap.Width || ny < 0 || ny >= gridMap.Height)
|
||
{
|
||
for (int li = 0; li < currentLayerCount; li++)
|
||
{
|
||
var layer = cell.HeightLayers[li];
|
||
if (!layer.IsBoundary)
|
||
{
|
||
layer.IsBoundary = true;
|
||
cell.HeightLayers[li] = layer;
|
||
totalBoundaryLayers++;
|
||
//LogManager.Debug($"【边界检测】网格({x},{y}) Layer{li} 标记为边界(地图边界),方向={dirNames[dir]}");
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
var neighbor = gridMap.Cells[nx, ny];
|
||
int neighborLayerCount = (neighbor.HeightLayers != null) ? neighbor.HeightLayers.Count : 0;
|
||
|
||
// 情况2:邻居是Unknown - 标记当前网格所有层为边界
|
||
if (neighbor.CellType == "空洞")
|
||
{
|
||
for (int li = 0; li < currentLayerCount; li++)
|
||
{
|
||
var layer = cell.HeightLayers[li];
|
||
if (!layer.IsBoundary)
|
||
{
|
||
layer.IsBoundary = true;
|
||
cell.HeightLayers[li] = layer;
|
||
totalBoundaryLayers++;
|
||
//LogManager.Debug($"【边界检测】网格({x},{y}) Layer{li} 标记为边界(邻居Unknown),方向={dirNames[dir]}");
|
||
}
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// 情况3:层数梯度检测:当前层数 > 邻居层数
|
||
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 != null
|
||
? ResolveBoundsMinElevation(
|
||
gridMap.Bounds.Min.X,
|
||
gridMap.Bounds.Min.Y,
|
||
gridMap.Bounds.Min.Z,
|
||
gridMap.CoordinateSystemType)
|
||
: 0.0;
|
||
}
|
||
|
||
// 计算层间高度差
|
||
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} 个边界层");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从物流元素集合中过滤出门类型的元素
|
||
/// </summary>
|
||
private List<ModelItem> FilterDoorItems(ICollection<ModelItem> items)
|
||
{
|
||
var doorItems = new List<ModelItem>();
|
||
|
||
if (items == null || !items.Any())
|
||
{
|
||
return doorItems;
|
||
}
|
||
|
||
foreach (var item in items)
|
||
{
|
||
try
|
||
{
|
||
var logisticsType = CategoryAttributeManager.GetCategoryId(item);
|
||
if (logisticsType == "门")
|
||
{
|
||
doorItems.Add(item);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"【门元素过滤】获取物流类型失败: {item.DisplayName}, 错误: {ex.Message}");
|
||
}
|
||
}
|
||
|
||
return doorItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成网格点坐标用于垂直扫描
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <returns>网格点坐标列表</returns>
|
||
private List<Point3D> GenerateGridPoints(GridMap gridMap)
|
||
{
|
||
var points = new List<Point3D>();
|
||
|
||
// 只为通道网格生成扫描点,不扫描整个网格
|
||
for (int x = 0; x < gridMap.Width; x++)
|
||
{
|
||
for (int y = 0; y < gridMap.Height; y++)
|
||
{
|
||
var cell = gridMap.Cells[x, y];
|
||
|
||
// 只为通道类型的网格生成扫描点
|
||
if (cell.CellType == "通道" && cell.IsInChannel)
|
||
{
|
||
// 使用网格的实际Z坐标
|
||
var worldPos = gridMap.GridToWorld3D(new GridPoint2D(x, y));
|
||
|
||
// 重要修复:使用通道顶面作为垂直扫描的起点,而不是底面
|
||
// 从通道构建器的日志可知通道总边界MaxZ = 38.8,这是正确的扫描起点
|
||
double channelTopElevation = GetChannelTopZ(worldPos, gridMap);
|
||
|
||
points.Add(gridMap.SetWorldElevation(worldPos, channelTopElevation));
|
||
}
|
||
}
|
||
}
|
||
|
||
return points;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取通道顶面Z坐标作为垂直扫描起点
|
||
/// </summary>
|
||
/// <param name="worldPos">世界坐标位置</param>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <returns>通道顶面Z坐标</returns>
|
||
private double GetChannelTopZ(Point3D worldPos, GridMap gridMap)
|
||
{
|
||
try
|
||
{
|
||
if (gridMap.Bounds != null)
|
||
{
|
||
var (minElevation, maxElevation) =
|
||
GetObstacleElevationRange(gridMap.Bounds.Min, gridMap.Bounds.Max, gridMap);
|
||
|
||
if (maxElevation > minElevation)
|
||
{
|
||
return maxElevation;
|
||
}
|
||
}
|
||
|
||
double fallbackTopElevation = gridMap.GetWorldElevation(worldPos) + 3.4;
|
||
LogManager.Debug($"[通道顶面计算] 使用备选方案: 底面高程{gridMap.GetWorldElevation(worldPos):F2} + 3.4m = 顶面高程{fallbackTopElevation:F2}");
|
||
return fallbackTopElevation;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[通道顶面计算] 计算失败: {ex.Message},使用世界位置高程");
|
||
return gridMap.GetWorldElevation(worldPos);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将高度区间信息集成到网格地图中
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <param name="heightIntervals">高度区间数据</param>
|
||
private void IntegrateHeightIntervalsToGrid(GridMap gridMap, Dictionary<Point3D, HeightInterval> heightIntervals)
|
||
{
|
||
int updatedCells = 0;
|
||
int channelCellsWithHeightData = 0;
|
||
int channelCellsBecomingUnwalkable = 0;
|
||
|
||
foreach (var kvp in heightIntervals)
|
||
{
|
||
var point = kvp.Key;
|
||
var interval = kvp.Value;
|
||
|
||
// 将世界坐标转换为网格坐标
|
||
var gridPos = gridMap.WorldToGrid(point);
|
||
int gridX = gridPos.X;
|
||
int gridY = gridPos.Y;
|
||
|
||
// 确保坐标在有效范围内
|
||
if (gridX >= 0 && gridX < gridMap.Width && gridY >= 0 && gridY < gridMap.Height)
|
||
{
|
||
// 更新网格单元格的高度区间信息
|
||
var cell = gridMap.Cells[gridX, gridY];
|
||
|
||
// 多层架构:高度区间在HeightLayer中设置,这里不再需要
|
||
// cell.PassableHeight = interval;
|
||
|
||
// 关键修复:只处理通道单元格的高度信息集成
|
||
// 非通道单元格不应该通过高度扫描而改变其类型或可通行性
|
||
if (cell.CellType == "通道" && cell.IsInChannel)
|
||
{
|
||
channelCellsWithHeightData++;
|
||
|
||
if (interval.MaxZ > interval.MinZ)
|
||
{
|
||
// 通道区域有足够净空高度,设置为可通行
|
||
var cellPos = new GridPoint2D(gridX, gridY);
|
||
|
||
// 创建更新后的通道GridCell,保持原有属性但更新位置和高度
|
||
var updatedCell = new GridCell
|
||
{
|
||
CellType = "通道",
|
||
IsInChannel = true,
|
||
ChannelType = ChannelType.Corridor,
|
||
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
|
||
SpeedLimit = cell.SpeedLimit, // 保持原有限速
|
||
HeightLayers = cell.HeightLayers, // 保持高度层
|
||
Cost = 1.0
|
||
};
|
||
|
||
// 放置更新的GridCell
|
||
gridMap.PlaceCell(cellPos, updatedCell);
|
||
}
|
||
else
|
||
{
|
||
// 通道区域但净空高度不足,标记为不可通行但保持通道类型
|
||
var cellPos = new GridPoint2D(gridX, gridY);
|
||
|
||
// 创建高度不足的通道GridCell,保持原有属性但设为不可通行
|
||
var updatedCell = new GridCell
|
||
{
|
||
CellType = "通道",
|
||
IsInChannel = true,
|
||
ChannelType = ChannelType.Corridor,
|
||
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
|
||
SpeedLimit = cell.SpeedLimit, // 保持原有限速
|
||
HeightLayers = cell.HeightLayers, // 保持高度层
|
||
Cost = double.MaxValue
|
||
};
|
||
|
||
// 放置更新的GridCell
|
||
gridMap.PlaceCell(cellPos, updatedCell);
|
||
|
||
channelCellsBecomingUnwalkable++;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 非通道单元格:不应该通过垂直扫描改变其状态
|
||
// 这种情况理论上不应该发生,因为只对通道单元格进行扫描
|
||
LogManager.Warning($"[通道2.5D模式] 警告:非通道单元格 ({gridX},{gridY}) 收到高度数据,类型:{cell.CellType}, IsInChannel:{cell.IsInChannel}");
|
||
|
||
// 多层架构:高度区间在HeightLayer中设置
|
||
// cell.PassableHeight = interval;
|
||
gridMap.Cells[gridX, gridY] = cell;
|
||
}
|
||
updatedCells++;
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[通道2.5D模式] 高度信息集成完成,更新了 {updatedCells} 个网格单元格");
|
||
LogManager.Info($"[通道2.5D模式] 处理的通道单元格数量: {channelCellsWithHeightData}, 因高度不足变为不可通行: {channelCellsBecomingUnwalkable}");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 验证网格统计一致性,确保可通行单元格数量不超过预期
|
||
/// </summary>
|
||
/// <param name="gridMap">要验证的网格地图</param>
|
||
/// <param name="stage">验证阶段描述</param>
|
||
private void ValidateGridStatisticsConsistency(GridMap gridMap, string stage)
|
||
{
|
||
int totalCells = gridMap.Width * gridMap.Height;
|
||
int walkableCount = 0;
|
||
int channelCount = 0;
|
||
int openSpaceCount = 0;
|
||
int obstacleCount = 0;
|
||
int doorCount = 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.HasAnyWalkableLayer())
|
||
{
|
||
walkableCount++;
|
||
switch (cell.CellType)
|
||
{
|
||
case "通道":
|
||
channelCount++;
|
||
break;
|
||
case "楼板":
|
||
openSpaceCount++;
|
||
break;
|
||
case "门":
|
||
doorCount++;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (cell.CellType == "障碍物")
|
||
{
|
||
obstacleCount++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[网格验证] {stage} - 总单元格: {totalCells}, 可通行: {walkableCount}");
|
||
LogManager.Info($"[网格验证] 可通行单元格明细 - 通道: {channelCount}, 开放空间: {openSpaceCount}, 门: {doorCount}");
|
||
LogManager.Info($"[网格验证] 不可通行单元格 - 障碍物: {obstacleCount}, 其他: {totalCells - walkableCount - obstacleCount}");
|
||
|
||
// 验证:可通行的开放空间不应该在通道2.5D模式中出现,除非是合理的扩展
|
||
if (openSpaceCount > 0)
|
||
{
|
||
LogManager.Warning($"[网格验证] 警告: 发现 {openSpaceCount} 个可通行的开放空间单元格,这在通道2.5D模式中可能是异常的");
|
||
}
|
||
|
||
// 验证:总可通行数量的合理性检查
|
||
if (walkableCount > channelCount * 1.2) // 允许20%的合理扩展
|
||
{
|
||
LogManager.Warning($"[网格验证] 警告: 可通行单元格数量({walkableCount})明显超过通道单元格数量({channelCount})");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 应用物体尺寸膨胀
|
||
/// 在障碍物周围扩展不可通行区域,考虑物体尺寸
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <param name="objectRadiusInModelUnits">物体半径(模型单位)</param>
|
||
private void ApplyObjectInflation(GridMap gridMap, double objectRadiusInModelUnits)
|
||
{
|
||
if (objectRadiusInModelUnits <= 0) return;
|
||
|
||
try
|
||
{
|
||
// 计算膨胀半径(网格单元格数)
|
||
int inflationRadius = (int)Math.Ceiling(objectRadiusInModelUnits / gridMap.CellSize);
|
||
LogManager.Info($"[高效膨胀] 膨胀半径: {inflationRadius}个网格单元");
|
||
|
||
// 使用高效的距离变换算法
|
||
ApplyObjectInflation(gridMap, inflationRadius);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[高效膨胀] 应用物体膨胀时发生错误: {ex.Message}");
|
||
throw new AutoPathPlanningException($"物体膨胀失败: {ex.Message}", ex);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 多层膨胀算法 - 分两次处理:障碍物膨胀 + 边界膨胀
|
||
/// 核心原则:障碍物从外围膨胀(不含本身),边界从边界本身膨胀(包含边界)
|
||
/// </summary>
|
||
private void ApplyObjectInflation(GridMap gridMap, int inflationRadius)
|
||
{
|
||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||
|
||
LogManager.Info($"【多层膨胀】开始按层处理膨胀,网格大小: {gridMap.Width}x{gridMap.Height}, 膨胀半径: {inflationRadius}");
|
||
|
||
// 计算高度差阈值(用于楼梯口保护)
|
||
double maxHeightDiff = ConfigManager.Instance.Current.PathEditing.MaxHeightDiff;
|
||
|
||
// 统计每个层索引的处理情况
|
||
var layerStats = new Dictionary<int, (int obstacleInflated, int boundaryCount, int boundaryInflated)>();
|
||
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++)
|
||
{
|
||
// 步骤1:障碍物膨胀(不包括障碍物本身)
|
||
int obstacleInflated = InflateObstacles(gridMap, layerIndex, inflationRadius);
|
||
|
||
// 步骤2:边界膨胀(包括边界本身)
|
||
var (boundaryCount, boundaryInflated) = InflateBoundaries(gridMap, layerIndex, inflationRadius, maxHeightDiff);
|
||
|
||
if (boundaryCount > 0 || obstacleInflated > 0)
|
||
{
|
||
layerStats[layerIndex] = (obstacleInflated, boundaryCount, boundaryInflated);
|
||
LogManager.Info($"【多层膨胀】Layer{layerIndex} 完成 - 障碍物膨胀: {obstacleInflated}层, 边界: {boundaryCount}个, 边界膨胀: {boundaryInflated}层");
|
||
}
|
||
}
|
||
|
||
stopwatch.Stop();
|
||
LogManager.Info($"【多层膨胀】完成,耗时: {stopwatch.ElapsedMilliseconds}ms");
|
||
|
||
foreach (var kvp in layerStats.OrderBy(k => k.Key))
|
||
{
|
||
LogManager.Info($"【多层膨胀】Layer{kvp.Key}: 障碍物膨胀{kvp.Value.obstacleInflated}层, 边界{kvp.Value.boundaryCount}个, 边界膨胀{kvp.Value.boundaryInflated}层");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 距离变换算法 - 8连通距离计算
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 障碍物膨胀 - 从障碍物外围开始膨胀,不包括障碍物本身
|
||
/// </summary>
|
||
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 == "空洞")
|
||
{
|
||
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 == "门")
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 边界膨胀 - 从边界网格本身开始膨胀,包括边界网格
|
||
/// </summary>
|
||
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 == "门")
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 收集物流项目的相关节点
|
||
/// 根据节点类型决定收集策略:几何体节点收集父节点和同级节点,集合节点收集子节点
|
||
/// </summary>
|
||
/// <param name="items">物流项目节点列表</param>
|
||
/// <returns>包含所有相关节点的集合</returns>
|
||
private HashSet<ModelItem> CollectRelatedItems(List<ModelItem> items)
|
||
{
|
||
var relatedItems = new HashSet<ModelItem>();
|
||
|
||
foreach (var item in items)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info($"[节点收集] 开始处理节点: '{item.DisplayName}'");
|
||
|
||
// 使用 ModelItemAnalysisHelper 的通用方法收集相关节点
|
||
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(item);
|
||
|
||
// 将结果添加到 HashSet 中
|
||
foreach (var node in itemRelatedNodes)
|
||
{
|
||
relatedItems.Add(node);
|
||
}
|
||
|
||
LogManager.Info($"[节点收集] 从 '{item.DisplayName}' 收集到 {itemRelatedNodes.Count} 个相关节点");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[节点收集] 处理节点 '{item?.DisplayName ?? "NULL"}' 时出错: {ex.Message}");
|
||
// 出错时至少保证节点本身被添加
|
||
relatedItems.Add(item);
|
||
}
|
||
}
|
||
|
||
LogManager.Info($"[节点收集] 共收集到 {relatedItems.Count} 个相关节点");
|
||
return relatedItems;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 轻量级后处理:只对预筛选的几何体项目进行必要的属性提取
|
||
/// 大幅减少API调用次数
|
||
/// </summary>
|
||
private Dictionary<ModelItem, ItemProperties> PostProcessGeometryItems(
|
||
List<ModelItem> geometryItems,
|
||
HashSet<ModelItem> traversableItemsSet,
|
||
HashSet<ModelItem> irrelevantItemsSet)
|
||
{
|
||
LogManager.Info("[轻量级后处理] 开始处理Search API筛选结果");
|
||
var startTime = DateTime.Now;
|
||
var itemCache = new Dictionary<ModelItem, ItemProperties>();
|
||
|
||
var totalItems = geometryItems.Count;
|
||
var apiCalls = 0;
|
||
var skippedByChannel = 0;
|
||
var skippedByIrrelevant = 0;
|
||
var skippedByBoundingBox = 0;
|
||
|
||
foreach (var item in geometryItems)
|
||
{
|
||
try
|
||
{
|
||
var properties = new ItemProperties
|
||
{
|
||
// 已知条件:HasGeometry = true(Search API保证)
|
||
HasGeometry = true,
|
||
|
||
// 快速筛除1:通道相关项目(HashSet查找,极快)
|
||
IsChannelItem = traversableItemsSet.Contains(item)
|
||
};
|
||
if (properties.IsChannelItem)
|
||
{
|
||
skippedByChannel++;
|
||
continue; // 跳过通道项目,不进行昂贵的API调用
|
||
}
|
||
|
||
properties.IsChildOfChannel = IsChildOfChannelItems(item, traversableItemsSet);
|
||
if (properties.IsChildOfChannel)
|
||
{
|
||
skippedByChannel++;
|
||
continue; // 跳过通道子项目
|
||
}
|
||
|
||
// 快速筛除2:无关项(不参与网格生成的大型构件)
|
||
properties.IsIrrelevantItem = irrelevantItemsSet.Contains(item);
|
||
if (properties.IsIrrelevantItem)
|
||
{
|
||
skippedByIrrelevant++;
|
||
continue; // 跳过无关项,避免昂贵的BoundingBox API调用
|
||
}
|
||
|
||
// 设为已知值(避免后续筛选中的判断错误)
|
||
properties.IsContainer = false; // 基于测试验证,几何体项目都不是容器
|
||
|
||
// API调用1: BoundingBox() - 获取边界框(唯一保留的API调用)
|
||
properties.BoundingBox = item.BoundingBox();
|
||
apiCalls++;
|
||
|
||
if (properties.BoundingBox == null)
|
||
{
|
||
skippedByBoundingBox++;
|
||
continue; // 跳过无边界框项目
|
||
}
|
||
|
||
// 设置为默认值,实际的高度检查在后续阶段进行
|
||
properties.IsInScanHeightRange = true;
|
||
|
||
// 只有通过所有筛选的项目才加入结果
|
||
itemCache[item] = properties;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"[轻量级后处理] 处理模型项失败: {item?.DisplayName ?? "NULL"}, {ex.Message}");
|
||
}
|
||
}
|
||
|
||
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
|
||
LogManager.Info($"[轻量级后处理] 完成,输入 {totalItems} 项,有效结果 {itemCache.Count} 项,API调用 {apiCalls} 次,耗时: {elapsed:F1}ms");
|
||
LogManager.Info($"[轻量级后处理] 筛除统计 - 通道相关: {skippedByChannel}, 无关项: {skippedByIrrelevant}, 无边界框: {skippedByBoundingBox}");
|
||
|
||
return itemCache;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用包围盒遍历方式处理障碍物(高性能优化版)
|
||
/// 分离API访问和数值计算,利用并行处理加速纯计算部分
|
||
/// </summary>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <param name="channelItems">通道模型项列表</param>
|
||
/// <param name="scanHeightInModelUnits">扫描高度范围(模型单位)</param>
|
||
/// <param name="channelCoverage">通道覆盖信息,包含通道边界</param>
|
||
/// <param name="irrelevantItemsSet">无关项集合(不参与网格生成的大型构件)</param>
|
||
private void ProcessObstaclesWithBoundingBox(GridMap gridMap, HashSet<ModelItem> channelItems, double scanHeightInModelUnits, ChannelCoverage channelCoverage, HashSet<ModelItem> irrelevantItemsSet)
|
||
{
|
||
try
|
||
{
|
||
LogManager.Info("[高性能障碍物处理] 开始处理障碍物 - Search API优化版本");
|
||
var startTime = DateTime.Now;
|
||
|
||
var traversableItemsSet = channelItems ?? new HashSet<ModelItem>();
|
||
|
||
// 阶段1:Search API预筛选(一次性批量获取几何体项目)
|
||
var geometryItems = ModelItemAnalysisHelper.GetAllVisibleGeometryItems();
|
||
|
||
LogManager.Info($"[高性能障碍物处理] 输入统计 - 几何体项目: {geometryItems.Count}, 通道元素: {traversableItemsSet.Count}");
|
||
|
||
// 阶段2:轻量级后处理(只对预筛选结果进行必要的API调用)
|
||
var itemCache = PostProcessGeometryItems(geometryItems, traversableItemsSet, irrelevantItemsSet);
|
||
|
||
// 阶段3:条件过滤(并行数值计算)- 使用50%CPU核心优化
|
||
LogManager.Info("[高性能障碍物处理] 阶段3: 并行条件过滤");
|
||
var filterStart = DateTime.Now;
|
||
|
||
// 🔥 性能优化:限制并行度为CPU核心数的50%,避免过度并行化
|
||
var optimalParallelism = Math.Max(1, Environment.ProcessorCount / 2);
|
||
LogManager.Info($"[并行优化] CPU核心: {Environment.ProcessorCount}, 使用并行度: {optimalParallelism}");
|
||
|
||
var validItems = itemCache.AsParallel()
|
||
.WithDegreeOfParallelism(optimalParallelism)
|
||
.Where(kvp => kvp.Value.HasGeometry &&
|
||
!kvp.Value.IsContainer &&
|
||
!kvp.Value.IsChannelItem &&
|
||
!kvp.Value.IsChildOfChannel &&
|
||
kvp.Value.BoundingBox != null &&
|
||
kvp.Value.IsInScanHeightRange)
|
||
.ToList();
|
||
|
||
var filterElapsed = (DateTime.Now - filterStart).TotalMilliseconds;
|
||
LogManager.Info($"[高性能障碍物处理] 阶段3完成: 从缓存中筛选出 {validItems.Count} 个有效项,耗时: {filterElapsed:F1}ms");
|
||
|
||
int horizontalOverlapCount = validItems.Count(kvp => IntersectsGridPlanarExtents(kvp.Value.BoundingBox, gridMap));
|
||
LogManager.Info($"[高性能障碍物处理] 阶段3.5: 与当前网格平面范围相交的有效几何体: {horizontalOverlapCount}/{validItems.Count}");
|
||
|
||
// 获取可通行网格的实际高度范围信息
|
||
var walkableMinZ = channelCoverage.WalkableMinZ;
|
||
var walkableMaxZ = channelCoverage.WalkableMaxZ;
|
||
var walkableGridCount = channelCoverage.WalkableGridCount;
|
||
var walkableHeightRange = walkableMaxZ - walkableMinZ;
|
||
LogManager.Info($"[高性能障碍物处理] 可通行网格高度信息 - 最低点: {walkableMinZ:F2}, 最高点: {walkableMaxZ:F2}");
|
||
LogManager.Info($"[高性能障碍物处理] 可通行网格数量: {walkableGridCount}, 高度范围: {walkableHeightRange:F2}, 加物体高度后扫描范围: [{walkableMinZ:F2}, {walkableMaxZ + scanHeightInModelUnits:F2}]");
|
||
|
||
// 阶段4:网格覆盖计算(并行几何计算)- 使用50%CPU核心优化 + 基于通道的精确高度检查
|
||
LogManager.Info("[高性能障碍物处理] 阶段4: 并行网格覆盖计算 + 基于通道高度的精确高度检查");
|
||
var gridCalcStart = DateTime.Now;
|
||
|
||
var heightCheckedItems = validItems.AsParallel()
|
||
.WithDegreeOfParallelism(optimalParallelism)
|
||
.Where(kvp =>
|
||
{
|
||
// 精确高度检查:使用几何体中心点对应的网格高度
|
||
var bbox = kvp.Value.BoundingBox;
|
||
var centerPoint = new Point3D(
|
||
(bbox.Min.X + bbox.Max.X) / 2,
|
||
(bbox.Min.Y + bbox.Max.Y) / 2,
|
||
(bbox.Min.Z + bbox.Max.Z) / 2);
|
||
|
||
var centerGridPos = gridMap.WorldToGrid(centerPoint);
|
||
if (!gridMap.IsValidGridPosition(centerGridPos))
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var centerCell = gridMap.Cells[centerGridPos.X, centerGridPos.Y];
|
||
|
||
// 使用可通行网格高度范围计算扫描范围
|
||
// 扫描范围:从可通行网格最低点开始,到可通行网格最高点+物体高度+安全间隙
|
||
var scanMin = walkableMinZ;
|
||
var scanMax = walkableMaxZ + scanHeightInModelUnits;
|
||
|
||
// 只有与该网格高度范围重叠的几何体才进入处理
|
||
var (obstacleMinElevation, obstacleMaxElevation) =
|
||
GetObstacleElevationRange(bbox.Min, bbox.Max, gridMap);
|
||
bool isInRange = !(obstacleMaxElevation <= scanMin || obstacleMinElevation > scanMax);
|
||
|
||
return isInRange;
|
||
})
|
||
.ToList();
|
||
|
||
var gridUpdates = heightCheckedItems.AsParallel()
|
||
.WithDegreeOfParallelism(optimalParallelism)
|
||
.SelectMany(kvp =>
|
||
{
|
||
var coveredCells = CalculateBoundingBoxGridCoverage(kvp.Value.BoundingBox, gridMap);
|
||
return coveredCells.Select(cell => new GridUpdate
|
||
{
|
||
Item = kvp.Key,
|
||
X = cell.x,
|
||
Y = cell.y,
|
||
BoundingBox = kvp.Value.BoundingBox
|
||
});
|
||
})
|
||
.ToList();
|
||
|
||
var gridCalcElapsed = (DateTime.Now - gridCalcStart).TotalMilliseconds;
|
||
var totalCheckedItems = validItems.Count;
|
||
var filteredItems = totalCheckedItems - heightCheckedItems.Count;
|
||
int overlappingItemsRetained = heightCheckedItems.Count(kvp => IntersectsGridPlanarExtents(kvp.Value.BoundingBox, gridMap));
|
||
int overlappingItemsFiltered = validItems.Count(kvp => IntersectsGridPlanarExtents(kvp.Value.BoundingBox, gridMap)) - overlappingItemsRetained;
|
||
|
||
LogManager.Info($"[高性能障碍物处理] 阶段4完成: 生成 {gridUpdates.Count} 个网格更新操作,耗时: {gridCalcElapsed:F1}ms");
|
||
LogManager.Info($"[精确高度检查] 检查了 {totalCheckedItems} 个几何体,过滤掉 {filteredItems} 个高度范围外的几何体,保留 {heightCheckedItems.Count} 个");
|
||
LogManager.Info($"[精确高度检查] 其中与当前网格平面范围相交的有效几何体: 过滤 {overlappingItemsFiltered} 个, 保留 {overlappingItemsRetained} 个");
|
||
|
||
// 阶段5:网格状态更新(单线程写入,3D高度检查)
|
||
LogManager.Info("[高性能障碍物处理] 阶段5: 单线程网格状态更新(3D高度检查)");
|
||
var updateStart = DateTime.Now;
|
||
|
||
var updatedCells = 0;
|
||
var obstacleItems = 0;
|
||
var layersBlocked = 0;
|
||
var cellsFullyBlocked = 0;
|
||
var cellsWithLayers = 0;
|
||
var totalLayersChecked = 0;
|
||
|
||
foreach (var update in gridUpdates)
|
||
{
|
||
var cell = gridMap.Cells[update.X, update.Y];
|
||
|
||
// 只更新通道网格,跳过已是障碍物的网格
|
||
if (cell.CellType == "通道" && cell.IsInChannel)
|
||
{
|
||
var bbox = update.BoundingBox;
|
||
|
||
// 统计有多少网格有高度层
|
||
if (cell.HeightLayers != null && cell.HeightLayers.Count > 0)
|
||
{
|
||
cellsWithLayers++;
|
||
totalLayersChecked += cell.HeightLayers.Count;
|
||
}
|
||
|
||
// 3D高度检查:标记与障碍物重叠的层为不可通行(保留层结构)
|
||
bool anyLayerModified = false;
|
||
|
||
for (int i = 0; i < cell.HeightLayers.Count; i++)
|
||
{
|
||
var layer = cell.HeightLayers[i];
|
||
|
||
// 计算该层的高度范围
|
||
double layerMinZ = layer.Z;
|
||
double layerMaxZ = layer.Z + layer.PassableHeight.GetSpan();
|
||
|
||
// 障碍物的高度范围
|
||
var (obstacleMinZ, obstacleMaxZ) = GetObstacleElevationRange(bbox.Min, bbox.Max, gridMap);
|
||
|
||
// 检查是否重叠
|
||
bool overlaps = !(obstacleMaxZ <= layerMinZ || obstacleMinZ > layerMaxZ);
|
||
|
||
if (overlaps && layer.IsWalkable)
|
||
{
|
||
// 重叠:标记为不可通行,但保留层结构
|
||
layer.IsWalkable = false;
|
||
cell.HeightLayers[i] = layer;
|
||
layersBlocked++;
|
||
anyLayerModified = true;
|
||
}
|
||
}
|
||
|
||
// 如果所有层都被标记为不可通行,标记整个网格为障碍物
|
||
if (anyLayerModified && !cell.HeightLayers.Any(l => l.IsWalkable))
|
||
{
|
||
cell.CellType = "障碍物";
|
||
cell.Cost = double.MaxValue;
|
||
cell.IsInChannel = false;
|
||
cell.RelatedModelItem = update.Item;
|
||
cellsFullyBlocked++;
|
||
}
|
||
|
||
gridMap.Cells[update.X, update.Y] = cell;
|
||
updatedCells++;
|
||
}
|
||
}
|
||
|
||
obstacleItems = gridUpdates.GroupBy(u => u.Item).Count();
|
||
var updateElapsed = (DateTime.Now - updateStart).TotalMilliseconds;
|
||
LogManager.Info($"[高性能障碍物处理] 阶段5完成: 更新 {updatedCells} 个网格单元,涉及 {obstacleItems} 个障碍物项,耗时: {updateElapsed:F1}ms");
|
||
LogManager.Info($"[3D高度检查] 有层的网格: {cellsWithLayers}, 检查的总层数: {totalLayersChecked}, 阻挡了 {layersBlocked} 个高度层");
|
||
LogManager.Info($"[3D高度检查] {cellsFullyBlocked} 个网格被完全阻挡,{updatedCells - cellsFullyBlocked} 个网格保留了部分层");
|
||
|
||
// 输出总体统计
|
||
var totalElapsed = (DateTime.Now - startTime).TotalMilliseconds;
|
||
LogManager.Info($"[高性能障碍物处理] 处理完成,总耗时: {totalElapsed:F1}ms");
|
||
|
||
var searchTime = (DateTime.Now - startTime).TotalMilliseconds - filterElapsed - gridCalcElapsed - updateElapsed;
|
||
LogManager.Info($"[高性能障碍物处理] 性能分解 - Search API预筛选+后处理: {searchTime:F1}ms, 过滤: {filterElapsed:F1}ms, 计算: {gridCalcElapsed:F1}ms, 更新: {updateElapsed:F1}ms");
|
||
|
||
// 性能对比统计
|
||
LogManager.Info($"[性能优化统计] 处理几何体项目: {geometryItems.Count}, 最终有效项目: {validItems.Count}, 优化比率: {1.0 - (double)validItems.Count / geometryItems.Count:P1}");
|
||
LogManager.Info($"[精确高度过滤统计] 高度检查项目: {totalCheckedItems}, 高度过滤项目: {filteredItems}, 精确过滤率: {(double)filteredItems / Math.Max(1, totalCheckedItems):P1}");
|
||
LogManager.Info($"[并行优化统计] 使用 {optimalParallelism}/{Environment.ProcessorCount} CPU核心,每核心处理 {Math.Ceiling((double)validItems.Count / optimalParallelism)} 个项目");
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[高性能障碍物处理] 处理失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 获取世界坐标范围在当前网格坐标语义下的高程区间。
|
||
/// </summary>
|
||
private (double min, double max) GetObstacleElevationRange(Point3D minPoint, Point3D maxPoint, GridMap gridMap)
|
||
{
|
||
return GetObstacleElevationRange(
|
||
minPoint.X, minPoint.Y, minPoint.Z,
|
||
maxPoint.X, maxPoint.Y, maxPoint.Z,
|
||
gridMap.CoordinateSystemType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据世界坐标最小/最大点,计算其在当前网格坐标语义下覆盖的平面网格范围。
|
||
/// </summary>
|
||
private List<(int x, int y)> CalculateGridCoverageFromWorldExtents(Point3D minPoint, Point3D maxPoint, GridMap gridMap)
|
||
{
|
||
return CalculateGridCoverageFromWorldExtents(
|
||
minPoint.X, minPoint.Y, minPoint.Z,
|
||
maxPoint.X, maxPoint.Y, maxPoint.Z,
|
||
gridMap.Origin.X, gridMap.Origin.Y, gridMap.Origin.Z,
|
||
gridMap.Width, gridMap.Height, gridMap.CellSize,
|
||
gridMap.CoordinateSystemType);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取世界坐标范围在当前宿主坐标语义下的高程区间。
|
||
/// 仅使用纯数值输入,便于单元测试验证多坐标系语义。
|
||
/// </summary>
|
||
private (double min, double max) GetObstacleElevationRange(
|
||
double minX, double minY, double minZ,
|
||
double maxX, double maxY, double maxZ,
|
||
CoordinateSystemType coordinateSystemType)
|
||
{
|
||
var adapter = new HostCoordinateAdapter(coordinateSystemType);
|
||
double elevation1 = HostPlanarGridHelper.GetElevation3(new Vector3((float)minX, (float)minY, (float)minZ), adapter);
|
||
double elevation2 = HostPlanarGridHelper.GetElevation3(new Vector3((float)maxX, (float)maxY, (float)maxZ), adapter);
|
||
return (Math.Min(elevation1, elevation2), Math.Max(elevation1, elevation2));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析边界最小点在当前宿主坐标语义下的高程值。
|
||
/// 仅修正“高程轴”的解释,不改变原有边界层业务逻辑。
|
||
/// </summary>
|
||
private static double ResolveBoundsMinElevation(
|
||
double minX, double minY, double minZ,
|
||
CoordinateSystemType coordinateSystemType)
|
||
{
|
||
var adapter = new HostCoordinateAdapter(coordinateSystemType);
|
||
return HostPlanarGridHelper.GetElevation3(new Vector3((float)minX, (float)minY, (float)minZ), adapter);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据世界坐标最小/最大点与网格元数据,计算覆盖的平面网格范围。
|
||
/// 仅使用纯数值输入,便于单元测试验证多坐标系语义。
|
||
/// </summary>
|
||
private List<(int x, int y)> CalculateGridCoverageFromWorldExtents(
|
||
double minX, double minY, double minZ,
|
||
double maxX, double maxY, double maxZ,
|
||
double originX, double originY, double originZ,
|
||
int width, int height, double cellSize,
|
||
CoordinateSystemType coordinateSystemType)
|
||
{
|
||
var adapter = new HostCoordinateAdapter(coordinateSystemType);
|
||
var minPoint = new Vector3((float)minX, (float)minY, (float)minZ);
|
||
var maxPoint = new Vector3((float)maxX, (float)maxY, (float)maxZ);
|
||
var originPoint = new Vector3((float)originX, (float)originY, (float)originZ);
|
||
|
||
var (minH1, minH2) = HostPlanarGridHelper.GetHorizontalCoords3(minPoint, adapter);
|
||
var (maxH1, maxH2) = HostPlanarGridHelper.GetHorizontalCoords3(maxPoint, adapter);
|
||
var (originH1, originH2) = HostPlanarGridHelper.GetHorizontalCoords3(originPoint, adapter);
|
||
|
||
int minGridX = (int)Math.Round((minH1 - originH1) / cellSize);
|
||
int minGridY = (int)Math.Round((minH2 - originH2) / cellSize);
|
||
int maxGridX = (int)Math.Round((maxH1 - originH1) / cellSize);
|
||
int maxGridY = (int)Math.Round((maxH2 - originH2) / cellSize);
|
||
|
||
int clampedMinX = Math.Max(0, Math.Min(minGridX, maxGridX));
|
||
int clampedMinY = Math.Max(0, Math.Min(minGridY, maxGridY));
|
||
int clampedMaxX = Math.Min(width - 1, Math.Max(minGridX, maxGridX));
|
||
int clampedMaxY = Math.Min(height - 1, Math.Max(minGridY, maxGridY));
|
||
|
||
if (clampedMaxX < clampedMinX) clampedMaxX = clampedMinX;
|
||
if (clampedMaxY < clampedMinY) clampedMaxY = clampedMinY;
|
||
|
||
var coveredCells = new List<(int x, int y)>();
|
||
for (int x = clampedMinX; x <= clampedMaxX; x++)
|
||
{
|
||
for (int y = clampedMinY; y <= clampedMaxY; y++)
|
||
{
|
||
coveredCells.Add((x, y));
|
||
}
|
||
}
|
||
|
||
return coveredCells;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 判断包围盒在当前宿主平面语义下,是否与当前网格的平面范围真实相交。
|
||
/// 这里只做诊断,不改变原有网格覆盖或裁剪逻辑。
|
||
/// </summary>
|
||
private bool IntersectsGridPlanarExtents(BoundingBox3D bbox, GridMap gridMap)
|
||
{
|
||
if (bbox == null || gridMap == null || gridMap.Bounds == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var adapter = new HostCoordinateAdapter(gridMap.CoordinateSystemType);
|
||
var hostMin = new Vector3((float)bbox.Min.X, (float)bbox.Min.Y, (float)bbox.Min.Z);
|
||
var hostMax = new Vector3((float)bbox.Max.X, (float)bbox.Max.Y, (float)bbox.Max.Z);
|
||
var gridHostMin = new Vector3((float)gridMap.Bounds.Min.X, (float)gridMap.Bounds.Min.Y, (float)gridMap.Bounds.Min.Z);
|
||
var gridHostMax = new Vector3((float)gridMap.Bounds.Max.X, (float)gridMap.Bounds.Max.Y, (float)gridMap.Bounds.Max.Z);
|
||
|
||
var (bboxMinH1, bboxMaxH1, bboxMinH2, bboxMaxH2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
|
||
var (gridMinH1, gridMaxH1, gridMinH2, gridMaxH2) = HostPlanarGridHelper.GetHorizontalRange3(gridHostMin, gridHostMax, adapter);
|
||
|
||
bool overlapH1 = !(bboxMaxH1 < gridMinH1 || bboxMinH1 > gridMaxH1);
|
||
bool overlapH2 = !(bboxMaxH2 < gridMinH2 || bboxMinH2 > gridMaxH2);
|
||
return overlapH1 && overlapH2;
|
||
}
|
||
|
||
private static (double minH1, double maxH1, double minH2, double maxH2) GetPlanarRange(
|
||
Point3D minPoint,
|
||
Point3D maxPoint,
|
||
CoordinateSystemType coordinateSystemType)
|
||
{
|
||
var adapter = new HostCoordinateAdapter(coordinateSystemType);
|
||
var hostMin = new Vector3((float)minPoint.X, (float)minPoint.Y, (float)minPoint.Z);
|
||
var hostMax = new Vector3((float)maxPoint.X, (float)maxPoint.Y, (float)maxPoint.Z);
|
||
return HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算包围盒在网格上覆盖的单元坐标
|
||
/// 严格按照"网格坐标代表左下角"的设计原则进行边界处理
|
||
/// </summary>
|
||
/// <param name="bbox">包围盒</param>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <returns>覆盖的网格单元坐标列表</returns>
|
||
private List<(int x, int y)> CalculateBoundingBoxGridCoverage(BoundingBox3D bbox, GridMap gridMap)
|
||
{
|
||
try
|
||
{
|
||
return CalculateGridCoverageFromWorldExtents(bbox.Min, bbox.Max, gridMap);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Debug($"[包围盒覆盖计算] 计算失败: {ex.Message}");
|
||
return new List<(int x, int y)>();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据门包围盒、限宽和当前宿主平面语义,计算门开口在网格上的覆盖范围。
|
||
/// 关键约束:
|
||
/// - 仅替换“哪两个轴组成水平平面”的解释,不改变历史限宽业务规则。
|
||
/// - 门宽仍取平面上较大的那一维;限宽仍只裁剪门宽方向。
|
||
/// </summary>
|
||
private List<(int x, int y)> CalculateDoorOpeningCoverageFromWorldExtents(
|
||
double minX, double minY, double minZ,
|
||
double maxX, double maxY, double maxZ,
|
||
double limitWidthInModelUnits,
|
||
double originX, double originY, double originZ,
|
||
int width, int height, double cellSize,
|
||
CoordinateSystemType coordinateSystemType)
|
||
{
|
||
var adapter = new HostCoordinateAdapter(coordinateSystemType);
|
||
var hostMin = new Vector3((float)minX, (float)minY, (float)minZ);
|
||
var hostMax = new Vector3((float)maxX, (float)maxY, (float)maxZ);
|
||
var hostOrigin = new Vector3((float)originX, (float)originY, (float)originZ);
|
||
var (minH1, maxH1, minH2, maxH2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
|
||
var (originH1, originH2) = HostPlanarGridHelper.GetHorizontalCoords3(hostOrigin, adapter);
|
||
|
||
double planarWidth = maxH1 - minH1;
|
||
double planarDepth = maxH2 - minH2;
|
||
bool widthAlongH1 = planarWidth > planarDepth;
|
||
double doorWidth = Math.Max(planarWidth, planarDepth);
|
||
double effectiveWidth = Math.Min(limitWidthInModelUnits, doorWidth);
|
||
|
||
double openingMinH1;
|
||
double openingMaxH1;
|
||
double openingMinH2;
|
||
double openingMaxH2;
|
||
|
||
if (widthAlongH1)
|
||
{
|
||
double centerH1 = (minH1 + maxH1) / 2.0;
|
||
double halfWidth = effectiveWidth / 2.0;
|
||
openingMinH1 = centerH1 - halfWidth;
|
||
openingMaxH1 = centerH1 + halfWidth;
|
||
openingMinH2 = minH2;
|
||
openingMaxH2 = maxH2;
|
||
}
|
||
else
|
||
{
|
||
double centerH2 = (minH2 + maxH2) / 2.0;
|
||
double halfWidth = effectiveWidth / 2.0;
|
||
openingMinH1 = minH1;
|
||
openingMaxH1 = maxH1;
|
||
openingMinH2 = centerH2 - halfWidth;
|
||
openingMaxH2 = centerH2 + halfWidth;
|
||
}
|
||
|
||
int minGridX = (int)Math.Round((openingMinH1 - originH1) / cellSize);
|
||
int minGridY = (int)Math.Round((openingMinH2 - originH2) / cellSize);
|
||
int maxGridX = (int)Math.Round((openingMaxH1 - originH1) / cellSize);
|
||
int maxGridY = (int)Math.Round((openingMaxH2 - originH2) / cellSize);
|
||
|
||
int clampedMinX = Math.Max(0, Math.Min(minGridX, maxGridX));
|
||
int clampedMinY = Math.Max(0, Math.Min(minGridY, maxGridY));
|
||
int clampedMaxX = Math.Min(width - 1, Math.Max(minGridX, maxGridX));
|
||
int clampedMaxY = Math.Min(height - 1, Math.Max(minGridY, maxGridY));
|
||
|
||
if (clampedMaxX < clampedMinX) clampedMaxX = clampedMinX;
|
||
if (clampedMaxY < clampedMinY) clampedMaxY = clampedMinY;
|
||
|
||
var coveredCells = new List<(int x, int y)>();
|
||
for (int x = clampedMinX; x <= clampedMaxX; x++)
|
||
{
|
||
for (int y = clampedMinY; y <= clampedMaxY; y++)
|
||
{
|
||
coveredCells.Add((x, y));
|
||
}
|
||
}
|
||
|
||
return coveredCells;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算门开口区域在网格上覆盖的单元坐标(考虑限宽)
|
||
/// </summary>
|
||
/// <param name="doorItem">门模型项</param>
|
||
/// <param name="bbox">门的包围盒</param>
|
||
/// <param name="gridMap">网格地图</param>
|
||
/// <returns>开口区域覆盖的网格单元坐标列表</returns>
|
||
private List<(int x, int y)> CalculateDoorOpeningCoverage(ModelItem doorItem, BoundingBox3D bbox, GridMap gridMap)
|
||
{
|
||
var coveredCells = new List<(int x, int y)>();
|
||
|
||
try
|
||
{
|
||
// 1. 获取门的限宽属性
|
||
string widthLimitStr = CategoryAttributeManager.GetLogisticsPropertyValue(doorItem, CategoryAttributeManager.LogisticsProperties.WIDTH_LIMIT);
|
||
|
||
if (string.IsNullOrEmpty(widthLimitStr))
|
||
{
|
||
LogManager.Warning($"【门开口计算】门元素 {doorItem.DisplayName} 没有设置限宽属性,跳过处理");
|
||
return coveredCells;
|
||
}
|
||
|
||
// 2. 解析限宽属性值(支持带单位的格式)
|
||
double limitWidthMeters = CategoryAttributeManager.ParseLogisticsLimitValue(widthLimitStr, "限宽");
|
||
|
||
if (limitWidthMeters <= 0)
|
||
{
|
||
LogManager.Warning($"【门开口计算】门元素 {doorItem.DisplayName} 限宽属性解析失败: {widthLimitStr}");
|
||
return coveredCells;
|
||
}
|
||
|
||
// 3. 转换限宽到模型单位
|
||
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
|
||
double limitWidthInModelUnits = limitWidthMeters * metersToModelUnits;
|
||
|
||
var adapter = new HostCoordinateAdapter(gridMap.CoordinateSystemType);
|
||
var hostMin = new Vector3((float)bbox.Min.X, (float)bbox.Min.Y, (float)bbox.Min.Z);
|
||
var hostMax = new Vector3((float)bbox.Max.X, (float)bbox.Max.Y, (float)bbox.Max.Z);
|
||
var (minH1, maxH1, minH2, maxH2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
|
||
double planarWidth = maxH1 - minH1;
|
||
double planarDepth = maxH2 - minH2;
|
||
double doorWidth = Math.Max(planarWidth, planarDepth);
|
||
|
||
coveredCells = CalculateDoorOpeningCoverageFromWorldExtents(
|
||
bbox.Min.X, bbox.Min.Y, bbox.Min.Z,
|
||
bbox.Max.X, bbox.Max.Y, bbox.Max.Z,
|
||
limitWidthInModelUnits,
|
||
gridMap.Origin.X, gridMap.Origin.Y, gridMap.Origin.Z,
|
||
gridMap.Width, gridMap.Height, gridMap.CellSize,
|
||
gridMap.CoordinateSystemType);
|
||
|
||
if (limitWidthInModelUnits > doorWidth)
|
||
{
|
||
LogManager.Info($"【门开口计算】{doorItem.DisplayName} 限宽({limitWidthMeters}m)超过门宽({doorWidth / metersToModelUnits:F2}m),按门宽处理");
|
||
}
|
||
|
||
if (coveredCells.Any())
|
||
{
|
||
int gridMinX = coveredCells.Min(cell => cell.x);
|
||
int gridMinY = coveredCells.Min(cell => cell.y);
|
||
int gridMaxX = coveredCells.Max(cell => cell.x);
|
||
int gridMaxY = coveredCells.Max(cell => cell.y);
|
||
LogManager.Info($"【门开口计算】{doorItem.DisplayName} 成功处理 - 门宽: {doorWidth / metersToModelUnits:F2}m, 限宽: {limitWidthMeters}m, 开口覆盖: {coveredCells.Count}个网格 (范围: [{gridMinX},{gridMinY}]-[{gridMaxX},{gridMaxY}])");
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"【门开口计算】计算失败: {doorItem.DisplayName}, 错误: {ex.Message}");
|
||
// 出错时返回空列表,不影响其他门的处理
|
||
}
|
||
|
||
return coveredCells;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查模型项是否是通道项的子项
|
||
/// </summary>
|
||
/// <param name="item">要检查的模型项</param>
|
||
/// <param name="traversableItemsSet">通道项集合</param>
|
||
/// <returns>是否是通道项的子项</returns>
|
||
private bool IsChildOfChannelItems(ModelItem item, HashSet<ModelItem> traversableItemsSet)
|
||
{
|
||
try
|
||
{
|
||
var parent = item.Parent;
|
||
while (parent != null)
|
||
{
|
||
if (traversableItemsSet.Contains(parent))
|
||
{
|
||
return true;
|
||
}
|
||
parent = parent.Parent;
|
||
}
|
||
return false;
|
||
}
|
||
catch
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
#region 性能优化数据结构
|
||
|
||
/// <summary>
|
||
/// 模型项缓存属性,存储昂贵API调用的结果
|
||
/// </summary>
|
||
internal class ItemProperties
|
||
{
|
||
public bool HasGeometry { get; set; }
|
||
public bool IsContainer { get; set; }
|
||
public BoundingBox3D BoundingBox { get; set; }
|
||
public bool IsChannelItem { get; set; }
|
||
public bool IsChildOfChannel { get; set; }
|
||
public bool IsIrrelevantItem { get; set; }
|
||
public bool IsInScanHeightRange { get; set; }
|
||
}
|
||
|
||
/// <summary>
|
||
/// 网格更新操作,用于并行计算后的批量更新
|
||
/// </summary>
|
||
internal class GridUpdate
|
||
{
|
||
public ModelItem Item { get; set; }
|
||
public int X { get; set; }
|
||
public int Y { get; set; }
|
||
public BoundingBox3D BoundingBox { get; set; }
|
||
}
|
||
|
||
#endregion
|
||
}
|