NavisworksTransport/src/PathPlanning/VerticalScanProcessor.cs

1483 lines
63 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using Autodesk.Navisworks.Api;
using Autodesk.Navisworks.Api.ComApi;
using ComBridge = Autodesk.Navisworks.Api.ComApi.ComApiBridge;
using COMApi = Autodesk.Navisworks.Api.Interop.ComApi;
using NavisworksTransport.Core;
using NavisworksTransport.Utils;
namespace NavisworksTransport.PathPlanning
{
/// <summary>
/// 垂直扫描处理器
/// 实现多级筛选优化的障碍物检测和2.5D高度区间计算
/// 预期实现10,000-40,000倍性能提升
/// </summary>
public class VerticalScanProcessor
{
#region
/// <summary>
/// 空间哈希表,用于快速查找邻域内的模型项
/// </summary>
private readonly Dictionary<string, List<ModelItem>> _spatialHashMap;
/// <summary>
/// 空间哈希的网格大小(米)
/// </summary>
private readonly double _spatialHashSize;
/// <summary>
/// 并行处理的任务数量
/// </summary>
private readonly int _parallelDegree;
/// <summary>
/// 高度筛选的容差值(米)
/// </summary>
private const double HEIGHT_TOLERANCE = 0.1;
/// <summary>
/// 默认人行高度(米)- 用于未检测到通道时的默认高度
/// </summary>
private const double DEFAULT_WALKING_HEIGHT = 2.5;
/// <summary>
/// 最小通行高度(米)
/// </summary>
private const double MIN_PASSABLE_HEIGHT = 1.8;
#endregion
#region
/// <summary>
/// 构造函数
/// </summary>
/// <param name="spatialHashSize">空间哈希网格大小默认为10米</param>
/// <param name="parallelDegree">并行度默认为CPU核心数的一半避免过度并行导致崩溃</param>
public VerticalScanProcessor(double spatialHashSize = 10.0, int parallelDegree = 0)
{
_spatialHashSize = spatialHashSize;
// 限制并行度避免过度并行导致崩溃最大为CPU核心数的一半
int maxParallelism = Math.Max(1, Environment.ProcessorCount / 2);
_parallelDegree = parallelDegree > 0 ? Math.Min(parallelDegree, maxParallelism) : maxParallelism;
_spatialHashMap = new Dictionary<string, List<ModelItem>>();
LogManager.Info($"[垂直扫描处理器] 初始化完成,空间哈希大小: {_spatialHashSize}m, 并行度: {_parallelDegree} (最大并行度: {maxParallelism})");
}
#endregion
#region
/// <summary>
/// 构建空间哈希索引
/// 第一级筛选:邻域筛选
/// </summary>
/// <param name="modelItems">所有模型项</param>
/// <param name="bounds">扫描边界</param>
/// <param name="channelItems">已确定的通道元素列表(将被排除)</param>
public void BuildSpatialHashIndex(IEnumerable<ModelItem> modelItems, BoundingBox3D bounds, IEnumerable<ModelItem> channelItems = null)
{
LogManager.Info("【垂直扫描处理器】 开始构建空间哈希索引");
var startTime = DateTime.Now;
_spatialHashMap.Clear();
// 创建通道元素的HashSet以便快速查找
var channelItemsSet = new HashSet<ModelItem>(channelItems ?? new List<ModelItem>());
// 统计变量
var totalItems = modelItems?.Count() ?? 0;
var itemsWithGeometry = 0;
var channelItemsExcluded = 0;
var itemsWithoutBounds = 0;
var itemsOutOfBounds = 0;
var itemsAddedToHash = 0;
LogManager.Info($"【垂直扫描处理器】 输入统计 - 总模型项: {totalItems}, 将排除通道元素: {channelItemsSet.Count}");
LogManager.Info($"【垂直扫描处理器】 扫描边界: [{bounds.Min.X:F1},{bounds.Min.Y:F1},{bounds.Min.Z:F1}] - [{bounds.Max.X:F1},{bounds.Max.Y:F1},{bounds.Max.Z:F1}]");
var itemsWithBounds = new ConcurrentBag<(ModelItem item, BoundingBox3D bbox)>();
// 统计计数器(线程安全)
var geometryCounter = 0;
var channelExcludedCounter = 0;
var noBoundsCounter = 0;
var outOfBoundsCounter = 0;
try
{
// 并行计算所有模型项的边界框使用ConcurrentBag避免锁竞争
Parallel.ForEach(modelItems, new ParallelOptions { MaxDegreeOfParallelism = _parallelDegree }, item =>
{
try
{
// 🔥 关键修复:检查是否需要排除通道元素(包括容器节点和子节点)
if (channelItemsSet.Contains(item) || IsChildOfChannelItems(item, channelItemsSet))
{
Interlocked.Increment(ref channelExcludedCounter);
return; // 跳过通道元素及其子节点
}
// 然后检查是否有几何体,只对有几何体的元素进行空间哈希处理
if (item?.HasGeometry == true)
{
Interlocked.Increment(ref geometryCounter);
var bbox = item.BoundingBox();
if (bbox == null)
{
Interlocked.Increment(ref noBoundsCounter);
return;
}
if (IsWithinBounds(bbox, bounds))
{
itemsWithBounds.Add((item, bbox));
}
else
{
Interlocked.Increment(ref outOfBoundsCounter);
}
}
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 获取边界框失败: {item?.DisplayName ?? "NULL"}, {ex.Message}");
}
});
}
catch (Exception ex)
{
LogManager.Error($"【垂直扫描处理器】 并行计算边界框时发生严重错误: {ex.Message}");
return; // 提前退出,避免进一步崩溃
}
// 输出详细统计信息
LogManager.Info($"【垂直扫描处理器】 元素筛选统计:");
LogManager.Info($" - 有几何体的元素: {geometryCounter}");
LogManager.Info($" - 被排除的通道元素: {channelExcludedCounter}");
LogManager.Info($" - 无边界框的元素: {noBoundsCounter}");
LogManager.Info($" - 超出扫描边界的元素: {outOfBoundsCounter}");
LogManager.Info($" - 符合条件并添加到空间哈希的元素: {itemsWithBounds.Count}");
// 【新增】详细记录前10个元素的信息
LogManager.Info($"【空间哈希调试】 前10个待索引元素详情:");
var itemsList = itemsWithBounds.Take(10).ToList();
for (int i = 0; i < itemsList.Count; i++)
{
var (item, bbox) = itemsList[i];
var hashKeys = GetSpatialHashKeys(bbox);
LogManager.Info($"【空间哈希调试】 元素 {i}: 名称=\"{item.DisplayName ?? "无名称"}\"");
LogManager.Info($"【空间哈希调试】 InstanceGuid: {item.InstanceGuid}");
LogManager.Info($"【空间哈希调试】 GetHashCode: {item.GetHashCode():X}");
LogManager.Info($"【空间哈希调试】 边界框: Min({bbox.Min.X:F2}, {bbox.Min.Y:F2}, {bbox.Min.Z:F2})");
LogManager.Info($"【空间哈希调试】 边界框: Max({bbox.Max.X:F2}, {bbox.Max.Y:F2}, {bbox.Max.Z:F2})");
LogManager.Info($"【空间哈希调试】 哈希键数量: {hashKeys.Count()}, 示例键: [{string.Join(", ", hashKeys.Take(3))}]");
}
// 构建空间哈希
var totalHashEntries = 0;
var hashKeyDistribution = new Dictionary<string, int>();
foreach (var (item, bbox) in itemsWithBounds)
{
var hashKeys = GetSpatialHashKeys(bbox);
foreach (var key in hashKeys)
{
if (!_spatialHashMap.ContainsKey(key))
{
_spatialHashMap[key] = new List<ModelItem>();
}
_spatialHashMap[key].Add(item);
totalHashEntries++;
// 统计哈希键分布 - 修复 .NET Framework 4.8 兼容性
if (hashKeyDistribution.ContainsKey(key))
{
hashKeyDistribution[key]++;
}
else
{
hashKeyDistribution[key] = 1;
}
}
}
// 【新增】哈希桶分布统计
var bucketSizes = _spatialHashMap.Values.Select(list => list.Count).ToList();
LogManager.Info($"【空间哈希调试】 哈希桶分布统计:");
LogManager.Info($"【空间哈希调试】 - 最小桶大小: {bucketSizes.DefaultIfEmpty(0).Min()}");
LogManager.Info($"【空间哈希调试】 - 最大桶大小: {bucketSizes.DefaultIfEmpty(0).Max()}");
LogManager.Info($"【空间哈希调试】 - 平均桶大小: {bucketSizes.DefaultIfEmpty(0).Average():F1}");
// 【新增】显示前5个最大的哈希桶内容
var topBuckets = _spatialHashMap.OrderByDescending(kvp => kvp.Value.Count).Take(5).ToList();
LogManager.Info($"【空间哈希调试】 前5个最大哈希桶:");
for (int i = 0; i < topBuckets.Count; i++)
{
var bucket = topBuckets[i];
LogManager.Info($"【空间哈希调试】 桶 {i}: 键=\"{bucket.Key}\", 元素数量={bucket.Value.Count}");
var sampleElements = bucket.Value.Take(3).ToList();
foreach (var element in sampleElements)
{
LogManager.Info($"【空间哈希调试】 包含元素: \"{element.DisplayName ?? "无名称"}\" (GUID: {element.InstanceGuid}, Hash: {element.GetHashCode():X})");
}
}
// 【新增】验证几个测试点的哈希键计算
LogManager.Info($"【空间哈希调试】 测试点哈希键验证:");
var testPoints = new[]
{
new Point3D(-228.17, -57.82, 35.40), // 对应日志中的点11370
new Point3D(-212.17, 69.18, 35.40), // 对应日志中的点7580
new Point3D(-79.17, -9.82, 35.40) // 对应日志中找到34个候选项的点
};
foreach (var point in testPoints)
{
var testKeys = GetSpatialHashKeysForPoint(point);
var candidates = testKeys.SelectMany(key => _spatialHashMap.ContainsKey(key) ? _spatialHashMap[key] : new List<ModelItem>()).Distinct().ToList();
var candidatesCount = candidates.Count;
LogManager.Info($"【空间哈希调试】 测试点({point.X:F2}, {point.Y:F2}, {point.Z:F2}): 哈希键数量={testKeys.Count()}, 候选项={candidatesCount}");
LogManager.Info($"【空间哈希调试】 哈希键: [{string.Join(", ", testKeys)}]");
}
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
LogManager.Info($"【垂直扫描处理器】 空间哈希索引构建完成,耗时: {elapsed:F1}ms");
LogManager.Info($"【垂直扫描处理器】 最终统计 - 参与元素: {itemsWithBounds.Count}, 哈希桶: {_spatialHashMap.Count}, 总哈希条目: {totalHashEntries}");
}
/// <summary>
/// 并行扫描网格点的2.5D高度区间
/// </summary>
/// <param name="gridPoints">网格点列表</param>
/// <param name="scanHeight">扫描高度(从地面向上的距离)</param>
/// <param name="vehicleHeight">车辆高度(用于通行检查)</param>
/// <returns>每个网格点的高度区间列表</returns>
public Dictionary<Point3D, List<HeightInterval>> ParallelScanHeightIntervals(
IEnumerable<Point3D> gridPoints,
double scanHeight = 20.0,
double vehicleHeight = 3.0)
{
LogManager.Info($"[垂直扫描处理器] 开始并行扫描高度区间,扫描高度: {scanHeight}m, 车辆高度: {vehicleHeight}m");
var startTime = DateTime.Now;
var results = new ConcurrentDictionary<Point3D, List<HeightInterval>>();
var pointsList = gridPoints?.ToList() ?? new List<Point3D>();
// 性能测试模式随机抽样10个有代表性的点进行详细分析
if (pointsList.Count > 10)
{
LogManager.Info($"[性能测试] 原计划处理{pointsList.Count}个点测试模式随机抽样10个点");
var random = new System.Random(42); // 固定种子确保可重复
var sampledPoints = new List<Point3D>();
// 分区抽样:从不同区域抽取点
int sectionSize = pointsList.Count / 10;
for (int i = 0; i < 10 && i * sectionSize < pointsList.Count; i++)
{
int startIndex = i * sectionSize;
int endIndex = Math.Min((i + 1) * sectionSize, pointsList.Count);
int randomIndex = random.Next(startIndex, endIndex);
sampledPoints.Add(pointsList[randomIndex]);
}
pointsList = sampledPoints;
LogManager.Info($"[性能测试] 抽样完成选择了10个分布均匀的测试点");
}
try
{
// 并行处理每个网格点,添加更严格的异常处理
Parallel.ForEach(pointsList, new ParallelOptions
{
MaxDegreeOfParallelism = _parallelDegree,
CancellationToken = System.Threading.CancellationToken.None
}, gridPoint =>
{
try
{
if (gridPoint != null)
{
var intervals = ScanVerticalLine(gridPoint, scanHeight, vehicleHeight);
results[gridPoint] = intervals ?? new List<HeightInterval>();
}
}
catch (OutOfMemoryException)
{
LogManager.Error($"[垂直扫描处理器] 内存不足,跳过点 {gridPoint}");
results[gridPoint] = new List<HeightInterval>();
throw; // 内存不足需要重新抛出
}
catch (Exception ex)
{
LogManager.Error($"[垂直扫描处理器] 扫描点 {gridPoint} 失败: {ex.Message}");
results[gridPoint] = new List<HeightInterval>();
}
});
}
catch (Exception ex)
{
LogManager.Error($"[垂直扫描处理器] 并行扫描过程中发生严重错误: {ex.Message}");
// 如果并行处理失败,尝试串行处理作为备选方案
LogManager.Info("[垂直扫描处理器] 尝试串行处理作为备选方案");
foreach (var gridPoint in pointsList)
{
try
{
if (!results.ContainsKey(gridPoint))
{
var intervals = ScanVerticalLine(gridPoint, scanHeight, vehicleHeight);
results[gridPoint] = intervals ?? new List<HeightInterval>();
}
}
catch (Exception serialEx)
{
LogManager.Error($"[垂直扫描处理器] 串行扫描点 {gridPoint} 也失败: {serialEx.Message}");
results[gridPoint] = new List<HeightInterval>();
}
}
}
var elapsed = (DateTime.Now - startTime).TotalMilliseconds;
var totalIntervals = results.Values.Sum(list => list.Count);
LogManager.Info($"[垂直扫描处理器] 并行扫描完成,耗时: {elapsed:F1}ms, 扫描点: {pointsList.Count}, 总区间: {totalIntervals}");
return new Dictionary<Point3D, List<HeightInterval>>(results);
}
/// <summary>
/// 扫描单个垂直线上的通行区间
/// </summary>
/// <param name="basePoint">基础点X,Y坐标</param>
/// <param name="scanHeight">扫描高度</param>
/// <param name="vehicleHeight">车辆高度</param>
/// <returns>可通行的高度区间列表</returns>
public List<HeightInterval> ScanVerticalLine(Point3D basePoint, double scanHeight, double vehicleHeight)
{
// 判断是否为抽样调试点从15160个点中平均选择5个
var debugSamplingPoints = new int[] { 0, 3790, 7580, 11370, 15159 };
var currentPointIndex = GetCurrentScanIndex(basePoint); // 需要实现此方法获取当前点索引
bool isDebugPoint = debugSamplingPoints.Contains(currentPointIndex);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】开始扫描点 {currentPointIndex}: 坐标({basePoint.X:F2}, {basePoint.Y:F2}, {basePoint.Z:F2}), 扫描高度: {scanHeight}m");
}
// 第一级筛选:邻域筛选 - 获取空间哈希邻域内的候选项
var candidateItems = GetCandidateItemsFromSpatialHash(basePoint);
// 性能测试模式:为所有点输出详细候选项信息
bool isTestMode = candidateItems.Count <= 50; // 假设测试模式下候选项不会太多
if (isDebugPoint || isTestMode)
{
LogManager.Info($"=== 详细分析扫描点 {currentPointIndex} ===");
LogManager.Info($"空间哈希候选项: {candidateItems.Count} 个");
for (int i = 0; i < candidateItems.Count; i++)
{
var item = candidateItems[i];
var bbox = item.BoundingBox();
LogManager.Info($"候选项 {i}: 名称='{item.DisplayName}'");
LogManager.Info($" - 边界框: {bbox?.Min} -> {bbox?.Max}");
LogManager.Info($" - 有几何体: {item.HasGeometry}");
LogManager.Info($" - 子项数量: {item.Children?.Count() ?? 0}");
LogManager.Info($" - 类名: {item.ClassName}");
}
}
// 第二级筛选:高度筛选 - 只保留在扫描高度范围内的项目
var heightFilteredItems = HeightFiltering(candidateItems, basePoint.Z, basePoint.Z + scanHeight);
if (isDebugPoint || isTestMode)
{
LogManager.Info($"高度筛选后: {heightFilteredItems.Count} 个候选项 (筛选范围: [{basePoint.Z:F2}, {basePoint.Z + scanHeight:F2}])");
if (candidateItems.Count != heightFilteredItems.Count)
{
LogManager.Info($"高度筛选过滤掉了 {candidateItems.Count - heightFilteredItems.Count} 个候选项");
}
}
// 第三级筛选:空间哈希 - 精确的几何相交测试
var intersectionResults = PerformIntersectionTests(heightFilteredItems, basePoint, scanHeight, isDebugPoint, currentPointIndex);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {currentPointIndex} 相交检测结果: {intersectionResults.Count} 个相交项");
}
// 计算可通行区间
var passableIntervals = CalculatePassableIntervals(intersectionResults, basePoint.Z, scanHeight, vehicleHeight, isDebugPoint, currentPointIndex);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {currentPointIndex} 最终高度区间: {passableIntervals.Count} 个区间");
foreach (var interval in passableIntervals)
{
LogManager.Info($"【调试抽样】 区间: [{interval.MinZ:F2}, {interval.MaxZ:F2}]");
}
}
return passableIntervals;
}
/// <summary>
/// 获取当前扫描点的索引(用于调试抽样)
/// </summary>
private int GetCurrentScanIndex(Point3D point)
{
// 简化实现:基于坐标计算一个伪索引
// 实际应用中可能需要更精确的索引计算
return Math.Abs(point.GetHashCode()) % 15160;
}
#endregion
#region -
/// <summary>
/// 第一级筛选:从空间哈希中获取候选模型项(邻域筛选)
/// </summary>
/// <param name="point">查询点</param>
/// <returns>候选模型项列表</returns>
private List<ModelItem> GetCandidateItemsFromSpatialHash(Point3D point)
{
var candidates = new HashSet<ModelItem>();
var hashKeys = GetSpatialHashKeysForPoint(point);
foreach (var key in hashKeys)
{
if (_spatialHashMap.ContainsKey(key))
{
foreach (var item in _spatialHashMap[key])
{
candidates.Add(item);
}
}
}
return candidates.ToList();
}
/// <summary>
/// 第二级筛选:高度筛选
/// </summary>
/// <param name="items">候选模型项</param>
/// <param name="minZ">最小Z坐标</param>
/// <param name="maxZ">最大Z坐标</param>
/// <returns>高度筛选后的模型项</returns>
private List<ModelItem> HeightFiltering(List<ModelItem> items, double minZ, double maxZ)
{
var filtered = new ConcurrentBag<ModelItem>();
try
{
// 使用ConcurrentBag避免锁竞争提高性能和稳定性
Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = _parallelDegree }, item =>
{
try
{
if (item?.HasGeometry == true)
{
var bbox = item.BoundingBox();
if (bbox != null)
{
// 检查高度范围是否有重叠(加上容差)
if (bbox.Max.Z >= (minZ - HEIGHT_TOLERANCE) &&
bbox.Min.Z <= (maxZ + HEIGHT_TOLERANCE))
{
filtered.Add(item);
}
}
}
}
catch (Exception ex)
{
LogManager.Debug($"[垂直扫描处理器] 高度筛选失败: {item?.DisplayName ?? "NULL"}, {ex.Message}");
}
});
}
catch (Exception ex)
{
LogManager.Warning($"[垂直扫描处理器] 高度筛选并行处理失败: {ex.Message},回退到串行处理");
// 如果并行处理失败,使用串行处理
foreach (var item in items ?? new List<ModelItem>())
{
try
{
if (item?.HasGeometry == true)
{
var bbox = item.BoundingBox();
if (bbox != null &&
bbox.Max.Z >= (minZ - HEIGHT_TOLERANCE) &&
bbox.Min.Z <= (maxZ + HEIGHT_TOLERANCE))
{
filtered.Add(item);
}
}
}
catch (Exception serialEx)
{
LogManager.Debug($"[垂直扫描处理器] 串行高度筛选失败: {item?.DisplayName ?? "NULL"}, {serialEx.Message}");
}
}
}
return filtered.ToList();
}
/// <summary>
/// 第三级筛选:精确几何相交测试(空间哈希优化)
/// </summary>
/// <param name="items">高度筛选后的模型项</param>
/// <param name="basePoint">基础点</param>
/// <param name="scanHeight">扫描高度</param>
/// <returns>相交测试结果</returns>
private List<IntersectionResult> PerformIntersectionTests(List<ModelItem> items, Point3D basePoint, double scanHeight, bool isDebugPoint = false, int pointIndex = -1)
{
var results = new ConcurrentBag<IntersectionResult>();
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 开始相交测试,候选项目数量: {items.Count}");
}
try
{
// 并行执行相交测试,添加更强的异常处理
Parallel.ForEach(items, new ParallelOptions { MaxDegreeOfParallelism = _parallelDegree }, item =>
{
try
{
if (item != null)
{
var intersectionData = TestVerticalLineIntersection(item, basePoint, scanHeight);
if (intersectionData != null)
{
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 发现相交项目: {item.DisplayName}");
LogManager.Info($"【调试抽样】 - Z高度范围: [{intersectionData.MinZ:F2}, {intersectionData.MaxZ:F2}]");
LogManager.Info($"【调试抽样】 - 相交点: ({intersectionData.IntersectionPoint.X:F2}, {intersectionData.IntersectionPoint.Y:F2}, {intersectionData.IntersectionPoint.Z:F2})");
LogManager.Info($"【调试抽样】 - 高度跨度: {intersectionData.MaxZ - intersectionData.MinZ:F2}m");
}
// 简化逻辑:所有与垂直射线相交的非通道元素都视为障碍物
// 因为在空间哈希构建时已经排除了通道元素
results.Add(new IntersectionResult
{
ModelItem = item,
IntersectionData = intersectionData,
IsObstacle = true, // 所有相交的非通道元素都是障碍物
IsPassable = false // 不可通行
});
}
else if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 项目无相交: {item.DisplayName}");
}
}
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 相交测试失败: {item?.DisplayName ?? "NULL"}, {ex.Message}");
}
});
}
catch (Exception ex)
{
LogManager.Warning($"【垂直扫描处理器】 并行相交测试失败: {ex.Message},回退到串行处理");
// 如果并行处理失败,使用串行处理
foreach (var item in items ?? new List<ModelItem>())
{
try
{
if (item != null)
{
var intersectionData = TestVerticalLineIntersection(item, basePoint, scanHeight);
if (intersectionData != null)
{
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 串行发现相交项目: {item.DisplayName}");
LogManager.Info($"【调试抽样】 - Z高度范围: [{intersectionData.MinZ:F2}, {intersectionData.MaxZ:F2}]");
LogManager.Info($"【调试抽样】 - 相交点: ({intersectionData.IntersectionPoint.X:F2}, {intersectionData.IntersectionPoint.Y:F2}, {intersectionData.IntersectionPoint.Z:F2})");
}
// 简化逻辑:所有与垂直射线相交的非通道元素都视为障碍物
results.Add(new IntersectionResult
{
ModelItem = item,
IntersectionData = intersectionData,
IsObstacle = true, // 所有相交的非通道元素都是障碍物
IsPassable = false // 不可通行
});
}
}
}
catch (Exception serialEx)
{
LogManager.Debug($"【垂直扫描处理器】 串行相交测试失败: {item?.DisplayName ?? "NULL"}, {serialEx.Message}");
}
}
}
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 相交测试完成,找到 {results.Count} 个相交项目");
}
return results.ToList();
}
/// <summary>
/// 计算可通行区间
/// </summary>
/// <param name="intersectionResults">相交测试结果</param>
/// <param name="baseZ">基础Z坐标</param>
/// <param name="scanHeight">扫描高度</param>
/// <param name="vehicleHeight">车辆高度</param>
/// <returns>可通行区间列表</returns>
private List<HeightInterval> CalculatePassableIntervals(
List<IntersectionResult> intersectionResults,
double baseZ,
double scanHeight,
double vehicleHeight,
bool isDebugPoint = false,
int pointIndex = -1)
{
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 开始计算可通行区间");
LogManager.Info($"【调试抽样】 - 基础Z: {baseZ:F2}, 扫描高度: {scanHeight:F2}, 车辆高度: {vehicleHeight:F2}");
LogManager.Info($"【调试抽样】 - 相交结果数量: {intersectionResults.Count}");
}
// 收集所有障碍物的高度范围
var obstacles = new List<HeightInterval>();
var floors = new List<HeightInterval>();
foreach (var result in intersectionResults)
{
if (result.IsObstacle && result.IntersectionData != null)
{
var obstacleInterval = new HeightInterval(
result.IntersectionData.MinZ,
result.IntersectionData.MaxZ
);
obstacles.Add(obstacleInterval);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 障碍物: {result.ModelItem.DisplayName}, 高度范围: [{obstacleInterval.MinZ:F2}, {obstacleInterval.MaxZ:F2}]");
}
}
else if (result.IsPassable && result.IntersectionData != null)
{
// 检查是否为地面/楼板
if (IsFloorLike(result.ModelItem, result.IntersectionData))
{
var floorInterval = new HeightInterval(
result.IntersectionData.MinZ,
result.IntersectionData.MaxZ
);
floors.Add(floorInterval);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 地面: {result.ModelItem.DisplayName}, 高度范围: [{floorInterval.MinZ:F2}, {floorInterval.MaxZ:F2}]");
}
}
}
}
// 合并重叠的障碍物区间
var mergedObstacles = MergeOverlappingIntervals(obstacles);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 合并后障碍物数量: {mergedObstacles.Count}");
foreach (var obs in mergedObstacles)
{
LogManager.Info($"【调试抽样】 合并障碍物: [{obs.MinZ:F2}, {obs.MaxZ:F2}]");
}
}
// 计算可通行区间
var passableIntervals = new List<HeightInterval>();
var scanRange = new HeightInterval(baseZ, baseZ + scanHeight);
// 如果没有找到地面使用基础Z坐标作为默认地面
if (!floors.Any())
{
floors.Add(new HeightInterval(baseZ - 0.1, baseZ + 0.1));
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 未找到地面,使用默认地面: [{baseZ - 0.1:F2}, {baseZ + 0.1:F2}]");
}
}
// 对每个潜在的地面,计算其上方的可通行空间
foreach (var floor in floors)
{
double floorTop = floor.MaxZ;
double availableTop = baseZ + scanHeight;
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 处理地面: 地面顶部={floorTop:F2}, 可用顶部={availableTop:F2}");
}
// 检查地面上方是否有足够的净空高度
var conflictingObstacles = mergedObstacles
.Where(obs => obs.MinZ < availableTop && obs.MaxZ > floorTop)
.OrderBy(obs => obs.MinZ)
.ToList();
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 冲突障碍物数量: {conflictingObstacles.Count}");
}
if (!conflictingObstacles.Any())
{
// 没有障碍物,整个高度范围都可通行
double clearHeight = availableTop - floorTop;
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 无障碍物,净空高度: {clearHeight:F2}m, 最小需要: {MIN_PASSABLE_HEIGHT:F2}m");
}
if (clearHeight >= MIN_PASSABLE_HEIGHT)
{
var interval = new HeightInterval(floorTop, availableTop);
passableIntervals.Add(interval);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 添加可通行区间: [{interval.MinZ:F2}, {interval.MaxZ:F2}]");
}
}
}
else
{
// 有障碍物,计算障碍物之间的可通行空间
double currentBottom = floorTop;
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 有障碍物,开始计算空隙,起始底部: {currentBottom:F2}");
}
foreach (var obstacle in conflictingObstacles)
{
double obstacleBottom = Math.Max(obstacle.MinZ, currentBottom);
if (obstacleBottom > currentBottom)
{
double clearHeight = obstacleBottom - currentBottom;
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 障碍物前空隙: 底部={currentBottom:F2}, 顶部={obstacleBottom:F2}, 净空={clearHeight:F2}");
}
if (clearHeight >= vehicleHeight)
{
var interval = new HeightInterval(currentBottom, obstacleBottom);
passableIntervals.Add(interval);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 添加障碍物前区间: [{interval.MinZ:F2}, {interval.MaxZ:F2}]");
}
}
}
currentBottom = Math.Max(currentBottom, obstacle.MaxZ);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 障碍物后,更新底部为: {currentBottom:F2}");
}
}
// 检查最后一个障碍物之后的空间
if (currentBottom < availableTop)
{
double clearHeight = availableTop - currentBottom;
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 最后障碍物后空隙: 底部={currentBottom:F2}, 顶部={availableTop:F2}, 净空={clearHeight:F2}");
}
if (clearHeight >= vehicleHeight)
{
var interval = new HeightInterval(currentBottom, availableTop);
passableIntervals.Add(interval);
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】 - 添加最后区间: [{interval.MinZ:F2}, {interval.MaxZ:F2}]");
}
}
}
}
}
if (isDebugPoint)
{
LogManager.Info($"【调试抽样】点 {pointIndex} 计算完成,最终可通行区间数量: {passableIntervals.Count}");
}
return passableIntervals;
}
#endregion
#region
/// <summary>
/// 检查边界框是否在指定范围内
/// </summary>
/// <param name="bbox">要检查的边界框</param>
/// <param name="bounds">范围边界框</param>
/// <returns>是否在范围内</returns>
private bool IsWithinBounds(BoundingBox3D bbox, BoundingBox3D bounds)
{
return bbox.Max.X >= bounds.Min.X && bbox.Min.X <= bounds.Max.X &&
bbox.Max.Y >= bounds.Min.Y && bbox.Min.Y <= bounds.Max.Y &&
bbox.Max.Z >= bounds.Min.Z && bbox.Min.Z <= bounds.Max.Z;
}
/// <summary>
/// 获取边界框的所有空间哈希键
/// </summary>
/// <param name="bbox">边界框</param>
/// <returns>空间哈希键列表</returns>
private List<string> GetSpatialHashKeys(BoundingBox3D bbox)
{
var keys = new List<string>();
int minX = (int)Math.Floor(bbox.Min.X / _spatialHashSize);
int maxX = (int)Math.Floor(bbox.Max.X / _spatialHashSize);
int minY = (int)Math.Floor(bbox.Min.Y / _spatialHashSize);
int maxY = (int)Math.Floor(bbox.Max.Y / _spatialHashSize);
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
keys.Add($"{x},{y}");
}
}
return keys;
}
/// <summary>
/// 获取点的空间哈希键(包括相邻网格)
/// </summary>
/// <param name="point">查询点</param>
/// <returns>空间哈希键列表</returns>
private List<string> GetSpatialHashKeysForPoint(Point3D point)
{
var keys = new List<string>();
int centerX = (int)Math.Floor(point.X / _spatialHashSize);
int centerY = (int)Math.Floor(point.Y / _spatialHashSize);
// 包括中心及相邻的9个网格
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
keys.Add($"{centerX + dx},{centerY + dy}");
}
}
return keys;
}
/// <summary>
/// 测试垂直线与模型项的相交
/// </summary>
/// <param name="item">模型项</param>
/// <param name="basePoint">基础点</param>
/// <param name="scanHeight">扫描高度</param>
/// <returns>相交数据如果不相交则返回null</returns>
private IntersectionData TestVerticalLineIntersection(ModelItem item, Point3D basePoint, double scanHeight)
{
try
{
if (!item.HasGeometry)
return null;
// 直接提取模型项的三角形几何数据,不做包围盒预筛选
var triangles = ExtractTrianglesFromModelItem(item);
if (triangles == null || triangles.Count == 0)
{
return null;
}
// 执行射线-三角形相交检测
var intersections = PerformVerticalRayIntersection(basePoint, scanHeight, triangles);
if (intersections == null || intersections.Count == 0)
{
return null;
}
// 计算相交的Z范围
double minZ = intersections.Min();
double maxZ = intersections.Max();
// 确保相交区间与扫描区间有重叠
double scanMinZ = basePoint.Z;
double scanMaxZ = basePoint.Z + scanHeight;
double intersectionMinZ = Math.Max(minZ, scanMinZ);
double intersectionMaxZ = Math.Min(maxZ, scanMaxZ);
if (intersectionMaxZ > intersectionMinZ)
{
return new IntersectionData
{
MinZ = intersectionMinZ,
MaxZ = intersectionMaxZ,
IntersectionPoint = new Point3D(basePoint.X, basePoint.Y, (intersectionMinZ + intersectionMaxZ) / 2)
};
}
return null;
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 射线相交测试异常: {item.DisplayName}, {ex.Message}");
return null;
}
}
/// <summary>
/// 提取模型项的三角形几何数据复用自ChannelHeightDetector
/// </summary>
/// <param name="modelItem">模型项</param>
/// <returns>三角形列表</returns>
private List<Triangle3D> ExtractTrianglesFromModelItem(ModelItem modelItem)
{
LogManager.Info($"[几何提取] 开始处理: {modelItem.DisplayName}");
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var allTriangles = new List<Triangle3D>();
try
{
// 创建几何提取回调
var callback = new GeometryExtractorCallback();
// 递归处理模型项及其子项
ExtractTrianglesRecursive(modelItem, callback, allTriangles);
stopwatch.Stop();
LogManager.Info($"[几何提取] 完成处理: {modelItem.DisplayName}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 三角形数量: {allTriangles.Count}");
return allTriangles;
}
catch (Exception ex)
{
stopwatch.Stop();
LogManager.Error($"[几何提取] 处理失败: {modelItem.DisplayName}, 耗时: {stopwatch.ElapsedMilliseconds}ms, 错误: {ex.Message}");
return allTriangles;
}
}
/// <summary>
/// 递归提取三角形复用自ChannelHeightDetector
/// </summary>
private void ExtractTrianglesRecursive(ModelItem modelItem, GeometryExtractorCallback callback, List<Triangle3D> allTriangles)
{
try
{
// 如果当前项有几何数据,尝试提取
if (modelItem.HasGeometry)
{
ExtractTrianglesFromGeometry(modelItem, callback, allTriangles);
}
// 递归处理子项
if (modelItem.Children != null)
{
foreach (var child in modelItem.Children)
{
ExtractTrianglesRecursive(child, callback, allTriangles);
}
}
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 递归提取三角形异常: {ex.Message}");
}
}
/// <summary>
/// 从单个有几何数据的ModelItem中提取三角形复用自ChannelHeightDetector
/// </summary>
private void ExtractTrianglesFromGeometry(ModelItem modelItem, GeometryExtractorCallback callback, List<Triangle3D> allTriangles)
{
try
{
// 基于示例代码的方法直接从COM状态获取节点并遍历fragments
var comState = ComBridge.State;
var comObject = ComBridge.ToInwOaPath(modelItem);
if (comObject == null)
{
return;
}
// 检查路径是否有效并获取节点
var nodesColl = comObject.Nodes();
if (nodesColl.Count > 0)
{
var comNode = nodesColl[nodesColl.Count] as COMApi.InwOaNode; // 获取最后一个节点并转换类型
if (comNode != null && comNode.IsGeometry)
{
var fragCount = comNode.Fragments().Count;
LogManager.Info($"[COM API] 开始片段处理: {modelItem.DisplayName}, 片段数量: {fragCount}");
for (long fragIndex = 1; fragIndex <= fragCount; fragIndex++)
{
var fragStopwatch = System.Diagnostics.Stopwatch.StartNew();
var fragsColl = comNode.Fragments();
var fragment = fragsColl[fragIndex] as COMApi.InwOaFragment3;
if (fragment != null)
{
// 获取变换矩阵
var transformMatrix = GetTransformMatrix(fragment);
callback.SetTransformMatrix(transformMatrix);
// 清空回调中的三角形
callback.ClearTriangles();
// 记录GenerateSimplePrimitives调用前的三角形数量
int trianglesBeforeGeneration = allTriangles.Count;
// 生成几何图元 - 使用正确的枚举值
fragment.GenerateSimplePrimitives(COMApi.nwEVertexProperty.eNORMAL, callback);
fragStopwatch.Stop();
int newTriangles = callback.Triangles.Count;
LogManager.Info($"[COM API] 片段 {fragIndex}/{fragCount} 处理完成, 耗时: {fragStopwatch.ElapsedMilliseconds}ms, 新增三角形: {newTriangles}");
// 将提取的三角形添加到总列表
allTriangles.AddRange(callback.Triangles);
}
}
}
}
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 从 {modelItem.DisplayName} 提取几何时出错: {ex.Message}");
}
}
/// <summary>
/// 获取Fragment的变换矩阵复用自ChannelHeightDetector
/// </summary>
private Matrix4 GetTransformMatrix(COMApi.InwOaFragment3 fragment)
{
try
{
var transform = fragment.GetLocalToWorldMatrix();
if (transform != null)
{
object matrixArrayObj = transform.Matrix;
if (matrixArrayObj is Array matrixArray && matrixArray.Length >= 16)
{
var matrix = new double[16];
matrixArray.CopyTo(matrix, 0);
// Navisworks使用列主序矩阵需要转置
return new Matrix4(
matrix[0], matrix[4], matrix[8], matrix[12],
matrix[1], matrix[5], matrix[9], matrix[13],
matrix[2], matrix[6], matrix[10], matrix[14],
matrix[3], matrix[7], matrix[11], matrix[15]
);
}
}
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 获取变换矩阵失败: {ex.Message}");
}
return Matrix4.Identity;
}
/// <summary>
/// 执行垂直射线与三角形相交检测
/// </summary>
/// <param name="basePoint">射线起点</param>
/// <param name="scanHeight">扫描高度</param>
/// <param name="triangles">三角形列表</param>
/// <returns>相交点Z坐标列表</returns>
private List<double> PerformVerticalRayIntersection(Point3D basePoint, double scanHeight, List<Triangle3D> triangles)
{
var intersectionPoints = new List<double>();
try
{
// 创建垂直向上的射线从basePoint开始向上扫描scanHeight距离
var rayOrigin = new Point3D(basePoint.X, basePoint.Y, basePoint.Z);
var rayDirection = new Point3D(0, 0, 1); // 向上
foreach (var triangle in triangles)
{
if (RayTriangleIntersect(rayOrigin, rayDirection, triangle, out double intersectionZ))
{
// 检查相交点是否在扫描范围内
if (intersectionZ >= basePoint.Z && intersectionZ <= basePoint.Z + scanHeight)
{
intersectionPoints.Add(intersectionZ);
}
}
}
return intersectionPoints;
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 射线-三角形相交计算失败: {ex.Message}");
return intersectionPoints;
}
}
/// <summary>
/// 射线-三角形相交算法Möller-Trumbore算法复用自ChannelHeightDetector
/// </summary>
/// <param name="rayOrigin">射线起点</param>
/// <param name="rayDirection">射线方向</param>
/// <param name="triangle">三角形</param>
/// <param name="intersectionZ">输出相交点Z坐标</param>
/// <returns>是否相交</returns>
private bool RayTriangleIntersect(Point3D rayOrigin, Point3D rayDirection, Triangle3D triangle, out double intersectionZ)
{
intersectionZ = 0.0;
const double EPSILON = 0.0000001;
try
{
// 计算三角形的两条边
var edge1 = SubtractPoints(triangle.V2, triangle.V1);
var edge2 = SubtractPoints(triangle.V3, triangle.V1);
// 计算射线方向与edge2的叉积
var h = CrossProduct(rayDirection, edge2);
var a = DotProduct(edge1, h);
// 如果a接近0射线与三角形平行
if (a > -EPSILON && a < EPSILON)
{
return false;
}
var f = 1.0 / a;
var s = SubtractPoints(rayOrigin, triangle.V1);
var u = f * DotProduct(s, h);
if (u < 0.0 || u > 1.0)
{
return false;
}
var q = CrossProduct(s, edge1);
var v = f * DotProduct(rayDirection, q);
if (v < 0.0 || u + v > 1.0)
{
return false;
}
// 计算t值射线参数
var t = f * DotProduct(edge2, q);
if (t > EPSILON) // 射线相交
{
// 计算交点
intersectionZ = rayOrigin.Z + t * rayDirection.Z;
return true;
}
return false; // 线段相交但射线不相交
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 射线相交计算出错: {ex.Message}");
return false;
}
}
/// <summary>
/// 向量减法
/// </summary>
private Point3D SubtractPoints(Point3D p1, Point3D p2)
{
return new Point3D(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z);
}
/// <summary>
/// 向量叉积
/// </summary>
private Point3D CrossProduct(Point3D v1, Point3D v2)
{
return new Point3D(
v1.Y * v2.Z - v1.Z * v2.Y,
v1.Z * v2.X - v1.X * v2.Z,
v1.X * v2.Y - v1.Y * v2.X
);
}
/// <summary>
/// 向量点积
/// </summary>
private double DotProduct(Point3D v1, Point3D v2)
{
return v1.X * v2.X + v1.Y * v2.Y + v1.Z * v2.Z;
}
/// <summary>
/// 检查模型项是否类似地面/楼板
/// </summary>
/// <param name="item">模型项</param>
/// <param name="intersectionData">相交数据</param>
/// <returns>是否为地面类型</returns>
private bool IsFloorLike(ModelItem item, IntersectionData intersectionData)
{
string displayName = item.DisplayName?.ToLower() ?? "";
// 通过名称判断
string[] floorKeywords = {
"地面", "floor", "楼板", "slab", "板", "deck",
"地板", "flooring", "基础", "foundation"
};
foreach (string keyword in floorKeywords)
{
if (displayName.Contains(keyword))
{
return true;
}
}
// 通过几何特征判断(薄的水平结构)
double thickness = intersectionData.MaxZ - intersectionData.MinZ;
return thickness < 0.5; // 小于50cm厚度认为是楼板
}
/// <summary>
/// 合并重叠的区间
/// </summary>
/// <param name="intervals">区间列表</param>
/// <returns>合并后的区间列表</returns>
private List<HeightInterval> MergeOverlappingIntervals(List<HeightInterval> intervals)
{
if (!intervals.Any())
return new List<HeightInterval>();
var sortedIntervals = intervals.OrderBy(i => i.MinZ).ToList();
var merged = new List<HeightInterval>();
var current = sortedIntervals[0];
for (int i = 1; i < sortedIntervals.Count; i++)
{
var next = sortedIntervals[i];
if (next.MinZ <= current.MaxZ + HEIGHT_TOLERANCE)
{
// 合并重叠区间
current = new HeightInterval(current.MinZ, Math.Max(current.MaxZ, next.MaxZ));
}
else
{
merged.Add(current);
current = next;
}
}
merged.Add(current);
return merged;
}
/// <summary>
/// 检查指定的ModelItem是否为通道集合中任意一个通道的子节点
/// </summary>
/// <param name="item">要检查的ModelItem</param>
/// <param name="channelItemsSet">通道集合</param>
/// <returns>如果是通道的子节点则返回true</returns>
private bool IsChildOfChannelItems(ModelItem item, HashSet<ModelItem> channelItemsSet)
{
try
{
if (item?.Parent == null)
return false;
// 递归向上检查父节点链
var currentParent = item.Parent;
while (currentParent != null)
{
if (channelItemsSet.Contains(currentParent))
{
return true; // 找到了通道父节点
}
currentParent = currentParent.Parent;
}
return false;
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 检查子节点关系时出错: {item?.DisplayName ?? "NULL"}, {ex.Message}");
return false; // 出错时保守处理,不排除
}
}
#endregion
#region
/// <summary>
/// 相交数据结构
/// </summary>
private class IntersectionData
{
public double MinZ { get; set; }
public double MaxZ { get; set; }
public Point3D IntersectionPoint { get; set; }
}
/// <summary>
/// 相交测试结果
/// </summary>
private class IntersectionResult
{
public ModelItem ModelItem { get; set; }
public IntersectionData IntersectionData { get; set; }
public bool IsObstacle { get; set; }
public bool IsPassable { get; set; }
}
/// <summary>
/// 三角形数据结构复用自ChannelHeightDetector
/// </summary>
public class Triangle3D
{
public Point3D V1 { get; set; }
public Point3D V2 { get; set; }
public Point3D V3 { get; set; }
public Triangle3D(Point3D v1, Point3D v2, Point3D v3)
{
V1 = v1;
V2 = v2;
V3 = v3;
}
}
/// <summary>
/// 几何提取回调类复用自ChannelHeightDetector
/// </summary>
public class GeometryExtractorCallback : COMApi.InwSimplePrimitivesCB
{
private readonly List<Triangle3D> _triangles;
private Matrix4 _transformMatrix;
public List<Triangle3D> Triangles => _triangles;
public GeometryExtractorCallback()
{
_triangles = new List<Triangle3D>();
_transformMatrix = Matrix4.Identity;
}
public void SetTransformMatrix(Matrix4 matrix)
{
_transformMatrix = matrix;
}
public void Line(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2)
{
// 我们只关心三角形,忽略线段
}
public void Point(COMApi.InwSimpleVertex v1)
{
// 我们只关心三角形,忽略点
}
public void SnapPoint(COMApi.InwSimpleVertex v1)
{
// 我们只关心三角形,忽略捕捉点
}
public void Triangle(COMApi.InwSimpleVertex v1, COMApi.InwSimpleVertex v2, COMApi.InwSimpleVertex v3)
{
try
{
// 提取顶点坐标
var vertex1 = ConvertVertex(v1);
var vertex2 = ConvertVertex(v2);
var vertex3 = ConvertVertex(v3);
// 应用变换矩阵到顶点坐标
vertex1 = _transformMatrix.Transform(vertex1);
vertex2 = _transformMatrix.Transform(vertex2);
vertex3 = _transformMatrix.Transform(vertex3);
// 创建三角形并添加到列表
var triangle = new Triangle3D(vertex1, vertex2, vertex3);
_triangles.Add(triangle);
}
catch (Exception ex)
{
LogManager.Debug($"【垂直扫描处理器】 处理三角形时出错: {ex.Message}");
}
}
private Point3D ConvertVertex(COMApi.InwSimpleVertex vertex)
{
// 从COM API顶点获取坐标
object coordObj = vertex.coord;
if (coordObj is Array coordinates && coordinates.Length >= 3)
{
var coords = new double[3];
coordinates.CopyTo(coords, 0);
return new Point3D(coords[0], coords[1], coords[2]);
}
return new Point3D(0, 0, 0);
}
public void ClearTriangles()
{
_triangles.Clear();
}
public List<Triangle3D> GetTriangles()
{
return new List<Triangle3D>(_triangles);
}
}
#endregion
}
}