diff --git a/api/src/main/java/com/seibel/lod/core/util/BitShiftUtil.java b/api/src/main/java/com/seibel/lod/core/util/BitShiftUtil.java new file mode 100644 index 000000000..4dde773e0 --- /dev/null +++ b/api/src/main/java/com/seibel/lod/core/util/BitShiftUtil.java @@ -0,0 +1,35 @@ +package com.seibel.lod.core.util; + +/** + * A list of helper methods to make code easier to read.
+ * Specifically written because bit shifts short circuit James' brain. + * + * @author James Seibel + * @version 2022-11-6 + */ +public class BitShiftUtil +{ + /** + * Equivalent to:
+ * 1 << value,
+ * 2^value,
+ * Math.pow(2, value)

+ * + * 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.

+ * + * Can also be used to replace bit shifts in the format:
+ * multiplier << value;
+ * multiplier * powerOfTwo(value); + */ + public static int powerOfTwo(int value) { return 1 << value; } + + /** + * Equivalent to:
+ * value >> 1,
+ * value / 2

+ * + * Note: value / 2 isn't identical for negative values + */ + public static int half(int value) { return value >> 1; } + +} diff --git a/api/src/main/java/com/seibel/lod/core/util/MathUtil.java b/api/src/main/java/com/seibel/lod/core/util/MathUtil.java index d232be9fc..1f4df9af3 100644 --- a/api/src/main/java/com/seibel/lod/core/util/MathUtil.java +++ b/api/src/main/java/com/seibel/lod/core/util/MathUtil.java @@ -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; } } diff --git a/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java b/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java index b8d855c09..24eb191f2 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/column/ColumnRenderSource.java @@ -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++) diff --git a/core/src/main/java/com/seibel/lod/core/datatype/full/SparseDataSource.java b/core/src/main/java/com/seibel/lod/core/datatype/full/SparseDataSource.java index 6f6328399..2ea7b9e31 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/full/SparseDataSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/full/SparseDataSource.java @@ -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; diff --git a/core/src/main/java/com/seibel/lod/core/datatype/full/SpottyDataSource.java b/core/src/main/java/com/seibel/lod/core/datatype/full/SpottyDataSource.java index 6c0377ae9..af6782999 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/full/SpottyDataSource.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/full/SpottyDataSource.java @@ -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()); diff --git a/core/src/main/java/com/seibel/lod/core/datatype/transform/FullToColumnTransformer.java b/core/src/main/java/com/seibel/lod/core/datatype/transform/FullToColumnTransformer.java index 82f2390a4..2ec83b920 100644 --- a/core/src/main/java/com/seibel/lod/core/datatype/transform/FullToColumnTransformer.java +++ b/core/src/main/java/com/seibel/lod/core/datatype/transform/FullToColumnTransformer.java @@ -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); diff --git a/core/src/main/java/com/seibel/lod/core/file/datafile/DataFileHandler.java b/core/src/main/java/com/seibel/lod/core/file/datafile/DataFileHandler.java index 34142d9fe..f08a20f53 100644 --- a/core/src/main/java/com/seibel/lod/core/file/datafile/DataFileHandler.java +++ b/core/src/main/java/com/seibel/lod/core/file/datafile/DataFileHandler.java @@ -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 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. diff --git a/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java b/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java index 63d685cef..cf57342b1 100644 --- a/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java +++ b/core/src/main/java/com/seibel/lod/core/generation/GenerationQueue.java @@ -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 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[] subFutures = new CompletableFuture[subPosCount*subPosCount]; ArrayList subTasks = new ArrayList<>(subPosCount*subPosCount); @@ -171,7 +170,7 @@ public class GenerationQueue implements Closeable { for (int oz = 0; oz < subPosCount; oz++) { CompletableFuture 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); diff --git a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java index b293edaf5..039313450 100644 --- a/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java +++ b/core/src/main/java/com/seibel/lod/core/level/DhClientServerLevel.java @@ -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); } } diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java index 3591c2e1e..966e93988 100644 --- a/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java +++ b/core/src/main/java/com/seibel/lod/core/pos/DhLodPos.java @@ -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 { - 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 +{ + 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

+ * + * Relative child positions returned for each index:
+ * 0 = (0,0)
+ * 1 = (1,0)
+ * 2 = (0,1)
+ * 3 = (1,1)
+ * + * @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 + "]"; } + } diff --git a/core/src/main/java/com/seibel/lod/core/pos/DhLodUnit.java b/core/src/main/java/com/seibel/lod/core/pos/DhLodUnit.java index 2b6fe2099..b7dd65cd8 100644 --- a/core/src/main/java/com/seibel/lod/core/pos/DhLodUnit.java +++ b/core/src/main/java/com/seibel/lod/core/pos/DhLodUnit.java @@ -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) { //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))); + } + } + } 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 26fb6fbc9..6bf0b412f 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 @@ -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 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

+ * + * Relative child positions returned for each index:
+ * 0 = (0,0)
+ * 1 = (1,0)
+ * 2 = (0,1)
+ * 3 = (1,1)
+ * + * @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 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); + } + } diff --git a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java index ea8402dc3..7684b7d36 100644 --- a/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/lod/core/render/LodQuadTree.java @@ -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 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); } } } diff --git a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java index b4e9f9b13..578d906eb 100644 --- a/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java +++ b/core/src/main/java/com/seibel/lod/core/render/RenderBufferHandler.java @@ -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; }