7.4 KiB
7.4 KiB
空间哈希原理与应用
一、核心问题
在3D空间中进行几何查询时,最直接的方法是遍历所有几何体,但这会导致巨大的计算量。
示例场景:
- 10,000个查询点
- 10,000个三角形
- 暴力方法:1亿次相交测试
二、空间哈希基本原理
空间哈希是一种空间索引数据结构,通过将空间划分成规则网格,预先将几何体分类到对应的格子中,查询时只检查相关格子内的几何体。
2.1 核心思想
空间划分 → 几何体分类 → 快速查询
2.2 数据结构
Dictionary<int, List<Geometry>> spatialHash;
// Key: 格子的哈希值
// Value: 该格子内的几何体列表
三、工作流程
3.1 构建阶段(Build Phase)
步骤1:确定空间范围和格子大小
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:建立哈希映射
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:计算查询点所在格子
int gridX = (int)(queryPoint.X / cellSize);
int gridY = (int)(queryPoint.Y / cellSize);
int hash = gridX * GRID_WIDTH + gridY;
步骤2:获取该格子的几何体
var localGeometry = spatialHash[hash];
// 只返回少量相关几何体,而不是全部
步骤3:执行精确测试
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 是最关键的参数,影响性能和内存占用。
太小的问题
- 格子数量多 → 内存占用大
- 几何体被分到多个格子 → 构建时间长
- 哈希表变大 → 缓存性能差
太大的问题
- 每格包含太多几何体 → 筛选效果差
- 退化成暴力搜索
最佳实践
cellSize = averageGeometrySize * 2
// 或
cellSize = queryGridSize * 2
5.2 哈希函数选择
// 简单哈希(推荐)
int hash = x * LARGE_PRIME + y;
// 莫顿码(Z-order)
int hash = MortonEncode(x, y); // 保持空间局部性
// 避免冲突
int hash = (x * 73856093) ^ (y * 19349663);
六、优化技巧
6.1 层次化空间哈希
class HierarchicalSpatialHash
{
SpatialHash coarseLevel; // 10m × 10m
SpatialHash fineLevel; // 1m × 1m
List<Geometry> Query(Point p)
{
// 先查询粗粒度
var candidates = coarseLevel.Query(p);
// 再查询细粒度
return fineLevel.Query(p, candidates);
}
}
6.2 动态更新
// 移除旧位置
spatialHash[oldHash].Remove(geometry);
// 添加到新位置
spatialHash[newHash].Add(geometry);
6.3 并行构建
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 完整的空间哈希类
public class SpatialHash<T> where T : IHasBounds
{
private Dictionary<int, List<T>> hash;
private double cellSize;
private int gridWidth;
public SpatialHash(double cellSize, int gridWidth = 10000)
{
this.cellSize = cellSize;
this.gridWidth = gridWidth;
hash = new Dictionary<int, List<T>>();
}
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<T>();
hash[key].Add(item);
}
}
}
public List<T> Query(Point2D point)
{
var grid = WorldToGrid(point);
int key = GetHash(grid.X, grid.Y);
return hash.ContainsKey(key) ? hash[key] : new List<T>();
}
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 使用示例
// 构建
var spatialHash = new SpatialHash<Triangle3D>(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倍,是路径规划等场景的理想选择。