Files
XericLibrary-WebGL-Publish/Runtime/MacroLibrary/MacroCurveMapping.cs
T
2026-07-23 09:12:25 +08:00

648 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#if ENABLE_BURST
using Unity.Burst;
#endif
using System.Runtime.CompilerServices;
namespace XericLibrary.Runtime.MacroLibrary
{
/// <summary>
/// 空间填充曲线映射——提供 Z 字曲线(Morton Code) 与 Hilbert 曲线的 2D/3D 坐标与 1D 索引的映射工具
/// 所有方法支持 Burst 编译,可用于 Unity 多线程环境
/// </summary>
#if ENABLE_BURST
[BurstCompile]
#endif
public static class MacroCurveMapping
{
#region Z Morton Code
/// <summary>
/// 将 2D 块坐标交织为 1D Z 索引 (Morton Code)
/// </summary>
/// <param name="blockX">X 坐标 (非负)</param>
/// <param name="blockY">Y 坐标 (非负)</param>
/// <returns>64 位 Z 索引</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ZOrderEncode(int blockX, int blockY)
{
ulong x = (uint)blockX;
ulong y = (uint)blockY;
x = (x | (x << 16)) & 0x0000FFFF0000FFFF;
x = (x | (x << 8)) & 0x00FF00FF00FF00FF;
x = (x | (x << 4)) & 0x0F0F0F0F0F0F0F0F;
x = (x | (x << 2)) & 0x3333333333333333;
x = (x | (x << 1)) & 0x5555555555555555;
y = (y | (y << 16)) & 0x0000FFFF0000FFFF;
y = (y | (y << 8)) & 0x00FF00FF00FF00FF;
y = (y | (y << 4)) & 0x0F0F0F0F0F0F0F0F;
y = (y | (y << 2)) & 0x3333333333333333;
y = (y | (y << 1)) & 0x5555555555555555;
return x | (y << 1);
}
/// <summary>
/// 将 1D Z 索引解码还原为 2D 块坐标
/// </summary>
/// <param name="z">Z 索引</param>
/// <param name="blockX">解码后的 X 坐标</param>
/// <param name="blockY">解码后的 Y 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZOrderDecode(ulong z, out int blockX, out int blockY)
{
ulong x = z & 0x5555555555555555;
ulong y = (z >> 1) & 0x5555555555555555;
x = (x ^ (x >> 1)) & 0x3333333333333333;
x = (x ^ (x >> 2)) & 0x0F0F0F0F0F0F0F0F;
x = (x ^ (x >> 4)) & 0x00FF00FF00FF00FF;
x = (x ^ (x >> 8)) & 0x0000FFFF0000FFFF;
x = (x ^ (x >> 16)) & 0x00000000FFFFFFFF;
y = (y ^ (y >> 1)) & 0x3333333333333333;
y = (y ^ (y >> 2)) & 0x0F0F0F0F0F0F0F0F;
y = (y ^ (y >> 4)) & 0x00FF00FF00FF00FF;
y = (y ^ (y >> 8)) & 0x0000FFFF0000FFFF;
y = (y ^ (y >> 16)) & 0x00000000FFFFFFFF;
blockX = (int)x;
blockY = (int)y;
}
/// <summary>
/// 将 3D 块坐标编码为 1D Z 索引 (Morton Code)
/// </summary>
/// <param name="x">X 坐标 (非负, 最多 21 bit)</param>
/// <param name="y">Y 坐标 (非负, 最多 21 bit)</param>
/// <param name="z">Z 坐标 (非负, 最多 21 bit)</param>
/// <returns>64 位 Z 索引</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong ZOrderEncode3D(int x, int y, int z)
{
return SplitBy3((uint)x) | (SplitBy3((uint)y) << 1) | (SplitBy3((uint)z) << 2);
}
/// <summary>
/// 将 1D Z 索引解码还原为 3D 坐标
/// </summary>
#if ENABLE_BURST
[BurstCompile]
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ZOrderDecode3D(ulong code, out int x, out int y, out int z)
{
x = (int)CompactBy3((uint)(code));
y = (int)CompactBy3((uint)(code >> 1));
z = (int)CompactBy3((uint)(code >> 2));
}
/// <summary>
/// 将 21-bit 整数按位分散到 63-bit 空间 (每 3 位留 1 位)
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ulong SplitBy3(uint a)
{
ulong x = (ulong)a & 0x1fffffUL; // 仅保留低 21 位
x = (x | (x << 32)) & 0x1f00000000ffffUL;
x = (x | (x << 16)) & 0x1f0000ff0000ffUL;
x = (x | (x << 8)) & 0x100f00f00f00f00fUL;
x = (x | (x << 4)) & 0x10c30c30c30c30c3UL;
x = (x | (x << 2)) & 0x1249249249249249UL;
return x;
}
/// <summary>
/// CompactBy3: 反向提取分散的位,还原为原始 21-bit 整数
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static uint CompactBy3(uint a)
{
ulong x = (ulong)a & 0x1249249249249249UL;
x = (x ^ (x >> 2)) & 0x10c30c30c30c30c3UL;
x = (x ^ (x >> 4)) & 0x100f00f00f00f00fUL;
x = (x ^ (x >> 8)) & 0x1f0000ff0000ffUL;
x = (x ^ (x >> 16)) & 0x1f00000000ffffUL;
x = (x ^ (x >> 32)) & 0x1fffffUL;
return (uint)x;
}
#endregion
#region Hilbert Curve 2D
// 2D Hilbert 状态转移表: encodeTable[state][rx*2+ry] = (quadrant << 2) | nextState
private static readonly byte[] Hilbert2DEncodeTable = new byte[16]
{
0x00, 0x01, 0x0E, 0x0B, // state 0: (0,0)→0/0, (0,1)→1/0, (1,0)→3/3, (1,1)→2/3
0x08, 0x05, 0x06, 0x03, // state 1: (0,0)→2/1, (0,1)→1/1, (1,0)→1/2, (1,1)→0/3
0x08, 0x0D, 0x04, 0x01, // state 2: (0,0)→2/2, (0,1)→3/2, (1,0)→1/1, (1,1)→0/0
0x00, 0x0D, 0x07, 0x0A, // state 3: (0,0)→0/3, (0,1)→3/0, (1,0)→3/3, (1,1)→2/2
};
// 2D Hilbert 解码用: decodeTable[state][quadrant] = (rx << 1 | ry << 0) | (nextState << 2)
private static readonly byte[] Hilbert2DDecodeTable = new byte[16]
{
0x00, 0x01, 0x0B, 0x06, // state 0: 0→(0,0)/0, 1→(0,1)/0, 2→(1,1)/2, 3→(1,0)/3
0x08, 0x05, 0x03, 0x06, // state 1: 0→(1,1)/2, 1→(0,1)/1, 2→(0,0)/0, 3→(1,0)/3
0x0A, 0x07, 0x01, 0x04, // state 2: 0→(1,1)/2, 1→(1,0)/3, 2→(0,0)/0, 3→(0,1)/1
0x00, 0x0D, 0x0B, 0x06, // state 3: 0→(0,0)/0, 1→(1,0)/3, 2→(1,1)/2, 3→(0,1)/1
};
/// <summary>
/// 将 2D 坐标编码为 Hilbert 曲线索引
/// </summary>
/// <param name="x">X 坐标 (非负)</param>
/// <param name="y">Y 坐标 (非负)</param>
/// <param name="order">曲线阶数 (1~16), 决定网格大小为 2^order</param>
/// <returns>Hilbert 索引</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
public static ulong Hilbert2DEncode(int x, int y, int order)
{
ulong index = 0;
int state = 0;
for (int s = order - 1; s >= 0; s--)
{
int rx = (x >> s) & 1;
int ry = (y >> s) & 1;
int key = (state << 2) | (rx << 1) | ry;
byte entry = Hilbert2DEncodeTable[key];
int quadrant = entry & 3;
state = entry >> 2;
index = (index << 2) | (uint)quadrant;
}
return index;
}
/// <summary>
/// 将 Hilbert 索引解码为 2D 坐标
/// </summary>
/// <param name="index">Hilbert 索引</param>
/// <param name="order">曲线阶数</param>
/// <param name="x">解码后的 X 坐标</param>
/// <param name="y">解码后的 Y 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
public static void Hilbert2DDecode(ulong index, int order, out int x, out int y)
{
x = 0;
y = 0;
int state = 0;
for (int s = 0; s < order; s++)
{
int quadrant = (int)((index >> ((order - 1 - s) * 2)) & 3);
int key = (state << 2) | quadrant;
byte entry = Hilbert2DDecodeTable[key];
int rx = (entry >> 1) & 1;
int ry = entry & 1;
state = entry >> 2;
x = (x << 1) | rx;
y = (y << 1) | ry;
}
}
#endregion
#region Hilbert Curve 3D
// 3D Hilbert 编码状态转移表: encode3DTable[state][rx*4+ry*2+rz] = (octant << 3) | nextState
// state 索引原始坐标位(rx,ry,rz), 返回:高4位为octant,低3位为nextState
private static readonly byte[] Hilbert3DEncodeTable = new byte[64]
{
// state 0
0x00, 0x01, 0x13, 0x12, 0x3F, 0x36, 0x24, 0x2D,
// state 1
0x09, 0x08, 0x1B, 0x1A, 0x37, 0x3E, 0x2C, 0x25,
// state 2
0x1B, 0x1A, 0x08, 0x09, 0x2C, 0x25, 0x37, 0x3E,
// state 3
0x12, 0x13, 0x01, 0x00, 0x24, 0x2D, 0x3F, 0x36,
// state 4
0x2B, 0x32, 0x38, 0x31, 0x04, 0x05, 0x17, 0x16,
// state 5
0x3A, 0x33, 0x21, 0x28, 0x0C, 0x0D, 0x1F, 0x1E,
// state 6
0x20, 0x29, 0x3A, 0x33, 0x15, 0x1C, 0x0C, 0x05,
// state 7
0x29, 0x20, 0x33, 0x3A, 0x1D, 0x14, 0x04, 0x0D,
};
// 3D Hilbert 解码状态转移表: decode3DTable[state][octant] = (rx<<2|ry<<1|rz) | (nextState << 3)
private static readonly byte[] Hilbert3DDecodeTable = new byte[64]
{
// state 0
0x00, 0x01, 0x1B, 0x13, 0x36, 0x37, 0x25, 0x2C,
// state 1
0x09, 0x10, 0x1A, 0x03, 0x3E, 0x2F, 0x24, 0x35,
// state 2
0x1B, 0x02, 0x08, 0x11, 0x25, 0x3C, 0x3F, 0x26,
// state 3
0x12, 0x0B, 0x01, 0x18, 0x2D, 0x2C, 0x36, 0x27,
// state 4
0x2B, 0x2A, 0x39, 0x30, 0x04, 0x0D, 0x1F, 0x16,
// state 5
0x32, 0x23, 0x38, 0x21, 0x0C, 0x15, 0x1E, 0x07,
// state 6
0x38, 0x29, 0x2A, 0x23, 0x1D, 0x04, 0x0D, 0x16,
// state 7
0x21, 0x30, 0x23, 0x2A, 0x15, 0x0C, 0x0D, 0x0E,
};
/// <summary>
/// 将 3D 坐标编码为 Hilbert 曲线索引
/// </summary>
/// <param name="x">X 坐标 (非负)</param>
/// <param name="y">Y 坐标 (非负)</param>
/// <param name="z">Z 坐标 (非负)</param>
/// <param name="order">曲线阶数 (1~10), 决定网格大小为 2^order</param>
/// <returns>Hilbert 索引</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
public static ulong Hilbert3DEncode(int x, int y, int z, int order)
{
ulong index = 0;
int state = 0;
for (int s = order - 1; s >= 0; s--)
{
int rx = (x >> s) & 1;
int ry = (y >> s) & 1;
int rz = (z >> s) & 1;
int key = (state << 3) | (rx << 2) | (ry << 1) | rz;
byte entry = Hilbert3DEncodeTable[key];
int octant = entry >> 3;
state = entry & 7;
index = (index << 3) | (uint)octant;
}
return index;
}
/// <summary>
/// 将 Hilbert 索引解码为 3D 坐标
/// </summary>
/// <param name="index">Hilbert 索引</param>
/// <param name="order">曲线阶数</param>
/// <param name="x">解码后的 X 坐标</param>
/// <param name="y">解码后的 Y 坐标</param>
/// <param name="z">解码后的 Z 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
public static void Hilbert3DDecode(ulong index, int order, out int x, out int y, out int z)
{
x = 0;
y = 0;
z = 0;
int state = 0;
for (int s = 0; s < order; s++)
{
int octant = (int)((index >> ((order - 1 - s) * 3)) & 7);
int key = (state << 3) | octant;
byte entry = Hilbert3DDecodeTable[key];
int rx = (entry >> 2) & 1;
int ry = (entry >> 1) & 1;
int rz = entry & 1;
state = entry >> 3;
x = (x << 1) | rx;
y = (y << 1) | ry;
z = (z << 1) | rz;
}
}
#endregion
#region Peano Curve 2D 3×3
// Peano 编码表: encodePeano[state][tx*3+ty] = (digit << 1) | nextState
// state 0 = 正向扫描, state 1 = 反向扫描
// 正向: row0 L→R (digits 0,1,2), row1 R→L (digits 3,4,5), row2 L→R (digits 6,7,8)
// 反向: 整体倒序
private static readonly byte[] Peano2DEncodeTable = new byte[18]
{
// state 0 (正向)
0x01, 0x02, 0x05, 0x06, 0x09, 0x0A, 0x0D, 0x0E, 0x11,
// state 1 (反向)
0x0C, 0x0F, 0x10, 0x0B, 0x08, 0x07, 0x04, 0x03, 0x00,
};
// Peano 解码表: decodePeano[state][digit] = (tx << 3) | (ty << 1) | nextState
private static readonly byte[] Peano2DDecodeTable = new byte[18]
{
// state 0
0x01, 0x08, 0x11, 0x12, 0x0B, 0x02, 0x05, 0x0C, 0x15,
// state 1
0x15, 0x0C, 0x05, 0x02, 0x0B, 0x12, 0x11, 0x08, 0x01,
};
/// <summary>
/// 将 2D 坐标编码为 Peano 曲线索引
/// </summary>
/// <param name="x">X 坐标 (非负, 0~3^order-1)</param>
/// <param name="y">Y 坐标 (非负, 0~3^order-1)</param>
/// <param name="order">曲线阶数 (1~10), 网格大小为 3^order</param>
/// <returns>Peano 索引 (9^order)</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
public static ulong Peano2DEncode(int x, int y, int order)
{
ulong index = 0;
int state = 0;
int divisor = 1;
for (int s = 0; s < order; s++)
divisor *= 3;
for (int s = order - 1; s >= 0; s--)
{
divisor /= 3;
int tx = (x / divisor) % 3;
int ty = (y / divisor) % 3;
int key = state * 9 + tx * 3 + ty;
byte entry = Peano2DEncodeTable[key];
int digit = entry >> 1;
state = entry & 1;
index = index * 9 + (uint)digit;
}
return index;
}
/// <summary>
/// 将 Peano 曲线索引解码为 2D 坐标
/// </summary>
/// <param name="index">Peano 索引</param>
/// <param name="order">曲线阶数</param>
/// <param name="x">解码后的 X 坐标</param>
/// <param name="y">解码后的 Y 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
public static void Peano2DDecode(ulong index, int order, out int x, out int y)
{
x = 0;
y = 0;
int state = 0;
ulong divisor = 1;
for (int s = 0; s < order - 1; s++)
divisor *= 9;
for (int s = 0; s < order; s++)
{
int digit = (int)((index / divisor) % 9);
int key = state * 9 + digit;
byte entry = Peano2DDecodeTable[key];
int tx = entry >> 3;
int ty = (entry >> 1) & 3;
state = entry & 1;
x = x * 3 + tx;
y = y * 3 + ty;
divisor /= 9;
}
}
#endregion
#region Moore 2D Hilbert variant
// Moore 曲线与 Hilbert 共享状态转移表
// 区别在于: Moore 的 top-level 4 个象限首尾相连形成闭环
// 每个象限内包含一个 order-1 阶 Hilbert 子曲线
/// <summary>
/// 将 2D 坐标编码为 Moore 曲线索引(闭环 Hilbert)
/// </summary>
/// <param name="x">X 坐标 (非负)</param>
/// <param name="y">Y 坐标 (非负)</param>
/// <param name="order">曲线阶数 (1~16), 网格大小为 2^order</param>
/// <returns>Moore 索引</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
public static ulong Moore2DEncode(int x, int y, int order)
{
// order=1 时与 Hilbert 相同
if (order <= 1)
return Hilbert2DEncode(x, y, order);
int half = 1 << (order - 1);
int subOrder = order - 1;
// 确定象限: TL=0, TR=1, BR=2, BL=3
// 象限内坐标变换 + 调用 Hilbert 子编码
switch ((x >= half ? 1 : 0) | ((y >= half ? 1 : 0) << 1))
{
case 0: // TL - 子曲线入口在下, 出口在右
{
ulong sub = Hilbert2DEncode(x, half - 1 - y, subOrder);
return (0UL << (subOrder * 2)) | sub;
}
case 1: // TR - 子曲线入口在左, 出口在下
{
ulong sub = Hilbert2DEncode(y, half - 1 - (x - half), subOrder);
return (1UL << (subOrder * 2)) | sub;
}
case 3: // BR - 子曲线入口在上, 出口在左
{
ulong sub = Hilbert2DEncode(half - 1 - (x - half), half - 1 - (y - half), subOrder);
return (2UL << (subOrder * 2)) | sub;
}
default: // BL - 子曲线入口在右, 出口在上
{
ulong sub = Hilbert2DEncode(half - 1 - y, x, subOrder);
return (3UL << (subOrder * 2)) | sub;
}
}
}
/// <summary>
/// 将 Moore 曲线索引解码为 2D 坐标
/// </summary>
/// <param name="index">Moore 索引</param>
/// <param name="order">曲线阶数</param>
/// <param name="x">解码后的 X 坐标</param>
/// <param name="y">解码后的 Y 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
public static void Moore2DDecode(ulong index, int order, out int x, out int y)
{
if (order <= 1)
{
Hilbert2DDecode(index, order, out x, out y);
return;
}
int subOrder = order - 1;
int half = 1 << subOrder;
// 提取最高 2 bits 确定象限
ulong quadrant = index >> (subOrder * 2);
ulong subIndex = index & (((1UL << (subOrder * 2)) - 1));
int sx, sy;
switch (quadrant)
{
case 0: // TL
Hilbert2DDecode(subIndex, subOrder, out sx, out sy);
x = sx;
y = half - 1 - sy;
break;
case 1: // TR
Hilbert2DDecode(subIndex, subOrder, out sx, out sy);
x = half + (half - 1 - sy);
y = sx;
break;
case 2: // BR
Hilbert2DDecode(subIndex, subOrder, out sx, out sy);
x = half + (half - 1 - sx);
y = half + (half - 1 - sy);
break;
default: // BL
Hilbert2DDecode(subIndex, subOrder, out sx, out sy);
x = sy;
y = half + sx;
break;
}
}
#endregion
#region 方形螺旋曲线 (Square Spiral, 中心向外)
/// <summary>
/// 将 2D 坐标编码为方形螺旋索引(从中心(0,0)开始,向右→上→左→下→右…螺旋向外)
/// </summary>
/// <param name="x">X 坐标</param>
/// <param name="y">Y 坐标</param>
/// <returns>螺旋索引 (0-based),中心(0,0)=0</returns>
#if ENABLE_BURST
[BurstCompile]
#endif
public static ulong Spiral2DEncode(int x, int y)
{
// 确定环数 k = max(|x|, |y|)
int ax = x < 0 ? -x : x;
int ay = y < 0 ? -y : y;
int k = ax > ay ? ax : ay;
if (k == 0)
return 0;
// 环 k 的起始索引 = (2k-1)^2
ulong kk = (ulong)k;
ulong baseIdx = (2 * kk - 1) * (2 * kk - 1);
// 环 k 内 4 段: A(右侧↑), B(顶侧←), C(左侧↓), D(底侧→)
// A: x == k, y ∈ [-(k-1), k] → pos = y + k - 1 (0 ~ 2k-1)
// B: x ∈ [k-1, -k], y == k → pos = 3k - 1 - x (2k ~ 4k-1)
// C: x == -k, y ∈ [k-1, -k] → pos = 5k - 1 - y (4k ~ 6k-1)
// D: x ∈ [-(k-1), k], y == -k → pos = x + 7k - 1 (6k ~ 8k-1)
ulong pos;
if (x == k && y > -k)
{
// A: 右侧向上
pos = (ulong)(y + k - 1);
}
else if (y == k)
{
// B: 顶侧向左
pos = (ulong)(3 * k - 1 - x);
}
else if (x == -k)
{
// C: 左侧向下
pos = (ulong)(5 * k - 1 - y);
}
else
{
// D: 底侧向右 (y == -k)
pos = (ulong)(x + 7 * k - 1);
}
return baseIdx + pos;
}
/// <summary>
/// 将方形螺旋索引解码为 2D 坐标
/// </summary>
/// <param name="index">螺旋索引 (0-based)</param>
/// <param name="x">解码后的 X 坐标</param>
/// <param name="y">解码后的 Y 坐标</param>
#if ENABLE_BURST
[BurstCompile]
#endif
public static void Spiral2DDecode(ulong index, out int x, out int y)
{
if (index == 0)
{
x = 0;
y = 0;
return;
}
// 计算环数 k = floor((sqrt(index) + 1) / 2)
int k = (int)((System.Math.Sqrt((double)index) + 1.0) * 0.5);
ulong kk = (ulong)k;
ulong baseIdx = (2 * kk - 1) * (2 * kk - 1);
// 如果 baseIdx > index,说明 k 估大了(浮点误差),减 1
if (baseIdx > index)
{
k--;
kk = (ulong)k;
baseIdx = (2 * kk - 1) * (2 * kk - 1);
}
ulong pos = index - baseIdx; // 0 ~ 8k-1
int segLen = 2 * k;
if (pos < (ulong)segLen)
{
// A: 右侧向上
x = k;
y = (int)pos - k + 1;
return;
}
pos -= (ulong)segLen;
if (pos < (ulong)segLen)
{
// B: 顶侧向左
x = k - 1 - (int)pos;
y = k;
return;
}
pos -= (ulong)segLen;
if (pos < (ulong)segLen)
{
// C: 左侧向下
x = -k;
y = k - 1 - (int)pos;
return;
}
pos -= (ulong)segLen;
// D: 底侧向右
x = -(k - 1) + (int)pos;
y = -k;
}
#endregion
}
}