From 20d1ff6d497dc414b6df3e8097752f429e005a3d Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 7 Aug 2021 18:30:36 -0500 Subject: [PATCH] revert LodQuadTree region to LodQuadTree and other refactors --- .../LodQuadTreeDimensionFileHandler.java | 8 +- .../com/seibel/lod/objects/LodQuadTree.java | 547 ++++++++++++++++++ .../lod/objects/LodQuadTreeDimension.java | 50 +- .../seibel/lod/objects/LodQuadTreeNode.java | 22 +- .../seibel/lod/objects/LodQuadTreeRegion.java | 482 --------------- 5 files changed, 593 insertions(+), 516 deletions(-) create mode 100644 src/main/java/com/seibel/lod/objects/LodQuadTree.java delete mode 100644 src/main/java/com/seibel/lod/objects/LodQuadTreeRegion.java diff --git a/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java b/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java index a9f0f4227..96fdc9c57 100644 --- a/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java +++ b/src/main/java/com/seibel/lod/handlers/LodQuadTreeDimensionFileHandler.java @@ -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 diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTree.java b/src/main/java/com/seibel/lod/objects/LodQuadTree.java new file mode 100644 index 000000000..bb64ef38f --- /dev/null +++ b/src/main/java/com/seibel/lod/objects/LodQuadTree.java @@ -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 . + */ +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.
+ * 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 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 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 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 getNodeListWithMask(Set complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) + { + List 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 getNodeToRender(BlockPos playerPos, int targetLevel, Set complexityMask, int maxDistance, int minDistance) + { + int x = playerPos.getX(); + int z = playerPos.getZ(); + + List 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 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> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) { + + List 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> 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(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; + */ + } +} \ No newline at end of file diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java index d1a85a7b1..a9bb543de 100644 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java +++ b/src/main/java/com/seibel/lod/objects/LodQuadTreeDimension.java @@ -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> 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 getNodesWithMask(Set 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 diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java index 048a21cdf..637869ea4 100644 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java +++ b/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java @@ -287,29 +287,37 @@ public class LodQuadTreeNode return lodDataPoint; } - public void combineData(List dataList){ - if(dataList.isEmpty()){ + public void combineData(List 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; } diff --git a/src/main/java/com/seibel/lod/objects/LodQuadTreeRegion.java b/src/main/java/com/seibel/lod/objects/LodQuadTreeRegion.java deleted file mode 100644 index 4309505e0..000000000 --- a/src/main/java/com/seibel/lod/objects/LodQuadTreeRegion.java +++ /dev/null @@ -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 . - */ -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 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 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 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 getNodeListWithMask(Set complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) { - List 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 getNodeToRender(int x, int z, int targetLevel, Set complexityMask, int maxDistance, int minDistance) - { - List 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 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> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) { - - List 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> 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(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; - */ - } -} \ No newline at end of file