NavisworksTransport/doc/working/spatial_hash_principle.md

7.4 KiB
Raw Blame History

空间哈希原理与应用

一、核心问题

在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倍是路径规划等场景的理想选择。