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 a8ef0a72e..0a81cc761 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 @@ -143,7 +143,7 @@ public class LodQuadTree extends QuadTree implements AutoClose { // walk up the tree until we hit the root node // this is done so any high detail changes flow up to the lower detail render sections as well - while (pos.sectionDetailLevel <= this.treeMaxDetailLevel) + while (pos.sectionDetailLevel <= this.treeMinDetailLevel) { try { @@ -368,7 +368,7 @@ public class LodQuadTree extends QuadTree implements AutoClose // If not done corners may not be flush with the other LODs, which looks bad. byte minSectionDetailLevel = this.getDetailLevelFromDistance(this.blockRenderDistanceRadius); // get the minimum allowed detail level minSectionDetailLevel -= 1; // -1 so corners can't render lower than their adjacent neighbors. space - minSectionDetailLevel = (byte) Math.min(minSectionDetailLevel, this.treeMaxDetailLevel); // don't allow rendering lower detail sections than what the tree contains + minSectionDetailLevel = (byte) Math.min(minSectionDetailLevel, this.treeMinDetailLevel); // don't allow rendering lower detail sections than what the tree contains this.minRenderDetailLevel = (byte) Math.max(minSectionDetailLevel, this.maxRenderDetailLevel); // respect the user's selected max resolution if it is lower detail (IE they want 2x2 block, but minSectionDetailLevel is specifically for 1x1 block render resolution) } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/quadTree/QuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/quadTree/QuadTree.java index 82437dcc7..2014e4039 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/objects/quadTree/QuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/objects/quadTree/QuadTree.java @@ -45,18 +45,24 @@ public class QuadTree - /** The largest number detail level in this tree. */ - public final byte treeMaxDetailLevel; - /** The smallest number detail level in this tree. */ + /** + * The largest numerical detail level this tree supports.
+ * IE: the detail level used by the root nodes. + */ public final byte treeMinDetailLevel; + /** + * The smallest numerical detail level this tree supports.
+ * IE: the detail level used by the leaf nodes. + */ + public final byte treeMaxDetailLevel; + + private final int widthInBlocks; // diameterInBlocks /** contain the actual data in the quad tree structure */ private final MovableGridRingList> topRingList; private DhBlockPos2D centerBlockPos; - private int widthInBlocks; - /** @@ -64,21 +70,21 @@ public class QuadTree * * @param widthInBlocks equivalent to the distance between two opposing sides */ - public QuadTree(int widthInBlocks, DhBlockPos2D centerBlockPos, byte treeMinDetailLevel) + public QuadTree(int widthInBlocks, DhBlockPos2D centerBlockPos, byte treeMaxDetailLevel) { this.centerBlockPos = centerBlockPos; this.widthInBlocks = widthInBlocks; - this.treeMinDetailLevel = treeMinDetailLevel; - // the max detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the minimum detail level - this.treeMaxDetailLevel = (byte) Math.max(Math.max(1, this.treeMinDetailLevel), MathUtil.log2(widthInBlocks)); + this.treeMaxDetailLevel = treeMaxDetailLevel; + // the min detail level must be greater than 0 (to prevent divide by 0 errors) and greater than the maximum detail level + this.treeMinDetailLevel = (byte) Math.max(Math.max(1, this.treeMaxDetailLevel), MathUtil.log2(widthInBlocks)); - int halfSizeInRootNodes = Math.floorDiv(this.widthInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMaxDetailLevel); + int halfSizeInRootNodes = Math.floorDiv(this.widthInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMinDetailLevel); halfSizeInRootNodes = halfSizeInRootNodes + 1; // always add 1 so nodes will always have a parent, even if the tree's center is offset from the root node grid Pos2D ringListCenterPos = new Pos2D( - BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMaxDetailLevel), - BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMaxDetailLevel)); + BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel), + BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel)); this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.x, ringListCenterPos.y); } @@ -115,7 +121,7 @@ public class QuadTree { if (!runBoundaryChecks || this.isSectionPosInBounds(pos)) { - DhSectionPos rootPos = pos.convertToDetailLevel(this.treeMaxDetailLevel); + DhSectionPos rootPos = pos.convertToDetailLevel(this.treeMinDetailLevel); int ringListPosX = rootPos.sectionX; int ringListPosZ = rootPos.sectionZ; @@ -127,7 +133,7 @@ public class QuadTree return null; } - topQuadNode = new QuadNode(rootPos, this.treeMinDetailLevel); + topQuadNode = new QuadNode(rootPos, this.treeMaxDetailLevel); boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode); LodUtil.assertTrue(successfullyAdded, "Failed to add top quadTree node at position: " + rootPos); } @@ -150,14 +156,14 @@ public class QuadTree int radius = this.diameterInBlocks() / 2; DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-radius, -radius)); DhBlockPos2D maxPos = this.getCenterBlockPos().add(new DhBlockPos2D(radius, radius)); - throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeMinDetailLevel + ", max detail level: " + this.treeMaxDetailLevel + ". Given Position: " + pos + " = block pos: " + pos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL)); + throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeMaxDetailLevel + ", max detail level: " + this.treeMinDetailLevel + ". Given Position: " + pos + " = block pos: " + pos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL)); } } public boolean isSectionPosInBounds(DhSectionPos testPos) { // check if the testPos is within the detail level limits of the tree - boolean detailLevelWithinBounds = this.treeMinDetailLevel <= testPos.sectionDetailLevel && testPos.sectionDetailLevel <= this.treeMaxDetailLevel; + boolean detailLevelWithinBounds = this.treeMaxDetailLevel <= testPos.sectionDetailLevel && testPos.sectionDetailLevel <= this.treeMinDetailLevel; if (!detailLevelWithinBounds) { return false; @@ -240,8 +246,8 @@ public class QuadTree this.centerBlockPos = newCenterPos; Pos2D expectedCenterPos = new Pos2D( - BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMaxDetailLevel), - BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMaxDetailLevel)); + BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel), + BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel)); if (this.topRingList.getCenter().equals(expectedCenterPos)) { @@ -378,7 +384,7 @@ public class QuadTree // } @Override - public String toString() { return "center block: " + this.centerBlockPos + ", block width: " + this.widthInBlocks + ", detail level range: [" + this.treeMinDetailLevel + "-" + this.treeMaxDetailLevel + "], leaf #: " + this.leafNodeCount(); } + public String toString() { return "center block: " + this.centerBlockPos + ", block width: " + this.widthInBlocks + ", detail level range: [" + this.treeMaxDetailLevel + "-" + this.treeMinDetailLevel + "], leaf #: " + this.leafNodeCount(); } @@ -398,7 +404,7 @@ public class QuadTree { if (node != null || includeNullNodes) { - DhSectionPos rootPos = new DhSectionPos(QuadTree.this.treeMaxDetailLevel, pos2D.x, pos2D.y); + DhSectionPos rootPos = new DhSectionPos(QuadTree.this.treeMinDetailLevel, pos2D.x, pos2D.y); if (QuadTree.this.isSectionPosInBounds(rootPos)) { this.iteratorPosQueue.add(rootPos); diff --git a/core/src/test/java/tests/QuadTreeTest.java b/core/src/test/java/tests/QuadTreeTest.java index 3e64be258..ea79698af 100644 --- a/core/src/test/java/tests/QuadTreeTest.java +++ b/core/src/test/java/tests/QuadTreeTest.java @@ -55,7 +55,7 @@ public class QuadTreeTest { AbstractTestTreeParams treeParams = new LargeTestTree(); QuadTree tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL); - Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeMaxDetailLevel >= 10 && tree.treeMinDetailLevel <= 10 - 4); + Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeMinDetailLevel >= 10 && tree.treeMaxDetailLevel <= 10 - 4); // (pseudo) root node // @@ -170,7 +170,7 @@ public class QuadTreeTest { // very specific tree parameters to match test results QuadTree tree = new QuadTree<>(512, new DhBlockPos2D(125, -516), (byte) 6); - Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeMaxDetailLevel); + Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeMinDetailLevel); DhSectionPos rootPos = new DhSectionPos((byte) 9, 0, -1); @@ -411,7 +411,7 @@ public class QuadTreeTest { AbstractTestTreeParams treeParams = new TinyTestTree(); final QuadTree tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, 0), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0); // confirm the root node were added int rootNodeCount = 0; @@ -499,10 +499,10 @@ public class QuadTreeTest Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks()); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, 0), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class); int rootNodeCount = 0; Iterator rootNodeIterator = tree.rootNodePosIterator(); @@ -529,18 +529,18 @@ public class QuadTreeTest // 2x2 valid positions (overlap the tree's width) - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, 0), 0); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, 0), 0); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, -1), 0); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, -1), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 0), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, 0), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, -1), 0); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, -1), 0); // invalid positions - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class); - testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class); + testSet(tree, new DhSectionPos(tree.treeMinDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class); int rootNodeCount = 0; @@ -562,7 +562,7 @@ public class QuadTreeTest { AbstractTestTreeParams treeParams = new MediumTestTree(); QuadTree tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 8); - Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMaxDetailLevel); + Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMinDetailLevel); // valid detail levels testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1); @@ -584,14 +584,14 @@ public class QuadTreeTest { AbstractTestTreeParams treeParams = new MediumTestTree(); QuadTree tree = new QuadTree<>(treeParams.getWidthInBlocks(), new DhBlockPos2D(0, 0), (byte) 6); - Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMaxDetailLevel); + Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeMinDetailLevel); // create the root node testSet(tree, new DhSectionPos((byte) 10, 0, 0), 1); - AtomicInteger minimumDetailLevelReachedRef = new AtomicInteger(tree.treeMaxDetailLevel); + AtomicInteger minimumDetailLevelReachedRef = new AtomicInteger(tree.treeMinDetailLevel); // recurse down the tree Iterator rootNodePosIterator = tree.rootNodePosIterator(); @@ -618,13 +618,13 @@ public class QuadTreeTest QuadNode childNode = ChildIterator.next(); Assert.assertNotNull(childNode); // TODO is this correct? - recursivelyCreateNodeChildren(childNode, tree.treeMinDetailLevel, minimumDetailLevelReachedRef); + recursivelyCreateNodeChildren(childNode, tree.treeMaxDetailLevel, minimumDetailLevelReachedRef); } } } // confirm that the tree can and did iterate all the way down to the minimum detail level - Assert.assertEquals("Incorrect minimum detail level reached.", tree.treeMinDetailLevel, minimumDetailLevelReachedRef.get()); + Assert.assertEquals("Incorrect minimum detail level reached.", tree.treeMaxDetailLevel, minimumDetailLevelReachedRef.get()); } private void recursivelyCreateNodeChildren(QuadNode node, byte minDetailLevel, AtomicInteger minimumDetailLevelReachedRef) {