Complete the dynamic section data detail offset quad tree. (Untested)
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user