Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Culling if Origin is in an Unloaded Section #2893

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package net.caffeinemc.mods.sodium.client.render.chunk.occlusion;

import it.unimi.dsi.fastutil.longs.Long2ReferenceMap;
import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import net.caffeinemc.mods.sodium.client.render.chunk.RenderSection;
import net.caffeinemc.mods.sodium.client.render.viewport.CameraTransform;
import net.caffeinemc.mods.sodium.client.render.viewport.Viewport;
Expand All @@ -17,6 +19,8 @@ public class OcclusionCuller {
private final Level level;

private final DoubleBufferedQueue<RenderSection> queue = new DoubleBufferedQueue<>();
private LongArrayFIFOQueue initQueue;
private LongOpenHashSet initVisited;

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

private static boolean isWithinRenderDistance(CameraTransform camera, RenderSection section, float maxDistance) {
return isWithinRenderDistance(camera, section.getOriginX(), section.getOriginY(), section.getOriginZ(), maxDistance);
}

private static boolean isWithinRenderDistance(CameraTransform camera, int originX, int originY, int originZ, float maxDistance) {
// origin point of the chunk's bounding box (in view space)
int ox = section.getOriginX() - camera.intX;
int oy = section.getOriginY() - camera.intY;
int oz = section.getOriginZ() - camera.intZ;
int ox = originX - camera.intX;
int oy = originY - camera.intY;
int oz = originZ - camera.intZ;

// coordinates of the point to compare (in view space)
// this is the closest point within the bounding box to the center (0, 0, 0)
Expand Down Expand Up @@ -233,18 +241,16 @@ private void init(Visitor visitor,
this.initOutsideWorldHeight(queue, viewport, searchDistance, frame,
this.level.getMaxSectionY(), GraphDirection.UP);
} else {
this.initWithinWorld(visitor, queue, viewport, useOcclusionCulling, frame);
var originSection = this.sections.get(origin.asLong());
if (originSection != null) {
this.initAtExistingSection(visitor, queue, originSection, useOcclusionCulling, frame);
} else {
this.initAtNonExistingSection(queue, viewport, searchDistance, frame);
}
}
}

private void initWithinWorld(Visitor visitor, WriteQueue<RenderSection> queue, Viewport viewport, boolean useOcclusionCulling, int frame) {
var origin = viewport.getChunkCoord();
var section = this.getRenderSection(origin.getX(), origin.getY(), origin.getZ());

if (section == null) {
return;
}

private void initAtExistingSection(Visitor visitor, WriteQueue<RenderSection> queue, RenderSection section, boolean useOcclusionCulling, int frame) {
section.setLastVisibleFrame(frame);
section.setIncomingDirections(GraphDirectionSet.NONE);

Expand All @@ -264,6 +270,81 @@ private void initWithinWorld(Visitor visitor, WriteQueue<RenderSection> queue, V
visitNeighbors(queue, section, outgoing, frame);
}

private void initAtNonExistingSection(WriteQueue<RenderSection> queue, Viewport viewport, float searchDistance, int frame) {
var transform = viewport.getTransform();

var origin = viewport.getChunkCoord();
if (this.initQueue == null) {
this.initQueue = new LongArrayFIFOQueue(200);
} else {
this.initQueue.clear();
}
if (this.initVisited == null) {
this.initVisited = new LongOpenHashSet(200);
} else {
this.initVisited.clear();
}

var originPos = origin.asLong();
this.initQueue.enqueue(originPos);
this.initVisited.add(originPos);

var minY = this.level.getMinSectionY();
var maxY = this.level.getMaxSectionY();
var originX = origin.getX();
var originY = origin.getY();
var originZ = origin.getZ();

while (!this.initQueue.isEmpty()) {
var current = this.initQueue.dequeueLong();

var x = SectionPos.x(current);
var y = SectionPos.y(current);
var z = SectionPos.z(current);

// visit neighbors and add them to the init queue and/or the main graph traversal queue
if (x <= originX) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x - 1, y, z, GraphDirection.WEST);
}
if (x >= originX) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x + 1, y, z, GraphDirection.EAST);
}
if (y <= originY && y > minY) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x, y - 1, z, GraphDirection.DOWN);
}
if (y >= originY && y < maxY) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x, y + 1, z, GraphDirection.UP);
}
if (z <= originZ) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x, y, z - 1, GraphDirection.NORTH);
}
if (z >= originZ) {
visitEmptyNeighbor(queue, frame, transform, searchDistance, x, y, z + 1, GraphDirection.SOUTH);
}
}
}

private void visitEmptyNeighbor(WriteQueue<RenderSection> queue, int frame, CameraTransform transform, float searchDistance, int x, int y, int z, int incoming) {
if (!isWithinRenderDistance(transform, x << 4, y << 4, z << 4, searchDistance)) {
return;
}

var pos = SectionPos.asLong(x, y, z);
var section = this.sections.get(pos);

// sections that exist get queued
if (section != null) {
visitNode(queue, section, incoming, frame);
}

// sections that don't exist or are empty are further traversed in the init process
if (section == null || section.getFlags() == 0) {
if (this.initVisited.add(pos)) {
this.initQueue.enqueue(pos);
}
}
}

// Enqueues sections that are inside the viewport using diamond spiral iteration to avoid sorting and ensure a
// consistent order. Innermost layers are enqueued first. Within each layer, iteration starts at the northernmost
// section and proceeds counterclockwise (N->W->S->E).
Expand Down