Complete the dynamic section data detail offset quad tree. (Untested)

This commit is contained in:
TomTheFurry
2022-05-13 14:10:33 +08:00
parent 8e524a915c
commit 8e296f98d7
7 changed files with 323 additions and 166 deletions
@@ -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<LodSection>[] 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<ContainerTypeConfigEntry> 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<LodSection>(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<LodSection>(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<LodSection> 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<LodSection> 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<LodSection> ringList = ringLists[sectLevel - startingSectionLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
sectLevel == startingSectionLevel ? null : ringLists[sectLevel - startingSectionLevel - 1];
final MovableGridRingList<LodSection> 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<LodSection> ringList = ringLists[detailLevel];
for (byte sectLevel = (byte) (numbersOfSectionLevels - 1); sectLevel >= startingSectionLevel; sectLevel--) {
final MovableGridRingList<LodSection> ringList = ringLists[sectLevel - startingSectionLevel];
final MovableGridRingList<LodSection> childRingList =
detailLevel == 0 ? null : ringLists[detailLevel - 1];
final MovableGridRingList<LodSection> 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()) {
@@ -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;
}
@@ -0,0 +1,4 @@
package com.seibel.lod.core.objects.a7.datatype.full;
public class FullDatatype {
}
@@ -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));
}
}
}
@@ -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<DhSectionPos> 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());
}
}
@@ -99,7 +99,7 @@ public class RenderBufferHandler {
public RenderBufferHandler(LodQuadTree target) {
this.target = target;
MovableGridRingList<LodSection> referenceList = target.getRingList((byte) (target.getNumbersOfDetailLevels() - 1));
MovableGridRingList<LodSection> 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<LodSection> referenceList = target.getRingList(topDetail);
Pos2D center = referenceList.getCenter();
renderBufferNodes.move(center.x, center.y, RenderBufferNode::close); // Note: may lock the list
@@ -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));
}