diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index cb5a97e4d..c2d07b321 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -250,11 +250,12 @@ public class LodQuadTree extends QuadTree implements IDebugRen ArrayList nodesNeedingRetrieval, ArrayList nodesNeedingLoading) { - //===============================// - // node and render section setup // - //===============================// + //=====================// + // get/create the node // + // and render section // + //=====================// - // make sure the node is created + // create the node if (quadNode == null && this.isSectionPosInBounds(sectionPos)) // the position bounds should only fail when at the edge of the user's render distance { rootNode.setValue(sectionPos, new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider)); @@ -266,15 +267,12 @@ public class LodQuadTree extends QuadTree implements IDebugRen return false; } - // make sure the render section is created + // make sure the render section is created (shouldn't be necessary, but just in case) LodRenderSection renderSection = quadNode.value; - // create a new render section if missing if (renderSection == null) { - LodRenderSection newRenderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider); - rootNode.setValue(sectionPos, newRenderSection); - - renderSection = newRenderSection; // TODO this never seemed to be called, is it necessary? + renderSection = new LodRenderSection(sectionPos, this, this.level, this.fullDataSourceProvider); + rootNode.setValue(sectionPos, renderSection); } @@ -283,20 +281,22 @@ public class LodQuadTree extends QuadTree implements IDebugRen // handle enabling, loading, // // and disabling render sections // //===============================// - + //byte expectedDetailLevel = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL + 3; // can be used instead of the following logic for testing byte expectedDetailLevel = this.calculateExpectedDetailLevel(playerPos, sectionPos); expectedDetailLevel = (byte) Math.min(expectedDetailLevel, this.minRenderDetailLevel); expectedDetailLevel += DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - if (DhSectionPos.getDetailLevel(sectionPos) > expectedDetailLevel) { - // section detail level too high // + //=======================// + // detail level too high // + //=======================// + boolean thisPosIsRendering = renderSection.getRenderingEnabled(); boolean allChildrenSectionsAreLoaded = true; - // recursively update all child render sections + // recursively update each child render section for (int i = 0; i < 4; i++) { QuadNode childNode = quadNode.getChildByIndex(i); @@ -304,6 +304,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; } + if (!allChildrenSectionsAreLoaded) { // not all child positions are loaded yet, or this section is out of render range @@ -311,93 +312,90 @@ public class LodQuadTree extends QuadTree implements IDebugRen } else { - // all child positions are loaded, disable this section and enable its children. - renderSection.setRenderingEnabled(false); + // onRenderingDisabled() needs to be fired before the children are enabled so beacons render correctly + if (renderSection.getRenderingEnabled()) + { + renderSection.onRenderingDisabled(); + + // this position's rendering has been disabled due to children being rendered + DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(renderSection.pos, 128f, 156f, 0.09f, Color.WHITE), 0.2, 32f)); + } - // walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now + + // walk back down the tree and enable each child section for (int i = 0; i < 4; i++) { QuadNode childNode = quadNode.getChildByIndex(i); - boolean childSectionLoaded = this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); - allChildrenSectionsAreLoaded = childSectionLoaded && allChildrenSectionsAreLoaded; - } - if (!allChildrenSectionsAreLoaded) - { - // FIXME having world generation enabled in a pre-generated world that doesn't have any DH data can cause this to happen - // surprisingly reloadPos() doesn't appear to be the culprit, maybe there is an issue with reloading/changing the full data source? - //LOGGER.warn("Potential QuadTree concurrency issue. All child sections should be enabled and ready to render for pos: "+DhSectionPos.toString(sectionPos)); + this.recursivelyUpdateRenderSectionNode(playerPos, rootNode, childNode, DhSectionPos.getChildByIndex(sectionPos, i), parentSectionIsRendering, nodesNeedingRetrieval, nodesNeedingLoading); } + // disabling rendering must be done after the children are enabled + // otherwise holes may appear in the world, overlaps are less noticeable + renderSection.setRenderingEnabled(false); + // this section is now being rendered via its children - return allChildrenSectionsAreLoaded; + return true; } } // TODO this should only equal the expected detail level, the (expectedDetailLevel-1) is a temporary fix to prevent corners from being cut out else if (DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel || DhSectionPos.getDetailLevel(sectionPos) == expectedDetailLevel - 1) { - // this is the detail level we want to render // + //======================// + // desired detail level // + //======================// - /* Can be uncommented to easily debug a single render section. */ - /* Don't forget the disableRendering() at the bottom though. */ - //if (sectionPos.getDetailLevel() == 10 - // && - // ( - // sectionPos.getX() == 0 && - // sectionPos.getZ() == -4 - // )) + // prepare this section for rendering + if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null) { - // prepare this section for rendering - if (!renderSection.gpuUploadInProgress() && renderSection.renderBuffer == null) + nodesNeedingLoading.add(renderSection); + } + + // queue world gen if needed + if (!renderSection.isFullyGenerated()) + { + nodesNeedingRetrieval.add(renderSection); + } + + // update debug if needed + if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get()) + { + this.debugRenderSections.add(renderSection); + } + + + + // wait for the parent to disable before enabling this section, so we don't have a hole + if (!parentSectionIsRendering && renderSection.canRender()) + { + // if rendering is already enabled we don't have to re-enable it + if (!renderSection.getRenderingEnabled()) { - nodesNeedingLoading.add(renderSection); - } - - if (Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus.get()) - { - this.debugRenderSections.add(renderSection); - } - - // wait for the parent to disable before enabling this section, so we don't overdraw/overlap render sections - if (!parentSectionIsRendering && renderSection.canRender()) - { - // if rendering is already enabled we don't have to re-enable it - if (!renderSection.getRenderingEnabled()) + renderSection.setRenderingEnabled(true); + + // disabling rendering must be done after the parent is enabled + // otherwise holes may appear in the world, overlaps are less noticeable + quadNode.deleteAllChildren((childRenderSection) -> { - // delete/disable children, all of them will be a lower detail level than requested - quadNode.deleteAllChildren((childRenderSection) -> + if (childRenderSection != null) { - if (childRenderSection != null) + if (childRenderSection.getRenderingEnabled()) { - if (childRenderSection.getRenderingEnabled()) - { - // show that this position's rendering has been disabled due to a parent rendering - DebugRenderer.makeParticle( - new DebugRenderer.BoxParticle( - new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA.darker()), - 0.2, 32f - ) - ); - } - - childRenderSection.setRenderingEnabled(false); - childRenderSection.close(); + // this position's rendering has been disabled due to a parent rendering + DebugRenderer.makeParticle(new DebugRenderer.BoxParticle(new DebugRenderer.Box(childRenderSection.pos, 128f, 156f, 0.09f, Color.MAGENTA),0.2, 32f)); } - }); - - renderSection.setRenderingEnabled(true); - } - } - - if (!renderSection.isFullyGenerated()) - { - nodesNeedingRetrieval.add(renderSection); + + childRenderSection.setRenderingEnabled(false); + childRenderSection.onRenderingDisabled(); + childRenderSection.close(); + } + }); + + // onRenderingEnabled() needs to be fired after the children are disabled so beacons render correctly + renderSection.onRenderingEnabled(); + } } - //else - //{ - // renderSection.disableRendering(); - //} return renderSection.canRender(); } @@ -587,12 +585,6 @@ public class LodQuadTree extends QuadTree implements IDebugRen } - //==================// - // config listeners // - //==================// - - private void onHorizontalQualityChange() { /*this.clearRenderDataCache();*/ } - //===========// // debugging // diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 367c5e4ac..d9f4dae45 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -393,37 +393,35 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable public boolean canRender() { return this.canRender; } public boolean getRenderingEnabled() { return this.renderingEnabled; } - public void setRenderingEnabled(boolean enabled) + /** + * Separate from {@link LodRenderSection#onRenderingEnabled} and {@link LodRenderSection#onRenderingDisabled} + * since we need to trigger external changes in disabled -> enabled order + * so beacons are removed and then re-added. + * However, to prevent holes in the world when disabling sections we need to + * enable the new section(s) first before disabling the old one(s). + */ + public void setRenderingEnabled(boolean enabled) { this.renderingEnabled = enabled;} + + /** @see LodRenderSection#setRenderingEnabled */ + public void onRenderingEnabled() { this.level.loadBeaconBeamsInPos(this.pos); } + /** @see LodRenderSection#setRenderingEnabled */ + public void onRenderingDisabled() { - // some logic should only be run when enabling/disabling - // a section for the first time - boolean stateChanged = (this.renderingEnabled != enabled); - if (stateChanged) - { - if (enabled) - { - this.level.loadBeaconBeamsInPos(this.pos); - } - else - { - this.level.unloadBeaconBeamsInPos(this.pos); - - if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) - { - // show that this position has just been disabled - DebugRenderer.makeParticle( - new DebugRenderer.BoxParticle( - new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), - 0.2, 32f - ) - ); - } - } - } + this.level.unloadBeaconBeamsInPos(this.pos); - this.renderingEnabled = enabled; + if (Config.Client.Advanced.Debugging.DebugWireframe.showRenderSectionStatus.get()) + { + // show that this position has just been disabled + DebugRenderer.makeParticle( + new DebugRenderer.BoxParticle( + new DebugRenderer.Box(this.pos, 128f, 156f, 0.09f, Color.CYAN.darker()), + 0.2, 32f + ) + ); + } } + public boolean gpuUploadInProgress() { return this.buildAndUploadRenderDataToGpuFuture != null; }