547 lines
16 KiB
Java
547 lines
16 KiB
Java
/*
|
|
* 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;
|
|
*/
|
|
}
|
|
} |