Skip to content

Commit 83c7389

Browse files
committed
use air bfs traversal instead of shell iteration for better result
1 parent ea43041 commit 83c7389

File tree

1 file changed

+71
-51
lines changed

1 file changed

+71
-51
lines changed

common/src/main/java/net/caffeinemc/mods/sodium/client/render/chunk/occlusion/OcclusionCuller.java

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package net.caffeinemc.mods.sodium.client.render.chunk.occlusion;
22

33
import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
4+
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
5+
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
46
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
57
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
68
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
@@ -17,6 +19,8 @@ public class OcclusionCuller {
1719
private final Level level;
1820

1921
private final DoubleBufferedQueue<RenderSection> queue = new DoubleBufferedQueue<>();
22+
private LongArrayFIFOQueue initQueue;
23+
private LongOpenHashSet initVisited;
2024

2125
public OcclusionCuller(Long2ReferenceMap<RenderSection> sections, Level level) {
2226
this.sections = sections;
@@ -180,10 +184,14 @@ private static int getOutwardDirections(SectionPos origin, RenderSection section
180184
}
181185

182186
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) {
183191
// 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;
187195

188196
// coordinates of the point to compare (in view space)
189197
// this is the closest point within the bounding box to the center (0, 0, 0)
@@ -223,6 +231,7 @@ private void init(Visitor visitor,
223231
int frame)
224232
{
225233
var origin = viewport.getChunkCoord();
234+
var transform = viewport.getTransform();
226235

227236
if (origin.getY() < this.level.getMinSectionY()) {
228237
// below the level
@@ -237,7 +246,7 @@ private void init(Visitor visitor,
237246
if (originSection != null) {
238247
this.initAtExistingSection(visitor, queue, originSection, useOcclusionCulling, frame);
239248
} else {
240-
this.initAtNonExistingSection(visitor, queue, viewport, useOcclusionCulling, searchDistance, frame);
249+
this.initAtNonExistingSection(queue, viewport, transform, searchDistance, frame);
241250
}
242251
}
243252
}
@@ -262,64 +271,75 @@ private void initAtExistingSection(Visitor visitor, WriteQueue<RenderSection> qu
262271
visitNeighbors(queue, section, outgoing, frame);
263272
}
264273

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) {
266275
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+
267291
var minY = this.level.getMinSectionY();
268292
var maxY = this.level.getMaxSectionY();
269293
var originX = origin.getX();
270294
var originY = origin.getY();
271295
var originZ = origin.getZ();
272296

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();
285299

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);
316307
}
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);
317333

318-
radius++;
334+
// sections that exist get queued
335+
if (section != null) {
336+
visitNode(queue, section, incoming, frame);
337+
}
319338

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);
323343
}
324344
}
325345
}

0 commit comments

Comments
 (0)