NavisworksTransport/src/Core/Spatial/SpatialIndexManager.cs
tian 320dfa23f3 perf: 空间索引复用几何对象缓存避免重复遍历
问题描述:
- 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>
2025-10-14 12:15:29 +08:00

317 lines
12 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.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()}";
}
}
}