NavisworksTransport/src/PathPlanning/ChannelHeightDetector.cs

1055 lines
46 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;
using NavisworksTransport.Utils.CoordinateSystem;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 通道地面高度检测器
/// 负责检测路径点在通道中的精确地面高度
/// 支持动态坐标系Z-Up 或 Y-Up
/// </summary>
public class ChannelHeightDetector
{
/// <summary>
/// 默认射线投射容差(米)- 与配置中的安全间隙一致
/// </summary>
private const double DEFAULT_RAYCAST_TOLERANCE_METERS = 0.1;
/// <summary>
/// 当前使用的坐标系
/// </summary>
private readonly ICoordinateSystem _coordinateSystem;
private readonly Dictionary<string, ChannelHeightInfo> _heightCache;
private readonly double _raycastTolerance;
/// <summary>
/// 构造函数(自动使用当前坐标系)
/// </summary>
/// <param name="raycastTolerance">射线投射容差(米)</param>
public ChannelHeightDetector(double raycastTolerance = DEFAULT_RAYCAST_TOLERANCE_METERS)
: this(CoordinateSystemManager.Instance.Current, raycastTolerance)
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="coordinateSystem">坐标系</param>
/// <param name="raycastTolerance">射线投射容差(米)</param>
public ChannelHeightDetector(ICoordinateSystem coordinateSystem, double raycastTolerance = DEFAULT_RAYCAST_TOLERANCE_METERS)
{
_coordinateSystem = coordinateSystem ?? throw new ArgumentNullException(nameof(coordinateSystem));
_heightCache = new Dictionary<string, ChannelHeightInfo>();
_raycastTolerance = raycastTolerance;
}
/// <summary>
/// 获取指定位置的通道地面高度(底面高度)
/// 注意:此方法返回楼板/通道结构的底面高度,用于传统的高度检测
/// 对于物体路径规划建议使用GetChannelTopHeight获取顶面行驶表面高度
/// </summary>
/// <param name="position">世界坐标位置</param>
/// <param name="channelItems">通道模型项集合(可能包含楼板、走廊等可通行结构)</param>
/// <returns>通道底面高度(米)</returns>
public double GetChannelFloorHeight(Point3D position, IEnumerable<ModelItem> channelItems)
{
try
{
//LogManager.Info($"[高度检测] 🔍 开始检测位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 的地面高度,通道数={channelItems?.Count() ?? 0}");
// 寻找包含该位置的通道
var containingChannel = FindContainingChannel(position, channelItems);
if (containingChannel == null)
{
double fallbackElevation = _coordinateSystem.GetElevation(position);
LogManager.Info($"[高度检测] ❌ 位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 未找到包含的通道,使用当前宿主高程: {fallbackElevation:F2}");
return fallbackElevation;
}
//LogManager.Info($"[高度检测] ✅ 找到包含通道: {containingChannel.DisplayName}");
// 检查缓存
var cacheKey = GenerateCacheKey(containingChannel, position);
if (_heightCache.TryGetValue(cacheKey, out var cachedInfo))
{
// 使用缓存的高度信息,但调用精确高度计算方法来确定最终高度
var preciseHeight = CalculatePreciseHeightAtPosition(position, cachedInfo, containingChannel);
LogManager.Debug($"[高度检测] 使用缓存的高度信息: {preciseHeight:F2}");
return preciseHeight;
}
// 分析通道几何,获取高度信息
var heightInfo = AnalyzeChannelGeometry(containingChannel);
if (heightInfo != null)
{
_heightCache[cacheKey] = heightInfo;
// 根据位置在通道中的具体位置计算精确高度
var preciseHeight = CalculatePreciseHeightAtPosition(position, heightInfo, containingChannel);
LogManager.Debug($"[高度检测] 计算得到精确高度: {preciseHeight:F2}");
return preciseHeight;
}
// 如果几何分析失败,使用射线投射方法
var raycastHeight = GetHeightByRaycast(position, containingChannel);
LogManager.Debug($"[高度检测] 射线投射得到高度: {raycastHeight:F2}");
return raycastHeight;
}
catch (Exception ex)
{
LogManager.Error($"[高度检测] 检测高度时发生错误: {ex.Message}");
return _coordinateSystem.GetElevation(position);
}
}
/// <summary>
/// 获取通道顶面高度(物体行驶表面)
/// 专门用于物体路径规划的高度校正,返回楼板/通道结构的顶面高度
///
/// 应用场景:
/// - 楼板结构:物体在楼板顶面行驶,而不是底面
/// - 走廊/通道:返回可行驶表面的高度
/// - 路径规划:确保物体路径点位于正确的行驶表面
/// </summary>
/// <param name="position">位置坐标</param>
/// <param name="channelItems">通道模型项集合(包含楼板、走廊等可通行结构)</param>
/// <returns>通道顶面高度(米),即物体实际行驶的表面高度</returns>
public double GetChannelTopHeight(Point3D position, IEnumerable<ModelItem> channelItems)
{
try
{
//LogManager.Info($"[高度检测] 🔍 开始检测位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 的顶面高度,通道数={channelItems?.Count() ?? 0}");
// 寻找包含该位置的通道
var containingChannel = FindContainingChannel(position, channelItems);
if (containingChannel == null)
{
double fallbackElevation = _coordinateSystem.GetElevation(position);
LogManager.Info($"[高度检测] ❌ 位置 ({position.X:F2}, {position.Y:F2}, {position.Z:F2}) 未找到包含的通道,使用当前宿主高程: {fallbackElevation:F2}");
return fallbackElevation;
}
//LogManager.Info($"[高度检测] ✅ 找到包含通道: {containingChannel.DisplayName}");
// 检查缓存
var cacheKey = GenerateCacheKey(containingChannel, position);
if (_heightCache.TryGetValue(cacheKey, out var cachedInfo))
{
// 🔥 关键修改:返回通道顶面高度而不是底面高度
var topHeight = cachedInfo.CeilingHeight;
//LogManager.Info($"[高度检测] 使用缓存的顶面高度信息: {topHeight:F2}");
return topHeight;
}
// 分析通道几何,获取高度信息
var heightInfo = AnalyzeChannelGeometry(containingChannel);
if (heightInfo != null)
{
_heightCache[cacheKey] = heightInfo;
// 🔥 关键修改:返回通道顶面高度(楼板表面,物体行驶面)
var topHeight = heightInfo.CeilingHeight;
//LogManager.Info($"[高度检测] 计算得到通道顶面高度: {topHeight:F2}");
return topHeight;
}
// 如果几何分析失败,尝试使用边界框获取顶面高度
var bounds = containingChannel.BoundingBox();
if (bounds != null)
{
var (_, fallbackTopHeight) = GetBoundsElevationRange(bounds, _coordinateSystem);
LogManager.Info($"[高度检测] 使用边界框顶面高度: {fallbackTopHeight:F2}");
return fallbackTopHeight;
}
// 最后的备选方案
double finalFallbackElevation = _coordinateSystem.GetElevation(position);
LogManager.Warning($"[高度检测] ⚠️ 无法获取通道顶面高度,使用当前宿主高程: {finalFallbackElevation:F2}");
return finalFallbackElevation;
}
catch (Exception ex)
{
LogManager.Error($"[高度检测] 检测顶面高度时发生错误: {ex.Message}");
return _coordinateSystem.GetElevation(position);
}
}
/// <summary>
/// 分析通道几何,提取高度信息
/// </summary>
/// <param name="channel">通道模型项</param>
/// <returns>通道高度信息</returns>
public ChannelHeightInfo AnalyzeChannelGeometry(ModelItem channel)
{
try
{
if (channel?.Geometry == null)
{
LogManager.Warning("[高度检测] 通道没有几何信息");
return null;
}
// 获取通道的边界框
var bounds = channel.Geometry.BoundingBox;
if (bounds == null)
{
LogManager.Warning("[高度检测] 无法获取通道边界框");
return null;
}
var (floorHeight, ceilingHeight) = GetBoundsElevationRange(bounds, _coordinateSystem);
// 创建高度信息对象
var heightInfo = new ChannelHeightInfo
{
FloorHeight = floorHeight,
CeilingHeight = ceilingHeight,
Type = ChannelType.Other, // 不再依赖类型判断
HeightProfile = new List<HeightSample>()
};
// 采样通道高度剖面
SampleChannelHeightProfile(channel, heightInfo);
return heightInfo;
}
catch (Exception ex)
{
LogManager.Error($"[高度检测] 分析通道几何时发生错误: {ex.Message}");
return null;
}
}
/// <summary>
/// 寻找包含指定位置的通道
/// </summary>
/// <param name="position">位置点</param>
/// <param name="channelItems">通道集合</param>
/// <returns>包含该位置的通道</returns>
private ModelItem FindContainingChannel(Point3D position, IEnumerable<ModelItem> channelItems)
{
if (channelItems == null) return null;
foreach (var channel in channelItems)
{
if (IsPositionInChannel(position, channel))
{
//LogManager.Debug($"[高度检测] 找到包含位置的通道: {channel.DisplayName}");
return channel;
}
}
return null;
}
/// <summary>
/// 检查位置是否在通道内
/// </summary>
/// <param name="position">位置点</param>
/// <param name="channel">通道模型项</param>
/// <returns>是否在通道内</returns>
private bool IsPositionInChannel(Point3D position, ModelItem channel)
{
try
{
if (channel?.Geometry?.BoundingBox == null)
return false;
var bounds = channel.Geometry.BoundingBox;
return IsPositionWithinBounds(position, bounds, _coordinateSystem, 2.0);
}
catch
{
return false;
}
}
/// <summary>
/// 采样通道高度剖面
/// </summary>
/// <param name="channel">通道模型项</param>
/// <param name="heightInfo">高度信息对象</param>
private void SampleChannelHeightProfile(ModelItem channel, ChannelHeightInfo heightInfo)
{
try
{
var bounds = channel.Geometry.BoundingBox;
var sampleCount = 10; // 采样10个点
// 沿通道长度方向采样
for (int i = 0; i < sampleCount; i++)
{
double ratio = (double)i / (sampleCount - 1);
var samplePosition = CreateHeightProfileSamplePoint(bounds, _coordinateSystem, ratio);
var (floorHeight, ceilingHeight) = GetBoundsElevationRange(bounds, _coordinateSystem);
var sample = new HeightSample
{
Position = samplePosition,
FloorHeight = floorHeight,
CeilingHeight = ceilingHeight
};
heightInfo.HeightProfile.Add(sample);
}
}
catch (Exception ex)
{
LogManager.Warning($"[高度检测] 采样高度剖面时发生错误: {ex.Message}");
}
}
/// <summary>
/// 根据位置在通道中计算精确高度
/// </summary>
/// <param name="position">位置点</param>
/// <param name="heightInfo">通道高度信息</param>
/// <param name="channel">通道模型项</param>
/// <returns>精确高度</returns>
private double CalculatePreciseHeightAtPosition(Point3D position, ChannelHeightInfo heightInfo, ModelItem channel)
{
LogManager.Info($"[高度计算] 开始精确表面高度检测: 位置({position.X:F2}, {position.Y:F2})");
// 方法1: 尝试使用几何射线投射进行精确表面检测
if (TryGeometricRaycast(position, channel, out double raycastHeight))
{
LogManager.Info($"[高度计算] ✅ 几何射线投射成功: {raycastHeight:F2}");
return raycastHeight;
}
// 方法2: 如果几何射线投射失败尝试使用View API作为备选
if (TryGetGeometricSurfaceHeight(position, channel, out double viewApiHeight))
{
LogManager.Info($"[高度计算] ✅ View API备选检测成功: {viewApiHeight:F2}");
return viewApiHeight;
}
// 方法3: 最后使用几何分析
var surfaceHeight = AnalyzeSurfaceHeightAtPosition(position, channel, heightInfo.FloorHeight, heightInfo.CeilingHeight);
LogManager.Info($"[高度计算] 📐 几何分析结果: {surfaceHeight:F2}");
return surfaceHeight;
}
/// <summary>
/// 从高度剖面插值计算高度
/// </summary>
/// <param name="position">位置点</param>
/// <param name="heightProfile">高度剖面</param>
/// <returns>插值高度</returns>
private double InterpolateHeightFromProfile(Point3D position, List<HeightSample> heightProfile)
{
if (heightProfile.Count == 0) return 0;
if (heightProfile.Count == 1) return heightProfile[0].CeilingHeight;
// 找到最近的两个采样点进行插值
var closestSamples = heightProfile
.OrderBy(s => CalculatePlanarDistance(s.Position, position, _coordinateSystem))
.Take(2)
.ToList();
if (closestSamples.Count == 1)
{
return closestSamples[0].CeilingHeight;
}
// 线性插值
var sample1 = closestSamples[0];
var sample2 = closestSamples[1];
var distance1 = CalculatePlanarDistance(sample1.Position, position, _coordinateSystem);
var distance2 = CalculatePlanarDistance(sample2.Position, position, _coordinateSystem);
var totalDistance = distance1 + distance2;
if (totalDistance < 0.001) return sample1.CeilingHeight;
var weight1 = 1.0 - (distance1 / totalDistance);
var weight2 = 1.0 - (distance2 / totalDistance);
return (sample1.CeilingHeight * weight1 + sample2.CeilingHeight * weight2) / (weight1 + weight2);
}
/// <summary>
/// 使用射线投射获取高度
/// </summary>
/// <param name="position">位置点</param>
/// <param name="channel">通道模型项</param>
/// <returns>射线投射得到的高度</returns>
private double GetHeightByRaycast(Point3D position, ModelItem channel)
{
try
{
// 尝试使用Navisworks几何分析获取更精确的表面高度
if (channel?.Geometry?.BoundingBox != null)
{
var bbox = channel.Geometry.BoundingBox;
if (IsPositionWithinBounds(position, bbox, _coordinateSystem, 0.0))
{
// 位置在通道范围内,尝试分析表面高度
var (floorHeight, ceilingHeight) = GetBoundsElevationRange(bbox, _coordinateSystem);
var surfaceHeight = AnalyzeSurfaceHeightAtPosition(position, channel, floorHeight, ceilingHeight);
LogManager.Debug($"[射线投射] 表面高度分析结果: {surfaceHeight:F2}");
return surfaceHeight;
}
else
{
LogManager.Warning($"[射线投射] 位置({position.X:F2}, {position.Y:F2}, {position.Z:F2})不在通道范围内");
return _coordinateSystem.GetElevation(position);
}
}
double fallbackElevation = _coordinateSystem.GetElevation(position);
LogManager.Warning($"[射线投射] 通道几何信息无效,使用当前宿主高程: {fallbackElevation:F2}");
return fallbackElevation;
}
catch (Exception ex)
{
LogManager.Error($"[射线投射] 射线投射计算失败: {ex.Message}");
return _coordinateSystem.GetElevation(position);
}
}
/// <summary>
/// 分析指定位置的表面高度
/// </summary>
/// <param name="position">位置</param>
/// <param name="channel">通道模型</param>
/// <param name="floorHeight">地面高度</param>
/// <param name="ceilingHeight">顶面高度</param>
/// <returns>表面高度</returns>
private double AnalyzeSurfaceHeightAtPosition(Point3D position, ModelItem channel, double floorHeight, double ceilingHeight)
{
try
{
LogManager.Info($"[表面分析] 开始分析位置({position.X:F2}, {position.Y:F2})的实际表面高度");
LogManager.Info($"[表面分析] 通道边界: 地面={floorHeight:F2}, 顶面={ceilingHeight:F2}");
// 尝试多种方法获取真实表面高度
// 方法1: 使用射线投射从上方检测表面
var raycastHeight = TryRaycastFromAbove(position, channel, ceilingHeight);
if (raycastHeight.HasValue)
{
LogManager.Info($"[表面分析] ✅ 射线投射检测到表面高度: {raycastHeight.Value:F2}");
return raycastHeight.Value;
}
// 方法2: 使用多点采样获取局部表面高度
var sampledHeight = TryMultiPointSampling(position, channel);
if (sampledHeight.HasValue)
{
LogManager.Info($"[表面分析] ✅ 多点采样检测到表面高度: {sampledHeight.Value:F2}");
return sampledHeight.Value;
}
// 方法3: 作为最后手段,返回几何中心高度
var midHeight = (floorHeight + ceilingHeight) / 2.0;
LogManager.Warning($"[表面分析] ⚠️ 无法检测到精确表面,使用几何中心高度: {midHeight:F2}");
return midHeight;
}
catch (Exception ex)
{
LogManager.Error($"[表面分析] 表面高度分析失败: {ex.Message},使用顶面高度");
return ceilingHeight;
}
}
/// <summary>
/// 尝试获取几何表面高度使用View投影和PickItemFromPoint方法
/// </summary>
private bool TryGetGeometricSurfaceHeight(Point3D position, ModelItem channel, out double height)
{
height = 0;
try
{
// 获取当前激活的视图
var activeView = Application.ActiveDocument?.ActiveView;
if (activeView == null)
{
LogManager.Debug($"[几何分析] 无法获取活动视图");
return false;
}
// 将3D世界坐标投影到屏幕坐标
if (TryProjectWorldToScreen(position, activeView, out GridPoint2D screenPoint))
{
// 检查屏幕坐标是否在视图范围内
if (screenPoint.X >= 0 && screenPoint.X < activeView.Width &&
screenPoint.Y >= 0 && screenPoint.Y < activeView.Height)
{
// 使用PickItemFromPoint获取该屏幕位置的实际表面点
var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y);
if (pickResult != null)
{
LogManager.Debug($"[几何分析] PickItemFromPoint结果: 模型={pickResult.ModelItem?.DisplayName ?? "NULL"}, 3D点=({pickResult.Point.X:F2}, {pickResult.Point.Y:F2}, {pickResult.Point.Z:F2})");
// 验证拾取到的模型是否为目标通道(或其子项)
if (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel))
{
height = GetPickedSurfaceElevation(
pickResult.Point.X,
pickResult.Point.Y,
pickResult.Point.Z,
_coordinateSystem.Type);
LogManager.Info($"[几何分析] ✅ 使用投影法获得精确表面高度: {height:F2},通道: {channel.DisplayName}");
return true;
}
else
{
LogManager.Debug($"[几何分析] 拾取到的模型不是目标通道: 拾取={pickResult.ModelItem?.DisplayName}, 目标={channel.DisplayName}");
}
}
else
{
LogManager.Debug($"[几何分析] PickItemFromPoint在屏幕坐标 ({screenPoint.X:F1}, {screenPoint.Y:F1}) 处未找到模型");
}
}
else
{
LogManager.Debug($"[几何分析] 投影的屏幕坐标 ({screenPoint.X:F1}, {screenPoint.Y:F1}) 超出视图范围 ({activeView.Width}x{activeView.Height})");
}
}
else
{
LogManager.Debug($"[几何分析] 3D坐标投影到屏幕失败");
}
return false;
}
catch (Exception ex)
{
LogManager.Warning($"[几何分析] 几何表面高度获取失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 将3D世界坐标投影到屏幕坐标
/// </summary>
private bool TryProjectWorldToScreen(Point3D worldPoint, View view, out GridPoint2D screenPoint)
{
screenPoint = new GridPoint2D(0, 0);
try
{
// 使用Navisworks View API的ProjectPoint方法进行精确投影
var projectionResult = view.ProjectPoint(worldPoint, false, false);
if (projectionResult != null)
{
screenPoint = new GridPoint2D((int)projectionResult.X, (int)projectionResult.Y);
LogManager.Debug($"[投影] 3D世界坐标 ({worldPoint.X:F2}, {worldPoint.Y:F2}, {worldPoint.Z:F2}) 投影到屏幕坐标 ({projectionResult.X:F1}, {projectionResult.Y:F1})");
return true;
}
else
{
LogManager.Debug($"[投影] 投影失败ProjectPoint返回null");
return false;
}
}
catch (Exception ex)
{
LogManager.Debug($"[投影] 投影计算失败: {ex.Message}");
return false;
}
}
/// <summary>
/// 计算位置在通道中的相对位置
/// </summary>
private Point3D CalculateRelativePositionInChannel(Point3D position, BoundingBox3D bbox)
{
var (positionH1, positionH2) = _coordinateSystem.GetHorizontalCoords(position);
var (minH1, maxH1, minH2, maxH2) = _coordinateSystem.GetHorizontalRange(bbox);
var (minElevation, maxElevation) = GetBoundsElevationRange(bbox, _coordinateSystem);
var relativeX = Math.Abs(maxH1 - minH1) < 1e-9 ? 0.0 : (positionH1 - minH1) / (maxH1 - minH1);
var relativeY = Math.Abs(maxH2 - minH2) < 1e-9 ? 0.0 : (positionH2 - minH2) / (maxH2 - minH2);
var currentElevation = _coordinateSystem.GetElevation(position);
var relativeZ = Math.Abs(maxElevation - minElevation) < 1e-9 ? 0.0 : (currentElevation - minElevation) / (maxElevation - minElevation);
return new Point3D(relativeX, relativeY, relativeZ);
}
/// <summary>
/// 检查两个边界框是否重叠
/// </summary>
private bool BoundingBoxesOverlap(BoundingBox3D bbox1, BoundingBox3D bbox2)
{
return !(bbox1.Max.X < bbox2.Min.X || bbox2.Max.X < bbox1.Min.X ||
bbox1.Max.Y < bbox2.Min.Y || bbox2.Max.Y < bbox1.Min.Y ||
bbox1.Max.Z < bbox2.Min.Z || bbox2.Max.Z < bbox1.Min.Z);
}
/// <summary>
/// 生成缓存键
/// </summary>
/// <param name="channel">通道模型项</param>
/// <param name="position">位置点</param>
/// <returns>缓存键</returns>
private string GenerateCacheKey(ModelItem channel, Point3D position)
{
// 使用通道的实例ID和位置的网格坐标作为缓存键
var (h1, h2) = _coordinateSystem.GetHorizontalCoords(position);
var gridX = (int)(h1 / 1.0);
var gridY = (int)(h2 / 1.0);
return $"{channel.InstanceGuid}_{gridX}_{gridY}";
}
/// <summary>
/// 清除高度缓存
/// </summary>
public void ClearCache()
{
_heightCache.Clear();
LogManager.Info("[高度检测] 高度缓存已清除");
}
/// <summary>
/// 获取缓存统计信息
/// </summary>
/// <returns>缓存项数量</returns>
public int GetCacheCount()
{
return _heightCache.Count;
}
/// <summary>
/// 尝试从上方进行射线投射检测表面高度
/// </summary>
/// <param name="position">目标位置</param>
/// <param name="channel">通道模型</param>
/// <param name="maxHeight">最大高度</param>
/// <returns>检测到的表面高度如果失败则返回null</returns>
private double? TryRaycastFromAbove(Point3D position, ModelItem channel, double maxHeight)
{
try
{
LogManager.Info($"[射线投射] 从上方({position.X:F2}, {position.Y:F2}, {maxHeight:F2})向下投射射线");
// 获取当前活动视图
var activeView = Application.ActiveDocument?.ActiveView;
if (activeView == null)
{
LogManager.Debug($"[射线投射] 无法获取活动视图");
return null;
}
// 从通道上方的点开始,向下投射多条射线
var testHeights = new[] { maxHeight + 100, maxHeight + 50, maxHeight + 10 };
foreach (var testHeight in testHeights)
{
var testPoint = _coordinateSystem.SetElevation(position, testHeight);
// 将3D点投影到屏幕
if (TryProjectWorldToScreen(testPoint, activeView, out GridPoint2D screenPoint))
{
// 检查屏幕坐标是否在视图范围内
if (screenPoint.X >= 0 && screenPoint.X < activeView.Width &&
screenPoint.Y >= 0 && screenPoint.Y < activeView.Height)
{
// 使用PickItemFromPoint获取该屏幕位置的实际表面点
var pickResult = activeView.PickItemFromPoint(screenPoint.X, screenPoint.Y);
if (pickResult != null && (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel)))
{
double detectedElevation = _coordinateSystem.GetElevation(pickResult.Point);
LogManager.Info($"[射线投射] ✅ 成功检测到表面点高程: {detectedElevation:F2}");
return detectedElevation;
}
}
}
}
LogManager.Debug($"[射线投射] 所有射线投射尝试均失败");
return null;
}
catch (Exception ex)
{
LogManager.Warning($"[射线投射] 射线投射失败: {ex.Message}");
return null;
}
}
/// <summary>
/// 尝试COM API几何射线投射获取精确表面高度
/// </summary>
/// <param name="position">目标位置</param>
/// <param name="channel">通道模型</param>
/// <param name="surfaceHeight">输出的表面高度</param>
/// <returns>是否成功获取表面高度</returns>
private bool TryGeometricRaycast(Point3D position, ModelItem channel, out double surfaceHeight)
{
surfaceHeight = 0.0;
try
{
LogManager.Info($"[COM几何射线] 开始从ModelItem提取三角形几何数据: {channel.DisplayName}");
// 使用COM API提取三角形几何数据
var triangles = GeometryHelper.ExtractTriangles(new[] { channel });
if (triangles.Count == 0)
{
LogManager.Warning($"[COM几何射线] 未能从ModelItem提取到三角形数据: {channel.DisplayName}");
return false;
}
LogManager.Info($"[COM几何射线] 成功提取到 {triangles.Count} 个三角形,开始射线-三角形交点计算");
// 执行垂直射线与三角形的交点检测
var intersectionPoints = PerformRayTriangleIntersection(position, triangles);
if (intersectionPoints.Count > 0)
{
// 选择最高的交点作为表面高度
surfaceHeight = intersectionPoints.Max();
LogManager.Info($"[COM几何射线] ✅ 射线-三角形交点检测成功: 找到 {intersectionPoints.Count} 个交点最高点Z={surfaceHeight:F2}");
return true;
}
else
{
LogManager.Warning($"[COM几何射线] 射线-三角形交点检测失败: 位置({position.X:F2}, {position.Y:F2})未与任何三角形相交");
return false;
}
}
catch (Exception ex)
{
LogManager.Error($"[COM几何射线] COM API几何射线投射出错: {ex.Message}");
return false;
}
}
/// <summary>
/// 执行射线与三角形的交点检测
/// </summary>
/// <param name="position">射线起点的水平坐标</param>
/// <param name="triangles">三角形列表</param>
/// <returns>交点的高度值列表</returns>
private List<double> PerformRayTriangleIntersection(Point3D position, List<Triangle3D> triangles)
{
var intersectionPoints = new List<double>();
try
{
// 获取坐标系的垂直扫描方向
var scanDirection = _coordinateSystem.VerticalScanDirection;
// 计算三角形在垂直方向上的最大范围
double maxHeight = 0;
if (triangles.Count > 0)
{
maxHeight = triangles.Max(t =>
Math.Max(
_coordinateSystem.GetElevation(t.Point1),
Math.Max(
_coordinateSystem.GetElevation(t.Point2),
_coordinateSystem.GetElevation(t.Point3)
)
)
);
}
// 从最高点上方开始扫描
var scanStartHeight = maxHeight + 1000.0;
var rayOrigin = _coordinateSystem.SetElevation(position, scanStartHeight);
// 使用坐标系的垂直扫描方向(转换为 Point3D
var rayDirection = new Point3D(scanDirection.X, scanDirection.Y, scanDirection.Z);
int intersectionCount = 0;
foreach (var triangle in triangles)
{
if (GeometryHelper.RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionElevation))
{
intersectionPoints.Add(intersectionElevation);
intersectionCount++;
}
}
LogManager.Debug($"[射线-三角形] 找到 {intersectionCount} 个交点,扫描方向: ({rayDirection.X:F2}, {rayDirection.Y:F2}, {rayDirection.Z:F2})");
return intersectionPoints;
}
catch (Exception ex)
{
LogManager.Error($"[射线-三角形] 射线-三角形交点计算失败: {ex.Message}");
return intersectionPoints;
}
}
/// <summary>
/// 尝试多点采样获取表面高度
/// </summary>
/// <param name="position">中心位置</param>
/// <param name="channel">通道模型</param>
/// <returns>采样得到的表面高度如果失败则返回null</returns>
private double? TryMultiPointSampling(Point3D position, ModelItem channel)
{
try
{
LogManager.Info($"[多点采样] 在位置({position.X:F2}, {position.Y:F2})周围进行多点采样");
var activeView = Application.ActiveDocument?.ActiveView;
if (activeView == null)
{
LogManager.Debug($"[多点采样] 无法获取活动视图");
return null;
}
var validHeights = new List<double>();
var sampleRadius = 50.0; // 50单位半径内采样
var samplePoints = new[]
{
CreatePlanarOffsetPoint(position, _coordinateSystem, 0.0, 0.0),
CreatePlanarOffsetPoint(position, _coordinateSystem, sampleRadius, 0.0),
CreatePlanarOffsetPoint(position, _coordinateSystem, -sampleRadius, 0.0),
CreatePlanarOffsetPoint(position, _coordinateSystem, 0.0, sampleRadius),
CreatePlanarOffsetPoint(position, _coordinateSystem, 0.0, -sampleRadius),
};
foreach (var samplePoint in samplePoints)
{
if (TryProjectWorldToScreen(samplePoint, activeView, out GridPoint2D screenPoint))
{
if (screenPoint.X >= 0 && screenPoint.X < activeView.Width &&
screenPoint.Y >= 0 && screenPoint.Y < activeView.Height)
{
var pickResult = activeView.PickItemFromPoint((int)screenPoint.X, (int)screenPoint.Y);
if (pickResult != null && (pickResult.ModelItem.Equals(channel) || ModelItemAnalysisHelper.IsChildOf(pickResult.ModelItem, channel)))
{
double detectedElevation = _coordinateSystem.GetElevation(pickResult.Point);
validHeights.Add(detectedElevation);
LogManager.Debug($"[多点采样] 采样点({samplePoint.X:F1}, {samplePoint.Y:F1}, {samplePoint.Z:F1})高程: {detectedElevation:F2}");
}
}
}
}
if (validHeights.Count > 0)
{
// 使用中位数减少异常值影响
validHeights.Sort();
var medianHeight = validHeights[validHeights.Count / 2];
LogManager.Info($"[多点采样] ✅ 采样成功,采样点数={validHeights.Count},中位数高度={medianHeight:F2}");
return medianHeight;
}
LogManager.Debug($"[多点采样] 没有找到有效的采样点");
return null;
}
catch (Exception ex)
{
LogManager.Warning($"[多点采样] 多点采样失败: {ex.Message}");
return null;
}
}
private static (double min, double max) GetBoundsElevationRange(BoundingBox3D bounds, ICoordinateSystem coordinateSystem)
{
if (bounds == null)
{
throw new ArgumentNullException(nameof(bounds));
}
if (coordinateSystem == null)
{
throw new ArgumentNullException(nameof(coordinateSystem));
}
return GetBoundsElevationRange(
bounds.Min.X, bounds.Min.Y, bounds.Min.Z,
bounds.Max.X, bounds.Max.Y, bounds.Max.Z,
coordinateSystem.Type);
}
private static Point3D CreateHeightProfileSamplePoint(BoundingBox3D bounds, ICoordinateSystem coordinateSystem, double ratio)
{
if (bounds == null)
{
throw new ArgumentNullException(nameof(bounds));
}
if (coordinateSystem == null)
{
throw new ArgumentNullException(nameof(coordinateSystem));
}
return CreateHeightProfileSamplePoint(
bounds.Min.X, bounds.Min.Y, bounds.Min.Z,
bounds.Max.X, bounds.Max.Y, bounds.Max.Z,
coordinateSystem.Type,
ratio);
}
private static bool IsPositionWithinBounds(Point3D position, BoundingBox3D bounds, ICoordinateSystem coordinateSystem, double elevationTolerance)
{
if (position == null || bounds == null || coordinateSystem == null)
{
return false;
}
var (positionH1, positionH2) = coordinateSystem.GetHorizontalCoords(position);
var (minH1, maxH1, minH2, maxH2) = coordinateSystem.GetHorizontalRange(bounds);
var (minElevation, maxElevation) = coordinateSystem.GetHeightRange(bounds);
var elevation = coordinateSystem.GetElevation(position);
return positionH1 >= minH1 && positionH1 <= maxH1 &&
positionH2 >= minH2 && positionH2 <= maxH2 &&
elevation >= minElevation - elevationTolerance &&
elevation <= maxElevation + elevationTolerance;
}
private static double CalculatePlanarDistance(Point3D pointA, Point3D pointB, ICoordinateSystem coordinateSystem)
{
var (aH1, aH2) = coordinateSystem.GetHorizontalCoords(pointA);
var (bH1, bH2) = coordinateSystem.GetHorizontalCoords(pointB);
return Math.Sqrt(Math.Pow(aH1 - bH1, 2) + Math.Pow(aH2 - bH2, 2));
}
private static Point3D CreatePlanarOffsetPoint(Point3D position, ICoordinateSystem coordinateSystem, double deltaH1, double deltaH2)
{
var (h1, h2) = coordinateSystem.GetHorizontalCoords(position);
var elevation = coordinateSystem.GetElevation(position);
return coordinateSystem.CreatePoint(h1 + deltaH1, h2 + deltaH2, elevation);
}
private static (double min, double max) GetBoundsElevationRange(
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
CoordinateSystemType coordinateSystemType)
{
var adapter = new HostCoordinateAdapter(coordinateSystemType);
double elevation1 = HostPlanarGridHelper.GetElevation3(
new System.Numerics.Vector3((float)minX, (float)minY, (float)minZ),
adapter);
double elevation2 = HostPlanarGridHelper.GetElevation3(
new System.Numerics.Vector3((float)maxX, (float)maxY, (float)maxZ),
adapter);
return (Math.Min(elevation1, elevation2), Math.Max(elevation1, elevation2));
}
private static Point3D CreateHeightProfileSamplePoint(
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
CoordinateSystemType coordinateSystemType,
double ratio)
{
var samplePoint = CreateHeightProfileSamplePoint3(
minX, minY, minZ,
maxX, maxY, maxZ,
coordinateSystemType,
ratio);
return new Point3D(samplePoint.X, samplePoint.Y, samplePoint.Z);
}
private static System.Numerics.Vector3 CreateHeightProfileSamplePoint3(
double minX, double minY, double minZ,
double maxX, double maxY, double maxZ,
CoordinateSystemType coordinateSystemType,
double ratio)
{
var adapter = new HostCoordinateAdapter(coordinateSystemType);
var hostMin = new System.Numerics.Vector3((float)minX, (float)minY, (float)minZ);
var hostMax = new System.Numerics.Vector3((float)maxX, (float)maxY, (float)maxZ);
var (minH1, maxH1, minH2, maxH2) = HostPlanarGridHelper.GetHorizontalRange3(hostMin, hostMax, adapter);
var (minElevation, _) = GetBoundsElevationRange(minX, minY, minZ, maxX, maxY, maxZ, coordinateSystemType);
double sampleH1 = minH1 + (maxH1 - minH1) * ratio;
double sampleH2 = minH2 + (maxH2 - minH2) * ratio;
return HostPlanarGridHelper.CreateHostPoint3(sampleH1, sampleH2, minElevation, adapter);
}
private static double GetPickedSurfaceElevation(
double pointX,
double pointY,
double pointZ,
CoordinateSystemType coordinateSystemType)
{
var adapter = new HostCoordinateAdapter(coordinateSystemType);
return HostPlanarGridHelper.GetElevation3(
new System.Numerics.Vector3((float)pointX, (float)pointY, (float)pointZ),
adapter);
}
}
/// <summary>
/// 通道高度信息
/// </summary>
public class ChannelHeightInfo
{
/// <summary>
/// 地面高度(米)
/// </summary>
public double FloorHeight { get; set; }
/// <summary>
/// 顶面高度(米)
/// </summary>
public double CeilingHeight { get; set; }
/// <summary>
/// 通道类型
/// </summary>
public ChannelType Type { get; set; }
/// <summary>
/// 高度剖面采样点
/// </summary>
public List<HeightSample> HeightProfile { get; set; } = new List<HeightSample>();
}
/// <summary>
/// 高度采样点
/// </summary>
public class HeightSample
{
/// <summary>
/// 采样位置
/// </summary>
public Point3D Position { get; set; }
/// <summary>
/// 该位置的地面高度
/// </summary>
public double FloorHeight { get; set; }
/// <summary>
/// 该位置的顶面高度
/// </summary>
public double CeilingHeight { get; set; }
}
/// <summary>
/// 通道类型枚举
/// </summary>
public enum ChannelType
{
/// <summary>
/// 走廊/通道
/// </summary>
Corridor,
/// <summary>
/// 楼梯
/// </summary>
Stairs,
/// <summary>
/// 坡道
/// </summary>
Ramp,
/// <summary>
/// 电梯
/// </summary>
Elevator,
/// <summary>
/// 其他
/// </summary>
Other
}
}