1
1
package net .caffeinemc .mods .sodium .client .render .chunk .occlusion ;
2
2
3
3
import it .unimi .dsi .fastutil .longs .Long2ReferenceMap ;
4
+ import it .unimi .dsi .fastutil .longs .LongArrayFIFOQueue ;
5
+ import it .unimi .dsi .fastutil .longs .LongOpenHashSet ;
4
6
import net .caffeinemc .mods .sodium .client .render .chunk .RenderSection ;
5
7
import net .caffeinemc .mods .sodium .client .render .viewport .CameraTransform ;
6
8
import net .caffeinemc .mods .sodium .client .render .viewport .Viewport ;
@@ -17,6 +19,8 @@ public class OcclusionCuller {
17
19
private final Level level ;
18
20
19
21
private final DoubleBufferedQueue <RenderSection > queue = new DoubleBufferedQueue <>();
22
+ private LongArrayFIFOQueue initQueue ;
23
+ private LongOpenHashSet initVisited ;
20
24
21
25
public OcclusionCuller (Long2ReferenceMap <RenderSection > sections , Level level ) {
22
26
this .sections = sections ;
@@ -180,10 +184,14 @@ private static int getOutwardDirections(SectionPos origin, RenderSection section
180
184
}
181
185
182
186
private static boolean isWithinRenderDistance (CameraTransform camera , RenderSection section , float maxDistance ) {
187
+ return isWithinRenderDistance (camera , section .getOriginX (), section .getOriginY (), section .getOriginZ (), maxDistance );
188
+ }
189
+
190
+ private static boolean isWithinRenderDistance (CameraTransform camera , int originX , int originY , int originZ , float maxDistance ) {
183
191
// origin point of the chunk's bounding box (in view space)
184
- int ox = section . getOriginX () - camera .intX ;
185
- int oy = section . getOriginY () - camera .intY ;
186
- int oz = section . getOriginZ () - camera .intZ ;
192
+ int ox = originX - camera .intX ;
193
+ int oy = originY - camera .intY ;
194
+ int oz = originZ - camera .intZ ;
187
195
188
196
// coordinates of the point to compare (in view space)
189
197
// this is the closest point within the bounding box to the center (0, 0, 0)
@@ -223,6 +231,7 @@ private void init(Visitor visitor,
223
231
int frame )
224
232
{
225
233
var origin = viewport .getChunkCoord ();
234
+ var transform = viewport .getTransform ();
226
235
227
236
if (origin .getY () < this .level .getMinSectionY ()) {
228
237
// below the level
@@ -237,7 +246,7 @@ private void init(Visitor visitor,
237
246
if (originSection != null ) {
238
247
this .initAtExistingSection (visitor , queue , originSection , useOcclusionCulling , frame );
239
248
} else {
240
- this .initAtNonExistingSection (visitor , queue , viewport , useOcclusionCulling , searchDistance , frame );
249
+ this .initAtNonExistingSection (queue , viewport , transform , searchDistance , frame );
241
250
}
242
251
}
243
252
}
@@ -262,64 +271,75 @@ private void initAtExistingSection(Visitor visitor, WriteQueue<RenderSection> qu
262
271
visitNeighbors (queue , section , outgoing , frame );
263
272
}
264
273
265
- private void initAtNonExistingSection (Visitor visitor , WriteQueue <RenderSection > queue , Viewport viewport , boolean useOcclusionCulling , float searchDistance , int frame ) {
274
+ private void initAtNonExistingSection (WriteQueue <RenderSection > queue , Viewport viewport , CameraTransform transform , float searchDistance , int frame ) {
266
275
var origin = viewport .getChunkCoord ();
276
+ if (this .initQueue == null ) {
277
+ this .initQueue = new LongArrayFIFOQueue (200 );
278
+ } else {
279
+ this .initQueue .clear ();
280
+ }
281
+ if (this .initVisited == null ) {
282
+ this .initVisited = new LongOpenHashSet (200 );
283
+ } else {
284
+ this .initVisited .clear ();
285
+ }
286
+
287
+ var originPos = origin .asLong ();
288
+ this .initQueue .enqueue (originPos );
289
+ this .initVisited .add (originPos );
290
+
267
291
var minY = this .level .getMinSectionY ();
268
292
var maxY = this .level .getMaxSectionY ();
269
293
var originX = origin .getX ();
270
294
var originY = origin .getY ();
271
295
var originZ = origin .getZ ();
272
296
273
- // iterate shells until one is found with a loaded and visible section
274
- var foundAny = false ;
275
- var radius = 1 ;
276
- while (!foundAny ) {
277
- // iterate a shell around the origin
278
- var bigStep = radius * 2 ;
279
- for (var dy = -radius ; dy <= radius ; dy ++) {
280
- var y = originY + dy ;
281
- // skip layers outside the world's y range
282
- if (y < minY || y > maxY ) {
283
- continue ;
284
- }
297
+ while (!this .initQueue .isEmpty ()) {
298
+ var current = this .initQueue .dequeueLong ();
285
299
286
- // iterate only the perimeter with the current radius
287
- var notYFace = !(dy == -radius || dy == radius );
288
- for (var dx = -radius ; dx <= radius ; dx ++) {
289
- var zStep = notYFace && (dx == -radius || dx == radius ) ? bigStep : 1 ;
290
- for (var dz = -radius ; dz <= radius ; dz += zStep ) {
291
- var x = originX + dx ;
292
- var z = originZ + dz ;
293
-
294
- // visit loaded visible sections and queue their neighbors
295
- var section = this .getRenderSection (x , y , z );
296
- if (section != null && isSectionVisible (section , viewport , searchDistance )) {
297
- foundAny = true ;
298
-
299
- // use all directions as incoming, using just some yields a broken result
300
- var incoming = GraphDirectionSet .ALL ;
301
- section .setIncomingDirections (incoming );
302
- section .setLastVisibleFrame (frame );
303
-
304
- visitor .visit (section );
305
-
306
- // reduce set of neighbors to visit based on visibility connections
307
- int connections = getOutwardDirections (origin , section );
308
- if (useOcclusionCulling ) {
309
- connections &= VisibilityEncoding .getConnections (section .getVisibilityData (), incoming );
310
- }
311
-
312
- visitNeighbors (queue , section , connections , frame );
313
- }
314
- }
315
- }
300
+ var x = SectionPos .x (current );
301
+ var y = SectionPos .y (current );
302
+ var z = SectionPos .z (current );
303
+
304
+ // visit neighbors and add them to the init queue and/or the main graph traversal queue
305
+ if (x <= originX ) {
306
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x - 1 , y , z , GraphDirection .WEST );
316
307
}
308
+ if (x >= originX ) {
309
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x + 1 , y , z , GraphDirection .EAST );
310
+ }
311
+ if (y <= originY && y > minY ) {
312
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x , y - 1 , z , GraphDirection .DOWN );
313
+ }
314
+ if (y >= originY && y < maxY ) {
315
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x , y + 1 , z , GraphDirection .UP );
316
+ }
317
+ if (z <= originZ ) {
318
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x , y , z - 1 , GraphDirection .NORTH );
319
+ }
320
+ if (z >= originZ ) {
321
+ visitEmptyNeighbor (queue , frame , transform , searchDistance , x , y , z + 1 , GraphDirection .SOUTH );
322
+ }
323
+ }
324
+ }
325
+
326
+ private void visitEmptyNeighbor (WriteQueue <RenderSection > queue , int frame , CameraTransform transform , float searchDistance , int x , int y , int z , int incoming ) {
327
+ if (!isWithinRenderDistance (transform , x << 4 , y << 4 , z << 4 , searchDistance )) {
328
+ return ;
329
+ }
330
+
331
+ var pos = SectionPos .asLong (x , y , z );
332
+ var section = this .sections .get (pos );
317
333
318
- radius ++;
334
+ // sections that exist get queued
335
+ if (section != null ) {
336
+ visitNode (queue , section , incoming , frame );
337
+ }
319
338
320
- // don't exceed the search distance with the init search
321
- if (radius << 4 > searchDistance ) {
322
- break ;
339
+ // sections that don't exist or are empty are further traversed in the init process
340
+ if (section == null || section .getFlags () == 0 ) {
341
+ if (this .initVisited .add (pos )) {
342
+ this .initQueue .enqueue (pos );
323
343
}
324
344
}
325
345
}
0 commit comments