Very fast scanline mesher

This commit is contained in:
mcrcortex
2024-12-02 20:26:27 +10:00
parent 3618c136f7
commit 5a6819757b

View File

@@ -1,10 +1,12 @@
package me.cortex.voxy.client.core.util;
public class ScanMesher2D {
import java.util.Random;
public abstract class ScanMesher2D {
// is much faster if implemented inline into parent
private long[] rowData = new long[32];
private int[] rowLength = new int[32];//How long down does a row entry go
private int[] rowDepth = new int[32];//How many rows does it cover
private final long[] rowData = new long[32];
private final int[] rowLength = new int[32];//How long down does a row entry go
private final int[] rowDepth = new int[32];//How many rows does it cover
private int rowBitset = 0;
int currentIndex = 0;
@@ -13,12 +15,12 @@ public class ScanMesher2D {
//Two different ways to do it, scanline then only merge on change, or try to merge with previous row at every step
// or even can also attempt to merge previous but if the lengths are different split the current one and merge to previous
public void putNext(long data) {
int thisIdx = (this.currentIndex++)&31;//Mask to current row, but keep total so can compute actual indexing
public final void putNext(long data) {
int idx = (this.currentIndex++)&31;//Mask to current row, but keep total so can compute actual indexing
//If we are on the zero index, ignore it as we are going from empty state to maybe something state
// setup data
if (thisIdx == 0) {
if (idx == 0) {
//If the previous data is not zero, that means it was not merge-able, so emit it at the pos
if (this.currentData!=0) {
if ((this.rowBitset&(1<<31))!=0) {
@@ -35,59 +37,39 @@ public class ScanMesher2D {
this.currentSum = 0;
}
//If we are the same as last data increment
if (data == this.currentData) {
this.currentSum++;
//If we merge, then just continue
this.mergeCurrentIfPossibleOrEmit(thisIdx);
return;
}
//write out previous data
//If we are different from previous (this can never happen if previous is index 0)
if (data != this.currentData) {
//write out previous data if its a non sentinel, it is guarenteed to not have a row bit set
if (this.currentData != 0) {
int prev = thisIdx-1;//We need to write in the previous entry
int prev = idx-1;//We need to write in the previous entry
if ((this.rowBitset&(1<<prev))!=0) {
throw new IllegalStateException();
}
this.rowDepth[prev] = 1;
this.rowLength[prev] = this.currentSum;
this.rowData[prev] = this.currentData;
this.rowBitset |= 1<<prev;
}
this.currentData = data;
this.currentSum = 1;
this.mergeCurrentIfPossibleOrEmit(thisIdx);
this.currentSum = 0;
}
this.currentSum++;
private boolean mergeCurrentIfPossibleOrEmit(int index) {
boolean isSet = (this.rowBitset&(1<<index))!=0;
boolean isSet = (this.rowBitset&(1<<idx))!=0;
//Greadily merge with previous row if possible
if (this.currentData != 0 &&//Ignore sentinel empty
isSet &&
this.rowLength[index] == this.currentSum &&
this.rowData[index] == this.currentData) {//Can merge with previous row
this.rowDepth[index]++;
this.rowLength[idx] == this.currentSum &&
this.rowData[idx] == this.currentData) {//Can merge with previous row
this.rowDepth[idx]++;
this.currentSum = 0;//Clear sum since we went down
this.currentData = 0;//Zero is sentinel value for absent
return true;
} else if (isSet) {
this.emitQuad(this.rowLength[idx], this.rowDepth[idx], this.rowData[idx]);
this.rowBitset &= ~(1<<idx);
}
if (isSet) {
this.emitQuad(this.rowLength[index], this.rowDepth[index], this.rowData[index]);
this.rowBitset &= ~(1<<index);
}
return false;
}
private boolean attemptMerge(int index, int length, long data) {
if ((this.rowBitset & (1 << index)) != 0 &&//Entry in previous bitset
this.rowLength[index] == length &&//If previous row merged length matches current
this.rowData[index] == data) {//If the previous row entry data matches
this.rowDepth[index]++;//Increment the depth
return true;
}
return false;
}
//Emits quads that exist at the mask pos and clear
@@ -95,7 +77,7 @@ public class ScanMesher2D {
{//Emit quads that cover the previous indices
int rowSet = this.rowBitset&msk;
while (rowSet!=0) {//Need to emit quads that would have skipped, note that this does not include the current index
int index = Integer.numberOfLeadingZeros(rowSet);
int index = Integer.numberOfTrailingZeros(rowSet);
rowSet &= ~Integer.lowestOneBit(rowSet);
//Emit the quad, dont need to clear the data since it not existing in the bitmask is implicit no data
@@ -105,11 +87,120 @@ public class ScanMesher2D {
}
}
protected void emitQuad(int length, int width, long data) {
System.err.println("Quad, length: " + length + " width: " + width + " data: " + data );
public final void endPush() {
putNext(0);
this.currentIndex--;//HACK
this.emitRanged(-1);
}
protected abstract void emitQuad(int length, int width, long data);
public static void main(String[] args) {
var r = new Random(0);
long[] data = new long[32*32];
float DENSITY = 0.5f;
int RANGE = 50;
for (int i = 0; i < data.length; i++) {
data[i] = r.nextFloat()<DENSITY?(r.nextInt(RANGE)+1):0;
}
int[] qc = new int[2];
var mesher = new ScanMesher2D(){
@Override
protected void emitQuad(int length, int width, long data) {
qc[0]++;
qc[1]+=length*width;
}
};
for (int i = 0; i < 500000; i++) {
for (long v : data) {
mesher.putNext(v);
}
mesher.endPush();
}
var m2 = new Mesher2D();
for (int i = 0; i < 500000; i++) {
int j = 0;
m2.reset();
for (long v : data) {
if (v!=0)
m2.put(j&31, j>>5, v);
j++;
}
m2.process();
}
long t = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
for (long v : data) {
mesher.putNext(v);
}
mesher.endPush();
}
long delta = System.nanoTime()-t;
System.out.println(delta*1e-6);
t = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
int j = 0;
m2.reset();
for (long v : data) {
if (v!=0)
m2.put(j&31, j>>5, v);
j++;
}
m2.process();
}
delta = System.nanoTime()-t;
System.out.println(delta*1e-6);
}
public static void main3(String[] args) {
var r = new Random(0);
int[] qc = new int[2];
var mesher = new ScanMesher2D(){
@Override
protected void emitQuad(int length, int width, long data) {
qc[0]++;
qc[1]+=length*width;
}
};
var mesh2 = new Mesher2D();
float DENSITY = 0.5f;
int RANGE = 50;
int total = 0;
while (true) {
DENSITY = r.nextFloat();
RANGE = r.nextInt(500)+1;
qc[0] = 0; qc[1] = 0;
int c = 0;
for (int i = 0; i < 32*32; i++) {
long val = r.nextFloat()<DENSITY?(r.nextInt(RANGE)+1):0;
c += val==0?0:1;
mesher.putNext(val);
if (val != 0) {
mesh2.put(i&31, i>>5, val);
}
}
mesher.endPush();
if (c != qc[1]) {
System.out.println(c+", " + qc[1]);
}
int count = mesh2.process();
int delta = count - qc[0];
total += delta;
System.out.println(total);
//System.out.println(c+", new: " + qc[0] + " old: " + count);
}
}
public static void main2(String[] args) {
long[] sample = new long[32*32];
sample[0] = 1;
@@ -130,9 +221,17 @@ public class ScanMesher2D {
sample[7+32*1] = 2;
sample[31+32*0] = 6;
sample[31+32*1] = 6;
sample[30+32*2] = 7;
sample[31+32*2] = 7;
sample[31+32*3] = 8;
var mesher = new ScanMesher2D();
sample[30+32*3] = 7;
sample[31+32*3] = 7;
sample[31+32*8] = 8;
var mesher = new ScanMesher2D() {
@Override
protected void emitQuad(int length, int width, long data) {
System.out.println(length + ", " + width + ", " + data);
}
};
int j = 0;
for (long i : sample) {
if (j%32 == 0) {