1576 lines
80 KiB
C#
1576 lines
80 KiB
C#
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 = true(Search 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>();
|
||
|
||
// 阶段1:Search 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
|
||
} |