# 空间哈希原理与应用 ## 一、核心问题 在3D空间中进行几何查询时,最直接的方法是遍历所有几何体,但这会导致巨大的计算量。 **示例场景**: - 10,000个查询点 - 10,000个三角形 - 暴力方法:1亿次相交测试 ## 二、空间哈希基本原理 空间哈希是一种空间索引数据结构,通过将空间划分成规则网格,预先将几何体分类到对应的格子中,查询时只检查相关格子内的几何体。 ### 2.1 核心思想 ``` 空间划分 → 几何体分类 → 快速查询 ``` ### 2.2 数据结构 ```csharp Dictionary> spatialHash; // Key: 格子的哈希值 // Value: 该格子内的几何体列表 ``` ## 三、工作流程 ### 3.1 构建阶段(Build Phase) #### 步骤1:确定空间范围和格子大小 ```csharp BoundingBox bounds = CalculateBounds(allGeometry); double cellSize = gridSize * 2; // 经验值 ``` #### 步骤2:计算每个几何体覆盖的格子 ``` 三角形投影到2D平面: /\ / \ 覆盖格子:[2,3,7,8] /____\ 网格划分: ┌─┬─┬─┬─┬─┐ │0│1│2│3│4│ ├─┼─┼─┼─┼─┤ │5│6│7│8│9│ └─┴─┴─┴─┴─┘ ``` #### 步骤3:建立哈希映射 ```csharp foreach (var triangle in triangles) { var coveredCells = GetCoveredCells(triangle); foreach (var cell in coveredCells) { int hash = cell.X * GRID_WIDTH + cell.Y; spatialHash[hash].Add(triangle); } } ``` ### 3.2 查询阶段(Query Phase) #### 步骤1:计算查询点所在格子 ```csharp int gridX = (int)(queryPoint.X / cellSize); int gridY = (int)(queryPoint.Y / cellSize); int hash = gridX * GRID_WIDTH + gridY; ``` #### 步骤2:获取该格子的几何体 ```csharp var localGeometry = spatialHash[hash]; // 只返回少量相关几何体,而不是全部 ``` #### 步骤3:执行精确测试 ```csharp foreach (var geometry in localGeometry) { if (ExactIntersectionTest(queryPoint, geometry)) return true; } ``` ## 四、性能分析 ### 4.1 时间复杂度 | 操作 | 暴力方法 | 空间哈希 | |-----|---------|---------| | 构建 | O(1) | O(n×m) | | 单次查询 | O(n) | O(1)平均 | | k次查询 | O(k×n) | O(k) | - n: 几何体数量 - m: 平均每个几何体覆盖的格子数 - k: 查询次数 ### 4.2 空间复杂度 ``` 内存占用 = 格子数量 × 每格平均几何体数 × 引用大小 ≈ O(n×m) ``` ### 4.3 实际性能提升 ``` 示例:L型通道(5000个三角形) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 暴力方法: 每个查询点 × 5000个三角形 = 5000次测试 空间哈希(cellSize = 1.0m): 格子数量:~100个 每格平均三角形:~50个 每次查询:~50次测试 性能提升:100倍 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ``` ## 五、参数选择 ### 5.1 cellSize 选择 cellSize 是最关键的参数,影响性能和内存占用。 #### 太小的问题 - 格子数量多 → 内存占用大 - 几何体被分到多个格子 → 构建时间长 - 哈希表变大 → 缓存性能差 #### 太大的问题 - 每格包含太多几何体 → 筛选效果差 - 退化成暴力搜索 #### 最佳实践 ```csharp cellSize = averageGeometrySize * 2 // 或 cellSize = queryGridSize * 2 ``` ### 5.2 哈希函数选择 ```csharp // 简单哈希(推荐) int hash = x * LARGE_PRIME + y; // 莫顿码(Z-order) int hash = MortonEncode(x, y); // 保持空间局部性 // 避免冲突 int hash = (x * 73856093) ^ (y * 19349663); ``` ## 六、优化技巧 ### 6.1 层次化空间哈希 ```csharp class HierarchicalSpatialHash { SpatialHash coarseLevel; // 10m × 10m SpatialHash fineLevel; // 1m × 1m List Query(Point p) { // 先查询粗粒度 var candidates = coarseLevel.Query(p); // 再查询细粒度 return fineLevel.Query(p, candidates); } } ``` ### 6.2 动态更新 ```csharp // 移除旧位置 spatialHash[oldHash].Remove(geometry); // 添加到新位置 spatialHash[newHash].Add(geometry); ``` ### 6.3 并行构建 ```csharp Parallel.ForEach(triangles, triangle => { var cells = GetCoveredCells(triangle); lock (spatialHash) { foreach (var cell in cells) spatialHash[cell].Add(triangle); } }); ``` ## 七、适用场景 ### 7.1 适合空间哈希的场景 ✅ **均匀分布**的几何体 ✅ **大量查询**的场景 ✅ **2D/2.5D**空间查询 ✅ **静态或缓慢变化**的几何体 ✅ **局部性查询**(查询点附近) ### 7.2 不适合的场景 ❌ 极度不均匀分布(考虑四叉树) ❌ 动态场景频繁更新(考虑松散四叉树) ❌ 稀疏数据(考虑KD树) ❌ 需要最近邻查询(考虑R树) ## 八、与其他数据结构对比 | 数据结构 | 构建时间 | 查询时间 | 内存占用 | 适用场景 | |---------|---------|---------|---------|---------| | **空间哈希** | O(n) | O(1) | 中等 | 均匀分布 | | 四叉树/八叉树 | O(nlogn) | O(logn) | 较大 | 不均匀分布 | | KD树 | O(nlogn) | O(logn) | 较小 | 最近邻查询 | | BVH | O(nlogn) | O(logn) | 中等 | 光线追踪 | | R树 | O(nlogn) | O(logn) | 较大 | 范围查询 | ## 九、实现示例 ### 9.1 完整的空间哈希类 ```csharp public class SpatialHash where T : IHasBounds { private Dictionary> hash; private double cellSize; private int gridWidth; public SpatialHash(double cellSize, int gridWidth = 10000) { this.cellSize = cellSize; this.gridWidth = gridWidth; hash = new Dictionary>(); } public void Add(T item) { var bounds = item.GetBounds(); var minCell = WorldToGrid(bounds.Min); var maxCell = WorldToGrid(bounds.Max); for (int x = minCell.X; x <= maxCell.X; x++) { for (int y = minCell.Y; y <= maxCell.Y; y++) { int key = GetHash(x, y); if (!hash.ContainsKey(key)) hash[key] = new List(); hash[key].Add(item); } } } public List Query(Point2D point) { var grid = WorldToGrid(point); int key = GetHash(grid.X, grid.Y); return hash.ContainsKey(key) ? hash[key] : new List(); } private Point2I WorldToGrid(Point2D world) { return new Point2I( (int)(world.X / cellSize), (int)(world.Y / cellSize) ); } private int GetHash(int x, int y) { return x * gridWidth + y; } } ``` ### 9.2 使用示例 ```csharp // 构建 var spatialHash = new SpatialHash(1.0); foreach (var triangle in triangles) { spatialHash.Add(triangle); } // 查询 var point = new Point2D(10.5, 20.3); var localTriangles = spatialHash.Query(point); // 只返回相关的几个三角形,而不是全部 // 精确测试 foreach (var triangle in localTriangles) { if (RayTriangleIntersect(point, triangle)) { // 找到相交 break; } } ``` ## 十、总结 空间哈希是一种简单高效的空间索引结构,特别适合: - 均匀分布的数据 - 大量的点查询 - 2D/2.5D场景 通过合理的参数选择和优化,可以将查询性能提升100-1000倍,是路径规划等场景的理想选择。