问题描述: - BuildAllGeometryItemsCache() 已经遍历模型树获取290个几何对象 - SpatialIndexManager 又重新遍历一次模型树获取290个对象 - 重复工作导致性能浪费 优化方案: 1. 在 ClashDetectiveIntegration 中添加 GetAllGeometryItemsCache() 公开方法 2. SpatialIndexManager 调用该方法获取缓存的几何对象列表 3. 避免重复遍历模型树,提升性能 技术细节: - GetAllGeometryItemsCache() 返回列表副本保证线程安全 - 添加缓存不存在时的回退逻辑(保险措施) - 日志输出改为"从缓存获取"以明确数据来源 性能提升: - 减少模型树遍历次数:2次 → 1次 - 优化空间索引构建流程 - 所有碰撞检测和空间索引共享同一个几何对象列表 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
317 lines
12 KiB
C#
317 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.Linq;
|
||
using Autodesk.Navisworks.Api;
|
||
using NavisworksTransport.Utils;
|
||
using g4; // geometry4Sharp
|
||
|
||
// 使用命名空间引用以避免类名冲突
|
||
using ClashIntegration = NavisworksTransport.ClashDetectiveIntegration;
|
||
|
||
namespace NavisworksTransport.Core.Spatial
|
||
{
|
||
/// <summary>
|
||
/// 全局空间索引管理器
|
||
/// 单例模式,为所有动画提供高效的空间查询服务
|
||
/// </summary>
|
||
public class SpatialIndexManager
|
||
{
|
||
private static SpatialIndexManager _instance;
|
||
private static readonly object _lockObj = new object();
|
||
|
||
/// <summary>
|
||
/// 单例实例
|
||
/// </summary>
|
||
public static SpatialIndexManager Instance
|
||
{
|
||
get
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
lock (_lockObj)
|
||
{
|
||
if (_instance == null)
|
||
{
|
||
_instance = new SpatialIndexManager();
|
||
}
|
||
}
|
||
}
|
||
return _instance;
|
||
}
|
||
}
|
||
|
||
private SpatialHashGrid<ModelItem> _globalSpatialIndex;
|
||
private double _cellSize = 1.0; // 默认格子大小(模型单位)
|
||
private bool _isInitialized = false;
|
||
private int _indexedObjectCount = 0;
|
||
|
||
// 缓存对象位置,避免重复计算
|
||
private readonly Dictionary<ModelItem, Vector3d> _objectPositions = new Dictionary<ModelItem, Vector3d>();
|
||
|
||
private SpatialIndexManager()
|
||
{
|
||
}
|
||
|
||
/// <summary>
|
||
/// 空间索引是否已初始化
|
||
/// </summary>
|
||
public bool IsInitialized => _isInitialized;
|
||
|
||
/// <summary>
|
||
/// 格子大小(模型单位)
|
||
/// </summary>
|
||
public double CellSize => _cellSize;
|
||
|
||
/// <summary>
|
||
/// 已索引的对象数量
|
||
/// </summary>
|
||
public int IndexedObjectCount => _indexedObjectCount;
|
||
|
||
/// <summary>
|
||
/// 构建全局空间索引
|
||
/// </summary>
|
||
/// <param name="cellSizeInModelUnits">格子大小(模型单位),建议设置为车辆半径的2倍</param>
|
||
public void BuildGlobalIndex(double cellSizeInModelUnits = 1.0)
|
||
{
|
||
var sw = Stopwatch.StartNew();
|
||
LogManager.Info("[空间索引] 开始构建全局空间索引");
|
||
LogManager.Info($"[空间索引] 格子大小: {cellSizeInModelUnits:F2} 模型单位");
|
||
|
||
try
|
||
{
|
||
_cellSize = cellSizeInModelUnits;
|
||
|
||
// 0. 构建通道对象缓存(用于排除通道)
|
||
ClashIntegration.Instance.BuildChannelObjectsCache();
|
||
ClashIntegration.BuildAllGeometryItemsCache();
|
||
|
||
// 1. 从缓存获取所有几何对象(避免重复遍历)
|
||
var allItems = ClashIntegration.GetAllGeometryItemsCache();
|
||
|
||
if (allItems == null)
|
||
{
|
||
// 缓存不存在,手动获取(不应该发生)
|
||
LogManager.Warning("[空间索引] 几何对象缓存不存在,回退到实时获取");
|
||
allItems = Application.ActiveDocument.Models.RootItemDescendantsAndSelf
|
||
.Where(item => item.HasGeometry)
|
||
.ToList();
|
||
}
|
||
|
||
LogManager.Info($"[空间索引] 从缓存获取 {allItems.Count} 个几何对象(含通道)");
|
||
|
||
if (allItems.Count == 0)
|
||
{
|
||
LogManager.Warning("[空间索引] 场景中没有几何对象");
|
||
return;
|
||
}
|
||
|
||
// 2. 创建空间哈希网格
|
||
_globalSpatialIndex = new SpatialHashGrid<ModelItem>(cellSizeInModelUnits);
|
||
_objectPositions.Clear();
|
||
|
||
// 3. 索引所有对象(排除通道对象)
|
||
int indexedCount = 0;
|
||
int failedCount = 0;
|
||
int channelExcludedCount = 0;
|
||
|
||
foreach (var item in allItems)
|
||
{
|
||
try
|
||
{
|
||
// 排除通道对象
|
||
if (ClashIntegration.Instance.IsChannelObjectPublic(item))
|
||
{
|
||
channelExcludedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 获取包围盒中心作为对象位置
|
||
var bbox = item.BoundingBox();
|
||
var center = new Vector3d(
|
||
(bbox.Min.X + bbox.Max.X) / 2.0,
|
||
(bbox.Min.Y + bbox.Max.Y) / 2.0,
|
||
(bbox.Min.Z + bbox.Max.Z) / 2.0
|
||
);
|
||
|
||
// 插入到空间索引
|
||
_globalSpatialIndex.Insert(item, center);
|
||
|
||
// 缓存位置
|
||
_objectPositions[item] = center;
|
||
|
||
indexedCount++;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Warning($"[空间索引] 索引对象失败: {item.DisplayName}, 错误: {ex.Message}");
|
||
failedCount++;
|
||
}
|
||
}
|
||
|
||
_indexedObjectCount = indexedCount;
|
||
_isInitialized = true;
|
||
|
||
sw.Stop();
|
||
|
||
LogManager.Info("[空间索引] 构建完成");
|
||
LogManager.Info($" - 成功索引: {indexedCount} 个对象");
|
||
LogManager.Info($" - 排除通道: {channelExcludedCount} 个对象");
|
||
LogManager.Info($" - 失败: {failedCount} 个对象");
|
||
LogManager.Info($" - 格子数量: {_globalSpatialIndex.CellCount} 个");
|
||
LogManager.Info($" - 耗时: {sw.ElapsedMilliseconds} ms");
|
||
|
||
// 输出统计信息
|
||
LogManager.Debug("[空间索引] " + _globalSpatialIndex.GetStatistics());
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[空间索引] 构建失败: {ex.Message}");
|
||
LogManager.Error($"[空间索引] 堆栈跟踪: {ex.StackTrace}");
|
||
_isInitialized = false;
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 范围查询:查找指定位置附近的对象
|
||
/// </summary>
|
||
/// <param name="position">查询中心位置(Navisworks Point3D)</param>
|
||
/// <param name="searchRadiusInModelUnits">查询半径(模型单位)</param>
|
||
/// <param name="excludeObject">要排除的对象(通常是移动物体本身)</param>
|
||
/// <returns>范围内的对象列表</returns>
|
||
public List<ModelItem> FindNearbyObjects(
|
||
Point3D position,
|
||
double searchRadiusInModelUnits,
|
||
ModelItem excludeObject = null)
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
throw new InvalidOperationException(
|
||
"[空间索引] 空间索引未初始化,请先调用 BuildGlobalIndex()");
|
||
}
|
||
|
||
var sw = Stopwatch.StartNew();
|
||
|
||
try
|
||
{
|
||
// 转换为 geometry4Sharp 的 Vector3d
|
||
var centerVec = new Vector3d(position.X, position.Y, position.Z);
|
||
|
||
// 使用空间哈希网格进行范围查询(带精确距离检查)
|
||
var nearbyObjects = _globalSpatialIndex.FindInRadiusExact(
|
||
centerVec,
|
||
searchRadiusInModelUnits,
|
||
getPositionFunc: item =>
|
||
{
|
||
// 从缓存中获取对象位置
|
||
if (_objectPositions.TryGetValue(item, out var pos))
|
||
{
|
||
return pos;
|
||
}
|
||
|
||
// 缓存未命中,重新计算(理论上不应该发生)
|
||
var bbox = item.BoundingBox();
|
||
return new Vector3d(
|
||
(bbox.Min.X + bbox.Max.X) / 2.0,
|
||
(bbox.Min.Y + bbox.Max.Y) / 2.0,
|
||
(bbox.Min.Z + bbox.Max.Z) / 2.0
|
||
);
|
||
}
|
||
);
|
||
|
||
// 排除指定对象
|
||
if (excludeObject != null)
|
||
{
|
||
nearbyObjects = nearbyObjects.Where(obj => !obj.Equals(excludeObject)).ToList();
|
||
}
|
||
|
||
sw.Stop();
|
||
|
||
LogManager.Debug($"[空间索引] 范围查询完成: " +
|
||
$"位置=({position.X:F2}, {position.Y:F2}, {position.Z:F2}), " +
|
||
$"半径={searchRadiusInModelUnits:F2}, " +
|
||
$"结果={nearbyObjects.Count} 个对象, " +
|
||
$"耗时={sw.Elapsed.TotalMilliseconds:F2}ms");
|
||
|
||
return nearbyObjects;
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogManager.Error($"[空间索引] 范围查询失败: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 范围查询(带排除列表)
|
||
/// </summary>
|
||
/// <param name="position">查询中心位置</param>
|
||
/// <param name="searchRadiusInModelUnits">查询半径</param>
|
||
/// <param name="excludeObjects">要排除的对象列表</param>
|
||
/// <returns>范围内的对象列表</returns>
|
||
public List<ModelItem> FindNearbyObjects(
|
||
Point3D position,
|
||
double searchRadiusInModelUnits,
|
||
IEnumerable<ModelItem> excludeObjects)
|
||
{
|
||
var results = FindNearbyObjects(position, searchRadiusInModelUnits, excludeObject: null);
|
||
|
||
if (excludeObjects != null)
|
||
{
|
||
var excludeSet = new HashSet<ModelItem>(excludeObjects);
|
||
results = results.Where(obj => !excludeSet.Contains(obj)).ToList();
|
||
}
|
||
|
||
return results;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取对象的缓存位置
|
||
/// </summary>
|
||
/// <param name="item">模型对象</param>
|
||
/// <returns>对象的3D位置(Vector3d)</returns>
|
||
public Vector3d? GetObjectPosition(ModelItem item)
|
||
{
|
||
if (_objectPositions.TryGetValue(item, out var pos))
|
||
{
|
||
return pos;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清除空间索引
|
||
/// </summary>
|
||
public void Clear()
|
||
{
|
||
LogManager.Info("[空间索引] 清除空间索引");
|
||
|
||
_globalSpatialIndex?.Clear();
|
||
_objectPositions.Clear();
|
||
_isInitialized = false;
|
||
_indexedObjectCount = 0;
|
||
|
||
LogManager.Info("[空间索引] 空间索引已清除");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取空间索引统计信息
|
||
/// </summary>
|
||
/// <returns>统计信息字符串</returns>
|
||
public string GetStatistics()
|
||
{
|
||
if (!_isInitialized)
|
||
{
|
||
return "[空间索引] 未初始化";
|
||
}
|
||
|
||
return $"[空间索引] 统计信息:\n" +
|
||
$" - 已索引对象: {_indexedObjectCount} 个\n" +
|
||
$" - 格子大小: {_cellSize:F2} 模型单位\n" +
|
||
$" - 格子数量: {_globalSpatialIndex.CellCount} 个\n" +
|
||
$"{_globalSpatialIndex.GetStatistics()}";
|
||
}
|
||
}
|
||
}
|