cull LOD rendering on the quad tree
This commit is contained in:
@@ -342,7 +342,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
// outdated when child LODs are updated.
|
||||
// (They'd have to be reloaded from file anyway during an update)
|
||||
long parentPos = renderSection.pos;
|
||||
while (DhSectionPos.getDetailLevel(parentPos) <= this.treeMinDetailLevel)
|
||||
while (DhSectionPos.getDetailLevel(parentPos) <= this.treeRootDetailLevel)
|
||||
{
|
||||
QuadNode<LodRenderSection> parentNode = this.getNode(parentPos);
|
||||
if (parentNode != null)
|
||||
@@ -579,7 +579,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
|
||||
// If not done corners may not be flush with the other LODs, which looks bad.
|
||||
byte minSectionDetailLevel = this.getDetailLevelFromDistance(this.blockRenderDistanceDiameter); // 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.treeMinDetailLevel); // don't allow rendering lower detail sections than what the tree contains
|
||||
minSectionDetailLevel = (byte) Math.min(minSectionDetailLevel, this.treeRootDetailLevel); // 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)
|
||||
}
|
||||
|
||||
|
||||
+43
-47
@@ -35,6 +35,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.pos.Pos2D;
|
||||
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.util.objects.RollingAverage;
|
||||
import com.seibel.distanthorizons.core.util.objects.SortedArraySet;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftGLWrapper;
|
||||
@@ -174,50 +175,17 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
}
|
||||
}
|
||||
|
||||
Pos2D cPos = this.lodQuadTree.getCenterBlockPos().toPos2D();
|
||||
Pos2D centerPos = this.lodQuadTree.getCenterBlockPos().toPos2D();
|
||||
|
||||
// Now that we have the axis directions, we can sort the render list
|
||||
Comparator<LoadedRenderBuffer> farToNearComparator = (loadedBufferA, loadedBufferB) ->
|
||||
{
|
||||
Pos2D aPos = DhSectionPos.getCenterBlockPos(loadedBufferA.pos).toPos2D();
|
||||
Pos2D bPos = DhSectionPos.getCenterBlockPos(loadedBufferB.pos).toPos2D();
|
||||
if (true)
|
||||
{
|
||||
int aManhattanDistance = aPos.manhattanDist(cPos);
|
||||
int bManhattanDistance = bPos.manhattanDist(cPos);
|
||||
return bManhattanDistance - aManhattanDistance;
|
||||
}
|
||||
|
||||
for (EDhDirection axisDirection : axisDirections)
|
||||
{
|
||||
if (axisDirection.getAxis().isVertical())
|
||||
{
|
||||
continue; // We only sort in the horizontal direction
|
||||
}
|
||||
|
||||
int abPosDifference;
|
||||
if (axisDirection.getAxis().equals(EDhDirection.Axis.X))
|
||||
{
|
||||
abPosDifference = aPos.getX() - bPos.getX();
|
||||
}
|
||||
else
|
||||
{
|
||||
abPosDifference = aPos.getY() - bPos.getY();
|
||||
}
|
||||
|
||||
if (abPosDifference == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (axisDirection.getAxisDirection().equals(EDhDirection.AxisDirection.NEGATIVE))
|
||||
{
|
||||
abPosDifference = -abPosDifference; // Reverse the sign
|
||||
}
|
||||
return abPosDifference;
|
||||
}
|
||||
|
||||
return DhSectionPos.getDetailLevel(loadedBufferA.pos) - DhSectionPos.getDetailLevel(loadedBufferB.pos); // If all else fails, sort by detail
|
||||
int aManhattanDistance = aPos.manhattanDist(centerPos);
|
||||
int bManhattanDistance = bPos.manhattanDist(centerPos);
|
||||
return bManhattanDistance - aManhattanDistance;
|
||||
};
|
||||
this.loadedNearToFarBuffers = new SortedArraySet<>((a, b) -> -farToNearComparator.compare(a, b)); // TODO is the comparator named wrong?
|
||||
|
||||
@@ -277,19 +245,21 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
this.culledBufferCount = 0;
|
||||
}
|
||||
|
||||
boolean rebuildAllBuffers = this.rebuildAllBuffers.getAndSet(false);
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIterator();
|
||||
while (nodeIterator.hasNext())
|
||||
// setup iterator with culling frustum
|
||||
Iterator<QuadNode<LodRenderSection>> nodeIterator = this.lodQuadTree.nodeIteratorWithStoppingFilter((QuadNode<LodRenderSection> node) ->
|
||||
{
|
||||
QuadNode<LodRenderSection> node = nodeIterator.next();
|
||||
if (node == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
long sectionPos = node.sectionPos;
|
||||
LodRenderSection renderSection = node.value;
|
||||
if (renderSection == null)
|
||||
{
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
if (enableFrustumCulling)
|
||||
@@ -309,22 +279,48 @@ public class RenderBufferHandler implements AutoCloseable
|
||||
this.culledBufferCount++;
|
||||
}
|
||||
|
||||
continue;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Unexpected issue during culling for node pos: ["+DhSectionPos.toString(node.sectionPos)+"], error: ["+e.getMessage()+"].", e);
|
||||
|
||||
// don't cull if there was an unexpected issue
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
while (nodeIterator.hasNext())
|
||||
{
|
||||
QuadNode<LodRenderSection> node = nodeIterator.next();
|
||||
|
||||
long sectionPos = node.sectionPos;
|
||||
LodRenderSection renderSection = node.value;
|
||||
if (renderSection == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
ColumnRenderBuffer buffer = renderSection.renderBuffer;
|
||||
if (buffer == null || !renderSection.getRenderingEnabled())
|
||||
if (buffer == null
|
||||
|| !renderSection.getRenderingEnabled())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
this.loadedNearToFarBuffers.add(new LoadedRenderBuffer(buffer, sectionPos));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.error("Error updating QuadTree render source at " + renderSection.pos + ".", e);
|
||||
LOGGER.error("Error updating QuadTree render source at [" + DhSectionPos.toString(renderSection.pos) + "], error: ["+e.getMessage()+"].", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+18
-12
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadNodeD
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.iterators.QuadTreeNodeIterator;
|
||||
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.function.Consumer;
|
||||
@@ -37,7 +38,11 @@ public class QuadNode<T>
|
||||
|
||||
|
||||
public final long sectionPos;
|
||||
public final byte minimumDetailLevel;
|
||||
/**
|
||||
* this is the highest detail level this tree can provide.
|
||||
* IE the detail levels that the root nodes in the tree are.
|
||||
*/
|
||||
public final byte parentTreeLeafDetailLevel;
|
||||
public T value;
|
||||
|
||||
|
||||
@@ -68,10 +73,10 @@ public class QuadNode<T>
|
||||
|
||||
|
||||
|
||||
public QuadNode(long sectionPos, byte minimumDetailLevel)
|
||||
public QuadNode(long sectionPos, byte parentTreeLeafDetailLevel)
|
||||
{
|
||||
this.sectionPos = sectionPos;
|
||||
this.minimumDetailLevel = minimumDetailLevel;
|
||||
this.parentTreeLeafDetailLevel = parentTreeLeafDetailLevel;
|
||||
}
|
||||
|
||||
|
||||
@@ -191,12 +196,12 @@ public class QuadNode<T>
|
||||
|
||||
if (DhSectionPos.getDetailLevel(inputSectionPos) == DhSectionPos.getDetailLevel(this.sectionPos) && inputSectionPos != this.sectionPos)
|
||||
{
|
||||
throw new IllegalArgumentException("Node and input detail level are equal, however positions are not; this tree doesn't contain the requested position. Node pos: " + this.sectionPos + ", input pos: " + inputSectionPos);
|
||||
throw new IllegalArgumentException("Node and input detail level are equal, however positions are not; this tree doesn't contain the requested position. Node pos: " + this.sectionPos + ", input pos: " + DhSectionPos.toString(inputSectionPos));
|
||||
}
|
||||
|
||||
if (DhSectionPos.getDetailLevel(inputSectionPos) < this.minimumDetailLevel)
|
||||
if (DhSectionPos.getDetailLevel(inputSectionPos) < this.parentTreeLeafDetailLevel)
|
||||
{
|
||||
throw new IllegalArgumentException("Input position is requesting a detail level lower than what this node can provide. Node minimum detail level: " + this.minimumDetailLevel + ", input pos: " + inputSectionPos);
|
||||
throw new IllegalArgumentException("Input position is requesting a detail level lower than what this node can provide. Tree leaf detail level: " + this.parentTreeLeafDetailLevel + ", input pos: " + DhSectionPos.toString(inputSectionPos));
|
||||
}
|
||||
|
||||
|
||||
@@ -231,7 +236,7 @@ public class QuadNode<T>
|
||||
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<>(nwPos, this.minimumDetailLevel);
|
||||
this.nwChild = new QuadNode<>(nwPos, this.parentTreeLeafDetailLevel);
|
||||
}
|
||||
childNode = this.nwChild;
|
||||
|
||||
@@ -244,7 +249,7 @@ public class QuadNode<T>
|
||||
if (replaceValue && this.swChild == null)
|
||||
{
|
||||
// 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, this.minimumDetailLevel);
|
||||
this.swChild = new QuadNode<>(swPos, this.parentTreeLeafDetailLevel);
|
||||
}
|
||||
childNode = this.swChild;
|
||||
|
||||
@@ -257,7 +262,7 @@ public class QuadNode<T>
|
||||
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, this.minimumDetailLevel);
|
||||
this.neChild = new QuadNode<>(nePos, this.parentTreeLeafDetailLevel);
|
||||
}
|
||||
childNode = this.neChild;
|
||||
|
||||
@@ -270,7 +275,7 @@ public class QuadNode<T>
|
||||
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, this.minimumDetailLevel);
|
||||
this.seChild = new QuadNode<>(sePos, this.parentTreeLeafDetailLevel);
|
||||
}
|
||||
childNode = this.seChild;
|
||||
|
||||
@@ -290,8 +295,9 @@ public class QuadNode<T>
|
||||
// iterators //
|
||||
//===========//
|
||||
|
||||
public Iterator<QuadNode<T>> getNodeIterator() { return new QuadTreeNodeIterator<>(this, false); }
|
||||
public Iterator<QuadNode<T>> getLeafNodeIterator() { return new QuadTreeNodeIterator<>(this, true); }
|
||||
public Iterator<QuadNode<T>> getNodeIterator() { return new QuadTreeNodeIterator<>(this, false, null); }
|
||||
public Iterator<QuadNode<T>> getNodeIterator(@Nullable QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc) { return new QuadTreeNodeIterator<>(this, false, stopIteratingFunc); }
|
||||
public Iterator<QuadNode<T>> getLeafNodeIterator() { return new QuadTreeNodeIterator<>(this, true, null); }
|
||||
|
||||
/** positions can point to null children */
|
||||
public LongIterator getChildPosIterator() { return new QuadNodeDirectChildPosIterator<>(this); }
|
||||
|
||||
+70
-33
@@ -35,6 +35,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.LongConsumer;
|
||||
|
||||
/**
|
||||
@@ -50,12 +51,12 @@ public class QuadTree<T>
|
||||
* The largest numerical detail level this tree supports. <br>
|
||||
* IE: the detail level used by the root nodes.
|
||||
*/
|
||||
public final byte treeMinDetailLevel;
|
||||
public final byte treeRootDetailLevel;
|
||||
/**
|
||||
* The smallest numerical detail level this tree supports. <br>
|
||||
* IE: the detail level used by the leaf nodes.
|
||||
*/
|
||||
public final byte treeMaxDetailLevel;
|
||||
public final byte treeLeafDetailLevel;
|
||||
|
||||
private final int diameterInBlocks; // diameterInBlocks
|
||||
|
||||
@@ -71,21 +72,21 @@ public class QuadTree<T>
|
||||
*
|
||||
* @param diameterInBlocks equivalent to the distance between the two opposing sides
|
||||
*/
|
||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeMaxDetailLevel)
|
||||
public QuadTree(int diameterInBlocks, DhBlockPos2D centerBlockPos, byte treeLeafDetailLevel)
|
||||
{
|
||||
this.centerBlockPos = centerBlockPos;
|
||||
this.diameterInBlocks = diameterInBlocks;
|
||||
|
||||
this.treeMaxDetailLevel = treeMaxDetailLevel;
|
||||
this.treeLeafDetailLevel = treeLeafDetailLevel;
|
||||
// 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(diameterInBlocks));
|
||||
this.treeRootDetailLevel = (byte) Math.max(Math.max(1, this.treeLeafDetailLevel), MathUtil.log2(diameterInBlocks));
|
||||
|
||||
int halfSizeInRootNodes = Math.floorDiv(this.diameterInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeMinDetailLevel);
|
||||
int halfSizeInRootNodes = Math.floorDiv(this.diameterInBlocks, 2) / BitShiftUtil.powerOfTwo(this.treeRootDetailLevel);
|
||||
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.treeMinDetailLevel),
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel));
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
||||
this.topRingList = new MovableGridRingList<>(halfSizeInRootNodes, ringListCenterPos.getX(), ringListCenterPos.getY());
|
||||
|
||||
}
|
||||
@@ -129,12 +130,12 @@ public class QuadTree<T>
|
||||
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.treeMaxDetailLevel + ", max detail level: " + this.treeMinDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
|
||||
throw new IndexOutOfBoundsException("QuadTree GetOrSet failed. Position out of bounds, min pos: " + minPos + ", max pos: " + maxPos + ", min detail level: " + this.treeLeafDetailLevel + ", max detail level: " + this.treeRootDetailLevel + ". Given Position: [" + DhSectionPos.toString(pos) + "] = block pos: " + DhSectionPos.convertToDetailLevel(pos, LodUtil.BLOCK_DETAIL_LEVEL));
|
||||
}
|
||||
|
||||
|
||||
|
||||
long rootPos = DhSectionPos.convertToDetailLevel(pos, this.treeMinDetailLevel);
|
||||
long rootPos = DhSectionPos.convertToDetailLevel(pos, this.treeRootDetailLevel);
|
||||
int ringListPosX = DhSectionPos.getX(rootPos);
|
||||
int ringListPosZ = DhSectionPos.getZ(rootPos);
|
||||
|
||||
@@ -146,7 +147,7 @@ public class QuadTree<T>
|
||||
return null;
|
||||
}
|
||||
|
||||
topQuadNode = new QuadNode<T>(rootPos, this.treeMaxDetailLevel);
|
||||
topQuadNode = new QuadNode<T>(rootPos, this.treeLeafDetailLevel);
|
||||
boolean successfullyAdded = this.topRingList.set(ringListPosX, ringListPosZ, topQuadNode);
|
||||
if (!successfullyAdded)
|
||||
{
|
||||
@@ -171,7 +172,7 @@ public class QuadTree<T>
|
||||
public boolean isSectionPosInBounds(long testPos)
|
||||
{
|
||||
// check if the testPos is within the detail level limits of the tree
|
||||
boolean detailLevelWithinBounds = this.treeMaxDetailLevel <= DhSectionPos.getDetailLevel(testPos) && DhSectionPos.getDetailLevel(testPos) <= this.treeMinDetailLevel;
|
||||
boolean detailLevelWithinBounds = this.treeLeafDetailLevel <= DhSectionPos.getDetailLevel(testPos) && DhSectionPos.getDetailLevel(testPos) <= this.treeRootDetailLevel;
|
||||
if (!detailLevelWithinBounds)
|
||||
{
|
||||
return false;
|
||||
@@ -237,10 +238,12 @@ public class QuadTree<T>
|
||||
//===========//
|
||||
|
||||
/** can include null nodes */
|
||||
public LongIterator rootNodePosIterator() { return new QuadTreeRootPosIterator(true); }
|
||||
public LongIterator rootNodePosIterator() { return new QuadTreeRootPosIterator(true, null); }
|
||||
|
||||
public Iterator<QuadNode<T>> nodeIterator() { return new QuadTreeNodeIterator(false); }
|
||||
public Iterator<QuadNode<T>> leafNodeIterator() { return new QuadTreeNodeIterator(true); }
|
||||
/** @see INodeIteratorStoppingFunc */
|
||||
public Iterator<QuadNode<T>> nodeIteratorWithStoppingFilter(INodeIteratorStoppingFunc<T> stoppingFilterFunc) { return new QuadTreeNodeIterator(false, stoppingFilterFunc); }
|
||||
public Iterator<QuadNode<T>> nodeIterator() { return new QuadTreeNodeIterator(false, null); }
|
||||
public Iterator<QuadNode<T>> leafNodeIterator() { return new QuadTreeNodeIterator(true, null); }
|
||||
|
||||
|
||||
|
||||
@@ -254,8 +257,8 @@ public class QuadTree<T>
|
||||
this.centerBlockPos = newCenterPos;
|
||||
|
||||
Pos2D expectedCenterPos = new Pos2D(
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeMinDetailLevel),
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeMinDetailLevel));
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.x, this.treeRootDetailLevel),
|
||||
BitShiftUtil.divideByPowerOfTwo(this.centerBlockPos.z, this.treeRootDetailLevel));
|
||||
|
||||
if (this.topRingList.getCenter().equals(expectedCenterPos))
|
||||
{
|
||||
@@ -320,16 +323,14 @@ public class QuadTree<T>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//==============//
|
||||
// base methods //
|
||||
//==============//
|
||||
|
||||
public boolean isEmpty() { return this.count() == 0; } // TODO this should be rewritten to short-circuit
|
||||
public boolean isEmpty() { return this.nodeCount() == 0; } // this should be rewritten to short-circuit
|
||||
|
||||
/** @return the number of non-null nodes in the tree */
|
||||
public int count()
|
||||
public int nodeCount()
|
||||
{
|
||||
int count = 0;
|
||||
for (QuadNode<T> node : this.topRingList)
|
||||
@@ -394,7 +395,13 @@ public class QuadTree<T>
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() { return "center block: " + this.centerBlockPos + ", block width: " + this.diameterInBlocks + ", detail level range: [" + this.treeMaxDetailLevel + "-" + this.treeMinDetailLevel + "], leaf #: " + this.leafNodeCount(); }
|
||||
public String toString()
|
||||
{
|
||||
return "center block: " + this.centerBlockPos +
|
||||
", block width: " + this.diameterInBlocks +
|
||||
", detail level range: [" + this.treeLeafDetailLevel + "-" + this.treeRootDetailLevel + "], " +
|
||||
"leaf #: " + this.leafNodeCount();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -402,19 +409,39 @@ public class QuadTree<T>
|
||||
// iterator classes //
|
||||
//==================//
|
||||
|
||||
/** @see INodeIteratorStoppingFunc#iteratorShouldStop(QuadNode) */
|
||||
@FunctionalInterface
|
||||
public interface INodeIteratorStoppingFunc<T>
|
||||
{
|
||||
/** if this function returns true then the iterator will stop walking down the tree */
|
||||
boolean iteratorShouldStop(QuadNode<T> node);
|
||||
}
|
||||
|
||||
private class QuadTreeRootPosIterator implements LongIterator
|
||||
{
|
||||
private final LongArrayFIFOQueue iteratorPosQueue = new LongArrayFIFOQueue();
|
||||
private final LongArrayFIFOQueue iteratorPosQueue;
|
||||
@Nullable
|
||||
private final INodeIteratorStoppingFunc<T> stopIteratingFunc;
|
||||
|
||||
|
||||
|
||||
public QuadTreeRootPosIterator(boolean includeNullNodes)
|
||||
public QuadTreeRootPosIterator(boolean includeNullNodes, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||
{
|
||||
this.iteratorPosQueue = new LongArrayFIFOQueue();
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
|
||||
QuadTree.this.topRingList.forEachPosOrdered((node, pos2D) ->
|
||||
{
|
||||
if (node != null || includeNullNodes)
|
||||
if (this.stopIteratingFunc != null
|
||||
&& this.stopIteratingFunc.iteratorShouldStop(node))
|
||||
{
|
||||
long rootPos = DhSectionPos.encode(QuadTree.this.treeMinDetailLevel, pos2D.getX(), pos2D.getY());
|
||||
return;
|
||||
}
|
||||
|
||||
if (node != null
|
||||
|| includeNullNodes)
|
||||
{
|
||||
long rootPos = DhSectionPos.encode(QuadTree.this.treeRootDetailLevel, pos2D.getX(), pos2D.getY());
|
||||
if (QuadTree.this.isSectionPosInBounds(rootPos))
|
||||
{
|
||||
this.iteratorPosQueue.enqueue(rootPos);
|
||||
@@ -459,13 +486,17 @@ public class QuadTree<T>
|
||||
private QuadNode<T> lastNode = null;
|
||||
|
||||
private final boolean onlyReturnLeaves;
|
||||
@Nullable
|
||||
private final INodeIteratorStoppingFunc<T> stopIteratingFunc;
|
||||
|
||||
|
||||
|
||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves)
|
||||
public QuadTreeNodeIterator(boolean onlyReturnLeaves, @Nullable INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||
{
|
||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false);
|
||||
this.rootNodeIterator = new QuadTreeRootPosIterator(false, stopIteratingFunc);
|
||||
this.onlyReturnLeaves = onlyReturnLeaves;
|
||||
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
}
|
||||
|
||||
|
||||
@@ -473,23 +504,28 @@ public class QuadTree<T>
|
||||
@Override
|
||||
public boolean hasNext()
|
||||
{
|
||||
if (!this.rootNodeIterator.hasNext() && this.currentNodeIterator != null && !this.currentNodeIterator.hasNext())
|
||||
if (!this.rootNodeIterator.hasNext()
|
||||
&& this.currentNodeIterator != null
|
||||
&& !this.currentNodeIterator.hasNext())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (this.currentNodeIterator == null || !this.currentNodeIterator.hasNext())
|
||||
if (this.currentNodeIterator == null
|
||||
|| !this.currentNodeIterator.hasNext())
|
||||
{
|
||||
this.currentNodeIterator = this.getNextChildNodeIterator();
|
||||
}
|
||||
|
||||
return this.currentNodeIterator != null && this.currentNodeIterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuadNode<T> next()
|
||||
{
|
||||
if (this.currentNodeIterator == null || !this.currentNodeIterator.hasNext())
|
||||
if (this.currentNodeIterator == null
|
||||
|| !this.currentNodeIterator.hasNext())
|
||||
{
|
||||
this.currentNodeIterator = this.getNextChildNodeIterator();
|
||||
}
|
||||
@@ -503,13 +539,14 @@ public class QuadTree<T>
|
||||
private Iterator<QuadNode<T>> getNextChildNodeIterator()
|
||||
{
|
||||
Iterator<QuadNode<T>> nodeIterator = null;
|
||||
while ((nodeIterator == null || !nodeIterator.hasNext()) && this.rootNodeIterator.hasNext())
|
||||
while ((nodeIterator == null || !nodeIterator.hasNext())
|
||||
&& this.rootNodeIterator.hasNext())
|
||||
{
|
||||
long sectionPos = this.rootNodeIterator.nextLong();
|
||||
QuadNode<T> rootNode = QuadTree.this.getNode(sectionPos);
|
||||
if (rootNode != null)
|
||||
{
|
||||
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator();
|
||||
nodeIterator = this.onlyReturnLeaves ? rootNode.getLeafNodeIterator() : rootNode.getNodeIterator(this.stopIteratingFunc);
|
||||
}
|
||||
}
|
||||
return nodeIterator;
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ public class QuadNodeChildIndexIterator<T> implements Iterator<Integer>
|
||||
public QuadNodeChildIndexIterator(QuadNode<T> parentNode, boolean returnNullChildPos)
|
||||
{
|
||||
// only get the children if this section isn't at the bottom of the tree
|
||||
if (DhSectionPos.getDetailLevel(parentNode.sectionPos) > parentNode.minimumDetailLevel)
|
||||
if (DhSectionPos.getDetailLevel(parentNode.sectionPos) > parentNode.parentTreeLeafDetailLevel)
|
||||
{
|
||||
// go over each child pos
|
||||
for (int i = 0; i < 4; i++)
|
||||
|
||||
+41
-11
@@ -21,6 +21,8 @@ package com.seibel.distanthorizons.core.util.objects.quadTree.iterators;
|
||||
|
||||
import com.seibel.distanthorizons.core.pos.DhSectionPos;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
|
||||
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadTree;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
@@ -36,31 +38,36 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
private byte iteratorDetailLevel = 0;
|
||||
|
||||
private final boolean onlyReturnLeafValues;
|
||||
@Nullable
|
||||
private final QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc;
|
||||
|
||||
|
||||
|
||||
public QuadTreeNodeIterator(QuadNode<T> rootNode, boolean onlyReturnLeafValues)
|
||||
public QuadTreeNodeIterator(
|
||||
QuadNode<T> rootNode,
|
||||
boolean onlyReturnLeafValues, @Nullable QuadTree.INodeIteratorStoppingFunc<T> stopIteratingFunc)
|
||||
{
|
||||
this.onlyReturnLeafValues = onlyReturnLeafValues;
|
||||
this.stopIteratingFunc = stopIteratingFunc;
|
||||
// TODO the naming conversion for these are flipped in a lot of places
|
||||
this.highestDetailLevel = rootNode.minimumDetailLevel;
|
||||
this.highestDetailLevel = rootNode.parentTreeLeafDetailLevel;
|
||||
this.iteratorDetailLevel = DhSectionPos.getDetailLevel(rootNode.sectionPos);
|
||||
|
||||
|
||||
if (!this.onlyReturnLeafValues)
|
||||
{
|
||||
// return all nodes
|
||||
|
||||
// set the start for the iterator
|
||||
this.validNodesForDetailLevel.add(rootNode);
|
||||
this.iteratorNodeQueue.add(rootNode);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fully populate the iterator
|
||||
// return leaf nodes
|
||||
|
||||
// fully populate the iterator
|
||||
// this isn't the best way to do this, especially for large trees,
|
||||
// but it is simple and functions well enough for now
|
||||
|
||||
|
||||
Queue<QuadNode<T>> parentNodeQueue = new ArrayDeque<>();
|
||||
parentNodeQueue.add(rootNode);
|
||||
|
||||
@@ -68,6 +75,13 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
while (parentNodeQueue.peek() != null)
|
||||
{
|
||||
QuadNode<T> parentNode = parentNodeQueue.poll();
|
||||
|
||||
if (stopIteratingFunc != null
|
||||
&& stopIteratingFunc.iteratorShouldStop(parentNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
QuadNode<T> childNode = parentNode.getChildByIndex(i);
|
||||
@@ -112,7 +126,8 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
|
||||
// the iterator queue should be fully populated for leaf values,
|
||||
// for all values, it needs to be populated for each detail level
|
||||
if (this.iteratorNodeQueue.size() == 0 && !onlyReturnLeafValues)
|
||||
if (this.iteratorNodeQueue.size() == 0
|
||||
&& !this.onlyReturnLeafValues)
|
||||
{
|
||||
// populate the next detail level list
|
||||
|
||||
@@ -123,17 +138,32 @@ public class QuadTreeNodeIterator<T> implements Iterator<QuadNode<T>>
|
||||
Queue<QuadNode<T>> parentNodes = new LinkedList<>(this.validNodesForDetailLevel);
|
||||
this.validNodesForDetailLevel.clear();
|
||||
|
||||
// populate the list of nodes for this level
|
||||
// populate the list of nodes for this detail level
|
||||
for (QuadNode<T> parentNode : parentNodes)
|
||||
{
|
||||
if (this.stopIteratingFunc != null
|
||||
&& this.stopIteratingFunc.iteratorShouldStop(parentNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
QuadNode<T> childNode = parentNode.getChildByIndex(i);
|
||||
if (childNode != null)
|
||||
if (childNode == null)
|
||||
{
|
||||
this.iteratorNodeQueue.add(childNode);
|
||||
this.validNodesForDetailLevel.add(childNode);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.stopIteratingFunc != null
|
||||
&& this.stopIteratingFunc.iteratorShouldStop(childNode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
this.iteratorNodeQueue.add(childNode);
|
||||
this.validNodesForDetailLevel.add(childNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,9 +31,11 @@ import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.core.config.Configurator;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -56,7 +58,7 @@ public class QuadTreeTest
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new LargeTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeMinDetailLevel >= 10 && tree.treeMaxDetailLevel <= 10 - 4);
|
||||
Assert.assertTrue("Tree min/max detail level out of expected bounds: " + tree, tree.treeRootDetailLevel >= 10 && tree.treeLeafDetailLevel <= 10 - 4);
|
||||
|
||||
|
||||
// (pseudo) root node //
|
||||
@@ -171,7 +173,7 @@ public class QuadTreeTest
|
||||
{
|
||||
// very specific tree parameters to match test results
|
||||
QuadTree<Integer> 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.treeMinDetailLevel);
|
||||
Assert.assertEquals("Test may need to be re-calculated for different max detail level.", 9, tree.treeRootDetailLevel);
|
||||
|
||||
|
||||
long rootPos = DhSectionPos.encode((byte) 9, 0, -1);
|
||||
@@ -329,6 +331,125 @@ public class QuadTreeTest
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void QuadTreeIterationFilterTest()
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||
QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), (byte)0);
|
||||
|
||||
|
||||
|
||||
// fill tree root node(s)
|
||||
int rootNodeCount = 0;
|
||||
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
|
||||
while (rootNodePosIterator.hasNext())
|
||||
{
|
||||
long rootNodePos = rootNodePosIterator.nextLong();
|
||||
recursivelyFillRootNodeTree(tree, (byte)0, rootNodePos);
|
||||
|
||||
rootNodeCount++;
|
||||
}
|
||||
|
||||
Assert.assertEquals("Only one root node expected", 1, rootNodeCount);
|
||||
|
||||
// confirm tree isn't empty
|
||||
int totalNodeCount = tree.nodeCount();
|
||||
Assert.assertNotEquals("No nodes", 0, totalNodeCount);
|
||||
int leafCount = tree.leafNodeCount();
|
||||
Assert.assertNotEquals("No leaf nodes", 0, leafCount);
|
||||
|
||||
// confirm tree has correct number of nodes
|
||||
int treeDepth = tree.treeRootDetailLevel - tree.treeLeafDetailLevel;
|
||||
|
||||
// equation source: http://www.gamedev.net/forums/topic/325071-calculate-number-of-nodes-in-a-quad-tree/3098433/
|
||||
int expectedNodeCount = (int)Math.pow(4, treeDepth + 1);
|
||||
expectedNodeCount = (expectedNodeCount - 1) / 3;
|
||||
|
||||
Assert.assertEquals("Unexpected total node count", expectedNodeCount, totalNodeCount);
|
||||
int expectedLeafNodeCount = (int)Math.pow(4, treeDepth);
|
||||
Assert.assertEquals("Unexpected leaf node count", expectedLeafNodeCount, leafCount);
|
||||
|
||||
|
||||
|
||||
// filters //
|
||||
|
||||
// accept everything
|
||||
assertFilterCount(tree, "Get everything should return total node count", totalNodeCount, (node) -> false);
|
||||
// ignore everything
|
||||
assertFilterCount(tree, "Ignore everything filter shouldn't return anything", 0, (node) -> true);
|
||||
|
||||
// root detail level
|
||||
assertFilterCount(tree, "filter root detail", 1,
|
||||
(node) ->
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DhSectionPos.getDetailLevel(node.sectionPos) < tree.treeRootDetailLevel;
|
||||
});
|
||||
|
||||
// root - 1 detail level
|
||||
assertFilterCount(tree, "filter root-1 detail", 1 + 4,
|
||||
(node) ->
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return DhSectionPos.getDetailLevel(node.sectionPos) < tree.treeRootDetailLevel - 1;
|
||||
});
|
||||
|
||||
// only nodes in (4*0,0)
|
||||
assertFilterCount(tree, "filter to root-1 NW corner, should return 1/4th of all nodes plus root ", (totalNodeCount/4) + 1,
|
||||
(node) ->
|
||||
{
|
||||
if (node == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return !DhSectionPos.contains(DhSectionPos.encode((byte)4, 0, 0), node.sectionPos);
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
private static <T> void assertFilterCount(QuadTree<T> tree, String message, int expectedNodeCount, @Nullable QuadTree.INodeIteratorStoppingFunc<T> stoppingFilterFunc) // TODO functional interface
|
||||
{
|
||||
ArrayList<String> foundNodePositionStrings = new ArrayList<>();
|
||||
|
||||
int filteredNodeCount = 0;
|
||||
Iterator<QuadNode<T>> filteredNodeIterator = tree.nodeIteratorWithStoppingFilter(stoppingFilterFunc);
|
||||
while (filteredNodeIterator.hasNext())
|
||||
{
|
||||
QuadNode<T> node = filteredNodeIterator.next();
|
||||
foundNodePositionStrings.add(DhSectionPos.toString(node.sectionPos));
|
||||
filteredNodeCount++;
|
||||
}
|
||||
|
||||
Assert.assertEquals(message + " | found count: ["+foundNodePositionStrings.size()+"], found nodes: ["+ String.join(", ", foundNodePositionStrings)+"]", expectedNodeCount, filteredNodeCount);
|
||||
}
|
||||
private static void recursivelyFillRootNodeTree(QuadTree<Integer> tree, byte bottomDetailLevel, long rootNodePos)
|
||||
{
|
||||
byte thisDetailLevel = DhSectionPos.getDetailLevel(rootNodePos);
|
||||
tree.setValue(rootNodePos, (int)thisDetailLevel);
|
||||
|
||||
if (thisDetailLevel <= bottomDetailLevel)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
long childPos = DhSectionPos.getChildByIndex(rootNodePos, i);
|
||||
recursivelyFillRootNodeTree(tree, bottomDetailLevel, childPos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void NewQuadTreeIterationTest()
|
||||
{
|
||||
@@ -411,7 +532,7 @@ public class QuadTreeTest
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new TinyTestTree();
|
||||
final QuadTree<Integer> tree = new QuadTree<>(treeParams.getWidthInBlocks(), treeParams.getPositiveEdgeCenterPos(), LodUtil.BLOCK_DETAIL_LEVEL);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||
|
||||
// confirm the root node were added
|
||||
int rootNodeCount = 0;
|
||||
@@ -499,10 +620,10 @@ public class QuadTreeTest
|
||||
Assert.assertEquals("incorrect tree width", treeParams.getWidthInBlocks(), tree.diameterInBlocks());
|
||||
|
||||
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, -1, -1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
|
||||
|
||||
int rootNodeCount = 0;
|
||||
LongIterator rootNodeIterator = tree.rootNodePosIterator();
|
||||
@@ -529,18 +650,18 @@ public class QuadTreeTest
|
||||
|
||||
|
||||
// 2x2 valid positions (overlap the tree's width)
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, -1), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, -1), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, -1, 0), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, -1), 0);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, -1, -1), 0);
|
||||
|
||||
// invalid positions
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, -1, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 0, 1), -1, IndexOutOfBoundsException.class);
|
||||
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeMinDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 1, 0), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 1, 1), -1, IndexOutOfBoundsException.class);
|
||||
testSet(tree, DhSectionPos.encode(tree.treeRootDetailLevel, 1, -1), -1, IndexOutOfBoundsException.class);
|
||||
|
||||
|
||||
int rootNodeCount = 0;
|
||||
@@ -562,7 +683,7 @@ public class QuadTreeTest
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> 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.treeMinDetailLevel);
|
||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||
|
||||
// valid detail levels
|
||||
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
|
||||
@@ -584,14 +705,14 @@ public class QuadTreeTest
|
||||
{
|
||||
AbstractTestTreeParams treeParams = new MediumTestTree();
|
||||
QuadTree<Integer> 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.treeMinDetailLevel);
|
||||
Assert.assertEquals("Test detail level's need to be adjusted. This isn't necessarily a failed test.", 10, tree.treeRootDetailLevel);
|
||||
|
||||
// create the root node
|
||||
testSet(tree, DhSectionPos.encode((byte) 10, 0, 0), 1);
|
||||
|
||||
|
||||
|
||||
AtomicInteger minimumDetailLevelReachedRef = new AtomicInteger(tree.treeMinDetailLevel);
|
||||
AtomicInteger minimumDetailLevelReachedRef = new AtomicInteger(tree.treeRootDetailLevel);
|
||||
|
||||
// recurse down the tree
|
||||
LongIterator rootNodePosIterator = tree.rootNodePosIterator();
|
||||
@@ -618,13 +739,13 @@ public class QuadTreeTest
|
||||
QuadNode<Integer> childNode = ChildIterator.next();
|
||||
Assert.assertNotNull(childNode); // TODO is this correct?
|
||||
|
||||
this.recursivelyCreateNodeChildren(childNode, tree.treeMaxDetailLevel, minimumDetailLevelReachedRef);
|
||||
this.recursivelyCreateNodeChildren(childNode, tree.treeLeafDetailLevel, 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.treeMaxDetailLevel, minimumDetailLevelReachedRef.get());
|
||||
Assert.assertEquals("Incorrect minimum detail level reached.", tree.treeLeafDetailLevel, minimumDetailLevelReachedRef.get());
|
||||
}
|
||||
private void recursivelyCreateNodeChildren(QuadNode<Integer> node, byte minDetailLevel, AtomicInteger minimumDetailLevelReachedRef)
|
||||
{
|
||||
@@ -773,9 +894,9 @@ public class QuadTreeTest
|
||||
|
||||
//
|
||||
testSet(tree, DhSectionPos.encode((byte) 0, 0, 0), 1);
|
||||
Assert.assertEquals(1, tree.count());
|
||||
Assert.assertEquals(1, tree.nodeCount());
|
||||
tree.setCenterBlockPos(new DhBlockPos2D(treeWidth + (treeWidth / 2), 0));
|
||||
Assert.assertEquals(0, tree.count());
|
||||
Assert.assertEquals(0, tree.nodeCount());
|
||||
|
||||
}
|
||||
|
||||
@@ -899,9 +1020,10 @@ public class QuadTreeTest
|
||||
|
||||
/** the tree should be slightly larger than the width in blocks to account for offset centers */
|
||||
public int getWidthInRootNodes() { return MathUtil.log2(this.getWidthInBlocks()) + 2; }
|
||||
public byte getMaxDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
||||
/** the top (root) detail level in the tree */
|
||||
public byte getMinDetailLevel() { return (byte) MathUtil.log2(this.getWidthInBlocks()); }
|
||||
/** @return the block pos so that the tree's negative corner lines up with (0,0) */
|
||||
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getMaxDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getMaxDetailLevel()) / 2); }
|
||||
public DhBlockPos2D getPositiveEdgeCenterPos() { return new DhBlockPos2D(BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2, BitShiftUtil.powerOfTwo(this.getMinDetailLevel()) / 2); }
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user