Fix LODs flashing when moving and improve LodQuadTree code

This commit is contained in:
James Seibel
2024-09-01 21:10:22 -05:00
parent b00e8a08e9
commit 18e075538d
2 changed files with 102 additions and 112 deletions
@@ -250,11 +250,12 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
ArrayList<LodRenderSection> nodesNeedingRetrieval,
ArrayList<LodRenderSection> 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<LodRenderSection> 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<LodRenderSection> 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<LodRenderSection> childNode = quadNode.getChildByIndex(i);
@@ -304,6 +304,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> 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<LodRenderSection> 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<LodRenderSection> 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<LodRenderSection> implements IDebugRen
}
//==================//
// config listeners //
//==================//
private void onHorizontalQualityChange() { /*this.clearRenderDataCache();*/ }
//===========//
// debugging //
@@ -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; }