11package com .cleanroommc .kirino .engine .render .task .job ;
22
3- import com .cleanroommc .kirino .KirinoCore ;
43import com .cleanroommc .kirino .ecs .entity .EntityManager ;
54import com .cleanroommc .kirino .ecs .entity .EntityQuery ;
65import com .cleanroommc .kirino .ecs .job .IParallelJob ;
76import com .cleanroommc .kirino .ecs .job .JobDataQuery ;
87import com .cleanroommc .kirino .ecs .job .JobExternalDataQuery ;
98import com .cleanroommc .kirino .ecs .storage .IPrimitiveArray ;
9+ import com .cleanroommc .kirino .engine .render .geometry .Block ;
10+ import com .cleanroommc .kirino .engine .render .geometry .Meshlet ;
1011import com .cleanroommc .kirino .engine .render .geometry .component .ChunkComponent ;
1112import com .cleanroommc .kirino .engine .render .gizmos .GizmosManager ;
1213import com .google .common .base .Preconditions ;
14+ import com .google .common .collect .ImmutableList ;
1315import net .minecraft .block .state .IBlockState ;
1416import net .minecraft .client .multiplayer .ChunkProviderClient ;
1517import net .minecraft .init .Blocks ;
1618import net .minecraft .world .chunk .Chunk ;
19+ import org .joml .Vector3f ;
20+ import org .joml .Vector3i ;
1721import 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
2128public 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}
0 commit comments