You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏Octree导航故障:Morton Code节点插入位置错误求助

Octree导航节点插入错误排查求助

游戏开发中使用Octree并采用Morton Code(Z序)实现导航功能,但节点始终被插入到错误位置,无法定位问题根源。更换过基于查找表的Morton Code实现、改用非Morton的插入方法,问题依然存在。以下是关键代码片段及排查记录,请求技术协助:

关键代码片段

1. Morton支持数组初始化与映射表构建

static Utils()
{
    thresholds[0] = Consts.Lod0Range;
    for (int i = 1; i <= Consts.MaxLod; i++)
        thresholds[i] = thresholds[i - 1] * 2f;
    for (int i = 0; i < 3; i++)
    {
        morton256[i] = new uint[256];
        for (uint j = 0; j < 256; j++)
            morton256[i][j] = SpreadBits(j, i);
    }
    for (int x = 0; x < 2; x++)
        for (int y = 0; y < 2; y++)
            for (int z = 0; z < 2; z++)
                MortonMapping[x * 4 + y * 2 + z] = (int)(MortonEncode(new Vector3Int(x, y, z)) & 0b111);
}

private static uint SpreadBits(uint x, int offset)
{
    uint result = 0;
    for (int i = 0; i < 8; i++)
    {
        result <<= 3;
        result |= ((x >> (7 - i)) & 1) << offset;
    }
    return result;
}

2. Morton编码实现

public static ulong MortonEncode(Vector3Int vector)
{
    if (vector.x < 0 || vector.y < 0 || vector.z < 0)
        throw new System.Exception("Negative morton");
    ulong answer =
    morton256[0][(vector.x >> 16) & 0xFF] |
    morton256[1][(vector.y >> 16) & 0xFF] |
    morton256[2][(vector.z >> 16) & 0xFF];
    answer = answer << 24 |
    morton256[0][(vector.x >> 8) & 0xFF] |
    morton256[1][(vector.y >> 8) & 0xFF] |
    morton256[2][(vector.z >> 8) & 0xFF];
    answer = answer << 24 |
    morton256[0][(vector.x) & 0xFF] |
    morton256[1][(vector.y) & 0xFF] |
    morton256[2][(vector.z) & 0xFF];
    return answer;
}

3. 从Morton Code获取指定层级的子节点本地索引

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int MortonIndexForLevel(int level, ulong mortonIndex)
{
    return (int)(mortonIndex >> (level * 3)) & 0b111;
}

4. 从索引转换为子节点相对父节点的本地位置

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector3Int FlatIndexToVector(int realIdx)
{
    return new Vector3Int((realIdx & 4) >> 2, (realIdx & 2) >> 1, realIdx & 1);
}

5. 递归插入逻辑(初始版本)

//coords are relative to world 0,0,0 measured in smallest possible node size
public void Insert(byte lod, Vector3Int coords, NativeArray<ushort> data)
{
    EnsureSpace(coords);
    Chunk chunk = !data.IsCreated ? null : new Chunk(data);
    ulong idx = Utils.MortonEncode(coords - beginCorner); //Substract beginCorner of octree in order to localize coords

    //Debug section, no logic here
    Vector3 center = FromGlobalCoords(coords) + Vector3.one * (1 << lod) * lod0ChunkSize / 2;
    Debug.DrawLine(lastPos, center, Color.white, 4000000, false);
    lastPos = center;
    Debug.Log("Insert " + _root.Lod + ", " + lod + ", " + idx + " at: " + coords + ", " + (coords - beginCorner) + ", " + FromGlobalCoords(coords) + ", " + beginCorner + ", " + (chunk == null ? "empty" : "ok"));
    Debug.Log(Utils.ToMortonString(idx));

    InsertRecursive(_root, new Node(lod, chunk), idx, beginCorner, FromGlobalCoords(beginCorner));
}

private void InsertRecursive(Node parent, Node value, ulong idx, /*debug param*/ Vector3Int coords, /*debug param*/ Vector3 lastCenter)
{
    byte lod = (byte)(parent.Lod - 1);
    int curIdx = Utils.MortonIndexForLevel(lod, idx);

    //Debug section, no logic here
    Vector3Int localCoords = Utils.FlatIndexToVector(Utils.MortonMapping[curIdx]);
    coords += localCoords * (1 << lod);
    Vector3 center = FromGlobalCoords(coords) + Vector3.one * (1 << lod) * lod0ChunkSize / 2;
    if (idx == 12582911)
    {
        Debug.Log("Go down: " + parent.Lod + ", " + localCoords + ", " + coords + ", " + center + ", " + FromGlobalCoords(coords));
        Debug.DrawLine(lastCenter, center, Color.Lerp(Color.green, Color.blue, lod / 10f), 4000000, false);
    }

    if (lod == value.Lod)
    {
        parent[curIdx] = value;
        return;
    }
    if (parent[curIdx] == null)
        parent[curIdx] = new Node(lod);
    InsertRecursive(parent[curIdx], value, idx, coords, center);
}

调试截图

白色线条为服务器端Octree按Z序发送的数据接收顺序的真实位置:
调试截图1
调试截图2
调试截图3

排查记录

编辑1:更换Morton Code实现

测试了基于查找表(LUT)的Morton Code实现,问题仍未解决。

编辑2:改用非Morton的插入方法

更换插入逻辑,直接通过坐标比较确定子节点索引,问题依然存在:

public void Insert(byte lod, Vector3Int coords, NativeArray<ushort> data)
{
    EnsureSpace(coords);
    Chunk chunk = !data.IsCreated ? null : new Chunk(data);
    ulong idx = Utils.MortonEncode(coords - beginCorner); //Substract beginCorner of octree in order to localize coords

    //Debug section, no logic here
    Vector3 center = FromGlobalCoords(coords) + Vector3.one * (1 << lod) * lod0ChunkSize / 2;
    Debug.DrawLine(lastPos, center, Color.white, 4000000, false);
    lastPos = center;
    Debug.Log("Insert " + _root.Lod + ", " + lod + ", " + idx + " at: " + coords + ", " + (coords - beginCorner) + ", " + FromGlobalCoords(coords) + ", " + beginCorner + ", " + (chunk == null ? "empty" : "ok"));
    Debug.Log(Utils.ToMortonString(idx));

    InsertRecursive(_root, new Node(lod, chunk), idx, Vector3Int.zero, coords - beginCorner);
}

private void InsertRecursive(Node parent, Node value, ulong idx, Vector3Int parentCorner, Vector3Int coords)
{
    byte lod = (byte)(parent.Lod - 1);
    int curIdx = Utils.MortonIndexForLevel(lod, idx);

    Vector3Int parentCenter = parentCorner + Vector3Int.one * (1 << lod);
    int index = 0;
    if (coords.x >= parentCenter.x)
        index |= 4;
    if (coords.y >= parentCenter.y)
        index |= 2;
    if (coords.z >= parentCenter.z)
        index |= 1;

    Debug.Log("Indexes: " + index + ", " + curIdx + "\n" + Convert.ToString(index, 2).PadLeft(3, '0') + "\n" + Convert.ToString(curIdx, 2).PadLeft(3, '0'));

    if (lod == value.Lod)
    {
        parent[index] = value;
        return;
    }
    if (parent[index] == null)
        parent[index] = new Node(lod);
    InsertRecursive(parent[index], value, idx, parentCorner + Utils.FlatIndexToVector(index) * (1 << lod), coords);
}

内容的提问来源于stack exchange,提问作者Robert Ostrowski-Jagoda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 12:57:31