- 创建 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>
294 lines
10 KiB
C#
294 lines
10 KiB
C#
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()}";
|
||
}
|
||
}
|
||
}
|