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; 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 // is much faster if implemented inline into parent
private long[] rowData = new long[32]; private final long[] rowData = new long[32];
private int[] rowLength = new int[32];//How long down does a row entry go private final 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 int[] rowDepth = new int[32];//How many rows does it cover
private int rowBitset = 0; private int rowBitset = 0;
int currentIndex = 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 //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 // 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) { public final void putNext(long data) {
int thisIdx = (this.currentIndex++)&31;//Mask to current row, but keep total so can compute actual indexing 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 //If we are on the zero index, ignore it as we are going from empty state to maybe something state
// setup data // 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 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.currentData!=0) {
if ((this.rowBitset&(1<<31))!=0) { if ((this.rowBitset&(1<<31))!=0) {
@@ -35,59 +37,39 @@ public class ScanMesher2D {
this.currentSum = 0; this.currentSum = 0;
} }
//If we are the same as last data increment //If we are different from previous (this can never happen if previous is index 0)
if (data == this.currentData) { if (data != this.currentData) {
this.currentSum++; //write out previous data if its a non sentinel, it is guarenteed to not have a row bit set
//If we merge, then just continue if (this.currentData != 0) {
this.mergeCurrentIfPossibleOrEmit(thisIdx); int prev = idx-1;//We need to write in the previous entry
return; if ((this.rowBitset&(1<<prev))!=0) {
} throw new IllegalStateException();
}
//write out previous data this.rowDepth[prev] = 1;
if (this.currentData != 0) { this.rowLength[prev] = this.currentSum;
int prev = thisIdx-1;//We need to write in the previous entry this.rowData[prev] = this.currentData;
this.rowDepth[prev] = 1; this.rowBitset |= 1<<prev;
this.rowLength[prev] = this.currentSum; }
this.rowData[prev] = this.currentData;
this.rowBitset |= 1<<prev; this.currentData = data;
this.currentSum = 0;
} }
this.currentSum++;
this.currentData = data; boolean isSet = (this.rowBitset&(1<<idx))!=0;
this.currentSum = 1;
this.mergeCurrentIfPossibleOrEmit(thisIdx);
}
private boolean mergeCurrentIfPossibleOrEmit(int index) {
boolean isSet = (this.rowBitset&(1<<index))!=0;
//Greadily merge with previous row if possible //Greadily merge with previous row if possible
if (this.currentData != 0 &&//Ignore sentinel empty if (this.currentData != 0 &&//Ignore sentinel empty
isSet && isSet &&
this.rowLength[index] == this.currentSum && this.rowLength[idx] == this.currentSum &&
this.rowData[index] == this.currentData) {//Can merge with previous row this.rowData[idx] == this.currentData) {//Can merge with previous row
this.rowDepth[index]++; this.rowDepth[idx]++;
this.currentSum = 0;//Clear sum since we went down this.currentSum = 0;//Clear sum since we went down
this.currentData = 0;//Zero is sentinel value for absent 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 //Emits quads that exist at the mask pos and clear
@@ -95,7 +77,7 @@ public class ScanMesher2D {
{//Emit quads that cover the previous indices {//Emit quads that cover the previous indices
int rowSet = this.rowBitset&msk; 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 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); rowSet &= ~Integer.lowestOneBit(rowSet);
//Emit the quad, dont need to clear the data since it not existing in the bitmask is implicit no data //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) { public final void endPush() {
System.err.println("Quad, length: " + length + " width: " + width + " data: " + data ); putNext(0);
this.currentIndex--;//HACK
this.emitRanged(-1);
} }
protected abstract void emitQuad(int length, int width, long data);
public static void main(String[] args) { 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]; long[] sample = new long[32*32];
sample[0] = 1; sample[0] = 1;
@@ -130,9 +221,17 @@ public class ScanMesher2D {
sample[7+32*1] = 2; sample[7+32*1] = 2;
sample[31+32*0] = 6; sample[31+32*0] = 6;
sample[31+32*1] = 6; sample[31+32*1] = 6;
sample[30+32*2] = 7;
sample[31+32*2] = 7; sample[31+32*2] = 7;
sample[31+32*3] = 8; sample[30+32*3] = 7;
var mesher = new ScanMesher2D(); 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; int j = 0;
for (long i : sample) { for (long i : sample) {
if (j%32 == 0) { if (j%32 == 0) {