space-filling curves: use LUT instead of calculating indices on the fly

treemaps-chunkstates-spacefilling
EmaMaker 2022-08-23 11:21:50 +02:00
parent f48abfbe7f
commit ad2c5c2c18
5 changed files with 163 additions and 137 deletions

View File

@ -2,6 +2,7 @@ package com.emamaker.voxeltest.intervaltrees;
import com.emamaker.voxeltest.intervaltrees.data.IntervalMap;
import com.emamaker.voxeltest.intervaltrees.utils.Config;
import com.emamaker.voxeltest.intervaltrees.utils.SpaceFilling;
import com.emamaker.voxeltest.intervaltrees.world.WorldManager;
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
import com.jme3.app.SimpleApplication;
@ -34,6 +35,7 @@ public class Voxel extends SimpleApplication {
getCamera().lookAt(new Vector3f(Config.CHUNK_SIZE,Config.CHUNK_SIZE, Config.CHUNK_SIZE), Vector3f.UNIT_Y);
pos.set(this.getCamera().getLocation());
SpaceFilling.initLUT();
worldManager.initWorld();
// worldManager.benchmark();
}

View File

@ -1,6 +1,7 @@
package com.emamaker.voxeltest.intervaltrees.renderer;
import com.emamaker.voxeltest.intervaltrees.utils.Config;
import com.emamaker.voxeltest.intervaltrees.utils.SpaceFilling;
import com.emamaker.voxeltest.intervaltrees.utils.Utils;
import com.emamaker.voxeltest.intervaltrees.world.Chunk;
import com.emamaker.voxeltest.intervaltrees.world.WorldManager;
@ -71,9 +72,9 @@ public class ChunkRenderer {
*/
/*
* It's not feasible to just create a new mesh everytime a quad needs placing.
* This ends up creating TONS of meshes the game with just lag out.
* This ends up creating TONS of meshes and the game will just lag out.
* As I did in the past, it's better to create a single mesh for each chunk,
* containing all the quads. In the future, maybe traslucent blocks and liquids
* containing all the quads. In the future, maybe translucent blocks and liquids
* will need a separate mesh, but still on a per-chunk basis
*/
Vector3f[] vertices = new Vector3f[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 8];
@ -85,6 +86,7 @@ public class ChunkRenderer {
short cIndex = 0;
public void greedyMeshing(WorldManager mgr, Chunk chunk) {
long begin = System.nanoTime();
vIndex = 0;
iIndex = 0;
@ -93,10 +95,6 @@ public class ChunkRenderer {
// convert tree to array since it is easier to work with it
blocks = chunk.treeTo1DArray();
// System.out.println(Arrays.toString(blocks));
// chunk.chunkNode = new Node();
if (blocks.length == 0)
return;
@ -131,9 +129,9 @@ public class ChunkRenderer {
for (x[v] = 0; x[v] < Config.CHUNK_SIZE; x[v]++) {
for (x[u] = 0; x[u] < Config.CHUNK_SIZE; x[u]++) {
Blocks b1 = (x[dim] >= 0) ? blocks[Utils.MortonToHilbert3D(Utils.Morton_3D_Encode_10bit((short)x[0], (short)x[1], (short)x[2]), Config.HILBERT_BITS)] : null;
Blocks b1 = (x[dim] >= 0) ? blocks[SpaceFilling.HILBERT_XYZ_ENCODE[x[0]][x[1]][x[2]]] : null;
Blocks b2 = (x[dim] < Config.CHUNK_SIZE - 1)
? blocks[Utils.MortonToHilbert3D(Utils.Morton_3D_Encode_10bit((short)(x[0] + q[0]),(short)( x[1] + q[1]), (short) (x[2] + q[2])), Config.HILBERT_BITS)]
? blocks[SpaceFilling.HILBERT_XYZ_ENCODE[x[0] + q[0]][x[1] + q[1]][x[2]+q[2]]]
: null;
// This is the original line taken from rob's code, readapted (replace voxelFace
@ -247,6 +245,7 @@ public class ChunkRenderer {
geo.setMaterial(mat);
chunk.chunkNode.attachChild(geo);
}
System.out.println("Greedy meshing of chunk took " + (System.nanoTime()-begin ) + " ns");
}
final int VOXEL_SIZE = 1;

View File

@ -0,0 +1,152 @@
package com.emamaker.voxeltest.intervaltrees.utils;
public class SpaceFilling {
// http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
public static int[][][] MORTON_XYZ_ENCODE;
public static short[][] MORTON_XYZ_DECODE;
public static int[][][] HILBERT_XYZ_ENCODE;
public static short[][] HILBERT_XYZ_DECODE;
public static void initLUT() {
MORTON_XYZ_ENCODE = new int[Config.CHUNK_SIZE][Config.CHUNK_SIZE][Config.CHUNK_SIZE];
MORTON_XYZ_DECODE = new short[Config.CHUNK_3DCOORD_MAX_INDEX + 1][3];
HILBERT_XYZ_ENCODE = new int[Config.CHUNK_SIZE][Config.CHUNK_SIZE][Config.CHUNK_SIZE];
HILBERT_XYZ_DECODE = new short[Config.CHUNK_3DCOORD_MAX_INDEX + 1][3];
int morton, hilbert;
for (short i = 0; i < Config.CHUNK_SIZE; i++)
for (short j = 0; j < Config.CHUNK_SIZE; j++)
for (short k = 0; k < Config.CHUNK_SIZE; k++) {
morton = Morton_3D_Encode_10bit(i,j,k);
hilbert = MortonToHilbert3D(morton, Config.HILBERT_BITS);
MORTON_XYZ_ENCODE[i][j][k] = morton;
MORTON_XYZ_DECODE[morton][0] = i;
MORTON_XYZ_DECODE[morton][1] = j;
MORTON_XYZ_DECODE[morton][2] = k;
HILBERT_XYZ_ENCODE[i][j][k] = hilbert;
HILBERT_XYZ_DECODE[hilbert][0] = i;
HILBERT_XYZ_DECODE[hilbert][1] = j;
HILBERT_XYZ_DECODE[hilbert][2] = k;
}
}
public static int Morton_3D_Encode_10bit(short index1, short index2, short index3) { // pack 3 10-bit indices into a 30-bit Morton code
index1 &= 0x000003ff;
index2 &= 0x000003ff;
index3 &= 0x000003ff;
index1 |= (index1 << 16);
index2 |= (index2 << 16);
index3 |= (index3 << 16);
index1 &= 0x030000ff;
index2 &= 0x030000ff;
index3 &= 0x030000ff;
index1 |= (index1 << 8);
index2 |= (index2 << 8);
index3 |= (index3 << 8);
index1 &= 0x0300f00f;
index2 &= 0x0300f00f;
index3 &= 0x0300f00f;
index1 |= (index1 << 4);
index2 |= (index2 << 4);
index3 |= (index3 << 4);
index1 &= 0x030c30c3;
index2 &= 0x030c30c3;
index3 &= 0x030c30c3;
index1 |= (index1 << 2);
index2 |= (index2 << 2);
index3 |= (index3 << 2);
index1 &= 0x09249249;
index2 &= 0x09249249;
index3 &= 0x09249249;
return (index1 | (index2 << 1) | (index3 << 2));
}
public static short[] Morton_3D_Decode_10bit(int morton) { // unpack 3 10-bit indices from a 30-bit Morton code
int value1 = morton;
int value2 = (value1 >> 1);
int value3 = (value1 >> 2);
value1 &= 0x09249249;
value2 &= 0x09249249;
value3 &= 0x09249249;
value1 |= (value1 >> 2);
value2 |= (value2 >> 2);
value3 |= (value3 >> 2);
value1 &= 0x030c30c3;
value2 &= 0x030c30c3;
value3 &= 0x030c30c3;
value1 |= (value1 >> 4);
value2 |= (value2 >> 4);
value3 |= (value3 >> 4);
value1 &= 0x0300f00f;
value2 &= 0x0300f00f;
value3 &= 0x0300f00f;
value1 |= (value1 >> 8);
value2 |= (value2 >> 8);
value3 |= (value3 >> 8);
value1 &= 0x030000ff;
value2 &= 0x030000ff;
value3 &= 0x030000ff;
value1 |= (value1 >> 16);
value2 |= (value2 >> 16);
value3 |= (value3 >> 16);
value1 &= 0x000003ff;
value2 &= 0x000003ff;
value3 &= 0x000003ff;
return new short[]{(short) value1, (short) value2, (short) value3};
}
public static int MortonToHilbert3D(int morton, int bits) {
int hilbert = morton;
if (bits > 1) {
int block = ((bits * 3) - 3);
int hcode = ((hilbert >> block) & 7);
int mcode, shift, signs;
shift = signs = 0;
while (block > 0) {
block -= 3;
hcode <<= 2;
mcode = ((0x20212021 >> hcode) & 3);
shift = ((0x48 >> (7 - shift - mcode)) & 3);
signs = ((signs | (signs << 3)) >> mcode);
signs = ((signs ^ (0x53560300 >> hcode)) & 7);
mcode = ((hilbert >> block) & 7);
hcode = mcode;
hcode = (((hcode | (hcode << 3)) >> shift) & 7);
hcode ^= signs;
hilbert ^= ((mcode ^ hcode) << block);
}
}
hilbert ^= ((hilbert >> 1) & 0x92492492);
hilbert ^= ((hilbert & 0x92492492) >> 1);
return (hilbert);
}
public static int HilbertToMorton3D(int hilbert, int bits) {
int morton = hilbert;
morton ^= ((morton & 0x92492492) >> 1);
morton ^= ((morton >> 1) & 0x92492492);
if (bits > 1) {
int block = ((bits * 3) - 3);
int hcode = ((morton >> block) & 7);
int mcode, shift, signs;
shift = signs = 0;
while (block > 0) {
block -= 3;
hcode <<= 2;
mcode = ((0x20212021 >> hcode) & 3);
shift = ((0x48 >> (4 - shift + mcode)) & 3);
signs = ((signs | (signs << 3)) >> mcode);
signs = ((signs ^ (0x53560300 >> hcode)) & 7);
hcode = ((morton >> block) & 7);
mcode = hcode;
mcode ^= signs;
mcode = (((mcode | (mcode << 3)) >> shift) & 7);
morton ^= ((hcode ^ mcode) << block);
}
}
return (morton);
}
}

View File

@ -19,132 +19,4 @@ public class Utils {
final int x = idx % maxX;
return new int[]{ x, y, z };
}
// http://and-what-happened.blogspot.com/2011/08/fast-2d-and-3d-hilbert-curves-and.html
public static int Morton_3D_Encode_10bit( short index1, short index2, short index3 )
{ // pack 3 10-bit indices into a 30-bit Morton code
index1 &= 0x000003ff;
index2 &= 0x000003ff;
index3 &= 0x000003ff;
index1 |= ( index1 << 16 );
index2 |= ( index2 << 16 );
index3 |= ( index3 << 16 );
index1 &= 0x030000ff;
index2 &= 0x030000ff;
index3 &= 0x030000ff;
index1 |= ( index1 << 8 );
index2 |= ( index2 << 8 );
index3 |= ( index3 << 8 );
index1 &= 0x0300f00f;
index2 &= 0x0300f00f;
index3 &= 0x0300f00f;
index1 |= ( index1 << 4 );
index2 |= ( index2 << 4 );
index3 |= ( index3 << 4 );
index1 &= 0x030c30c3;
index2 &= 0x030c30c3;
index3 &= 0x030c30c3;
index1 |= ( index1 << 2 );
index2 |= ( index2 << 2 );
index3 |= ( index3 << 2 );
index1 &= 0x09249249;
index2 &= 0x09249249;
index3 &= 0x09249249;
return( index1 | ( index2 << 1 ) | ( index3 << 2 ) );
}
public static short[] Morton_3D_Decode_10bit(int morton)
{ // unpack 3 10-bit indices from a 30-bit Morton code
int value1 = morton;
int value2 = ( value1 >> 1 );
int value3 = ( value1 >> 2 );
value1 &= 0x09249249;
value2 &= 0x09249249;
value3 &= 0x09249249;
value1 |= ( value1 >> 2 );
value2 |= ( value2 >> 2 );
value3 |= ( value3 >> 2 );
value1 &= 0x030c30c3;
value2 &= 0x030c30c3;
value3 &= 0x030c30c3;
value1 |= ( value1 >> 4 );
value2 |= ( value2 >> 4 );
value3 |= ( value3 >> 4 );
value1 &= 0x0300f00f;
value2 &= 0x0300f00f;
value3 &= 0x0300f00f;
value1 |= ( value1 >> 8 );
value2 |= ( value2 >> 8 );
value3 |= ( value3 >> 8 );
value1 &= 0x030000ff;
value2 &= 0x030000ff;
value3 &= 0x030000ff;
value1 |= ( value1 >> 16 );
value2 |= ( value2 >> 16 );
value3 |= ( value3 >> 16 );
value1 &= 0x000003ff;
value2 &= 0x000003ff;
value3 &= 0x000003ff;
return new short[]{(short)value1, (short)value2, (short)value3};
}
public static int MortonToHilbert3D( int morton, int bits )
{
int hilbert = morton;
if ( bits > 1 )
{
int block = ( ( bits * 3 ) - 3 );
int hcode = ( ( hilbert >> block ) & 7 );
int mcode, shift, signs;
shift = signs = 0;
while ( block > 0 )
{
block -= 3;
hcode <<= 2;
mcode = ( ( 0x20212021 >> hcode ) & 3 );
shift = ( ( 0x48 >> ( 7 - shift - mcode ) ) & 3 );
signs = ( ( signs | ( signs << 3 ) ) >> mcode );
signs = ( ( signs ^ ( 0x53560300 >> hcode ) ) & 7 );
mcode = ( ( hilbert >> block ) & 7 );
hcode = mcode;
hcode = ( ( ( hcode | ( hcode << 3 ) ) >> shift ) & 7 );
hcode ^= signs;
hilbert ^= ( ( mcode ^ hcode ) << block );
}
}
hilbert ^= ( ( hilbert >> 1 ) & 0x92492492 );
hilbert ^= ( ( hilbert & 0x92492492 ) >> 1 );
return( hilbert );
}
public static int HilbertToMorton3D( int hilbert, int bits )
{
int morton = hilbert;
morton ^= ( ( morton & 0x92492492 ) >> 1 );
morton ^= ( ( morton >> 1 ) & 0x92492492 );
if ( bits > 1 )
{
int block = ( ( bits * 3 ) - 3 );
int hcode = ( ( morton >> block ) & 7 );
int mcode, shift, signs;
shift = signs = 0;
while ( block > 0 )
{
block -= 3;
hcode <<= 2;
mcode = ( ( 0x20212021 >> hcode ) & 3 );
shift = ( ( 0x48 >> ( 4 - shift + mcode ) ) & 3 );
signs = ( ( signs | ( signs << 3 ) ) >> mcode );
signs = ( ( signs ^ ( 0x53560300 >> hcode ) ) & 7 );
hcode = ( ( morton >> block ) & 7 );
mcode = hcode;
mcode ^= signs;
mcode = ( ( ( mcode | ( mcode << 3 ) ) >> shift ) & 7 );
morton ^= ( ( hcode ^ mcode ) << block );
}
}
return( morton );
}
}

View File

@ -2,6 +2,7 @@ package com.emamaker.voxeltest.intervaltrees.world;
import com.emamaker.voxeltest.intervaltrees.data.IntervalMap;
import com.emamaker.voxeltest.intervaltrees.utils.Config;
import com.emamaker.voxeltest.intervaltrees.utils.SpaceFilling;
import com.emamaker.voxeltest.intervaltrees.utils.Utils;
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
import com.jme3.math.Vector3f;
@ -111,7 +112,7 @@ public class Chunk {
for (int k = 0; k < Config.CHUNK_SIZE; k++) {
// Distance from 0,0,0
dist = (int) Math.sqrt((i * i) + (j * j) + (k * k));
hilbert = Utils.MortonToHilbert3D(Utils.Morton_3D_Encode_10bit((short)i,(short)j,(short)k), Config.HILBERT_BITS);
hilbert = SpaceFilling.HILBERT_XYZ_ENCODE[i][j][k];
if (dist <= (int) (Config.CHUNK_SIZE / 3))
array[hilbert] = Blocks.STONE;
else if (dist > (int) (Config.CHUNK_SIZE / 3) && dist <= (int) (2 * Config.CHUNK_SIZE / 3))