diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java b/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java
index 693420169..18722da06 100644
--- a/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java
+++ b/core/src/main/java/com/seibel/lod/core/pos/DhSectionPos.java
@@ -83,12 +83,22 @@ public class DhSectionPos
LodUtil.assertTrue(returnDetailLevel <= this.sectionDetailLevel, "returnDetailLevel must be less than sectionDetail");
if (returnDetailLevel == this.sectionDetailLevel)
+ {
return new DhLodPos(this.sectionDetailLevel, this.sectionX, this.sectionZ);
+ }
+
+ byte detailLevelOffset = (byte) (this.sectionDetailLevel - returnDetailLevel);
+
+ // we can't get the center of the position at block level, only attempt to get the position offset for detail levels above 0 // TODO should this also apply to detail level 1 or is it fine?
+ int positionOffset = 0;
+ if (this.sectionDetailLevel != 1 || returnDetailLevel != 0)
+ {
+ positionOffset = BitShiftUtil.powerOfTwo(detailLevelOffset - 1);
+ }
- byte offset = (byte) (this.sectionDetailLevel - returnDetailLevel);
return new DhLodPos(returnDetailLevel,
- (this.sectionX * BitShiftUtil.powerOfTwo(offset)) + BitShiftUtil.powerOfTwo(offset - 1),
- (this.sectionZ * BitShiftUtil.powerOfTwo(offset)) + BitShiftUtil.powerOfTwo(offset - 1));
+ (this.sectionX * BitShiftUtil.powerOfTwo(detailLevelOffset)) + positionOffset,
+ (this.sectionZ * BitShiftUtil.powerOfTwo(detailLevelOffset)) + positionOffset);
}
/** @return the corner with the smallest X and Z coordinate */
@@ -111,7 +121,10 @@ public class DhSectionPos
return new DhLodUnit(this.sectionDetailLevel, BitShiftUtil.powerOfTwo(offset));
}
- /** uses the absolute detail level aka detail levels like {@link LodUtil#CHUNK_DETAIL_LEVEL} instead of the dhSectionPos detaillevels */ // TODO comment
+ /**
+ * uses the absolute detail level aka detail levels like {@link LodUtil#CHUNK_DETAIL_LEVEL} instead of the dhSectionPos detailLevels
+ * @return the new position closest to negative infinity with the new detail level
+ */
public DhSectionPos convertToDetailLevel(byte newSectionDetailLevel)
{
DhLodPos lodPos = new DhLodPos(this.sectionDetailLevel, this.sectionX, this.sectionZ);
@@ -125,10 +138,10 @@ public class DhSectionPos
* Returns the DhLodPos 1 detail level lower
*
* Relative child positions returned for each index:
- * 0 = (0,0)
- * 1 = (1,0)
- * 2 = (0,1)
- * 3 = (1,1)
+ * 0 = (0,0) - North West
+ * 1 = (1,0) - South West
+ * 2 = (0,1) - North East
+ * 3 = (1,1) - South East
*
* @param child0to3 must be an int between 0 and 3
*/
@@ -172,10 +185,11 @@ public class DhSectionPos
/** NOTE: This does not consider yOffset! */
public boolean contains(DhSectionPos otherPos)
{
+ DhBlockPos2D thisMinBlockPos = this.getCorner(LodUtil.BLOCK_DETAIL_LEVEL).getCornerBlockPos();
DhBlockPos2D otherCornerBlockPos = otherPos.getCorner(LodUtil.BLOCK_DETAIL_LEVEL).getCornerBlockPos();
- DhBlockPos2D thisMinBlockPos = this.getCorner(LodUtil.BLOCK_DETAIL_LEVEL).getCornerBlockPos();
- DhBlockPos2D thisMaxBlockPos = new DhBlockPos2D(thisMinBlockPos.x + this.getWidth().toBlockWidth(), thisMinBlockPos.z + this.getWidth().toBlockWidth());
+ int thisBlockWidth = this.getWidth().toBlockWidth() - 1; // minus 1 to account for zero based positional indexing
+ DhBlockPos2D thisMaxBlockPos = new DhBlockPos2D(thisMinBlockPos.x + thisBlockWidth, thisMinBlockPos.z + thisBlockWidth);
return thisMinBlockPos.x <= otherCornerBlockPos.x && otherCornerBlockPos.x <= thisMaxBlockPos.x &&
thisMinBlockPos.z <= otherCornerBlockPos.z && otherCornerBlockPos.z <= thisMaxBlockPos.z;
diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java
index f2478876a..2b5b1dbc5 100644
--- a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java
+++ b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadNode.java
@@ -1,8 +1,8 @@
package com.seibel.lod.core.util.objects.quadTree;
import com.seibel.lod.core.logging.DhLoggerBuilder;
-import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
+import com.seibel.lod.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
import java.util.function.Consumer;
@@ -119,7 +119,7 @@ public class QuadNode
if (!this.sectionPos.contains(inputSectionPos))
{
LOGGER.error((replaceValue ? "set " : "get ")+inputSectionPos+" center block: "+inputSectionPos.getCenter().getCornerBlockPos()+", this pos: "+this.sectionPos+" this center block: "+this.sectionPos.getCenter().getCornerBlockPos());
- throw new IllegalArgumentException("Input section pos outside of this quadNode's range: "+this.sectionPos+" width: "+this.sectionPos.getWidth()+" input detail level: "+inputSectionPos+" width: "+inputSectionPos.getWidth());
+ throw new IllegalArgumentException("Input section pos "+inputSectionPos+" outside of this quadNode's pos: "+this.sectionPos+", this node's blockPos: "+this.sectionPos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL)+" block width: "+this.sectionPos.getWidth().toBlockWidth()+" input detail level: "+inputSectionPos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL)+" width: "+inputSectionPos.getWidth().toBlockWidth());
}
if (inputSectionPos.sectionDetailLevel > this.sectionPos.sectionDetailLevel)
@@ -149,65 +149,68 @@ public class QuadNode
// LOGGER.info((replaceValue ? "set " : "get ")+inputSectionPos+" center block: "+inputSectionPos.getCenter().getCornerBlockPos()+", this pos: "+this.sectionPos+" this center block: "+this.sectionPos.getCenter().getCornerBlockPos());
- DhLodPos nodeCenterPos = this.sectionPos.getCenter(); //.convertToDetailLevel((byte)0).getCenter();
- DhLodPos inputCenterPos = inputSectionPos.getCenter(); //.convertToDetailLevel((byte)0).getCenter();
+ DhSectionPos nwPos = this.sectionPos.getChildByIndex(0);
+ DhSectionPos swPos = this.sectionPos.getChildByIndex(1);
+ DhSectionPos nePos = this.sectionPos.getChildByIndex(2);
+ DhSectionPos sePos = this.sectionPos.getChildByIndex(3);
- // may or may not be at the requested detail level
+ // look for the child that contains the input position (there may be a faster way to do this, but this works for now)
QuadNode childNode;
- if (inputCenterPos.x <= nodeCenterPos.x)
+ if (nwPos.contains(inputSectionPos))
{
- if (inputCenterPos.z <= nodeCenterPos.z)
+ // TODO merge duplicate code
+ if (replaceValue && this.nwChild == null)
{
- // TODO merge duplicate code
- if (replaceValue && this.nwChild == null)
- {
- // if no node exists for this position, but we want to insert a new value at this position, create a new node
- this.nwChild = new QuadNode<>(this.sectionPos.getChildByIndex(0));
- }
-// LOGGER.info("NW");
- childNode = this.nwChild;
+ // if no node exists for this position, but we want to insert a new value at this position, create a new node
+ this.nwChild = new QuadNode<>(nwPos);
}
- else
+ childNode = this.nwChild;
+
+ // childNode should only be null when replaceValue = false and the end of a node chain has been reached
+ return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
+ }
+ else if (swPos.contains(inputSectionPos))
+ {
+ // TODO merge duplicate code
+ if (replaceValue && this.swChild == null)
{
- if (replaceValue && this.neChild == null)
- {
- this.neChild = new QuadNode<>(this.sectionPos.getChildByIndex(2));
- }
-// LOGGER.info("NE");
- childNode = this.neChild;
+ // if no node exists for this position, but we want to insert a new value at this position, create a new node
+ this.swChild = new QuadNode<>(swPos);
}
+ childNode = this.swChild;
+
+ // childNode should only be null when replaceValue = false and the end of a node chain has been reached
+ return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
+ }
+ else if (nePos.contains(inputSectionPos))
+ {
+ // TODO merge duplicate code
+ if (replaceValue && this.neChild == null)
+ {
+ // if no node exists for this position, but we want to insert a new value at this position, create a new node
+ this.neChild = new QuadNode<>(nePos);
+ }
+ childNode = this.neChild;
+
+ // childNode should only be null when replaceValue = false and the end of a node chain has been reached
+ return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
+ }
+ else if (sePos.contains(inputSectionPos))
+ {
+ // TODO merge duplicate code
+ if (replaceValue && this.seChild == null)
+ {
+ // if no node exists for this position, but we want to insert a new value at this position, create a new node
+ this.seChild = new QuadNode<>(sePos);
+ }
+ childNode = this.seChild;
+
+ // childNode should only be null when replaceValue = false and the end of a node chain has been reached
+ return (childNode != null) ? childNode.getOrSetValue(inputSectionPos, replaceValue, newValue) : null;
}
else
{
- if (inputCenterPos.z <= nodeCenterPos.z)
- {
- if (replaceValue && this.swChild == null)
- {
- this.swChild = new QuadNode<>(this.sectionPos.getChildByIndex(1));
- }
-// LOGGER.info("SW");
- childNode = this.swChild;
- }
- else
- {
- if (replaceValue && this.seChild == null)
- {
- this.seChild = new QuadNode<>(this.sectionPos.getChildByIndex(3));
- }
-// LOGGER.info("SE");
- childNode = this.seChild;
- }
- }
-
-
- if (childNode == null)
- {
- // should only happen when replaceValue = false and the end of a node chain has been reached
- return null;
- }
- else
- {
- return childNode.getOrSetValue(inputSectionPos, replaceValue, newValue);
+ throw new IllegalStateException("input position not contained by any node children. This should've been caught by the this.sectionPos.contains(inputPos) assert before this point.");
}
}
}
diff --git a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java
index 5eb2c3d4d..0998e86e2 100644
--- a/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java
+++ b/core/src/main/java/com/seibel/lod/core/util/objects/quadTree/QuadTree.java
@@ -2,6 +2,7 @@ package com.seibel.lod.core.util.objects.quadTree;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos2D;
+import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.pos.Pos2D;
import com.seibel.lod.core.util.BitShiftUtil;
@@ -11,6 +12,7 @@ import com.seibel.lod.core.util.gridList.MovableGridRingList;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BiConsumer;
import java.util.function.Consumer;
/**
@@ -30,7 +32,9 @@ public class QuadTree
/** contain the actual data in the quad tree structure */
private final MovableGridRingList> topRingList;
- DhBlockPos2D centerBlockPos;
+ private DhBlockPos2D centerBlockPos;
+
+ private int widthInBlocks;
@@ -42,20 +46,19 @@ public class QuadTree
{
DetailDistanceUtil.updateSettings(); //TODO: Move this to somewhere else
this.centerBlockPos = centerBlockPos;
+ this.widthInBlocks = widthInBlocks;
- this.treeMaxDetailLevel = 10; // TODO we may need to make this dynamic // detail 10 = (2^10) 1024 blocks wide
+ this.treeMaxDetailLevel = 10; // TODO in the future we may need to make this dynamic // detail 10 = (2^10) 1024 blocks wide
-// int halfSize = 12; // TODO use this.treeMaxDetailLevel to determine
- int halfSize = Math.floorDiv(widthInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMaxDetailLevel);
- halfSize = Math.max(halfSize, 1); // at minimum the ring list should have 3x3 (9) root nodes in it, to account for moving around
+ int halfSizeInRootNodes = Math.floorDiv(this.widthInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMaxDetailLevel);
+ 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));
-
- this.topRingList = new MovableGridRingList<>(halfSize, ringListCenterPos.x, ringListCenterPos.y);
+ this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.x, ringListCenterPos.y);
- }// constructor
+ }
@@ -70,7 +73,7 @@ public class QuadTree
protected final T getOrSet(DhSectionPos pos, boolean setNewValue, T newValue) throws IndexOutOfBoundsException
{
- if (this.isPositionInBounds(pos))
+ if (this.isSectionPosInBounds(pos))
{
DhSectionPos rootPos = pos.convertToDetailLevel(this.treeMaxDetailLevel);
int ringListPosX = rootPos.sectionX;
@@ -79,6 +82,11 @@ public class QuadTree
QuadNode topQuadNode = this.topRingList.get(ringListPosX, ringListPosZ);
if (topQuadNode == null)
{
+ if (!setNewValue)
+ {
+ return null;
+ }
+
topQuadNode = new QuadNode(rootPos);
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
LodUtil.assertTrue(successfullyAdded, "Failed to add top quadTree node at position: "+rootPos);
@@ -99,34 +107,44 @@ public class QuadTree
}
else
{
- // TODO give the min and max allowed positions
- int width = this.widthInBlocks()/2;
-
- DhBlockPos2D minPos = this.getCenterBlockPos().add(new DhBlockPos2D(-width, -width));
- DhBlockPos2D maxPos =this.getCenterBlockPos().add(new DhBlockPos2D(width, width));
- throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: "+minPos+", max pos: "+maxPos+", given Position: "+pos);
+ 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+", given Position: "+pos+" = block pos: "+pos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL));
}
}
-
- private boolean isPositionInBounds(DhSectionPos pos)
+ private boolean isSectionPosInBounds(DhSectionPos testPos)
{
- DhSectionPos blockPos = pos.convertToDetailLevel(LodUtil.BLOCK_DETAIL_LEVEL);
+ DhBlockPos2D blockCornerOfTree = this.centerBlockPos.add(new DhBlockPos2D(-this.widthInBlocks/2,-this.widthInBlocks/2));
+ DhLodPos cornerOfTreePos = new DhLodPos((byte)0, blockCornerOfTree.x, blockCornerOfTree.z);
- int halfWidthInBlocks = BitShiftUtil.powerOfTwo(this.treeMaxDetailLevel) * Math.floorDiv(this.topRingList.getWidth(), 2);
+ DhSectionPos sectionCornerOfInput = testPos.convertToDetailLevel((byte)0);
+ DhLodPos cornerOfInputPos = new DhLodPos((byte)0, sectionCornerOfInput.sectionX, sectionCornerOfInput.sectionZ);
+ int inputWidth = BitShiftUtil.powerOfTwo(testPos.sectionDetailLevel);
- int minX = this.centerBlockPos.x - halfWidthInBlocks;
- int maxX = this.centerBlockPos.x + halfWidthInBlocks;
+ return DoSquaresOverlap(cornerOfTreePos, this.widthInBlocks, cornerOfInputPos, inputWidth);
+ }
+ public static boolean DoSquaresOverlap(DhLodPos rect1Min, int rect1Width, DhLodPos rect2Min, int rect2Width)
+ {
+ // Determine the coordinates of the rectangles
+ float rect1MinX = rect1Min.x;
+ float rect1MaxX = rect1Min.x + rect1Width;
+ float rect1MinZ = rect1Min.z;
+ float rect1MaxZ = rect1Min.z + rect1Width;
- int minZ = this.centerBlockPos.z - halfWidthInBlocks;
- int maxZ = this.centerBlockPos.z + halfWidthInBlocks;
+ float rect2MinX = rect2Min.x;
+ float rect2MaxX = rect2Min.x + rect2Width;
+ float rect2MinZ = rect2Min.z;
+ float rect2MaxZ = rect2Min.z + rect2Width;
- return minX <= blockPos.sectionX && blockPos.sectionX <= maxX &&
- minZ <= blockPos.sectionZ && blockPos.sectionZ <= maxZ;
+ // Check if the rectangles overlap
+ return rect1MinX < rect2MaxX && rect1MaxX > rect2MinX && rect1MinZ < rect2MaxZ && rect1MaxZ > rect2MinZ;
}
- /** no nulls TODO */
+
+ /** no nulls TODO comment/rename */
public void forEachRootNode(Consumer> consumer)
{
this.topRingList.forEachOrdered((rootNode) ->
@@ -138,6 +156,18 @@ public class QuadTree
});
}
+ /** root nodes can be null */
+ public void forEachRootNodePos(BiConsumer, Pos2D> consumer)
+ {
+ this.topRingList.forEachPosOrdered((rootNode, pos2D) ->
+ {
+ if (isSectionPosInBounds(new DhSectionPos(this.treeMaxDetailLevel, pos2D.x, pos2D.y)))
+ {
+ consumer.accept(rootNode, pos2D);
+ }
+ });
+ }
+
public void forEachLeafValue(Consumer super T> consumer)
{
this.forEachRootNode((rootNode) ->
@@ -189,19 +219,16 @@ public class QuadTree
public int leafNodeCount()
{
AtomicInteger count = new AtomicInteger(0);
- this.topRingList.forEachPos((node, pos) ->
+ this.topRingList.forEachOrdered((node) ->
{
- if (node != null)
- {
- node.forAllLeafValues((value) -> { count.addAndGet(1); });
- }
+ node.forAllLeafValues((value) -> { count.addAndGet(1); });
});
return count.get();
}
public int ringListWidth() { return this.topRingList.getWidth(); }
- public int widthInBlocks() { return this.ringListWidth() * BitShiftUtil.powerOfTwo(this.treeMaxDetailLevel); }
+ public int diameterInBlocks() { return this.widthInBlocks; }
// public String getDebugString()
// {
diff --git a/core/src/test/java/tests/DhSectionPosTest.java b/core/src/test/java/tests/DhSectionPosTest.java
index 755abe118..a25368b10 100644
--- a/core/src/test/java/tests/DhSectionPosTest.java
+++ b/core/src/test/java/tests/DhSectionPosTest.java
@@ -21,6 +21,7 @@ package tests;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhBlockPos2D;
+import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.DhSectionPos;
import com.seibel.lod.core.util.BitShiftUtil;
import com.seibel.lod.core.util.LodUtil;
@@ -36,7 +37,7 @@ import java.util.concurrent.atomic.AtomicInteger;
public class DhSectionPosTest
{
@Test
- public void SectionPosTest()
+ public void ContainsPosTest()
{
DhSectionPos root = new DhSectionPos((byte)10, 0, 0);
DhSectionPos child = new DhSectionPos((byte)9, 1, 1);
@@ -46,10 +47,125 @@ public class DhSectionPosTest
root = new DhSectionPos((byte)10, 1, 0);
+
+ // out of bounds
+ child = new DhSectionPos((byte)9, 0, 0);
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
child = new DhSectionPos((byte)9, 1, 1);
- Assert.assertFalse("section pos contains fail", root.contains(child));
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
+
+ // in bounds
+ child = new DhSectionPos((byte)9, 2, 0);
+ Assert.assertTrue("position should be in bounds", root.contains(child));
+ child = new DhSectionPos((byte)9, 3, 1);
+ Assert.assertTrue("position should be in bounds", root.contains(child));
+
+ // out of bounds
child = new DhSectionPos((byte)9, 2, 2);
- Assert.assertTrue("section pos contains fail", root.contains(child));
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
+ child = new DhSectionPos((byte)9, 3, 3);
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
+
+ child = new DhSectionPos((byte)9, 4, 4);
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
+ child = new DhSectionPos((byte)9, 5, 5);
+ Assert.assertFalse("position should be out of bounds", root.contains(child));
+ }
+
+ @Test
+ public void ContainsAdjacentPosTest()
+ {
+ // neither should contain the other, they are single blocks that are next to each other
+ DhSectionPos left = new DhSectionPos((byte)0, 4606, 0);
+ DhSectionPos right = new DhSectionPos((byte)0, 4607, 0);
+ Assert.assertFalse(left.contains(right));
+ Assert.assertFalse(right.contains(left));
+
+
+ // 512 block wide sections that are adjacent, but not overlapping
+ left = new DhSectionPos((byte)9, 0, 0);
+ right = new DhSectionPos((byte)9, 1, 0);
+ Assert.assertFalse(left.contains(right));
+ Assert.assertFalse(right.contains(left));
+
+ }
+
+ @Test
+ public void ParentPosTest()
+ {
+ DhSectionPos leaf = new DhSectionPos((byte)0, 0, 0);
+ DhSectionPos convert = leaf.convertToDetailLevel((byte)1);
+ DhSectionPos parent = leaf.getParentPos();
+ Assert.assertEquals("get parent at 0,0 fail", convert, parent);
+
+
+ leaf = new DhSectionPos((byte)0, 1, 1);
+ convert = leaf.convertToDetailLevel((byte)1);
+ parent = leaf.getParentPos();
+ Assert.assertEquals("get parent at 1,1 fail", convert, parent);
+
+
+ leaf = new DhSectionPos((byte)1, 2, 2);
+ convert = leaf.convertToDetailLevel((byte)2);
+ parent = leaf.getParentPos();
+ Assert.assertEquals("parent upscale fail", convert, parent);
+ convert = leaf.convertToDetailLevel((byte)0);
+ DhSectionPos childIndex = leaf.getChildByIndex(0);
+ Assert.assertEquals("child detail fail", convert, childIndex);
+
+ }
+
+ @Test
+ public void ChildPosTest()
+ {
+ DhSectionPos node = new DhSectionPos((byte)1, 2302, 0);
+ DhSectionPos nw = node.getChildByIndex(0);
+ DhSectionPos sw = node.getChildByIndex(1);
+ DhSectionPos ne = node.getChildByIndex(2);
+ DhSectionPos se = node.getChildByIndex(3);
+
+ // confirm no children have the same values
+ Assert.assertNotEquals(nw, sw);
+ Assert.assertNotEquals(sw, ne);
+ Assert.assertNotEquals(ne, se);
+
+ // confirm each child has the correct value
+ Assert.assertEquals(nw, new DhSectionPos((byte)0, 4604, 0));
+ Assert.assertEquals(sw, new DhSectionPos((byte)0, 4605, 0));
+ Assert.assertEquals(ne, new DhSectionPos((byte)0, 4604, 1));
+ Assert.assertEquals(se, new DhSectionPos((byte)0, 4605, 1));
+
+ }
+
+ @Test
+ public void GetCenterTest()
+ {
+ DhSectionPos node = new DhSectionPos((byte)1, 2303, 0);
+ DhLodPos centerNode = node.getCenter();
+ DhLodPos expectedCenterNode = new DhLodPos((byte)0, 4606,0);
+ Assert.assertEquals("", expectedCenterNode, centerNode);
+
+
+
+ node = new DhSectionPos((byte)10, 0, 0); // 1024 blocks wide
+ centerNode = node.getCenter();
+ expectedCenterNode = new DhLodPos((byte)0, 1024/2,1024/2);
+ Assert.assertEquals("", expectedCenterNode, centerNode);
+
+ }
+
+ @Test
+ public void GetCenter2Test()
+ {
+ DhSectionPos parentNode = new DhSectionPos((byte)2, 1151, 0); // width 4 blocks
+ DhSectionPos inputPos = new DhSectionPos((byte)0, 4606, 0); // width 1 block
+ Assert.assertTrue(parentNode.contains(inputPos));
+
+ DhLodPos parentCenter = parentNode.getCenter();
+ DhLodPos inputCenter = inputPos.getCenter();
+
+ Assert.assertEquals(new DhLodPos((byte)0, 4606, 2), parentCenter);
+ Assert.assertEquals(new DhLodPos((byte)0, 4606, 0), inputCenter);
}
diff --git a/core/src/test/java/tests/QuadTreeTest.java b/core/src/test/java/tests/QuadTreeTest.java
index 7c4b4d1b2..735eb5d4e 100644
--- a/core/src/test/java/tests/QuadTreeTest.java
+++ b/core/src/test/java/tests/QuadTreeTest.java
@@ -37,10 +37,17 @@ public class QuadTreeTest
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
+ private static final DhBlockPos2D TREE_CENTER_POS = new DhBlockPos2D(BitShiftUtil.powerOfTwo(10)/2, BitShiftUtil.powerOfTwo(10)/2);
+
private static final int ROOT_NODE_WIDTH_IN_BLOCKS = BitShiftUtil.powerOfTwo(10);
/** needs to be an odd number to function correctly */
- private static final int BASIC_TREE_WIDTH_IN_ROOT_NODES = 9;
- private static final int BASIC_TREE_WIDTH_IN_BLOCKS = ROOT_NODE_WIDTH_IN_BLOCKS * BASIC_TREE_WIDTH_IN_ROOT_NODES;
+ private static final int BASIC_TREE_INPUT_WIDTH_IN_ROOT_NODES = 9;
+ private static final int BASIC_TREE_WIDTH_IN_BLOCKS = ROOT_NODE_WIDTH_IN_BLOCKS * BASIC_TREE_INPUT_WIDTH_IN_ROOT_NODES;
+
+ /** the tree should be slightly larger to account for offset centers */
+ private static final int BASIC_TREE_ACTUAL_WIDTH_IN_ROOT_NODES = BASIC_TREE_INPUT_WIDTH_IN_ROOT_NODES + 2;
+
+ private static final int MINIMUM_TREE_WIDTH_IN_BLOCKS = 32;
static
{
@@ -52,8 +59,8 @@ public class QuadTreeTest
@Test
public void BasicPositiveQuadTreeTest()
{
- QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0, 0));
- Assert.assertEquals("Incorrect basic tree width", BASIC_TREE_WIDTH_IN_ROOT_NODES, tree.ringListWidth());
+ QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
+ Assert.assertEquals("Incorrect basic tree width", BASIC_TREE_ACTUAL_WIDTH_IN_ROOT_NODES, tree.ringListWidth());
// root node //
@@ -87,7 +94,7 @@ public class QuadTreeTest
@Test
public void BasicNegativeQuadTreeTest()
{
- QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0, 0));
+ QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
// root node //
@@ -122,21 +129,52 @@ public class QuadTreeTest
@Test
public void OutOfBoundsQuadTreeTest()
{
- QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0, 0));
+ QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0,0));
+ Assert.assertEquals("tree diameter incorrect", BASIC_TREE_WIDTH_IN_BLOCKS, tree.diameterInBlocks());
+
// wrong detail level on purpose, if the detail level was 0 (block) this should work
DhSectionPos outOfBoundsPos = new DhSectionPos(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, ROOT_NODE_WIDTH_IN_BLOCKS, 0);
- testSet(tree, outOfBoundsPos, 2, IndexOutOfBoundsException.class);
+ testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
+
+ // out of bounds //
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2) + 1, 0);
+ testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
+ Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
+
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2), 0);
+ testSet(tree, outOfBoundsPos, -1, IndexOutOfBoundsException.class);
+ Assert.assertEquals("incorrect leaf node count", 0, tree.leafNodeCount());
+
+
+ // in bounds //
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2)-1, 0);
+ testSet(tree, outOfBoundsPos, 0);
+ Assert.assertEquals("incorrect leaf node count", 1, tree.leafNodeCount());
+
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2)-3, 0);
+ testSet(tree, outOfBoundsPos, 0);
+ Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
+
+ // TODO this position probably has trouble with getting the center.
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2)-2, 0);
+ testSet(tree, outOfBoundsPos, 0);
+ Assert.assertEquals("incorrect leaf node count", 3, tree.leafNodeCount());
+
+ outOfBoundsPos = new DhSectionPos(LodUtil.BLOCK_DETAIL_LEVEL, (BASIC_TREE_WIDTH_IN_BLOCKS/2)-4, 0);
+ testSet(tree, outOfBoundsPos, 0);
+ Assert.assertEquals("incorrect leaf node count", 4, tree.leafNodeCount());
+
}
@Test
- public void QuadTreeMovingTest()
+ public void QuadTreeRootAlignedMovingTest()
{
int treeWidthInRootNodes = 8;
int treeWidthInBlocks = ROOT_NODE_WIDTH_IN_BLOCKS * treeWidthInRootNodes;
- QuadTree tree = new QuadTree<>(treeWidthInBlocks, new DhBlockPos2D(0, 0));
+ QuadTree tree = new QuadTree<>(treeWidthInBlocks, TREE_CENTER_POS);
// root nodes //
@@ -212,18 +250,18 @@ public class QuadTreeTest
Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
// move so only the root nodes exactly on the X edge remain
- DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(ROOT_NODE_WIDTH_IN_BLOCKS - (BASIC_TREE_WIDTH_IN_ROOT_NODES*ROOT_NODE_WIDTH_IN_BLOCKS), 0);
+ DhBlockPos2D edgeMoveBlockPos = new DhBlockPos2D(ROOT_NODE_WIDTH_IN_BLOCKS - (BASIC_TREE_INPUT_WIDTH_IN_ROOT_NODES*ROOT_NODE_WIDTH_IN_BLOCKS), 0);
tree.setCenterBlockPos(edgeMoveBlockPos);
Assert.assertEquals("Tree center incorrect", edgeMoveBlockPos, tree.getCenterBlockPos());
- Assert.assertEquals("incorrect leaf node count", 1, tree.leafNodeCount());
+ Assert.assertEquals("incorrect leaf node count", 2, tree.leafNodeCount());
}
@Test
public void QuadTreeIterationTest()
{
- QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0, 0));
+ QuadTree tree = new QuadTree<>(BASIC_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
// root nodes //
@@ -258,25 +296,133 @@ public class QuadTreeTest
}
@Test
- public void FullTreeTest()
+ public void CenteredGridListIterationTest()
{
- QuadTree tree = new QuadTree<>(0, new DhBlockPos2D(0, 0));
- // minimum size tree should be 3 root nodes wide
- Assert.assertEquals("incorrect minimum size tree", 3, tree.ringListWidth());
-
-
- testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, -1), 0);
- testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, 0), 0);
- testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, -1, 1), 0);
-
- testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, -1), 0);
+ final QuadTree tree = new QuadTree<>(MINIMUM_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 0, 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.treeMaxDetailLevel, 1, 0), 0);
- testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 1, 1), 0);
+ // confirm the root node were added
+ final AtomicInteger rootNodeCount = new AtomicInteger(0);
+ tree.forEachRootNode((rootNode) -> { rootNodeCount.addAndGet(1); });
+ Assert.assertEquals("incorrect root count", 1, rootNodeCount.get());
+ // attempt to get and remove, each node in the tree
+ final AtomicInteger rootNodePosCount = new AtomicInteger(0);
+ tree.forEachRootNodePos((renderBufferNode, pos2d) ->
+ {
+ DhSectionPos sectionPos = new DhSectionPos(tree.treeMaxDetailLevel, pos2d.x, pos2d.y);
+
+ testGet(tree, sectionPos, 0);
+ testSet(tree, sectionPos, null);
+
+ rootNodePosCount.addAndGet(1);
+ });
+ Assert.assertEquals("incorrect root count", 1, rootNodeCount.get());
+
+ }
+
+ @Test
+ public void OffsetGridListIterationTest()
+ {
+
+ // offset fully inside (10*0,0)
+ final QuadTree fullyInsideTree = new QuadTree<>(MINIMUM_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
+
+ DhBlockPos2D fullyInsideOffsetBlockPos = new DhBlockPos2D(MINIMUM_TREE_WIDTH_IN_BLOCKS, MINIMUM_TREE_WIDTH_IN_BLOCKS);
+ fullyInsideTree.setCenterBlockPos(fullyInsideOffsetBlockPos);
+
+ fullyInsideTree.forEachRootNodePos((rootNode, pos2D) ->
+ {
+ testSet(fullyInsideTree, new DhSectionPos(fullyInsideTree.treeMaxDetailLevel, pos2D.x, pos2D.y), 0);
+ });
+
+ // only 1 root node should be added
+ final AtomicInteger fullyInsideRootNodeCount = new AtomicInteger(0);
+ fullyInsideTree.forEachRootNode((rootNode) -> { fullyInsideRootNodeCount.addAndGet(1); });
+ Assert.assertEquals("incorrect root count", 1, fullyInsideRootNodeCount.get());
+
+
+
+
+ // offset fully inside (10*0,0)
+ final QuadTree borderInsideTree = new QuadTree<>(MINIMUM_TREE_WIDTH_IN_BLOCKS, new DhBlockPos2D(MINIMUM_TREE_WIDTH_IN_BLOCKS * 2, MINIMUM_TREE_WIDTH_IN_BLOCKS * 2));
+
+ borderInsideTree.forEachRootNodePos((rootNode, pos2D) ->
+ {
+ testSet(borderInsideTree, new DhSectionPos(borderInsideTree.treeMaxDetailLevel, pos2D.x, pos2D.y), 0);
+ });
+
+ // only 1 root node should be added
+ final AtomicInteger borderInsideRootNodeCount = new AtomicInteger(0);
+ borderInsideTree.forEachRootNode((rootNode) -> { borderInsideRootNodeCount.addAndGet(1); });
+ Assert.assertEquals("incorrect root count", 1, borderInsideRootNodeCount.get());
+
+
+
+
+ // offset across (10*-1,0) and (10*0,0)
+ final QuadTree acrossTree = new QuadTree<>(MINIMUM_TREE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
+
+ DhBlockPos2D acrossOffsetBlockPos = new DhBlockPos2D(-MINIMUM_TREE_WIDTH_IN_BLOCKS/4, MINIMUM_TREE_WIDTH_IN_BLOCKS);
+ acrossTree.setCenterBlockPos(acrossOffsetBlockPos);
+
+ acrossTree.forEachRootNodePos((rootNode, pos2D) ->
+ {
+ testSet(acrossTree, new DhSectionPos(acrossTree.treeMaxDetailLevel, pos2D.x, pos2D.y), 0);
+ });
+
+ // 2 root nodes should be added
+ final AtomicInteger acrossRootNodeCount = new AtomicInteger(0);
+ acrossTree.forEachRootNode((rootNode) -> { acrossRootNodeCount.addAndGet(1); });
+ Assert.assertEquals("incorrect root count", 2, acrossRootNodeCount.get());
+
+ }
+
+ @Test
+ public void TinyGridAlignedTreeTest()
+ {
+ QuadTree tree = new QuadTree<>(ROOT_NODE_WIDTH_IN_BLOCKS, TREE_CENTER_POS);
+ // minimum size tree should be 3 root nodes wide
+ Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
+ Assert.assertEquals("incorrect tree width", ROOT_NODE_WIDTH_IN_BLOCKS, tree.diameterInBlocks());
+
+
+ testSet(tree, new DhSectionPos(tree.treeMaxDetailLevel, 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);
+
+ AtomicInteger rootCount = new AtomicInteger(0);
+ tree.forEachRootNode((rootNode) ->
+ {
+ rootCount.getAndAdd(1);
+ });
+ Assert.assertEquals("incorrect leaf value sum", 1, rootCount.get());
+
+ }
+
+ @Test
+ public void TinyGridOffsetTreeTest()
+ {
+ QuadTree tree = new QuadTree<>(ROOT_NODE_WIDTH_IN_BLOCKS, new DhBlockPos2D(0, 0));
+ // minimum size tree should be 3 root nodes wide
+ Assert.assertEquals("incorrect tree node width", 3, tree.ringListWidth());
+ Assert.assertEquals("incorrect tree width", ROOT_NODE_WIDTH_IN_BLOCKS, tree.diameterInBlocks());
+
+
+ // 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);
+
+ // 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.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);
AtomicInteger rootCount = new AtomicInteger(0);
@@ -284,7 +430,7 @@ public class QuadTreeTest
{
rootCount.getAndAdd(1);
});
- Assert.assertEquals("incorrect leaf value sum", 9, rootCount.get());
+ Assert.assertEquals("incorrect leaf value sum", 4, rootCount.get());
}