QuadTree version, still doesn't work

This commit is contained in:
Morippi
2021-07-05 14:46:51 +02:00
parent 7ec7d6bdc5
commit 72bcc87de7
7 changed files with 1678 additions and 0 deletions
@@ -0,0 +1,172 @@
package com.seibel.lod.builders;
import com.seibel.lod.enums.LodDetail;
import com.seibel.lod.handlers.LodConfig;
import com.seibel.lod.objects.LodChunk;
import com.seibel.lod.objects.LodDataPoint;
import com.seibel.lod.objects.LodDimension;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.objects.quadTree.LodNodeData;
import com.seibel.lod.objects.quadTree.LodQuadTreeWorld;
import kaptainwutax.biomeutils.source.BiomeSource;
import kaptainwutax.biomeutils.source.OverworldBiomeSource;
import kaptainwutax.mcutils.state.Dimension;
import kaptainwutax.mcutils.version.MCVersion;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.DimensionType;
import net.minecraft.world.IWorld;
import net.minecraft.world.chunk.IChunk;
import java.awt.*;
import java.util.Iterator;
import java.util.OptionalLong;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LodNodeBuilder {
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
private long seed;
private DimensionType dimension;
/** Default size of any LOD regions we use */
public int regionWidth = 5;
/** fast biome calculator */
private BiomeSource biomeSource;
//Biome biome=biomeSource.getBiome(x,y,z); // here y is always 0 no matter what you pass
public LodNodeBuilder(){
}
public setApproxGenerator(long seed){
//Dimension.OVERWORLD;
//Dimension.END;
//Dimension.NETHER;
biomeSource = BiomeSource.of(Dimension.OVERWORLD ,MCVersion.v1_16_4, seed);
}
public void generateLodNodeAsync(List<LodNodeData> dataList){
Thread thread = new Thread(() ->{
for(LodNodeData data : dataList){
}
});
thread.setPriority(4);
lodGenThreadPool.execute(thread);
return;
}
public void generateLodNodeAsync(IChunk chunk, LodWorld lodWorld, IWorld world)
{
if (lodWorld == null || !lodWorld.getIsWorldLoaded())
return;
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null)
return;
Thread thread = new Thread(() ->
{
try
{
DimensionType dim = world.getDimensionType();
LodChunk lod = generateLodFromChunk(chunk, config);
LodDimension lodDim;
if (lodWorld.getLodDimension(dim) == null)
{
lodDim = new LodDimension(dim, lodWorld, regionWidth);
lodWorld.addLodDimension(lodDim);
}
else
{
lodDim = lodWorld.getLodDimension(dim);
}
lodDim.addLod(lod);
}
catch(IllegalArgumentException | NullPointerException e)
{
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
}
});
lodGenThreadPool.execute(thread);
return;
}
/**
* Creates a LodChunk for a chunk in the given world.
*
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk) throws IllegalArgumentException
{
return generateLodFromChunk(chunk, new LodBuilderConfig());
}
/**
* Creates a LodChunk for a chunk in the given world.
*
* @throws IllegalArgumentException
* thrown if either the chunk or world is null.
*/
public LodChunk generateLodFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException
{
if(chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
LodDataPoint[][] dataPoints = new LodDataPoint[detail.lengthCount][detail.lengthCount];
for(int i = 0; i < detail.lengthCount * detail.lengthCount; i++)
{
int startX = detail.startX[i];
int startZ = detail.startZ[i];
int endX = detail.endX[i];
int endZ = detail.endZ[i];
Color color;
color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
short height;
short depth;
if (!config.useHeightmap)
{
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
}
else
{
height = determineHeightPoint(chunk.getHeightmap(LodChunk.DEFAULT_HEIGHTMAP), startX, startZ, endX, endZ);
depth = 0;
}
int x = i / detail.lengthCount;
int z = i % detail.lengthCount;
dataPoints[x][z] = new LodDataPoint(height, depth, color);
}
return new LodChunk(chunk.getPos(), dataPoints, detail);
}
}
@@ -0,0 +1,324 @@
package com.seibel.lod.handlers;
import com.seibel.lod.objects.quadTree.LodNodeData;
import com.seibel.lod.objects.quadTree.LodQuadTree;
import com.seibel.lod.objects.quadTree.LodQuadTreeDimension;
import com.seibel.lod.proxy.ClientProxy;
import java.io.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LodQuadTreeDimensionFileHandler {
/** This is what separates each piece of data */
public static final char DATA_DELIMITER = ',';
private LodQuadTreeDimension loadedDimension = null;
public long regionLastWriteTime[][];
private File dimensionDataSaveFolder;
/** lod */
private final String FILE_NAME_PREFIX = "lod";
/** .txt */
private final String FILE_EXTENSION = ".txt";
/** This is the file version currently accepted by this
* file handler, older versions (smaller numbers) will be deleted and overwritten,
* newer versions (larger numbers) will be ignored and won't be read. */
public static final int LOD_SAVE_FILE_VERSION = 2;
/** This is the string written before the file version */
private static final String LOD_FILE_VERSION_PREFIX = "lod_save_file_version";
/** Allow saving asynchronously, but never try to save multiple regions
* at a time */
private ExecutorService fileWritingThreadPool = Executors.newSingleThreadExecutor();
public LodQuadTreeDimensionFileHandler(File newSaveFolder, LodQuadTreeDimension newLoadedDimension)
{
if (newSaveFolder == null)
throw new IllegalArgumentException("LodDimensionFileHandler requires a valid File location to read and write to.");
dimensionDataSaveFolder = newSaveFolder;
loadedDimension = newLoadedDimension;
// these two variable are used in sync with the LodDimension
regionLastWriteTime = new long[loadedDimension.getWidth()][loadedDimension.getWidth()];
for(int i = 0; i < loadedDimension.getWidth(); i++)
for(int j = 0; j < loadedDimension.getWidth(); j++)
regionLastWriteTime[i][j] = -1;
}
//================//
// read from file //
//================//
/**
* Return the LodQuadTree region at the given coordinates.
* (null if the file doesn't exist)
*/
public LodQuadTree loadRegionFromFile(int regionX, int regionZ)
{
String fileName = getFileNameAndPathForRegion(regionX, regionZ);
File f = new File(fileName);
if (!f.exists())
{
// there wasn't a file, don't
// return anything
return null;
}
List<LodNodeData> dataList = new ArrayList<>();
try
{
BufferedReader br = new BufferedReader(new FileReader(f));
String s = br.readLine();
int fileVersion = -1;
if(s != null && !s.isEmpty())
{
// try to get the file version
try
{
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
}
catch(NumberFormatException | StringIndexOutOfBoundsException e)
{
// this file doesn't have a version
// keep the version as -1
fileVersion = -1;
}
// check if this file can be read by this file handler
if(fileVersion < LOD_SAVE_FILE_VERSION)
{
// the file we are reading is an older version,
// close the reader and delete the file.
br.close();
f.delete();
ClientProxy.LOGGER.info("Outdated LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
", version requested: " + LOD_SAVE_FILE_VERSION +
" File was been deleted.");
return null;
}
else if(fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// close the reader and ignore the file, we don't
// want to accidently delete anything the user may want.
br.close();
ClientProxy.LOGGER.info("Newer LOD region file for region: (" + regionX + "," + regionZ + ") version: " + fileVersion +
", version requested: " + LOD_SAVE_FILE_VERSION +
" this region will not be written to in order to protect the newer file.");
return null;
}
}
else
{
// there is no data in this file
br.close();
return null;
}
// this file is a readable version, begin reading the file
s = br.readLine();
while(s != null && !s.isEmpty())
{
try
{
dataList.add(new LodNodeData(s));
}
catch(IllegalArgumentException e)
{
// we were unable to create this chunk
// for whatever reason.
// skip to the next chunk
ClientProxy.LOGGER.warn(e.getMessage());
}
s = br.readLine();
}
br.close();
}
catch (IOException e)
{
// the buffered reader encountered a
// problem reading the file
return null;
}
return new LodQuadTree(dataList,regionX, regionZ);
}
//==============//
// Save to File //
//==============//
/**
* Save all dirty regions in this LodDimension to file.
*/
public void saveDirtyRegionsToFileAsync()
{
fileWritingThreadPool.execute(saveDirtyRegionsThread);
}
private Thread saveDirtyRegionsThread = new Thread(() ->
{
for(int i = 0; i < loadedDimension.getWidth(); i++)
{
for(int j = 0; j < loadedDimension.getWidth(); j++)
{
if(loadedDimension.isRegionDirty[i][j] && loadedDimension.regions[i][j] != null)
{
saveRegionToDisk(loadedDimension.regions[i][j]);
loadedDimension.isRegionDirty[i][j] = false;
}
}
}
});
/**
* Save a specific region to disk.<br>
* Note: <br>
* 1. If a file already exists for a newer version
* the file won't be written.<br>
* 2. This will save to the LodDimension that this
* handler is associated with.
*/
private void saveRegionToDisk(LodQuadTree region)
{
// convert chunk coordinates to region
// coordinates
int x = region.getLodNodeData().posX;
int z = region.getLodNodeData().posX;
File f = new File(getFileNameAndPathForRegion(x, z));
try
{
// make sure the file and folder exists
if (!f.exists())
{
// the file doesn't exist,
// create it and the folder if need be
if(!f.getParentFile().exists())
f.getParentFile().mkdirs();
f.createNewFile();
}
else
{
// the file exists, make sure it
// is the correct version.
// (to make sure we don't overwrite a newer
// version file if it exists)
BufferedReader br = new BufferedReader(new FileReader(f));
String s = br.readLine();
int fileVersion = LOD_SAVE_FILE_VERSION;
if(s != null && !s.isEmpty())
{
// try to get the file version
try
{
fileVersion = Integer.parseInt(s.substring(s.indexOf(' ')).trim());
}
catch(NumberFormatException | StringIndexOutOfBoundsException e)
{
// this file doesn't have a correctly formated version
// just overwrite the file
}
}
br.close();
// check if this file can be written to by the file handler
if(fileVersion <= LOD_SAVE_FILE_VERSION)
{
// we are good to continue and overwrite the old file
}
else //if(fileVersion > LOD_SAVE_FILE_VERSION)
{
// the file we are reading is a newer version,
// don't write anything, we don't want to accidently
// delete anything the user may want.
return;
}
}
FileWriter fw = new FileWriter(f);
// add the version of this file
fw.write(LOD_FILE_VERSION_PREFIX + " " + LOD_SAVE_FILE_VERSION + "\n");
// add each LodChunk to the file
for(LodNodeData lodNodeData : Collections.unmodifiableList(region.getNodeList(false, true, true))) {
fw.write(lodNodeData.toData() + "\n");
lodNodeData.dirty = false;
}
fw.close();
}
catch(Exception e)
{
ClientProxy.LOGGER.error("LOD file write error: " + e.getMessage());
}
}
//================//
// helper methods //
//================//
/**
* Return the name of the file that should contain the
* region at the given x and z. <br>
* Returns null if this object isn't ready to read and write. <br><br>
*
* example: "lod.FULL.0.0.txt"
*/
private String getFileNameAndPathForRegion(int regionX, int regionZ)
{
try
{
// saveFolder is something like
// ".\Super Flat\DIM-1\data"
// or
// ".\Super Flat\data"
return dimensionDataSaveFolder.getCanonicalPath() + File.separatorChar +
FILE_NAME_PREFIX + regionX + "." + regionZ + FILE_EXTENSION;
}
catch(IOException e)
{
return null;
}
}
}
@@ -0,0 +1,246 @@
package com.seibel.lod.objects.quadTree;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import java.awt.*;
import java.util.*;
import java.util.List;
public class LodNodeData {
/** This is what separates each piece of data in the toData method */
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
/** this is how many pieces of data are exported when toData is called */
public static final int NUMBER_OF_DELIMITERS = 9;
private static final Color INVISIBLE = new Color(0,0,0,0);
//level height goes from 0 to 9 with 0 the deepest (block size) and 9 the highest (region size)
public final byte level;
public static final byte REGION_LEVEL = 9; //at level 9 we reach the dimension of a single region
public static final byte CHUNK_LEVEL = 4; //at level 4 we reach the dimension of a single chunk
public static final byte BLOCK_LEVEL = 0; //at level 0 we reach the dimension of a single block
//indicate the width in block of this node (goes from 1 to 512)
public final short width;
public static final short REGION_WIDTH = 512; //at level 9 we reach the dimension of a single region
public static final short CHUNK_WIDTH = 16; //at level 4 we reach the dimension of a single chunk
public static final short BLOCK_WIDTH = 1; //at level 0 we reach the dimension of a single block
//this 2 values indicate the position of the LOD in the relative Level
//this will be useful in the generation process
public final int posX;
public final int posZ;
//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 and levelWidth
//so they could be removed and replaced with just a getter
public final int startX;
public final int startZ;
public final int endX;
public final int endZ;
//these 2 value indicate the center of the LodNode in real coordinate. This
//can be used to calculate the distance from the player
public final int centerX;
public final int centerZ;
/** highest point */
public short height;
/** lowest point */
public short depth;
/** The average color for the 6 cardinal directions */
public Color color;
public boolean real;
public boolean voidNode;
//if dirty is true, then this node have unsaved changes
public boolean dirty;
/**
* Creates and empty LodDataPoint
* This LodDataPoint only contains the position data
* @param level of the node
* @param posX position x in the level
* @param posZ posizion z in the level
*/
public LodNodeData(byte level, int posX, int posZ){
this.level = level;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, level);
startX = posX * width;
startZ = posZ * width;
endX = startX + width - 1;
endZ = startZ + width - 1;
centerX = startX + width/2;
centerZ = startZ + width/2;
height = -1;
depth = -1;
color = INVISIBLE;
real = false;
dirty = true;
voidNode = true;
}
/**
* Constructor for a LodNodeData
* @param level level of this
* @param posX
* @param posZ
* @param height
* @param depth
* @param color
* @param real
*/
public LodNodeData(byte level, int posX, int posZ, short height, short depth, Color color, boolean real){
this.level = level;
this.posX = posX;
this.posZ = posZ;
width = (short) Math.pow(2, level);
startX = posX * width;
startZ = posZ * width;
endX = startX + width - 1;
endZ = startZ + width - 1;
centerX = startX + width/2;
centerZ = startZ + width/2;
this.height = height;
this.depth = depth;
this.color = color;
this.real = real;
dirty = true;
voidNode = false;
}
public LodNodeData(byte level, int posX, int posZ, int height, int depth, Color color, boolean real) {
this(level, posX, posZ, (short) height,(short) depth, color, real);
}
public LodNodeData(String data)
{
int index = 0;
int lastIndex = 0;
index = data.indexOf(DATA_DELIMITER, 0);
this.level = (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.height = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
this.depth = (short) Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int r = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int g = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int b = Integer.parseInt(data.substring(lastIndex+1,index));
lastIndex = index;
index = data.indexOf(DATA_DELIMITER, lastIndex+1);
int a = Integer.parseInt(data.substring(lastIndex+1,index));
this.color = new Color(r,g,b,a);
int val = Integer.parseInt(data.substring(lastIndex+1,index));
this.real = (val == 1);
width = (short) Math.pow(2, level);
val = Integer.parseInt(data.substring(lastIndex+1,index));
this.voidNode = (val == 1);
startX = posX * width;
startZ = posZ * width;
endX = startX + width - 1;
endZ = startZ + width - 1;
centerX = startX + width/2;
centerZ = startZ + width/2;
dirty = false;
}
public void update(LodNodeData lodNodeData){
this.height = lodNodeData.height;
this.depth = lodNodeData.depth;
this.color = lodNodeData.color;
this.real = lodNodeData.real;
dirty = true;
}
public void combineData(List<LodNodeData> dataList){
if(dataList.isEmpty()){
height = -1;
depth = -1;
color = INVISIBLE;
}else {
short height = (short) dataList.stream().mapToInt(x -> (int) x.height).min().getAsInt();
short depth = (short) dataList.stream().mapToInt(x -> (int) x.depth).max().getAsInt();
height = height;
depth = depth;
color = dataList.get(0).color;
real = dataList.stream().filter(x -> x.real).count() == 4;
voidNode = dataList.stream().filter(x -> !x.voidNode).count() == 0;
}
dirty = true;
}
public int hashCode(){
return Objects.hash(this.real, this.level, this.posX, this.posZ, this.color, this.real, this.voidNode);
}
public boolean equals(LodNodeData other){
return (this.real == other.real
&& this.level == other.level
&& this.posX == other.posX
&& this.posZ == other.posZ
&& this.color.equals(other.color)
&& this.real == other.real
&& this.voidNode == other.voidNode);
}
/**
* Outputs all data in a csv format
*/
public String toData(){
String s = Integer.toString(level) + DATA_DELIMITER
+ posX + DATA_DELIMITER
+ posZ + DATA_DELIMITER
+ Integer.toString(height) + DATA_DELIMITER
+ Integer.toString(depth) + DATA_DELIMITER
+ color.getRed() + DATA_DELIMITER
+ color.getGreen() + DATA_DELIMITER
+ color.getBlue() + DATA_DELIMITER
+ color.getAlpha() + DATA_DELIMITER;
int val = real ? 1 : 0;
s += val + DATA_DELIMITER;
val = voidNode ? 1 : 0;
s += val + DATA_DELIMITER;
return s;
}
public String toString()
{
return this.toData();
}
}
@@ -0,0 +1,363 @@
package com.seibel.lod.objects.quadTree;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;
/**
* 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 LodQuadTree {
//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 final LodNodeData lodNodeData;
/*
.____.____.
| 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 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(int regionX, int regionZ) {
this(null, new LodNodeData(LodNodeData.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 LodQuadTree(LodQuadTree parent, byte level, int posX, int posZ) {
this(parent, new LodNodeData(level, posX, posZ));
}
/**
* Constructor for generic level via the LodNodeData
*
* @param lodNodeData object containing all the information of this node
*/
public LodQuadTree(LodQuadTree parent, LodNodeData lodNodeData) {
this.parent = parent;
this.lodNodeData = lodNodeData;
this.children = new LodQuadTree[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 LodQuadTree(List<LodNodeData> dataList, int regionX, int regionZ) {
this(null, new LodNodeData(LodNodeData.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<LodNodeData> dataList, boolean updateHigherLevel) {
for (LodNodeData lodNodeData : dataList) {
//this is slow, you could set update to false and use an only top down update method.
this.setNodeAtLowerLevel(lodNodeData, updateHigherLevel);
}
}
/**
* @param newLodNodeData 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(LodNodeData newLodNodeData, boolean updateHigherLevel) {
//check if we try to introduce a level that is higher or equal than the current one
byte targetLevel = newLodNodeData.level;
byte currentLevel = lodNodeData.level;
if (targetLevel < currentLevel) {
int posX = newLodNodeData.posX;
int posZ = newLodNodeData.posZ;
short widthRatio = (short) (lodNodeData.width / newLodNodeData.width);
int NS = (posX / widthRatio) % lodNodeData.posX;
int WE = (posZ / widthRatio) % lodNodeData.posZ;
if (getChild(NS, WE) == null) {
setChild(NS, WE);
}
LodQuadTree child = getChild(NS, WE);
if (!newLodNodeData.real && child.isNodeReal()) {
return false;
} else {
if (targetLevel == currentLevel - 1) {
child.setLodNodeData(lodNodeData, updateHigherLevel);
return true;
} else {
return child.setNodeAtLowerLevel(lodNodeData, updateHigherLevel);
}
}
} else {
return false;
}
}
public LodQuadTree getChild(int NS, int WE) {
return children[NS][WE];
}
/**
* setChild will put a child with given data in the given position
*
* @param newLodNodeData data to put in the child
* @param NS North-South position
* @param WE West-East position
*/
public void setChild(LodNodeData newLodNodeData, int NS, int WE) {
if (newLodNodeData.level == lodNodeData.level - 1) {
children[NS][WE] = new LodQuadTree(this, lodNodeData);
}
}
/**
* setChild will put a child with given data in the given position
*
* @param newLodNodeData data to put in the child
*/
public void setChild(LodNodeData newLodNodeData) {
if (newLodNodeData.level == lodNodeData.level - 1) {
int NS = newLodNodeData.posX % lodNodeData.posX;
int WE = newLodNodeData.posZ % lodNodeData.posZ;
children[NS][WE] = new LodQuadTree(this, lodNodeData);
}
}
/**
* 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 = lodNodeData.posX * 2 + WE;
int childZ = lodNodeData.posZ * 2 + NS;
children[NS][WE] = new LodQuadTree(this, (byte) (lodNodeData.level - 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<LodNodeData> dataList = new ArrayList<>();
for (int NS = 0; NS <= 1; NS++) {
for (int WE = 0; WE <= 1; WE++) {
if (children[NS][WE] != null) {
dataList.add(children[NS][WE].getLodNodeData());
isEmpty = false;
} else {
isFull = false;
}
}
}
nodeFull = isFull;
nodeEmpty = isEmpty;
lodNodeData.combineData(dataList);
if (lodNodeData.level > 0 && recursiveUpdate) {
this.parent.updateLevel(recursiveUpdate);
}
}
/**
* method to get certain nodes from the LodQuadTree
*
* @param getOnlyReal if true it will return only real nodes
* @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<LodNodeData> getNodeList(boolean getOnlyReal, boolean getOnlyDirty, boolean getOnlyLeaf) {
List<LodNodeData> nodeList = new ArrayList<>();
if (!isThereAnyChild()) {
if (!(getOnlyReal && !lodNodeData.dirty)
&& !(getOnlyReal && !lodNodeData.real)) {
nodeList.add(lodNodeData);
}
} else {
if (!getOnlyLeaf
&& !(getOnlyDirty && !lodNodeData.dirty)
&& !(getOnlyReal && !lodNodeData.real)) {
nodeList.add(lodNodeData);
}
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.getNodeList(getOnlyReal, getOnlyDirty, getOnlyLeaf));
}
}
}
}
return nodeList;
}
/**
* method to get certain nodes from the LodQuadTree
* @return list of nodes
*/
public List<LodNodeData> getNodeToRender(int x, int z, byte targetLevel, int maxDistance, int minDistance){
int distance = (int) Math.sqrt(Math.pow(x + lodNodeData.centerX,2) + Math.pow(z + lodNodeData.centerZ,2));
List<LodNodeData> nodeList = new ArrayList<>();
if(distance > maxDistance || distance < minDistance || targetLevel > lodNodeData.level) {
return nodeList;
}
if(targetLevel == lodNodeData.level || !isThereAnyChild()){
if(!lodNodeData.voidNode){
nodeList.add(lodNodeData);
return nodeList;
}else{
return nodeList;
}
} else {
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.getNodeToRender(x,z,targetLevel,maxDistance,minDistance));
}
}
}
}
return nodeList;
}
/**
* method to get certain nodes from the LodQuadTree
* @return list of nodes
*/
public List<AbstractMap.SimpleEntry<LodNodeData,Integer>> getNodeToGenerate(int x, int z, byte targetLevel, int maxDistance, int minDistance){
int distance = (int) Math.sqrt(Math.pow(x + lodNodeData.centerX,2) + Math.pow(z + lodNodeData.centerZ,2));
List<AbstractMap.SimpleEntry<LodNodeData,Integer>> nodeList = new ArrayList<>();
if(distance > maxDistance || distance < minDistance || targetLevel > lodNodeData.level) {
return nodeList;
}
if(targetLevel == lodNodeData.level){
return nodeList;
} else {
if(!isThereAnyChild()){
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.getNodeToGenerate(x,z,targetLevel,maxDistance,minDistance));
}
}
}
}else{
nodeList.add( new AbstractMap.SimpleEntry<>(lodNodeData,distance));
}
}
return nodeList;
}
/**
* simple getter for lodNodeData
*
* @return lodNodeData
*/
public LodNodeData getLodNodeData() {
return lodNodeData;
}
/**
* setter for lodNodeData, to maintain a correct relationship between level this method force update on all parent
*
* @param newLodNodeData data to set
* @param updateHigherLevel if true it will update all the upper levels.
*/
public void setLodNodeData(LodNodeData newLodNodeData, boolean updateHigherLevel) {
this.lodNodeData.update(lodNodeData);
//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 isNodeReal() {
return lodNodeData.real;
}
public boolean isRenderable() {
return (lodNodeData != null);
}
}
/*
EXAMPLES OF USES
*/
@@ -0,0 +1,418 @@
package com.seibel.lod.objects.quadTree;
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
import com.seibel.lod.objects.LodWorld;
import com.seibel.lod.objects.RegionPos;
import com.seibel.lod.util.LodUtil;
import net.minecraft.client.Minecraft;
import net.minecraft.world.DimensionType;
import net.minecraft.world.server.ServerChunkProvider;
import net.minecraft.world.server.ServerWorld;
import java.io.File;
import java.io.IOException;
import java.util.List;
public class LodQuadTreeDimension {
public final DimensionType dimension;
private volatile int width;
private volatile int halfWidth;
public long seed;
public volatile LodQuadTree regions[][];
public volatile boolean isRegionDirty[][];
private int centerX;
private int centerZ;
private LodQuadTreeDimensionFileHandler fileHandler;
public LodQuadTreeDimension(DimensionType newDimension, LodWorld lodWorld, int newMaxWidth)
{
dimension = newDimension;
width = newMaxWidth;
try
{
Minecraft mc = Minecraft.getInstance();
File saveDir;
if(mc.isIntegratedServerRunning())
{
// local world
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
seed = serverWorld.getSeed();
// provider needs a separate variable to prevent
// the compiler from complaining
ServerChunkProvider provider = serverWorld.getChunkProvider();
saveDir = new File(provider.getSavedData().folder.getCanonicalFile().getPath() + File.separatorChar + "lod");
}
else
{
// connected to server
saveDir = new File(mc.gameDir.getCanonicalFile().getPath() +
File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.world));
}
fileHandler = new LodQuadTreeDimensionFileHandler(saveDir, this);
}
catch(IOException e)
{
// the file handler wasn't able to be created
// we won't be able to read or write any files
}
regions = new LodQuadTree[width][width];
isRegionDirty = new boolean[width][width];
// populate isRegionDirty
for(int i = 0; i < width; i++)
for(int j = 0; j < width; j++)
isRegionDirty[i][j] = false;
centerX = 0;
centerZ = 0;
halfWidth = (int)Math.floor(width / 2);
}
/**
* Move the center of this LodDimension and move all owned
* regions over by the given x and z offset.
*/
public void move(int xOffset, int zOffset)
{
// if the x or z offset is equal to or greater than
// the total size, just delete the current data
// and update the centerX and/or centerZ
if (Math.abs(xOffset) >= width || Math.abs(zOffset) >= width)
{
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
regions[x][z] = null;
}
}
// update the new center
centerX += xOffset;
centerZ += zOffset;
return;
}
// X
if(xOffset > 0)
{
// move everything over to the left (as the center moves to the right)
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
if(x + xOffset < width)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
else
{
// move everything over to the right (as the center moves to the left)
for(int x = width - 1; x >= 0; x--)
{
for(int z = 0; z < width; z++)
{
if(x + xOffset >= 0)
regions[x][z] = regions[x + xOffset][z];
else
regions[x][z] = null;
}
}
}
// Z
if(zOffset > 0)
{
// move everything up (as the center moves down)
for(int x = 0; x < width; x++)
{
for(int z = 0; z < width; z++)
{
if(z + zOffset < width)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
else
{
// move everything down (as the center moves up)
for(int x = 0; x < width; x++)
{
for(int z = width - 1; z >= 0; z--)
{
if(z + zOffset >= 0)
regions[x][z] = regions[x][z + zOffset];
else
regions[x][z] = null;
}
}
}
// update the new center
centerX += xOffset;
centerZ += zOffset;
}
/**
* Gets the region at the given X and Z
* <br>
* Returns null if the region doesn't exist
* or is outside the loaded area.
*/
public LodQuadTree getRegion(int regionX, int regionZ)
{
int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth;
if (!regionIsInRange(regionX, regionZ))
// out of range
return null;
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = getRegionFromFile(regionX, regionZ);
if (regions[xIndex][zIndex] == null)
{
regions[xIndex][zIndex] = new LodQuadTree(regionX, regionZ);
}
}
return regions[xIndex][zIndex];
}
/**
* Overwrite the LodRegion at the location of newRegion with newRegion.
* @throws ArrayIndexOutOfBoundsException if newRegion is outside what can be stored in this LodDimension.
*/
public void setRegion(LodQuadTree newRegion) throws ArrayIndexOutOfBoundsException
{
int xIndex = (newRegion.getLodNodeData().posX - centerX) + halfWidth;
int zIndex = (centerZ - newRegion.getLodNodeData().posZ) + halfWidth;
if (!regionIsInRange(newRegion.getLodNodeData().posX, newRegion.getLodNodeData().posZ))
// out of range
throw new ArrayIndexOutOfBoundsException();
regions[xIndex][zIndex] = newRegion;
}
/**
* Add the given LOD to this dimension at the coordinate
* stored in the LOD. If an LOD already exists at the given
* coordinates it will be overwritten.
*/
public void addNode(LodNodeData lodNodeData)
{
RegionPos pos = new RegionPos(
lodNodeData.posX / lodNodeData.width,
lodNodeData.posZ / lodNodeData.width
);
// don't continue if the region can't be saved
if (!regionIsInRange(pos.x, pos.z))
{
return;
}
LodQuadTree region = getRegion(pos.x, pos.z);
if (region == null)
{
// if no region exists, create it
region = new LodQuadTree(pos.x, pos.z);
setRegion(region);
}
region.setNodeAtLowerLevel(lodNodeData, true);
// don't save empty place holders to disk
if (!lodNodeData.real && fileHandler != null)
{
// mark the region as dirty so it will be saved to disk
int xIndex = (pos.x - centerX) + halfWidth;
int zIndex = (pos.z - centerZ) + halfWidth;
isRegionDirty[xIndex][zIndex] = true;
fileHandler.saveDirtyRegionsToFileAsync();
}
}
/**
* Get the LodNodeData at the given X and Z position in the level
* in this dimension.
* <br>
* Returns null if the LodChunk doesn't exist or
* is outside the loaded area.
*/
public LodNodeData getLodFromCoordinates(int posX, int posZ, byte level)
{
/*TODO */
return null;
/*
RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ));
LodQuadTree region = getRegion(pos.x, pos.z);
if(region == null)
return null;
return region.getNode(chunkX, chunkZ);
*/
}
/**
* method to get all the nodes that have to be rendered based on the position of the player
* @return list of nodes
*/
public List getNodeToRender(int x, int z){
//BASIC IDEA
//Get all the node to render from every region
//combine them toghether
return null;
}
/**
* method to get all the nodes that have to be generated based on the position of the player
* @return list of nodes
*/
public List getNodeToGenerate(int x, int z){
//BASIC IDEA
//Get all the node to generate from every region
//combine them toghether
//sort them based on the distance to player
//this way
return null;
}
/**
* Get the region at the given X and Z coordinates from the
* RegionFileHandler.
*/
public LodQuadTree getRegionFromFile(int regionX, int regionZ)
{
if (fileHandler != null)
return fileHandler.loadRegionFromFile(regionX, regionZ);
else
return null;
}
/**
* Returns whether the region at the given X and Z coordinates
* is within the loaded range.
*/
public boolean regionIsInRange(int regionX, int regionZ)
{
int xIndex = (regionX - centerX) + halfWidth;
int zIndex = (regionZ - centerZ) + halfWidth;
return xIndex >= 0 && xIndex < width && zIndex >= 0 && zIndex < width;
}
public int getCenterX()
{
return centerX;
}
public int getCenterZ()
{
return centerZ;
}
/**
* Returns how many non-null LodChunks
* are stored in this LodDimension.
*/
public int getNumberOfLods()
{
int numbLods = 0;
for (LodQuadTree[] regions : regions)
{
if(regions == null)
continue;
for (LodQuadTree region : regions)
{
if(region == null)
continue;
numbLods= region.getNodeList(false,false,true).size();
}
}
return numbLods;
}
public int getWidth()
{
return width;
}
public void setRegionWidth(int newWidth)
{
width = newWidth;
halfWidth = (int)Math.floor(width / 2);
regions = new LodQuadTree[width][width];
isRegionDirty = new boolean[width][width];
// populate isRegionDirty
for(int i = 0; i < width; i++)
for(int j = 0; j < width; j++)
isRegionDirty[i][j] = false;
}
@Override
public String toString()
{
String s = "";
s += "dim: " + dimension.toString() + "\t";
s += "(" + centerX + "," + centerZ + ")";
return s;
}
}
@@ -0,0 +1,97 @@
package com.seibel.lod.objects.quadTree;
import net.minecraft.world.DimensionType;
import java.util.Hashtable;
import java.util.Map;
public class LodQuadTreeWorld {
private String worldName;
private Map<DimensionType, LodQuadTreeDimension> lodDimensions;
/**
* If true then the LOD world is setup and ready to use
*/
private boolean isWorldLoaded = false;
public static final String NO_WORLD_LOADED = "No world loaded";
public LodQuadTreeWorld() {
worldName = NO_WORLD_LOADED;
}
/**
* Set up the LodWorld with the given newWorldName. <br>
* This should be done whenever loading a new world.
* @param newWorldName name of the world
*/
public void selectWorld(String newWorldName) {
if (newWorldName.isEmpty()) {
deselectWorld();
return;
}
if (worldName.equals(newWorldName))
// don't recreate everything if we
// didn't actually change worlds
return;
worldName = newWorldName;
lodDimensions = new Hashtable<>();
isWorldLoaded = true;
}
/**
* Set the worldName to "No world loaded"
* and clear the lodDimensions Map. <br>
* This should be done whenever unloaded a world.
*/
public void deselectWorld() {
worldName = NO_WORLD_LOADED;
lodDimensions = null;
isWorldLoaded = false;
}
public void addLodDimension(LodQuadTreeDimension newStorage) {
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
lodDimensions.put(newStorage.dimension, newStorage);
}
public LodQuadTreeDimension getLodDimension(DimensionType dimension) {
if (lodDimensions.get(dimension) == null) {
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
}
return lodDimensions.get(dimension);
}
/**
* Resizes the max width in regions that each LodDimension
* should use.
*/
public void resizeDimensionRegionWidth(int newWidth) {
if (lodDimensions == null)
throw new IllegalStateException("LodWorld hasn't been given a world yet.");
for (DimensionType key : lodDimensions.keySet())
lodDimensions.get(key).setRegionWidth(newWidth);
}
public boolean getIsWorldLoaded() {
return isWorldLoaded;
}
public String getWorldName() {
return worldName;
}
@Override
public String toString() {
return "World name: " + worldName;
}
}
@@ -0,0 +1,58 @@
package com.seibel.lod.objects.quadTree;
import com.seibel.lod.builders.LodBuilder;
import com.seibel.lod.builders.LodNodeBuilder;
import com.seibel.lod.objects.LodDimension;
import net.minecraft.client.Minecraft;
import net.minecraft.world.DimensionType;
import java.awt.*;
import java.lang.reflect.Array;
import java.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
public class UsesExamples {
public static void main(String[] args){
//THIS CODE DOESN'T WORK AT THE MOMENT
/**TODO
* Complete all the new Lod objects
* Complete the getNodeToGenerate in LodQuadTreeDimension
* Complete the getNodeToRender in LodQuadTreeDimension
* Complete the node builder
* Complete the new renderer
* add everything to ClientProxy for the first test
* */
LodQuadTreeWorld lodWorld = new LodQuadTreeWorld();
LodQuadTreeDimension newLodDimension = new LodQuadTreeDimension(Minecraft.getInstance().world.getDimensionType(), Minecraft.getInstance().world, 10);
lodWorld.addLodDimension(newLodDimension);
LodQuadTreeDimension lodDimension = lodWorld.getLodDimension(Minecraft.getInstance().world.getDimensionType());
lodDimension.move(0,0);
/*
I will now generate some fake LodNodeData. This in the final implementation will be generated by a builder
this lodNodeData will be put at level 6 of the QuadTree. This LodNodeData represent a block of width
*/
LodNodeData lodNodeData1 = new LodNodeData((byte) 6, 4, 4, 64, 0, new Color(0,200,0),true);
LodNodeData lodNodeData2 = new LodNodeData((byte) 6, 4, 5, 64, 0, new Color(0,200,0),true);
LodNodeData lodNodeData3 = new LodNodeData((byte) 6, 5, 4, 64, 0, new Color(0,0,200),true);
/*
add like this
*/
lodDimension.addNode(lodNodeData1);
lodDimension.addNode(lodNodeData2);
lodDimension.addNode(lodNodeData3);
/*
Automatic generation
*/
List nodeToGenerate = (List) lodDimension.getRegion(0,0).getNodeToGenerate(0,0, (byte) 3,1000,0);
Collections.sort(nodeToGenerate, Map.Entry.<LodNodeData, Integer>comparingByValue();
/*
Call the builder to generate all the useful node
*/
}
}