Files
distant-horizons-sharded/src/main/java/com/seibel/lod/objects/LodQuadTreeNode.java
T
2021-08-10 17:11:36 +02:00

441 lines
13 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.awt.Color;
import java.util.List;
import java.util.Objects;
import com.seibel.lod.enums.DistanceGenerationMode;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.gen.Heightmap;
/**
* This object contains position, size,
* and color data for an LOD object.
*
* @author Leonardo Amato
* @author James Seibel
* @version 8-8-2021
*/
public class LodQuadTreeNode
{
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
/** If we ever have to use a heightmap for any reason, use this one. */
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 10;
/** If this is set to true then toData will return
* the empty string */
public boolean dontSave = false;
/** X position relative to the Quad tree. */
public final int posX;
/** Z position relative to the Quad tree */
public final int posZ;
//Complexity indicate how the block was built. This is important because we could use
public DistanceGenerationMode complexity;
/** Indicates how complicated this node is. <br>
* Goes from 0 to 9, 0 being the deepest (block size) and 9 being the highest (region size) */
public final byte detailLevel;
/** 512 blocks wide */
public static final byte REGION_LEVEL = 9;
/** 16 blocks wide */
public static final byte CHUNK_LEVEL = 4;
/** 1 block wide */
public static final byte BLOCK_LEVEL = 0;
/** Indicates the width in blocks of this node. <br>
* Goes from 1 to 512 */
public final short width;
/** detail level 9 */
public static final short REGION_WIDTH = 512;
/** detail level 4 */
public static final short CHUNK_WIDTH = 16;
/** detail level 0 */
public static final short BLOCK_WIDTH = 1;
// these 4 value indicate the corner of the LOD block
// they can be named SW, SE, NW, NE as the cardinal direction.
// the start values should always be smaller than the end values.
// All this value could be calculated from level, posx, and posz
// so they could be removed and replaced with just a getter
public final BlockPos startBlockPos;
public final BlockPos endBlockPos;
/**
* Indicates the center of the LodNode in absolute block coordinates. This
* can be used to calculate distance from the player.
*/
public final BlockPos center;
/** holds the height, depth, and color data for this Node. */
private LodDataPoint lodDataPoint;
/** if true this node doesn't have any data */
public boolean voidNode;
/** if dirty is true, then this node have unsaved changes */
public boolean dirty;
/**TODO There should be a check for the level. Level must be positive, i could use runtime exception or simple if*/
/**TODO There should be a good way to create node that must not be saved
* For example loading a 64 region wide dimension that is fully generated is too much memory heavy.
* There should be a way to create Node that are approximated and at region level, so you could load those
* for far region, and then when you get closer you load the actual region from the file or you generate it.
* */
/**
* Creates and empty LodDataPoint
* This LodDataPoint only contains the position data
* @param detailLevel of the node
* @param posX position x in the level
* @param posZ position z in the level
*/
public LodQuadTreeNode(byte detailLevel, int posX, int posZ)
{
this.detailLevel = detailLevel;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, detailLevel);
startBlockPos = new BlockPos(posX * width, 0, posZ * width);
endBlockPos = new BlockPos(startBlockPos.getX() + width - 1, 0, startBlockPos.getZ() + width - 1);
center = new BlockPos(startBlockPos.getX() + width/2, 0, startBlockPos.getZ() + width/2);
lodDataPoint = new LodDataPoint();
complexity = DistanceGenerationMode.NONE;
dirty = true;
voidNode = true;
dontSave = true;
}
/**
* Constructor for a LodNodeData
* @param level
* @param posX
* @param posZ
* @param height
* @param depth
* @param color
* @param complexity
*/
public LodQuadTreeNode(byte level, int posX, int posZ, short height, short depth , Color color, DistanceGenerationMode complexity)
{
this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity);
}
/**
* Constructor for a LodNodeData
* @param level
* @param posX
* @param posZ
* @param height
* @param depth
* @param color
* @param complexity
*/
public LodQuadTreeNode(byte level, int posX, int posZ, int height, int depth, Color color, DistanceGenerationMode complexity)
{
this(level, posX, posZ, new LodDataPoint(height,depth,color), complexity);
}
/**
* Constructor for a LodNodeData
* @param detailLevel level of this
* @param posX
* @param posZ
* @param lodDataPoint
* @param complexity
*/
public LodQuadTreeNode(byte detailLevel, int posX, int posZ, LodDataPoint lodDataPoint, DistanceGenerationMode complexity)
{
this.detailLevel = detailLevel;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, detailLevel);
startBlockPos = new BlockPos(posX * width, 0, posZ * width);
endBlockPos = new BlockPos(startBlockPos.getX() + width - 1, 0, startBlockPos.getZ() + width - 1);
center = new BlockPos(startBlockPos.getX() + width/2, 0, startBlockPos.getZ() + width/2);
this.lodDataPoint = lodDataPoint;
this.complexity = complexity;
dirty = true;
voidNode = false;
dontSave = false;
}
/**
* @throws IllegalArgumentException if the data string doesn't have the correct number of delimited entries
*/
public LodQuadTreeNode(String data) throws IllegalArgumentException
{
// make sure there are the correct number of entries
// in the data string
int count = 0;
for(int i = 0; i < data.length(); i++)
if(data.charAt(i) == DATA_DELIMITER)
count++;
if(count != NUMBER_OF_DELIMITERS)
throw new IllegalArgumentException("LodQuadTreeNode constructor givin an invalid string. The data given had " + count + " delimiters when it should have had " + NUMBER_OF_DELIMITERS + ".");
// start reading the data string
int index = 0;
int lastIndex = 0;
index = data.indexOf(DATA_DELIMITER, 0);
this.detailLevel = (byte) Integer.parseInt(data.substring(0,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.posX = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.posZ = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.complexity = DistanceGenerationMode.valueOf(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
short height = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
short depth = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int red = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int green = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int blue = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int alpha = Integer.parseInt(data.substring(lastIndex+1,index));
Color color = new Color(red,green,blue,alpha);
lodDataPoint = new LodDataPoint(height,depth,color);
int isVoid = Integer.parseInt(data.substring(lastIndex+1,index));
this.voidNode = (isVoid == 1);
width = (short) Math.pow(2, detailLevel);
startBlockPos = new BlockPos(posX * width, 0, posZ * width);
endBlockPos = new BlockPos(startBlockPos.getX() + width - 1, 0, startBlockPos.getZ() + width - 1);
center = new BlockPos(startBlockPos.getX() + width/2, 0, startBlockPos.getZ() + width/2);
dirty = false;
dontSave = false;
}
//================//
// data processes //
//================//
/**
* Replaces the data in this object.
*/
public void updateData(LodQuadTreeNode lodQuadTreeNode)
{
if (lodQuadTreeNode == null)
return;
lodDataPoint = lodQuadTreeNode.lodDataPoint;
complexity = lodQuadTreeNode.complexity;
voidNode = lodQuadTreeNode.voidNode;
dontSave = lodQuadTreeNode.dontSave;
dirty = true;
}
/**
* Combines and averages the data from a list of LodQuadTreeNodes into this node.
*/
public void combineData(List<LodQuadTreeNode> dataList)
{
if(dataList.isEmpty())
{
lodDataPoint = new LodDataPoint();
}
else
{
// TODO would it be better to use the average height/depth?
// get the lowest height from the all the given LodQuadTreeNodes
short height = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().height).max().getAsInt();
// get the highest depth
short depth = (short) dataList.stream().mapToInt(x -> (int) x.getLodDataPoint().depth).min().getAsInt();
// get the average color
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 is equal to the lowest complexity of the list
DistanceGenerationMode minComplexity = DistanceGenerationMode.SERVER;
for(LodQuadTreeNode node: dataList)
{
if (minComplexity.compareTo(node.complexity) > 0)
{
minComplexity = node.complexity;
}
}
complexity = minComplexity;
voidNode = lodDataPoint.isEmpty();
}
dirty = true;
dontSave = false;
}
//===================//
// basic comparisons //
//===================//
public int compareComplexity(LodQuadTreeNode other)
{
return this.complexity.compareTo(other.complexity);
}
public boolean equals(LodQuadTreeNode other)
{
return (this.complexity == other.complexity
&& this.detailLevel == other.detailLevel
&& this.posX == other.posX
&& this.posZ == other.posZ
&& this.lodDataPoint.equals(other.lodDataPoint)
&& this.complexity == other.complexity
&& this.voidNode == other.voidNode);
}
//=========================//
// basic setters / getters //
//=========================//
public LodDataPoint getLodDataPoint()
{
return lodDataPoint;
}
public void setLodDataPoint(LodDataPoint newLodDataPoint)
{
lodDataPoint = newLodDataPoint;
// update if this is node is currently void
voidNode = (lodDataPoint == null);
}
public boolean isVoidNode()
{
return voidNode;
}
public boolean isDirty()
{
return dirty;
}
//========//
// output //
//========//
@Override
public int hashCode()
{
return Objects.hash(this.complexity, this.detailLevel, this.posX, this.posZ, this.lodDataPoint, this.voidNode);
}
/**
* Outputs all data in a csv format
*/
public String toData()
{
if (dontSave)
return "";
String s = Integer.toString(detailLevel) + DATA_DELIMITER
+ Integer.toString(posX) + DATA_DELIMITER
+ Integer.toString(posZ) + DATA_DELIMITER
+ complexity.toString() + DATA_DELIMITER
+ Integer.toString((lodDataPoint.height)) + DATA_DELIMITER
+ Integer.toString((lodDataPoint.depth)) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getRed()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getGreen()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getBlue()) + DATA_DELIMITER
+ Integer.toString(lodDataPoint.color.getAlpha()) + DATA_DELIMITER
+ Integer.toString(voidNode ? 1 : 0);
return s;
}
@Override
public String toString()
{
return this.toData();
}
}