From 8e296f98d7f136ce2d60556232ca2411b0fd415a Mon Sep 17 00:00:00 2001 From: TomTheFurry Date: Fri, 13 May 2022 14:10:33 +0800 Subject: [PATCH] Complete the dynamic section data detail offset quad tree. (Untested) --- .../lod/core/objects/a7/LodQuadTree.java | 339 ++++++++++++------ .../a7/datatype/column/ColumnDatatype.java | 18 +- .../a7/datatype/full/FullDatatype.java | 4 + .../lod/core/objects/a7/pos/DhLodPos.java | 31 ++ .../lod/core/objects/a7/pos/DhSectionPos.java | 90 ++--- .../a7/render/RenderBufferHandler.java | 4 +- .../com/seibel/lod/core/util/LodUtil.java | 3 + 7 files changed, 323 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java diff --git a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java index 9ddb174a7..b8eaca46b 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/LodQuadTree.java @@ -1,6 +1,7 @@ package com.seibel.lod.core.objects.a7; import com.seibel.lod.core.objects.a7.datatype.column.ColumnDatatype; +import com.seibel.lod.core.objects.a7.datatype.full.FullDatatype; import com.seibel.lod.core.objects.a7.pos.DhBlockPos2D; import com.seibel.lod.core.objects.a7.pos.DhSectionPos; import com.seibel.lod.core.util.DetailDistanceUtil; @@ -9,7 +10,6 @@ import com.seibel.lod.core.util.gridList.MovableGridRingList; import java.util.ArrayList; import java.util.Collections; -import java.util.List; // QuadTree built from several layers of 2d ring buffers @@ -33,13 +33,14 @@ public abstract class LodQuadTree { */ - public final int numbersOfDetailLevels; + public final byte numbersOfSectionLevels; + public final byte startingSectionLevel; private final MovableGridRingList[] ringLists; static class ContainerTypeConfigEntry { final Class containerType; - final int levelOffset; - public ContainerTypeConfigEntry(Class containerType, int levelOffset) { + final byte levelOffset; + public ContainerTypeConfigEntry(Class containerType, byte levelOffset) { this.containerType = containerType; this.levelOffset = levelOffset; } @@ -47,25 +48,52 @@ public abstract class LodQuadTree { static final ArrayList containerTypeConfig = new ArrayList<>(); static { + //TODO: Make this dynamic Collections.addAll(containerTypeConfig, null, null, //1 null, //2 null, //3 - new ContainerTypeConfigEntry(FullDatatype.class, 4), //4 -> 0 - null, //5 breaks down to 4 - null, //6 breaks down to 4 + new ContainerTypeConfigEntry(FullDatatype.class, (byte) 4), //4 -> 0 + null, //5 force breaks down to 4 -> 0 + null, //6 force breaks down to 4 -> 0 new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET), //7 -> 1 - new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET), //8 -> 2 - new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET), //9 -> 3 - new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET), //10 -> 4 - new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET) //11 -> 5... + new ContainerTypeConfigEntry(ColumnDatatype.class, ColumnDatatype.SECTION_SIZE_OFFSET) //8 -> 2 + // ... And same onwards ); } - final ContainerTypeConfigEntry[] containerTypeConfigs; + static class SectionDetailLayer { + final byte targetDataDetail; + final Class containerType; + public SectionDetailLayer(byte targetDataDetail, Class containerType) { + this.targetDataDetail = targetDataDetail; + this.containerType = containerType; + } + } - //public static final + static void assertContainerTypeConfigCorrect() { + boolean isInFront = true; + for (int i = 0; i < containerTypeConfig.size(); i++) { + if (containerTypeConfig.get(i) == null) continue; + isInFront = false; + ContainerTypeConfigEntry entry = containerTypeConfig.get(i); + if (i - entry.levelOffset < 0) { + throw new RuntimeException("ContainerTypeConfigEntry " + i + " has a levelOffset of " + + entry.levelOffset + " which makes the dataDetail be " + (i - entry.levelOffset) + "," + + " which is less than 0!"); + } + if (entry.levelOffset < 0) { + throw new RuntimeException("ContainerTypeConfigEntry " + i + " has a levelOffset of " + + entry.levelOffset + " which is less than 0!"); + } + } + if (containerTypeConfig.get(containerTypeConfig.size()-1) == null) { + throw new RuntimeException("The last ContainerTypeConfigEntry is null, which is invalid!"); + } + } + + final SectionDetailLayer[] sectionDetailLayers; /** * Constructor of the quadTree @@ -74,47 +102,82 @@ public abstract class LodQuadTree { * @param initialPlayerZ player z coordinate */ public LodQuadTree(int viewDistance, int initialPlayerX, int initialPlayerZ) { - byte maxDetailLevel = DetailDistanceUtil.getDetailLevelFromDistance(viewDistance*Math.sqrt(2)); - ContainerTypeConfigEntry finalEntry = null; - byte topSectionLevel = 0; - for (; topSectionLevel < containerTypeConfig.size(); topSectionLevel++) { - if (containerTypeConfig.get(topSectionLevel) == null) continue; - finalEntry = containerTypeConfig.get(topSectionLevel); - if (topSectionLevel - finalEntry.levelOffset >= maxDetailLevel) break; - } - if (finalEntry == null) throw new RuntimeException("No container type found!"); - if (topSectionLevel == containerTypeConfig.size()) - topSectionLevel = (byte) (maxDetailLevel - finalEntry.levelOffset); - numbersOfDetailLevels = topSectionLevel + 1; - containerTypeConfigs = new ContainerTypeConfigEntry[numbersOfDetailLevels]; - finalEntry = null; - for (byte i = 0; i < numbersOfDetailLevels; i++) { - if (containerTypeConfig.get(i) == null) continue; //TODO: Next here + assertContainerTypeConfigCorrect(); - } - - ringLists = new MovableGridRingList[numbersOfDetailLevels]; - int size; - for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { - int distance = getFurthestPoint(detailLevel); - ContainerTypeConfigEntry configEntry = containerTypeConfig.get(detailLevel); - if (configEntry == null) { - continue; + { // Calculate the max section detail + byte maxDetailLevel = getMaxDetailInRange(viewDistance * Math.sqrt(2)); + ContainerTypeConfigEntry finalEntry = null; + byte topSectionLevel = 0; + byte firstLevel = -1; + for (; topSectionLevel < containerTypeConfig.size(); topSectionLevel++) { + if (containerTypeConfig.get(topSectionLevel) == null) continue; + finalEntry = containerTypeConfig.get(topSectionLevel); + if (firstLevel == -1) firstLevel = topSectionLevel; + if (topSectionLevel - finalEntry.levelOffset >= maxDetailLevel) break; } + if (finalEntry == null) throw new RuntimeException("No container type found!"); + if (topSectionLevel == containerTypeConfig.size()) + topSectionLevel = (byte) (maxDetailLevel - finalEntry.levelOffset); + numbersOfSectionLevels = (byte) (topSectionLevel + 1); + startingSectionLevel = firstLevel; + sectionDetailLayers = new SectionDetailLayer[numbersOfSectionLevels - startingSectionLevel]; + ringLists = new MovableGridRingList[numbersOfSectionLevels - startingSectionLevel]; + } - int sectionCount = LodUtil.ceilDiv(distance, DhSectionPos.getWidth(detailLevel).toBlock()) + 1; // +1 for the border during move - ringLists[detailLevel] = new MovableGridRingList(sectionCount, - initialPlayerX >> detailLevel, initialPlayerZ >> detailLevel); + { // Fill in the sectionDetailLayers info and construct the ringLists + byte lastNonNullEntry = -1; + for (byte i = startingSectionLevel; i < numbersOfSectionLevels; i++) { + byte targetDataDetail; + Class containerType; + + if (i < containerTypeConfig.size()) { + if (containerTypeConfig.get(i) == null) { + if (lastNonNullEntry == -1) continue; + targetDataDetail = sectionDetailLayers[lastNonNullEntry].targetDataDetail; + containerType = null; + } else { + lastNonNullEntry = i; + ContainerTypeConfigEntry entry = containerTypeConfig.get(i); + targetDataDetail = (byte) (i - entry.levelOffset); + containerType = entry.containerType; + } + } else { + LodUtil.assertTrue(containerTypeConfig.get(containerTypeConfig.size() - 1) != null, + "The last entry must not be null!"); + ContainerTypeConfigEntry entry = containerTypeConfig.get(containerTypeConfig.size() - 1); + targetDataDetail = (byte) (i - entry.levelOffset); + containerType = entry.containerType; + } + + LodUtil.assertTrue(targetDataDetail >= 0, "dataDetail must be >= 0!"); + int maxDist = getFurthestDistance(targetDataDetail); + int halfSize = LodUtil.ceilDiv(maxDist, (1 << i) + 2); + sectionDetailLayers[i - startingSectionLevel] = new SectionDetailLayer(targetDataDetail, containerType); + ringLists[i - startingSectionLevel] = new MovableGridRingList(halfSize, + initialPlayerX >> i, initialPlayerZ >> i); + } } } - + + /** * This method return the LodSection given the Section Pos - * @param pos the section positon + * @param pos the section positon. * @return the LodSection */ public LodSection getSection(DhSectionPos pos) { - return getSection(pos.detail, pos.x, pos.z); + return getSection(pos.sectionDetail, pos.sectionX, pos.sectionZ); + } + + public byte getFirstSectionDetailFromDataDetail(byte dataDetail) { + if (dataDetail <= startingSectionLevel) return startingSectionLevel; + for (byte i = 0; i < sectionDetailLayers.length; i++) { + if (sectionDetailLayers[i].targetDataDetail >= dataDetail) return (byte) (i + startingSectionLevel); + } + throw new RuntimeException("No section detail for dataDetail " + dataDetail+ " found!"); + } + public byte getDataDetail(byte sectionDetail) { + return sectionDetailLayers[sectionDetail - startingSectionLevel].targetDataDetail; } /** @@ -124,15 +187,19 @@ public abstract class LodQuadTree { * @return the RingList */ public MovableGridRingList getRingList(byte detailLevel) { - return ringLists[detailLevel]; + return ringLists[detailLevel - startingSectionLevel]; } /** * This method returns the number of detail levels in the quadTree * @return the number of detail levels */ - public int getNumbersOfDetailLevels() { - return numbersOfDetailLevels; + public byte getNumbersOfSectionLevels() { + return numbersOfSectionLevels; + } + + public byte getStartingSectionLevel() { + return startingSectionLevel; } /** @@ -143,7 +210,7 @@ public abstract class LodQuadTree { * @return the LodSection */ public LodSection getSection(byte detailLevel, int x, int z) { - return ringLists[detailLevel].get(x, z); + return ringLists[detailLevel - startingSectionLevel].get(x, z); } @@ -159,18 +226,32 @@ public abstract class LodQuadTree { playerPos.dist(sectionPos.getCenter().getCenter())); } + /** + * The method will return the highest detail level in a circle around the center + * Override this method if you want to use a different algorithm + * Note: the returned distance should always be the ceiling estimation of the distance + * //TODO: Make this input a bbox or a circle or something.... + * @param distance the circle radius + * @return the highest detail level in the circle + */ + public byte getMaxDetailInRange(double distance) { + return DetailDistanceUtil.getDetailLevelFromDistance(distance); + } + /** * The method will return the furthest distance to the center for the given detail level * Override this method if you want to use a different algorithm * Note: the returned distance should always be the ceiling estimation of the distance + * //TODO: Make this return a bbox instead of a distance in circle * @param detailLevel detail level * @return the furthest distance to the center, in blocks */ - public int getFurthestPoint(byte detailLevel) { + public int getFurthestDistance(byte detailLevel) { return (int)Math.ceil(DetailDistanceUtil.getDrawDistanceFromDetail(detailLevel)); } public abstract RenderDataProvider getRenderDataProvider(); + /** * Given a section pos at level n this method returns the parent section at level n+1 @@ -192,46 +273,59 @@ public abstract class LodQuadTree { return getSection(pos.getChild(child0to3)); } - - /** * This function update the quadTree based on the playerPos and the current game configs (static and global) * @param playerPos the reference position for the player */ public void tick(DhBlockPos2D playerPos) { - for (int detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { - ringLists[detailLevel].move(playerPos.x >> detailLevel, playerPos.z >> detailLevel, + for (int sectLevel = startingSectionLevel; sectLevel < numbersOfSectionLevels; sectLevel++) { + ringLists[sectLevel - startingSectionLevel] + .move(playerPos.x >> sectLevel, playerPos.z >> sectLevel, LodSection::dispose); } + + + + // First tick pass: update all sections' childCount from bottom level to top level. Step: - // If as detail 0 && section != null: + // If sectLevel is bottom && section != null: // - set childCount to 0 - // If section != null && child != 0: + // If section != null && child != 0: //TODO: Should I move this createChild steps to Second tick pass? // - // Section will be in the unloaded state. // - create parent if it doesn't exist, with childCount = 1 // - for each child: - // - if null, create new with childCount = 0 + // - if null, create new with childCount = 0 (force load due to neighboring issues) // - else if childCount == -1, set childCount = 0 (rescue it) // - set childCount to 4 // Else: // - Calculate targetLevel at that section - // - If targetLevel > detail && section != null: - // - Parent's childCount-- (Assert parent != null && childCount > 0 before decrementing) - // - // Note that this doesn't necessarily mean this section will be freed as it may be rescued later - // due to neighboring quadrants not able to be freed (they pass targetLevel checks or has children) - // - set childCount to -1 (Signal that this section will be freed if not rescued) - // - If targetLevel <= detail && section == null: - // - Parent's childCount++ (Create parent if needed) - for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { - final MovableGridRingList ringList = ringLists[detailLevel]; + // - If sectLevel == numberOfSectionLevels - 1: + // - // Section is the top level. + // - If targetLevel > dataLevel@sectLevel && section != null: + // - set childCount to -1 (Signal that section is to be freed) (this prob not be rescued as it is the top level) + // - If targetLevel <= dataLevel@sectLevel && section == null: (direct use the current sectLevel's dataLevel) + // - create new section with childCount = 0 + // - Else: + // - // Section is not the top level. So we also need to consider the parent. + // - If targetLevel >= dataLevel@(sectLevel+1) && section != null: (use the next level's dataLevel) + // - Parent's childCount-- (Assert parent != null && childCount > 0 before decrementing) + // - // Note that this doesn't necessarily mean this section will be freed as it may be rescued later + // due to neighboring quadrants not able to be freed (they pass targetLevel checks or has children) + // or due to parent's layer is in the Always Cascade mode. (containerType == null) + // - set childCount to -1 (Signal that this section will be freed if not rescued) + // - If targetLevel < dataLevel@(sectLevel+1) && section == null: (use the next level's dataLevel) + // - create new section with childCount = 0 + // - Parent's childCount++ (Create parent if needed) + for (byte sectLevel = startingSectionLevel; sectLevel < numbersOfSectionLevels; sectLevel++) { + final MovableGridRingList ringList = ringLists[sectLevel - startingSectionLevel]; final MovableGridRingList childRingList = - detailLevel == 0 ? null : ringLists[detailLevel - 1]; + sectLevel == startingSectionLevel ? null : ringLists[sectLevel - startingSectionLevel - 1]; final MovableGridRingList parentRingList = - detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; - final byte detail = detailLevel; + sectLevel == numbersOfSectionLevels - 1 ? null : ringLists[sectLevel - startingSectionLevel + 1]; + final byte f_sectLevel = sectLevel; ringList.forEachPosOrdered((section, pos) -> { - if (detail == 0 && section != null) { + if (f_sectLevel == 0 && section != null) { section.childCount = 0; } if (section != null && section.childCount != 0) { @@ -246,9 +340,9 @@ public abstract class LodQuadTree { LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0); for (byte i = 0; i < 4; i++) { DhSectionPos childPos = section.pos.getChild(i); - LodSection child = ringList.get(childPos.x, childPos.z); + LodSection child = childRingList.get(childPos.sectionX, childPos.sectionZ); if (child == null) { - child = ringList.setChained(childPos.x, childPos.z, + child = childRingList.setChained(childPos.sectionX, childPos.sectionZ, new LodSection(childPos, getRenderDataProvider())); child.childCount = 0; } else if (child.childCount == -1) { @@ -257,36 +351,53 @@ public abstract class LodQuadTree { } section.childCount = 4; } else { - DhSectionPos sectPos = section != null ? section.pos : new DhSectionPos(detail, pos.x, pos.y); + DhSectionPos sectPos = section != null ? section.pos : new DhSectionPos(f_sectLevel, pos.x, pos.y); byte targetLevel = calculateExpectedDetailLevel(playerPos, sectPos); - if (targetLevel > detail && section != null) { - LodUtil.assertTrue(parentRingList != null); - LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); - LodUtil.assertTrue(parent != null); - LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0); - parent.childCount--; - section.childCount = -1; - } else if (targetLevel <= detail && section == null) { - LodUtil.assertTrue(parentRingList != null); - LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); - if (parent == null) { - parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, - new LodSection(sectPos.getParent(), getRenderDataProvider())); + if (f_sectLevel == numbersOfSectionLevels -1) { + // Section is in the top level. + if (targetLevel > getDataDetail(f_sectLevel) && section != null) { + section.childCount = -1; + } + if (targetLevel <= getDataDetail(f_sectLevel) && section == null) { + section = ringList.setChained(pos.x, pos.y, + new LodSection(sectPos, getRenderDataProvider())); + } + } else { + // Section is not the top level. So we also need to consider the parent. + if (targetLevel >= getDataDetail((byte) (f_sectLevel+1)) && section != null) { + LodUtil.assertTrue(parentRingList != null); + LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); + LodUtil.assertTrue(parent != null); + LodUtil.assertTrue(parent.childCount <= 4 && parent.childCount > 0); + parent.childCount--; + section.childCount = -1; + } + if (targetLevel < getDataDetail((byte) (f_sectLevel+1)) && section == null) { + section = ringList.setChained(pos.x, pos.y, + new LodSection(sectPos, getRenderDataProvider())); + LodUtil.assertTrue(parentRingList != null); + LodSection parent = parentRingList.get(pos.x >> 1, pos.y >> 1); + if (parent == null) { + parent = parentRingList.setChained(pos.x >> 1, pos.y >> 1, + new LodSection(sectPos.getParent(), getRenderDataProvider())); + } + parent.childCount++; } - parent.childCount++; } } // Final quick assert to insure section pos is correct. if (section != null) { - LodUtil.assertTrue(section.pos.detail == detail); - LodUtil.assertTrue(section.pos.x == pos.x); - LodUtil.assertTrue(section.pos.z == pos.y); + LodUtil.assertTrue(section.pos.sectionDetail == f_sectLevel); + LodUtil.assertTrue(section.pos.sectionX == pos.x); + LodUtil.assertTrue(section.pos.sectionZ == pos.y); } }); } - // Second tick pass: load and unload sections (and can also be used to assert everything is working). Step: - // // ===Assertion steps=== + // Second tick pass: + // Cascade the layers that is in Always Cascade Mode from top to bottom. (layer's containerType == null) + // At the same time, load and unload sections (and can also be used to assert everything is working). Step: + // ===Assertion steps=== // assert childCount == 4 || childCount == 0 || childCount == -1 // if childCount == 4 assert all children exist // if childCount == 0 assert all children are null @@ -299,26 +410,50 @@ public abstract class LodQuadTree { // if childCount == -1: // (section can be loaded or unloaded, due to fast movement) // - set this section to null (TODO: Is this needed to be first or last or don't matter for concurrency?) // - If loaded unload section - for (byte detailLevel = 0; detailLevel < numbersOfDetailLevels; detailLevel++) { - final MovableGridRingList ringList = ringLists[detailLevel]; + for (byte sectLevel = (byte) (numbersOfSectionLevels - 1); sectLevel >= startingSectionLevel; sectLevel--) { + final MovableGridRingList ringList = ringLists[sectLevel - startingSectionLevel]; final MovableGridRingList childRingList = - detailLevel == 0 ? null : ringLists[detailLevel - 1]; - final MovableGridRingList parentRingList = - detailLevel == numbersOfDetailLevels - 1 ? null : ringLists[detailLevel + 1]; + sectLevel == startingSectionLevel ? null : ringLists[sectLevel - startingSectionLevel - 1]; + final boolean doCacsade = sectionDetailLayers[sectLevel].containerType == null; + ringList.forEachPosOrdered((section, pos) -> { + if (section == null) return; + + // Cascade layers + if (doCacsade && section.childCount == 0) { + // Create childs to cascade the layer. + for (byte i = 0; i < 4; i++) { + DhSectionPos childPos = section.pos.getChild(i); + LodSection child = childRingList.get(childPos.sectionX, childPos.sectionZ); + if (child == null) { + child = childRingList.setChained(childPos.sectionX, childPos.sectionZ, + new LodSection(childPos, getRenderDataProvider())); + child.childCount = 0; + } else { + LodUtil.assertTrue(child.childCount == -1, + "Self has child count 0 but an existing child's child count != -1!"); + child.childCount = 0; + } + } + section.childCount = 4; + } + + // Assertion steps LodUtil.assertTrue(section.childCount == 4 || section.childCount == 0 || section.childCount == -1); if (section.childCount == 4) LodUtil.assertTrue( getChildSection(section.pos, 0) != null && - getChildSection(section.pos, 1) != null && - getChildSection(section.pos, 2) != null && - getChildSection(section.pos, 3) != null); + getChildSection(section.pos, 1) != null && + getChildSection(section.pos, 2) != null && + getChildSection(section.pos, 3) != null); if (section.childCount == 0) LodUtil.assertTrue( getChildSection(section.pos, 0) == null && - getChildSection(section.pos, 1) == null && - getChildSection(section.pos, 2) == null && - getChildSection(section.pos, 3) == null); + getChildSection(section.pos, 1) == null && + getChildSection(section.pos, 2) == null && + getChildSection(section.pos, 3) == null); if (section.childCount == -1) LodUtil.assertTrue( getParentSection(section.pos).childCount == 0); + + // Load/unload section if (section.childCount == 4 && section.isLoaded()) { section.unload(); } else if (section.childCount == 0 && !section.isLoaded()) { diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java index b8dd441d6..cab3e45a4 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/column/ColumnDatatype.java @@ -20,7 +20,7 @@ import java.util.concurrent.atomic.AtomicReference; public class ColumnDatatype implements LodDataSource, RenderDataSource { public static final boolean DO_SAFETY_CHECKS = true; - public static final int SECTION_SIZE_OFFSET = 6; + public static final byte SECTION_SIZE_OFFSET = 6; public static final int SECTION_SIZE = 1 << SECTION_SIZE_OFFSET; public static final int LATEST_VERSION = 9; public final int AIR_LODS_SIZE = 16; @@ -46,7 +46,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { public ColumnDatatype(DhSectionPos sectionPos, DataInputStream inputData, int version) throws IOException { this.sectionPos = sectionPos; byte detailLevel = inputData.readByte(); - if (sectionPos.detail != detailLevel) { + if (sectionPos.dataDetail != detailLevel) { throw new IOException("Invalid data: detail level does not match"); } verticalSize = inputData.readByte() & 0b01111111; @@ -74,7 +74,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { verticalSize = maxVerticalSize; this.sectionPos = sectionPos; byte detailLevel = inputData.readByte(); - if (sectionPos.detail != detailLevel) { + if (sectionPos.dataDetail != detailLevel) { throw new IOException("Invalid data: detail level does not match"); } int fileMaxVerticalSize = inputData.readByte() & 0b01111111; @@ -109,7 +109,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { if (!sourcePos.overlaps(sectionPos)) { throw new IllegalArgumentException("The source section does not overlap with new target position"); } - if (sourcePos.detail > sectionPos.detail) { + if (sourcePos.dataDetail > sectionPos.dataDetail) { throw new IllegalArgumentException("The source section has higher detail than new target detail"); } if (sourcePos.yOffset != sectionPos.yOffset) { @@ -342,11 +342,11 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { { //We reset the array long[][] verticalUpdateArrays = tLocalVerticalUpdateArrays.get(); - long[] dataToMerge = verticalUpdateArrays[sectionPos.detail-1]; - int arrayLength = DetailDistanceUtil.getMaxVerticalData(sectionPos.detail-1) * 4; + long[] dataToMerge = verticalUpdateArrays[sectionPos.dataDetail -1]; + int arrayLength = DetailDistanceUtil.getMaxVerticalData(sectionPos.dataDetail -1) * 4; if (dataToMerge == null || dataToMerge.length != arrayLength) { dataToMerge = new long[arrayLength]; - verticalUpdateArrays[sectionPos.detail-1] = dataToMerge; + verticalUpdateArrays[sectionPos.dataDetail -1] = dataToMerge; } else Arrays.fill(dataToMerge, 0); //int lowerMaxVertical = dataToMerge.length / 4; @@ -387,7 +387,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { } public boolean writeData(DataOutputStream output) throws IOException { - output.writeByte(sectionPos.detail); + output.writeByte(sectionPos.dataDetail); output.writeByte((byte) verticalSize); // FIXME: yOffset is a int, but we only are writing a short. output.writeByte((byte) (sectionPos.yOffset & 0xFF)); @@ -807,7 +807,7 @@ public class ColumnDatatype implements LodDataSource, RenderDataSource { } public static RenderDataSource loadByCopying(LodDataSource dataSource, DhSectionPos sectionPos) { ColumnDatatype columns = new ColumnDatatype(sectionPos, dataSource, - DetailDistanceUtil.getMaxVerticalData(sectionPos.detail)); + DetailDistanceUtil.getMaxVerticalData(sectionPos.dataDetail)); return null; } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java b/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java new file mode 100644 index 000000000..9e1eb0a2c --- /dev/null +++ b/src/main/java/com/seibel/lod/core/objects/a7/datatype/full/FullDatatype.java @@ -0,0 +1,4 @@ +package com.seibel.lod.core.objects.a7.datatype.full; + +public class FullDatatype { +} diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java index 5317426d2..971a48c89 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhLodPos.java @@ -1,6 +1,9 @@ package com.seibel.lod.core.objects.a7.pos; import com.seibel.lod.core.objects.DHBlockPos; +import com.seibel.lod.core.util.LodUtil; + +import java.util.Objects; public class DhLodPos { public final byte detail; @@ -38,4 +41,32 @@ public class DhLodPos { public DhBlockPos2D getCorner() { return new DhBlockPos2D(getX().toBlock(), getZ().toBlock()); } + + public DhLodPos convertUpwardsTo(byte newDetail) { + LodUtil.assertTrue(newDetail >= detail); + return new DhLodPos(newDetail, x >> (newDetail - detail), z >> (newDetail - detail)); + } + + @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; + } + + @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 other.equals(this.convertUpwardsTo(other.detail)); + } else { + return this.equals(other.convertUpwardsTo(this.detail)); + } + } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java index 2ad87bee5..4d7cd9139 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/pos/DhSectionPos.java @@ -1,52 +1,55 @@ package com.seibel.lod.core.objects.a7.pos; import com.seibel.lod.core.enums.LodDirection; +import com.seibel.lod.core.util.LodUtil; import java.util.function.Consumer; public class DhSectionPos { - public final byte detail; - public final int x; - public final int z; - public final int yOffset; - public final byte dataDetailOffset; + public final byte sectionDetail; + public final int sectionX; // in sectionDetail level grid + public final int sectionZ; // in sectionDetail level grid - public DhSectionPos(byte detail, int x, int z, int yOffset, byte dataDetailOffset) { - this.detail = detail; - this.x = x; - this.z = z; - this.yOffset = yOffset; - this.dataDetailOffset = dataDetailOffset; + public DhSectionPos(byte sectionDetail, int sectionX, int sectionZ) { + this.sectionDetail = sectionDetail; + this.sectionX = sectionX; + this.sectionZ = sectionZ; } - public DhSectionPos withYOffset(int yOffset) { - return new DhSectionPos(detail, x, z, yOffset, dataDetailOffset); + 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 DhSectionPos withDataOffset(byte dataDetailOffset) { - return new DhSectionPos(detail, x, z, yOffset, dataDetailOffset); + 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() { - if (dataDetailOffset == 0) return new DhLodPos(detail, x, z); - return new DhLodPos(detail, (x << dataDetailOffset)+(1 << (dataDetailOffset-1)), (z << dataDetailOffset)+(1 << (dataDetailOffset-1))); + return getCenter((byte) (sectionDetail-1)); } - public DhLodPos getCorner() { - return new DhLodPos(detail, x << dataDetailOffset, z << dataDetailOffset); + return getCorner((byte) (sectionDetail-1)); } - public DhLodUnit getWidth() { - return new DhLodUnit(detail, 1 << dataDetailOffset); - } - - public static DhLodUnit getWidth(byte detail, byte dataDetailOffset){ - return new DhLodUnit(detail, 1 << dataDetailOffset); + return getWidth(sectionDetail); } public DhSectionPos getChild(int child0to3){ if (child0to3 < 0 || child0to3 > 3) throw new IllegalArgumentException("child0to3 must be between 0 and 3"); - if (detail-dataDetailOffset <= 0) throw new IllegalStateException("detail or data detail must be greater than 0"); - return new DhSectionPos((byte) (detail - 1), x * 2 + (child0to3 & 1), z * 2 + (child0to3 & 2) / 2, yOffset, dataDetailOffset); + 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) / 2); } public void forEachChild(Consumer callback){ @@ -56,40 +59,21 @@ public class DhSectionPos { } public DhSectionPos getParent(){ - return new DhSectionPos((byte) (detail + 1), x / 2, z / 2, yOffset, dataDetailOffset); + return new DhSectionPos((byte) (sectionDetail + 1), sectionX / 2, sectionZ / 2); } public DhSectionPos getAdjacent(LodDirection dir) { - return new DhSectionPos(detail, x + dir.getNormal().x, z + dir.getNormal().z, yOffset, dataDetailOffset); + return new DhSectionPos(sectionDetail, sectionX + dir.getNormal().x, sectionZ + dir.getNormal().z); } - public DhSectionPos convertUpwardsTo(byte newDetail){ - if (detail == newDetail) return this; - if (detail > newDetail) return - new DhSectionPos(newDetail, x >> (detail - newDetail), z >> (detail - newDetail), yOffset, dataDetailOffset); - throw new IllegalArgumentException("newDetail must be greater than detail"); + public DhLodPos getSectionBBoxPos() { + return new DhLodPos(sectionDetail, sectionX, sectionZ); } /** - * NOTE: This equals() does not consider yOffset or dataDetailOffset! - */ - - public boolean equals(Object o){ - if (o == this) return true; - if (!(o instanceof DhSectionPos)) return false; - DhSectionPos other = (DhSectionPos) o; - return detail == other.detail && x == other.x && z == other.z; - } - - /** - * NOTE: This does not consider yOffset! (dataDetailOffset is also ignored since, well, it doesn't effect the outcome) + * NOTE: This does not consider yOffset! */ public boolean overlaps(DhSectionPos other){ - if (this.equals(other)) - return true; - else if (detail < other.detail) - return other.equals(this.convertUpwardsTo(other.detail)); - else - return this.equals(other.convertUpwardsTo(detail)); + return getSectionBBoxPos().overlaps(other.getSectionBBoxPos()); } } diff --git a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java index 6b03e4396..d4c0d85e0 100644 --- a/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java +++ b/src/main/java/com/seibel/lod/core/objects/a7/render/RenderBufferHandler.java @@ -99,7 +99,7 @@ public class RenderBufferHandler { public RenderBufferHandler(LodQuadTree target) { this.target = target; - MovableGridRingList referenceList = target.getRingList((byte) (target.getNumbersOfDetailLevels() - 1)); + MovableGridRingList referenceList = target.getRingList((byte) (target.getNumbersOfSectionLevels() - 1)); Pos2D center = referenceList.getCenter(); renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center); } @@ -111,7 +111,7 @@ public class RenderBufferHandler { } public void update() { - byte topDetail = (byte) (target.getNumbersOfDetailLevels() - 1); + byte topDetail = (byte) (target.getNumbersOfSectionLevels() - 1); MovableGridRingList referenceList = target.getRingList(topDetail); Pos2D center = referenceList.getCenter(); renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list diff --git a/src/main/java/com/seibel/lod/core/util/LodUtil.java b/src/main/java/com/seibel/lod/core/util/LodUtil.java index ba7fd7e64..587d1230e 100644 --- a/src/main/java/com/seibel/lod/core/util/LodUtil.java +++ b/src/main/java/com/seibel/lod/core/util/LodUtil.java @@ -448,6 +448,9 @@ public class LodUtil public static void assertTrue(boolean condition) { if (!condition) throw new RuntimeException("Assertion failed"); } + public static void assertTrue(boolean condition, String message) { + if (!condition) throw new RuntimeException("Assertion failed: " + message); + } public static ExecutorService makeSingleThreadPool(String name, int relativePriority) { return Executors.newSingleThreadExecutor(new LodThreadFactory(name, Thread.NORM_PRIORITY+relativePriority)); }