NavisworksTransport/src/PathPlanning/GridMapGenerator.cs

1544 lines
76 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 Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
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;
/// <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="vehicleRadius">车辆半径(米)</param>
/// <param name="safetyMargin">安全间隙(米)</param>
/// <param name="planningStartPoint">规划起点</param>
/// <param name="planningEndPoint">规划终点</param>
/// <param name="vehicleHeight">车辆高度(米)</param>
/// <returns>生成的网格地图</returns>
public GridMap GenerateFromBIM(BoundingBox3D bounds, double cellSize, double vehicleRadius, double safetyMargin, Point3D planningStartPoint, Point3D planningEndPoint, double vehicleHeight)
{
try
{
// 第一步:统一转换所有米制参数为模型单位
double metersToModelUnitsConversionFactor = UnitsConverter.GetMetersToUnitsConversionFactor(Application.ActiveDocument.Units);
double cellSizeInModelUnits = cellSize * metersToModelUnitsConversionFactor;
double vehicleRadiusInModelUnits = vehicleRadius * metersToModelUnitsConversionFactor;
double safetyMarginInModelUnits = safetyMargin * metersToModelUnitsConversionFactor;
double vehicleHeightInModelUnits = vehicleHeight * metersToModelUnitsConversionFactor;
double scanHeightInModelUnits = vehicleHeightInModelUnits + safetyMarginInModelUnits; // 扫描高度 = 车辆高度 + 安全间隙
double totalInflationRadiusInModelUnits = vehicleRadiusInModelUnits + safetyMarginInModelUnits; // 膨胀半径 = 车辆半径 + 安全间隙
LogManager.Info($"【生成网格地图】参数单位转换完成 (转换系数: {metersToModelUnitsConversionFactor:F2}):");
LogManager.Info($" 网格大小: {cellSize}米 → {cellSizeInModelUnits:F2}模型单位");
LogManager.Info($" 车辆半径: {vehicleRadius}米 → {vehicleRadiusInModelUnits:F2}模型单位");
LogManager.Info($" 安全间隙: {safetyMargin}米 → {safetyMarginInModelUnits:F2}模型单位");
LogManager.Info($" 车辆高度: {vehicleHeight}米 → {vehicleHeightInModelUnits:F2}模型单位");
LogManager.Info($" 扫描高度: {scanHeightInModelUnits:F2}模型单位");
LogManager.Info($" 膨胀半径: {totalInflationRadiusInModelUnits:F2}模型单位");
// 生成缓存键(使用转换后的模型单位参数确保一致性)
var cacheKey = GridMapCacheKey.CreateFrom(bounds, cellSizeInModelUnits, vehicleRadiusInModelUnits, safetyMarginInModelUnits, vehicleHeightInModelUnits);
// 尝试从缓存获取
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 allChannelItems = CategoryAttributeManager.GetAllTraversableLogisticsItems();
LogManager.Info($"【生成网格地图】直接获取到 {allChannelItems.Count} 个可通行物流元素");
// 智能收集可通行模型相关节点(包括父节点、同级节点等)
var channelRelatedItems = CollectChannelRelatedItems(allChannelItems.ToList());
LogManager.Info($"【生成网格地图】智能收集到 {channelRelatedItems.Count} 个通道相关节点");
// 2. 直接遍历处理障碍物(包围盒检测) - 使用高性能优化版本
LogManager.Info("【生成网格地图】步骤2: 高性能包围盒遍历处理障碍物");
ProcessObstaclesWithBoundingBox(channelCoverage.GridMap, channelRelatedItems, scanHeightInModelUnits, channelCoverage);
LogManager.Info($"【阶段2完成】障碍物处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 2.5. 为所有可通行网格设置PassableHeights确保高度约束检查
LogManager.Info("【生成网格地图】步骤2.5: 为可通行网格设置高度约束");
SetChannelPassableHeights(channelCoverage.GridMap, scanHeightInModelUnits);
LogManager.Info($"【阶段2.5完成】高度约束设置后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 2.6. 单独处理门元素,设置为可通行,并设置通行高度
LogManager.Info("【生成网格地图】步骤2.6: 处理门元素");
ProcessDoorElements(channelCoverage.GridMap, allChannelItems);
LogManager.Info($"【阶段2.6完成】门元素处理后网格统计: {channelCoverage.GridMap.GetStatistics()}");
// 3. 应用车辆尺寸膨胀(如果需要)
if (vehicleRadius > 0 || safetyMargin > 0)
{
LogManager.Info("【生成网格地图】步骤3: 应用车辆膨胀");
ApplyVehicleInflation(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;
// 注意网格的Z坐标已在通道生成时设置为实际通道高度无需重新计算
}
// 将通道数据设置到GridMap中供后续PathPlanningManager使用
if (channelRelatedItems != null && channelRelatedItems.Any())
{
channelCoverage.GridMap.ChannelItems = channelRelatedItems.ToList();
LogManager.Info($"【生成网格地图】已将 {channelRelatedItems.Count} 个通道数据设置到GridMap中");
}
else if (channelCoverage.ChannelItems != null && channelCoverage.ChannelItems.Any())
{
channelCoverage.GridMap.ChannelItems = new List<ModelItem>(channelCoverage.ChannelItems);
LogManager.Info($"【生成网格地图】已将 {channelCoverage.ChannelItems.Count} 个通道数据设置到GridMap中");
}
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> 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;
// 获取门的限速
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 = new Point3D(world2D.X, world2D.Y, doorBottomZ);
// 使用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");
// 设置门的高度范围
if (doorHeight > 0)
{
cell.PassableHeight = new HeightInterval(0, doorHeight);
}
// 一次性放置完整配置的GridCell
gridMap.PlaceCell(gridPos, cell);
LogManager.Info($"[门网格放置] 网格坐标({gridPos.X},{gridPos.Y}) 最终限速: {gridMap.GetCell(gridPos)?.SpeedLimit:F2}m/s");
}
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;
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
var cell = gridMap.Cells[x, y];
if (cell.IsWalkable)
{
cell.PassableHeight = 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));
// 重要修复:使用通道顶面作为垂直扫描的起点,而不是底面
// 从通道构建器的日志可知通道总边界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>
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(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];
// 直接设置高度区间
cell.PassableHeight = interval;
// 关键修复:只处理通道单元格的高度信息集成
// 非通道单元格不应该通过高度扫描而改变其类型或可通行性
if (cell.CellType == CategoryAttributeManager.LogisticsElementType. && cell.IsInChannel)
{
channelCellsWithHeightData++;
if (interval.MaxZ > interval.MinZ)
{
// 通道区域有足够净空高度,设置为可通行
var cellPos = new GridPoint2D(gridX, gridY);
// 创建更新后的通道GridCell保持原有属性但更新位置和高度
var updatedCell = new GridCell
{
IsWalkable = true,
CellType = CategoryAttributeManager.LogisticsElementType.,
IsInChannel = true,
ChannelType = ChannelType.Corridor,
WorldPosition = point,
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
SpeedLimit = cell.SpeedLimit, // 保持原有限速
PassableHeight = interval,
Cost = 1.0
};
// 放置更新的GridCell
gridMap.PlaceCell(cellPos, updatedCell);
}
else
{
// 通道区域但净空高度不足,标记为不可通行但保持通道类型
var cellPos = new GridPoint2D(gridX, gridY);
// 创建高度不足的通道GridCell保持原有属性但设为不可通行
var updatedCell = new GridCell
{
IsWalkable = false,
CellType = CategoryAttributeManager.LogisticsElementType.,
IsInChannel = true,
ChannelType = ChannelType.Corridor,
WorldPosition = point,
RelatedModelItem = cell.RelatedModelItem, // 保持原有关联
SpeedLimit = cell.SpeedLimit, // 保持原有限速
PassableHeight = interval,
Cost = double.MaxValue
};
// 放置更新的GridCell
gridMap.PlaceCell(cellPos, updatedCell);
channelCellsBecomingUnwalkable++;
}
}
else
{
// 非通道单元格:不应该通过垂直扫描改变其状态
// 这种情况理论上不应该发生,因为只对通道单元格进行扫描
LogManager.Warning($"[通道2.5D模式] 警告:非通道单元格 ({gridX},{gridY}) 收到高度数据,类型:{cell.CellType}, IsInChannel{cell.IsInChannel}");
// 仍然更新高度信息,但保持原有状态
cell.WorldPosition = point;
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.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="vehicleRadiusInModelUnits">车辆半径(模型单位)</param>
private void ApplyVehicleInflation(GridMap gridMap, double vehicleRadiusInModelUnits)
{
if (vehicleRadiusInModelUnits <= 0) return;
try
{
// 计算膨胀半径(网格单元格数)
int inflationRadius = (int)Math.Ceiling(vehicleRadiusInModelUnits / gridMap.CellSize);
LogManager.Info($"[高效膨胀] 膨胀半径: {inflationRadius}个网格单元");
// 使用高效的距离变换算法
ApplyVehicleInflationOptimized(gridMap, inflationRadius);
}
catch (Exception ex)
{
LogManager.Error($"[高效膨胀] 应用车辆膨胀时发生错误: {ex.Message}");
throw new AutoPathPlanningException($"车辆膨胀失败: {ex.Message}", ex);
}
}
/// <summary>
/// 优化的车辆膨胀算法 - 使用正确的8连通距离变换方法
/// 时间复杂度: O(n*m) 而不是 O(n*m*r^2)
/// </summary>
private void ApplyVehicleInflationOptimized(GridMap gridMap, int inflationRadius)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
LogManager.Info($"【8连通膨胀】使用正确的8连通距离变换算法网格大小: {gridMap.Width}x{gridMap.Height}");
// 使用浮点数距离矩阵支持√2对角线距离
var distanceMap = new double[gridMap.Width, gridMap.Height];
const double SQRT2 = 1.414213562373095; // √2
// 初始化距离矩阵
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];
// 障碍物和Unknown网格作为膨胀源点
if (!cell.IsWalkable)
{
distanceMap[x, y] = 0.0;
obstacleCount++;
}
else if (cell.CellType == CategoryAttributeManager.LogisticsElementType.Unknown)
{
distanceMap[x, y] = 0.0;
unknownCount++;
}
else
{
distanceMap[x, y] = double.MaxValue;
}
}
}
LogManager.Info($"【8连通膨胀】膨胀源点统计 - 障碍物: {obstacleCount}, Unknown: {unknownCount}");
// 使用标准的距离变换算法 - 正向扫描
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
if (distanceMap[x, y] == double.MaxValue)
{
double minDist = double.MaxValue;
// 检查左侧和上方的邻居4连通
if (x > 0)
minDist = Math.Min(minDist, distanceMap[x - 1, y] + 1.0);
if (y > 0)
minDist = Math.Min(minDist, distanceMap[x, y - 1] + 1.0);
// 检查对角线邻居8连通扩展
if (x > 0 && y > 0)
minDist = Math.Min(minDist, distanceMap[x - 1, y - 1] + SQRT2);
if (x > 0 && y < gridMap.Height - 1)
minDist = Math.Min(minDist, distanceMap[x - 1, y + 1] + SQRT2);
if (minDist != double.MaxValue)
distanceMap[x, y] = minDist;
}
}
}
LogManager.Info($"【8连通膨胀】正向扫描完成耗时: {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.0)
{
double minDist = distanceMap[x, y];
// 检查右侧和下方的邻居4连通
if (x < gridMap.Width - 1)
minDist = Math.Min(minDist, distanceMap[x + 1, y] + 1.0);
if (y < gridMap.Height - 1)
minDist = Math.Min(minDist, distanceMap[x, y + 1] + 1.0);
// 检查对角线邻居8连通扩展
if (x < gridMap.Width - 1 && y < gridMap.Height - 1)
minDist = Math.Min(minDist, distanceMap[x + 1, y + 1] + SQRT2);
if (x < gridMap.Width - 1 && y > 0)
minDist = Math.Min(minDist, distanceMap[x + 1, y - 1] + SQRT2);
distanceMap[x, y] = minDist;
}
}
}
LogManager.Info($"【8连通膨胀】反向扫描完成耗时: {stopwatch.ElapsedMilliseconds}ms");
// 应用膨胀结果
int inflatedCells = 0;
int processedCells = 0;
int totalCells = gridMap.Width * gridMap.Height;
int skippedCells = 0;
int protectedDoorCells = 0;
LogManager.Info($"【8连通膨胀】开始应用膨胀结果总单元格数: {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($"【8连通膨胀】应用进度: {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] != double.MaxValue &&
distanceMap[x, y] > 0.0 &&
distanceMap[x, y] <= inflationRadius)
{
// 门元素保护:跳过门类型的网格,不将其膨胀为障碍物
if (cell.CellType == CategoryAttributeManager.LogisticsElementType.)
{
protectedDoorCells++;
// 跳过门网格,保持其可通行状态
continue;
}
// 创建障碍物GridCell
var worldPos = gridMap.GridToWorld3D(gridPos);
var obstacleCell = GridCellBuilder.Obstacle(worldPos, null);
gridMap.PlaceCell(gridPos, obstacleCell);
inflatedCells++;
}
else if (distanceMap[x, y] == double.MaxValue || distanceMap[x, y] > inflationRadius)
{
skippedCells++;
}
}
}
}
catch (Exception ex)
{
LogManager.Error($"【8连通膨胀】应用膨胀结果时发生错误: {ex.Message},已处理{processedCells}/{totalCells}个单元格");
throw;
}
// 单独处理包围盒边界膨胀
int boundaryInflatedCells = ApplyBoundaryInflationPost(gridMap, inflationRadius);
stopwatch.Stop();
LogManager.Info($"【8连通膨胀】车辆膨胀完成: 膨胀半径={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))
{
// 创建障碍物GridCell
var worldPos = gridMap.GridToWorld3D(gridPos);
var obstacleCell = GridCellBuilder.Obstacle(worldPos, null);
gridMap.PlaceCell(gridPos, obstacleCell);
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))
{
// 创建障碍物GridCell
var worldPos = gridMap.GridToWorld3D(gridPos);
var obstacleCell = GridCellBuilder.Obstacle(worldPos, null);
gridMap.PlaceCell(gridPos, obstacleCell);
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))
{
// 创建障碍物GridCell
var worldPos = gridMap.GridToWorld3D(gridPos);
var obstacleCell = GridCellBuilder.Obstacle(worldPos, null);
gridMap.PlaceCell(gridPos, obstacleCell);
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))
{
// 创建障碍物GridCell
var worldPos = gridMap.GridToWorld3D(gridPos);
var obstacleCell = GridCellBuilder.Obstacle(worldPos, null);
gridMap.PlaceCell(gridPos, obstacleCell);
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()
{
try
{
LogManager.Info("[Search API优化] 开始批量获取几何体项目");
var startTime = DateTime.Now;
// 统计模型总数
var totalModelItems = Application.ActiveDocument.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(Application.ActiveDocument, 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}");
// 🔥 关键优化使用PrimitiveTypes精确过滤线性几何体
// 基于多模型测试验证LINQ筛选条件(IsCollection, IsComposite, Children.Any)都返回0项
LogManager.Info("[Search API优化] 开始使用PrimitiveTypes过滤线性几何体");
var primitiveFilterStart = DateTime.Now;
int triangleCount = 0;
int lineOnlyCount = 0;
int pointOnlyCount = 0;
int snapPointOnlyCount = 0;
int textOnlyCount = 0;
int mixedCount = 0;
int noGeometryCount = 0;
var filteredItems = geometryItems.Where(item =>
{
try
{
var geometry = item.Geometry;
if (geometry == null)
{
noGeometryCount++;
return false;
}
var primitiveTypes = geometry.PrimitiveTypes;
// 统计各种图元类型
if (primitiveTypes.HasFlag(PrimitiveTypes.Triangles))
{
if (primitiveTypes == PrimitiveTypes.Triangles)
triangleCount++;
else
mixedCount++;
return true; // 包含三角形的几何体都保留
}
else if (primitiveTypes == PrimitiveTypes.Lines)
{
lineOnlyCount++;
return false; // 纯线条几何体过滤掉
}
else if (primitiveTypes == PrimitiveTypes.Points)
{
pointOnlyCount++;
return false; // 纯点几何体过滤掉
}
else if (primitiveTypes == PrimitiveTypes.SnapPoints)
{
snapPointOnlyCount++;
return false; // 纯捕捉点几何体过滤掉
}
else if (primitiveTypes == PrimitiveTypes.Text)
{
textOnlyCount++;
return false; // 纯文本几何体过滤掉
}
else
{
mixedCount++;
return true; // 其他混合类型保留
}
}
catch
{
return false; // 出错时跳过该项目
}
}).ToList();
var primitiveFilterElapsed = (DateTime.Now - primitiveFilterStart).TotalMilliseconds;
var filteredByPrimitives = geometryItems.Count() - filteredItems.Count;
LogManager.Info($"[Search API优化] PrimitiveTypes过滤完成: 过滤掉 {filteredByPrimitives} 个线性几何体,耗时: {primitiveFilterElapsed:F1}ms");
LogManager.Info($"[Search API优化] 图元类型统计 - 纯三角形: {triangleCount}, 纯线条: {lineOnlyCount}, 纯点: {pointOnlyCount}, 纯捕捉点: {snapPointOnlyCount}, 纯文本: {textOnlyCount}, 混合类型: {mixedCount}, 无几何体: {noGeometryCount}");
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 scanHeightInModelUnits)
{
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;
foreach (var item in geometryItems)
{
try
{
var properties = new ItemProperties
{
// 已知条件HasGeometry = trueSearch API保证
HasGeometry = true,
// 快速筛除1通道相关项目HashSet查找极快
IsChannelItem = channelItemsSet.Contains(item)
};
if (properties.IsChannelItem)
{
skippedByChannel++;
continue; // 跳过通道项目不进行昂贵的API调用
}
properties.IsChildOfChannel = IsChildOfChannelItems(item, channelItemsSet);
if (properties.IsChildOfChannel)
{
skippedByChannel++;
continue; // 跳过通道子项目
}
// 设为已知值(避免后续筛选中的判断错误)
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}, 无边界框: {skippedByBoundingBox}");
LogManager.Info("[轻量级后处理] 已移除全局高度预过滤,将在障碍物遍历阶段进行精确高度检查");
return itemCache;
}
/// <summary>
/// 使用包围盒遍历方式处理障碍物(高性能优化版)
/// 分离API访问和数值计算利用并行处理加速纯计算部分
/// </summary>
/// <param name="gridMap">网格地图</param>
/// <param name="channelItems">通道模型项列表</param>
/// <param name="scanHeightInModelUnits">扫描高度范围(模型单位)</param>
/// <param name="channelCoverage">通道覆盖信息,包含通道边界</param>
private void ProcessObstaclesWithBoundingBox(GridMap gridMap, HashSet<ModelItem> channelItems, double scanHeightInModelUnits, ChannelCoverage channelCoverage)
{
try
{
LogManager.Info("[高性能障碍物处理] 开始处理障碍物 - Search API优化版本");
var startTime = DateTime.Now;
var channelItemsSet = channelItems ?? new HashSet<ModelItem>();
// 阶段1Search API预筛选一次性批量获取几何体项目
var geometryItems = GetGeometryItemsUsingSearchAPI();
LogManager.Info($"[高性能障碍物处理] 输入统计 - 几何体项目: {geometryItems.Count}, 通道元素: {channelItemsSet.Count}");
// 阶段2轻量级后处理只对预筛选结果进行必要的API调用
var itemCache = PostProcessGeometryItems(geometryItems, channelItemsSet, gridMap, scanHeightInModelUnits);
// 阶段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");
// 获取可通行网格的实际高度范围信息
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 centerX = (bbox.Min.X + bbox.Max.X) / 2;
var centerY = (bbox.Min.Y + bbox.Max.Y) / 2;
var centerGridPos = gridMap.WorldToGrid(new Point3D(centerX, centerY, 0));
if (!gridMap.IsValidGridPosition(centerGridPos))
{
return false;
}
var centerCell = gridMap.Cells[centerGridPos.X, centerGridPos.Y];
// 使用可通行网格高度范围计算扫描范围
// 扫描范围:从可通行网格最低点开始,到可通行网格最高点+车辆高度+安全间隙
var scanMin = walkableMinZ;
var scanMax = walkableMaxZ + scanHeightInModelUnits;
// 只有与该网格高度范围重叠的几何体才进入处理
bool isInRange = !(bbox.Max.Z <= scanMin || bbox.Min.Z > 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;
LogManager.Info($"[高性能障碍物处理] 阶段4完成: 生成 {gridUpdates.Count} 个网格更新操作,耗时: {gridCalcElapsed:F1}ms");
LogManager.Info($"[精确高度检查] 检查了 {totalCheckedItems} 个几何体,过滤掉 {filteredItems} 个高度范围外的几何体,保留 {heightCheckedItems.Count} 个");
// 阶段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;
// 记录高度信息
var groundHeight = cell.WorldPosition.Z;
cell.PassableHeight = 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($"[精确高度过滤统计] 高度检查项目: {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>
/// <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
{
// 网格坐标(x,y)代表左下角,覆盖世界坐标范围:
// X: [Origin.X + x*CellSize, Origin.X + (x+1)*CellSize)
// Y: [Origin.Y + y*CellSize, Origin.Y + (y+1)*CellSize)
// 直接使用WorldToGrid进行坐标转换
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, minGridPos.X);
int minY = Math.Max(0, minGridPos.Y);
int maxX = Math.Min(gridMap.Width - 1, maxGridPos.X);
int maxY = Math.Min(gridMap.Height - 1, maxGridPos.Y);
// 确保至少覆盖一个网格(防止空包围盒)
if (maxX < minX) maxX = minX;
if (maxY < minY) maxY = minY;
// 遍历覆盖的矩形区域
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, minGridPos.X);
int gridMinY = Math.Max(0, minGridPos.Y);
int gridMaxX = Math.Min(gridMap.Width - 1, maxGridPos.X);
int gridMaxY = Math.Min(gridMap.Height - 1, maxGridPos.Y);
// 确保至少有1个网格可通行
if (gridMaxX < gridMinX) gridMaxX = gridMinX;
if (gridMaxY < gridMinY) gridMaxY = gridMinY;
// 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="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
}