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>
This commit is contained in:
parent
687b342e0f
commit
d889635c1c
242
src/Core/Spatial/SpatialHashGrid.cs
Normal file
242
src/Core/Spatial/SpatialHashGrid.cs
Normal file
@ -0,0 +1,242 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using g4; // geometry4Sharp
|
||||
|
||||
namespace NavisworksTransport.Core.Spatial
|
||||
{
|
||||
/// <summary>
|
||||
/// 自定义3D空间哈希网格
|
||||
/// 用于高效的空间范围查询,复杂度 O(1) 平均
|
||||
/// </summary>
|
||||
/// <typeparam name="T">存储的对象类型</typeparam>
|
||||
public class SpatialHashGrid<T>
|
||||
{
|
||||
private readonly Dictionary<Vector3i, List<T>> _grid;
|
||||
private readonly double _cellSize;
|
||||
|
||||
/// <summary>
|
||||
/// 创建空间哈希网格
|
||||
/// </summary>
|
||||
/// <param name="cellSize">格子大小(模型单位)</param>
|
||||
public SpatialHashGrid(double cellSize)
|
||||
{
|
||||
if (cellSize <= 0)
|
||||
throw new ArgumentException("格子大小必须大于0", nameof(cellSize));
|
||||
|
||||
_cellSize = cellSize;
|
||||
_grid = new Dictionary<Vector3i, List<T>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 格子大小(模型单位)
|
||||
/// </summary>
|
||||
public double CellSize => _cellSize;
|
||||
|
||||
/// <summary>
|
||||
/// 格子总数
|
||||
/// </summary>
|
||||
public int CellCount => _grid.Count;
|
||||
|
||||
/// <summary>
|
||||
/// 对象总数
|
||||
/// </summary>
|
||||
public int ObjectCount
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
foreach (var list in _grid.Values)
|
||||
{
|
||||
count += list.Count;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插入对象到空间哈希网格
|
||||
/// </summary>
|
||||
/// <param name="item">要插入的对象</param>
|
||||
/// <param name="position">对象的3D位置</param>
|
||||
public void Insert(T item, Vector3d position)
|
||||
{
|
||||
var gridCell = ToGridCoordinates(position);
|
||||
|
||||
if (!_grid.ContainsKey(gridCell))
|
||||
{
|
||||
_grid[gridCell] = new List<T>();
|
||||
}
|
||||
|
||||
_grid[gridCell].Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量插入对象
|
||||
/// </summary>
|
||||
/// <param name="items">对象和位置的键值对</param>
|
||||
public void InsertRange(IEnumerable<KeyValuePair<T, Vector3d>> items)
|
||||
{
|
||||
foreach (var kvp in items)
|
||||
{
|
||||
Insert(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定格子中的所有对象
|
||||
/// </summary>
|
||||
/// <param name="gridCell">格子坐标</param>
|
||||
/// <returns>该格子中的对象列表</returns>
|
||||
public List<T> GetObjectsInCell(Vector3i gridCell)
|
||||
{
|
||||
if (_grid.TryGetValue(gridCell, out var list))
|
||||
{
|
||||
return new List<T>(list); // 返回副本,避免外部修改
|
||||
}
|
||||
return new List<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询:查找指定位置附近指定半径内的所有对象
|
||||
/// </summary>
|
||||
/// <param name="center">查询中心位置</param>
|
||||
/// <param name="radius">查询半径(模型单位)</param>
|
||||
/// <param name="distanceFunc">可选的距离计算函数(用于精确过滤)</param>
|
||||
/// <returns>范围内的对象列表(去重)</returns>
|
||||
public List<T> FindInRadius(
|
||||
Vector3d center,
|
||||
double radius,
|
||||
Func<T, double> distanceFunc = null)
|
||||
{
|
||||
var results = new HashSet<T>();
|
||||
|
||||
// 计算需要检查的格子范围
|
||||
int gridRadius = (int)Math.Ceiling(radius / _cellSize);
|
||||
var centerGrid = ToGridCoordinates(center);
|
||||
|
||||
// 遍历查询范围内的所有格子
|
||||
for (int dx = -gridRadius; dx <= gridRadius; dx++)
|
||||
{
|
||||
for (int dy = -gridRadius; dy <= gridRadius; dy++)
|
||||
{
|
||||
for (int dz = -gridRadius; dz <= gridRadius; dz++)
|
||||
{
|
||||
var gridCell = new Vector3i(
|
||||
centerGrid.x + dx,
|
||||
centerGrid.y + dy,
|
||||
centerGrid.z + dz
|
||||
);
|
||||
|
||||
if (_grid.TryGetValue(gridCell, out var cellObjects))
|
||||
{
|
||||
foreach (var obj in cellObjects)
|
||||
{
|
||||
// 如果提供了距离函数,进行精确距离检查
|
||||
if (distanceFunc != null)
|
||||
{
|
||||
double distance = distanceFunc(obj);
|
||||
if (distance <= radius)
|
||||
{
|
||||
results.Add(obj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 否则直接添加(假设格子内的对象都在范围内)
|
||||
results.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new List<T>(results);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 范围查询(带精确距离检查)
|
||||
/// </summary>
|
||||
/// <param name="center">查询中心</param>
|
||||
/// <param name="radius">查询半径</param>
|
||||
/// <param name="getPositionFunc">获取对象位置的函数</param>
|
||||
/// <returns>范围内的对象列表</returns>
|
||||
public List<T> FindInRadiusExact(
|
||||
Vector3d center,
|
||||
double radius,
|
||||
Func<T, Vector3d> getPositionFunc)
|
||||
{
|
||||
if (getPositionFunc == null)
|
||||
throw new ArgumentNullException(nameof(getPositionFunc));
|
||||
|
||||
return FindInRadius(center, radius, obj =>
|
||||
{
|
||||
var objPos = getPositionFunc(obj);
|
||||
return Vector3d.Distance(center, objPos);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有数据
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
_grid.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将世界坐标转换为网格坐标
|
||||
/// </summary>
|
||||
/// <param name="position">世界坐标</param>
|
||||
/// <returns>网格坐标</returns>
|
||||
private Vector3i ToGridCoordinates(Vector3d position)
|
||||
{
|
||||
return new Vector3i(
|
||||
(int)Math.Floor(position.x / _cellSize),
|
||||
(int)Math.Floor(position.y / _cellSize),
|
||||
(int)Math.Floor(position.z / _cellSize)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取网格统计信息(用于调试)
|
||||
/// </summary>
|
||||
/// <returns>统计信息字符串</returns>
|
||||
public string GetStatistics()
|
||||
{
|
||||
int totalObjects = 0;
|
||||
int maxObjectsPerCell = 0;
|
||||
int minObjectsPerCell = int.MaxValue;
|
||||
int emptyCount = 0;
|
||||
|
||||
foreach (var list in _grid.Values)
|
||||
{
|
||||
int count = list.Count;
|
||||
totalObjects += count;
|
||||
|
||||
if (count == 0)
|
||||
emptyCount++;
|
||||
|
||||
if (count > maxObjectsPerCell)
|
||||
maxObjectsPerCell = count;
|
||||
|
||||
if (count < minObjectsPerCell && count > 0)
|
||||
minObjectsPerCell = count;
|
||||
}
|
||||
|
||||
double avgObjectsPerCell = _grid.Count > 0 ? (double)totalObjects / _grid.Count : 0;
|
||||
|
||||
if (minObjectsPerCell == int.MaxValue)
|
||||
minObjectsPerCell = 0;
|
||||
|
||||
return $"空间哈希网格统计:\n" +
|
||||
$" 格子大小: {_cellSize:F2} 模型单位\n" +
|
||||
$" 总格子数: {_grid.Count}\n" +
|
||||
$" 总对象数: {totalObjects}\n" +
|
||||
$" 平均每格对象数: {avgObjectsPerCell:F2}\n" +
|
||||
$" 最大每格对象数: {maxObjectsPerCell}\n" +
|
||||
$" 最小每格对象数: {minObjectsPerCell}\n" +
|
||||
$" 空格子数: {emptyCount}";
|
||||
}
|
||||
}
|
||||
}
|
||||
293
src/Core/Spatial/SpatialIndexManager.cs
Normal file
293
src/Core/Spatial/SpatialIndexManager.cs
Normal file
@ -0,0 +1,293 @@
|
||||
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()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user