using System;
using System.Collections.Generic;
using System.Linq;
using Autodesk.Navisworks.Api;
using NavisworksTransport.Utils;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.PathPlanning
{
///
/// 基于通道的网格构建器
/// 实现A*算法改进方案中的通道优先策略和2.5D垂直扫描
///
/// 术语说明:
/// - "通道"指所有可通行的建筑结构(楼板、走廊、过道等)
/// - 楼板:物体在其顶面行驶,底面用于结构支撑
/// - 走廊/过道:传统意义的通道空间
///
public class ChannelBasedGridBuilder
{
private ChannelHeightDetector _heightDetector;
private readonly ICoordinateSystem _coordinateSystem;
///
/// 通道几何类型
///
private enum ChannelGeometryType
{
///
/// 水平通道(楼板)
///
Horizontal,
///
/// 倾斜通道(楼梯、坡道)
///
Inclined,
///
/// 复杂形状
///
Complex
}
///
/// 构造函数(自动使用当前坐标系)
///
public ChannelBasedGridBuilder() : this(CoordinateSystemManager.Instance.Current)
{
}
///
/// 构造函数
///
/// 坐标系
public ChannelBasedGridBuilder(ICoordinateSystem coordinateSystem)
{
_coordinateSystem = coordinateSystem ?? throw new ArgumentNullException(nameof(coordinateSystem));
_heightDetector = new ChannelHeightDetector(_coordinateSystem);
LogManager.Info($"[通道网格构建器] 初始化完成,坐标系: {_coordinateSystem.Type}");
}
///
/// 构建基于通道的精确覆盖网格
///
/// 网格单元格大小
/// 当前文档
/// 通道覆盖信息
public ChannelCoverage BuildChannelCoverage(double gridSize)
{
LogManager.Info("[通道网格构建器] 开始构建通道覆盖网格");
// 1. 获取所有通行相关的物品(通道 + 楼梯 + 电梯)
var channelItems = new List();
// 获取通道
var channels = CategoryAttributeManager.GetLogisticsItemsByType(
"通道");
channelItems.AddRange(channels);
// 获取楼梯
var stairs = CategoryAttributeManager.GetLogisticsItemsByType(
"楼梯");
channelItems.AddRange(stairs);
// 获取电梯
var elevators = CategoryAttributeManager.GetLogisticsItemsByType(
"电梯");
channelItems.AddRange(elevators);
if (!channelItems.Any())
{
LogManager.Warning("[通道网格构建器] 未找到任何通行物品(通道/楼梯/电梯)");
return CreateEmptyChannelCoverage();
}
LogManager.Info($"[通道网格构建器] 找到通行物品: 通道{channels.Count}个, 楼梯{stairs.Count}个, 电梯{elevators.Count}个, 总计{channelItems.Count}个");
// 2. 计算通道总边界
var totalBounds = BoundingBoxGeometryUtils.CalculateTotalBounds(channelItems);
LogManager.Info($"[通道网格构建器] 通道总边界: {FormatBounds(totalBounds)}");
// 3. 创建网格地图
// 必须继续传递当前构建器绑定的坐标系,避免同一轮流程里
// ChannelBasedGridBuilder、GridMap、ChannelHeightDetector 各自重新抓取 Current。
var gridMap = new GridMap(totalBounds, gridSize, _coordinateSystem);
// 4. 为每个通道生成精确投影
var processedChannels = new List();
foreach (var channel in channelItems)
{
try
{
ProjectChannelToGrid(channel, gridMap);
processedChannels.Add(channel);
}
catch (Exception ex)
{
LogManager.Error($"[通道网格构建器] 投影通道失败 '{channel.DisplayName}': {ex.Message}");
}
}
LogManager.Info($"[通道网格构建器] 成功投影 {processedChannels.Count}/{channelItems.Count} 个通道");
// 5. 计算可通行网格的Z值范围
var (walkableMinZ, walkableMaxZ, walkableCount) = CalculateWalkableGridZRange(gridMap);
return new ChannelCoverage
{
GridMap = gridMap,
ChannelItems = processedChannels,
TotalBounds = totalBounds,
WalkableMinZ = walkableMinZ,
WalkableMaxZ = walkableMaxZ,
WalkableGridCount = walkableCount
};
}
///
/// 计算可通行网格的Z值范围
/// 遍历所有可通行网格,基于实际WorldPosition.Z坐标计算最小值和最大值
///
/// 网格地图
/// 包含最小Z值、最大Z值和可通行网格数量的元组
private (double minZ, double maxZ, int walkableCount) CalculateWalkableGridZRange(GridMap gridMap)
{
double minZ = double.MaxValue;
double maxZ = double.MinValue;
int walkableCount = 0;
LogManager.Info("[通道Z值计算] 开始计算可通行网格Z值范围");
for (int x = 0; x < gridMap.Width; x++)
{
for (int y = 0; y < gridMap.Height; y++)
{
var cell = gridMap.Cells[x, y];
// 只计算可通行的网格
if (cell.HasAnyWalkableLayer())
{
walkableCount++;
double cellZ = cell.HeightLayers != null && cell.HeightLayers.Count > 0
? cell.HeightLayers[0].Z
: 0;
if (cellZ < minZ)
minZ = cellZ;
if (cellZ > maxZ)
maxZ = cellZ;
}
}
}
// 如果没有可通行网格,返回默认值
if (walkableCount == 0)
{
LogManager.Warning("[通道Z值计算] 未找到可通行网格,使用默认Z值范围");
minZ = 0.0;
maxZ = 0.0;
}
LogManager.Info($"[通道Z值计算] 计算完成 - 可通行网格: {walkableCount}个, Z范围: [{minZ:F2}, {maxZ:F2}], 高度差: {maxZ - minZ:F2}");
return (minZ, maxZ, walkableCount);
}
///
/// 将通道投影到网格
/// 统一投影方式:基于三角形几何的精确投影,解决楼板洞口和不规则形状问题
///
/// 通道物品
/// 目标网格地图
private void ProjectChannelToGrid(ModelItem channel, GridMap gridMap)
{
LogManager.Info($"[通道网格构建器] 投影通道: {channel.DisplayName}");
// 获取通道限速(一次性查询,避免重复COM API调用)
double channelSpeedLimit = CategoryAttributeManager.GetSpeedLimit(channel);
// 提取三角形几何
var triangles = GeometryHelper.ExtractTriangles(new[] { channel });
if (triangles.Count == 0)
{
LogManager.Warning($"[通道网格构建器] 未能从 '{channel.DisplayName}' 提取到三角形");
return;
}
// 统一投影处理
int processedTriangles = 0;
foreach (var triangle in triangles)
{
var normal = ComputeTriangleNormal(triangle);
// 根据法向量决定处理方式(使用坐标系判断向上)
if (GetUpComponent(normal) > 0) // 朝上的面
{
RasterizeTriangleToGrid(gridMap, triangle, channel, channelSpeedLimit);
processedTriangles++;
}
// 垂直面和朝下的面不投影
}
LogManager.Info($"[通道网格构建器] 投影完成:{processedTriangles}/{triangles.Count} 个三角形");
}
///
/// 计算三角形法向量
///
/// 三角形
/// 法向量
private Point3D ComputeTriangleNormal(Triangle3D triangle)
{
var edge1 = new Point3D(
triangle.Point2.X - triangle.Point1.X,
triangle.Point2.Y - triangle.Point1.Y,
triangle.Point2.Z - triangle.Point1.Z);
var edge2 = new Point3D(
triangle.Point3.X - triangle.Point1.X,
triangle.Point3.Y - triangle.Point1.Y,
triangle.Point3.Z - triangle.Point1.Z);
// 计算叉积
var normal = new Point3D(
edge1.Y * edge2.Z - edge1.Z * edge2.Y,
edge1.Z * edge2.X - edge1.X * edge2.Z,
edge1.X * edge2.Y - edge1.Y * edge2.X);
// 归一化
double length = Math.Sqrt(normal.X * normal.X + normal.Y * normal.Y + normal.Z * normal.Z);
if (length > 0.0001)
{
normal = new Point3D(normal.X / length, normal.Y / length, normal.Z / length);
}
return normal;
}
///
/// 获取向上轴的分量(根据坐标系)
///
private double GetUpComponent(Point3D vector)
{
return _coordinateSystem.GetElevation(vector);
}
///
/// 将三角形光栅化到网格
///
/// 网格地图
/// 三角形
/// 通道模型项
/// 预计算的通道限速
private void RasterizeTriangleToGrid(GridMap gridMap, Triangle3D triangle, ModelItem channel, double channelSpeedLimit)
{
// 获取三角形的2D投影边界框(使用水平坐标)
var (p1h1, p1h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point1);
var (p2h1, p2h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point2);
var (p3h1, p3h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point3);
var minH1 = Math.Min(p1h1, Math.Min(p2h1, p3h1));
var maxH1 = Math.Max(p1h1, Math.Max(p2h1, p3h1));
var minH2 = Math.Min(p1h2, Math.Min(p2h2, p3h2));
var maxH2 = Math.Max(p1h2, Math.Max(p2h2, p3h2));
var minGrid = gridMap.WorldToGrid(_coordinateSystem.CreatePoint(minH1, minH2, 0));
var maxGrid = gridMap.WorldToGrid(_coordinateSystem.CreatePoint(maxH1, maxH2, 0));
// 遍历边界框内的所有网格点
for (int x = minGrid.X; x <= maxGrid.X; x++)
{
for (int y = minGrid.Y; y <= maxGrid.Y; y++)
{
var gridPos = new GridPoint2D(x, y);
if (gridMap.IsValidGridPosition(gridPos))
{
var worldPos = gridMap.GridToWorld3D(gridPos);
// 检查点是否在三角形内部(使用2D水平投影)
if (IsPointInTriangleHorizontal(worldPos, triangle))
{
// 计算网格中心点在三角形平面上的精确高度
double gridCenterElevation = GetElevationAtPoint(triangle, worldPos);
// 创建高度层(追加模式,不覆盖)
var channelType = CategoryAttributeManager.GetCategoryId(channel);
var layer = new HeightLayer(
z: gridCenterElevation,
passableHeight: new HeightInterval(0, 0), // 初始化,后续由SetChannelPassableHeights设置
sourceItem: channel,
speedLimit: channelSpeedLimit,
type: channelType
);
// 追加高度层(支持同一网格多个高度)
gridMap.AddHeightLayer(gridPos, layer);
}
}
}
}
}
///
/// 检查点是否在三角形的水平投影内
///
private bool IsPointInTriangleHorizontal(Point3D point, Triangle3D triangle)
{
var (ph1, ph2) = _coordinateSystem.GetHorizontalCoords(point);
var (p1h1, p1h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point1);
var (p2h1, p2h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point2);
var (p3h1, p3h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point3);
// 使用叉积法判断点是否在三角形内
double Cross(double ax, double ay, double bx, double by) => ax * by - ay * bx;
var d1 = Cross(ph1 - p1h1, ph2 - p1h2, p2h1 - p1h1, p2h2 - p1h2);
var d2 = Cross(ph1 - p2h1, ph2 - p2h2, p3h1 - p2h1, p3h2 - p2h2);
var d3 = Cross(ph1 - p3h1, ph2 - p3h2, p1h1 - p3h1, p1h2 - p3h2);
bool hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
bool hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
return !(hasNeg && hasPos);
}
///
/// 计算点在三角形平面上的高度值(根据当前坐标系)
///
private double GetElevationAtPoint(Triangle3D triangle, Point3D point)
{
var normal = ComputeTriangleNormal(triangle);
// 获取向上轴的分量
double upComponent = GetUpComponent(normal);
// 如果向上分量接近0,说明是垂直面,返回第一个点的Z值
if (Math.Abs(upComponent) < 0.0001)
{
return _coordinateSystem.GetElevation(triangle.Point1);
}
// 获取各点的水平坐标
var (p1h1, p1h2) = _coordinateSystem.GetHorizontalCoords(triangle.Point1);
var (ph1, ph2) = _coordinateSystem.GetHorizontalCoords(point);
// 获取向上轴的索引来正确计算
int upAxis = _coordinateSystem.UpAxisIndex;
// 使用平面方程计算点的高度
// n_x(x-x0) + n_y(y-y0) + n_z(z-z0) = 0
// 解出高度值
double elevation;
if (upAxis == 2) // Z-up
{
elevation = triangle.Point1.Z - (normal.X * (ph1 - p1h1) + normal.Y * (ph2 - p1h2)) / normal.Z;
}
else // Y-up
{
elevation = triangle.Point1.Y - (normal.X * (ph1 - p1h1) + normal.Z * (ph2 - p1h2)) / normal.Y;
}
return elevation;
}
///
/// 创建空的通道覆盖
///
/// 空的通道覆盖
private ChannelCoverage CreateEmptyChannelCoverage()
{
var emptyBounds = new BoundingBox3D(new Point3D(0, 0, 0), new Point3D(0, 0, 0));
return new ChannelCoverage
{
GridMap = new GridMap(emptyBounds, 1.0, _coordinateSystem),
ChannelItems = new List(),
TotalBounds = emptyBounds
};
}
///
/// 格式化边界框信息用于日志
///
/// 边界框
/// 格式化字符串
private string FormatBounds(BoundingBox3D bounds)
{
var (min1, max1, min2, max2) = _coordinateSystem.GetHorizontalRange(bounds);
var (minElev, maxElev) = _coordinateSystem.GetHeightRange(bounds);
return $"[H1:{min1:F2}-{max1:F2}, H2:{min2:F2}-{max2:F2}, Elev:{minElev:F2}-{maxElev:F2}]";
}
}
}