From 5dcda3199085fe79521871ec209753d54d11ccad Mon Sep 17 00:00:00 2001 From: James Seibel Date: Tue, 21 Apr 2026 07:48:07 -0500 Subject: [PATCH] Try fixing LOD flashing/stuck low details --- .../core/render/QuadTree/LodQuadTree.java | 152 +++++++++++------- 1 file changed, 96 insertions(+), 56 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java index 1867601ea..cb555039a 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/QuadTree/LodQuadTree.java @@ -245,7 +245,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen //===================// //region - // remove out of bounds sections + // remove out of bound sections this.setCenterBlockPos(playerPos, (renderSection) -> { if (renderSection != null) @@ -481,7 +481,29 @@ public class LodQuadTree extends QuadTree implements IDebugRen //=========================// //region - private void recursivelyUpdateRenderSectionNode( + @NotNull + private QuadNode tryAddNodeToTree( + @NotNull QuadNode rootNode, + @Nullable QuadNode quadNode, + long sectionPos // section pos is needed here since the quad node may be null + ) + { + // create the node + if (quadNode == null) + { + rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider)); + quadNode = rootNode.getNode(sectionPos); + } + if (quadNode == null) + { + LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"]."); + } + + return quadNode; + } + + /** @return true if the node at this position has uploaded its render data */ + private boolean recursivelyUpdateRenderSectionNode( @NotNull DhBlockPos2D playerPos, @NotNull QuadNode rootNode, @Nullable QuadNode parentNode, @@ -495,16 +517,17 @@ public class LodQuadTree extends QuadTree implements IDebugRen //=====================// //region - // create the node - if (quadNode == null) - { - rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider)); - quadNode = rootNode.getNode(sectionPos); - } - if (quadNode == null) - { - LodUtil.assertNotReach("Unable to add node with pos ["+DhSectionPos.toString(sectionPos)+"] to tree root ["+rootNode+"]."); - } + quadNode = this.tryAddNodeToTree(rootNode, quadNode, sectionPos); + + + //// Skip sections that are out-of-bounds. + //// If not done some sections will appear and/or generate + //// outside the desired render distance + //if (!this.isSectionPosInBounds(quadNode.sectionPos)) + //{ + // return true; + //} + // make sure the render section is created (shouldn't be necessary, but just in case) LodRenderSection renderSection = quadNode.value; @@ -538,13 +561,13 @@ public class LodQuadTree extends QuadTree implements IDebugRen if (DhSectionPos.getDetailLevel(quadNode.sectionPos) > expectedDetailLevel) { - this.onDetailLevelTooHigh(playerPos, rootNode, quadNode); + return this.onDetailLevelTooLow(playerPos, rootNode, quadNode); } // the (expectedDetailLevel-1) fixes corners being cut out due to distance calculations using the LOD center else if (DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(quadNode.sectionPos) == expectedDetailLevel - 1) { - this.onDesiredDetailLevel(quadNode, parentNode); + return this.onDesiredDetailLevel(quadNode, parentNode); } else { @@ -553,74 +576,91 @@ public class LodQuadTree extends QuadTree implements IDebugRen //endregion } - private void onDetailLevelTooHigh( + /** @return true if the node at this position has uploaded its render data */ + private boolean onDetailLevelTooLow( @NotNull DhBlockPos2D playerPos, - @NotNull QuadNode rootNode, @NotNull QuadNode quadNode) + @NotNull QuadNode rootNode, + @NotNull QuadNode quadNode) { // recursively update each child node - boolean allChildNodesCanRender = true; + int childNodeRenderCount = 0; for (int i = 0; i < 4; i++) { - QuadNode childNode = quadNode.getChildByIndex(i); long childPos = DhSectionPos.getChildByIndex(quadNode.sectionPos, i); - this.recursivelyUpdateRenderSectionNode( - playerPos, - rootNode, quadNode, childNode, childPos); - childNode = quadNode.getChildByIndex(i); // needs to be gotten again in case a new node was added to the tree (this will often happen when moving into new areas where the children were deleted) + QuadNode childNode = quadNode.getChildByIndex(i); - // nodes shouldn't be null, but just in case - if (childNode != null - && childNode.value != null - && !childNode.value.gpuUploadComplete()) + boolean childCanRender = this.recursivelyUpdateRenderSectionNode( + playerPos, + rootNode, quadNode, childNode, childPos); + if (childCanRender) { - // the node is present but not uploaded yet - allChildNodesCanRender = false; + // node can be rendered + childNodeRenderCount++; } } - if (allChildNodesCanRender) + if (childNodeRenderCount >= 4) { - // all child nodes can render, this node isn't needed this.tickNodeHolder.addDisableNode(quadNode); + + + /* + DEBUG_RENDERER.makeParticle( + new AbstractDebugWireframeRenderer.BoxParticle( + new AbstractDebugWireframeRenderer.Box( + quadNode.sectionPos + , -64, 80, + 0.0f, + Color.BLUE), + 0.25, 0f + )); + */ + + // all children can render, + // the area will be filled when rendering + return true; } else { - // not all child positions are loaded yet, this one should be rendered instead - this.tickNodeHolder.addEnableNode(quadNode); + boolean nodeCanRender = quadNode.value != null + && quadNode.value.gpuUploadComplete(); + if (nodeCanRender) + { + // not all child positions are loaded yet, this one should be rendered instead + this.tickNodeHolder.addEnableNode(quadNode); + } + else + { + this.tickNodeHolder.addDisableNode(quadNode); + } + + + return nodeCanRender; } } - private void onDesiredDetailLevel( - @NotNull QuadNode quadNode, @Nullable QuadNode parentNode) + /** @return true if the node at this position has uploaded its render data */ + private boolean onDesiredDetailLevel( + @NotNull QuadNode quadNode, + @Nullable QuadNode parentNode) { - boolean allAdjNodesCanRender = true; - - // if the parent node is null, that means we're at the root node - // and we should always render - if (parentNode != null) + // Skip sections that are out-of-bounds. + // If not done some sections will appear and/or generate + // outside the desired render distance + if (!this.isSectionPosInBounds(quadNode.sectionPos)) { - // check if all adjacent nodes are ready to render - // this check is done to prevent some overlapping due to the parent node - // still being active - for (int i = 0; i < 4; i++) - { - QuadNode adjNode = parentNode.getChildByIndex(i); - // nodes shouldn't be null, but just in case there's an issue - if (adjNode != null - && adjNode.value != null - && !adjNode.value.gpuUploadComplete()) - { - // the node is present but not uploaded yet - allAdjNodesCanRender = false; - } - } + return true; } - if (allAdjNodesCanRender - && quadNode.value != null + if (quadNode.value != null && quadNode.value.gpuUploadComplete()) { this.tickNodeHolder.addEnableDeleteChildrenNode(quadNode); + return true; + } + else + { + return false; } }