package net.minecraft.client.renderer; import com.google.common.collect.Lists; import com.google.common.collect.Queues; import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Queue; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import javax.annotation.Nullable; import net.minecraft.Util; import net.minecraft.client.Camera; import net.minecraft.client.renderer.chunk.SectionRenderDispatcher; import net.minecraft.client.renderer.culling.Frustum; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.core.SectionPos; import net.minecraft.server.level.ChunkTrackingView; import net.minecraft.util.Mth; import net.minecraft.util.VisibleForDebug; import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.LevelHeightAccessor; import net.minecraft.world.phys.Vec3; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.slf4j.Logger; @OnlyIn(Dist.CLIENT) public class SectionOcclusionGraph { private static final Logger LOGGER = LogUtils.getLogger(); private static final Direction[] DIRECTIONS = Direction.values(); private static final int MINIMUM_ADVANCED_CULLING_DISTANCE = 60; private static final double CEILED_SECTION_DIAGONAL = Math.ceil(Math.sqrt(3.0) * 16.0); private boolean needsFullUpdate = true; @Nullable private Future fullUpdateTask; @Nullable private ViewArea viewArea; private final AtomicReference currentGraph = new AtomicReference<>(); private final AtomicReference nextGraphEvents = new AtomicReference<>(); private final AtomicBoolean needsFrustumUpdate = new AtomicBoolean(false); public void waitAndReset(@Nullable ViewArea p_298923_) { if (this.fullUpdateTask != null) { try { this.fullUpdateTask.get(); this.fullUpdateTask = null; } catch (Exception exception) { LOGGER.warn("Full update failed", (Throwable)exception); } } this.viewArea = p_298923_; if (p_298923_ != null) { this.currentGraph.set(new SectionOcclusionGraph.GraphState(p_298923_)); this.invalidate(); } else { this.currentGraph.set(null); } } public void invalidate() { this.needsFullUpdate = true; } public void addSectionsInFrustum(Frustum p_299761_, List p_301346_, List p_365911_) { this.currentGraph.get().storage().sectionTree.visitNodes((p_357918_, p_357919_, p_357920_, p_357921_) -> { SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection = p_357918_.getSection(); if (sectionrenderdispatcher$rendersection != null) { p_301346_.add(sectionrenderdispatcher$rendersection); if (p_357921_) { p_365911_.add(sectionrenderdispatcher$rendersection); } } }, p_299761_, 32); } public boolean consumeFrustumUpdate() { return this.needsFrustumUpdate.compareAndSet(true, false); } public void onChunkReadyToRender(ChunkPos p_299612_) { SectionOcclusionGraph.GraphEvents sectionocclusiongraph$graphevents = this.nextGraphEvents.get(); if (sectionocclusiongraph$graphevents != null) { this.addNeighbors(sectionocclusiongraph$graphevents, p_299612_); } SectionOcclusionGraph.GraphEvents sectionocclusiongraph$graphevents1 = this.currentGraph.get().events; if (sectionocclusiongraph$graphevents1 != sectionocclusiongraph$graphevents) { this.addNeighbors(sectionocclusiongraph$graphevents1, p_299612_); } } public void schedulePropagationFrom(SectionRenderDispatcher.RenderSection p_301377_) { SectionOcclusionGraph.GraphEvents sectionocclusiongraph$graphevents = this.nextGraphEvents.get(); if (sectionocclusiongraph$graphevents != null) { sectionocclusiongraph$graphevents.sectionsToPropagateFrom.add(p_301377_); } SectionOcclusionGraph.GraphEvents sectionocclusiongraph$graphevents1 = this.currentGraph.get().events; if (sectionocclusiongraph$graphevents1 != sectionocclusiongraph$graphevents) { sectionocclusiongraph$graphevents1.sectionsToPropagateFrom.add(p_301377_); } } public void update( boolean p_301275_, Camera p_298972_, Frustum p_298939_, List p_300432_, LongOpenHashSet p_365816_ ) { Vec3 vec3 = p_298972_.getPosition(); if (this.needsFullUpdate && (this.fullUpdateTask == null || this.fullUpdateTask.isDone())) { this.scheduleFullUpdate(p_301275_, p_298972_, vec3, p_365816_); } this.runPartialUpdate(p_301275_, p_298939_, p_300432_, vec3, p_365816_); } private void scheduleFullUpdate(boolean p_298569_, Camera p_299582_, Vec3 p_297830_, LongOpenHashSet p_370191_) { this.needsFullUpdate = false; LongOpenHashSet longopenhashset = p_370191_.clone(); this.fullUpdateTask = CompletableFuture.runAsync(() -> { SectionOcclusionGraph.GraphState sectionocclusiongraph$graphstate = new SectionOcclusionGraph.GraphState(this.viewArea); this.nextGraphEvents.set(sectionocclusiongraph$graphstate.events); Queue queue = Queues.newArrayDeque(); this.initializeQueueForFullUpdate(p_299582_, queue); queue.forEach(p_299757_ -> sectionocclusiongraph$graphstate.storage.sectionToNodeMap.put(p_299757_.section, p_299757_)); this.runUpdates(sectionocclusiongraph$graphstate.storage, p_297830_, queue, p_298569_, p_299279_ -> { }, longopenhashset); this.currentGraph.set(sectionocclusiongraph$graphstate); this.nextGraphEvents.set(null); this.needsFrustumUpdate.set(true); }, Util.backgroundExecutor()); } private void runPartialUpdate( boolean p_298388_, Frustum p_299940_, List p_297967_, Vec3 p_299094_, LongOpenHashSet p_363554_ ) { SectionOcclusionGraph.GraphState sectionocclusiongraph$graphstate = this.currentGraph.get(); this.queueSectionsWithNewNeighbors(sectionocclusiongraph$graphstate); if (!sectionocclusiongraph$graphstate.events.sectionsToPropagateFrom.isEmpty()) { Queue queue = Queues.newArrayDeque(); while (!sectionocclusiongraph$graphstate.events.sectionsToPropagateFrom.isEmpty()) { SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection = sectionocclusiongraph$graphstate.events.sectionsToPropagateFrom.poll(); SectionOcclusionGraph.Node sectionocclusiongraph$node = sectionocclusiongraph$graphstate.storage .sectionToNodeMap .get(sectionrenderdispatcher$rendersection); if (sectionocclusiongraph$node != null && sectionocclusiongraph$node.section == sectionrenderdispatcher$rendersection) { queue.add(sectionocclusiongraph$node); } } Frustum frustum = LevelRenderer.offsetFrustum(p_299940_); Consumer consumer = p_357923_ -> { if (frustum.isVisible(p_357923_.getBoundingBox())) { this.needsFrustumUpdate.set(true); } }; this.runUpdates(sectionocclusiongraph$graphstate.storage, p_299094_, queue, p_298388_, consumer, p_363554_); } } private void queueSectionsWithNewNeighbors(SectionOcclusionGraph.GraphState p_298801_) { LongIterator longiterator = p_298801_.events.chunksWhichReceivedNeighbors.iterator(); while (longiterator.hasNext()) { long i = longiterator.nextLong(); List list = p_298801_.storage.chunksWaitingForNeighbors.get(i); if (list != null && list.get(0).hasAllNeighbors()) { p_298801_.events.sectionsToPropagateFrom.addAll(list); p_298801_.storage.chunksWaitingForNeighbors.remove(i); } } p_298801_.events.chunksWhichReceivedNeighbors.clear(); } private void addNeighbors(SectionOcclusionGraph.GraphEvents p_300825_, ChunkPos p_297758_) { p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x - 1, p_297758_.z)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x, p_297758_.z - 1)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x + 1, p_297758_.z)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x, p_297758_.z + 1)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x - 1, p_297758_.z - 1)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x - 1, p_297758_.z + 1)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x + 1, p_297758_.z - 1)); p_300825_.chunksWhichReceivedNeighbors.add(ChunkPos.asLong(p_297758_.x + 1, p_297758_.z + 1)); } private void initializeQueueForFullUpdate(Camera p_298889_, Queue p_297605_) { BlockPos blockpos = p_298889_.getBlockPosition(); long i = SectionPos.asLong(blockpos); int j = SectionPos.y(i); SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection = this.viewArea.getRenderSection(i); if (sectionrenderdispatcher$rendersection == null) { LevelHeightAccessor levelheightaccessor = this.viewArea.getLevelHeightAccessor(); boolean flag = j < levelheightaccessor.getMinSectionY(); int k = flag ? levelheightaccessor.getMinSectionY() : levelheightaccessor.getMaxSectionY(); int l = this.viewArea.getViewDistance(); List list = Lists.newArrayList(); int i1 = SectionPos.x(i); int j1 = SectionPos.z(i); for (int k1 = -l; k1 <= l; k1++) { for (int l1 = -l; l1 <= l; l1++) { SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection1 = this.viewArea .getRenderSection(SectionPos.asLong(k1 + i1, k, l1 + j1)); if (sectionrenderdispatcher$rendersection1 != null && this.isInViewDistance(i, sectionrenderdispatcher$rendersection1.getSectionNode())) { Direction direction = flag ? Direction.UP : Direction.DOWN; SectionOcclusionGraph.Node sectionocclusiongraph$node = new SectionOcclusionGraph.Node( sectionrenderdispatcher$rendersection1, direction, 0 ); sectionocclusiongraph$node.setDirections(sectionocclusiongraph$node.directions, direction); if (k1 > 0) { sectionocclusiongraph$node.setDirections(sectionocclusiongraph$node.directions, Direction.EAST); } else if (k1 < 0) { sectionocclusiongraph$node.setDirections(sectionocclusiongraph$node.directions, Direction.WEST); } if (l1 > 0) { sectionocclusiongraph$node.setDirections(sectionocclusiongraph$node.directions, Direction.SOUTH); } else if (l1 < 0) { sectionocclusiongraph$node.setDirections(sectionocclusiongraph$node.directions, Direction.NORTH); } list.add(sectionocclusiongraph$node); } } } list.sort(Comparator.comparingDouble(p_299847_ -> blockpos.distSqr(p_299847_.section.getOrigin().offset(8, 8, 8)))); p_297605_.addAll(list); } else { p_297605_.add(new SectionOcclusionGraph.Node(sectionrenderdispatcher$rendersection, null, 0)); } } private void runUpdates( SectionOcclusionGraph.GraphStorage p_299200_, Vec3 p_300018_, Queue p_300570_, boolean p_300892_, Consumer p_298647_, LongOpenHashSet p_362895_ ) { int i = 16; BlockPos blockpos = new BlockPos( Mth.floor(p_300018_.x / 16.0) * 16, Mth.floor(p_300018_.y / 16.0) * 16, Mth.floor(p_300018_.z / 16.0) * 16 ); long j = SectionPos.asLong(blockpos); BlockPos blockpos1 = blockpos.offset(8, 8, 8); while (!p_300570_.isEmpty()) { SectionOcclusionGraph.Node sectionocclusiongraph$node = p_300570_.poll(); SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection = sectionocclusiongraph$node.section; if (!p_362895_.contains(sectionocclusiongraph$node.section.getSectionNode())) { if (p_299200_.sectionTree.add(sectionocclusiongraph$node.section)) { p_298647_.accept(sectionocclusiongraph$node.section); } } else { sectionocclusiongraph$node.section .compiled .compareAndSet(SectionRenderDispatcher.CompiledSection.UNCOMPILED, SectionRenderDispatcher.CompiledSection.EMPTY); } boolean flag = Math.abs(sectionrenderdispatcher$rendersection.getOrigin().getX() - blockpos.getX()) > 60 || Math.abs(sectionrenderdispatcher$rendersection.getOrigin().getY() - blockpos.getY()) > 60 || Math.abs(sectionrenderdispatcher$rendersection.getOrigin().getZ() - blockpos.getZ()) > 60; for (Direction direction : DIRECTIONS) { SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection1 = this.getRelativeFrom( j, sectionrenderdispatcher$rendersection, direction ); if (sectionrenderdispatcher$rendersection1 != null && (!p_300892_ || !sectionocclusiongraph$node.hasDirection(direction.getOpposite()))) { if (p_300892_ && sectionocclusiongraph$node.hasSourceDirections()) { SectionRenderDispatcher.CompiledSection sectionrenderdispatcher$compiledsection = sectionrenderdispatcher$rendersection.getCompiled(); boolean flag1 = false; for (int k = 0; k < DIRECTIONS.length; k++) { if (sectionocclusiongraph$node.hasSourceDirection(k) && sectionrenderdispatcher$compiledsection.facesCanSeeEachother(DIRECTIONS[k].getOpposite(), direction)) { flag1 = true; break; } } if (!flag1) { continue; } } if (p_300892_ && flag) { BlockPos blockpos2 = sectionrenderdispatcher$rendersection1.getOrigin(); BlockPos blockpos3 = blockpos2.offset( ( direction.getAxis() == Direction.Axis.X ? blockpos1.getX() <= blockpos2.getX() : blockpos1.getX() >= blockpos2.getX() ) ? 0 : 16, ( direction.getAxis() == Direction.Axis.Y ? blockpos1.getY() <= blockpos2.getY() : blockpos1.getY() >= blockpos2.getY() ) ? 0 : 16, ( direction.getAxis() == Direction.Axis.Z ? blockpos1.getZ() <= blockpos2.getZ() : blockpos1.getZ() >= blockpos2.getZ() ) ? 0 : 16 ); Vec3 vec31 = new Vec3((double)blockpos3.getX(), (double)blockpos3.getY(), (double)blockpos3.getZ()); Vec3 vec3 = p_300018_.subtract(vec31).normalize().scale(CEILED_SECTION_DIAGONAL); boolean flag2 = true; while (p_300018_.subtract(vec31).lengthSqr() > 3600.0) { vec31 = vec31.add(vec3); LevelHeightAccessor levelheightaccessor = this.viewArea.getLevelHeightAccessor(); if (vec31.y > (double)levelheightaccessor.getMaxY() || vec31.y < (double)levelheightaccessor.getMinY()) { break; } SectionRenderDispatcher.RenderSection sectionrenderdispatcher$rendersection2 = this.viewArea .getRenderSectionAt(BlockPos.containing(vec31.x, vec31.y, vec31.z)); if (sectionrenderdispatcher$rendersection2 == null || p_299200_.sectionToNodeMap.get(sectionrenderdispatcher$rendersection2) == null ) { flag2 = false; break; } } if (!flag2) { continue; } } SectionOcclusionGraph.Node sectionocclusiongraph$node1 = p_299200_.sectionToNodeMap.get(sectionrenderdispatcher$rendersection1); if (sectionocclusiongraph$node1 != null) { sectionocclusiongraph$node1.addSourceDirection(direction); } else { SectionOcclusionGraph.Node sectionocclusiongraph$node2 = new SectionOcclusionGraph.Node( sectionrenderdispatcher$rendersection1, direction, sectionocclusiongraph$node.step + 1 ); sectionocclusiongraph$node2.setDirections(sectionocclusiongraph$node.directions, direction); if (sectionrenderdispatcher$rendersection1.hasAllNeighbors()) { p_300570_.add(sectionocclusiongraph$node2); p_299200_.sectionToNodeMap.put(sectionrenderdispatcher$rendersection1, sectionocclusiongraph$node2); } else if (this.isInViewDistance(j, sectionrenderdispatcher$rendersection1.getSectionNode())) { p_299200_.sectionToNodeMap.put(sectionrenderdispatcher$rendersection1, sectionocclusiongraph$node2); p_299200_.chunksWaitingForNeighbors .computeIfAbsent(ChunkPos.asLong(sectionrenderdispatcher$rendersection1.getOrigin()), p_298371_ -> new ArrayList<>()) .add(sectionrenderdispatcher$rendersection1); } } } } } } private boolean isInViewDistance(long p_363726_, long p_370059_) { return ChunkTrackingView.isInViewDistance( SectionPos.x(p_363726_), SectionPos.z(p_363726_), this.viewArea.getViewDistance(), SectionPos.x(p_370059_), SectionPos.z(p_370059_) ); } @Nullable private SectionRenderDispatcher.RenderSection getRelativeFrom(long p_369239_, SectionRenderDispatcher.RenderSection p_299737_, Direction p_301139_) { long i = p_299737_.getNeighborSectionNode(p_301139_); if (!this.isInViewDistance(p_369239_, i)) { return null; } else { return Mth.abs(SectionPos.y(p_369239_) - SectionPos.y(i)) > this.viewArea.getViewDistance() ? null : this.viewArea.getRenderSection(i); } } @Nullable @VisibleForDebug public SectionOcclusionGraph.Node getNode(SectionRenderDispatcher.RenderSection p_299335_) { return this.currentGraph.get().storage.sectionToNodeMap.get(p_299335_); } public Octree getOctree() { return this.currentGraph.get().storage.sectionTree; } @OnlyIn(Dist.CLIENT) static record GraphEvents(LongSet chunksWhichReceivedNeighbors, BlockingQueue sectionsToPropagateFrom) { GraphEvents() { this(new LongOpenHashSet(), new LinkedBlockingQueue<>()); } } @OnlyIn(Dist.CLIENT) static record GraphState(SectionOcclusionGraph.GraphStorage storage, SectionOcclusionGraph.GraphEvents events) { GraphState(ViewArea p_367222_) { this(new SectionOcclusionGraph.GraphStorage(p_367222_), new SectionOcclusionGraph.GraphEvents()); } } @OnlyIn(Dist.CLIENT) static class GraphStorage { public final SectionOcclusionGraph.SectionToNodeMap sectionToNodeMap; public final Octree sectionTree; public final Long2ObjectMap> chunksWaitingForNeighbors; public GraphStorage(ViewArea p_364979_) { this.sectionToNodeMap = new SectionOcclusionGraph.SectionToNodeMap(p_364979_.sections.length); this.sectionTree = new Octree(p_364979_.getCameraSectionPos(), p_364979_.getViewDistance(), p_364979_.sectionGridSizeY, p_364979_.level.getMinY()); this.chunksWaitingForNeighbors = new Long2ObjectOpenHashMap<>(); } } @OnlyIn(Dist.CLIENT) @VisibleForDebug public static class Node { @VisibleForDebug protected final SectionRenderDispatcher.RenderSection section; private byte sourceDirections; byte directions; @VisibleForDebug public final int step; Node(SectionRenderDispatcher.RenderSection p_299649_, @Nullable Direction p_299325_, int p_298364_) { this.section = p_299649_; if (p_299325_ != null) { this.addSourceDirection(p_299325_); } this.step = p_298364_; } void setDirections(byte p_298984_, Direction p_300480_) { this.directions = (byte)(this.directions | p_298984_ | 1 << p_300480_.ordinal()); } boolean hasDirection(Direction p_299145_) { return (this.directions & 1 << p_299145_.ordinal()) > 0; } void addSourceDirection(Direction p_299877_) { this.sourceDirections = (byte)(this.sourceDirections | this.sourceDirections | 1 << p_299877_.ordinal()); } @VisibleForDebug public boolean hasSourceDirection(int p_301075_) { return (this.sourceDirections & 1 << p_301075_) > 0; } boolean hasSourceDirections() { return this.sourceDirections != 0; } @Override public int hashCode() { return Long.hashCode(this.section.getSectionNode()); } @Override public boolean equals(Object p_300561_) { return !(p_300561_ instanceof SectionOcclusionGraph.Node sectionocclusiongraph$node) ? false : this.section.getSectionNode() == sectionocclusiongraph$node.section.getSectionNode(); } } @OnlyIn(Dist.CLIENT) static class SectionToNodeMap { private final SectionOcclusionGraph.Node[] nodes; SectionToNodeMap(int p_298573_) { this.nodes = new SectionOcclusionGraph.Node[p_298573_]; } public void put(SectionRenderDispatcher.RenderSection p_297513_, SectionOcclusionGraph.Node p_298532_) { this.nodes[p_297513_.index] = p_298532_; } @Nullable public SectionOcclusionGraph.Node get(SectionRenderDispatcher.RenderSection p_297749_) { int i = p_297749_.index; return i >= 0 && i < this.nodes.length ? this.nodes[i] : null; } } }