NavisworksTransport/src/Core/Spatial/SpatialIndexManager.cs
tian d889635c1c feat: 实现自定义空间哈希网格 (SpatialHashGrid)
- 创建 SpatialHashGrid.cs: 基于 Vector3i 的3D空间哈希表
  - 支持对象插入、范围查询、格子查询
  - O(1) 平均查询复杂度
  - 带精确距离检查的范围查询

- 创建 SpatialIndexManager.cs: 全局空间索引管理器
  - 单例模式,所有动画共享
  - 构建全局索引(索引所有几何对象)
  - FindNearbyObjects: 高效范围查询
  - 对象位置缓存(避免重复计算包围盒)

技术细节:
- 使用 geometry4Sharp (g4) 的 Vector3d, Vector3i
- 基于 Dictionary<Vector3i, List<ModelItem>> 实现
- 格子大小可配置(建议设为车辆半径 × 2)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-14 11:40:04 +08:00

294 lines
10 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
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;
// 1. 获取所有几何对象
var 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;
foreach (var item in allItems)
{
try
{
// 获取包围盒中心作为对象位置
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($" - 失败: {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()}";
}
}
}