NavisworksTransport/src/PathPlanning/GridMapGenerator.cs

1576 lines
80 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.Core;
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
{
private readonly CategoryAttributeManager _categoryManager;
private readonly ChannelBasedGridBuilder _channelBuilder;
private VerticalScanProcessor _verticalScanner;
/// <summary>
/// 构造函数
/// </summary>
public GridMapGenerator()
{
_categoryManager = new CategoryAttributeManager();
_channelBuilder = new ChannelBasedGridBuilder();
_verticalScanner = null;
}
/// <summary>
/// 从BIM模型生成网格地图
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="bounds">规划区域边界</param>
/// <param name="cellSize">网格单元格大小(米)</param>
/// <param name="vehicleRadius">车辆半径(用于障碍物膨胀)</param>
/// <param name="safetyMargin">安全间隙(额外的膨胀间距,米)</param>
/// <param name="planningStartPoint">规划起点(用于确定楼层)</param>
/// <param name="planningEndPoint">规划终点(用于确定楼层)</param>
/// <param name="mode">网格生成模式默认为2.5D模式</param>
/// <param name="vehicleHeight">车辆高度(用于垂直通行检查)</param>
/// <returns>生成的网格地图</returns>
public GridMap GenerateFromBIM(Document document, BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, GridGenerationMode mode, double vehicleHeight)
{
try
{
LogManager.Info($"[网格生成器] 使用模式: {mode}");
// 根据模式选择不同的生成策略
switch (mode)
{
case GridGenerationMode.ChannelBased2_5D:
return GenerateChannelBased2_5D(document, bounds, cellSize, vehicleRadius, safetyMargin, planningStartPoint, planningEndPoint, vehicleHeight);
case GridGenerationMode.BoundingBoxBased2_5D:
return GenerateBoundingBoxBased2_5D(document, bounds, cellSize, vehicleRadius, safetyMargin, planningStartPoint, planningEndPoint, vehicleHeight);
default:
return GenerateBoundingBoxBased2_5D(document, bounds, cellSize, vehicleRadius, safetyMargin, planningStartPoint, planningEndPoint, vehicleHeight);
}
}
catch (Exception ex)
{
LogManager.Error($"生成网格地图时发生错误: {ex.Message}");
throw new AutoPathPlanningException($"网格地图生成失败: {ex.Message}", ex);
}
}
/// <summary>
/// 通道基础2.5D模式的网格生成
/// 使用通道优先策略和垂直扫描处理器
///
/// 术语说明:
/// - "通道"在此指所有可通行结构(楼板、走廊、过道等)
/// - 对于楼板类通道,车辆在其顶面行驶
/// - 生成的网格地图用于车辆路径规划
/// </summary>
private GridMap GenerateChannelBased2_5D(Document document, BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double vehicleHeight)
{
try
{
LogManager.Info("[通道2.5D模式] 开始生成网格地图");
// 重要:将米制网格大小转换为模型单位
double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double cellSizeInModelUnits = cellSize * metersToModelUnitsConversionFactor;
LogManager.Info($"[通道2.5D模式] 网格大小单位转换: {cellSize}米 → {cellSizeInModelUnits:F2}模型单位 (转换系数: {metersToModelUnitsConversionFactor:F2})");
// 1. 使用通道构建器构建基础通道覆盖网格
LogManager.Info("[通道2.5D模式] 步骤1: 构建通道覆盖网格");
var channelCoverage = _channelBuilder.BuildChannelCoverage(cellSizeInModelUnits, document);
LogManager.Info($"[通道2.5D模式] 通道覆盖构建完成: {channelCoverage.GetStatistics()}");
LogManager.Info($"[阶段1完成] 通道覆盖网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 2. 构建垂直扫描处理器的空间索引
LogManager.Info("[通道2.5D模式] 步骤2: 构建空间哈希索引");
double spatialHashMultiplier = 10.0; // 可以根据模型密度调整高密设为6低密设为10
_verticalScanner = new VerticalScanProcessor(cellSize * spatialHashMultiplier);
var allItems = document.Models.RootItemDescendantsAndSelf;
_verticalScanner.BuildSpatialHashIndex(allItems, bounds, channelCoverage.ChannelItems);
// 3. 生成网格点坐标用于垂直扫描
LogManager.Info("[通道2.5D模式] 步骤3: 生成网格扫描点");
var gridPoints = GenerateGridPoints(channelCoverage.GridMap);
LogManager.Info($"[阶段3完成] 生成 {gridPoints.Count} 个扫描点");
// 4. 并行执行垂直扫描计算2.5D高度区间
LogManager.Info("[通道2.5D模式] 步骤4: 执行并行垂直扫描");
double scanHeight = vehicleHeight + safetyMargin; // 扫描高度 = 车辆高度 + 安全间隙
var heightIntervals = _verticalScanner.ParallelScanHeightIntervals(
gridPoints, scanHeight, vehicleHeight);
LogManager.Info($"[阶段4完成] 垂直扫描结果: 扫描了{gridPoints.Count}个通道点,生成了{heightIntervals.Count}个高度区间数据");
// 5. 将高度区间信息集成到网格地图中
LogManager.Info("[通道2.5D模式] 步骤5: 集成高度信息到网格");
// 添加集成前的验证日志
var preIntegrationStats = channelCoverage.GridMap.GetStatistics();
LogManager.Info($"[验证] 高度信息集成前网格统计: {preIntegrationStats}");
IntegrateHeightIntervalsToGrid(channelCoverage.GridMap, heightIntervals);
// 添加集成后的验证日志
var postIntegrationStats = channelCoverage.GridMap.GetStatistics();
LogManager.Info($"[阶段5完成] 高度信息集成后网格统计: {postIntegrationStats}");
// 验证网格统计一致性
ValidateGridStatisticsConsistency(channelCoverage.GridMap, "高度信息集成后");
// 6. 应用车辆尺寸膨胀(如果需要)
if (vehicleRadius > 0 || safetyMargin > 0)
{
LogManager.Info("[通道2.5D模式] 步骤6: 应用车辆膨胀");
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double totalInflationRadius = (vehicleRadius + safetyMargin) * metersToModelUnits;
ApplyVehicleInflation(channelCoverage.GridMap, totalInflationRadius);
LogManager.Info($"[通道2.5D模式] 车辆膨胀完成,膨胀半径: {totalInflationRadius:F2}模型单位");
LogManager.Info($"[阶段6完成] 车辆膨胀后网格统计: {channelCoverage.GridMap.GetStatistics()}");
}
// 注意:边界膨胀已集成到车辆膨胀中,无需单独处理
// 8. 设置规划起点和终点信息
if (planningStartPoint != default(Point3D) && planningEndPoint != default(Point3D))
{
channelCoverage.GridMap.PlanningStartPoint = planningStartPoint;
channelCoverage.GridMap.PlanningEndPoint = planningEndPoint;
channelCoverage.GridMap.HasPlanningPoints = true;
// 注意网格的Z坐标已在通道生成时设置为实际通道高度无需重新计算
}
// 🔥 关键修复将通道数据设置到GridMap中供后续PathPlanningManager使用
if (channelCoverage.ChannelItems != null && channelCoverage.ChannelItems.Any())
{
channelCoverage.GridMap.ChannelItems = new List<ModelItem>(channelCoverage.ChannelItems);
LogManager.Info($"[通道2.5D模式] 已将 {channelCoverage.ChannelItems.Count} 个通道数据设置到GridMap中");
}
LogManager.Info($"[通道2.5D模式] 网格地图生成完成: {channelCoverage.GridMap.GetStatistics()}");
return channelCoverage.GridMap;
}
catch (Exception ex)
{
LogManager.Error($"[通道2.5D模式] 生成失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 基于包围盒遍历的网格地图生成方法(优化版)
/// 使用简化的包围盒检测替代空间索引+垂直扫描,提升性能
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="bounds">扫描边界</param>
/// <param name="cellSize">网格单元大小(米)</param>
/// <param name="vehicleRadius">车辆半径(米)</param>
/// <param name="safetyMargin">安全间隙(米)</param>
/// <param name="planningStartPoint">规划起点</param>
/// <param name="planningEndPoint">规划终点</param>
/// <param name="vehicleHeight">车辆高度(米)</param>
/// <returns>生成的网格地图</returns>
private GridMap GenerateBoundingBoxBased2_5D(Document document, BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double vehicleHeight)
{
try
{
LogManager.Info("【包围盒2.5D模式】开始生成网格地图");
var startTime = DateTime.Now;
// 重要:将米制网格大小转换为模型单位
double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double cellSizeInModelUnits = cellSize * metersToModelUnitsConversionFactor;
LogManager.Info($"【包围盒2.5D模式】网格大小单位转换: {cellSize}米 → {cellSizeInModelUnits:F2}模型单位 (转换系数: {metersToModelUnitsConversionFactor:F2})");
// 1. 使用通道构建器构建基础通道覆盖网格
LogManager.Info("【包围盒2.5D模式】步骤1: 构建通道覆盖网格");
var channelCoverage = _channelBuilder.BuildChannelCoverage(cellSizeInModelUnits, document);
LogManager.Info($"【包围盒2.5D模式】通道覆盖构建完成: {channelCoverage.GetStatistics()}");
LogManager.Info($"【阶段1完成】通道覆盖网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 1.5. 使用智能收集策略收集所有通道相关节点
LogManager.Info("【包围盒2.5D模式】步骤1.5: 智能收集通道相关节点");
// 直接获取所有可通行的物流模型项
var allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems(document);
LogManager.Info($"【包围盒2.5D模式】直接获取到 {allChannelItems.Count} 个可通行物流元素");
// 智能收集通道相关节点(包括父节点、同级节点等)
var channelRelatedItems = CollectChannelRelatedItems(allChannelItems.ToList());
LogManager.Info($"【包围盒2.5D模式】智能收集到 {channelRelatedItems.Count} 个通道相关节点");
// 2. 直接遍历处理障碍物(包围盒检测) - 使用高性能优化版本
LogManager.Info("【包围盒2.5D模式】步骤2: 高性能包围盒遍历处理障碍物");
double scanHeight = vehicleHeight + safetyMargin; // 扫描高度 = 车辆高度 + 安全间隙
ProcessObstaclesWithBoundingBoxOptimized(document, channelCoverage.GridMap, channelRelatedItems, scanHeight);
LogManager.Info($"【阶段2完成】障碍物处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 2.5. 单独处理门元素,设置为可通行
LogManager.Info("【包围盒2.5D模式】步骤2.5: 处理门元素");
ProcessDoorElements(channelCoverage.GridMap, allChannelItems);
LogManager.Info($"【阶段2.5完成】门元素处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 2.6. 为通道网格设置PassableHeights确保高度约束检查
LogManager.Info("【包围盒2.5D模式】步骤2.6: 为通道网格设置高度约束");
SetChannelPassableHeights(channelCoverage.GridMap, scanHeight);
LogManager.Info($"【阶段2.6完成】通道高度约束设置后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 3. 应用车辆尺寸膨胀(如果需要)
if (vehicleRadius > 0 || safetyMargin > 0)
{
LogManager.Info("【包围盒2.5D模式】步骤3: 应用车辆膨胀");
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double totalInflationRadius = (vehicleRadius + safetyMargin) * metersToModelUnits;
ApplyVehicleInflation(channelCoverage.GridMap, totalInflationRadius);
LogManager.Info($"【包围盒2.5D模式】车辆膨胀完成,膨胀半径: {totalInflationRadius: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;
// 注意网格的Z坐标已在通道生成时设置为实际通道高度无需重新计算
}
// 将通道数据设置到GridMap中供后续PathPlanningManager使用
if (channelRelatedItems != null && channelRelatedItems.Any())
{
channelCoverage.GridMap.ChannelItems = channelRelatedItems.ToList();
LogManager.Info($"【包围盒2.5D模式】已将 {channelRelatedItems.Count} 个通道数据设置到GridMap中");
}
else if (channelCoverage.ChannelItems != null && channelCoverage.ChannelItems.Any())
{
channelCoverage.GridMap.ChannelItems = new List<ModelItem>(channelCoverage.ChannelItems);
LogManager.Info($"【包围盒2.5D模式】已将 {channelCoverage.ChannelItems.Count} 个通道数据设置到GridMap中");
}
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
LogManager.Info($"【包围盒2.5D模式】网格地图生成完成: {channelCoverage.GridMap.GetStatistics()}");
LogManager.Info($"【包围盒2.5D模式】总耗时: {elapsed:F1}ms");
return channelCoverage.GridMap;
}
catch (Exception ex)
{
LogManager.Error($"【包围盒2.5D模式】生成失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 处理门元素,将其设置为可通行网格
/// </summary>
private void ProcessDoorElements(GridMap gridMap, ICollection<ModelItem> allChannelItems)
{
try
{
// 过滤出门类型的元素
var doorItems = FilterDoorItems(allChannelItems);
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, "限高");
// 计算门的实际物理高度
double actualDoorHeight = bbox.Max.Z - bbox.Min.Z;
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}");
}
// 获取门底部的Z坐标
double doorBottomZ = bbox.Min.Z;
// 设置开口区域的网格为可通行的门类型
foreach (var (x, y) in coveredCells)
{
var gridPos = new GridPoint2D(x, y);
// 设置门网格cost传0让SetCell自动调用GetCost()计算门的默认cost是1.2
gridMap.SetCell(gridPos, true, 0, CategoryAttributeManager.LogisticsElementType.);
// 由于GridCell是结构体需要重新获取、修改、再设置回去
var cell = gridMap.Cells[x, y];
// 更新门网格的Z坐标为门底部位置
var worldPos = gridMap.GridToWorld3D(gridPos, doorBottomZ);
cell.WorldPosition = worldPos;
// 将门的高度范围存储在PassableHeights中供后续使用使用相对坐标
if (doorHeight > 0)
{
cell.PassableHeights = new List<HeightInterval>
{
new HeightInterval(0, doorHeight)
};
}
// 将修改后的结构体设置回数组
gridMap.Cells[x, y] = cell;
}
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="scanHeight">扫描高度(车辆高度+安全间隙)</param>
private void SetChannelPassableHeights(GridMap gridMap, double scanHeight)
{
// scanHeight是米需要转换为模型单位
double metersToModelUnits = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double scanHeightInModelUnits = scanHeight * metersToModelUnits;
LogManager.Info($"【通道高度设置】开始为通道网格设置高度约束,扫描高度: {scanHeight:F2}米 ({scanHeightInModelUnits:F2}模型单位)");
int processedCount = 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.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsWalkable)
{
cell.PassableHeights = new List<HeightInterval>
{
new HeightInterval(0, scanHeightInModelUnits)
};
gridMap.Cells[x, y] = cell;
processedCount++;
}
}
}
LogManager.Info($"【通道高度设置】完成,已处理 {processedCount} 个通道网格");
}
/// <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.GetLogisticsElementType(item);
if (logisticsType == CategoryAttributeManager.LogisticsElementType.)
{
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 == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
{
// 使用网格的实际Z坐标
var worldPos = gridMap.GridToWorld3D(new GridPoint2D(x, y), cell.WorldPosition.Z);
// 重要修复:使用通道顶面作为垂直扫描的起点,而不是底面
// 从通道构建器的日志可知通道总边界MaxZ = 38.8,这是正确的扫描起点
double channelTopZ = GetChannelTopZ(worldPos, gridMap);
points.Add(new Point3D(worldPos.X, worldPos.Y, channelTopZ));
}
}
}
return points;
}
/// <summary>
/// 获取通道顶面Z坐标作为垂直扫描起点
/// </summary>
/// <param name="worldPos">世界坐标位置</param>
/// <param name="gridMap">网格地图</param>
/// <returns>通道顶面Z坐标</returns>
private double GetChannelTopZ(Point3D worldPos, GridMap gridMap)
{
try
{
// 从通道构建器的日志信息可知:
// 通道总边界: [-242.7,-62.3,35.4] - [10.8,70.9,38.8]
// 通道顶面Z坐标为38.8,这应该作为垂直扫描的起点
// 方法1: 优先使用网格边界的MaxZ通道顶面
double channelTopZ = gridMap.Bounds.Max.Z;
// 方法2: 备选方案 - 如果网格边界不可用,使用固定的通道高度
if (channelTopZ <= gridMap.Bounds.Min.Z)
{
// 基于日志观察到的通道Z范围 [35.4, 38.8],使用顶面
channelTopZ = worldPos.Z + 3.4; // 假设通道高度为3.4米
LogManager.Debug($"[通道顶面计算] 使用备选方案: 底面{worldPos.Z:F2} + 3.4m = 顶面{channelTopZ:F2}");
}
// LogManager.Debug($"[通道顶面计算] 世界位置({worldPos.X:F2}, {worldPos.Y:F2}) -> 通道顶面Z={channelTopZ:F2}"); // 调试完成,日志已删除
return channelTopZ;
}
catch (Exception ex)
{
LogManager.Warning($"[通道顶面计算] 计算失败: {ex.Message}使用世界位置Z坐标");
return worldPos.Z;
}
}
/// <summary>
/// 将高度区间信息集成到网格地图中
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="heightIntervals">高度区间数据</param>
/// <summary>
/// 将高度区间信息集成到网格地图中
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="heightIntervals">高度区间数据</param>
private void IntegrateHeightIntervalsToGrid(GridMap gridMap, Dictionary<Point3D, List<HeightInterval>> heightIntervals)
{
int updatedCells = 0;
int channelCellsWithHeightData = 0;
int channelCellsBecomingUnwalkable = 0;
foreach (var kvp in heightIntervals)
{
var point = kvp.Key;
var intervals = kvp.Value;
// 将世界坐标转换为网格坐标
var gridPos = gridMap.WorldToGrid(new Point3D(point.X, point.Y, 0));
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];
if (cell.PassableHeights == null)
{
cell.PassableHeights = new List<HeightInterval>();
}
cell.PassableHeights.Clear();
cell.PassableHeights.AddRange(intervals);
// 关键修复:只处理通道单元格的高度信息集成
// 非通道单元格不应该通过高度扫描而改变其类型或可通行性
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
{
channelCellsWithHeightData++;
if (intervals.Any())
{
// 通道区域有足够净空高度,设置为可通行
// 🔥 关键修复使用SetCell方法正确更新网格统计
var cellPos = new GridPoint2D(gridX, gridY);
gridMap.SetCell(cellPos, true, 1.0, CategoryAttributeManager.LogisticsElementType.);
// 设置世界坐标和高度信息
var updatedCell = gridMap.Cells[gridX, gridY];
updatedCell.WorldPosition = point;
updatedCell.PassableHeights = new List<HeightInterval>(intervals);
updatedCell.IsInChannel = true;
gridMap.Cells[gridX, gridY] = updatedCell;
}
else
{
// 通道区域但净空高度不足,标记为不可通行但保持通道类型
var cellPos = new GridPoint2D(gridX, gridY);
gridMap.SetCell(cellPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
// 设置世界坐标和高度信息
var updatedCell = gridMap.Cells[gridX, gridY];
updatedCell.WorldPosition = point;
updatedCell.PassableHeights = new List<HeightInterval>(intervals);
updatedCell.IsInChannel = true;
gridMap.Cells[gridX, gridY] = updatedCell;
channelCellsBecomingUnwalkable++;
}
}
else
{
// 非通道单元格:不应该通过垂直扫描改变其状态
// 这种情况理论上不应该发生,因为只对通道单元格进行扫描
LogManager.Warning($"[通道2.5D模式] 警告:非通道单元格 ({gridX},{gridY}) 收到高度数据,类型:{cell.CellType}, IsInChannel{cell.IsInChannel}");
// 仍然更新高度信息,但保持原有状态
cell.WorldPosition = point;
cell.PassableHeights = new List<HeightInterval>(intervals);
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.IsWalkable)
{
walkableCount++;
switch (cell.CellType)
{
case CategoryAttributeManager.LogisticsElementType.:
channelCount++;
break;
case CategoryAttributeManager.LogisticsElementType.:
openSpaceCount++;
break;
case CategoryAttributeManager.LogisticsElementType.:
doorCount++;
break;
}
}
else
{
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
{
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="vehicleRadius">车辆半径</param>
public void ApplyVehicleInflation(GridMap gridMap, double vehicleRadius)
{
if (vehicleRadius <= 0) return;
try
{
// 计算膨胀半径(网格单元格数)
int inflationRadius = (int)Math.Ceiling(vehicleRadius / gridMap.CellSize);
LogManager.Info($"[高效膨胀] 膨胀半径: {inflationRadius}个网格单元");
// 使用高效的距离变换算法
ApplyVehicleInflationOptimized(gridMap, inflationRadius);
}
catch (Exception ex)
{
LogManager.Error($"[高效膨胀] 应用车辆膨胀时发生错误: {ex.Message}");
throw new AutoPathPlanningException($"车辆膨胀失败: {ex.Message}", ex);
}
}
/// <summary>
/// 优化的车辆膨胀算法 - 使用距离变换方法
/// 时间复杂度: O(n*m) 而不是 O(n*m*r^2)
/// </summary>
private void ApplyVehicleInflationOptimized(GridMap gridMap, int inflationRadius)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
LogManager.Info($"【高效膨胀】使用距离变换算法,网格大小: {gridMap.Width}x{gridMap.Height}");
// 创建距离矩阵
var distanceMap = new int[gridMap.Width, gridMap.Height];
// 初始化距离矩阵 - 膨胀源点障碍物、Unknown网格
int obstacleCount = 0;
int unknownCount = 0;
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
var cell = gridMap.Cells[x, y];
// 1. 障碍物作为膨胀源点
if (!cell.IsWalkable)
{
distanceMap[x, y] = 0;
obstacleCount++;
}
// 2. Unknown网格作为膨胀源点
else if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
{
distanceMap[x, y] = 0;
unknownCount++;
}
// 3. 其他可通行区域(包括边界通道)距离为无穷大
else
{
distanceMap[x, y] = int.MaxValue;
}
}
}
LogManager.Info($"【统一膨胀】膨胀源点统计 - 障碍物: {obstacleCount}, Unknown: {unknownCount}");
LogManager.Info($"【高效膨胀】距离矩阵初始化完成,耗时: {stopwatch.ElapsedMilliseconds}ms");
// 正向扫描(从左上到右下)
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
if (distanceMap[x, y] != 0 && distanceMap[x, y] != int.MaxValue)
{
// 检查左侧和上方的邻居
int minDist = distanceMap[x, y];
if (x > 0 && distanceMap[x - 1, y] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1);
if (y > 0 && distanceMap[x, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1);
if (x > 0 && y > 0 && distanceMap[x - 1, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + 1);
if (x > 0 && y < gridMap.Height - 1 && distanceMap[x - 1, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y + 1] + 1);
distanceMap[x, y] = minDist;
}
else if (distanceMap[x, y] == int.MaxValue)
{
// 对于可通行区域,计算到最近障碍物的距离
int minDist = int.MaxValue;
if (x > 0 && distanceMap[x - 1, y] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1);
if (y > 0 && distanceMap[x, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1);
if (x > 0 && y > 0 && distanceMap[x - 1, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + 1);
if (x > 0 && y < gridMap.Height - 1 && distanceMap[x - 1, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x - 1, y + 1] + 1);
if (minDist != int.MaxValue)
distanceMap[x, y] = minDist;
}
}
}
LogManager.Info($"【高效膨胀】正向扫描完成,耗时: {stopwatch.ElapsedMilliseconds}ms");
// 反向扫描(从右下到左上)
for (int x = gridMap.Width - 1; x >= 0; x--)
{
for (int y = gridMap.Height - 1; y >= 0; y--)
{
if (distanceMap[x, y] != 0 && distanceMap[x, y] != int.MaxValue)
{
// 检查右侧和下方的邻居
int minDist = distanceMap[x, y];
if (x < gridMap.Width - 1 && distanceMap[x + 1, y] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1);
if (y < gridMap.Height - 1 && distanceMap[x, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1);
if (x < gridMap.Width - 1 && y < gridMap.Height - 1 && distanceMap[x + 1, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + 1);
if (x < gridMap.Width - 1 && y > 0 && distanceMap[x + 1, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y - 1] + 1);
distanceMap[x, y] = minDist;
}
else if (distanceMap[x, y] == int.MaxValue)
{
// 对于可通行区域,计算到最近障碍物的距离
int minDist = int.MaxValue;
if (x < gridMap.Width - 1 && distanceMap[x + 1, y] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1);
if (y < gridMap.Height - 1 && distanceMap[x, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1);
if (x < gridMap.Width - 1 && y < gridMap.Height - 1 && distanceMap[x + 1, y + 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + 1);
if (x < gridMap.Width - 1 && y > 0 && distanceMap[x + 1, y - 1] != int.MaxValue)
minDist = Math.Min(minDist, distanceMap[x + 1, y - 1] + 1);
if (minDist != int.MaxValue)
distanceMap[x, y] = minDist;
}
}
}
LogManager.Info($"【高效膨胀】反向扫描完成,耗时: {stopwatch.ElapsedMilliseconds}ms");
// 应用膨胀结果
int inflatedCells = 0;
int processedCells = 0;
int totalCells = gridMap.Width * gridMap.Height;
int skippedCells = 0;
int protectedDoorCells = 0;
LogManager.Info($"【高效膨胀】开始应用膨胀结果,总单元格数: {totalCells}");
try
{
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
processedCells++;
// 每处理50万个单元格输出一次进度
if (processedCells % 500000 == 0)
{
double progress = (double)processedCells / totalCells * 100;
LogManager.Info($"【高效膨胀】应用进度: {progress:F1}% ({processedCells}/{totalCells})");
}
var gridPos = new GridPoint2D(x, y);
var cell = gridMap.Cells[x, y];
// 只有满足以下所有条件才进行膨胀:
// 1. 当前位置原本是可通行的
// 2. 距离值不是无穷大(有有效的距离计算)
// 3. 距离小于等于膨胀半径
// 4. 距离值大于0不是原始障碍物
// 5. 不是门类型(门保护)
if (gridMap.IsWalkable(gridPos) &&
distanceMap[x, y] != int.MaxValue &&
distanceMap[x, y] > 0 &&
distanceMap[x, y] <= inflationRadius)
{
// 门元素保护:跳过门类型的网格,不将其膨胀为障碍物
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
{
protectedDoorCells++;
// 跳过门网格,保持其可通行状态
continue;
}
gridMap.SetCell(gridPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
inflatedCells++;
}
else if (distanceMap[x, y] == int.MaxValue || distanceMap[x, y] > inflationRadius)
{
skippedCells++;
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"【高效膨胀】应用膨胀结果时发生错误: {ex.Message},已处理{processedCells}/{totalCells}个单元格");
throw;
}
// 单独处理包围盒边界膨胀
int boundaryInflatedCells = ApplyBoundaryInflationPost(gridMap, inflationRadius);
stopwatch.Stop();
LogManager.Info($"【高效膨胀】车辆膨胀完成: 膨胀半径={inflationRadius}格, 新增障碍物={inflatedCells}个, 边界膨胀={boundaryInflatedCells}个, 跳过={skippedCells}个, 保护门网格={protectedDoorCells}个, 总耗时={stopwatch.ElapsedMilliseconds}ms");
}
/// <summary>
/// 在距离变换膨胀完成后,额外处理包围盒边界膨胀
/// 从包围盒边界向内膨胀指定层数,确保车辆与边界保持安全距离
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="inflationRadius">膨胀半径(网格层数)</param>
/// <returns>被膨胀的边界网格数量</returns>
private int ApplyBoundaryInflationPost(GridMap gridMap, int inflationRadius)
{
int boundaryInflatedCount = 0;
LogManager.Info($"[边界后处理] 开始处理包围盒边界膨胀,膨胀半径: {inflationRadius}层");
// 逐层向内膨胀
for (int layer = 0; layer < inflationRadius; layer++)
{
int layerInflatedCount = 0;
// 处理上边界 (y = layer)
if (layer < gridMap.Height)
{
for (int x = 0; x < gridMap.Width; x++)
{
var gridPos = new GridPoint2D(x, layer);
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
layerInflatedCount++;
}
}
}
// 处理下边界 (y = maxY - layer)
if (gridMap.Height - 1 - layer >= 0 && gridMap.Height - 1 - layer != layer) // 避免重复处理
{
for (int x = 0; x < gridMap.Width; x++)
{
var gridPos = new GridPoint2D(x, gridMap.Height - 1 - layer);
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
layerInflatedCount++;
}
}
}
// 处理左边界 (x = layer)
if (layer < gridMap.Width)
{
for (int y = layer + 1; y < gridMap.Height - layer - 1; y++) // 避免角点重复
{
var gridPos = new GridPoint2D(layer, y);
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
layerInflatedCount++;
}
}
}
// 处理右边界 (x = maxX - layer)
if (gridMap.Width - 1 - layer >= 0 && gridMap.Width - 1 - layer != layer) // 避免重复处理
{
for (int y = layer + 1; y < gridMap.Height - layer - 1; y++) // 避免角点重复
{
var gridPos = new GridPoint2D(gridMap.Width - 1 - layer, y);
if (gridMap.IsWalkable(gridPos))
{
gridMap.SetCell(gridPos, false, double.MaxValue, CategoryAttributeManager.LogisticsElementType.);
layerInflatedCount++;
}
}
}
boundaryInflatedCount += layerInflatedCount;
LogManager.Info($"[边界后处理] 第{layer + 1}层膨胀完成,新增障碍物: {layerInflatedCount}个");
// 如果这一层没有可膨胀的网格,提前结束
if (layerInflatedCount == 0)
{
LogManager.Info($"[边界后处理] 第{layer + 1}层无可膨胀网格,膨胀结束");
break;
}
}
LogManager.Info($"[边界后处理] 边界膨胀完成,新增障碍物: {boundaryInflatedCount}个");
return boundaryInflatedCount;
}
/// <summary>
/// 收集通道相关节点
/// 根据节点类型决定收集策略:几何体节点收集父节点和同级节点,集合节点收集子节点
/// </summary>
/// <param name="channelItems">通道节点列表</param>
/// <returns>包含所有相关节点的集合</returns>
private HashSet<ModelItem> CollectChannelRelatedItems(List<ModelItem> channelItems)
{
var relatedItems = new HashSet<ModelItem>();
foreach (var channelItem in channelItems)
{
try
{
LogManager.Info($"[通道收集] 开始处理通道节点: '{channelItem.DisplayName}'");
// 使用 ModelItemAnalysisHelper 的通用方法收集相关节点
var itemRelatedNodes = ModelItemAnalysisHelper.CollectRelatedNodes(channelItem);
// 将结果添加到 HashSet 中
foreach (var node in itemRelatedNodes)
{
relatedItems.Add(node);
}
LogManager.Info($"[通道收集] 从 '{channelItem.DisplayName}' 收集到 {itemRelatedNodes.Count} 个相关节点");
}
catch (Exception ex)
{
LogManager.Warning($"[通道收集] 处理通道节点 '{channelItem?.DisplayName ?? "NULL"}' 时出错: {ex.Message}");
// 出错时至少保证通道本身被添加
relatedItems.Add(channelItem);
}
}
LogManager.Info($"[通道收集] 共收集到 {relatedItems.Count} 个通道相关节点");
return relatedItems;
}
/// <summary>
/// 使用Search API一次性获取所有有几何体的模型项
/// 这比逐项调用HasGeometry快得多
/// </summary>
private List<ModelItem> GetGeometryItemsUsingSearchAPI(Document document)
{
try
{
LogManager.Info("[Search API优化] 开始批量获取几何体项目");
var startTime = DateTime.Now;
// 统计模型总数
var totalModelItems = document.Models.RootItemDescendantsAndSelf.Count();
LogManager.Info($"[Search API优化] 模型总数统计: {totalModelItems} 个模型项");
var search = new Search();
// Search API阶段批量筛选基础条件
search.SearchConditions.Add(
SearchCondition.HasCategoryByName(PropertyCategoryNames.Geometry));
search.Selection.SelectAll();
search.Locations = SearchLocations.DescendantsAndSelf;
// 一次性获取所有有几何体的项目
var geometryItems = search.FindAll(document, false);
var searchElapsed = (DateTime.Now - startTime).TotalMilliseconds;
var filteredByGeometry = totalModelItems - geometryItems.Count();
var geometryFilterRatio = (double)filteredByGeometry / totalModelItems;
LogManager.Info($"[Search API优化] Search API阶段完成: 获取 {geometryItems.Count()} 个几何体项目,耗时: {searchElapsed:F1}ms");
LogManager.Info($"[Search API优化] 几何体筛选效果: 过滤掉 {filteredByGeometry} 个无几何体项目,筛选率: {geometryFilterRatio:P1}");
// 🔥 关键优化直接返回Search API结果移除所有昂贵的LINQ统计调用
// 基于多模型测试验证LINQ筛选条件(IsCollection, IsComposite, Children.Any)都返回0项
var filteredItems = geometryItems.ToList();
var totalElapsed = (DateTime.Now - startTime).TotalMilliseconds;
var totalFilteredCount = totalModelItems - filteredItems.Count;
var totalFilterRatio = (double)totalFilteredCount / totalModelItems;
LogManager.Info($"[Search API优化] 优化完成,总耗时: {totalElapsed:F1}ms");
LogManager.Info($"[Search API优化] 优化效果: 总模型项 {totalModelItems} → 几何体项目 {filteredItems.Count}");
LogManager.Info($"[Search API优化] 总优化率: 减少 {totalFilteredCount} 项 ({totalFilterRatio:P1})");
return filteredItems;
}
catch (Exception ex)
{
LogManager.Error($"[Search API优化] 筛选失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 轻量级后处理只对Search API预筛选的几何体项目进行必要的属性提取
/// 大幅减少API调用次数
/// </summary>
private Dictionary<ModelItem, ItemProperties> PostProcessGeometryItems(
List<ModelItem> geometryItems,
HashSet<ModelItem> channelItemsSet,
GridMap gridMap,
double scanHeight)
{
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 skippedByBoundingBox = 0;
var skippedByHeight = 0;
foreach (var item in geometryItems)
{
try
{
var properties = new ItemProperties();
// 已知条件HasGeometry = trueSearch API保证
properties.HasGeometry = true;
// 快速筛除1通道相关项目HashSet查找极快
properties.IsChannelItem = channelItemsSet.Contains(item);
if (properties.IsChannelItem)
{
skippedByChannel++;
continue; // 跳过通道项目不进行昂贵的API调用
}
properties.IsChildOfChannel = IsChildOfChannelItems(item, channelItemsSet);
if (properties.IsChildOfChannel)
{
skippedByChannel++;
continue; // 跳过通道子项目
}
// 移除容器检查基于4个模型测试验证所有几何体项目的容器检查结果都是0
// 注释:原有的容器检查代码
// API调用1: Children.Any() - 检查是否为容器
// properties.IsContainer = item.Children?.Any() ?? false;
// apiCalls++;
// if (properties.IsContainer) {
// skippedByContainer++;
// continue; // 跳过容器项目
// }
// 设为已知值(避免后续筛选中的判断错误)
properties.IsContainer = false; // 基于测试验证,几何体项目都不是容器
// API调用1: BoundingBox() - 获取边界框唯一保留的API调用
properties.BoundingBox = item.BoundingBox();
apiCalls++;
if (properties.BoundingBox == null)
{
skippedByBoundingBox++;
continue; // 跳过无边界框项目
}
// 快速计算高度范围检查纯数值计算无API调用
properties.IsInScanHeightRange = IsInScanHeightRange(properties.BoundingBox, gridMap, scanHeight);
if (!properties.IsInScanHeightRange)
{
skippedByHeight++;
continue; // 跳过高度范围外项目
}
// 只有通过所有筛选的项目才加入结果
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}, 无边界框: {skippedByBoundingBox}, 高度范围外: {skippedByHeight}");
return itemCache;
}
/// <summary>
/// 使用包围盒遍历方式处理障碍物(高性能优化版)
/// 分离API访问和数值计算利用并行处理加速纯计算部分
/// </summary>
/// <param name="document">Navisworks文档</param>
/// <param name="gridMap">网格地图</param>
/// <param name="channelItems">通道模型项列表</param>
/// <param name="scanHeight">扫描高度范围</param>
private void ProcessObstaclesWithBoundingBoxOptimized(Document document, GridMap gridMap, HashSet<ModelItem> channelItems, double scanHeight)
{
try
{
LogManager.Info("[高性能障碍物处理] 开始处理障碍物 - Search API优化版本");
var startTime = DateTime.Now;
var channelItemsSet = channelItems ?? new HashSet<ModelItem>();
// 阶段1Search API预筛选一次性批量获取几何体项目
var geometryItems = GetGeometryItemsUsingSearchAPI(document);
LogManager.Info($"[高性能障碍物处理] 输入统计 - 几何体项目: {geometryItems.Count}, 通道元素: {channelItemsSet.Count}");
// 阶段2轻量级后处理只对预筛选结果进行必要的API调用
var itemCache = PostProcessGeometryItems(geometryItems, channelItemsSet, gridMap, scanHeight);
// 阶段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");
// 阶段4网格覆盖计算并行几何计算- 使用50%CPU核心优化
LogManager.Info("[高性能障碍物处理] 阶段4: 并行网格覆盖计算");
var gridCalcStart = DateTime.Now;
var gridUpdates = validItems.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;
LogManager.Info($"[高性能障碍物处理] 阶段4完成: 生成 {gridUpdates.Count} 个网格更新操作,耗时: {gridCalcElapsed:F1}ms");
// 阶段5网格状态更新单线程写入
LogManager.Info("[高性能障碍物处理] 阶段5: 单线程网格状态更新");
var updateStart = DateTime.Now;
var updatedCells = 0;
var obstacleItems = 0;
foreach (var update in gridUpdates)
{
var cell = gridMap.Cells[update.X, update.Y];
// 只更新通道网格,跳过已是障碍物的网格
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
{
// 标记为障碍物
cell.IsWalkable = false;
cell.CellType = CategoryAttributeManager.LogisticsElementType.;
cell.Cost = double.MaxValue;
cell.IsInChannel = false;
cell.RelatedModelItem = update.Item;
// 记录高度信息
if (cell.PassableHeights == null)
cell.PassableHeights = new List<HeightInterval>();
var groundHeight = cell.WorldPosition.Z;
cell.PassableHeights.Add(new HeightInterval(
update.BoundingBox.Min.Z - groundHeight,
update.BoundingBox.Max.Z - groundHeight
));
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");
// 输出总体统计
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($"[并行优化统计] 使用 {optimalParallelism}/{Environment.ProcessorCount} CPU核心每核心处理 {Math.Ceiling((double)validItems.Count / optimalParallelism)} 个项目");
}
catch (Exception ex)
{
LogManager.Error($"[高性能障碍物处理] 处理失败: {ex.Message}");
throw;
}
}
/// <summary>
/// 计算包围盒在网格上覆盖的单元坐标
/// </summary>
/// <param name="bbox">包围盒</param>
/// <param name="gridMap">网格地图</param>
/// <returns>覆盖的网格单元坐标列表</returns>
private List<(int x, int y)> CalculateBoundingBoxGridCoverage(BoundingBox3D bbox, GridMap gridMap)
{
var coveredCells = new List<(int x, int y)>();
try
{
// 将包围盒的世界坐标转换为网格坐标
var minGridPos = gridMap.WorldToGrid(new Point3D(bbox.Min.X, bbox.Min.Y, 0));
var maxGridPos = gridMap.WorldToGrid(new Point3D(bbox.Max.X, bbox.Max.Y, 0));
// 确保坐标在有效范围内
int minX = Math.Max(0, Math.Min(minGridPos.X, maxGridPos.X));
int minY = Math.Max(0, Math.Min(minGridPos.Y, maxGridPos.Y));
int maxX = Math.Min(gridMap.Width - 1, Math.Max(minGridPos.X, maxGridPos.X));
int maxY = Math.Min(gridMap.Height - 1, Math.Max(minGridPos.Y, maxGridPos.Y));
// 遍历覆盖的矩形区域
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
coveredCells.Add((x, y));
}
}
}
catch (Exception ex)
{
LogManager.Debug($"[包围盒覆盖计算] 计算失败: {ex.Message}");
}
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;
// 4. 分析门的方向:哪个数大,哪个就是门的宽度
double width = bbox.Max.X - bbox.Min.X; // X方向宽度
double depth = bbox.Max.Y - bbox.Min.Y; // Y方向深度
double doorWidth = Math.Max(width, depth); // 门宽(较大的那个方向)
bool isDoorWidthInXDirection = width > depth; // 门宽是否在X方向
LogManager.Debug($"【门开口计算】{doorItem.DisplayName} - 尺寸: {width:F2}x{depth:F2}, 门宽: {doorWidth:F2}, 门宽方向: {(isDoorWidthInXDirection ? "X" : "Y")}, 限宽: {limitWidthMeters}m");
// 5. 限宽约束:限宽不能超过门宽
double effectiveWidth = Math.Min(limitWidthInModelUnits, doorWidth);
if (limitWidthInModelUnits > doorWidth)
{
LogManager.Info($"【门开口计算】{doorItem.DisplayName} 限宽({limitWidthMeters}m)超过门宽({doorWidth / metersToModelUnits:F2}m),按门宽处理");
}
// 6. 直接计算开口区域坐标范围
double minX, maxX, minY, maxY;
if (isDoorWidthInXDirection)
{
// 门宽在X方向在X方向应用限宽Y方向保持完整
double centerX = (bbox.Min.X + bbox.Max.X) / 2;
double halfWidth = effectiveWidth / 2;
minX = centerX - halfWidth;
maxX = centerX + halfWidth;
minY = bbox.Min.Y;
maxY = bbox.Max.Y;
}
else
{
// 门宽在Y方向在Y方向应用限宽X方向保持完整
double centerY = (bbox.Min.Y + bbox.Max.Y) / 2;
double halfWidth = effectiveWidth / 2;
minX = bbox.Min.X;
maxX = bbox.Max.X;
minY = centerY - halfWidth;
maxY = centerY + halfWidth;
}
LogManager.Debug($"【门开口计算】{doorItem.DisplayName} 开口区域坐标: [{minX:F2},{minY:F2}]-[{maxX:F2},{maxY:F2}]");
// 7. 将开口区域坐标直接转换为网格坐标
var minGridPos = gridMap.WorldToGrid(new Point3D(minX, minY, 0));
var maxGridPos = gridMap.WorldToGrid(new Point3D(maxX, maxY, 0));
// 确保坐标在有效范围内
int gridMinX = Math.Max(0, Math.Min(minGridPos.X, maxGridPos.X));
int gridMinY = Math.Max(0, Math.Min(minGridPos.Y, maxGridPos.Y));
int gridMaxX = Math.Min(gridMap.Width - 1, Math.Max(minGridPos.X, maxGridPos.X));
int gridMaxY = Math.Min(gridMap.Height - 1, Math.Max(minGridPos.Y, maxGridPos.Y));
// 确保至少有1个网格可通行
if (gridMinX >= gridMaxX)
{
int centerX = (gridMinX + gridMaxX) / 2;
gridMinX = Math.Max(0, centerX);
gridMaxX = Math.Min(gridMap.Width - 1, centerX);
}
if (gridMinY >= gridMaxY)
{
int centerY = (gridMinY + gridMaxY) / 2;
gridMinY = Math.Max(0, centerY);
gridMaxY = Math.Min(gridMap.Height - 1, centerY);
}
// 8. 生成覆盖的网格单元列表
for (int x = gridMinX; x <= gridMaxX; x++)
{
for (int y = gridMinY; y <= gridMaxY; y++)
{
coveredCells.Add((x, 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="bbox">包围盒</param>
/// <param name="gridMap">网格地图</param>
/// <param name="scanHeight">扫描高度范围</param>
/// <returns>是否在高度范围内</returns>
private bool IsInScanHeightRange(BoundingBox3D bbox, GridMap gridMap, double scanHeight)
{
try
{
// 使用通道的实际高度范围作为扫描基准
var scanMinZ = gridMap.Bounds.Max.Z; // 通道顶面
var scanMaxZ = gridMap.Bounds.Max.Z + scanHeight; // 从通道顶面向上扫描
// 检查包围盒的Z范围是否与扫描范围有重叠
return !(bbox.Max.Z < scanMinZ || bbox.Min.Z > scanMaxZ);
}
catch (Exception ex)
{
LogManager.Debug($"[高度范围检查] 检查失败: {ex.Message}");
return true; // 出错时保守处理,包含该项
}
}
/// <summary>
/// 检查模型项是否是通道项的子项
/// </summary>
/// <param name="item">要检查的模型项</param>
/// <param name="channelItemsSet">通道项集合</param>
/// <returns>是否是通道项的子项</returns>
private bool IsChildOfChannelItems(ModelItem item, HashSet<ModelItem> channelItemsSet)
{
try
{
var parent = item.Parent;
while (parent != null)
{
if (channelItemsSet.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 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
}