revert LodQuadTree region to LodQuadTree and other refactors

This commit is contained in:
James Seibel
2021-08-07 18:30:36 -05:00
parent ed188448f2
commit 20d1ff6d49
5 changed files with 593 additions and 516 deletions
@@ -28,7 +28,7 @@ import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.objects.LodQuadTreeRegion;
import com.seibel.lod.objects.LodQuadTree;
import com.seibel.lod.objects.LodQuadTreeDimension;
import com.seibel.lod.objects.LodQuadTreeNode;
import com.seibel.lod.proxy.ClientProxy;
@@ -98,7 +98,7 @@ public class LodQuadTreeDimensionFileHandler {
* Return the LodQuadTree region at the given coordinates.
* (null if the file doesn't exist)
*/
public LodQuadTreeRegion loadRegionFromFile(int regionX, int regionZ)
public LodQuadTree loadRegionFromFile(int regionX, int regionZ)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ);
@@ -195,7 +195,7 @@ public class LodQuadTreeDimensionFileHandler {
// problem reading the file
return null;
}
return new LodQuadTreeRegion(dataList,regionX, regionZ);
return new LodQuadTree(dataList,regionX, regionZ);
}
@@ -240,7 +240,7 @@ public class LodQuadTreeDimensionFileHandler {
* 2. This will save to the LodDimension that this
* handler is associated with.
*/
private void saveRegionToDisk(LodQuadTreeRegion region)
private void saveRegionToDisk(LodQuadTree region)
{
// convert chunk coordinates to region
// coordinates
@@ -0,0 +1,547 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.seibel.lod.enums.DistanceGenerationMode;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.ChunkPos;
/**
* This object contains all data useful to render LodBlock in a region (32x32 chunk to 512x512 block)
* for every node it contains the border of said node, its size, its block position in the world,
* color, height and depth.
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-7-2021
*/
public class LodQuadTree
{
// note:
// The term node correspond to a LodQuadTree object
/*
Example on how a LodQuadTreeRegion would be rendered (the number corresponds to the level of the LodNodeData)
.___.___._______._______________.
|6|6| 7 | | |
|6|6|___| 8 | |
| 7 | 7 | | |
|___|___|_______| 9 |
| | | |
| 8 | 8 | |
| | | |
|_______|_______|_______________|
| | |
| | |
| | |
| 9 | 9 |
| | |
| | |
| | |
|_______________|_______________|
*/
/** If true SetNodesAtLowerLevel will update the color and height of all higher level nodes */
public static boolean UPDATE_HIGHER_LEVEL = true;
//data useful to render
//if children are present then lodNodeData should be a combination of the lodData of the child. This can be
//turned off by deselecting the recursive update in all update method.
private LodQuadTreeNode lodNode;
/*
.____.____.
| NW | NE | |
|____|____| Z
| SW | SE | |
|____|____| V
-----X---->
North - negative z
South - positive z
West - negative x
east - positive x
*/
/** treeFull is true if and only if all child are not null */
private boolean treeFull;
private boolean treeEmpty;
/**
* The four child are based on the four diagonal cardinal directions.
* The first index is for North and South and the second index is for East and West. <br>
* Children should always be null for level 0.
*/
private final LodQuadTree[][] children;
//parent should always be null for level 9, and always not null for other levels.
private final LodQuadTree parent;
/**
* Constructor for level 0 without LodNodeData (region level constructor)
*
* @param regionX indicate the x region position of the node
* @param regionZ indicate the z region position of the node
*/
//maybe the use of useLevelCoordinate could be changed. I could use a builder to do all this work.
public LodQuadTree(RegionPos regionPos)
{
this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionPos.x, regionPos.z));
}
/**
* Constructor for generic world without LOD data
*
* @param parent parent of this node
* @param level level of this note
* @param posX position x in the level
* @param posZ position z in the level
*/
public LodQuadTree(LodQuadTree parent, byte level, RegionPos regionPos)
{
this(parent, new LodQuadTreeNode(level, regionPos.x, regionPos.z));
}
/**
* Constructor for a generic world via the LodNodeData
*
* @param newLodNode LodQuadTreeNode containing all the information of this node
*/
public LodQuadTree(LodQuadTree newParent, LodQuadTreeNode newLodNode)
{
parent = newParent;
lodNode = newLodNode;
children = new LodQuadTree[2][2];
treeEmpty = true;
treeFull = false;
}
/**
* Constructor using a dataList
*
* @param dataList list of LodNodeData to put in this LodQuadTree
* @param regionX x region coordinate
* @param regionZ z region coordinate
*/
public LodQuadTree(List<LodQuadTreeNode> dataList, int regionX, int regionZ)
{
this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ));
setNodesAtLowerLevel(dataList);
}
/**
* @param dataList list of data to put in the node
*/
public void setNodesAtLowerLevel(List<LodQuadTreeNode> dataList)
{
for (LodQuadTreeNode lodQuadTreeNode : dataList)
{
this.setNodeAtLowerLevel(lodQuadTreeNode);
}
}
/**
* @param newLowerLodNode data to put in the node
* @return true only if the QuadTree has been changed
*/
public boolean setNodeAtLowerLevel(LodQuadTreeNode newLowerLodNode)
{
byte targetLevel = newLowerLodNode.detailLevel;
byte currentLevel = lodNode.detailLevel;
if (targetLevel >= currentLevel)
{
// we can't add a node that has a equal or higher
// detail level than this region
return false;
}
short widthRatio = (short) (lodNode.width / (2 * newLowerLodNode.width));
int WE = Math.abs(Math.floorDiv(newLowerLodNode.posX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(newLowerLodNode.posZ , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
// if this child doesn't exist, create an empty one
setChild(NS, WE);
}
LodQuadTree child = getChild(NS, WE);
if (lodNode.compareComplexity(newLowerLodNode) > 0)
{
// the node we want to introduce is less complex than the current node
// we don't want to override higher complexity with lower complexity
return false;
}
else
{
if (targetLevel == currentLevel - 1)
{
// we are at the level we want to add the newLowerLodNode
child.setLodNodeData(newLowerLodNode);
return true;
}
else
{
// recurse until we reach the level we want to add the newLowerLodNode
return child.setNodeAtLowerLevel(newLowerLodNode);
}
}
}
/**
* Gets the LodQuadTreeNode at the given chunkPos and detailLevel.
* Returns null if no such LodQuadTreeNode exists.
*/
public LodQuadTreeNode getNodeAtChunkPos(ChunkPos chunkPos, int detailLevel)
{
if (detailLevel > LodQuadTreeNode.REGION_LEVEL)
throw new IllegalArgumentException("getNodeAtChunkPos given a level of \"" + detailLevel + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max.");
byte currentDetailLevel = lodNode.detailLevel;
if (detailLevel == currentDetailLevel)
{
return lodNode;
}
else if (detailLevel < currentDetailLevel)
{
// the detail level we need is lower, go down a layer
short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, detailLevel)));
int WE = Math.abs(Math.floorDiv(chunkPos.x , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(chunkPos.z , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
return null;
}
LodQuadTree child = getChild(NS, WE);
return child.getNodeAtChunkPos(chunkPos, detailLevel);
}
else
{
// the detail level was higher than this region's
return null;
}
}
public LodQuadTree getChild(int NS, int WE)
{
return children[NS][WE];
}
/**
* Put a child with the given data into the given position.
*
* @param newLodNode data to put in the child
*/
public void setChild(LodQuadTreeNode newLodNode)
{
// the child must be 1 detail level lower than this region
if (newLodNode.detailLevel == lodNode.detailLevel - 1)
{
int WE = newLodNode.posX % lodNode.posX;
int NS = newLodNode.posZ % lodNode.posZ;
children[NS][WE] = new LodQuadTree(this, lodNode);
}
}
/**
* Put an empty child in the given position.
*
* @param NS North-South position
* @param WE West-East position
*/
public void setChild(int NS, int WE)
{
// TODO is this correctly converting to a regionPos?
int childRegionX = lodNode.posX * 2 + WE;
int childRegionZ = lodNode.posZ * 2 + NS;
children[NS][WE] = new LodQuadTree(this, (byte) (lodNode.detailLevel - 1), new RegionPos(childRegionX, childRegionZ));
}
/**
* Update this region's data, specifically levelFull and lodNodeData.
*
* @param recursiveUpdate if recursive is true the update will rise up to the level 0
*/
private void updateRegion(boolean recursiveUpdate)
{
boolean isFull = true;
boolean isEmpty = true;
// determine if this region is empty or full
List<LodQuadTreeNode> dataList = new ArrayList<>();
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
if (getChild(NS,WE) != null)
{
dataList.add(getChild(NS,WE).getLodNodeData());
isEmpty = false;
}
else
{
isFull = false;
}
}
}
treeFull = isFull;
treeEmpty = isEmpty;
// update this regions
lodNode.combineData(dataList);
// update sub regions if requested
if (lodNode.detailLevel < LodQuadTreeNode.REGION_LEVEL && recursiveUpdate)
{
this.parent.updateRegion(recursiveUpdate);
}
}
/**
* Returns nodes that match the given mask.
*
* @param complexityMask holds the DistanceGenerationModes to accept
* @param getOnlyDirty if true it will return only dirty nodes
* @param getOnlyLeaf if true it will return only leaf nodes
* @return list of nodes
*/
public List<LodQuadTreeNode> getNodeListWithMask(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf)
{
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (hasChildren())
{
//There is at least 1 child
// this detail level's node
if (!getOnlyLeaf && !(getOnlyDirty && !lodNode.isDirty())
&& complexityMask.contains(lodNode.getComplexity()))
{
nodeList.add(lodNode);
}
// search the children for valid nodes
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
LodQuadTree child = children[NS][WE];
if (child != null)
{
nodeList.addAll(child.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf));
}
}
}
}
else
{
// This tree has no children
if (!(getOnlyDirty && !lodNode.isDirty()) && (complexityMask.contains(lodNode.getComplexity())))
{
nodeList.add(lodNode);
}
}
return nodeList;
}
/**
* This method will return all the nodes that can be rendered
*
* @param playerPos position of the player
* @param targetLevel minimum level that can be rendered
* @param maxDistance maximum distance from the player
* @param minDistance minimum distance from the player
* @return
*/
public List<LodQuadTreeNode> getNodeToRender(BlockPos playerPos, int targetLevel, Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
{
int x = playerPos.getX();
int z = playerPos.getZ();
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance)))
{
if (targetLevel == lodNode.detailLevel || !isNodeFull())
{
// we have either reached the right detail level or this tree isn't full
if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.getComplexity()))
{
// this node isn't void and has the complexity level we are looking for
nodeList.add(lodNode);
}
}
else
{
//
for (int NS = 0; NS <= 1; NS++)
{
for (int WE = 0; WE <= 1; WE++)
{
LodQuadTree child = getChild(NS, WE);
if (child != null)
{
nodeList.addAll(child.getNodeToRender(playerPos, targetLevel, complexityMask, maxDistance, minDistance));
}
}
}
}
}
return nodeList;
}
/**
* Nodes that can be generated in the approximated version
* A level is generated only if it has child and is higher than the target level and in the distance range
* @param x
* @param z
* @param targetLevel
* @param complexityToGenerate
* @param maxDistance
* @param minDistance
* @return
*/
public List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) {
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> nodeList = new ArrayList<>();
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) || isCoordinateInLevel(x, z))) {
if(!hasChildren() || targetLevel == lodNode.detailLevel){
if (this.lodNode.getComplexity().compareTo(complexityToGenerate) <= 0 ) {
nodeList.add(new AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>(this.lodNode, min));
}
}else {
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
if (getChild(NS, WE) == null) {
setChild(NS, WE);
}
nodeList.addAll(getChild(NS, WE).getNodesToGenerate(x, z, targetLevel, complexityToGenerate, maxDistance, minDistance));
}
}
}
}
return nodeList;
}
/**
* simple getter for lodNodeData
*
* @return lodNodeData
*/
public LodQuadTreeNode getLodNodeData() {
return lodNode;
}
/**
* setter for lodNodeData, to maintain a correct relationship between level this method force update on all parent
*
* @param newLodQuadTreeNode data to set
* @param updateHigherLevel if true it will update all the upper levels.
*/
public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode)
{
if (this.lodNode == null) {
this.lodNode = newLodQuadTreeNode;
} else {
this.lodNode.update(newLodQuadTreeNode);
}
//a recursive update is necessary to change higher level
if (parent != null && UPDATE_HIGHER_LEVEL) parent.updateRegion(true);
}
public boolean isNodeFull() {
return treeFull;
}
public boolean hasChildren()
{
return !treeEmpty;
}
public boolean isRenderable() {
return (lodNode != null);
}
public boolean isCoordinateInLevel(int x, int z){
return (lodNode.getStartX() * lodNode.width <= x && lodNode.getStartZ() * lodNode.width <= z && lodNode.getEndX() * lodNode.width >= x && lodNode.getEndZ() * lodNode.width >= z);
}
@Override
public String toString(){
String s = lodNode.toString();
return s;
/*
if(isThereAnyChild()){
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
LodQuadTree child = children[NS][WE];
if (child != null) {
s += '\n' + child.toString();
}
}
}
}
return s;
*/
}
}
@@ -74,7 +74,7 @@ public class LodQuadTreeDimension
}
public volatile LodQuadTreeRegion regions[][];
public volatile LodQuadTree regions[][];
public volatile boolean isRegionDirty[][];
/** a chunk Position */
@@ -131,7 +131,7 @@ public class LodQuadTreeDimension
regions = new LodQuadTreeRegion[width][width];
regions = new LodQuadTree[width][width];
isRegionDirty = new boolean[width][width];
// populate isRegionDirty
@@ -253,7 +253,7 @@ public class LodQuadTreeDimension
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodQuadTreeRegion getRegion(RegionPos regionPos)
public LodQuadTree getRegion(RegionPos regionPos)
{
int xIndex = (regionPos.x - center.x) + halfWidth;
int zIndex = (regionPos.z - center.z) + halfWidth;
@@ -267,7 +267,7 @@ public class LodQuadTreeDimension
regions[xIndex][zIndex] = getRegionFromFile(regionPos.x, regionPos.z);
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = new LodQuadTreeRegion(regionPos.x, regionPos.z);
regions[xIndex][zIndex] = new LodQuadTree(regionPos);
}
}
@@ -279,7 +279,7 @@ public class LodQuadTreeDimension
*
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public void addOrOverwriteRegion(LodQuadTreeRegion newRegion) throws ArrayIndexOutOfBoundsException
public void addOrOverwriteRegion(LodQuadTree newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.getLodNodeData().posX - center.x) + halfWidth;
int zIndex = (center.z - newRegion.getLodNodeData().posZ) + halfWidth;
@@ -299,7 +299,8 @@ public class LodQuadTreeDimension
{
int regionX;
int regionZ;
LodQuadTreeRegion region;
RegionPos regionPos;
LodQuadTree region;
for(int x = 0; x < regions.length; x++)
{
@@ -307,12 +308,13 @@ public class LodQuadTreeDimension
{
regionX = (x + center.x) - halfWidth;
regionZ = (z + center.z) - halfWidth;
region = getRegion(new RegionPos(regionX,regionZ));
regionPos = new RegionPos(regionX,regionZ);
region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodQuadTreeRegion(regionX, regionZ);
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
}
@@ -335,15 +337,15 @@ public class LodQuadTreeDimension
return false;
}
LodQuadTreeRegion region = getRegion(new RegionPos(regionPos.x, regionPos.z));
LodQuadTree region = getRegion(regionPos);
if (region == null)
{
// if no region exists, create it
region = new LodQuadTreeRegion(regionPos.x, regionPos.z);
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
boolean nodeAdded = region.setNodeAtLowerLevel(lodNode, true);
boolean nodeAdded = region.setNodeAtLowerLevel(lodNode);
// only save valid LODs to disk
if (!lodNode.dontSave && fileHandler != null)
@@ -385,14 +387,14 @@ public class LodQuadTreeDimension
// TODO possibly put this in LodUtil
int regionPosX = Math.floorDiv(chunkPos.x, (int) Math.pow(2,LodQuadTreeNode.REGION_LEVEL - detailLevel));
int regionPosZ = Math.floorDiv(chunkPos.z, (int) Math.pow(2,LodQuadTreeNode.REGION_LEVEL - detailLevel));
LodQuadTreeRegion region = getRegion(new RegionPos(regionPosX, regionPosZ));
LodQuadTree region = getRegion(new RegionPos(regionPosX, regionPosZ));
if(region == null)
{
return null;
}
return region.getNodeAtChunkPos(chunkPos.x, chunkPos.z, detailLevel);
return region.getNodeAtChunkPos(chunkPos, detailLevel);
}
/**
@@ -420,7 +422,7 @@ public class LodQuadTreeDimension
{
for(int j = 0; j < regions.length; j++)
{
listOfData.addAll(regions[i][j].getNodeToRender(playerPos.getX(), playerPos.getZ(), detailLevel, complexityMask, maxDistance, minDistance));
listOfData.addAll(regions[i][j].getNodeToRender(playerPos, detailLevel, complexityMask, maxDistance, minDistance));
}
}
@@ -435,7 +437,8 @@ public class LodQuadTreeDimension
{
int regionX;
int regionZ;
LodQuadTreeRegion region;
LodQuadTree region;
RegionPos regionPos;
List<Map.Entry<LodQuadTreeNode,Integer>> listOfQuadTree = new ArrayList<>();
// go through every region we have stored
@@ -445,11 +448,12 @@ public class LodQuadTreeDimension
{
regionX = (xIndex + center.x) - halfWidth;
regionZ = (zIndex + center.z) - halfWidth;
region = getRegion(new RegionPos(regionX,regionZ));
regionPos = new RegionPos(regionX,regionZ);
region = getRegion(regionPos);
if (region == null)
{
region = new LodQuadTreeRegion(regionX, regionZ);
region = new LodQuadTree(regionPos);
addOrOverwriteRegion(region);
}
@@ -462,7 +466,7 @@ public class LodQuadTreeDimension
}
/**
* @see LodQuadTreeRegion#getNodeListWithMask
* @see LodQuadTree#getNodeListWithMask
*/
public List<LodQuadTreeNode> getNodesWithMask(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf)
{
@@ -470,7 +474,7 @@ public class LodQuadTreeDimension
int xIndex;
int zIndex;
LodQuadTreeRegion region;
LodQuadTree region;
// go through every region we have stored
for(int xRegion = 0; xRegion < regions.length; xRegion++)
@@ -494,7 +498,7 @@ public class LodQuadTreeDimension
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public LodQuadTreeRegion getRegionFromFile(int regionX, int regionZ)
public LodQuadTree getRegionFromFile(int regionX, int regionZ)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionX, regionZ);
@@ -541,12 +545,12 @@ public class LodQuadTreeDimension
public int getNumberOfLods()
{
int numbLods = 0;
for (LodQuadTreeRegion[] regions : regions)
for (LodQuadTree[] regions : regions)
{
if(regions == null)
continue;
for (LodQuadTreeRegion region : regions)
for (LodQuadTree region : regions)
{
if(region == null)
continue;
@@ -583,7 +587,7 @@ public class LodQuadTreeDimension
width = newWidth;
halfWidth = (int)Math.floor(width / 2);
regions = new LodQuadTreeRegion[width][width];
regions = new LodQuadTree[width][width];
isRegionDirty = new boolean[width][width];
// populate isRegionDirty
@@ -287,29 +287,37 @@ public class LodQuadTreeNode
return lodDataPoint;
}
public void combineData(List<LodQuadTreeNode> dataList){
if(dataList.isEmpty()){
public void combineData(List<LodQuadTreeNode> dataList)
{
if(dataList.isEmpty())
{
lodDataPoint = new LodDataPoint();
}else {
}
else
{
short height = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().height).min().getAsInt();
short depth = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().depth).max().getAsInt();
int red= dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getRed()).sum()/dataList.size();
int green= dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getGreen()).sum()/dataList.size();
int red = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getRed()).sum()/dataList.size();
int green = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getGreen()).sum()/dataList.size();
int blue = dataList.stream().mapToInt(x -> x.getLodDataPoint().color.getBlue()).sum()/dataList.size();
Color color = new Color(red,green,blue);
lodDataPoint = new LodDataPoint(height,depth,color);
//the new complexity equal to the lowest complexity of the list
DistanceGenerationMode minComplexity = DistanceGenerationMode.SERVER;
for(LodQuadTreeNode node: dataList){
if (minComplexity.compareTo(node.complexity) > 0){
for(LodQuadTreeNode node: dataList)
{
if (minComplexity.compareTo(node.complexity) > 0)
{
minComplexity = node.complexity;
}
}
complexity = minComplexity;
voidNode = dataList.stream().filter(x -> !x.voidNode).count() == 0;
}
dirty = true;
dontSave = false;
}
@@ -1,482 +0,0 @@
/*
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
*
* Copyright (C) 2020 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.objects;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.seibel.lod.enums.DistanceGenerationMode;
/**
* This object contains all data useful to render LodBlock in a region (32x32 chunk o 512x512 block)
* for every node it contains the border of the block, the size, the position at it's level, the color, the height and the depth.
*/
public class LodQuadTreeRegion
{
//notes
//The term node correspond to a LodQuadTree object
/*
Example on how it will be rendered (the number correspond to the level in the LodNodeData)
.___.___._______._______________.
|6|6| 7 | | |
|6|6|___| 8 | |
| 7 | 7 | | |
|___|___|_______| 9 |
| | | |
| 8 | 8 | |
| | | |
|_______|_______|_______________|
| | |
| | |
| | |
| 9 | 9 |
| | |
| | |
| | |
|_______________|_______________|
*/
//data useful to render
//if children are present then lodNodeData should be a combination of the lodData of the child. This can be
//turned off by deselecting the recursive update in all update method.
private LodQuadTreeNode lodNode;
/*
.____.____.
| NW | NE | |
|____|____| Z
| SW | SE | |
|____|____| V
-----X---->
North - negative z
South - positive z
West - negative x
east - positive x
*/
//level completed is true if and only if all child are not null
private boolean nodeFull;
private boolean nodeEmpty;
//the four child based on the four diagonal cardinal direction
//the first index is for N and S and the second index is for W and S
//children should always be null for level 0.
private final LodQuadTreeRegion[][] children;
//parent should always be null for level 9, and always not null for other levels.
private final LodQuadTreeRegion parent;
/**
* Constructor for level 0 without LodNodeData (region level constructor)
*
* @param regionX indicate the x region position of the node
* @param regionZ indicate the z region position of the node
*/
//maybe the use of useLevelCoordinate could be changed. I could use a builder to do all this work.
public LodQuadTreeRegion(int regionX, int regionZ)
{
this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ));
}
/**
* Constructor for generic level without LodNodeData
*
* @param parent parent of this node
* @param level level of this note
* @param posX position x in the level
* @param posZ position z in the level
*/
public LodQuadTreeRegion(LodQuadTreeRegion parent, byte level, int posX, int posZ) {
this(parent, new LodQuadTreeNode(level, posX, posZ));
}
/**
* Constructor for generic level via the LodNodeData
*
* @param lodNode object containing all the information of this node
*/
public LodQuadTreeRegion(LodQuadTreeRegion parent, LodQuadTreeNode lodNode) {
this.parent = parent;
this.lodNode = lodNode;
this.children = new LodQuadTreeRegion[2][2];
this.nodeEmpty = true;
this.nodeFull = false;
}
/**
* Constructor using a dataList
*
* @param dataList list of LodNodeData to put in this LodQuadTree
* @param regionX x region coordinate
* @param regionZ z region coordinate
*/
public LodQuadTreeRegion(List<LodQuadTreeNode> dataList, int regionX, int regionZ) {
this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ));
this.setNodesAtLowerLevel(dataList, true);
}
/**
* @param dataList list of data to put in the node
* @param updateHigherLevel will update the color and height of higher level only if true
*/
public void setNodesAtLowerLevel(List<LodQuadTreeNode> dataList, boolean updateHigherLevel) {
for (LodQuadTreeNode lodQuadTreeNode : dataList) {
//this is slow, you could set update to false and use an only top down update method.
this.setNodeAtLowerLevel(lodQuadTreeNode, updateHigherLevel);
}
}
/**
* @param newLodNode data to put in the node
* @param updateHigherLevel will update the color and height of higher level only if true
* @return true only if the QuadTree has been changed
*/
public boolean setNodeAtLowerLevel(LodQuadTreeNode newLodNode, boolean updateHigherLevel)
{
//check if we try to introduce a level that is higher or equal than the current one
byte targetLevel = newLodNode.detailLevel;
byte currentLevel = lodNode.detailLevel;
if (targetLevel < currentLevel) {
int posX = newLodNode.posX;
int posZ = newLodNode.posZ;
short widthRatio = (short) (lodNode.width / (2 * newLodNode.width));
int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2);
if (getChild(NS, WE) == null) {
setChild(NS, WE);
}
LodQuadTreeRegion child = getChild(NS, WE);
if (lodNode.compareComplexity(newLodNode) > 0)
{
//the node we want to introduce is less complex than the current node
//we don't want to override higher complexity with lower complexity
return false;
}
else
{
if (targetLevel == currentLevel - 1)
{
child.setLodNodeData(newLodNode, true);
return true;
}
else
{
return child.setNodeAtLowerLevel(newLodNode, updateHigherLevel);
}
}
}
else
{
return false;
}
}
/**
* @param chunkPosX
* @param chunkPosZ
* @param targetLevel
* @return
*/
public LodQuadTreeNode getNodeAtChunkPos(int chunkPosX, int chunkPosZ, int targetLevel)
{
if (targetLevel > LodQuadTreeNode.REGION_LEVEL)
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + targetLevel + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max.");
byte currentLevel = lodNode.detailLevel;
if (targetLevel == currentLevel)
{
return lodNode;
}
else if (targetLevel < currentLevel)
{
short widthRatio = (short) (lodNode.width / (2 * Math.pow(2, targetLevel)));
int WE = Math.abs(Math.floorDiv(chunkPosX , widthRatio) % 2);
int NS = Math.abs(Math.floorDiv(chunkPosZ , widthRatio) % 2);
if (getChild(NS, WE) == null)
{
return null;
}
LodQuadTreeRegion child = getChild(NS, WE);
return child.getNodeAtChunkPos(chunkPosX, chunkPosZ, targetLevel);
}
else
{
return null;
}
}
public LodQuadTreeRegion getChild(int NS, int WE)
{
return children[NS][WE];
}
/**
* setChild will put a child with given data in the given position
*
* @param newLodNode data to put in the child
* @param NS North-South position
* @param WE West-East position
*/
public void setChild(LodQuadTreeNode newLodNode, int NS, int WE) {
if (newLodNode.detailLevel == lodNode.detailLevel - 1) {
children[NS][WE] = new LodQuadTreeRegion(this, lodNode);
}
}
/**
* setChild will put a child with given data in the given position
*
* @param newLodNode data to put in the child
*/
public void setChild(LodQuadTreeNode newLodNode) {
if (newLodNode.detailLevel == lodNode.detailLevel - 1) {
int WE = newLodNode.posX % lodNode.posX;
int NS = newLodNode.posZ % lodNode.posZ;
children[NS][WE] = new LodQuadTreeRegion(this, lodNode);
}
}
/**
* setChild will put a child in the given position
*
* @param NS North-South position
* @param WE West-East position
*/
public void setChild(int NS, int WE) {
int childX = lodNode.posX * 2 + WE;
int childZ = lodNode.posZ * 2 + NS;
children[NS][WE] = new LodQuadTreeRegion(this, (byte) (lodNode.detailLevel - 1), childX, childZ);
}
/**
* Update level update the level data such as levelFull and lodNodeData.
*
* @param recursiveUpdate if recursive is true the update will rise up to the level 0
*/
private void updateLevel(boolean recursiveUpdate) {
boolean isFull = true;
boolean isEmpty = true;
List<LodQuadTreeNode> dataList = new ArrayList<>();
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
if (getChild(NS,WE) != null) {
dataList.add(getChild(NS,WE).getLodNodeData());
isEmpty = false;
} else {
isFull = false;
}
}
}
nodeFull = isFull;
nodeEmpty = isEmpty;
lodNode.combineData(dataList);
if (lodNode.detailLevel < 9 && recursiveUpdate) {
this.parent.updateLevel(true);
}
}
/**
*
* method to get certain nodes from the LodQuadTree
*
* @param complexityMask set of complexity to accept
* @param getOnlyDirty if true it will return only dirty nodes
* @param getOnlyLeaf if true it will return only leaf nodes
* @return list of nodes
*/
public List<LodQuadTreeNode> getNodeListWithMask(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) {
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (isThereAnyChild()) {
//There is at least 1 child
if (!getOnlyLeaf
&& !(getOnlyDirty && !lodNode.isDirty())
&& complexityMask.contains(lodNode.getComplexity())) {
nodeList.add(lodNode);
}
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
LodQuadTreeRegion child = children[NS][WE];
if (child != null) {
nodeList.addAll(child.getNodeListWithMask(complexityMask, getOnlyDirty, getOnlyLeaf));
}
}
}
} else {
//There are no children
if (!(getOnlyDirty && !lodNode.isDirty())
&& (complexityMask.contains(lodNode.getComplexity()))){
nodeList.add(lodNode);
}
}
return nodeList;
}
/**
* This method will return all the nodes that can be rendered based on the data given
*
* @param x position of the player
* @param z position of the player
* @param targetLevel minimum level that can be rendered
* @param maxDistance maximum distance from the player
* @param minDistance minimum distance from the player
* @return
*/
public List<LodQuadTreeNode> getNodeToRender(int x, int z, int targetLevel, Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
{
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<LodQuadTreeNode> nodeList = new ArrayList<>();
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) /*|| isCoordinateInLevel(x, z)*/)) {
if (targetLevel == lodNode.detailLevel || !isNodeFull()) {
if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.getComplexity())) {
nodeList.add(lodNode);
}
} else {
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
LodQuadTreeRegion child = getChild(NS, WE);
if (child != null) {
nodeList.addAll(child.getNodeToRender(x, z, targetLevel, complexityMask, maxDistance, minDistance));
}
}
}
}
}
return nodeList;
}
/**
* Nodes that can be generated in the approximated version
* A level is generated only if it has child and is higher than the target level and in the distance range
* @param x
* @param z
* @param targetLevel
* @param complexityToGenerate
* @param maxDistance
* @param minDistance
* @return
*/
public List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) {
List<Integer> distances = new ArrayList<>();
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> nodeList = new ArrayList<>();
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) || isCoordinateInLevel(x, z))) {
if(!isThereAnyChild() || targetLevel == lodNode.detailLevel){
if (this.lodNode.getComplexity().compareTo(complexityToGenerate) <= 0 ) {
nodeList.add(new AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>(this.lodNode, min));
}
}else {
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
if (getChild(NS, WE) == null) {
setChild(NS, WE);
}
nodeList.addAll(getChild(NS, WE).getNodesToGenerate(x, z, targetLevel, complexityToGenerate, maxDistance, minDistance));
}
}
}
}
return nodeList;
}
/**
* simple getter for lodNodeData
*
* @return lodNodeData
*/
public LodQuadTreeNode getLodNodeData() {
return lodNode;
}
/**
* setter for lodNodeData, to maintain a correct relationship between level this method force update on all parent
*
* @param newLodQuadTreeNode data to set
* @param updateHigherLevel if true it will update all the upper levels.
*/
public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode, boolean updateHigherLevel) {
if (this.lodNode == null) {
this.lodNode = newLodQuadTreeNode;
} else {
this.lodNode.update(newLodQuadTreeNode);
}
//a recursive update is necessary to change higher level
if (parent != null && updateHigherLevel) parent.updateLevel(true);
}
public boolean isNodeFull() {
return nodeFull;
}
public boolean isThereAnyChild() {
return !nodeEmpty;
}
public boolean isRenderable() {
return (lodNode != null);
}
public boolean isCoordinateInLevel(int x, int z){
return (lodNode.getStartX() * lodNode.width <= x && lodNode.getStartZ() * lodNode.width <= z && lodNode.getEndX() * lodNode.width >= x && lodNode.getEndZ() * lodNode.width >= z);
}
@Override
public String toString(){
String s = lodNode.toString();
return s;
/*
if(isThereAnyChild()){
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
LodQuadTree child = children[NS][WE];
if (child != null) {
s += '\n' + child.toString();
}
}
}
}
return s;
*/
}
}