Skip to content

Commit 1ab177f

Browse files
committed
le algorithm
1 parent 63ad4ba commit 1ab177f

File tree

5 files changed

+190
-53
lines changed

5 files changed

+190
-53
lines changed

src/main/java/com/cleanroommc/kirino/engine/render/geometry/Block.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package com.cleanroommc.kirino.engine.render.geometry;
22

33
import com.cleanroommc.kirino.ecs.component.scan.CleanStruct;
4-
import org.joml.Vector3f;
4+
import org.joml.Vector3i;
55

66
@CleanStruct
77
public class Block {
8-
public Vector3f position;
9-
public int faces;
8+
public Vector3i position;
9+
public int faceMask;
1010

1111
public Block() {
12-
position = new Vector3f();
13-
faces = 0b111111;
12+
position = new Vector3i();
13+
faceMask = 0b111111;
1414
}
1515

16-
public Block(int x, int y, int z, int faces) {
17-
position = new Vector3f(x, y, z);
18-
this.faces = faces;
16+
public Block(int x, int y, int z, int faceMask) {
17+
position = new Vector3i(x, y, z);
18+
this.faceMask = faceMask;
1919
}
2020

2121
/**
@@ -89,8 +89,8 @@ public Block(int x, int y, int z, int faces) {
8989
* @return The integer as described above
9090
*/
9191
int compress() {
92-
return ((int)position.x & 0b1111) << 14
93-
| ((int)position.y & 0b1111) << 10
94-
| ((int)position.z & 0b1111) << 6;
92+
return (position.x & 0b1111) << 14
93+
| (position.y & 0b1111) << 10
94+
| (position.z & 0b1111) << 6;
9595
}
9696
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.cleanroommc.kirino.engine.render.geometry;
22

3-
import java.util.ArrayList;
43
import java.util.List;
54

65
public class Meshlet {
7-
public final List<Block> blocks = new ArrayList<>();
6+
public final List<Block> blocks;
7+
8+
public Meshlet(List<Block> blocks) {
9+
this.blocks = blocks;
10+
}
811
}

src/main/java/com/cleanroommc/kirino/engine/render/gizmos/GizmosManager.java

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@ public void addMeshlet(Meshlet meshlet) {
4343
Color color = new Color(random.nextFloat(), random.nextFloat(), random.nextFloat(), 0.5f);
4444
for (Block block : meshlet.blocks) {
4545
StringBuilder face = new StringBuilder();
46-
face.append((block.faces & FACE_X_POS) != 0 ? "1" : "0")
47-
.append((block.faces & FACE_X_NEG) != 0 ? "1" : "0")
48-
.append((block.faces & FACE_Y_POS) != 0 ? "1" : "0")
49-
.append((block.faces & FACE_Y_NEG) != 0 ? "1" : "0")
50-
.append((block.faces & FACE_Z_POS) != 0 ? "1" : "0")
51-
.append((block.faces & FACE_Z_NEG) != 0 ? "1" : "0");
46+
face.append((block.faceMask & FACE_X_POS) != 0 ? "1" : "0")
47+
.append((block.faceMask & FACE_X_NEG) != 0 ? "1" : "0")
48+
.append((block.faceMask & FACE_Y_POS) != 0 ? "1" : "0")
49+
.append((block.faceMask & FACE_Y_NEG) != 0 ? "1" : "0")
50+
.append((block.faceMask & FACE_Z_POS) != 0 ? "1" : "0")
51+
.append((block.faceMask & FACE_Z_NEG) != 0 ? "1" : "0");
5252

5353
KirinoCore.LOGGER.info(" block pos: " + block.position.x + ", " + block.position.y + ", " + block.position.z + ", " + face);
5454

55-
if (blocks.contains(new BlockRecord(block.position.x, block.position.y, block.position.z, block.faces))) {
56-
addBlockSurface(block.position.x, block.position.y, block.position.z, block.faces, Color.RED.getRGB());
55+
if (blocks.contains(new BlockRecord(block.position.x, block.position.y, block.position.z, block.faceMask))) {
56+
addBlockSurface(block.position.x, block.position.y, block.position.z, block.faceMask, Color.RED.getRGB());
5757
} else {
58-
blocks.add(new BlockRecord(block.position.x, block.position.y, block.position.z, block.faces));
59-
addBlockSurface(block.position.x, block.position.y, block.position.z, block.faces, color.getRGB());
58+
blocks.add(new BlockRecord(block.position.x, block.position.y, block.position.z, block.faceMask));
59+
addBlockSurface(block.position.x, block.position.y, block.position.z, block.faceMask, color.getRGB());
6060
}
6161
}
6262
}

src/main/java/com/cleanroommc/kirino/engine/render/task/job/ChunkMeshletGenJob.java

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
package com.cleanroommc.kirino.engine.render.task.job;
22

3-
import com.cleanroommc.kirino.KirinoCore;
43
import com.cleanroommc.kirino.ecs.entity.EntityManager;
54
import com.cleanroommc.kirino.ecs.entity.EntityQuery;
65
import com.cleanroommc.kirino.ecs.job.IParallelJob;
76
import com.cleanroommc.kirino.ecs.job.JobDataQuery;
87
import com.cleanroommc.kirino.ecs.job.JobExternalDataQuery;
98
import com.cleanroommc.kirino.ecs.storage.IPrimitiveArray;
9+
import com.cleanroommc.kirino.engine.render.geometry.Block;
10+
import com.cleanroommc.kirino.engine.render.geometry.Meshlet;
1011
import com.cleanroommc.kirino.engine.render.geometry.component.ChunkComponent;
1112
import com.cleanroommc.kirino.engine.render.gizmos.GizmosManager;
1213
import com.google.common.base.Preconditions;
14+
import com.google.common.collect.ImmutableList;
1315
import net.minecraft.block.state.IBlockState;
1416
import net.minecraft.client.multiplayer.ChunkProviderClient;
1517
import net.minecraft.init.Blocks;
1618
import net.minecraft.world.chunk.Chunk;
19+
import org.joml.Vector3f;
20+
import org.joml.Vector3i;
1721
import org.jspecify.annotations.NonNull;
1822

19-
import java.awt.*;
23+
import java.util.ArrayDeque;
24+
import java.util.ArrayList;
25+
import java.util.Deque;
26+
import java.util.List;
2027

2128
public class ChunkMeshletGenJob implements IParallelJob {
2229
// todo: pass opaque or transparent
@@ -46,14 +53,32 @@ public void query(@NonNull EntityQuery entityQuery) {
4653
}
4754

4855
record ChunkCluster(
49-
int chunkX, int chunkY, int chunkZ,
56+
int chunkY,
5057
Chunk center,
5158
Chunk xPlus,
5259
Chunk xMinus,
5360
Chunk zPlus,
5461
Chunk zMinus) {
5562
}
5663

64+
final static ImmutableList<Vector3i> FACE_DIRS = ImmutableList.of(
65+
new Vector3i(1, 0, 0),
66+
new Vector3i(-1, 0, 0),
67+
new Vector3i(0, 1, 0),
68+
new Vector3i(0, -1, 0),
69+
new Vector3i(0, 0, 1),
70+
new Vector3i(0, 0, -1));
71+
72+
final static int FACE_X_POS = 0b100000;
73+
final static int FACE_X_NEG = 0b010000;
74+
final static int FACE_Y_POS = 0b001000;
75+
final static int FACE_Y_NEG = 0b000100;
76+
final static int FACE_Z_POS = 0b000010;
77+
final static int FACE_Z_NEG = 0b000001;
78+
79+
final static double MESHLET_MAX_ANGLE = Math.PI / 2f;
80+
final static int MESHLET_MAX_SIZE = 32;
81+
5782
@Override
5883
public void execute(@NonNull EntityManager entityManager, int index) {
5984
Preconditions.checkState(pass == 0 || pass == 1,
@@ -68,16 +93,18 @@ public void execute(@NonNull EntityManager entityManager, int index) {
6893
int chunkZ = chunkPosZArray.getInt(index);
6994

7095
ChunkCluster chunkCluster = new ChunkCluster(
71-
chunkX, chunkY, chunkZ,
96+
chunkY,
7297
chunkProvider.provideChunk(chunkX, chunkZ),
7398
chunkProvider.provideChunk(chunkX + 1, chunkZ),
7499
chunkProvider.provideChunk(chunkX - 1, chunkZ),
75100
chunkProvider.provideChunk(chunkX, chunkZ + 1),
76101
chunkProvider.provideChunk(chunkX, chunkZ - 1));
77102

78103
int[][][] faceMask = new int[16][16][16];
104+
boolean[][][] visited = new boolean[16][16][16];
79105

80106
buildFaceMask(faceMask, chunkCluster);
107+
regionGrowing(faceMask, visited);
81108
}
82109

83110
/**
@@ -155,18 +182,126 @@ boolean blockExists(ChunkCluster chunkCluster, int x, int y, int z) {
155182
}
156183

157184
void buildFaceMask(int[][][] faceMask, ChunkCluster chunkCluster) {
158-
int xWorldCoord = chunkCluster.chunkX * 16;
159-
int yWorldCoord = chunkCluster.chunkY * 16;
160-
int zWorldCoord = chunkCluster.chunkZ * 16;
161-
162185
for (int x = 0; x < 16; x++) {
163186
for (int y = 0; y < 16; y++) {
164187
for (int z = 0; z < 16; z++) {
188+
faceMask[x][y][z] = 0;
165189
if (blockExists(chunkCluster, x, y, z)) {
166-
gizmosManager.addBlockSurface(x, y, z, 0b111111, new Color(0.5f, 0, 0, 0.5f).getRGB());
190+
for (int i = 0; i < FACE_DIRS.size(); i++) {
191+
Vector3i dir = FACE_DIRS.get(i);
192+
int mask = 1 << (5 - i);
193+
// neighbor doesn't exist -> face exists
194+
if (!blockExists(chunkCluster, x + dir.x, y + dir.y, z + dir.z)) {
195+
faceMask[x][y][z] |= mask;
196+
}
197+
}
167198
}
168199
}
169200
}
170201
}
171202
}
203+
204+
Vector3f dominantNormal(int faceMask) {
205+
float x = 0f, y = 0f, z = 0f;
206+
if ((faceMask & FACE_X_POS) != 0 && (faceMask & FACE_X_NEG) != 0) {
207+
x += 2f;
208+
} else {
209+
if ((faceMask & FACE_X_POS) != 0) {
210+
x += 1f;
211+
}
212+
if ((faceMask & FACE_X_NEG) != 0) {
213+
x -= 1f;
214+
}
215+
}
216+
if ((faceMask & FACE_Y_POS) != 0 && (faceMask & FACE_Y_NEG) != 0) {
217+
y += 2f;
218+
} else {
219+
if ((faceMask & FACE_Y_POS) != 0) {
220+
y += 1f;
221+
}
222+
if ((faceMask & FACE_Y_NEG) != 0) {
223+
y -= 1f;
224+
}
225+
}
226+
if ((faceMask & FACE_Z_POS) != 0 && (faceMask & FACE_Z_NEG) != 0) {
227+
z += 2f;
228+
} else {
229+
if ((faceMask & FACE_Z_POS) != 0) {
230+
z += 1f;
231+
}
232+
if ((faceMask & FACE_Z_NEG) != 0) {
233+
z -= 1f;
234+
}
235+
}
236+
return (new Vector3f(x, y, z)).normalize();
237+
}
238+
239+
void regionGrowing(int[][][] faceMask, boolean[][][] visited) {
240+
for (int x = 0; x < 16; x++) {
241+
for (int y = 0; y < 16; y++) {
242+
for (int z = 0; z < 16; z++) {
243+
244+
if (faceMask[x][y][z] == 0) {
245+
continue;
246+
}
247+
if (visited[x][y][z]) {
248+
continue;
249+
}
250+
251+
Block voxel = new Block(x, y, z, faceMask[x][y][z]);
252+
253+
List<Block> cluster = new ArrayList<>();
254+
Deque<Block> queue = new ArrayDeque<>();
255+
256+
cluster.add(voxel);
257+
queue.offerLast(voxel);
258+
visited[x][y][z] = true;
259+
260+
Vector3f meshletNormal = dominantNormal(faceMask[x][y][z]);
261+
262+
Block v;
263+
while ((v = queue.pollFirst()) != null) {
264+
for (Vector3i dir : FACE_DIRS) {
265+
int nx = v.position.x + dir.x;
266+
int ny = v.position.y + dir.y;
267+
int nz = v.position.z + dir.z;
268+
// ignore if it's out of the bounds
269+
if (nx == -1 || nx == 16 || ny == -1 || ny == 16 || nz == -1 || nz == 16) {
270+
continue;
271+
}
272+
// ignore if it's not the surface
273+
if (faceMask[nx][ny][nz] == 0) {
274+
continue;
275+
}
276+
if (visited[nx][ny][nz]) {
277+
continue;
278+
}
279+
280+
Vector3f voxelNormal = dominantNormal(faceMask[nx][ny][nz]);
281+
282+
if (meshletNormal.dot(voxelNormal) < Math.cos(MESHLET_MAX_ANGLE)) {
283+
continue;
284+
}
285+
if (cluster.size() >= MESHLET_MAX_SIZE) {
286+
continue;
287+
}
288+
289+
Block newVoxel = new Block(nx, ny, nz, faceMask[nx][ny][nz]);
290+
cluster.add(newVoxel);
291+
queue.offerLast(newVoxel);
292+
visited[nx][ny][nz] = true;
293+
294+
float n = cluster.size();
295+
meshletNormal = meshletNormal
296+
.mul((n - 1f) / n)
297+
.add(voxelNormal.mul(1f / n))
298+
.normalize();
299+
}
300+
}
301+
302+
gizmosManager.addMeshlet(new Meshlet(cluster));
303+
}
304+
}
305+
}
306+
}
172307
}

src/test/java/com/cleanroommc/test/kirino/QuantileUtilsTest.java

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.cleanroommc.test.kirino;
22

3-
import com.cleanroommc.kirino.engine.render.task.adt.Meshlet;
43
import com.cleanroommc.kirino.utils.QuantileUtils;
54
import net.minecraft.util.EnumFacing;
65
import org.junit.Test;
@@ -41,24 +40,24 @@ public void testMedianStrings() {
4140
assertEquals("5", QuantileUtils.median(arr));
4241
}
4342

44-
@Test
45-
public void testMedianObjects() {
46-
Random rng = new Random();
47-
rng.setSeed(114514);
48-
49-
List<Meshlet> list = new ArrayList<>();
50-
for (int x = 0; x < 16; x++) {
51-
for (int y = 0; y < 16; y++) {
52-
for (int z = 0; z < 16; z++) {
53-
if (rng.nextBoolean()) {
54-
list.add(new Meshlet(EnumFacing.DOWN, x, y, z, 0b111111,false));
55-
}
56-
}
57-
}
58-
}
59-
60-
QuantileUtils.median(list.toArray(new Meshlet[0]));
61-
62-
//assertDoesNotThrow(() -> QuantileUtils.median(list.toArray(new Meshlet[0])));
63-
}
43+
// @Test
44+
// public void testMedianObjects() {
45+
// Random rng = new Random();
46+
// rng.setSeed(114514);
47+
//
48+
// List<Meshlet> list = new ArrayList<>();
49+
// for (int x = 0; x < 16; x++) {
50+
// for (int y = 0; y < 16; y++) {
51+
// for (int z = 0; z < 16; z++) {
52+
// if (rng.nextBoolean()) {
53+
// list.add(new Meshlet(EnumFacing.DOWN, x, y, z, 0b111111,false));
54+
// }
55+
// }
56+
// }
57+
// }
58+
//
59+
// QuantileUtils.median(list.toArray(new Meshlet[0]));
60+
//
61+
// //assertDoesNotThrow(() -> QuantileUtils.median(list.toArray(new Meshlet[0])));
62+
// }
6463
}

0 commit comments

Comments
 (0)