Refactor/comment DhSectionPos, DhLodPos, and DhLodUnit

Also add BitShiftUtil to hold bit shift operation aliases for easier reading
This commit is contained in:
James Seibel
2022-11-06 21:25:37 -06:00
parent ae7dd5ba7e
commit 8099925dc2
15 changed files with 437 additions and 296 deletions
@@ -0,0 +1,35 @@
package com.seibel.lod.core.util;
/**
* A list of helper methods to make code easier to read. <br>
* Specifically written because bit shifts short circuit James' brain.
*
* @author James Seibel
* @version 2022-11-6
*/
public class BitShiftUtil
{
/**
* Equivalent to: <br>
* 1 << value, <br>
* 2^value, <br>
* Math.pow(2, value) <br><br>
*
* Note: Math.pow() isn't identical for large values where bits would be lost in the shift, however for medium to small values they function the same. <br><br>
*
* Can also be used to replace bit shifts in the format: <br>
* multiplier << value; <br>
* multiplier * powerOfTwo(value);
*/
public static int powerOfTwo(int value) { return 1 << value; }
/**
* Equivalent to: <br>
* value >> 1, <br>
* value / 2 <br><br>
*
* Note: value / 2 isn't identical for negative values
*/
public static int half(int value) { return value >> 1; }
}
@@ -6,43 +6,28 @@ public class MathUtil
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static int clamp(int min, int value, int max)
{
return Math.min(max, Math.max(value, min));
}
public static int clamp(int min, int value, int max) { return Math.min(max, Math.max(value, min)); }
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static float clamp(float min, float value, float max)
{
return Math.min(max, Math.max(value, min));
}
public static float clamp(float min, float value, float max) { return Math.min(max, Math.max(value, min)); }
/**
* Clamps the given value between the min and max values.
* May behave strangely if min > max.
*/
public static double clamp(double min, double value, double max)
{
return Math.min(max, Math.max(value, min));
}
public static double clamp(double min, double value, double max) { return Math.min(max, Math.max(value, min)); }
/**
* Like Math.floorDiv, but reverse in that it is a ceilDiv
*/
public static int ceilDiv(int value, int divider) {
return -Math.floorDiv(-value, divider);
}
public static int ceilDiv(int value, int divider) { return -Math.floorDiv(-value, divider); }
// Why is this not in the standard library?! Come on Java!
public static byte min(byte a, byte b) {
return a < b ? a : b;
}
public static byte max(byte a, byte b) {
return a > b ? a : b;
}
public static byte min(byte a, byte b) { return a < b ? a : b; }
public static byte max(byte a, byte b) { return a > b ? a : b; }
/** This is copied from Minecraft's MathHelper class */
@@ -54,11 +39,10 @@ public class MathUtil
numb = Float.intBitsToFloat(i);
return numb * (1.5F - half * numb * numb);
}
public static float pow2(float x) {return x*x;}
public static double pow2(double x) {return x*x;}
public static int pow2(int x) {return x*x;}
public static long pow2(long x) {return x*x;}
public static float pow2(float x) { return x * x; }
public static double pow2(double x) { return x * x; }
public static int pow2(int x) { return x * x; }
public static long pow2(long x) { return x * x; }
}
@@ -374,7 +374,7 @@ public class ColumnRenderSource implements ILodRenderSource, IColumnDatatype
ColumnRenderSource[] data = new ColumnRenderSource[ELodDirection.ADJ_DIRECTIONS.length];
for (ELodDirection direction : ELodDirection.ADJ_DIRECTIONS)
{
LodRenderSection section = quadTree.getSection(this.sectionPos.getAdjacent(direction)); //FIXME: Handle traveling through different detail levels
LodRenderSection section = quadTree.getSection(this.sectionPos.getAdjacentPos(direction)); //FIXME: Handle traveling through different detail levels
if (section != null && section.getRenderSource() != null && section.getRenderSource() instanceof ColumnRenderSource)
{
data[direction.ordinal() - 2] = ((ColumnRenderSource) section.getRenderSource());
@@ -503,7 +503,7 @@ public class ColumnRenderSource implements ILodRenderSource, IColumnDatatype
stringBuilder.append(sectionPos);
stringBuilder.append(LINE_DELIMITER);
int size = sectionPos.getWidth().value;
int size = sectionPos.getWidth().numberOfLodSectionsWide;
for (int z = 0; z < size; z++)
{
for (int x = 0; x < size; x++)
@@ -156,7 +156,7 @@ public class SparseDataSource implements IIncompleteDataSource
DhLodPos basePos = sectionPos.getCorner(SPARSE_UNIT_DETAIL);
DhLodPos dataPos = pos.getCorner(SPARSE_UNIT_DETAIL);
int coveredChunks = pos.getWidth(SPARSE_UNIT_DETAIL).value;
int coveredChunks = pos.getWidth(SPARSE_UNIT_DETAIL).numberOfLodSectionsWide;
int sourceDataPerChunk = SPARSE_UNIT_SIZE >>> fullSource.getDataDetail();
LodUtil.assertTrue(coveredChunks*sourceDataPerChunk == FullDataSource.SECTION_SIZE);
int offsetX = dataPos.x-basePos.x;
@@ -214,7 +214,7 @@ public class SpottyDataSource extends FullArrayView implements IIncompleteDataSo
int offsetZ = dataPos.z - basePos.z;
LodUtil.assertTrue(offsetX >= 0 && offsetX < SECTION_SIZE && offsetZ >= 0 && offsetZ < SECTION_SIZE);
int chunksPerData = 1 << (getDataDetail() - SparseDataSource.SPARSE_UNIT_DETAIL);
int dataSpan = sectionPos.getWidth(getDataDetail()).value;
int dataSpan = sectionPos.getWidth(getDataDetail()).numberOfLodSectionsWide;
for (int ox = 0; ox < dataSpan; ox++) {
for (int oz = 0; oz < dataSpan; oz++) {
@@ -229,7 +229,7 @@ public class SpottyDataSource extends FullArrayView implements IIncompleteDataSo
}
} else {
DhLodPos dataPos = pos.getSectionBBoxPos();
int lowerSectionsPerData = sectionPos.getWidth(dataPos.detail).value;
int lowerSectionsPerData = sectionPos.getWidth(dataPos.detailLevel).numberOfLodSectionsWide;
if (dataPos.x % lowerSectionsPerData != 0 || dataPos.z % lowerSectionsPerData != 0) return;
DhLodPos basePos = sectionPos.getCorner(getDataDetail());
@@ -254,7 +254,7 @@ public class SpottyDataSource extends FullArrayView implements IIncompleteDataSo
DhLodPos dataPos = pos.getCorner(getDataDetail());
int offsetX = dataPos.x - basePos.x;
int offsetZ = dataPos.z - basePos.z;
int dataSpan = sectionPos.getWidth(getDataDetail()).value;
int dataSpan = sectionPos.getWidth(getDataDetail()).numberOfLodSectionsWide;
for (int ox = 0; ox < dataSpan; ox++) {
for (int oz = 0; oz < dataSpan; oz++) {
isColumnNotEmpty.set((offsetX + ox) * SECTION_SIZE + offsetZ + oz, true);
@@ -262,7 +262,7 @@ public class SpottyDataSource extends FullArrayView implements IIncompleteDataSo
}
} else {
DhLodPos dataPos = pos.getSectionBBoxPos();
int lowerSectionsPerData = sectionPos.getWidth(dataPos.detail).value;
int lowerSectionsPerData = sectionPos.getWidth(dataPos.detailLevel).numberOfLodSectionsWide;
if (dataPos.x % lowerSectionsPerData != 0 || dataPos.z % lowerSectionsPerData != 0) return;
DhLodPos basePos = sectionPos.getCorner(getDataDetail());
dataPos = dataPos.convertUpwardsTo(getDataDetail());
@@ -37,8 +37,8 @@ public class FullToColumnTransformer {
if (dataDetail == columnSource.getDataDetail()) {
int baseX = pos.getCorner().getCorner().x;
int baseZ = pos.getCorner().getCorner().z;
for (int x = 0; x < pos.getWidth(dataDetail).value; x++) {
for (int z = 0; z < pos.getWidth(dataDetail).value; z++) {
for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) {
for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) {
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
SingleFullArrayView fullArrayView = data.get(x, z);
convertColumnData(level, baseX + x, baseZ + z, columnArrayView, fullArrayView, 1);
@@ -77,8 +77,8 @@ public class FullToColumnTransformer {
if (dataDetail == columnSource.getDataDetail()) {
int baseX = pos.getCorner().getCorner().x;
int baseZ = pos.getCorner().getCorner().z;
for (int x = 0; x < pos.getWidth(dataDetail).value; x++) {
for (int z = 0; z < pos.getWidth(dataDetail).value; z++) {
for (int x = 0; x < pos.getWidth(dataDetail).numberOfLodSectionsWide; x++) {
for (int z = 0; z < pos.getWidth(dataDetail).numberOfLodSectionsWide; z++) {
SingleFullArrayView fullArrayView = data.tryGet(x, z);
if (fullArrayView == null) continue;
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
@@ -136,7 +136,7 @@ public class DataFileHandler implements IDataSourceProvider {
outerLoop:
while (--detail >= minDetailLevel) {
DhLodPos min = pos.getCorner().getCorner(detail);
int count = pos.getSectionBBoxPos().getWidth(detail);
int count = pos.getSectionBBoxPos().getBlockWidth(detail);
for (int ox = 0; ox<count; ox++) {
for (int oz = 0; oz<count; oz++) {
DhSectionPos subPos = new DhSectionPos(detail, ox+min.x, oz+min.z);
@@ -158,7 +158,7 @@ public class DataFileHandler implements IDataSourceProvider {
missing.add(pos);
} else {
{
DhSectionPos childPos = pos.getChild(0);
DhSectionPos childPos = pos.getChildByIndex(0);
if (FullDataSource.neededForPosition(basePos, childPos)) {
DataMetaFile metaFile = files.get(childPos);
if (metaFile != null) {
@@ -171,7 +171,7 @@ public class DataFileHandler implements IDataSourceProvider {
}
}
{
DhSectionPos childPos = pos.getChild(1);
DhSectionPos childPos = pos.getChildByIndex(1);
if (FullDataSource.neededForPosition(basePos, childPos)) {
DataMetaFile metaFile = files.get(childPos);
if (metaFile != null) {
@@ -184,7 +184,7 @@ public class DataFileHandler implements IDataSourceProvider {
}
}
{
DhSectionPos childPos = pos.getChild(2);
DhSectionPos childPos = pos.getChildByIndex(2);
if (FullDataSource.neededForPosition(basePos, childPos)) {
DataMetaFile metaFile = files.get(childPos);
if (metaFile != null) {
@@ -197,7 +197,7 @@ public class DataFileHandler implements IDataSourceProvider {
}
}
{
DhSectionPos childPos = pos.getChild(3);
DhSectionPos childPos = pos.getChildByIndex(3);
if (FullDataSource.neededForPosition(basePos, childPos)) {
DataMetaFile metaFile = files.get(childPos);
if (metaFile != null) {
@@ -232,7 +232,7 @@ public class DataFileHandler implements IDataSourceProvider {
DhLodPos chunkPos = new DhLodPos((byte) (chunkData.dataDetail+4), chunkData.x, chunkData.z);
LodUtil.assertTrue(chunkPos.overlaps(sectionPos.getSectionBBoxPos()), "Chunk {} does not overlap section {}", chunkPos, sectionPos);
chunkPos = chunkPos.convertUpwardsTo((byte) minDetailLevel); // TODO: Handle if chunkData has higher detail than lowestDetail.
recursiveWrite(new DhSectionPos(chunkPos.detail, chunkPos.x, chunkPos.z), chunkData);
recursiveWrite(new DhSectionPos(chunkPos.detailLevel, chunkPos.x, chunkPos.z), chunkData);
}
private void recursiveWrite(DhSectionPos sectionPos, ChunkSizedData chunkData) {
DataMetaFile metaFile = files.get(sectionPos);
@@ -240,7 +240,7 @@ public class DataFileHandler implements IDataSourceProvider {
metaFile.addToWriteQueue(chunkData);
}
if (sectionPos.sectionDetail <= topDetailLevel.get()) {
recursiveWrite(sectionPos.getParent(), chunkData);
recursiveWrite(sectionPos.getParentPos(), chunkData);
}
}
@@ -151,10 +151,10 @@ public class RenderFileHandler implements IRenderSourceProvider {
private void recursive_write(DhSectionPos sectPos, ChunkSizedData chunkData) {
if (!sectPos.getSectionBBoxPos().overlaps(new DhLodPos((byte) (4 + chunkData.dataDetail), chunkData.x, chunkData.z))) return;
if (sectPos.sectionDetail > ColumnRenderSource.SECTION_SIZE_OFFSET) {
recursive_write(sectPos.getChild(0), chunkData);
recursive_write(sectPos.getChild(1), chunkData);
recursive_write(sectPos.getChild(2), chunkData);
recursive_write(sectPos.getChild(3), chunkData);
recursive_write(sectPos.getChildByIndex(0), chunkData);
recursive_write(sectPos.getChildByIndex(1), chunkData);
recursive_write(sectPos.getChildByIndex(2), chunkData);
recursive_write(sectPos.getChildByIndex(3), chunkData);
}
RenderMetaFile metaFile = files.get(sectPos);
if (metaFile != null) { // Fast path: if there is a file for this section, just write to it.
@@ -4,7 +4,6 @@ import com.seibel.lod.core.datatype.full.ChunkSizedData;
import com.seibel.lod.core.pos.DhBlockPos2D;
import com.seibel.lod.core.pos.DhLodPos;
import com.seibel.lod.core.pos.Pos2D;
import com.seibel.lod.core.util.MathUtil;
import com.seibel.lod.core.util.objects.UncheckedInterruptedException;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.pos.DhChunkPos;
@@ -120,7 +119,7 @@ public class GenerationQueue implements Closeable {
// FIXME: This is using up a TONS of time to process!
private final ConcurrentSkipListMap<DhLodPos, TaskGroup> taskGroups = new ConcurrentSkipListMap<>(
(a, b) -> {
if (a.detail != b.detail) return a.detail - b.detail;
if (a.detailLevel != b.detailLevel) return a.detailLevel - b.detailLevel;
int aDist = a.getCenter().toPos2D().chebyshevDist(Pos2D.ZERO);
int bDist = b.getCenter().toPos2D().chebyshevDist(Pos2D.ZERO);
if (aDist != bDist) return aDist - bDist;
@@ -154,13 +153,13 @@ public class GenerationQueue implements Closeable {
}
if (requiredDataDetail > maxDataDetail) requiredDataDetail = maxDataDetail;
LodUtil.assertTrue(pos.detail > requiredDataDetail+4);
byte granularity = (byte) (pos.detail - requiredDataDetail);
LodUtil.assertTrue(pos.detailLevel > requiredDataDetail+4);
byte granularity = (byte) (pos.detailLevel - requiredDataDetail);
if (granularity > maxGranularity) {
// Too big of a chunk. We need to split it up
byte subDetail = (byte) (maxGranularity + requiredDataDetail);
int subPosCount = pos.getWidth(subDetail);
int subPosCount = pos.getBlockWidth(subDetail);
DhLodPos cornerSubPos = pos.getCorner(subDetail);
CompletableFuture<Boolean>[] subFutures = new CompletableFuture[subPosCount*subPosCount];
ArrayList<GenTask> subTasks = new ArrayList<>(subPosCount*subPosCount);
@@ -171,7 +170,7 @@ public class GenerationQueue implements Closeable {
for (int oz = 0; oz < subPosCount; oz++) {
CompletableFuture<Boolean> subFuture = new CompletableFuture<>();
subFutures[i++] = subFuture;
subTasks.add(new GenTask(cornerSubPos.offset(ox, oz), requiredDataDetail, splitTask, subFuture));
subTasks.add(new GenTask(cornerSubPos.addOffset(ox, oz), requiredDataDetail, splitTask, subFuture));
}
}
}
@@ -207,7 +206,7 @@ public class GenerationQueue implements Closeable {
}
private void addAndCombineGroup(TaskGroup target) {
byte granularity = (byte) (target.pos.detail - target.dataDetail);
byte granularity = (byte) (target.pos.detailLevel - target.dataDetail);
LodUtil.assertTrue(granularity <= maxGranularity && granularity >= minGranularity);
LodUtil.assertTrue(!taskGroups.containsKey(target.pos));
@@ -221,7 +220,7 @@ public class GenerationQueue implements Closeable {
if (!group.pos.overlaps(target.pos)) continue;
// We should have already ALWAYS selected the higher granularity.
LodUtil.assertTrue(group.pos.detail < target.pos.detail);
LodUtil.assertTrue(group.pos.detailLevel < target.pos.detailLevel);
groupIter.remove(); // Remove and consume all from that lower granularity request
target.members.addAll(group.members);
}
@@ -231,12 +230,12 @@ public class GenerationQueue implements Closeable {
if (granularity < maxGranularity) { // Obviously, only do so if we aren't at the maxGranularity already
// Check for merging and upping the granularity
DhLodPos corePos = target.pos;
DhLodPos parentPos = corePos.convertUpwardsTo((byte) (corePos.detail+1));
DhLodPos parentPos = corePos.convertUpwardsTo((byte) (corePos.detailLevel +1));
int targetChildId = target.pos.getChildIndexOfParent();
boolean allPassed = true;
for (int i = 0; i < 4; i++) {
if (i == targetChildId) continue;
TaskGroup group = taskGroups.get(parentPos.getChild(i));
TaskGroup group = taskGroups.get(parentPos.getChildByIndex(i));
if (group == null || group.dataDetail != target.dataDetail) {
allPassed = false;
break;
@@ -247,7 +246,7 @@ public class GenerationQueue implements Closeable {
TaskGroup[] groups = new TaskGroup[4];
for (int i = 0; i < 4; i++) {
if (i==targetChildId) groups[i] = target;
else groups[i] = taskGroups.remove(parentPos.getChild(i));
else groups[i] = taskGroups.remove(parentPos.getChildByIndex(i));
LodUtil.assertTrue(groups[i] != null && groups[i].dataDetail == target.dataDetail);
}
@@ -286,7 +285,7 @@ public class GenerationQueue implements Closeable {
GenTask task = looseTasks.poll();
taskProcessed++;
byte taskDataDetail = task.dataDetail;
byte taskGranularity = (byte) (task.pos.detail - taskDataDetail);
byte taskGranularity = (byte) (task.pos.detailLevel - taskDataDetail);
LodUtil.assertTrue(taskGranularity >= 4 && taskGranularity >= minGranularity && taskGranularity <= maxGranularity);
// Check existing one
@@ -401,7 +400,7 @@ public class GenerationQueue implements Closeable {
private void startTaskGroup(InProgressTask task) {
byte dataDetail = task.group.dataDetail;
DhLodPos pos = task.group.pos;
byte granularity = (byte) (pos.detail - dataDetail);
byte granularity = (byte) (pos.detailLevel - dataDetail);
LodUtil.assertTrue(granularity >= minGranularity && granularity <= maxGranularity);
LodUtil.assertTrue(dataDetail >= minDataDetail && dataDetail <= maxDataDetail);
@@ -141,9 +141,9 @@ public class DhClientServerLevel implements IDhClientLevel, IDhServerLevel
RenderState rs = renderState.get();
DhLodPos pos = data.getBBoxLodPos().convertUpwardsTo(FullDataSource.SECTION_SIZE_OFFSET);
if (rs != null) {
rs.renderFileHandler.write(new DhSectionPos(pos.detail, pos.x, pos.z), data);
rs.renderFileHandler.write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
} else {
dataFileHandler.write(new DhSectionPos(pos.detail, pos.x, pos.z), data);
dataFileHandler.write(new DhSectionPos(pos.detailLevel, pos.x, pos.z), data);
}
}
@@ -1,105 +1,165 @@
package com.seibel.lod.core.pos;
import com.seibel.lod.core.util.BitShiftUtil;
import com.seibel.lod.core.util.LodUtil;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
public class DhLodPos implements Comparable<DhLodPos> {
public final byte detail;
public final int x;
public final int z;
public DhLodPos(byte detail, int x, int z) {
this.detail = detail;
this.x = x;
this.z = z;
}
public String toString() {
return "[" + detail + "*" + x + "," + z + "]";
}
public DhLodUnit getX() {
return new DhLodUnit(detail, x);
}
public DhLodUnit getZ() {
return new DhLodUnit(detail, z);
}
public int getWidth() {
return 1 << detail;
}
public int getWidth(byte detail) {
LodUtil.assertTrue(detail <= this.detail);
return 1 << (this.detail - detail);
}
public static int blockWidth(byte detail) {
return 1 << detail;
}
public DhBlockPos2D getCenter() {
return new DhBlockPos2D(getX().toBlock() + (getWidth() >> 1), getZ().toBlock() + (getWidth() >> 1));
}
public DhBlockPos2D getCorner() {
return new DhBlockPos2D(getX().toBlock(), getZ().toBlock());
}
public DhLodPos getCorner(byte newDetail) {
LodUtil.assertTrue(newDetail <= detail);
return new DhLodPos(newDetail, x << (detail-newDetail), z << (detail-newDetail));
}
public DhLodPos convertUpwardsTo(byte newDetail) {
LodUtil.assertTrue(newDetail >= detail);
return new DhLodPos(newDetail, Math.floorDiv(x, 1<<(newDetail-detail)), Math.floorDiv(z, 1<<(newDetail-detail)));
}
public DhLodPos getChild(int child0to3) {
if (child0to3 < 0 || child0to3 > 3) throw new IllegalArgumentException("child0to3 must be between 0 and 3");
if (detail <= 0) throw new IllegalStateException("detail must be greater than 0");
return new DhLodPos((byte) (detail - 1),
x * 2 + (child0to3 & 1),
z * 2 + ((child0to3 & 2) >> 1));
}
public int getChildIndexOfParent() {
return (x & 1) + ((z & 1) << 1);
}
/**
* A MC world position that is relative to a given detail level.
*
* @author Leetom
* @version 2022-11-6
*/
public class DhLodPos implements Comparable<DhLodPos>
{
public final byte detailLevel;
public final int x;
public final int z;
public DhLodPos(byte detailLevel, int x, int z)
{
this.detailLevel = detailLevel;
this.x = x;
this.z = z;
}
public DhLodUnit getX() { return new DhLodUnit(this.detailLevel, this.x); }
public DhLodUnit getZ() { return new DhLodUnit(this.detailLevel, this.z); }
public int getBlockWidth() { return this.getBlockWidth(this.detailLevel); }
public int getBlockWidth(byte detailLevel)
{
LodUtil.assertTrue(detailLevel <= this.detailLevel);
return BitShiftUtil.powerOfTwo(this.detailLevel - detailLevel);
}
public DhBlockPos2D getCenter()
{
return new DhBlockPos2D(
this.getX().toBlockWidth() + BitShiftUtil.half(this.getBlockWidth()),
this.getZ().toBlockWidth() + BitShiftUtil.half(this.getBlockWidth()));
}
public DhBlockPos2D getCorner() { return new DhBlockPos2D(this.getX().toBlockWidth(), this.getZ().toBlockWidth()); }
public DhLodPos getCorner(byte newDetail)
{
LodUtil.assertTrue(newDetail <= this.detailLevel);
return new DhLodPos(newDetail,
this.x * BitShiftUtil.powerOfTwo(this.detailLevel - newDetail),
this.z * BitShiftUtil.powerOfTwo(this.detailLevel - newDetail));
}
public DhLodPos convertUpwardsTo(byte newDetail)
{
LodUtil.assertTrue(newDetail >= this.detailLevel);
return new DhLodPos(newDetail,
Math.floorDiv(this.x, BitShiftUtil.powerOfTwo(newDetail - this.detailLevel)),
Math.floorDiv(this.z, BitShiftUtil.powerOfTwo(newDetail - this.detailLevel)));
}
/**
* Returns the DhLodPos 1 detail level lower <br><br>
*
* Relative child positions returned for each index: <br>
* 0 = (0,0) <br>
* 1 = (1,0) <br>
* 2 = (0,1) <br>
* 3 = (1,1) <br>
*
* @param child0to3 must be an int between 0 and 3
*/
public DhLodPos getChildByIndex(int child0to3) throws IllegalArgumentException, IllegalStateException
{
if (child0to3 < 0 || child0to3 > 3)
throw new IllegalArgumentException("child0to3 must be between 0 and 3");
if (this.detailLevel <= 0)
throw new IllegalStateException("detailLevel must be greater than 0");
return new DhLodPos((byte) (this.detailLevel - 1),
this.x * 2 + (child0to3 & 1),
this.z * 2 + BitShiftUtil.half(child0to3 & 2));
}
/** Returns this position's child index in its parent */
public int getChildIndexOfParent() { return (this.x & 1) + ((this.z & 1) << 1); }
public boolean overlaps(DhLodPos other)
{
if (this.equals(other))
return true;
if (this.detailLevel == other.detailLevel)
return false;
if (this.detailLevel > other.detailLevel)
{
return this.equals(other.convertUpwardsTo(this.detailLevel));
}
else
{
return other.equals(this.convertUpwardsTo(other.detailLevel));
}
}
/** Only valid for DhLodUnits for an equal or greater detail level */
public DhLodPos addLodUnit(DhLodUnit width)
{
if (width.detailLevel < this.detailLevel)
throw new IllegalArgumentException("add called with width.detailLevel < pos detail");
return new DhLodPos(this.detailLevel,
x + width.createFromDetailLevel(this.detailLevel).numberOfLodSectionsWide,
z + width.createFromDetailLevel(this.detailLevel).numberOfLodSectionsWide);
}
/** Equivalent to adding a DhLodUnit with the same detail level as this DhLodPos */
public DhLodPos addOffset(int xOffset, int zOffset) { return new DhLodPos(this.detailLevel, this.x + xOffset, this.z + zOffset); }
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj == null || this.getClass() != obj.getClass())
{
return false;
}
else
{
DhLodPos otherPos = (DhLodPos) obj;
return this.detailLevel == otherPos.detailLevel && this.x == otherPos.x && this.z == otherPos.z;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DhLodPos dhLodPos = (DhLodPos) o;
return detail == dhLodPos.detail && x == dhLodPos.x && z == dhLodPos.z;
}
public int hashCode() { return Objects.hash(detailLevel, x, z); }
@Override
public int hashCode() {
return Objects.hash(detail, x, z);
}
public boolean overlaps(DhLodPos other) {
if (equals(other)) return true;
if (detail == other.detail) return false;
if (detail > other.detail) {
return this.equals(other.convertUpwardsTo(this.detail));
} else {
return other.equals(this.convertUpwardsTo(other.detail));
}
}
public DhLodPos add(DhLodUnit width) {
if (width.detail < detail) throw new IllegalArgumentException("add called with width.detail < pos detail");
return new DhLodPos(detail, x + width.convertTo(detail).value, z + width.convertTo(detail).value);
}
public DhLodPos offset(int ox, int oz) {
return new DhLodPos(detail, x+ox, z+oz);
}
@Override
public int compareTo(@NotNull DhLodPos o) {
return detail != o.detail ? Integer.compare(detail, o.detail) : x != o.x ? Integer.compare(x, o.x) : Integer.compare(z, o.z);
}
public int compareTo(@NotNull DhLodPos obj)
{
if (this.detailLevel != obj.detailLevel)
{
return Integer.compare(this.detailLevel, obj.detailLevel);
}
else if (this.x != obj.x)
{
return Integer.compare(this.x, obj.x);
}
else
{
return Integer.compare(this.z, obj.z);
}
}
@Override
public String toString() { return "[" + this.detailLevel + "*" + this.x + "," + this.z + "]"; }
}
@@ -1,29 +1,54 @@
package com.seibel.lod.core.pos;
public class DhLodUnit {
public final byte detail;
public final int value;
import com.seibel.lod.core.util.BitShiftUtil;
public DhLodUnit(byte detail, int value) {
this.detail = detail;
this.value = value;
}
public int toBlock() {
return value << detail;
}
public static DhLodUnit fromBlock(int block, byte targetDetail) {
return new DhLodUnit(targetDetail, Math.floorDiv(block, 1<<targetDetail));
}
public DhLodUnit convertTo(byte targetDetail) {
if (detail == targetDetail) {
return this;
}
if (detail > targetDetail) { //TODO check if this is correct
return new DhLodUnit(targetDetail, value << (detail - targetDetail));
}
return new DhLodUnit(targetDetail, Math.floorDiv(value, 1<<(targetDetail-detail)));
/**
* Often used to measure LOD widths
*
* @author Leetom
* @version 2022-11-6
*/
public class DhLodUnit
{
/** The detail level of this LOD Unit */
public final byte detailLevel;
/** How many LOD columns wide this LOD Unit represents */
public final int numberOfLodSectionsWide;
public DhLodUnit(byte detailLevel, int numberOfLodSectionsWide)
{
this.detailLevel = detailLevel;
this.numberOfLodSectionsWide = numberOfLodSectionsWide;
}
/** @return the size of this LOD unit in Minecraft blocks */
public int toBlockWidth() { return this.numberOfLodSectionsWide << this.detailLevel; }
/** @return the LOD Unit relative to the given block width and detail level */
public static DhLodUnit fromBlockWidth(int blockWidth, byte targetDetailLevel) { return new DhLodUnit(targetDetailLevel, Math.floorDiv(blockWidth, BitShiftUtil.powerOfTwo(targetDetailLevel))); }
/**
* if the targetDetailLevel and this object's detail are the same,
* this will be returned instead of creating a new object
*/
public DhLodUnit createFromDetailLevel(byte targetDetailLevel)
{
if (this.detailLevel == targetDetailLevel)
{
// no need to create a new object, this one is already the right detail level
return this;
}
else if (this.detailLevel > targetDetailLevel)
{
//TODO check if this is correct
return new DhLodUnit(targetDetailLevel, this.numberOfLodSectionsWide * BitShiftUtil.powerOfTwo(this.detailLevel - targetDetailLevel));
}
else
{
return new DhLodUnit(targetDetailLevel, Math.floorDiv(this.numberOfLodSectionsWide, BitShiftUtil.powerOfTwo(targetDetailLevel - this.detailLevel)));
}
}
}
@@ -1,112 +1,150 @@
package com.seibel.lod.core.pos;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.util.BitShiftUtil;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.MathUtil;
import java.util.function.Consumer;
public class DhSectionPos {
public final byte sectionDetail;
public final int sectionX; // in sectionDetail level grid
public final int sectionZ; // in sectionDetail level grid
public DhSectionPos(byte sectionDetail, int sectionX, int sectionZ) {
this.sectionDetail = sectionDetail;
this.sectionX = sectionX;
this.sectionZ = sectionZ;
}
public DhLodPos getCenter(byte returnDetailLevel) {
LodUtil.assertTrue(returnDetailLevel <= sectionDetail, "returnDetailLevel must be less than sectionDetail");
if (returnDetailLevel == sectionDetail)
return new DhLodPos(sectionDetail, sectionX, sectionZ);
byte offset = (byte) (sectionDetail - returnDetailLevel);
return new DhLodPos(returnDetailLevel, (sectionX << offset)+(1 << (offset -1)),
(sectionZ << offset)+(1 << (offset -1)));
}
public DhLodPos getCorner(byte returnDetailLevel) {
LodUtil.assertTrue(returnDetailLevel <= sectionDetail, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (sectionDetail - returnDetailLevel);
return new DhLodPos(returnDetailLevel, sectionX << offset, sectionZ << offset);
}
public DhLodUnit getWidth(byte returnDetailLevel) {
LodUtil.assertTrue(returnDetailLevel <= sectionDetail, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (sectionDetail - returnDetailLevel);
return new DhLodUnit(sectionDetail, 1 << offset);
}
public DhLodPos getCenter() {
return getCenter((byte)0);
}
public DhLodPos getCorner() {
return getCorner((byte) (sectionDetail-1));
}
public DhLodUnit getWidth() {
return getWidth(sectionDetail);
}
public DhSectionPos getChild(int child0to3){
if (child0to3 < 0 || child0to3 > 3) throw new IllegalArgumentException("child0to3 must be between 0 and 3");
if (sectionDetail <= 0) throw new IllegalStateException("section detail must be greater than 0");
return new DhSectionPos((byte) (sectionDetail - 1),
sectionX * 2 + (child0to3 & 1),
sectionZ * 2 + ((child0to3 & 2) >> 1));
}
public int getChildIndexOfParent() {
return (sectionX & 1) + ((sectionZ & 1) << 1);
}
public void forEachChild(Consumer<DhSectionPos> callback){
for (int i = 0; i < 4; i++) {
callback.accept(getChild(i));
}
}
public DhSectionPos getParent(){
return new DhSectionPos((byte) (sectionDetail + 1), sectionX >> 1, sectionZ >> 1);
}
public DhSectionPos getAdjacent(ELodDirection dir) {
return new DhSectionPos(sectionDetail, sectionX + dir.getNormal().x, sectionZ + dir.getNormal().z);
}
public DhLodPos getSectionBBoxPos() {
return new DhLodPos(sectionDetail, sectionX, sectionZ);
}
/**
* NOTE: This does not consider yOffset!
*/
public boolean overlaps(DhSectionPos other){
return getSectionBBoxPos().overlaps(other.getSectionBBoxPos());
}
@Override
public String toString() {
return "{" + sectionDetail +
"*" + sectionX +
"," + sectionZ +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DhSectionPos that = (DhSectionPos) o;
return sectionDetail == that.sectionDetail &&
sectionX == that.sectionX &&
sectionZ == that.sectionZ;
}
@Override
public int hashCode() {
return Integer.hashCode(sectionDetail) ^
Integer.hashCode(sectionX) ^
Integer.hashCode(sectionZ);
}
// Serialize() is different from toString() as this requires it to NEVER be changed, and should be in a short format
public String serialize() {
return "[" + sectionDetail + ',' + sectionX + ',' + sectionZ + ']';
}
/**
* The position object used to define LOD objects in the quad trees.
*
* @author Leetom
* @version 2022-11-6
*/
public class DhSectionPos
{
public final byte sectionDetail;
/** in sectionDetail level grid */
public final int sectionX;
/** in sectionDetail level grid */
public final int sectionZ;
public DhSectionPos(byte sectionDetail, int sectionX, int sectionZ)
{
this.sectionDetail = sectionDetail;
this.sectionX = sectionX;
this.sectionZ = sectionZ;
}
/** Returns the center for the highest detail level (0) */
public DhLodPos getCenter() { return this.getCenter((byte) 0); }
public DhLodPos getCenter(byte returnDetailLevel)
{
LodUtil.assertTrue(returnDetailLevel <= this.sectionDetail, "returnDetailLevel must be less than sectionDetail");
if (returnDetailLevel == this.sectionDetail)
return new DhLodPos(this.sectionDetail, this.sectionX, this.sectionZ);
byte offset = (byte) (this.sectionDetail - returnDetailLevel);
return new DhLodPos(returnDetailLevel,
(this.sectionX * BitShiftUtil.powerOfTwo(offset)) + BitShiftUtil.powerOfTwo(offset - 1),
(this.sectionZ * BitShiftUtil.powerOfTwo(offset)) + BitShiftUtil.powerOfTwo(offset - 1));
}
/** @return the corner with the smallest X and Z coordinate */
public DhLodPos getCorner() { return this.getCorner((byte) (this.sectionDetail - 1)); }
/** @return the corner with the smallest X and Z coordinate */
public DhLodPos getCorner(byte returnDetailLevel)
{
LodUtil.assertTrue(returnDetailLevel <= this.sectionDetail, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (this.sectionDetail - returnDetailLevel);
return new DhLodPos(returnDetailLevel,
this.sectionX * BitShiftUtil.powerOfTwo(offset),
this.sectionZ * BitShiftUtil.powerOfTwo(offset));
}
public DhLodUnit getWidth() { return this.getWidth(this.sectionDetail); }
public DhLodUnit getWidth(byte returnDetailLevel)
{
LodUtil.assertTrue(returnDetailLevel <= this.sectionDetail, "returnDetailLevel must be less than sectionDetail");
byte offset = (byte) (this.sectionDetail - returnDetailLevel);
return new DhLodUnit(this.sectionDetail, BitShiftUtil.powerOfTwo(offset));
}
/**
* Returns the DhLodPos 1 detail level lower <br><br>
*
* Relative child positions returned for each index: <br>
* 0 = (0,0) <br>
* 1 = (1,0) <br>
* 2 = (0,1) <br>
* 3 = (1,1) <br>
*
* @param child0to3 must be an int between 0 and 3
*/
public DhSectionPos getChildByIndex(int child0to3) throws IllegalArgumentException, IllegalStateException
{
if (child0to3 < 0 || child0to3 > 3)
throw new IllegalArgumentException("child0to3 must be between 0 and 3");
if (this.sectionDetail <= 0)
throw new IllegalStateException("section detail must be greater than 0");
return new DhSectionPos((byte) (this.sectionDetail - 1),
this.sectionX * 2 + (child0to3 & 1),
this.sectionZ * 2 + BitShiftUtil.half(child0to3 & 2));
}
/** Returns this position's child index in its parent */
public int getChildIndexOfParent() { return (this.sectionX & 1) + ((this.sectionZ & 1) << 1); }
/** Applies the given consumer to all 4 of this position's children. */
public void forEachChild(Consumer<DhSectionPos> callback)
{
for (int i = 0; i < 4; i++)
{
callback.accept(this.getChildByIndex(i));
}
}
public DhSectionPos getParentPos() { return new DhSectionPos((byte) (this.sectionDetail + 1), BitShiftUtil.half(this.sectionX), BitShiftUtil.half(this.sectionZ)); }
public DhSectionPos getAdjacentPos(ELodDirection dir)
{
return new DhSectionPos(this.sectionDetail,
this.sectionX + dir.getNormal().x,
this.sectionZ + dir.getNormal().z);
}
public DhLodPos getSectionBBoxPos() { return new DhLodPos(this.sectionDetail, this.sectionX, this.sectionZ); }
/** NOTE: This does not consider yOffset! */
public boolean overlaps(DhSectionPos other) { return this.getSectionBBoxPos().overlaps(other.getSectionBBoxPos()); }
/** Serialize() is different from toString() as it must NEVER be changed, and should be in a short format */
public String serialize() { return "[" + this.sectionDetail + ',' + this.sectionX + ',' + this.sectionZ + ']'; }
@Override
public String toString() { return "{" + this.sectionDetail + "*" + this.sectionX + "," + this.sectionZ + "}"; }
@Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null || this.getClass() != obj.getClass())
return false;
DhSectionPos that = (DhSectionPos) obj;
return this.sectionDetail == that.sectionDetail &&
this.sectionX == that.sectionX &&
this.sectionZ == that.sectionZ;
}
@Override
public int hashCode()
{
return Integer.hashCode(this.sectionDetail) ^ // XOR
Integer.hashCode(this.sectionX) ^ // XOR
Integer.hashCode(this.sectionZ);
}
}
@@ -181,7 +181,7 @@ public class LodQuadTree implements AutoCloseable {
* @return the parent LodSection
*/
public LodRenderSection getParentSection(DhSectionPos pos) {
return getSection(pos.getParent());
return getSection(pos.getParentPos());
}
/**
@@ -192,7 +192,7 @@ public class LodQuadTree implements AutoCloseable {
* @return one of the child LodSection
*/
public LodRenderSection getChildSection(DhSectionPos pos, int child0to3) {
return getSection(pos.getChild(child0to3));
return getSection(pos.getChildByIndex(child0to3));
}
private LodRenderSection _set(MovableGridRingList<LodRenderSection> list, int x, int z, LodRenderSection t) {
@@ -280,15 +280,15 @@ public class LodQuadTree implements AutoCloseable {
if (parentRingList != null) {
LodRenderSection parent = _get(parentRingList, pos.x >> 1, pos.y >> 1);
if (parent == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", section.pos, section.pos.getParent());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(section.pos.getParent()));
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", section.pos, section.pos.getParentPos());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(section.pos.getParentPos()));
parent.childCount++;
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", section.pos.getParent(), parent.childCount);
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", section.pos.getParentPos(), parent.childCount);
}
LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0);
}
for (byte i = 0; i < 4; i++) {
DhSectionPos childPos = section.pos.getChild(i);
DhSectionPos childPos = section.pos.getChildByIndex(i);
LodUtil.assertTrue(childRingList != null);
LodRenderSection child = _get(childRingList, childPos.sectionX, childPos.sectionZ);
if (child == null) {
@@ -338,11 +338,11 @@ public class LodQuadTree implements AutoCloseable {
LodUtil.assertTrue(parentRingList != null);
LodRenderSection parent = _get(parentRingList, pos.x >> 1, pos.y >> 1);
if (parent == null) {
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", sectPos, sectPos.getParent());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(sectPos.getParent()));
if (SUPER_VERBOSE_LOGGING) LOGGER.info("sect {} missing parent. Creating at {}", sectPos, sectPos.getParentPos());
parent = _set(parentRingList, pos.x >> 1, pos.y >> 1, new LodRenderSection(sectPos.getParentPos()));
}
parent.childCount++;
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", sectPos.getParent(), parent.childCount);
if (SUPER_VERBOSE_LOGGING) LOGGER.info("parent sect {} now has {} childs.", sectPos.getParentPos(), parent.childCount);
}
}
}
@@ -156,7 +156,7 @@ public class RenderBufferHandler {
if (children == null) {
RenderBufferNode[] childs = new RenderBufferNode[4];
for (int i = 0; i < 4; i++) {
childs[i] = new RenderBufferNode(pos.getChild(i));
childs[i] = new RenderBufferNode(pos.getChildByIndex(i));
}
children = childs;
}