Start merging in 1.16.5_QuadTree
The mod does compile and render, however distance LODs don't generate or render correctly and there are other problems as well.
This commit is contained in:
@@ -47,7 +47,6 @@ public class LodMain
|
||||
public static ClientProxy client_proxy;
|
||||
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void init(final FMLCommonSetupEvent event)
|
||||
{
|
||||
ModLoadingContext.get().registerConfig(ModConfig.Type.CLIENT, LodConfig.clientSpec);
|
||||
|
||||
@@ -275,11 +275,12 @@ public class LodBufferBuilder
|
||||
else
|
||||
currentBuffer = buildableFarBuffer;
|
||||
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
// get the desired LodTemplate and
|
||||
// add this LOD to the buffer
|
||||
LodConfig.CLIENT.lodTemplate.get().
|
||||
template.addLodToBuffer(currentBuffer, lodDim, lod,
|
||||
xOffset, yOffset, zOffset, renderer.debugging);
|
||||
// LodConfig.CLIENT.lodTemplate.get().
|
||||
// template.addLodToBuffer(currentBuffer, lodDim, lod,
|
||||
// xOffset, yOffset, zOffset, renderer.debugging);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
/*
|
||||
* 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.builders;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.lwjgl.opengl.GL11;
|
||||
|
||||
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.objects.NearFarBuffer;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.render.LodNodeRenderer;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.biome.BiomeContainer;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.WorldWorkerManager;
|
||||
|
||||
/**
|
||||
* This object is used to create NearFarBuffer objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-19-2021
|
||||
*/
|
||||
public class LodNodeBufferBuilder
|
||||
{
|
||||
private Minecraft mc;
|
||||
|
||||
/** This holds the thread used to generate new LODs off the main thread. */
|
||||
private ExecutorService genThread = Executors.newSingleThreadExecutor();
|
||||
|
||||
private LodNodeBuilder LodQuadTreeNodeBuilder;
|
||||
|
||||
/** The buffers that are used to create LODs using near fog */
|
||||
public volatile BufferBuilder buildableNearBuffer;
|
||||
/** The buffers that are used to create LODs using far fog */
|
||||
public volatile BufferBuilder buildableFarBuffer;
|
||||
|
||||
/** if this is true the LOD buffers are currently being
|
||||
* regenerated. */
|
||||
public volatile boolean generatingBuffers = false;
|
||||
|
||||
/** if this is true new LOD buffers have been generated
|
||||
* and are waiting to be swapped with the drawable buffers*/
|
||||
private volatile boolean switchBuffers = false;
|
||||
|
||||
/** This keeps track of how many chunk generation requests are on going.
|
||||
* This is to prevent chunks from being generated for a long time in an area
|
||||
* the player is no longer in. */
|
||||
public volatile AtomicInteger numberOfChunksWaitingToGenerate = new AtomicInteger(0);
|
||||
|
||||
|
||||
|
||||
/** how many chunks to generate outside of the player's
|
||||
* view distance at one time. (or more specifically how
|
||||
* many requests to make at one time).
|
||||
* I multiply by 8 to make sure there is always a buffer of chunk requests,
|
||||
* to make sure the CPU is always busy and we can generate LODs as quickly as
|
||||
* possible. */
|
||||
public int maxChunkGenRequests = LodConfig.CLIENT.numberOfWorldGenerationThreads.get() * 8;
|
||||
|
||||
|
||||
public LodNodeBufferBuilder(LodNodeBuilder newLodBuilder)
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
LodQuadTreeNodeBuilder = newLodBuilder;
|
||||
}
|
||||
|
||||
|
||||
private BiomeContainer biomeContainer = null;
|
||||
private LodQuadTreeDimension previousDimension = null;
|
||||
|
||||
|
||||
/**
|
||||
* Create a thread to asynchronously generate LOD buffers
|
||||
* centered around the given camera X and Z.
|
||||
* <br>
|
||||
* This method will write to the drawable near and far buffers.
|
||||
* <br>
|
||||
* After the buildable buffers have been generated they must be
|
||||
* swapped with the drawable buffers in the LodRenderer to be drawn.
|
||||
*/
|
||||
public void generateLodBuffersAsync(LodNodeRenderer renderer, LodQuadTreeDimension lodDim,
|
||||
double playerX, double playerZ, int numbChunksWide)
|
||||
{
|
||||
// only allow one generation process to happen at a time
|
||||
if (generatingBuffers)
|
||||
return;
|
||||
|
||||
if (buildableNearBuffer == null || buildableFarBuffer == null)
|
||||
throw new IllegalStateException("generateLodBuffersAsync was called before the buildableNearBuffer and buildableFarBuffer were created.");
|
||||
|
||||
if (previousDimension != lodDim)
|
||||
{
|
||||
biomeContainer = LodUtil.getServerWorldFromDimension(lodDim.dimension).getChunk(0, 0, ChunkStatus.EMPTY).getBiomes();
|
||||
previousDimension = lodDim;
|
||||
}
|
||||
|
||||
|
||||
|
||||
generatingBuffers = true;
|
||||
|
||||
|
||||
|
||||
// this seemingly useless math is required,
|
||||
// just using (int) playerX/Z doesn't work
|
||||
int playerXChunkOffset = ((int) playerX / LodQuadTreeNode.CHUNK_WIDTH) * LodQuadTreeNode.CHUNK_WIDTH;
|
||||
int playerZChunkOffset = ((int) playerZ / LodQuadTreeNode.CHUNK_WIDTH) * LodQuadTreeNode.CHUNK_WIDTH;
|
||||
// this is where we will start drawing squares
|
||||
// (exactly half the total width)
|
||||
int startX = (-LodQuadTreeNode.CHUNK_WIDTH * (numbChunksWide / 2)) + playerXChunkOffset;
|
||||
int startZ = (-LodQuadTreeNode.CHUNK_WIDTH * (numbChunksWide / 2)) + playerZChunkOffset;
|
||||
|
||||
|
||||
Thread thread = new Thread(()->
|
||||
{
|
||||
// index of the chunk currently being added to the
|
||||
// generation list
|
||||
int chunkGenIndex = 0;
|
||||
|
||||
ChunkPos[] chunksToGen = new ChunkPos[maxChunkGenRequests];
|
||||
// if we don't have a full number of chunks to generate in chunksToGen
|
||||
// we can top it off from the reserve
|
||||
ChunkPos[] chunksToGenReserve = new ChunkPos[maxChunkGenRequests];
|
||||
int minChunkDist = Integer.MAX_VALUE;
|
||||
ChunkPos playerChunkPos = new ChunkPos((int)playerX / LodQuadTreeNode.CHUNK_WIDTH, (int)playerZ / LodQuadTreeNode.CHUNK_WIDTH);
|
||||
|
||||
|
||||
// generate our new buildable buffers
|
||||
buildableNearBuffer.begin(GL11.GL_QUADS, LodNodeRenderer.LOD_VERTEX_FORMAT);
|
||||
buildableFarBuffer.begin(GL11.GL_QUADS, LodNodeRenderer.LOD_VERTEX_FORMAT);
|
||||
|
||||
// x axis
|
||||
for (int i = 0; i < numbChunksWide; i++)
|
||||
{
|
||||
// z axis
|
||||
for (int j = 0; j < numbChunksWide; j++)
|
||||
{
|
||||
int chunkX = i + (startX / LodQuadTreeNode.CHUNK_WIDTH);
|
||||
int chunkZ = j + (startZ / LodQuadTreeNode.CHUNK_WIDTH);
|
||||
|
||||
// skip any chunks that Minecraft is going to render
|
||||
if(isCoordInCenterArea(i, j, (numbChunksWide / 2))
|
||||
&& renderer.vanillaRenderedChunks.contains(new ChunkPos(chunkX, chunkZ)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// set where this square will be drawn in the world
|
||||
double xOffset = (LodQuadTreeNode.CHUNK_WIDTH * i) + // offset by the number of LOD blocks
|
||||
startX; // offset so the center LOD block is centered underneath the player
|
||||
double yOffset = 0;
|
||||
double zOffset = (LodQuadTreeNode.CHUNK_WIDTH * j) + startZ;
|
||||
|
||||
LodQuadTreeNode lod = lodDim.getLodFromCoordinates(chunkX, chunkZ, LodQuadTreeNode.CHUNK_LEVEL);
|
||||
|
||||
if (lod == null || lod.getComplexity() == DistanceGenerationMode.NONE)
|
||||
{
|
||||
// generate a new chunk if no chunk currently exists
|
||||
// and we aren't waiting on any other chunks to generate
|
||||
if (lod == null && numberOfChunksWaitingToGenerate.get() < maxChunkGenRequests)
|
||||
{
|
||||
ChunkPos pos = new ChunkPos(chunkX, chunkZ);
|
||||
|
||||
|
||||
// can be used for debugging
|
||||
// if (chunksToGen == null)
|
||||
// {
|
||||
// chunkGenIndex = 0;
|
||||
// chunksToGen = new ChunkPos[maxChunkGenRequests];
|
||||
// }
|
||||
//
|
||||
// if (chunkGenIndex < maxChunkGenRequests)
|
||||
// {
|
||||
// chunksToGen[chunkGenIndex] = pos;
|
||||
// chunkGenIndex++;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
// determine if this position is closer to the player
|
||||
// than the previous
|
||||
int newDistance = playerChunkPos.getChessboardDistance(pos);
|
||||
|
||||
// issue #40
|
||||
// TODO optimize this code,
|
||||
// using the purely optimized code above we can achieve close to
|
||||
// 100% CPU utilization, this code generally achieves 40 - 50%
|
||||
// after a certain point; and I'm sure there is a better data
|
||||
// structure for this.
|
||||
if (newDistance < minChunkDist)
|
||||
{
|
||||
// this chunk is closer, clear any previous
|
||||
// positions and update the new minimum distance
|
||||
minChunkDist = newDistance;
|
||||
chunksToGenReserve = chunksToGen;
|
||||
|
||||
// move all the old chunks into the reserve
|
||||
ChunkPos[] newReserve = new ChunkPos[maxChunkGenRequests];
|
||||
int oldToGenIndex = 0;
|
||||
int oldReserveIndex = 0;
|
||||
for(int tmpIndex = 0; tmpIndex < newReserve.length; tmpIndex++)
|
||||
{
|
||||
// we don't check if the boundaries are good since
|
||||
// the tmp array will always be the same length
|
||||
// as chunksToGen and chunksToGenReserve
|
||||
|
||||
if (chunksToGen[oldToGenIndex] != null)
|
||||
{
|
||||
// add all the closest chunks...
|
||||
newReserve[tmpIndex] = chunksToGen[oldToGenIndex];
|
||||
oldToGenIndex++;
|
||||
}
|
||||
else if (chunksToGenReserve[oldReserveIndex] != null)
|
||||
{
|
||||
// ...then add all the previous reserve chunks
|
||||
// (which are farther away)
|
||||
newReserve[tmpIndex] = chunksToGenReserve[oldToGenIndex];
|
||||
oldReserveIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have moved all the items from
|
||||
// the old chunksToGen and reserve
|
||||
break;
|
||||
}
|
||||
}
|
||||
chunksToGenReserve = newReserve;
|
||||
|
||||
|
||||
|
||||
chunkGenIndex = 0;
|
||||
chunksToGen = new ChunkPos[maxChunkGenRequests];
|
||||
chunksToGen[chunkGenIndex] = pos;
|
||||
chunkGenIndex++;
|
||||
}
|
||||
else if (newDistance <= minChunkDist)
|
||||
{
|
||||
// this chunk position is as close or closers than the
|
||||
// minimum distance
|
||||
if(chunkGenIndex < maxChunkGenRequests)
|
||||
{
|
||||
// we are still under the number of chunks to generate
|
||||
// add this position to the list
|
||||
chunksToGen[chunkGenIndex] = pos;
|
||||
chunkGenIndex++;
|
||||
}
|
||||
}
|
||||
} // lod null and can generate more chunks
|
||||
|
||||
// don't render this null/empty chunk
|
||||
continue;
|
||||
|
||||
} // lod null or empty
|
||||
|
||||
|
||||
BufferBuilder currentBuffer = null;
|
||||
if (isCoordinateInNearFogArea(i, j, numbChunksWide / 2))
|
||||
currentBuffer = buildableNearBuffer;
|
||||
else
|
||||
currentBuffer = buildableFarBuffer;
|
||||
|
||||
// get the desired LodTemplate and
|
||||
// add this LOD to the buffer
|
||||
LodConfig.CLIENT.lodTemplate.get().
|
||||
template.addLodToBuffer(currentBuffer, lodDim, lod,
|
||||
xOffset, yOffset, zOffset, renderer.debugging);
|
||||
}
|
||||
}
|
||||
|
||||
ClientProxy.LOGGER.info(numberOfChunksWaitingToGenerate.get());
|
||||
|
||||
// issue #19
|
||||
// TODO add a way for a server side mod to generate chunks requested here
|
||||
if(mc.hasSingleplayerServer())
|
||||
{
|
||||
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(lodDim.dimension);
|
||||
|
||||
// make sure we have as many chunks to generate as we are allowed
|
||||
if (chunkGenIndex < maxChunkGenRequests)
|
||||
{
|
||||
for(int i = chunkGenIndex, j = 0; i < maxChunkGenRequests; i++, j++)
|
||||
{
|
||||
chunksToGen[i] = chunksToGenReserve[j];
|
||||
}
|
||||
}
|
||||
|
||||
// start chunk generation
|
||||
for(ChunkPos chunkPos : chunksToGen)
|
||||
{
|
||||
if(chunkPos == null)
|
||||
break;
|
||||
|
||||
// make sure we don't generate this chunk again
|
||||
lodDim.addNode(new LodQuadTreeNode(chunkPos));
|
||||
|
||||
numberOfChunksWaitingToGenerate.addAndGet(1);
|
||||
|
||||
LodNodeGenWorker genWorker = new LodNodeGenWorker(chunkPos, renderer, LodQuadTreeNodeBuilder, this, lodDim, serverWorld, biomeContainer);
|
||||
WorldWorkerManager.addWorker(genWorker);
|
||||
}
|
||||
}
|
||||
|
||||
// finish the buffer building
|
||||
buildableNearBuffer.end();
|
||||
buildableFarBuffer.end();
|
||||
|
||||
// mark that the buildable buffers as ready to swap
|
||||
generatingBuffers = false;
|
||||
switchBuffers = true;
|
||||
});
|
||||
|
||||
genThread.execute(thread);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//====================//
|
||||
// generation helpers //
|
||||
//====================//
|
||||
|
||||
/**
|
||||
* Returns if the given coordinate is in the loaded area of the world.
|
||||
* @param centerCoordinate the center of the loaded world
|
||||
*/
|
||||
private boolean isCoordInCenterArea(int i, int j, int centerCoordinate)
|
||||
{
|
||||
return (i >= centerCoordinate - mc.options.renderDistance
|
||||
&& i <= centerCoordinate + mc.options.renderDistance)
|
||||
&&
|
||||
(j >= centerCoordinate - mc.options.renderDistance
|
||||
&& j <= centerCoordinate + mc.options.renderDistance);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the coordinates that are in the center half of the given
|
||||
* 2D matrix, starting at (0,0) and going to (2 * lodRadius, 2 * lodRadius).
|
||||
*/
|
||||
private static boolean isCoordinateInNearFogArea(int chunkX, int chunkZ, int lodRadius)
|
||||
{
|
||||
int halfRadius = lodRadius / 2;
|
||||
|
||||
return (chunkX >= lodRadius - halfRadius
|
||||
&& chunkX <= lodRadius + halfRadius)
|
||||
&&
|
||||
(chunkZ >= lodRadius - halfRadius
|
||||
&& chunkZ <= lodRadius + halfRadius);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===============================//
|
||||
// BufferBuilder related methods //
|
||||
//===============================//
|
||||
|
||||
|
||||
/**
|
||||
* Called from the LodRenderer to create the
|
||||
* BufferBuilders at the right size.
|
||||
*
|
||||
* @param bufferMaxCapacity
|
||||
*/
|
||||
public void setupBuffers(int bufferMaxCapacity)
|
||||
{
|
||||
buildableNearBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
buildableFarBuffer = new BufferBuilder(bufferMaxCapacity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the drawable and buildable buffers and return
|
||||
* the old drawable buffers.
|
||||
* @param drawableNearBuffer
|
||||
* @param drawableFarBuffer
|
||||
*/
|
||||
public NearFarBuffer swapBuffers(BufferBuilder drawableNearBuffer, BufferBuilder drawableFarBuffer)
|
||||
{
|
||||
// swap the BufferBuilders
|
||||
BufferBuilder tmp = buildableNearBuffer;
|
||||
buildableNearBuffer = drawableNearBuffer;
|
||||
drawableNearBuffer = tmp;
|
||||
|
||||
tmp = buildableFarBuffer;
|
||||
buildableFarBuffer = drawableFarBuffer;
|
||||
drawableFarBuffer = tmp;
|
||||
|
||||
|
||||
// the buffers have been swapped
|
||||
switchBuffers = false;
|
||||
|
||||
return new NearFarBuffer(drawableNearBuffer, drawableFarBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* If this is true the buildable near and far
|
||||
* buffers have been generated and are ready to be
|
||||
* sent to the LodRenderer.
|
||||
*/
|
||||
public boolean newBuffersAvaliable()
|
||||
{
|
||||
return switchBuffers;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* 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.builders;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.objects.LodDataPoint;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.objects.LodQuadTreeWorld;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.FlowingFluidBlock;
|
||||
import net.minecraft.block.GrassBlock;
|
||||
import net.minecraft.block.LeavesBlock;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.IWorld;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.chunk.ChunkSection;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
public class LodNodeBuilder {
|
||||
private ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor();
|
||||
private long seed;
|
||||
private DimensionType dimension;
|
||||
|
||||
public static final int CHUNK_DATA_WIDTH = LodQuadTreeNode.CHUNK_WIDTH;
|
||||
public static final int CHUNK_SECTION_HEIGHT = CHUNK_DATA_WIDTH;
|
||||
public static final Heightmap.Type DEFAULT_HEIGHTMAP = Heightmap.Type.WORLD_SURFACE_WG;
|
||||
|
||||
/**
|
||||
* 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, LodQuadTreeWorld lodWorld, IWorld world) {
|
||||
if (lodWorld == null || !lodWorld.getIsWorldLoaded()) {
|
||||
System.out.println("This case?");
|
||||
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.dimensionType();
|
||||
|
||||
LodQuadTreeNode node = generateLodNodeFromChunk(chunk);
|
||||
|
||||
LodQuadTreeDimension lodDim;
|
||||
|
||||
if (lodWorld.getLodDimension(dim) == null)
|
||||
{
|
||||
//System.out.println("Adding");
|
||||
lodDim = new LodQuadTreeDimension(dim, lodWorld, regionWidth);
|
||||
lodWorld.addLodDimension(lodDim);
|
||||
}
|
||||
else
|
||||
{
|
||||
//System.out.println("Not adding");
|
||||
lodDim = lodWorld.getLodDimension(dim);
|
||||
}
|
||||
|
||||
lodDim.addNode(node);
|
||||
} catch (IllegalArgumentException | NullPointerException e) {
|
||||
e.printStackTrace();
|
||||
// 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);
|
||||
//System.out.println("Is this ENDING?");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
*/
|
||||
public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk) throws IllegalArgumentException {
|
||||
return generateLodNodeFromChunk(chunk, new LodBuilderConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a LodChunk for a chunk in the given world.
|
||||
*
|
||||
* @throws IllegalArgumentException thrown if either the chunk or world is null.
|
||||
* @return
|
||||
*/
|
||||
public LodQuadTreeNode generateLodNodeFromChunk(IChunk chunk, LodBuilderConfig config) throws IllegalArgumentException {
|
||||
if (chunk == null)
|
||||
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
|
||||
|
||||
// TODO startX/Z and endX/Z are relative coordinates
|
||||
// getMin/Max appears to return world block coordinates
|
||||
int startX = 0; //chunk.getPos().getMinBlockX();
|
||||
int startZ = 0; //chunk.getPos().getMinBlockZ();
|
||||
int endX = 15; //chunk.getPos().getMaxBlockX();
|
||||
int endZ = 15; //chunk.getPos().getMaxBlockZ();
|
||||
|
||||
Color color = generateLodColorForArea(chunk, config, startX, startZ, endX, endZ);
|
||||
|
||||
short height;
|
||||
short depth;
|
||||
|
||||
/**TODO I HAVE TO RE-ADD THE HEIGHTMAP**/
|
||||
height = determineHeightPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
depth = determineBottomPointForArea(chunk.getSections(), startX, startZ, endX, endZ);
|
||||
|
||||
|
||||
return new LodQuadTreeNode(LodQuadTreeNode.CHUNK_LEVEL, chunk.getPos().x, chunk.getPos().z, new LodDataPoint(height, depth, color) , DistanceGenerationMode.SERVER);
|
||||
}
|
||||
|
||||
|
||||
//=====================//
|
||||
// constructor helpers //
|
||||
//=====================//
|
||||
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*
|
||||
* @param chunkSections
|
||||
* @param startX
|
||||
* @param startZ
|
||||
* @param endX
|
||||
* @param endZ
|
||||
*/
|
||||
private short determineBottomPointForArea(ChunkSection[] chunkSections,
|
||||
int startX, int startZ, int endX, int endZ) {
|
||||
int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2);
|
||||
|
||||
// search from the bottom up
|
||||
for (int section = 0; section < CHUNK_DATA_WIDTH; section++) {
|
||||
for (int y = 0; y < CHUNK_SECTION_HEIGHT; y++) {
|
||||
int numberOfBlocksFound = 0;
|
||||
|
||||
for (int x = startX; x < endX; x++) {
|
||||
for (int z = startZ; z < endZ; z++) {
|
||||
if (isLayerValidLodPoint(chunkSections, section, y, x, z)) {
|
||||
numberOfBlocksFound++;
|
||||
|
||||
if (numberOfBlocksFound >= numberOfBlocksRequired) {
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the lowest valid point from the bottom.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
private short determineBottomPoint(Heightmap heightmap) {
|
||||
// the heightmap only shows how high the blocks go, it
|
||||
// doesn't have any info about how low they go
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find the highest valid point from the Top
|
||||
*
|
||||
* @param chunkSections
|
||||
* @param startX
|
||||
* @param startZ
|
||||
* @param endX
|
||||
* @param endZ
|
||||
*/
|
||||
private short determineHeightPointForArea(ChunkSection[] chunkSections,
|
||||
int startX, int startZ, int endX, int endZ) {
|
||||
int numberOfBlocksRequired = ((endX - startX) * (endZ - startZ) / 2);
|
||||
// search from the top down
|
||||
for (int section = chunkSections.length - 1; section >= 0; section--) {
|
||||
for (int y = CHUNK_DATA_WIDTH - 1; y >= 0; y--) {
|
||||
int numberOfBlocksFound = 0;
|
||||
|
||||
for (int x = startX; x < endX; x++) {
|
||||
for (int z = startZ; z < endZ; z++) {
|
||||
if (isLayerValidLodPoint(chunkSections, section, y, x, z)) {
|
||||
numberOfBlocksFound++;
|
||||
|
||||
if (numberOfBlocksFound >= numberOfBlocksRequired) {
|
||||
// we found
|
||||
// enough blocks in this
|
||||
// layer to count as an
|
||||
// LOD point
|
||||
return (short) (y + (section * CHUNK_SECTION_HEIGHT));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we never found a valid LOD point
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the highest point from the Top
|
||||
*/
|
||||
private short determineHeightPoint(Heightmap heightmap,
|
||||
int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
short highest = 0;
|
||||
for (int x = startX; x < endX; x++) {
|
||||
for (int z = startZ; z < endZ; z++) {
|
||||
short newHeight = (short) heightmap.getFirstAvailable(x, z);
|
||||
if (newHeight > highest)
|
||||
highest = newHeight;
|
||||
}
|
||||
}
|
||||
|
||||
return highest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the color for the given chunk using biome
|
||||
* water color, foliage color, and grass color.
|
||||
*
|
||||
* @param config_useSolidBlocksInColorGen <br>
|
||||
* If true we look down from the top of the <br>
|
||||
* chunk until we find a non-invisible block, and then use <br>
|
||||
* its color. If false we generate the color immediately for <br>
|
||||
* each x and z.
|
||||
* @param config_useBiomeColors <br>
|
||||
* If true use biome foliage, water, and grass colors, <br>
|
||||
* otherwise only use the block's material color
|
||||
*/
|
||||
private Color generateLodColorForArea(IChunk chunk, LodBuilderConfig config, int startX, int startZ, int endX, int endZ)
|
||||
{
|
||||
ChunkSection[] chunkSections = chunk.getSections();
|
||||
|
||||
int numbOfBlocks = 0;
|
||||
int red = 0;
|
||||
int green = 0;
|
||||
int blue = 0;
|
||||
|
||||
|
||||
for(int x = startX; x < endX; x++)
|
||||
{
|
||||
for(int z = startZ; z < endZ; z++)
|
||||
{
|
||||
boolean foundBlock = false;
|
||||
|
||||
// go top down
|
||||
for(int i = chunkSections.length - 1; !foundBlock && i >= 0; i--)
|
||||
{
|
||||
if( !foundBlock && (chunkSections[i] != null || !config.useSolidBlocksInColorGen))
|
||||
{
|
||||
for(int y = CHUNK_SECTION_HEIGHT - 1; !foundBlock && y >= 0; y--)
|
||||
{
|
||||
int colorInt = 0;
|
||||
BlockState blockState = null;
|
||||
|
||||
if (chunkSections[i] != null)
|
||||
{
|
||||
blockState = chunkSections[i].getBlockState(x, y, z);
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
if(colorInt == 0 && config.useSolidBlocksInColorGen)
|
||||
{
|
||||
// skip air or invisible blocks
|
||||
continue;
|
||||
}
|
||||
|
||||
if (config.useBiomeColors)
|
||||
{
|
||||
// the bit shift is equivalent to dividing by 4
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
|
||||
|
||||
if (biome.getBiomeCategory() == Biome.Category.OCEAN ||
|
||||
biome.getBiomeCategory() == Biome.Category.RIVER)
|
||||
{
|
||||
if (config.useSolidBlocksInColorGen &&
|
||||
blockState != Blocks.WATER.defaultBlockState())
|
||||
{
|
||||
// non-water block
|
||||
colorInt = getColorForBlock(x, z, blockState, biome);
|
||||
}
|
||||
else
|
||||
{
|
||||
// water block
|
||||
colorInt = biome.getWaterColor();
|
||||
}
|
||||
}
|
||||
else if (biome.getBiomeCategory() == Biome.Category.EXTREME_HILLS)
|
||||
{
|
||||
colorInt = Blocks.STONE.defaultMaterialColor().col;
|
||||
}
|
||||
else if (biome.getBiomeCategory() == Biome.Category.ICY)
|
||||
{
|
||||
colorInt = LodUtil.colorToInt(Color.WHITE);
|
||||
}
|
||||
else if (biome.getBiomeCategory() == Biome.Category.THEEND)
|
||||
{
|
||||
colorInt = Blocks.END_STONE.defaultBlockState().materialColor.col;
|
||||
}
|
||||
else if (config.useSolidBlocksInColorGen)
|
||||
{
|
||||
colorInt = getColorForBlock(x, z, blockState, biome);
|
||||
}
|
||||
else
|
||||
{
|
||||
colorInt = biome.getGrassColor(x, z);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// I have no idea why I need to bit shift to the right, but
|
||||
// if I don't the biomes don't show up correctly.
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, y + i * chunkSections.length >> 2, z >> 2);
|
||||
colorInt = getColorForBlock(x,z, blockState, biome);
|
||||
}
|
||||
|
||||
|
||||
Color c = LodUtil.intToColor(colorInt);
|
||||
|
||||
red += c.getRed();
|
||||
green += c.getGreen();
|
||||
blue += c.getBlue();
|
||||
|
||||
numbOfBlocks++;
|
||||
|
||||
|
||||
// we found a valid block, skip to the
|
||||
// next x and z
|
||||
foundBlock = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(numbOfBlocks == 0)
|
||||
numbOfBlocks = 1;
|
||||
|
||||
red /= numbOfBlocks;
|
||||
green /= numbOfBlocks;
|
||||
blue /= numbOfBlocks;
|
||||
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a color int for a given block.
|
||||
*/
|
||||
private int getColorForBlock(int x, int z, BlockState blockState, Biome biome)
|
||||
{
|
||||
int colorInt = 0;
|
||||
|
||||
if (blockState == Blocks.AIR.defaultBlockState()) {
|
||||
colorInt = biome.getGrassColor(x, z);
|
||||
} else if (blockState.getBlock() instanceof LeavesBlock) {
|
||||
Color leafColor = LodUtil.intToColor(biome.getFoliageColor()).darker();
|
||||
colorInt = LodUtil.colorToInt(leafColor);
|
||||
} else if (blockState.getBlock() instanceof GrassBlock) {
|
||||
colorInt = biome.getGrassColor(x, z);
|
||||
} else if (blockState.getBlock() instanceof FlowingFluidBlock) {
|
||||
colorInt = biome.getWaterColor();
|
||||
} else {
|
||||
colorInt = blockState.materialColor.col;
|
||||
}
|
||||
|
||||
return colorInt;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the layer between the given X, Z, and dataIndex
|
||||
* values a valid LOD point?
|
||||
*/
|
||||
private boolean isLayerValidLodPoint(
|
||||
ChunkSection[] chunkSections,
|
||||
int sectionIndex, int y,
|
||||
int x, int z)
|
||||
{
|
||||
if (chunkSections[sectionIndex] == null)
|
||||
{
|
||||
// this section doesn't have any blocks,
|
||||
// it is not a valid section
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (chunkSections[sectionIndex].getBlockState(x, y, z) != null &&
|
||||
chunkSections[sectionIndex].getBlockState(x, y, z).getBlock() != Blocks.AIR)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.builders.lodNodeTemplates;
|
||||
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* This is the abstract class used to create different
|
||||
* BufferBuilders.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public abstract class AbstractLodNodeTemplate
|
||||
{
|
||||
public abstract void addLodToBuffer(BufferBuilder buffer,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging);
|
||||
|
||||
/** add the given position and color to the buffer */
|
||||
protected void addPosAndColor(BufferBuilder buffer,
|
||||
double x, double y, double z,
|
||||
int red, int green, int blue, int alpha)
|
||||
{
|
||||
buffer.vertex(x, y, z).color(red, green, blue, alpha).endVertex();
|
||||
}
|
||||
|
||||
/** Returns in bytes how much buffer memory is required
|
||||
* for one LOD object */
|
||||
public abstract int getBufferMemoryForSingleLod(int level);
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
/*
|
||||
* 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.builders.lodNodeTemplates;
|
||||
import java.awt.Color;
|
||||
|
||||
import com.seibel.lod.enums.ShadingMode;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.util.math.AxisAlignedBB;
|
||||
|
||||
|
||||
/**
|
||||
* Builds LODs as rectangular prisms.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class CubicLodNodeTemplate extends AbstractLodNodeTemplate
|
||||
{
|
||||
public CubicLodNodeTemplate()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging) {
|
||||
AxisAlignedBB bbox;
|
||||
|
||||
// Add this LOD to the BufferBuilder
|
||||
int halfWidth = lod.width / 2;
|
||||
int startX = lod.startX;
|
||||
int startZ = lod.startZ;
|
||||
|
||||
// returns null if the lod is empty at the given location
|
||||
bbox = generateBoundingBox(
|
||||
lod.lodDataPoint.height,
|
||||
lod.lodDataPoint.depth,
|
||||
lod.width,
|
||||
xOffset - halfWidth + startX,
|
||||
yOffset,
|
||||
zOffset - halfWidth + startZ);
|
||||
|
||||
if (bbox != null) {
|
||||
addBoundingBoxToBuffer(buffer, bbox, lod.lodDataPoint.color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private AxisAlignedBB generateBoundingBox(int height, int depth, int width, double xOffset, double yOffset, double zOffset)
|
||||
{
|
||||
// don't add an LOD if it is empty
|
||||
if (height == -1 && depth == -1)
|
||||
return null;
|
||||
|
||||
if (depth == height)
|
||||
{
|
||||
// if the top and bottom points are at the same height
|
||||
// render this LOD as 1 block thick
|
||||
height++;
|
||||
}
|
||||
|
||||
return new AxisAlignedBB(0, depth, 0, width, height, width).move(xOffset, yOffset, zOffset);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void addBoundingBoxToBuffer(BufferBuilder buffer, AxisAlignedBB bb, Color c)
|
||||
{
|
||||
Color topColor = c;
|
||||
Color northSouthColor = c;
|
||||
Color eastWestColor = c;
|
||||
Color bottomColor = c;
|
||||
|
||||
// darken the bottom and side colors if requested
|
||||
if (LodConfig.CLIENT.shadingMode.get() == ShadingMode.DARKEN_SIDES)
|
||||
{
|
||||
// the side colors are different because
|
||||
// when using fast lighting in Minecraft the north/south
|
||||
// and east/west sides are different in a similar way
|
||||
int northSouthDarkenAmount = 25;
|
||||
int eastWestDarkenAmount = 50;
|
||||
int bottomDarkenAmount = 75;
|
||||
|
||||
northSouthColor = new Color(Math.max(0, c.getRed() - northSouthDarkenAmount), Math.max(0, c.getGreen() - northSouthDarkenAmount), Math.max(0, c.getBlue() - northSouthDarkenAmount), c.getAlpha());
|
||||
eastWestColor = new Color(Math.max(0, c.getRed() - eastWestDarkenAmount), Math.max(0, c.getGreen() - eastWestDarkenAmount), Math.max(0, c.getBlue() - eastWestDarkenAmount), c.getAlpha());
|
||||
bottomColor = new Color(Math.max(0, c.getRed() - bottomDarkenAmount), Math.max(0, c.getGreen() - bottomDarkenAmount), Math.max(0, c.getBlue() - bottomDarkenAmount), c.getAlpha());
|
||||
}
|
||||
|
||||
|
||||
// apply the user specified saturation and brightness
|
||||
float saturationMultiplier = LodConfig.CLIENT.saturationMultiplier.get().floatValue();
|
||||
float brightnessMultiplier = LodConfig.CLIENT.brightnessMultiplier.get().floatValue();
|
||||
|
||||
topColor = applySaturationAndBrightnessMultipliers(topColor, saturationMultiplier, brightnessMultiplier);
|
||||
northSouthColor = applySaturationAndBrightnessMultipliers(northSouthColor, saturationMultiplier, brightnessMultiplier);
|
||||
bottomColor = applySaturationAndBrightnessMultipliers(bottomColor, saturationMultiplier, brightnessMultiplier);
|
||||
|
||||
|
||||
|
||||
// top (facing up)
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, topColor.getRed(), topColor.getGreen(), topColor.getBlue(), topColor.getAlpha());
|
||||
// bottom (facing down)
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, bottomColor.getRed(), bottomColor.getGreen(), bottomColor.getBlue(), bottomColor.getAlpha());
|
||||
|
||||
// south (facing -Z)
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
// north (facing +Z)
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, northSouthColor.getRed(), northSouthColor.getGreen(), northSouthColor.getBlue(), northSouthColor.getAlpha());
|
||||
|
||||
// west (facing -X)
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.minX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
// east (facing +X)
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.maxY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.maxZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
addPosAndColor(buffer, bb.maxX, bb.minY, bb.minZ, eastWestColor.getRed(), eastWestColor.getGreen(), eastWestColor.getBlue(), eastWestColor.getAlpha());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Edit the given color as a HSV (Hue Saturation Value) color.
|
||||
*/
|
||||
private Color applySaturationAndBrightnessMultipliers(Color color, float saturationMultiplier, float brightnessMultiplier)
|
||||
{
|
||||
float[] hsv = Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), null);
|
||||
return Color.getHSBColor(
|
||||
hsv[0], // hue
|
||||
LodUtil.clamp(0.0f, hsv[1] * saturationMultiplier, 1.0f),
|
||||
LodUtil.clamp(0.0f, hsv[2] * brightnessMultiplier, 1.0f));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleLod(int detailLevel) // TODO maybe multiply by how many would be per chunk?
|
||||
{
|
||||
// (sidesOnACube * pointsInASquare * (positionPoints + colorPoints))) * howManyPointsPerLodChunk
|
||||
return (6 * 4 * (3 + 4));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.builders.lodNodeTemplates;
|
||||
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* TODO DynamicLodTemplate
|
||||
* Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class DynamicLodNodeTemplate extends AbstractLodNodeTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleLod(int detailLevel)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.builders.lodNodeTemplates;
|
||||
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
/**
|
||||
* TODO #21 TriangularLodTemplate
|
||||
* Builds each LOD chunk as a singular rectangular prism.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
*/
|
||||
public class TriangularLodNodeTemplate extends AbstractLodNodeTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferMemoryForSingleLod(int detailLevel)
|
||||
{
|
||||
// TODO Auto-generated method stub
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
@@ -32,10 +32,10 @@ import net.minecraft.client.renderer.BufferBuilder;
|
||||
*/
|
||||
public abstract class AbstractLodTemplate
|
||||
{
|
||||
public abstract void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging);
|
||||
public abstract void addLodToBuffer(BufferBuilder buffer,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging);
|
||||
|
||||
/** add the given position and color to the buffer */
|
||||
protected void addPosAndColor(BufferBuilder buffer,
|
||||
|
||||
@@ -22,8 +22,8 @@ import java.awt.Color;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.enums.ShadingMode;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
@@ -46,8 +46,8 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
AxisAlignedBB bbox;
|
||||
@@ -56,28 +56,18 @@ public class CubicLodTemplate extends AbstractLodTemplate
|
||||
// using the quality setting set by the config
|
||||
LodDetail detail = LodConfig.CLIENT.lodDetail.get();
|
||||
|
||||
int halfWidth = detail.dataPointWidth / 2;
|
||||
// returns null if the lod is empty at the given location
|
||||
bbox = generateBoundingBox(
|
||||
centerLod.lodDataPoint.height,
|
||||
centerLod.lodDataPoint.depth,
|
||||
detail.dataPointWidth,
|
||||
xOffset - (centerLod.width / 2),
|
||||
yOffset,
|
||||
zOffset - (centerLod.width / 2));
|
||||
|
||||
for(int i = 0; i < detail.dataPointLengthCount * detail.dataPointLengthCount; i++)
|
||||
if (bbox != null)
|
||||
{
|
||||
int startX = detail.startX[i];
|
||||
int startZ = detail.startZ[i];
|
||||
int endX = detail.endX[i];
|
||||
int endZ = detail.endZ[i];
|
||||
|
||||
// returns null if the lod is empty at the given location
|
||||
bbox = generateBoundingBox(
|
||||
centerLod.getAverageHeightOverArea(startX, startZ, endX, endZ),
|
||||
centerLod.getAverageDepthOverArea(startX, startZ, endX, endZ),
|
||||
detail.dataPointWidth,
|
||||
xOffset - (halfWidth / 2) + detail.startX[i],
|
||||
yOffset,
|
||||
zOffset - (halfWidth / 2) + detail.startZ[i]);
|
||||
|
||||
if (bbox != null)
|
||||
{
|
||||
addBoundingBoxToBuffer(buffer, bbox, centerLod.getAverageColorOverArea(startX, startZ, endX, endZ, debugging));
|
||||
}
|
||||
addBoundingBoxToBuffer(buffer, bbox, centerLod.lodDataPoint.color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
@@ -36,9 +36,9 @@ public class DynamicLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
package com.seibel.lod.builders.lodTemplates;
|
||||
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
|
||||
@@ -34,9 +34,9 @@ public class TriangularLodTemplate extends AbstractLodTemplate
|
||||
{
|
||||
@Override
|
||||
public void addLodToBuffer(BufferBuilder buffer,
|
||||
LodDimension lodDim, LodChunk lod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
LodQuadTreeDimension lodDim, LodQuadTreeNode centerLod,
|
||||
double xOffset, double yOffset, double zOffset,
|
||||
boolean debugging)
|
||||
{
|
||||
System.err.println("DynamicLodTemplate not implemented!");
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
@@ -188,6 +187,9 @@ public class LodChunkGenWorker implements IWorker
|
||||
|
||||
switch(LodConfig.CLIENT.distanceGenerationMode.get())
|
||||
{
|
||||
case NONE:
|
||||
// don't generate
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
// fastest
|
||||
@@ -205,6 +207,8 @@ public class LodChunkGenWorker implements IWorker
|
||||
// very slow
|
||||
generateWithServer();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
lodRenderer.regenerateLODsNextFrame();
|
||||
@@ -505,7 +509,8 @@ public class LodChunkGenWorker implements IWorker
|
||||
*/
|
||||
private void generateWithServer()
|
||||
{
|
||||
lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
|
||||
throw new UnsupportedOperationException("Not Implemented");
|
||||
//lodChunkBuilder.generateLodChunkAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,645 @@
|
||||
/*
|
||||
* 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.builders.worldGeneration;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import com.seibel.lod.builders.LodBuilderConfig;
|
||||
import com.seibel.lod.builders.LodNodeBufferBuilder;
|
||||
import com.seibel.lod.builders.LodNodeBuilder;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.render.LodNodeRenderer;
|
||||
|
||||
import net.minecraft.block.Block;
|
||||
import net.minecraft.block.BlockState;
|
||||
import net.minecraft.util.WeightedList.Entry;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.palette.UpgradeData;
|
||||
import net.minecraft.world.biome.Biome;
|
||||
import net.minecraft.world.biome.BiomeContainer;
|
||||
import net.minecraft.world.chunk.ChunkPrimer;
|
||||
import net.minecraft.world.chunk.ChunkStatus;
|
||||
import net.minecraft.world.chunk.IChunk;
|
||||
import net.minecraft.world.gen.ChunkGenerator;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
import net.minecraft.world.gen.blockstateprovider.WeightedBlockStateProvider;
|
||||
import net.minecraft.world.gen.feature.BlockClusterFeatureConfig;
|
||||
import net.minecraft.world.gen.feature.ConfiguredFeature;
|
||||
import net.minecraft.world.gen.feature.DecoratedFeatureConfig;
|
||||
import net.minecraft.world.gen.feature.FeatureSpread;
|
||||
import net.minecraft.world.gen.feature.FeatureSpreadConfig;
|
||||
import net.minecraft.world.gen.feature.IceAndSnowFeature;
|
||||
import net.minecraft.world.gen.feature.NoFeatureConfig;
|
||||
import net.minecraft.world.gen.placement.ConfiguredPlacement;
|
||||
import net.minecraft.world.gen.placement.DecoratedPlacementConfig;
|
||||
import net.minecraft.world.gen.placement.IPlacementConfig;
|
||||
import net.minecraft.world.gen.placement.NoiseDependant;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraft.world.server.ServerWorldLightManager;
|
||||
import net.minecraftforge.common.WorldWorkerManager.IWorker;
|
||||
|
||||
/**
|
||||
* This is used to generate a LodChunk at a given ChunkPos.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 7-4-2021
|
||||
*/
|
||||
public class LodNodeGenWorker implements IWorker
|
||||
{
|
||||
public static ExecutorService genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
|
||||
|
||||
private boolean threadStarted = false;
|
||||
private LodChunkGenThread thread;
|
||||
|
||||
/** If a configured feature fails for whatever reason,
|
||||
* add it to this list, this is to hopefully remove any
|
||||
* features that could cause issues down the line. */
|
||||
private static ConcurrentHashMap<Integer, ConfiguredFeature<?, ?>> configuredFeaturesToAvoid = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
|
||||
public LodNodeGenWorker(ChunkPos newPos, LodNodeRenderer newLodRenderer,
|
||||
LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder,
|
||||
LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld,
|
||||
BiomeContainer newBiomeContainer)
|
||||
{
|
||||
// just a few sanity checks
|
||||
if (newPos == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null ChunkPos");
|
||||
|
||||
if (newLodRenderer == null)
|
||||
throw new IllegalArgumentException("LodChunkGenWorker must have a non-null LodRenderer");
|
||||
|
||||
if (newLodBuilder == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodChunkBuilder");
|
||||
|
||||
if (newLodBufferBuilder == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodBufferBuilder");
|
||||
|
||||
if (newLodDimension == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null LodDimension");
|
||||
|
||||
if (newServerWorld == null)
|
||||
throw new IllegalArgumentException("LodChunkGenThread requires a non-null ServerWorld");
|
||||
|
||||
|
||||
|
||||
thread = new LodChunkGenThread(newPos, newLodRenderer,
|
||||
newLodBuilder, newLodBufferBuilder,
|
||||
newLodDimension, newServerWorld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doWork()
|
||||
{
|
||||
if (!threadStarted)
|
||||
{
|
||||
thread.lodBufferBuilder.numberOfChunksWaitingToGenerate.addAndGet(-1);
|
||||
|
||||
if (LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.SERVER)
|
||||
{
|
||||
// if we are using SERVER generation that has to be done
|
||||
// synchronously to prevent crashing and harmful
|
||||
// interactions with the normal world generator
|
||||
thread.run();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Every other method can
|
||||
// be done asynchronously
|
||||
genThreads.execute(thread);
|
||||
}
|
||||
|
||||
threadStarted = true;
|
||||
|
||||
// useful for debugging
|
||||
// ClientProxy.LOGGER.info(thread.lodDim.getNumberOfLods());
|
||||
// ClientProxy.LOGGER.info(genThreads.toString());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasWork()
|
||||
{
|
||||
return !threadStarted;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private class LodChunkGenThread implements Runnable
|
||||
{
|
||||
public final ServerWorld serverWorld;
|
||||
public final LodQuadTreeDimension lodDim;
|
||||
public final LodNodeBuilder lodChunkBuilder;
|
||||
public final LodNodeRenderer lodRenderer;
|
||||
private LodNodeBufferBuilder lodBufferBuilder;
|
||||
|
||||
private ChunkPos pos;
|
||||
|
||||
public LodChunkGenThread(ChunkPos newPos, LodNodeRenderer newLodRenderer,
|
||||
LodNodeBuilder newLodBuilder, LodNodeBufferBuilder newLodBufferBuilder,
|
||||
LodQuadTreeDimension newLodDimension, ServerWorld newServerWorld)
|
||||
{
|
||||
pos = newPos;
|
||||
lodRenderer = newLodRenderer;
|
||||
lodChunkBuilder = newLodBuilder;
|
||||
lodBufferBuilder = newLodBufferBuilder;
|
||||
lodDim = newLodDimension;
|
||||
serverWorld = newServerWorld;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
// only generate LodChunks if they can
|
||||
// be added to the current LodDimension
|
||||
if (lodDim.regionIsInRange(pos.x / LodRegion.SIZE, pos.z / LodRegion.SIZE))
|
||||
{
|
||||
// long startTime = System.currentTimeMillis();
|
||||
|
||||
switch(LodConfig.CLIENT.distanceGenerationMode.get())
|
||||
{
|
||||
case NONE:
|
||||
// don't generate
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
// fastest
|
||||
generateUsingBiomesOnly();
|
||||
break;
|
||||
case SURFACE:
|
||||
// faster
|
||||
generateUsingSurface();
|
||||
break;
|
||||
case FEATURES:
|
||||
// fast
|
||||
generateUsingFeatures();
|
||||
break;
|
||||
case SERVER:
|
||||
// very slow
|
||||
generateWithServer();
|
||||
break;
|
||||
}
|
||||
|
||||
lodRenderer.regenerateLODsNextFrame();
|
||||
|
||||
|
||||
// if (lodDim.getLodFromCoordinates(pos.x, pos.z) != null)
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z + " Success!");
|
||||
// else
|
||||
// ClientProxy.LOGGER.info(pos.x + " " + pos.z);
|
||||
|
||||
// long endTime = System.currentTimeMillis();
|
||||
// System.out.println(endTime - startTime);
|
||||
|
||||
}// if in range
|
||||
|
||||
}// run
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* takes about 2-5 ms
|
||||
*/
|
||||
private void generateUsingBiomesOnly()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
|
||||
|
||||
// generate fake height data for this LOD
|
||||
int seaLevel = serverWorld.getSeaLevel();
|
||||
|
||||
boolean simulateHeight = LodConfig.CLIENT.distanceGenerationMode.get() == DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT;
|
||||
boolean inTheEnd = false;
|
||||
|
||||
// add fake heightmap data so our LODs aren't at height 0
|
||||
Heightmap heightmap = new Heightmap(chunk, LodChunk.DEFAULT_HEIGHTMAP);
|
||||
for(int x = 0; x < LodChunk.WIDTH && !inTheEnd; x++)
|
||||
{
|
||||
for(int z = 0; z < LodChunk.WIDTH && !inTheEnd; z++)
|
||||
{
|
||||
if (simulateHeight)
|
||||
{
|
||||
// TODO use the biomes around each block to smooth out the transition
|
||||
|
||||
// these heights are of course aren't super accurate,
|
||||
// they are just to simulate height data where there isn't any
|
||||
switch(chunk.getBiomes().getNoiseBiome(x >> 2, seaLevel >> 2, z >> 2).getBiomeCategory())
|
||||
{
|
||||
case NETHER:
|
||||
heightmap.setHeight(x, z, serverWorld.getHeight() / 2);
|
||||
break;
|
||||
|
||||
case EXTREME_HILLS:
|
||||
heightmap.setHeight(x, z, seaLevel + 30);
|
||||
break;
|
||||
case MESA:
|
||||
heightmap.setHeight(x, z, seaLevel + 20);
|
||||
break;
|
||||
case JUNGLE:
|
||||
heightmap.setHeight(x, z, seaLevel + 20);
|
||||
break;
|
||||
case BEACH:
|
||||
heightmap.setHeight(x, z, seaLevel + 5);
|
||||
break;
|
||||
case NONE:
|
||||
heightmap.setHeight(x, z, 0);
|
||||
break;
|
||||
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
heightmap.setHeight(x, z, seaLevel);
|
||||
break;
|
||||
|
||||
case THEEND:
|
||||
inTheEnd = true;
|
||||
break;
|
||||
|
||||
// DESERT
|
||||
// FOREST
|
||||
// ICY
|
||||
// MUSHROOM
|
||||
// SAVANNA
|
||||
// SWAMP
|
||||
// TAIGA
|
||||
// PLAINS
|
||||
default:
|
||||
heightmap.setHeight(x, z, seaLevel + 10);
|
||||
break;
|
||||
}// heightmap switch
|
||||
}
|
||||
else
|
||||
{
|
||||
// we aren't simulating height
|
||||
// always use sea level
|
||||
heightmap.setHeight(x, z, seaLevel);
|
||||
}
|
||||
}// z
|
||||
}// x
|
||||
|
||||
chunk.setHeightmap(LodChunk.DEFAULT_HEIGHTMAP, heightmap.getRawData());
|
||||
|
||||
|
||||
LodQuadTreeNode lod;
|
||||
if (!inTheEnd)
|
||||
{
|
||||
lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false));
|
||||
}
|
||||
else
|
||||
{
|
||||
// if we are in the end, don't generate any chunks.
|
||||
// Since we don't know where the islands are, everything
|
||||
// generates the same and it looks really bad.
|
||||
lod = new LodQuadTreeNode(LodQuadTreeNode.CHUNK_LEVEL,chunk.getPos().x, chunk.getPos().z);
|
||||
}
|
||||
lodDim.addNode(lod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* takes about 10 - 20 ms
|
||||
*/
|
||||
private void generateUsingSurface()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
|
||||
// this feature has proved to be thread safe
|
||||
// so we will add it
|
||||
IceAndSnowFeature snowFeature = new IceAndSnowFeature(NoFeatureConfig.CODEC);
|
||||
snowFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition(), null);
|
||||
|
||||
LodQuadTreeNode lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false));
|
||||
lodDim.addNode(lod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* takes about 15 - 20 ms
|
||||
*
|
||||
* Causes concurrentModification Exceptions,
|
||||
* which could cause instability or world generation bugs
|
||||
*/
|
||||
private void generateUsingFeatures()
|
||||
{
|
||||
List<IChunk> chunkList = new LinkedList<>();
|
||||
ChunkPrimer chunk = new ChunkPrimer(pos, UpgradeData.EMPTY);
|
||||
chunkList.add(chunk);
|
||||
LodServerWorld lodServerWorld = new LodServerWorld(serverWorld, chunk);
|
||||
|
||||
ServerChunkProvider chunkSource = serverWorld.getChunkSource();
|
||||
ChunkGenerator chunkGen = chunkSource.generator;
|
||||
|
||||
|
||||
// generate the terrain (this is thread safe)
|
||||
ChunkStatus.EMPTY.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
// override the chunk status so we can run the next generator stage
|
||||
chunk.setStatus(ChunkStatus.STRUCTURE_REFERENCES);
|
||||
ChunkStatus.BIOMES.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.NOISE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
ChunkStatus.SURFACE.generate(serverWorld, chunkGen, serverWorld.getStructureManager(), (ServerWorldLightManager) serverWorld.getLightEngine(), null, chunkList);
|
||||
|
||||
|
||||
// get all the biomes in the chunk
|
||||
HashSet<Biome> biomes = new HashSet<>();
|
||||
for (int x = 0; x < LodChunk.WIDTH; x++)
|
||||
{
|
||||
for (int z = 0; z < LodChunk.WIDTH; z++)
|
||||
{
|
||||
Biome biome = chunk.getBiomes().getNoiseBiome(x >> 2, serverWorld.getSeaLevel() >> 2, z >> 2);
|
||||
|
||||
// Issue #35
|
||||
// For some reason Jungle biomes cause incredible lag
|
||||
// the features here must be interacting with each other
|
||||
// in unpredictable ways (specifically tree feature generation).
|
||||
// When generating Features my CPU usage generally hovers around 30 - 40%
|
||||
// when generating Jungles it spikes to 100%.
|
||||
if (biome.getBiomeCategory() != Biome.Category.JUNGLE)
|
||||
{
|
||||
// should probably use the heightmap here instead of seaLevel,
|
||||
// but this seems to get the job done well enough
|
||||
biomes.add(biome);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowUnstableFeatures = LodConfig.CLIENT.allowUnstableFeatureGeneration.get();
|
||||
|
||||
// generate all the features related to this chunk.
|
||||
// this may or may not be thread safe
|
||||
for (Biome biome : biomes)
|
||||
{
|
||||
List<List<Supplier<ConfiguredFeature<?, ?>>>> featuresForState = biome.generationSettings.features();
|
||||
|
||||
for(int featureStateToGenerate = 0; featureStateToGenerate < featuresForState.size(); featureStateToGenerate++)
|
||||
{
|
||||
for(Supplier<ConfiguredFeature<?, ?>> featureSupplier : featuresForState.get(featureStateToGenerate))
|
||||
{
|
||||
ConfiguredFeature<?, ?> configuredFeature = featureSupplier.get();
|
||||
|
||||
if (!allowUnstableFeatures &&
|
||||
configuredFeaturesToAvoid.containsKey(configuredFeature.hashCode()))
|
||||
continue;
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
configuredFeature.place(lodServerWorld, chunkGen, serverWorld.random, chunk.getPos().getWorldPosition());
|
||||
}
|
||||
catch(ConcurrentModificationException e)
|
||||
{
|
||||
// This will happen. I'm not sure what to do about it
|
||||
// except pray that it doesn't effect the normal world generation
|
||||
// in any harmful way.
|
||||
// Update: this can cause crashes and high CPU usage.
|
||||
|
||||
// Issue #35
|
||||
// I tried cloning the config for each feature, but that
|
||||
// path was blocked since I can't clone lambda methods.
|
||||
// I tried using a deep cloning library and discovered
|
||||
// the problem there.
|
||||
// ( https://github.com/kostaskougios/cloning
|
||||
// and
|
||||
// https://github.com/EsotericSoftware/kryo )
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
catch(UnsupportedOperationException e)
|
||||
{
|
||||
// This will happen when the LodServerWorld
|
||||
// isn't able to return something that a feature
|
||||
// generator needs
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
// I'm not sure what happened, print to the log
|
||||
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
e.printStackTrace();
|
||||
System.out.println();
|
||||
System.out.println();
|
||||
|
||||
if (!allowUnstableFeatures)
|
||||
configuredFeaturesToAvoid.put(configuredFeature.hashCode(), configuredFeature);
|
||||
// ClientProxy.LOGGER.info(configuredFeaturesToAvoid.mappingCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// generate a Lod like normal
|
||||
|
||||
LodQuadTreeNode lod = lodChunkBuilder.generateLodNodeFromChunk(chunk, new LodBuilderConfig(true, true, false));
|
||||
lodDim.addNode(lod);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* on pre generated chunks 0 - 1 ms
|
||||
* on un generated chunks 0 - 50 ms
|
||||
* with the median seeming to hover around 15 - 30 ms
|
||||
* and outliers in the 100 - 200 ms range
|
||||
*
|
||||
* Note this should not be multithreaded and does cause server/simulation lag
|
||||
* (Higher lag for generating than loading)
|
||||
*/
|
||||
private void generateWithServer() {
|
||||
//lodChunkBuilder.generateLodNodeAsync(serverWorld.getChunk(pos.x, pos.z, ChunkStatus.FEATURES), ClientProxy.getLodWorld(), serverWorld);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// Unused methods //
|
||||
//================//
|
||||
|
||||
// Sadly I wasn't able to get these to work,
|
||||
// they are here for documentation purposes
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked", "unused" })
|
||||
private DecoratedFeatureConfig cloneDecoratedFeatureConfig(DecoratedFeatureConfig config)
|
||||
{
|
||||
IPlacementConfig placementConfig = null;
|
||||
|
||||
Class oldConfigClass = config.decorator.config().getClass();
|
||||
|
||||
if (oldConfigClass == FeatureSpreadConfig.class)
|
||||
{
|
||||
FeatureSpreadConfig oldPlacementConfig = (FeatureSpreadConfig) config.decorator.config();
|
||||
FeatureSpread oldSpread = oldPlacementConfig.count();
|
||||
|
||||
placementConfig = new FeatureSpreadConfig(oldSpread);
|
||||
}
|
||||
else if(oldConfigClass == DecoratedPlacementConfig.class)
|
||||
{
|
||||
DecoratedPlacementConfig oldPlacementConfig = (DecoratedPlacementConfig) config.decorator.config();
|
||||
placementConfig = new DecoratedPlacementConfig(oldPlacementConfig.inner(), oldPlacementConfig.outer());
|
||||
}
|
||||
else if(oldConfigClass == NoiseDependant.class)
|
||||
{
|
||||
NoiseDependant oldPlacementConfig = (NoiseDependant) config.decorator.config();
|
||||
placementConfig = new NoiseDependant(oldPlacementConfig.noiseLevel, oldPlacementConfig.belowNoise, oldPlacementConfig.aboveNoise);
|
||||
}
|
||||
else
|
||||
{
|
||||
// ClientProxy.LOGGER.debug("unkown decorated placement config: \"" + config.decorator.config().getClass() + "\"");
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
ConfiguredPlacement<?> newPlacement = new ConfiguredPlacement(config.decorator.decorator, placementConfig);
|
||||
return new DecoratedFeatureConfig(config.feature, newPlacement);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private BlockClusterFeatureConfig cloneBlockClusterFeatureConfig(BlockClusterFeatureConfig config)
|
||||
{
|
||||
WeightedBlockStateProvider provider = new WeightedBlockStateProvider();
|
||||
for(Entry<BlockState> state : ((WeightedBlockStateProvider) config.stateProvider).weightedList.entries)
|
||||
provider.weightedList.entries.add(state);
|
||||
|
||||
HashSet<Block> whitelist = new HashSet<>();
|
||||
for(Block block : config.whitelist)
|
||||
whitelist.add(block);
|
||||
|
||||
HashSet<BlockState> blacklist = new HashSet<>();
|
||||
for(BlockState state : config.blacklist)
|
||||
blacklist.add(state);
|
||||
|
||||
|
||||
BlockClusterFeatureConfig.Builder builder = new BlockClusterFeatureConfig.Builder(provider, config.blockPlacer);
|
||||
builder.whitelist(whitelist);
|
||||
builder.blacklist(blacklist);
|
||||
builder.xspread(config.xspread);
|
||||
builder.yspread(config.yspread);
|
||||
builder.zspread(config.zspread);
|
||||
if(config.canReplace) { builder.canReplace(); }
|
||||
if(config.needWater) { builder.needWater(); }
|
||||
if(config.project) { builder.noProjection(); }
|
||||
builder.tries(config.tries);
|
||||
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stops the current genThreads if they are running
|
||||
* and then recreates the Executer service. <br><br>
|
||||
*
|
||||
* This is done to clear any outstanding tasks
|
||||
* that may exist after the player leaves their current world.
|
||||
* If this isn't done unfinished tasks may be left in the queue
|
||||
* preventing new LodChunks form being generated.
|
||||
*/
|
||||
public static void restartExecuterService()
|
||||
{
|
||||
if (genThreads != null && !genThreads.isShutdown())
|
||||
{
|
||||
genThreads.shutdownNow();
|
||||
}
|
||||
genThreads = Executors.newFixedThreadPool(LodConfig.CLIENT.numberOfWorldGenerationThreads.get());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* performance/generation tests related to
|
||||
* serverWorld.getChunk(x, z, ChunkStatus. *** )
|
||||
|
||||
true/false is whether they generated blocks or not
|
||||
the time is how long it took to generate
|
||||
|
||||
ChunkStatus.EMPTY 0 - 1 ms false (empty, what did you expect? :P)
|
||||
ChunkStatus.STRUCTURE_REFERENCES 1 - 2 ms false (no height, only generates some chunks)
|
||||
ChunkStatus.BIOMES 1 - 10 ms false (no height)
|
||||
ChunkStatus.NOISE 4 - 15 ms true (all blocks are stone)
|
||||
ChunkStatus.LIQUID_CARVERS 6 - 12 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.SURFACE 5 - 15 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.CARVERS 5 - 30 ms true (no snow/trees, just grass)
|
||||
ChunkStatus.FEATURES 7 - 25 ms true
|
||||
ChunkStatus.HEIGHTMAPS 20 - 40 ms true
|
||||
ChunkStatus.LIGHT 20 - 40 ms true
|
||||
ChunkStatus.FULL 30 - 50 ms true
|
||||
ChunkStatus.SPAWN 50 - 80 ms true
|
||||
|
||||
|
||||
At this point I would suggest using FEATURES, as it generates snow and trees
|
||||
(and any other object that is needed to make biomes distinct)
|
||||
|
||||
Otherwise if snow/trees aren't necessary SURFACE is the next fastest (although not by much)
|
||||
*/
|
||||
}
|
||||
@@ -31,12 +31,15 @@ package com.seibel.lod.enums;
|
||||
*/
|
||||
public enum DistanceGenerationMode
|
||||
{
|
||||
/** No generation has be used*/
|
||||
NONE(0),
|
||||
|
||||
/** Only generate the biomes and use biome
|
||||
* grass/foliage color, water color, or ice color
|
||||
* to generate the color.
|
||||
* Doesn't generate height, everything is shown at sea level.
|
||||
* Multithreaded - Fastest (2-5 ms) */
|
||||
BIOME_ONLY,
|
||||
BIOME_ONLY(1),
|
||||
|
||||
/**
|
||||
* Same as BIOME_ONLY, except instead
|
||||
@@ -44,25 +47,37 @@ public enum DistanceGenerationMode
|
||||
* different biome types (mountain, ocean, forest, etc.)
|
||||
* use predetermined heights to simulate having height data.
|
||||
*/
|
||||
BIOME_ONLY_SIMULATE_HEIGHT,
|
||||
BIOME_ONLY_SIMULATE_HEIGHT(2),
|
||||
|
||||
/** Generate the world surface,
|
||||
* this does NOT include caves, trees,
|
||||
* or structures.
|
||||
* Multithreaded - Faster (10-20 ms) */
|
||||
SURFACE,
|
||||
SURFACE(3),
|
||||
|
||||
/** Generate everything except structures.
|
||||
* NOTE: This may cause world generation bugs or instability,
|
||||
* since some features cause concurrentModification exceptions.
|
||||
* Multithreaded - Fast (15-20 ms) */
|
||||
FEATURES,
|
||||
FEATURES(4),
|
||||
|
||||
/** Ask the server to generate/load each chunk.
|
||||
* This is the most compatible, but causes server/simulation lag.
|
||||
* This will also show player made structures if you
|
||||
* are adding the mod to a pre-existing world.
|
||||
* Singlethreaded - Slow (15-50 ms, with spikes up to 200 ms) */
|
||||
SERVER;
|
||||
|
||||
SERVER(5);
|
||||
|
||||
public final int complexity;
|
||||
|
||||
DistanceGenerationMode(int complexity) {
|
||||
this.complexity = complexity;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public int compareTo(DistanceGenerationMode other){
|
||||
return Integer.compare(complexity, other.complexity);
|
||||
)
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -17,16 +17,16 @@
|
||||
*/
|
||||
package com.seibel.lod.enums;
|
||||
|
||||
import com.seibel.lod.builders.lodTemplates.AbstractLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.CubicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.DynamicLodTemplate;
|
||||
import com.seibel.lod.builders.lodTemplates.TriangularLodTemplate;
|
||||
import com.seibel.lod.builders.lodNodeTemplates.AbstractLodNodeTemplate;
|
||||
import com.seibel.lod.builders.lodNodeTemplates.CubicLodNodeTemplate;
|
||||
import com.seibel.lod.builders.lodNodeTemplates.DynamicLodNodeTemplate;
|
||||
import com.seibel.lod.builders.lodNodeTemplates.TriangularLodNodeTemplate;
|
||||
|
||||
/**
|
||||
* Cubic, Triangular, Dynamic
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 06-16-2021
|
||||
* @version 8-4-2021
|
||||
*/
|
||||
public enum LodTemplate
|
||||
{
|
||||
@@ -34,28 +34,28 @@ public enum LodTemplate
|
||||
|
||||
/** Chunks are rendered as
|
||||
* rectangular prisms. */
|
||||
CUBIC(new CubicLodTemplate()),
|
||||
CUBIC(new CubicLodNodeTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other. */
|
||||
TRIANGULAR(new TriangularLodTemplate()),
|
||||
TRIANGULAR(new TriangularLodNodeTemplate()),
|
||||
|
||||
/** Chunks smoothly transition between
|
||||
* each other, unless a neighboring chunk
|
||||
* is at a significantly different height. */
|
||||
DYNAMIC(new DynamicLodTemplate());
|
||||
DYNAMIC(new DynamicLodNodeTemplate());
|
||||
|
||||
|
||||
public final AbstractLodTemplate template;
|
||||
public final AbstractLodNodeTemplate template;
|
||||
|
||||
private LodTemplate(AbstractLodTemplate newTemplate)
|
||||
private LodTemplate(AbstractLodNodeTemplate newTemplate)
|
||||
{
|
||||
template = newTemplate;
|
||||
}
|
||||
|
||||
|
||||
public int getBufferMemoryForSingleLod(LodDetail detail)
|
||||
public int getBufferMemoryForSingleLod(int detailLevel)
|
||||
{
|
||||
return template.getBufferMemoryForSingleLod(detail);
|
||||
return template.getBufferMemoryForSingleLod(detailLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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.handlers;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import com.seibel.lod.objects.LodQuadTree;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
|
||||
/**
|
||||
* This object handles creating LodRegions
|
||||
* from files and saving LodRegion objects
|
||||
* to file.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 6-27-2021
|
||||
*/
|
||||
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 = 3;
|
||||
|
||||
/** 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<LodQuadTreeNode> 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 LodQuadTreeNode(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 (LodQuadTreeNode lodQuadTreeNode : Collections.unmodifiableList(region.getNodeList(LodQuadTreeDimension.FULL_COMPLEXITY_MASK , true, true)))
|
||||
{
|
||||
fw.write(lodQuadTreeNode.toData() + "\n");
|
||||
lodQuadTreeNode.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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.seibel.lod.handlers.LodDimensionFileHandler;
|
||||
|
||||
@@ -72,8 +73,17 @@ public class LodDataPoint
|
||||
depth = (short) newDepth;
|
||||
color = newColor;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int hashCode(){
|
||||
return Objects.hash(this.height, this.depth, this.color);
|
||||
}
|
||||
|
||||
public boolean equals(LodDataPoint other){
|
||||
return (this.height == other.height
|
||||
&& this.depth == other.depth
|
||||
&& this.color == other.color);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs all data in a csv format
|
||||
* with the given delimiter.
|
||||
|
||||
@@ -0,0 +1,470 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
|
||||
/**
|
||||
* This object contains all data useful to render LodBlock in a region (32x32 chunk o 512x512 block)
|
||||
* for every node it contains the border of the block, the size, the position at it's level, the color, the height and the depth.
|
||||
*/
|
||||
public class 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 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 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 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 LodQuadTree(LodQuadTree 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 LodQuadTree(LodQuadTree parent, LodQuadTreeNode lodNode) {
|
||||
this.parent = parent;
|
||||
this.lodNode = lodNode;
|
||||
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<LodQuadTreeNode> dataList, int regionX, int regionZ) {
|
||||
this(null, new LodQuadTreeNode(LodQuadTreeNode.REGION_LEVEL, regionX, regionZ));
|
||||
this.setNodesAtLowerLevel(dataList, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param dataList list of data to put in the node
|
||||
* @param updateHigherLevel will update the color and height of higher level only if true
|
||||
*/
|
||||
public void setNodesAtLowerLevel(List<LodQuadTreeNode> dataList, boolean updateHigherLevel) {
|
||||
for (LodQuadTreeNode lodQuadTreeNode : dataList) {
|
||||
//this is slow, you could set update to false and use an only top down update method.
|
||||
this.setNodeAtLowerLevel(lodQuadTreeNode, updateHigherLevel);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param newLodNode data to put in the node
|
||||
* @param updateHigherLevel will update the color and height of higher level only if true
|
||||
* @return true only if the QuadTree has been changed
|
||||
*/
|
||||
public boolean setNodeAtLowerLevel(LodQuadTreeNode newLodNode, boolean updateHigherLevel) {
|
||||
//check if we try to introduce a level that is higher or equal than the current one
|
||||
byte targetLevel = newLodNode.detailLevel;
|
||||
byte currentLevel = lodNode.detailLevel;
|
||||
if (targetLevel < currentLevel) {
|
||||
int posX = newLodNode.posX;
|
||||
int posZ = newLodNode.posZ;
|
||||
short widthRatio = (short) (lodNode.width / (2 * newLodNode.width));
|
||||
int WE = Math.abs(Math.floorDiv(posX , widthRatio) % 2);
|
||||
int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2);
|
||||
if (getChild(NS, WE) == null) {
|
||||
setChild(NS, WE);
|
||||
}
|
||||
LodQuadTree 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 posX
|
||||
* @param posZ
|
||||
* @param targetLevel
|
||||
* @return
|
||||
*/
|
||||
public LodQuadTreeNode getNodeAtLevelPosition(int posX, int posZ, 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(posX , widthRatio) % 2);
|
||||
int NS = Math.abs(Math.floorDiv(posZ , widthRatio) % 2);
|
||||
if (getChild(NS, WE) == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
LodQuadTree child = getChild(NS, WE);
|
||||
return child.getNodeAtLevelPosition(posX, posZ, targetLevel);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public LodQuadTree 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 LodQuadTree(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 LodQuadTree(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 LodQuadTree(this, (byte) (lodNode.detailLevel - 1), childX, childZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update level update the level data such as levelFull and lodNodeData.
|
||||
*
|
||||
* @param recursiveUpdate if recursive is true the update will rise up to the level 0
|
||||
*/
|
||||
private void updateLevel(boolean recursiveUpdate) {
|
||||
boolean isFull = true;
|
||||
boolean isEmpty = true;
|
||||
List<LodQuadTreeNode> dataList = new ArrayList<>();
|
||||
for (int NS = 0; NS <= 1; NS++) {
|
||||
for (int WE = 0; WE <= 1; WE++) {
|
||||
if (getChild(NS,WE) != null) {
|
||||
dataList.add(getChild(NS,WE).getLodNodeData());
|
||||
isEmpty = false;
|
||||
} else {
|
||||
isFull = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
nodeFull = isFull;
|
||||
nodeEmpty = isEmpty;
|
||||
lodNode.combineData(dataList);
|
||||
if (lodNode.detailLevel < 9 && recursiveUpdate) {
|
||||
this.parent.updateLevel(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* method to get certain nodes from the LodQuadTree
|
||||
*
|
||||
* @param complexityMask set of complexity to accept
|
||||
* @param getOnlyDirty if true it will return only dirty nodes
|
||||
* @param getOnlyLeaf if true it will return only leaf nodes
|
||||
* @return list of nodes
|
||||
*/
|
||||
public List<LodQuadTreeNode> getNodeList(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf) {
|
||||
List<LodQuadTreeNode> nodeList = new ArrayList<>();
|
||||
if (isThereAnyChild()) {
|
||||
//There is at least 1 child
|
||||
if (!getOnlyLeaf
|
||||
&& !(getOnlyDirty && !lodNode.isDirty())
|
||||
&& complexityMask.contains(lodNode.getComplexity())) {
|
||||
nodeList.add(lodNode);
|
||||
}
|
||||
for (int NS = 0; NS <= 1; NS++) {
|
||||
for (int WE = 0; WE <= 1; WE++) {
|
||||
LodQuadTree child = children[NS][WE];
|
||||
if (child != null) {
|
||||
nodeList.addAll(child.getNodeList(complexityMask, getOnlyDirty, getOnlyLeaf));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//There are no children
|
||||
if (!(getOnlyDirty && !lodNode.isDirty())
|
||||
&& (complexityMask.contains(lodNode.getComplexity()))){
|
||||
nodeList.add(lodNode);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return all the nodes that can be rendered based on the data given
|
||||
*
|
||||
* @param x position of the player
|
||||
* @param z position of the player
|
||||
* @param targetLevel minimum level that can be rendered
|
||||
* @param maxDistance maximum distance from the player
|
||||
* @param minDistance minimum distance from the player
|
||||
* @return
|
||||
*/
|
||||
public List<LodQuadTreeNode> getNodeToRender(int x, int z, int targetLevel, Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
|
||||
{
|
||||
List<Integer> distances = new ArrayList<>();
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
|
||||
|
||||
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
|
||||
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
|
||||
List<LodQuadTreeNode> nodeList = new ArrayList<>();
|
||||
|
||||
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) /*|| isCoordinateInLevel(x, z)*/)) {
|
||||
if (targetLevel == lodNode.detailLevel || !isNodeFull()) {
|
||||
if (!lodNode.isVoidNode() && complexityMask.contains(lodNode.getComplexity())) {
|
||||
nodeList.add(lodNode);
|
||||
}
|
||||
} else {
|
||||
for (int NS = 0; NS <= 1; NS++) {
|
||||
for (int WE = 0; WE <= 1; WE++) {
|
||||
LodQuadTree child = getChild(NS, WE);
|
||||
if (child != null) {
|
||||
nodeList.addAll(child.getNodeToRender(x, z, targetLevel, complexityMask, maxDistance, minDistance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Nodes that can be generated in the approximated version
|
||||
* A level is generated only if it has child and is higher than the target level and in the distance range
|
||||
* @param x
|
||||
* @param z
|
||||
* @param targetLevel
|
||||
* @param complexityToGenerate
|
||||
* @param maxDistance
|
||||
* @param minDistance
|
||||
* @return
|
||||
*/
|
||||
public List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> getNodesToGenerate(int x, int z, byte targetLevel, DistanceGenerationMode complexityToGenerate, int maxDistance, int minDistance) {
|
||||
|
||||
List<Integer> distances = new ArrayList<>();
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getStartX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getStartZ(), 2)));
|
||||
distances.add((int) Math.sqrt(Math.pow(x - lodNode.getEndX(), 2) + Math.pow(z - lodNode.getEndZ(), 2)));
|
||||
|
||||
int min = distances.stream().mapToInt(Integer::intValue).min().getAsInt();
|
||||
int max = distances.stream().mapToInt(Integer::intValue).max().getAsInt();
|
||||
List<AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>> nodeList = new ArrayList<>();
|
||||
if (targetLevel <= lodNode.detailLevel && ((min <= maxDistance && max >= minDistance) || isCoordinateInLevel(x, z))) {
|
||||
if(!isThereAnyChild() || targetLevel == lodNode.detailLevel){
|
||||
if (this.lodNode.getComplexity().compareTo(complexityToGenerate) <= 0 ) {
|
||||
nodeList.add(new AbstractMap.SimpleEntry<LodQuadTreeNode, Integer>(this.lodNode, min));
|
||||
}
|
||||
}else {
|
||||
for (int NS = 0; NS <= 1; NS++) {
|
||||
for (int WE = 0; WE <= 1; WE++) {
|
||||
if (getChild(NS, WE) == null) {
|
||||
setChild(NS, WE);
|
||||
}
|
||||
nodeList.addAll(getChild(NS, WE).getNodesToGenerate(x, z, targetLevel, complexityToGenerate, maxDistance, minDistance));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nodeList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* simple getter for lodNodeData
|
||||
*
|
||||
* @return lodNodeData
|
||||
*/
|
||||
public LodQuadTreeNode getLodNodeData() {
|
||||
return lodNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* setter for lodNodeData, to maintain a correct relationship between level this method force update on all parent
|
||||
*
|
||||
* @param newLodQuadTreeNode data to set
|
||||
* @param updateHigherLevel if true it will update all the upper levels.
|
||||
*/
|
||||
public void setLodNodeData(LodQuadTreeNode newLodQuadTreeNode, boolean updateHigherLevel) {
|
||||
if (this.lodNode == null) {
|
||||
this.lodNode = newLodQuadTreeNode;
|
||||
} else {
|
||||
this.lodNode.update(newLodQuadTreeNode);
|
||||
}
|
||||
//a recursive update is necessary to change higher level
|
||||
if (parent != null && updateHigherLevel) parent.updateLevel(true);
|
||||
}
|
||||
|
||||
public boolean isNodeFull() {
|
||||
return nodeFull;
|
||||
}
|
||||
|
||||
public boolean isThereAnyChild() {
|
||||
return !nodeEmpty;
|
||||
}
|
||||
|
||||
public boolean isRenderable() {
|
||||
return (lodNode != null);
|
||||
}
|
||||
|
||||
|
||||
public boolean isCoordinateInLevel(int x, int z){
|
||||
return (lodNode.getStartX() * lodNode.width <= x && lodNode.getStartZ() * lodNode.width <= z && lodNode.getEndX() * lodNode.width >= x && lodNode.getEndZ() * lodNode.width >= z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
String s = lodNode.toString();
|
||||
return s;
|
||||
/*
|
||||
if(isThereAnyChild()){
|
||||
for (int NS = 0; NS <= 1; NS++) {
|
||||
for (int WE = 0; WE <= 1; WE++) {
|
||||
LodQuadTree child = children[NS][WE];
|
||||
if (child != null) {
|
||||
s += '\n' + child.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s;
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,572 @@
|
||||
/*
|
||||
* 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.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.handlers.LodQuadTreeDimensionFileHandler;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerChunkProvider;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
|
||||
public class LodQuadTreeDimension
|
||||
{
|
||||
|
||||
/**TODO a dimension should support two different type of quadTree.
|
||||
* The ones that are near from the player should always be saved and can be fully generated (even at block level)
|
||||
* The ones that are far from the player should always be non-savable and at a high level
|
||||
* If this is not done then you could see how heavy a fully generated 64 region dimension can get.
|
||||
* IDEA : use a mask like the "isRegionDirty" to achieve this*/
|
||||
|
||||
public final DimensionType dimension;
|
||||
|
||||
private volatile int width;
|
||||
private volatile int halfWidth;
|
||||
public long seed;
|
||||
|
||||
public static final Set<DistanceGenerationMode> FULL_COMPLEXITY_MASK = new HashSet<DistanceGenerationMode>();
|
||||
static
|
||||
{
|
||||
// I moved the setup here because eclipse was complaining
|
||||
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY);
|
||||
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
|
||||
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SURFACE);
|
||||
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.FEATURES);
|
||||
FULL_COMPLEXITY_MASK.add(DistanceGenerationMode.SERVER);
|
||||
}
|
||||
|
||||
|
||||
public volatile LodQuadTree regions[][];
|
||||
public volatile boolean isRegionDirty[][];
|
||||
|
||||
private volatile int centerX;
|
||||
private volatile int centerZ;
|
||||
|
||||
private LodQuadTreeDimensionFileHandler fileHandler;
|
||||
|
||||
|
||||
|
||||
|
||||
public LodQuadTreeDimension(DimensionType newDimension, LodQuadTreeWorld lodWorld, int newMaxWidth)
|
||||
{
|
||||
dimension = newDimension;
|
||||
width = newMaxWidth;
|
||||
if(newDimension != null && lodWorld != null) {
|
||||
try {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
File saveDir;
|
||||
if (mc.hasSingleplayerServer()) {
|
||||
// local world
|
||||
|
||||
ServerWorld serverWorld = LodUtil.getServerWorldFromDimension(newDimension);
|
||||
seed = serverWorld.getSeed();
|
||||
// provider needs a separate variable to prevent
|
||||
// the compiler from complaining
|
||||
ServerChunkProvider provider = serverWorld.getChunkSource();
|
||||
saveDir = new File(provider.dataStorage.dataFolder.getCanonicalFile().getPath() + File.separatorChar + "lod");
|
||||
} else {
|
||||
// connected to server
|
||||
|
||||
saveDir = new File(mc.gameDirectory.getCanonicalFile().getPath() +
|
||||
File.separatorChar + "lod server data" + File.separatorChar + LodUtil.getDimensionIDFromWorld(mc.level));
|
||||
}
|
||||
|
||||
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 synchronized 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 addOrOverwriteRegion(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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*this method create all the regions that are null
|
||||
*/
|
||||
public void initializeNullRegions(){
|
||||
int n = regions.length;
|
||||
int xIndex;
|
||||
int zIndex;
|
||||
LodQuadTree region;
|
||||
for(int xRegion=0; xRegion<n; xRegion++){
|
||||
for(int zRegion=0; zRegion<n; zRegion++){
|
||||
xIndex = (xRegion + centerX) - halfWidth;
|
||||
zIndex = (zRegion + centerZ) - halfWidth;
|
||||
region = getRegion(xIndex,zIndex);
|
||||
if (region == null)
|
||||
{
|
||||
// if no region exists, create it
|
||||
region = new LodQuadTree(xIndex, zIndex);
|
||||
addOrOverwriteRegion(region);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 Boolean addNode(LodQuadTreeNode lodNode)
|
||||
{
|
||||
RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(lodNode.centerX, lodNode.centerZ));
|
||||
|
||||
// don't continue if the region can't be saved
|
||||
if (!regionIsInRange(pos.x, pos.z))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
LodQuadTree region = getRegion(pos.x, pos.z);
|
||||
|
||||
if (region == null)
|
||||
{
|
||||
// if no region exists, create it
|
||||
region = new LodQuadTree(pos.x, pos.z);
|
||||
addOrOverwriteRegion(region);
|
||||
}
|
||||
boolean coorectlyAdded = region.setNodeAtLowerLevel(lodNode, true);
|
||||
|
||||
// only save valid LODs to disk
|
||||
if (!lodNode.dontSave && 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();
|
||||
}
|
||||
return coorectlyAdded;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public LodQuadTreeNode getLodFromCoordinates(ChunkPos chunkPos)
|
||||
{
|
||||
return getLodFromCoordinates(chunkPos.x, chunkPos.z, LodQuadTreeNode.CHUNK_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public LodQuadTreeNode getLodFromCoordinates(int chunkPosX, int chunkPosZ)
|
||||
{
|
||||
return getLodFromCoordinates(chunkPosX, chunkPosZ, LodQuadTreeNode.CHUNK_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the LodNodeData at the given X and Z coordinates
|
||||
* in this dimension.
|
||||
* <br>
|
||||
* Returns null if the LodChunk doesn't exist or
|
||||
* is outside the loaded area.
|
||||
*/
|
||||
public LodQuadTreeNode getLodFromCoordinates(int posX, int posZ, int detailLevel)
|
||||
{
|
||||
if (detailLevel > LodQuadTreeNode.REGION_LEVEL)
|
||||
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + detailLevel + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max.");
|
||||
|
||||
LodQuadTree region = getRegion(
|
||||
(Math.floorDiv(posX, (int) (LodQuadTreeNode.REGION_WIDTH/Math.pow(detailLevel,2)))),
|
||||
(Math.floorDiv(posZ, (int) (LodQuadTreeNode.REGION_WIDTH/Math.pow(detailLevel,2)))));
|
||||
|
||||
if(region == null)
|
||||
{
|
||||
//System.out.println("THIS CASE");
|
||||
return null;
|
||||
}
|
||||
|
||||
return region.getNodeAtLevelPosition(posX, posZ, detailLevel);
|
||||
|
||||
/*
|
||||
RegionPos pos = LodUtil.convertChunkPosToRegionPos(new ChunkPos(chunkX, chunkZ));
|
||||
|
||||
LodQuadTree region = getRegion(pos.x, pos.z);
|
||||
|
||||
return region.getNode(chunkX, chunkZ);
|
||||
*/
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if and only if the node at that position exist
|
||||
*/
|
||||
public boolean hasThisPositionBeenGenerated(int posX, int posZ, int level)
|
||||
{
|
||||
if (level > LodQuadTreeNode.REGION_LEVEL)
|
||||
throw new IllegalArgumentException("getLodFromCoordinates given a level of \"" + level + "\" when \"" + LodQuadTreeNode.REGION_LEVEL + "\" is the max.");
|
||||
|
||||
return getLodFromCoordinates(posX,posZ,level).detailLevel == level;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all the nodes that have to be rendered based on the position of the player
|
||||
* @return list of nodes
|
||||
*/
|
||||
public List<LodQuadTreeNode> getNodeToRender(int x, int z, int level, Set<DistanceGenerationMode> complexityMask, int maxDistance, int minDistance)
|
||||
{
|
||||
int n = regions.length;
|
||||
List<LodQuadTreeNode> listOfData = new ArrayList<>();
|
||||
for(int i=0; i<n; i++)
|
||||
{
|
||||
for(int j=0; j<n; j++)
|
||||
{
|
||||
listOfData.addAll(regions[i][j].getNodeToRender(x,z,level,complexityMask,maxDistance,minDistance));
|
||||
}
|
||||
}
|
||||
return listOfData;
|
||||
}
|
||||
|
||||
/**
|
||||
* method to get all the quadtree level that have to be generated based on the position of the player
|
||||
* @return list of quadTrees
|
||||
*/
|
||||
public List<LodQuadTreeNode> getNodesToGenerate(int x, int z, byte level, DistanceGenerationMode complexity, int maxDistance, int minDistance)
|
||||
{
|
||||
int n = regions.length;
|
||||
int xIndex;
|
||||
int zIndex;
|
||||
LodQuadTree region;
|
||||
List<Map.Entry<LodQuadTreeNode,Integer>> listOfQuadTree = new ArrayList<>();
|
||||
for(int xRegion=0; xRegion<n; xRegion++){
|
||||
for(int zRegion=0; zRegion<n; zRegion++){
|
||||
xIndex = (xRegion + centerX) - halfWidth;
|
||||
zIndex = (zRegion + centerZ) - halfWidth;
|
||||
region = getRegion(xIndex,zIndex);
|
||||
if (region == null){
|
||||
region = new LodQuadTree(xIndex, zIndex);
|
||||
addOrOverwriteRegion(region);
|
||||
}
|
||||
listOfQuadTree.addAll(region.getNodesToGenerate(x,z,level,complexity,maxDistance,minDistance));
|
||||
}
|
||||
}
|
||||
Collections.sort(listOfQuadTree,Map.Entry.comparingByValue());
|
||||
return listOfQuadTree.stream().map(entry -> entry.getKey()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* getNodes
|
||||
* @return list of quadTrees
|
||||
*/
|
||||
public List<LodQuadTreeNode> getNodes(Set<DistanceGenerationMode> complexityMask, boolean getOnlyDirty, boolean getOnlyLeaf){
|
||||
int n = regions.length;
|
||||
List<LodQuadTreeNode> listOfNodes = new ArrayList<>();
|
||||
int xIndex;
|
||||
int zIndex;
|
||||
LodQuadTree region;
|
||||
for(int xRegion=0; xRegion<n; xRegion++){
|
||||
for(int zRegion=0; zRegion<n; zRegion++){
|
||||
xIndex = (xRegion + centerX) - halfWidth;
|
||||
zIndex = (zRegion + centerZ) - halfWidth;
|
||||
region = getRegion(xIndex,zIndex);
|
||||
if (region != null){
|
||||
listOfNodes.addAll(region.getNodeList(complexityMask, getOnlyDirty, getOnlyLeaf));
|
||||
}
|
||||
}
|
||||
}
|
||||
return listOfNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO THIS METHOD HAVE TO BE CHANGES. IS NOT THE SAME AS NUMER OF CHUNK
|
||||
* Is it good now? - James
|
||||
*
|
||||
* 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;
|
||||
|
||||
for(LodQuadTreeNode node : region.getNodeList(FULL_COMPLEXITY_MASK,false,true))
|
||||
{
|
||||
if (node != null && !node.voidNode)
|
||||
numbLods++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return numbLods;
|
||||
}
|
||||
|
||||
|
||||
public int getWidth()
|
||||
{
|
||||
if (regions != null)
|
||||
{
|
||||
// we want to get the length directly from the
|
||||
// source to make sure it is in sync with region
|
||||
// and isRegionDirty
|
||||
return regions.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
return width;
|
||||
}
|
||||
}
|
||||
|
||||
public void setRegionWidth(int newWidth)
|
||||
{
|
||||
width = newWidth;
|
||||
|
||||
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,424 @@
|
||||
/*
|
||||
* 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.ChunkPos;
|
||||
import net.minecraft.world.gen.Heightmap;
|
||||
|
||||
public class LodQuadTreeNode
|
||||
{
|
||||
/** This is what separates each piece of data in the toData method */
|
||||
private static final char DATA_DELIMITER = LodQuadTreeDimensionFileHandler.DATA_DELIMITER;
|
||||
|
||||
/** alpha used when drawing chunks in debug mode */
|
||||
private static final int DEBUG_ALPHA = 255; // 0 - 255
|
||||
@SuppressWarnings("unused")
|
||||
private static final Color DEBUG_BLACK = new Color(0, 0, 0, DEBUG_ALPHA);
|
||||
@SuppressWarnings("unused")
|
||||
private static final Color DEBUG_WHITE = new Color(255, 255, 255, DEBUG_ALPHA);
|
||||
@SuppressWarnings("unused")
|
||||
private static final Color INVISIBLE = new Color(0,0,0,0);
|
||||
|
||||
|
||||
/** 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;
|
||||
|
||||
|
||||
/** If this is set to true then toData will return
|
||||
* the empty string */
|
||||
public boolean dontSave = false;
|
||||
|
||||
|
||||
|
||||
/** this is how many pieces of data are exported when toData is called */
|
||||
public static final int NUMBER_OF_DELIMITERS = 10;
|
||||
|
||||
|
||||
//Complexity indicate how the block was built. This is important because we could use
|
||||
public DistanceGenerationMode complexity;
|
||||
|
||||
//level height goes from 0 to 9 with 0 the deepest (block size) and 9 the highest (region size)
|
||||
public final byte detailLevel;
|
||||
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, posx and posz
|
||||
//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;
|
||||
|
||||
public LodDataPoint lodDataPoint;
|
||||
|
||||
//void node is used
|
||||
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(ChunkPos pos)
|
||||
{
|
||||
this(CHUNK_LEVEL, pos.x, pos.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
startX = posX * width;
|
||||
startZ = posZ * width;
|
||||
endX = startX + width - 1;
|
||||
endZ = startZ + width - 1;
|
||||
|
||||
centerX = startX + width/2;
|
||||
centerZ = startZ + 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);
|
||||
|
||||
startX = posX * width;
|
||||
startZ = posZ * width;
|
||||
|
||||
endX = startX + width - 1;
|
||||
endZ = startZ + width - 1;
|
||||
centerX = startX + width/2;
|
||||
centerZ = startZ + width/2;
|
||||
|
||||
this.lodDataPoint = lodDataPoint;
|
||||
this.complexity = complexity;
|
||||
|
||||
dirty = true;
|
||||
voidNode = false;
|
||||
dontSave = false;
|
||||
}
|
||||
|
||||
public LodQuadTreeNode(String data)
|
||||
{
|
||||
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 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));
|
||||
Color color = new Color(r,g,b,a);
|
||||
lodDataPoint = new LodDataPoint(height,depth,color);
|
||||
|
||||
int val = Integer.parseInt(data.substring(lastIndex+1,index));
|
||||
this.voidNode = (val == 1);
|
||||
|
||||
|
||||
width = (short) Math.pow(2, detailLevel);
|
||||
|
||||
startX = posX * width;
|
||||
startZ = posZ * width;
|
||||
endX = startX + width - 1;
|
||||
endZ = startZ + width - 1;
|
||||
|
||||
centerX = startX + width/2;
|
||||
centerZ = startZ + width/2;
|
||||
|
||||
dirty = false;
|
||||
dontSave = false;
|
||||
}
|
||||
|
||||
public void update(LodQuadTreeNode lodQuadTreeNode){
|
||||
this.lodDataPoint = lodQuadTreeNode.lodDataPoint;
|
||||
this.complexity = lodQuadTreeNode.complexity;
|
||||
this.voidNode = lodQuadTreeNode.voidNode;
|
||||
dirty = true;
|
||||
dontSave = false;
|
||||
}
|
||||
|
||||
public LodDataPoint getLodDataPoint(){
|
||||
return lodDataPoint;
|
||||
}
|
||||
|
||||
public void combineData(List<LodQuadTreeNode> dataList){
|
||||
if(dataList.isEmpty()){
|
||||
lodDataPoint = new LodDataPoint();
|
||||
}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 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){
|
||||
minComplexity = node.complexity;
|
||||
}
|
||||
}
|
||||
complexity = minComplexity;
|
||||
|
||||
voidNode = dataList.stream().filter(x -> !x.voidNode).count() == 0;
|
||||
}
|
||||
dirty = true;
|
||||
dontSave = false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int hashCode(){
|
||||
return Objects.hash(this.complexity, this.detailLevel, this.posX, this.posZ, this.lodDataPoint, this.voidNode);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
int val = voidNode ? 1 : 0;
|
||||
s += Integer.toString(val) + DATA_DELIMITER;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return this.toData();
|
||||
}
|
||||
|
||||
|
||||
// These getters should be used
|
||||
|
||||
public byte getDetailLevel() {
|
||||
return detailLevel;
|
||||
}
|
||||
|
||||
public DistanceGenerationMode getComplexity() {
|
||||
return complexity;
|
||||
}
|
||||
|
||||
public short getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
public int getPosX() {
|
||||
return posX;
|
||||
}
|
||||
|
||||
public int getPosZ() {
|
||||
return posZ;
|
||||
}
|
||||
|
||||
public int getStartX() {
|
||||
return startX;
|
||||
}
|
||||
|
||||
public int getStartZ() {
|
||||
return startZ;
|
||||
}
|
||||
|
||||
public int getEndX() {
|
||||
return endX;
|
||||
}
|
||||
|
||||
public int getEndZ() {
|
||||
return endZ;
|
||||
}
|
||||
|
||||
public int getCenterX() {
|
||||
return centerX;
|
||||
}
|
||||
|
||||
public int getCenterZ() {
|
||||
return centerZ;
|
||||
}
|
||||
|
||||
public boolean isVoidNode() {
|
||||
return voidNode;
|
||||
}
|
||||
|
||||
public boolean isDirty() {
|
||||
return dirty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* This file is part of the LOD Mod, licensed under the GNU GPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.seibel.lod.objects;
|
||||
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
import net.minecraft.world.DimensionType;
|
||||
|
||||
/**
|
||||
* This stores all LODs for a given world.
|
||||
*
|
||||
*/
|
||||
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<DimensionType, LodQuadTreeDimension>();
|
||||
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 == 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,354 @@
|
||||
/*
|
||||
* 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.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.Shape;
|
||||
import java.awt.Stroke;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.Timer;
|
||||
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.util.BiomeColorsUtils;
|
||||
|
||||
import kaptainwutax.biomeutils.source.OverworldBiomeSource;
|
||||
import kaptainwutax.mcutils.version.MCVersion;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class QuadTreeImage extends JPanel
|
||||
{
|
||||
private static final int PREF_W = 1024;
|
||||
private static final int PREF_H = PREF_W;
|
||||
private List<MyDrawable> drawables = new ArrayList<>();
|
||||
|
||||
public QuadTreeImage() {
|
||||
setBackground(Color.white);
|
||||
}
|
||||
|
||||
public void addMyDrawable(MyDrawable myDrawable) {
|
||||
drawables.add(myDrawable);
|
||||
repaint();
|
||||
}
|
||||
|
||||
@Override
|
||||
// make it bigger
|
||||
public Dimension getPreferredSize() {
|
||||
if (isPreferredSizeSet()) {
|
||||
return super.getPreferredSize();
|
||||
}
|
||||
return new Dimension(PREF_W, PREF_H);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g) {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g;
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
|
||||
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
for (MyDrawable myDrawable : drawables) {
|
||||
myDrawable.draw(g2);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearAll() {
|
||||
drawables.clear();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private static void createAndShowGui() {
|
||||
|
||||
final QuadTreeImage quadTreeImage = new QuadTreeImage();
|
||||
|
||||
|
||||
JFrame frame = new JFrame("DrawChit");
|
||||
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||
frame.getContentPane().add(quadTreeImage);
|
||||
frame.pack();
|
||||
frame.setLocationByPlatform(true);
|
||||
frame.setVisible(true);
|
||||
List<List<LodQuadTreeNode>> listOfList = new ArrayList<>();
|
||||
OverworldBiomeSource biomeSource = new OverworldBiomeSource(MCVersion.v1_16_5, 1000);
|
||||
//EndBiomeSource biomeSource = new EndBiomeSource(MCVersion.v1_16_5, 1000);
|
||||
int sizeOfTheWorld = 32;
|
||||
|
||||
LodQuadTreeDimension dim = new LodQuadTreeDimension(null, null, sizeOfTheWorld);
|
||||
|
||||
//SIMULATING A PLAYER MOVING,
|
||||
int[] playerXs = {0, 100, 200, 300, 400, 1000};
|
||||
int[] playerZs = {0, 100, 200, 300, 400, 500};
|
||||
for (int pos = 0; pos < 1; pos++) {
|
||||
int playerX = 0 + playerXs[pos]; //2097152
|
||||
int playerZ = 0 + playerZs[pos]/2;
|
||||
|
||||
//int sizeOfTheWorld=512; //TRY THIS TO SEE A 250'000 BLOCK RENDER DISTANCE
|
||||
dim.move(Math.floorDiv(playerX, 512), Math.floorDiv(playerZ, 512));
|
||||
/*
|
||||
System.out.println(dim.getRegion(0, 0));
|
||||
System.out.println(dim.getCenterX());
|
||||
System.out.println(dim.getCenterZ());
|
||||
System.out.println(dim.getWidth());
|
||||
|
||||
System.out.println("GETTING LOD FROM COORDINATE BEFORE GENERETION");
|
||||
System.out.println(dim.getLodFromCoordinates(-6, -6));
|
||||
*/
|
||||
|
||||
DistanceGenerationMode[] complexities = {DistanceGenerationMode.BIOME_ONLY, DistanceGenerationMode.BIOME_ONLY, DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT, DistanceGenerationMode.SURFACE, DistanceGenerationMode.SURFACE, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES, DistanceGenerationMode.FEATURES};
|
||||
int[] distances = {1000000, 8000, 4000, 2000, 1000, 500, 250, 100, 50, 25};
|
||||
for (int i = 0; i <= (9-2); i++) {
|
||||
List<LodQuadTreeNode> levelToGenerate = dim.getNodesToGenerate(playerX, playerZ, (byte) (9 - i), complexities[i], distances[i]*2, 0);
|
||||
//System.out.println(levelToGenerate);
|
||||
for (LodQuadTreeNode node : levelToGenerate) {
|
||||
Color color;
|
||||
int startX = node.startX;
|
||||
int startZ = node.startZ;
|
||||
int endX = node.endX;
|
||||
int endZ = node.endZ;
|
||||
int centerX = node.centerX;
|
||||
int centerZ = node.centerZ;
|
||||
int width = node.width;
|
||||
byte otherLevel = LodQuadTreeNode.BLOCK_LEVEL;
|
||||
int otherWidth = LodQuadTreeNode.BLOCK_WIDTH;
|
||||
|
||||
List<Integer> posXs = new ArrayList<>();
|
||||
List<Integer> posZs = new ArrayList<>();
|
||||
posXs.add(Math.floorDiv(startX, otherWidth));
|
||||
posXs.add(Math.floorDiv(centerX, otherWidth));
|
||||
posZs.add(Math.floorDiv(startZ, otherWidth));
|
||||
posZs.add(Math.floorDiv(centerZ, otherWidth));
|
||||
|
||||
for (Integer posXI : posXs) {
|
||||
for (Integer posZI : posZs) {
|
||||
int posX = posXI.intValue();
|
||||
int posZ = posZI.intValue();
|
||||
color = BiomeColorsUtils.getColorFromBiomeManual(biomeSource.getBiome(posX, 0, posZ));
|
||||
LodQuadTreeNode newNode = new LodQuadTreeNode(otherLevel, posX, posZ, new LodDataPoint(0, 0, color), complexities[i]);
|
||||
if (dim.addNode(newNode)) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Set<DistanceGenerationMode> complexityMask = new HashSet<>();
|
||||
//complexityMask.add(DistanceGenerationMode.SERVER);
|
||||
//complexityMask.add(DistanceGenerationMode.FEATURES);
|
||||
//complexityMask.add(DistanceGenerationMode.SURFACE);
|
||||
//complexityMask.add(DistanceGenerationMode.BIOME_ONLY_SIMULATE_HEIGHT);
|
||||
//complexityMask.add(DistanceGenerationMode.BIOME_ONLY);
|
||||
Set<DistanceGenerationMode> complexityMask = LodQuadTreeDimension.FULL_COMPLEXITY_MASK;
|
||||
|
||||
List<LodQuadTreeNode> lodList = new ArrayList<>();
|
||||
//The min and max distances should increase quadratically
|
||||
//int[] distances2 = {100000, 8000, 4000, 2000, 1000, 500, 250, 0};
|
||||
int[] distances2 = {0, 250, 500, 1000, 2000, 4000, 8000, 100000};
|
||||
for (int h = 0; h <= (9 - 3); h++) {
|
||||
lodList.addAll(dim.getNodeToRender(playerX, playerZ, (byte) (3+h), complexityMask, distances2[h+1], distances2[h]));
|
||||
}
|
||||
System.out.println("Number of node to render " + lodList.size());
|
||||
listOfList.add(lodList);
|
||||
System.out.println("Number of list " + listOfList.size());
|
||||
/*
|
||||
List<LodQuadTreeNode> lodList = dim.getNodes(complexityMask, false, false); //USE THIS TO SEE AL THE LODS
|
||||
listOfList.add(lodList);
|
||||
*/
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
System.out.println("GETTING LOD FROM COORDINATE AFTER GENERETION");
|
||||
System.out.println(dim.getLodFromCoordinates(0, 100, (byte) 1));
|
||||
//FROM THIS POINT ON THE CODE JUST CREATE THE IMAGE
|
||||
|
||||
int timerDelay = 1000;
|
||||
System.out.println("STARTING");
|
||||
System.out.println(dim.getWidth());
|
||||
System.out.println(dim.getCenterX());
|
||||
int xOffset = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startX).min().getAsInt()).min().getAsInt();
|
||||
int zOffset = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startZ).min().getAsInt()).min().getAsInt();
|
||||
int maxX = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startX).max().getAsInt()).min().getAsInt();
|
||||
int maxZ = listOfList.stream().mapToInt(x -> x.stream().mapToInt(y -> y.startZ).max().getAsInt()).min().getAsInt();
|
||||
int maxSize = Math.max(maxX - xOffset, maxZ - zOffset) / 512;
|
||||
System.out.println(xOffset);
|
||||
System.out.println(zOffset);
|
||||
new Timer(timerDelay, new ActionListener() {
|
||||
private int drawCount = 0;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
if (drawCount >= listOfList.size()) {
|
||||
drawCount = 0;
|
||||
} else {
|
||||
if (drawCount == 0) quadTreeImage.clearAll();
|
||||
final List<MyDrawable> myDrawables = new ArrayList<>();
|
||||
double amp = ((double) 2) / ((double) sizeOfTheWorld);
|
||||
Collection<LodQuadTreeNode> lodList = listOfList.get(drawCount);
|
||||
for (LodQuadTreeNode data : lodList) {
|
||||
Color colorOfComplexity = Color.black;
|
||||
switch (data.complexity){
|
||||
case NONE:
|
||||
colorOfComplexity = Color.black;
|
||||
break;
|
||||
case BIOME_ONLY:
|
||||
colorOfComplexity = Color.red;
|
||||
break;
|
||||
case BIOME_ONLY_SIMULATE_HEIGHT:
|
||||
colorOfComplexity = Color.yellow;
|
||||
break;
|
||||
case SURFACE:
|
||||
colorOfComplexity = Color.blue;
|
||||
break;
|
||||
case FEATURES:
|
||||
colorOfComplexity = Color.cyan;
|
||||
break;
|
||||
case SERVER:
|
||||
colorOfComplexity = Color.green;
|
||||
break;
|
||||
}
|
||||
myDrawables.add(new MyDrawable(new Rectangle2D.Double(
|
||||
((data.startX - xOffset) * amp),
|
||||
((data.startZ - zOffset) * amp),
|
||||
data.width * amp,
|
||||
data.width * amp),
|
||||
data.lodDataPoint.color, new BasicStroke(1)));
|
||||
}
|
||||
myDrawables.add(new MyDrawable(new Rectangle2D.Double(
|
||||
(playerXs[0] - xOffset) * amp,
|
||||
(playerZs[0] - zOffset) * amp,
|
||||
20,
|
||||
20),
|
||||
Color.yellow, new BasicStroke(1)));
|
||||
for (int k = 0; k < myDrawables.size(); k++) {
|
||||
quadTreeImage.addMyDrawable(myDrawables.get(k));
|
||||
}
|
||||
/*
|
||||
BufferedImage img = new BufferedImage(frame.getWidth(), frame.getHeight(), BufferedImage.TYPE_INT_RGB);
|
||||
Graphics2D g2d = img.createGraphics();
|
||||
frame.printAll(g2d);
|
||||
g2d.dispose();
|
||||
try {
|
||||
ImageIO.write(img, "png", new File("ImgEnd" + drawCount + ".png"));
|
||||
} catch (IOException ioException) {
|
||||
ioException.printStackTrace();
|
||||
}
|
||||
*/
|
||||
drawCount++;
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
/*
|
||||
LodQuadTreeDimension dim2 = new LodQuadTreeDimension(null, null, 1);
|
||||
dim2.move(10000000,10000000);
|
||||
List<LodQuadTreeNode> levelToGenerate = dim2.getNodesToGenerate(10000000, 10000000, (byte) 7,DistanceGenerationMode.SERVER, (int) 10000, 0);
|
||||
System.out.println(levelToGenerate);
|
||||
dim2.addNode(new LodQuadTreeNode((byte) 0,0,0,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER));
|
||||
dim2.addNode(new LodQuadTreeNode((byte) 0,256,0,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER));
|
||||
dim2.addNode(new LodQuadTreeNode((byte) 0,0,256,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER));
|
||||
dim2.addNode(new LodQuadTreeNode((byte) 0,256,256,new LodDataPoint(-1,-1, new Color(100,100,100)),DistanceGenerationMode.SERVER));
|
||||
levelToGenerate = dim2.getNodesToGenerate(10000000, 10000000, (byte) 7,DistanceGenerationMode.SERVER, (int) 10000, 0);
|
||||
System.out.println(levelToGenerate);
|
||||
|
||||
*/
|
||||
/*
|
||||
System.out.println(DistanceGenerationMode.SERVER.compareTo(DistanceGenerationMode.SERVER));
|
||||
System.out.println(DistanceGenerationMode.NONE.compareTo(DistanceGenerationMode.SERVER));
|
||||
System.out.println(DistanceGenerationMode.SERVER.compareTo(DistanceGenerationMode.NONE));
|
||||
System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.SURFACE));
|
||||
System.out.println(DistanceGenerationMode.SURFACE.compareTo(DistanceGenerationMode.BIOME_ONLY));
|
||||
System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.BIOME_ONLY));
|
||||
System.out.println(DistanceGenerationMode.BIOME_ONLY.compareTo(DistanceGenerationMode.NONE));
|
||||
System.out.println(DistanceGenerationMode.NONE.compareTo(DistanceGenerationMode.BIOME_ONLY));
|
||||
|
||||
*/
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
createAndShowGui();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class MyDrawable {
|
||||
private Shape shape;
|
||||
private Color color;
|
||||
private Stroke stroke;
|
||||
|
||||
public MyDrawable(Shape shape, Color color, Stroke stroke) {
|
||||
this.shape = shape;
|
||||
this.color = color;
|
||||
this.stroke = stroke;
|
||||
}
|
||||
|
||||
public Shape getShape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
public Color getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public Stroke getStroke() {
|
||||
return stroke;
|
||||
}
|
||||
|
||||
public void draw(Graphics2D g2) {
|
||||
Color oldColor = g2.getColor();
|
||||
Stroke oldStroke = g2.getStroke();
|
||||
|
||||
g2.setColor(color);
|
||||
g2.fill(shape);
|
||||
//g2.setStroke(stroke);
|
||||
g2.draw(shape);
|
||||
|
||||
g2.setColor(oldColor);
|
||||
g2.setStroke(oldStroke);
|
||||
}
|
||||
|
||||
public void fill(Graphics2D g2) {
|
||||
Color oldColor = g2.getColor();
|
||||
Stroke oldStroke = g2.getStroke();
|
||||
|
||||
g2.setColor(color);
|
||||
g2.setStroke(stroke);
|
||||
g2.fill(shape);
|
||||
|
||||
g2.setColor(oldColor);
|
||||
g2.setStroke(oldStroke);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,17 +20,18 @@ package com.seibel.lod.proxy;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.seibel.lod.builders.LodBufferBuilder;
|
||||
import com.seibel.lod.builders.LodChunkBuilder;
|
||||
import com.seibel.lod.builders.LodNodeBufferBuilder;
|
||||
import com.seibel.lod.builders.LodNodeBuilder;
|
||||
import com.seibel.lod.builders.worldGeneration.LodChunkGenWorker;
|
||||
import com.seibel.lod.builders.worldGeneration.LodNodeGenWorker;
|
||||
import com.seibel.lod.enums.DistanceGenerationMode;
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeWorld;
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.objects.LodWorld;
|
||||
import com.seibel.lod.render.LodRenderer;
|
||||
import com.seibel.lod.render.LodNodeRenderer;
|
||||
import com.seibel.lod.util.LodUtil;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -51,10 +52,16 @@ public class ClientProxy
|
||||
{
|
||||
public static final Logger LOGGER = LogManager.getLogger("LOD");
|
||||
|
||||
private static LodWorld lodWorld = new LodWorld();
|
||||
private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder();
|
||||
private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder);
|
||||
private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
|
||||
// private static LodWorld lodWorld = new LodWorld();
|
||||
// private static LodChunkBuilder lodChunkBuilder = new LodChunkBuilder();
|
||||
// private static LodBufferBuilder lodBufferBuilder = new LodBufferBuilder(lodChunkBuilder);
|
||||
// private static LodRenderer renderer = new LodRenderer(lodBufferBuilder);
|
||||
|
||||
private static LodQuadTreeWorld lodWorld = new LodQuadTreeWorld();
|
||||
private static LodNodeBuilder lodNodeBuilder = new LodNodeBuilder();
|
||||
private static LodNodeBufferBuilder lodBufferBuilder = new LodNodeBufferBuilder(lodNodeBuilder);
|
||||
private static LodNodeRenderer renderer = new LodNodeRenderer(lodBufferBuilder);
|
||||
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
@@ -85,17 +92,17 @@ public class ClientProxy
|
||||
// TODO is this logic good?
|
||||
(mc.options.renderDistance * LodChunk.WIDTH * 2 * LodConfig.CLIENT.lodChunkRadiusMultiplier.get()) / LodRegion.SIZE
|
||||
);
|
||||
if (lodChunkBuilder.regionWidth != newWidth)
|
||||
if (lodNodeBuilder.regionWidth != newWidth)
|
||||
{
|
||||
lodWorld.resizeDimensionRegionWidth(newWidth);
|
||||
lodChunkBuilder.regionWidth = newWidth;
|
||||
lodNodeBuilder.regionWidth = newWidth;
|
||||
|
||||
// skip this frame, hopefully the lodWorld
|
||||
// should have everything set up by then
|
||||
return;
|
||||
}
|
||||
|
||||
LodDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
|
||||
LodQuadTreeDimension lodDim = lodWorld.getLodDimension(mc.player.level.dimensionType());
|
||||
if (lodDim == null)
|
||||
return;
|
||||
|
||||
@@ -155,7 +162,8 @@ public class ClientProxy
|
||||
@SubscribeEvent
|
||||
public void chunkLoadEvent(ChunkEvent.Load event)
|
||||
{
|
||||
lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld());
|
||||
//lodChunkBuilder.generateLodChunkAsync(event.getChunk(), lodWorld, event.getWorld());
|
||||
lodNodeBuilder.generateLodNodeAsync(event.getChunk(), lodWorld, event.getWorld());
|
||||
}
|
||||
|
||||
|
||||
@@ -179,6 +187,7 @@ public class ClientProxy
|
||||
// if this isn't done unfinished tasks may be left in the queue
|
||||
// preventing new LodChunks form being generated
|
||||
LodChunkGenWorker.restartExecuterService();
|
||||
LodNodeGenWorker.restartExecuterService();
|
||||
|
||||
lodBufferBuilder.numberOfChunksWaitingToGenerate.set(0);
|
||||
// the player has disconnected from a server
|
||||
@@ -197,7 +206,8 @@ public class ClientProxy
|
||||
event.getClass() == BlockEvent.PortalSpawnEvent.class)
|
||||
{
|
||||
// recreate the LOD where the blocks were changed
|
||||
lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
|
||||
//lodChunkBuilder.generateLodChunkAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
|
||||
lodNodeBuilder.generateLodNodeAsync(event.getWorld().getChunk(event.getPos()), lodWorld, event.getWorld());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,17 +218,17 @@ public class ClientProxy
|
||||
// public getters //
|
||||
//================//
|
||||
|
||||
public static LodWorld getLodWorld()
|
||||
public static LodQuadTreeWorld getLodWorld()
|
||||
{
|
||||
return lodWorld;
|
||||
}
|
||||
|
||||
public static LodChunkBuilder getLodBuilder()
|
||||
public static LodNodeBuilder getLodBuilder()
|
||||
{
|
||||
return lodChunkBuilder;
|
||||
return lodNodeBuilder;
|
||||
}
|
||||
|
||||
public static LodRenderer getRenderer()
|
||||
public static LodNodeRenderer getRenderer()
|
||||
{
|
||||
return renderer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,886 @@
|
||||
/*
|
||||
* 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.render;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.lwjgl.opengl.GL;
|
||||
import org.lwjgl.opengl.GL11;
|
||||
import org.lwjgl.opengl.NVFogDistance;
|
||||
|
||||
import com.mojang.blaze3d.matrix.MatrixStack;
|
||||
import com.mojang.blaze3d.platform.GlStateManager;
|
||||
import com.mojang.blaze3d.systems.RenderSystem;
|
||||
import com.seibel.lod.builders.LodNodeBufferBuilder;
|
||||
import com.seibel.lod.enums.FogDistance;
|
||||
import com.seibel.lod.enums.FogDrawOverride;
|
||||
import com.seibel.lod.enums.FogQuality;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.handlers.ReflectionHandler;
|
||||
import com.seibel.lod.objects.LodQuadTreeDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.objects.NearFarBuffer;
|
||||
import com.seibel.lod.objects.NearFarFogSettings;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.entity.player.ClientPlayerEntity;
|
||||
import net.minecraft.client.renderer.ActiveRenderInfo;
|
||||
import net.minecraft.client.renderer.BufferBuilder;
|
||||
import net.minecraft.client.renderer.FogRenderer;
|
||||
import net.minecraft.client.renderer.GameRenderer;
|
||||
import net.minecraft.client.renderer.WorldRenderer;
|
||||
import net.minecraft.client.renderer.vertex.DefaultVertexFormats;
|
||||
import net.minecraft.client.renderer.vertex.VertexBuffer;
|
||||
import net.minecraft.client.renderer.vertex.VertexFormat;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.profiler.IProfiler;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
import net.minecraft.util.math.MathHelper;
|
||||
import net.minecraft.util.math.vector.Matrix4f;
|
||||
import net.minecraft.util.math.vector.Vector3d;
|
||||
import net.minecraft.util.math.vector.Vector3f;
|
||||
|
||||
|
||||
/**
|
||||
* This is where all the magic happens. <br>
|
||||
* This is where LODs are draw to the world.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 07-4-2021
|
||||
*/
|
||||
public class LodNodeRenderer
|
||||
{
|
||||
/** this is the light used when rendering the LODs,
|
||||
* it should be something different than what is used by Minecraft */
|
||||
private static final int LOD_GL_LIGHT_NUMBER = GL11.GL_LIGHT2;
|
||||
|
||||
/**
|
||||
* 64 MB by default is the maximum amount of memory that
|
||||
* can be directly allocated. <br><br>
|
||||
*
|
||||
* I know there are commands to change that amount
|
||||
* (specifically "-XX:MaxDirectMemorySize"), but
|
||||
* I have no idea how to access that amount. <br>
|
||||
* So I guess this will be the hard limit for now. <br><br>
|
||||
*
|
||||
* https://stackoverflow.com/questions/50499238/bytebuffer-allocatedirect-and-xmx
|
||||
*/
|
||||
public static final int MAX_ALOCATEABLE_DIRECT_MEMORY = 64 * 1024 * 1024;
|
||||
|
||||
/** Does this computer's GPU support fancy fog? */
|
||||
private static Boolean fancyFogAvailable = null;
|
||||
|
||||
|
||||
|
||||
/** If true the LODs colors will be replaced with
|
||||
* a checkerboard, this can be used for debugging. */
|
||||
public boolean debugging = false;
|
||||
|
||||
private Minecraft mc;
|
||||
private GameRenderer gameRender;
|
||||
private IProfiler profiler;
|
||||
private float farPlaneDistance;
|
||||
private ReflectionHandler reflectionHandler;
|
||||
|
||||
|
||||
/** This is used to generate the buildable buffers */
|
||||
private LodNodeBufferBuilder lodNodeBufferBuilder;
|
||||
|
||||
/** The buffers that are used to draw LODs using near fog */
|
||||
private volatile BufferBuilder drawableNearBuffer;
|
||||
/** The buffers that are used to draw LODs using far fog */
|
||||
private volatile BufferBuilder drawableFarBuffer;
|
||||
|
||||
/** This is the VertexBuffer used to draw any LODs that use near fog */
|
||||
private volatile VertexBuffer nearVbo;
|
||||
/** This is the VertexBuffer used to draw any LODs that use far fog */
|
||||
private volatile VertexBuffer farVbo;
|
||||
public static final VertexFormat LOD_VERTEX_FORMAT = DefaultVertexFormats.POSITION_COLOR;
|
||||
|
||||
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int previousChunkRenderDistance = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int prevChunkX = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private int prevChunkZ = 0;
|
||||
/** This is used to determine if the LODs should be regenerated */
|
||||
private FogDistance prevFogDistance = FogDistance.NEAR_AND_FAR;
|
||||
|
||||
/** if this is true the LOD buffers should be regenerated,
|
||||
* provided they aren't already being regenerated. */
|
||||
private volatile boolean regen = false;
|
||||
|
||||
/** This HashSet contains every chunk that Vanilla Minecraft
|
||||
* is going to render */
|
||||
public HashSet<ChunkPos> vanillaRenderedChunks = new HashSet<>();
|
||||
|
||||
|
||||
|
||||
public LodNodeRenderer(LodNodeBufferBuilder newLodNodeBufferBuilder)
|
||||
{
|
||||
mc = Minecraft.getInstance();
|
||||
gameRender = mc.gameRenderer;
|
||||
|
||||
reflectionHandler = new ReflectionHandler();
|
||||
lodNodeBufferBuilder = newLodNodeBufferBuilder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Besides drawing the LODs this method also starts
|
||||
* the async process of generating the Buffers that hold those LODs.
|
||||
*
|
||||
* @param newDim The dimension to draw, if null doesn't replace the current dimension.
|
||||
* @param partialTicks how far into the current tick this method was called.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public void drawLODs(LodQuadTreeDimension lodDim, float partialTicks, IProfiler newProfiler)
|
||||
{
|
||||
if (lodDim == null)
|
||||
{
|
||||
// if there aren't any loaded LodChunks
|
||||
// don't try drawing anything
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===============//
|
||||
// initial setup //
|
||||
//===============//
|
||||
|
||||
profiler = newProfiler;
|
||||
profiler.push("LOD setup");
|
||||
|
||||
|
||||
// only check the GPU capability's once
|
||||
if (fancyFogAvailable == null)
|
||||
{
|
||||
// see if this GPU can run fancy fog
|
||||
fancyFogAvailable = GL.getCapabilities().GL_NV_fog_distance;
|
||||
|
||||
if (!fancyFogAvailable)
|
||||
{
|
||||
ClientProxy.LOGGER.info("This GPU does not support GL_NV_fog_distance. This means that fancy fog options will not be available.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ClientPlayerEntity player = mc.player;
|
||||
|
||||
// should LODs be regenerated?
|
||||
if ((int)player.getX() / LodQuadTreeNode.CHUNK_WIDTH != prevChunkX ||
|
||||
(int)player.getZ() / LodQuadTreeNode.CHUNK_WIDTH != prevChunkZ ||
|
||||
previousChunkRenderDistance != mc.options.renderDistance ||
|
||||
prevFogDistance != LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
// yes
|
||||
regen = true;
|
||||
|
||||
prevChunkX = (int)player.getX() / LodQuadTreeNode.CHUNK_WIDTH;
|
||||
prevChunkZ = (int)player.getZ() / LodQuadTreeNode.CHUNK_WIDTH;
|
||||
prevFogDistance = LodConfig.CLIENT.fogDistance.get();
|
||||
}
|
||||
else
|
||||
{
|
||||
// nope, the player hasn't moved, the
|
||||
// render distance hasn't changed, and
|
||||
// the dimension is the same
|
||||
}
|
||||
|
||||
// did the user change the debug setting?
|
||||
if (LodConfig.CLIENT.debugMode.get() != debugging)
|
||||
{
|
||||
debugging = LodConfig.CLIENT.debugMode.get();
|
||||
regen = true;
|
||||
}
|
||||
|
||||
|
||||
// determine how far the game's render distance is currently set
|
||||
int renderDistWidth = mc.options.renderDistance;
|
||||
farPlaneDistance = renderDistWidth * LodQuadTreeNode.CHUNK_WIDTH;
|
||||
|
||||
// set how big the LODs will be and how far they will go
|
||||
int totalLength = (int) farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 10;
|
||||
int numbChunksWide = (totalLength / LodQuadTreeNode.CHUNK_WIDTH);
|
||||
|
||||
// determine which LODs should not be rendered close to the player
|
||||
HashSet<ChunkPos> chunkPosToSkip = getNearbyLodChunkPosToSkip(lodDim, player.blockPosition());
|
||||
|
||||
// see if the chunks Minecraft is going to render are the
|
||||
// same as last time
|
||||
if (!vanillaRenderedChunks.containsAll(chunkPosToSkip))
|
||||
{
|
||||
regen = true;
|
||||
vanillaRenderedChunks = chunkPosToSkip;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// create the LODs //
|
||||
//=================//
|
||||
|
||||
// only regenerate the LODs if:
|
||||
// 1. we want to regenerate LODs
|
||||
// 2. we aren't already regenerating the LODs
|
||||
// 3. we aren't waiting for the build and draw buffers to swap
|
||||
// (this is to prevent thread conflicts)
|
||||
if (regen && !lodNodeBufferBuilder.generatingBuffers && !lodNodeBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
// this will mainly happen when the view distance is changed
|
||||
if (drawableNearBuffer == null || drawableFarBuffer == null ||
|
||||
previousChunkRenderDistance != mc.options.renderDistance)
|
||||
setupBuffers(numbChunksWide);
|
||||
|
||||
// generate the LODs on a separate thread to prevent stuttering or freezing
|
||||
lodNodeBufferBuilder.generateLodBuffersAsync(this, lodDim, player.getX(), player.getZ(), numbChunksWide);
|
||||
|
||||
// the regen process has been started,
|
||||
// it will be done when lodBufferBuilder.newBuffersAvaliable
|
||||
// is true
|
||||
regen = false;
|
||||
}
|
||||
|
||||
// replace the buffers used to draw and build,
|
||||
// this is only done when the createLodBufferGenerationThread
|
||||
// has finished executing on a parallel thread.
|
||||
if (lodNodeBufferBuilder.newBuffersAvaliable())
|
||||
{
|
||||
swapBuffers();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===========================//
|
||||
// GL settings for rendering //
|
||||
//===========================//
|
||||
|
||||
// set the required open GL settings
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
||||
GL11.glDisable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glEnable(GL11.GL_CULL_FACE);
|
||||
GL11.glEnable(GL11.GL_COLOR_MATERIAL);
|
||||
GL11.glEnable(GL11.GL_DEPTH_TEST);
|
||||
|
||||
// disable the lights Minecraft uses
|
||||
GL11.glDisable(GL11.GL_LIGHT0);
|
||||
GL11.glDisable(GL11.GL_LIGHT1);
|
||||
|
||||
// get the default projection matrix so we can
|
||||
// reset it after drawing the LODs
|
||||
float[] defaultProjMatrix = new float[16];
|
||||
GL11.glGetFloatv(GL11.GL_PROJECTION_MATRIX, defaultProjMatrix);
|
||||
|
||||
Matrix4f modelViewMatrix = generateModelViewMatrix(partialTicks);
|
||||
|
||||
setupProjectionMatrix(partialTicks);
|
||||
setupLighting(lodDim, partialTicks);
|
||||
|
||||
NearFarFogSettings fogSettings = determineFogSettings();
|
||||
|
||||
// determine the current fog settings so they can be
|
||||
// reset after drawing the LODs
|
||||
float defaultFogStartDist = GL11.glGetFloat(GL11.GL_FOG_START);
|
||||
float defaultFogEndDist = GL11.glGetFloat(GL11.GL_FOG_END);
|
||||
int defaultFogMode = GL11.glGetInteger(GL11.GL_FOG_MODE);
|
||||
int defaultFogDistance = GL11.glGetInteger(NVFogDistance.GL_FOG_DISTANCE_MODE_NV);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//===========//
|
||||
// rendering //
|
||||
//===========//
|
||||
profiler.popPush("LOD draw");
|
||||
|
||||
setupFog(fogSettings.near.distance, fogSettings.near.quality);
|
||||
sendLodsToGpuAndDraw(nearVbo, modelViewMatrix);
|
||||
|
||||
setupFog(fogSettings.far.distance, fogSettings.far.quality);
|
||||
sendLodsToGpuAndDraw(farVbo, modelViewMatrix);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// cleanup //
|
||||
//=========//
|
||||
|
||||
profiler.popPush("LOD cleanup");
|
||||
|
||||
GL11.glPolygonMode(GL11.GL_FRONT_AND_BACK, GL11.GL_FILL);
|
||||
GL11.glEnable(GL11.GL_TEXTURE_2D);
|
||||
GL11.glDisable(LOD_GL_LIGHT_NUMBER);
|
||||
// re-enable the lights Minecraft uses
|
||||
GL11.glEnable(GL11.GL_LIGHT0);
|
||||
GL11.glEnable(GL11.GL_LIGHT1);
|
||||
RenderSystem.disableLighting();
|
||||
|
||||
// this can't be called until after the buffers are built
|
||||
// because otherwise the buffers may be set to the wrong size
|
||||
previousChunkRenderDistance = mc.options.renderDistance;
|
||||
|
||||
// reset the fog settings so the normal chunks
|
||||
// will be drawn correctly
|
||||
cleanupFog(fogSettings, defaultFogStartDist, defaultFogEndDist, defaultFogMode, defaultFogDistance);
|
||||
|
||||
// reset the projection matrix so anything drawn after
|
||||
// the LODs will use the correct projection matrix
|
||||
Matrix4f mvm = new Matrix4f(defaultProjMatrix);
|
||||
mvm.transpose();
|
||||
gameRender.resetProjectionMatrix(mvm);
|
||||
|
||||
// clear the depth buffer so anything drawn is drawn
|
||||
// over the LODs
|
||||
GL11.glClear(GL11.GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
|
||||
// end of internal LOD profiling
|
||||
profiler.pop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This is where the actual drawing happens.
|
||||
*
|
||||
* @param buffers the buffers sent to the GPU to draw
|
||||
*/
|
||||
private void sendLodsToGpuAndDraw(VertexBuffer vbo, Matrix4f modelViewMatrix)
|
||||
{
|
||||
if (vbo == null)
|
||||
return;
|
||||
|
||||
vbo.bind();
|
||||
// 0L is the starting pointer
|
||||
LOD_VERTEX_FORMAT.setupBufferState(0L);
|
||||
|
||||
vbo.draw(modelViewMatrix, GL11.GL_QUADS);
|
||||
|
||||
VertexBuffer.unbind();
|
||||
LOD_VERTEX_FORMAT.clearBufferState();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//=================//
|
||||
// Setup Functions //
|
||||
//=================//
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setupFog(FogDistance fogDistance, FogQuality fogQuality)
|
||||
{
|
||||
if(fogQuality == FogQuality.OFF)
|
||||
{
|
||||
FogRenderer.setupNoFog();
|
||||
RenderSystem.disableFog();
|
||||
return;
|
||||
}
|
||||
|
||||
if(fogDistance == FogDistance.NEAR_AND_FAR)
|
||||
{
|
||||
throw new IllegalArgumentException("setupFog doesn't accept the NEAR_AND_FAR fog distance.");
|
||||
}
|
||||
|
||||
|
||||
// determine the fog distance mode to use
|
||||
int glFogDistanceMode;
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
// fancy fog (fragment distance based fog)
|
||||
glFogDistanceMode = NVFogDistance.GL_EYE_RADIAL_NV;
|
||||
}
|
||||
else
|
||||
{
|
||||
// fast fog (frustum distance based fog)
|
||||
glFogDistanceMode = NVFogDistance.GL_EYE_PLANE_ABSOLUTE_NV;
|
||||
}
|
||||
|
||||
|
||||
// the multipliers are percentages
|
||||
// of the regular view distance.
|
||||
if(fogDistance == FogDistance.NEAR)
|
||||
{
|
||||
// the reason that I wrote fogEnd then fogStart backwards
|
||||
// is because we are using fog backwards to how
|
||||
// it is normally used, with it hiding near objects
|
||||
// instead of far objects.
|
||||
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.75f);
|
||||
RenderSystem.fogStart(farPlaneDistance * 1.95f);
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
// for the far fog of the normal chunks
|
||||
// to start right where the LODs' end use:
|
||||
// end = 0.8f, start = 1.5f
|
||||
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.5f);
|
||||
RenderSystem.fogStart(farPlaneDistance * 2.0f);
|
||||
}
|
||||
}
|
||||
else if(fogDistance == FogDistance.FAR)
|
||||
{
|
||||
if (fogQuality == FogQuality.FANCY)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.85f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
RenderSystem.fogEnd(farPlaneDistance * 1.0f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
}
|
||||
else if(fogQuality == FogQuality.FAST)
|
||||
{
|
||||
RenderSystem.fogStart(farPlaneDistance * 0.5f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
RenderSystem.fogEnd(farPlaneDistance * 0.75f * LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GL11.glEnable(GL11.GL_FOG);
|
||||
RenderSystem.enableFog();
|
||||
RenderSystem.setupNvFogDistance();
|
||||
RenderSystem.fogMode(GlStateManager.FogMode.LINEAR);
|
||||
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, glFogDistanceMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Revert any changes that were made to the fog.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void cleanupFog(NearFarFogSettings fogSettings,
|
||||
float defaultFogStartDist, float defaultFogEndDist,
|
||||
int defaultFogMode, int defaultFogDistance)
|
||||
{
|
||||
RenderSystem.fogStart(defaultFogStartDist);
|
||||
RenderSystem.fogEnd(defaultFogEndDist);
|
||||
RenderSystem.fogMode(defaultFogMode);
|
||||
GL11.glFogi(NVFogDistance.GL_FOG_DISTANCE_MODE_NV, defaultFogDistance);
|
||||
|
||||
// disable fog if Minecraft wasn't rendering fog
|
||||
// but we were
|
||||
if(!fogSettings.vanillaIsRenderingFog &&
|
||||
(fogSettings.near.quality != FogQuality.OFF ||
|
||||
fogSettings.far.quality != FogQuality.OFF))
|
||||
{
|
||||
GL11.glDisable(GL11.GL_FOG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create the model view matrix to move the LODs
|
||||
* from object space into world space.
|
||||
*/
|
||||
private Matrix4f generateModelViewMatrix(float partialTicks)
|
||||
{
|
||||
// get all relevant camera info
|
||||
ActiveRenderInfo renderInfo = mc.gameRenderer.getMainCamera();
|
||||
Vector3d projectedView = renderInfo.getPosition();
|
||||
|
||||
|
||||
// generate the model view matrix
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.pushPose();
|
||||
// translate and rotate to the current camera location
|
||||
matrixStack.mulPose(Vector3f.XP.rotationDegrees(renderInfo.getXRot()));
|
||||
matrixStack.mulPose(Vector3f.YP.rotationDegrees(renderInfo.getYRot() + 180));
|
||||
matrixStack.translate(-projectedView.x, -projectedView.y, -projectedView.z);
|
||||
|
||||
return matrixStack.last().pose();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* create a new projection matrix and send it over to the GPU
|
||||
* <br><br>
|
||||
* A lot of this code is copied from renderLevel (line 567)
|
||||
* in the GameRender class. The code copied is anything with
|
||||
* a matrixStack and is responsible for making sure the LOD
|
||||
* objects distort correctly relative to the rest of the world.
|
||||
* Distortions are caused by: standing in a nether portal,
|
||||
* nausea potion effect, walking bobbing.
|
||||
*
|
||||
* @param partialTicks how many ticks into the frame we are
|
||||
*/
|
||||
private void setupProjectionMatrix(float partialTicks)
|
||||
{
|
||||
// Note: if the LOD objects don't distort correctly
|
||||
// compared to regular minecraft terrain, make sure
|
||||
// all the transformations in renderWorld are here too
|
||||
|
||||
MatrixStack matrixStack = new MatrixStack();
|
||||
matrixStack.pushPose();
|
||||
|
||||
gameRender.bobHurt(matrixStack, partialTicks);
|
||||
if (this.mc.options.bobView) {
|
||||
gameRender.bobView(matrixStack, partialTicks);
|
||||
}
|
||||
|
||||
// potion and nausea effects
|
||||
float f = MathHelper.lerp(partialTicks, this.mc.player.oPortalTime, this.mc.player.portalTime) * this.mc.options.screenEffectScale * this.mc.options.screenEffectScale;
|
||||
if (f > 0.0F) {
|
||||
int i = this.mc.player.hasEffect(Effects.CONFUSION) ? 7 : 20;
|
||||
float f1 = 5.0F / (f * f + 5.0F) - f * 0.04F;
|
||||
f1 = f1 * f1;
|
||||
Vector3f vector3f = new Vector3f(0.0F, MathHelper.SQRT_OF_TWO / 2.0F, MathHelper.SQRT_OF_TWO / 2.0F);
|
||||
matrixStack.mulPose(vector3f.rotationDegrees((gameRender.tick + partialTicks) * i));
|
||||
matrixStack.scale(1.0F / f1, 1.0F, 1.0F);
|
||||
float f2 = -(gameRender.tick + partialTicks) * i;
|
||||
matrixStack.mulPose(vector3f.rotationDegrees(f2));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// this projection matrix allows us to see past the normal
|
||||
// world render distance
|
||||
Matrix4f projectionMatrix =
|
||||
Matrix4f.perspective(
|
||||
getFov(partialTicks, true),
|
||||
(float)this.mc.getWindow().getScreenWidth() / (float)this.mc.getWindow().getScreenHeight(),
|
||||
// it is possible to see the near clip plane, but
|
||||
// you have to be flying quickly in spectator mode through ungenerated
|
||||
// terrain, so I don't think it is much of an issue.
|
||||
LodConfig.CLIENT.lodChunkRadiusMultiplier.get(),
|
||||
this.farPlaneDistance * LodConfig.CLIENT.lodChunkRadiusMultiplier.get() * 2);
|
||||
|
||||
// add the screen space distortions
|
||||
projectionMatrix.multiply(matrixStack.last().pose());
|
||||
gameRender.resetProjectionMatrix(projectionMatrix);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* setup the lighting to be used for the LODs
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
private void setupLighting(LodQuadTreeDimension lodDimension, float partialTicks)
|
||||
{
|
||||
float sunBrightness = lodDimension.dimension.hasSkyLight() ? mc.level.getSkyDarken(partialTicks) : 0.2f;
|
||||
float gammaMultiplyer = (float)mc.options.gamma - 0.5f;
|
||||
float lightStrength = ((sunBrightness / 2f) - 0.2f) + (gammaMultiplyer * 0.3f);
|
||||
|
||||
float lightAmbient[] = {lightStrength, lightStrength, lightStrength, 1.0f};
|
||||
|
||||
// can be used for debugging
|
||||
// if (partialTicks < 0.005)
|
||||
// ClientProxy.LOGGER.debug(lightStrength);
|
||||
|
||||
ByteBuffer temp = ByteBuffer.allocateDirect(16);
|
||||
temp.order(ByteOrder.nativeOrder());
|
||||
GL11.glLightfv(LOD_GL_LIGHT_NUMBER, GL11.GL_AMBIENT, (FloatBuffer) temp.asFloatBuffer().put(lightAmbient).flip());
|
||||
GL11.glEnable(LOD_GL_LIGHT_NUMBER); // Enable the above lighting
|
||||
|
||||
RenderSystem.enableLighting();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all buffers that will be used.
|
||||
*/
|
||||
private void setupBuffers(int numbChunksWide)
|
||||
{
|
||||
// calculate the max amount of memory needed (in bytes)
|
||||
int bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(LodConfig.CLIENT.lodChunkRadiusMultiplier.get());
|
||||
|
||||
// if the required memory is greater than the
|
||||
// MAX_ALOCATEABLE_DIRECT_MEMORY lower the lodChunkRadiusMultiplier
|
||||
// to fit.
|
||||
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
|
||||
{
|
||||
int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodQuadTreeNode.CHUNK_LEVEL);
|
||||
|
||||
ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high "
|
||||
+ "and had to be lowered to fit memory constraints "
|
||||
+ "from " + LodConfig.CLIENT.lodChunkRadiusMultiplier.get() + " "
|
||||
+ "to " + maxRadiusMultiplier);
|
||||
|
||||
LodConfig.CLIENT.lodChunkRadiusMultiplier.set(
|
||||
maxRadiusMultiplier);
|
||||
|
||||
bufferMemory = RenderUtil.getBufferMemoryForRadiusMultiplier(maxRadiusMultiplier);
|
||||
}
|
||||
|
||||
drawableNearBuffer = new BufferBuilder(bufferMemory);
|
||||
drawableFarBuffer = new BufferBuilder(bufferMemory);
|
||||
|
||||
lodNodeBufferBuilder.setupBuffers(bufferMemory);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//======================//
|
||||
// Other Misc Functions //
|
||||
//======================//
|
||||
|
||||
/**
|
||||
* If this is called then the next time "drawLODs" is called
|
||||
* the LODs will be regenerated; the same as if the player moved.
|
||||
*/
|
||||
public void regenerateLODsNextFrame()
|
||||
{
|
||||
regen = true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace the current drawable buffers with the newly
|
||||
* created buffers from the lodBufferBuilder.
|
||||
*/
|
||||
private void swapBuffers()
|
||||
{
|
||||
// replace the drawable buffers with
|
||||
// the newly created buffers from the lodBufferBuilder
|
||||
NearFarBuffer newBuffers = lodNodeBufferBuilder.swapBuffers(drawableNearBuffer, drawableFarBuffer);
|
||||
drawableNearBuffer = newBuffers.nearBuffer;
|
||||
drawableFarBuffer = newBuffers.farBuffer;
|
||||
|
||||
|
||||
// bind the buffers with their respective VBOs
|
||||
if (nearVbo != null)
|
||||
nearVbo.close();
|
||||
|
||||
nearVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
|
||||
nearVbo.upload(drawableNearBuffer);
|
||||
|
||||
|
||||
if (farVbo != null)
|
||||
farVbo.close();
|
||||
|
||||
farVbo = new VertexBuffer(LOD_VERTEX_FORMAT);
|
||||
farVbo.upload(drawableFarBuffer);
|
||||
}
|
||||
|
||||
|
||||
private double getFov(float partialTicks, boolean useFovSetting)
|
||||
{
|
||||
return mc.gameRenderer.getFov(mc.gameRenderer.getMainCamera(), partialTicks, useFovSetting);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return what fog settings should be used when rendering.
|
||||
*/
|
||||
private NearFarFogSettings determineFogSettings()
|
||||
{
|
||||
NearFarFogSettings fogSettings = new NearFarFogSettings();
|
||||
|
||||
|
||||
FogQuality quality = reflectionHandler.getFogQuality();
|
||||
FogDrawOverride override = LodConfig.CLIENT.fogDrawOverride.get();
|
||||
|
||||
|
||||
if (quality == FogQuality.OFF)
|
||||
fogSettings.vanillaIsRenderingFog = false;
|
||||
else
|
||||
fogSettings.vanillaIsRenderingFog = true;
|
||||
|
||||
|
||||
// use any fog overrides the user may have set
|
||||
switch(override)
|
||||
{
|
||||
case ALWAYS_DRAW_FOG_FANCY:
|
||||
quality = FogQuality.FANCY;
|
||||
break;
|
||||
|
||||
case NEVER_DRAW_FOG:
|
||||
quality = FogQuality.OFF;
|
||||
break;
|
||||
|
||||
case ALWAYS_DRAW_FOG_FAST:
|
||||
quality = FogQuality.FAST;
|
||||
break;
|
||||
|
||||
case USE_OPTIFINE_FOG_SETTING:
|
||||
// don't override anything
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
// only use fancy fog if the user's GPU can deliver
|
||||
if (!fancyFogAvailable && quality == FogQuality.FANCY)
|
||||
{
|
||||
quality = FogQuality.FAST;
|
||||
}
|
||||
|
||||
|
||||
// how different distances are drawn depends on the quality set
|
||||
switch(quality)
|
||||
{
|
||||
case FANCY:
|
||||
fogSettings.near.quality = FogQuality.FANCY;
|
||||
fogSettings.far.quality = FogQuality.FANCY;
|
||||
|
||||
switch(LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSettings.near.distance = FogDistance.FAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case FAST:
|
||||
fogSettings.near.quality = FogQuality.FAST;
|
||||
fogSettings.far.quality = FogQuality.FAST;
|
||||
|
||||
// fast fog setting should only have one type of
|
||||
// fog, since the LODs are separated into a near
|
||||
// and far portion; and fast fog is rendered from the
|
||||
// frustrum's perspective instead of the camera
|
||||
switch(LodConfig.CLIENT.fogDistance.get())
|
||||
{
|
||||
case NEAR_AND_FAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case NEAR:
|
||||
fogSettings.near.distance = FogDistance.NEAR;
|
||||
fogSettings.far.distance = FogDistance.NEAR;
|
||||
break;
|
||||
|
||||
case FAR:
|
||||
fogSettings.near.distance = FogDistance.FAR;
|
||||
fogSettings.far.distance = FogDistance.FAR;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OFF:
|
||||
|
||||
fogSettings.near.quality = FogQuality.OFF;
|
||||
fogSettings.far.quality = FogQuality.OFF;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
|
||||
return fogSettings;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get a HashSet of all ChunkPos within the normal render distance
|
||||
* that should not be rendered.
|
||||
*/
|
||||
private HashSet<ChunkPos> getNearbyLodChunkPosToSkip(LodQuadTreeDimension lodDim, BlockPos playerPos)
|
||||
{
|
||||
int chunkRenderDist = mc.options.renderDistance;
|
||||
int blockRenderDist = chunkRenderDist * 16;
|
||||
ChunkPos centerChunk = new ChunkPos(playerPos);
|
||||
|
||||
// skip chunks that are already going to be rendered by Minecraft
|
||||
HashSet<ChunkPos> posToSkip = getRenderedChunks();
|
||||
|
||||
|
||||
// go through each chunk within the normal view distance
|
||||
for(int x = centerChunk.x - chunkRenderDist; x < centerChunk.x + chunkRenderDist; x++)
|
||||
{
|
||||
for(int z = centerChunk.z - chunkRenderDist; z < centerChunk.z + chunkRenderDist; z++)
|
||||
{
|
||||
LodQuadTreeNode lod = lodDim.getLodFromCoordinates(x, z, 4);
|
||||
if (lod != null)
|
||||
{
|
||||
short lodHighestPoint = lod.lodDataPoint.height;
|
||||
|
||||
if (playerPos.getY() < lodHighestPoint)
|
||||
{
|
||||
// don't draw Lod's that are taller than the player
|
||||
// to prevent LODs being drawn on top of the player
|
||||
posToSkip.add(new ChunkPos(x, z));
|
||||
}
|
||||
else if (blockRenderDist < Math.abs(playerPos.getY() - lodHighestPoint))
|
||||
{
|
||||
// draw Lod's that are lower than the player's view range
|
||||
posToSkip.remove(new ChunkPos(x, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return posToSkip;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method returns the ChunkPos of all chunks that Minecraft
|
||||
* is going to render this frame. <br><br>
|
||||
*
|
||||
* Note: This isn't perfect. It will return some chunks that are outside
|
||||
* the clipping plane. (For example, if you are high above the ground some chunks
|
||||
* will be incorrectly added, even though they are outside render range).
|
||||
*/
|
||||
public static HashSet<ChunkPos> getRenderedChunks()
|
||||
{
|
||||
HashSet<ChunkPos> loadedPos = new HashSet<>();
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
|
||||
// Wow those are some long names!
|
||||
|
||||
// go through every RenderInfo to get the compiled chunks
|
||||
for(WorldRenderer.LocalRenderInformationContainer worldrenderer$localrenderinformationcontainer : mc.levelRenderer.renderChunks)
|
||||
{
|
||||
if (!worldrenderer$localrenderinformationcontainer.chunk.getCompiledChunk().hasNoRenderableLayers())
|
||||
{
|
||||
// add the ChunkPos for every empty compiled chunk
|
||||
BlockPos bpos = worldrenderer$localrenderinformationcontainer.chunk.getOrigin();
|
||||
|
||||
loadedPos.add(new ChunkPos(bpos.getX() / 16, bpos.getZ() / 16));
|
||||
}
|
||||
}
|
||||
|
||||
return loadedPos;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.handlers.ReflectionHandler;
|
||||
import com.seibel.lod.objects.LodChunk;
|
||||
import com.seibel.lod.objects.LodDimension;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
import com.seibel.lod.objects.NearFarBuffer;
|
||||
import com.seibel.lod.objects.NearFarFogSettings;
|
||||
import com.seibel.lod.proxy.ClientProxy;
|
||||
@@ -622,7 +623,7 @@ public class LodRenderer
|
||||
// to fit.
|
||||
if (bufferMemory > MAX_ALOCATEABLE_DIRECT_MEMORY)
|
||||
{
|
||||
int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodConfig.CLIENT.lodDetail.get());
|
||||
int maxRadiusMultiplier = RenderUtil.getMaxRadiusMultiplierWithAvaliableMemory(LodConfig.CLIENT.lodTemplate.get(), LodQuadTreeNode.CHUNK_LEVEL);
|
||||
|
||||
ClientProxy.LOGGER.warn("The lodChunkRadiusMultiplier was set too high "
|
||||
+ "and had to be lowered to fit memory constraints "
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
*/
|
||||
package com.seibel.lod.render;
|
||||
|
||||
import com.seibel.lod.enums.LodDetail;
|
||||
import com.seibel.lod.enums.LodTemplate;
|
||||
import com.seibel.lod.handlers.LodConfig;
|
||||
import com.seibel.lod.objects.LodQuadTreeNode;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.util.math.ChunkPos;
|
||||
@@ -94,16 +94,16 @@ public class RenderUtil
|
||||
// calculate the max amount of buffer memory needed (in bytes)
|
||||
return numbChunksWide * numbChunksWide *
|
||||
LodConfig.CLIENT.lodTemplate.get().
|
||||
getBufferMemoryForSingleLod(LodConfig.CLIENT.lodDetail.get());
|
||||
getBufferMemoryForSingleLod(LodQuadTreeNode.CHUNK_LEVEL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maxViewDistanceMultiplier for the given LodTemplate
|
||||
* at the given LodDetail level.
|
||||
*/
|
||||
public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, LodDetail lodDetail)
|
||||
public static int getMaxRadiusMultiplierWithAvaliableMemory(LodTemplate lodTemplate, int detailLevel)
|
||||
{
|
||||
int maxNumberOfLods = LodRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(lodDetail);
|
||||
int maxNumberOfLods = LodNodeRenderer.MAX_ALOCATEABLE_DIRECT_MEMORY / lodTemplate.getBufferMemoryForSingleLod(detailLevel);
|
||||
int numbLodsWide = (int) Math.sqrt(maxNumberOfLods);
|
||||
|
||||
return numbLodsWide / (2 * mc.options.renderDistance);
|
||||
|
||||
@@ -0,0 +1,719 @@
|
||||
/*
|
||||
* 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.util;
|
||||
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.world.biome.*;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
public class BiomeColorsUtils {
|
||||
//public static OverworldBiomeSource overworldBiomeSource = new OverworldBiomeSource(MCVersion.v1_16_4, 64971835648254);
|
||||
|
||||
public static Color getColorFromBiome(Biome biome,double x, double y){
|
||||
int color = 0;
|
||||
switch(biome.getBiomeCategory()) {
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
color = Blocks.SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case EXTREME_HILLS:
|
||||
color = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
case NONE:
|
||||
break;
|
||||
case FOREST:
|
||||
case JUNGLE:
|
||||
case TAIGA:
|
||||
color = biome.getFoliageColor();
|
||||
break;
|
||||
case MUSHROOM:
|
||||
color = Blocks.MYCELIUM.defaultMaterialColor().col;
|
||||
break;
|
||||
case PLAINS:
|
||||
case SAVANNA:
|
||||
color = biome.getGrassColor(x,y);
|
||||
break;
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
case SWAMP:
|
||||
color = biome.getWaterColor();
|
||||
break;
|
||||
case ICY:
|
||||
color = Blocks.PACKED_ICE.defaultMaterialColor().col;
|
||||
break;
|
||||
case THEEND:
|
||||
color = Blocks.END_STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
case NETHER:
|
||||
color = Blocks.NETHERRACK.defaultMaterialColor().col;
|
||||
break;
|
||||
case MESA:
|
||||
color = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
default:
|
||||
color = 0;
|
||||
}
|
||||
return new Color(color);
|
||||
}
|
||||
|
||||
public static Color getColorFromBiome(Biome biome){
|
||||
int color = 0;
|
||||
switch(biome.getBiomeCategory()) {
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
color = Blocks.SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case EXTREME_HILLS:
|
||||
color = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
case FOREST:
|
||||
case SAVANNA:
|
||||
case JUNGLE:
|
||||
case TAIGA:
|
||||
color = biome.getFoliageColor();
|
||||
break;
|
||||
case MUSHROOM:
|
||||
color = Blocks.MYCELIUM.defaultMaterialColor().col;
|
||||
break;
|
||||
case PLAINS:
|
||||
color = Blocks.GRASS_BLOCK.defaultMaterialColor().col;
|
||||
break;
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
case SWAMP:
|
||||
color = biome.getWaterColor();
|
||||
break;
|
||||
case ICY:
|
||||
color = Blocks.PACKED_ICE.defaultMaterialColor().col;
|
||||
break;
|
||||
case THEEND:
|
||||
color = Blocks.END_STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
case NETHER:
|
||||
color = Blocks.NETHERRACK.defaultMaterialColor().col;
|
||||
break;
|
||||
case MESA:
|
||||
color = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
color = 0;
|
||||
}
|
||||
return new Color(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* methods that gives the ChunkBase color of biomes
|
||||
* @param biome to check
|
||||
* @return color of the biome
|
||||
*/
|
||||
public static Color getColorFromIdRealistic(kaptainwutax.biomeutils.biome.Biome biome){
|
||||
Biome.Builder builder = new Biome.Builder();
|
||||
int color = 0;
|
||||
switch(biome.getCategory()) {
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
color = Blocks.SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case EXTREME_HILLS:
|
||||
color = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
case FOREST:
|
||||
builder.biomeCategory(Biome.Category.FOREST);
|
||||
color = builder.build().getFoliageColor();
|
||||
break;
|
||||
case SAVANNA:
|
||||
builder.biomeCategory(Biome.Category.SAVANNA);
|
||||
color = builder.build().getFoliageColor();
|
||||
break;
|
||||
case JUNGLE:
|
||||
builder.biomeCategory(Biome.Category.JUNGLE);
|
||||
color = builder.build().getFoliageColor();
|
||||
break;
|
||||
case TAIGA:
|
||||
builder.biomeCategory(Biome.Category.TAIGA);
|
||||
color = builder.build().getFoliageColor();
|
||||
break;
|
||||
case MUSHROOM:
|
||||
color = Blocks.MYCELIUM.defaultMaterialColor().col;
|
||||
break;
|
||||
case PLAINS:
|
||||
color = Blocks.GRASS_BLOCK.defaultMaterialColor().col;
|
||||
break;
|
||||
case OCEAN:
|
||||
builder.biomeCategory(Biome.Category.OCEAN);
|
||||
color = builder.build().getWaterColor();
|
||||
break;
|
||||
case RIVER:
|
||||
builder.biomeCategory(Biome.Category.RIVER);
|
||||
color = builder.build().getWaterColor();
|
||||
break;
|
||||
case SWAMP:
|
||||
builder.biomeCategory(Biome.Category.SWAMP);
|
||||
color = builder.build().getWaterColor();
|
||||
break;
|
||||
case ICY:
|
||||
color = Blocks.PACKED_ICE.defaultMaterialColor().col;
|
||||
break;
|
||||
case THE_END:
|
||||
color = Blocks.END_STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
case NETHER:
|
||||
color = Blocks.NETHERRACK.defaultMaterialColor().col;
|
||||
break;
|
||||
case BADLANDS_PLATEAU:
|
||||
case MESA:
|
||||
color = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
color = 0;
|
||||
}
|
||||
return new Color(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* methods that gives the ChunkBase color of biomes
|
||||
* @param biome to check
|
||||
* @return color of the biome
|
||||
*/
|
||||
public static Color getColorFromBiomeBlock(kaptainwutax.biomeutils.biome.Biome biome){
|
||||
int color = 0;
|
||||
switch(biome.getCategory()) {
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
color = Blocks.SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case EXTREME_HILLS:
|
||||
color = Blocks.SNOW.defaultMaterialColor().col;
|
||||
break;
|
||||
case FOREST:
|
||||
color = Blocks.OAK_LEAVES.defaultMaterialColor().col;
|
||||
break;
|
||||
case SAVANNA:
|
||||
color = Blocks.ACACIA_LEAVES.defaultMaterialColor().col;
|
||||
break;
|
||||
case JUNGLE:
|
||||
color = Blocks.JUNGLE_LEAVES.defaultMaterialColor().col;
|
||||
break;
|
||||
case TAIGA:
|
||||
color = Blocks.SPRUCE_LEAVES.defaultMaterialColor().col;
|
||||
break;
|
||||
case MUSHROOM:
|
||||
color = Blocks.MYCELIUM.defaultMaterialColor().col;
|
||||
break;
|
||||
case PLAINS:
|
||||
color = Blocks.GRASS_BLOCK.defaultMaterialColor().col;
|
||||
break;
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
color = Blocks.WATER.defaultMaterialColor().col;
|
||||
case SWAMP:
|
||||
color = Blocks.LILY_PAD.defaultMaterialColor().col;
|
||||
break;
|
||||
case ICY:
|
||||
color = Blocks.PACKED_ICE.defaultMaterialColor().col;
|
||||
break;
|
||||
case THE_END:
|
||||
color = Blocks.END_STONE.defaultMaterialColor().col;
|
||||
break;
|
||||
case NETHER:
|
||||
color = Blocks.NETHERRACK.defaultMaterialColor().col;
|
||||
break;
|
||||
case BADLANDS_PLATEAU:
|
||||
case MESA:
|
||||
color = Blocks.RED_SAND.defaultMaterialColor().col;
|
||||
break;
|
||||
case NONE:
|
||||
default:
|
||||
color = 0;
|
||||
}
|
||||
return new Color(color);
|
||||
}
|
||||
|
||||
/**
|
||||
* methods that gives the ChunkBase color of biomes
|
||||
* @param biome to check
|
||||
* @return color of the biome
|
||||
*/
|
||||
public static Color getColorFromBiomeManual(kaptainwutax.biomeutils.biome.Biome biome){
|
||||
Color color;
|
||||
switch(biome.getCategory()) {
|
||||
case BEACH:
|
||||
case DESERT:
|
||||
color = new Color(220,214,170);
|
||||
break;
|
||||
case EXTREME_HILLS:
|
||||
color = new Color(81,129,60);
|
||||
break;
|
||||
case FOREST:
|
||||
color = new Color(81,129,60);
|
||||
break;
|
||||
case SAVANNA:
|
||||
color = new Color(119,113,53);
|
||||
break;
|
||||
case JUNGLE:
|
||||
color = new Color(41,141,4);
|
||||
break;
|
||||
case TAIGA:
|
||||
color = new Color(70,95,68);
|
||||
break;
|
||||
case MUSHROOM:
|
||||
color = new Color(123,105,109);
|
||||
break;
|
||||
case PLAINS:
|
||||
color = new Color(96,125,59);
|
||||
break;
|
||||
case OCEAN:
|
||||
case RIVER:
|
||||
color = new Color(54,73,229);
|
||||
break;
|
||||
case SWAMP:
|
||||
color = new Color(83,86,67);
|
||||
break;
|
||||
case ICY:
|
||||
color = new Color(199,217,254);
|
||||
break;
|
||||
case THE_END:
|
||||
color = new Color(100,100,0);
|
||||
break;
|
||||
case NETHER:
|
||||
color = new Color(100,0,0);
|
||||
break;
|
||||
case BADLANDS_PLATEAU:
|
||||
case MESA:
|
||||
color = new Color(188,103,39);
|
||||
break;
|
||||
case NONE:
|
||||
color = new Color(96,125,59);
|
||||
break;
|
||||
default:
|
||||
color = new Color(0,0,0,0);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
/**
|
||||
* methods that gives the ChunkBase color of biomes
|
||||
* @param biomeId id of the biome
|
||||
* @return color of the biome
|
||||
*/
|
||||
public static Color getColorFromIdCB(int biomeId){
|
||||
int red=0;
|
||||
int green=0;
|
||||
int blue=0;
|
||||
switch(biomeId) {
|
||||
case 0:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 112;
|
||||
break;
|
||||
case 1:
|
||||
red = 141;
|
||||
green = 179;
|
||||
blue = 96;
|
||||
break;
|
||||
case 2:
|
||||
red = 250;
|
||||
green = 148;
|
||||
blue = 24;
|
||||
break;
|
||||
case 3:
|
||||
red = 96;
|
||||
green = 96;
|
||||
blue = 96;
|
||||
break;
|
||||
case 4:
|
||||
red = 5;
|
||||
green = 102;
|
||||
blue = 33;
|
||||
break;
|
||||
case 5:
|
||||
red = 11;
|
||||
green = 2;
|
||||
blue = 89;
|
||||
break;
|
||||
case 6:
|
||||
red = 7;
|
||||
green = 249;
|
||||
blue = 178;
|
||||
break;
|
||||
case 7:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 255;
|
||||
break;
|
||||
case 8:
|
||||
red = 255;
|
||||
green = 0;
|
||||
blue = 0;
|
||||
break;
|
||||
case 9:
|
||||
red = 128;
|
||||
green = 128;
|
||||
blue = 255;
|
||||
break;
|
||||
case 10:
|
||||
red = 112;
|
||||
green = 112;
|
||||
blue = 214;
|
||||
break;
|
||||
case 11:
|
||||
red = 160;
|
||||
green = 160;
|
||||
blue = 255;
|
||||
break;
|
||||
case 12:
|
||||
red = 255;
|
||||
green = 255;
|
||||
blue = 255;
|
||||
break;
|
||||
case 13:
|
||||
red = 160;
|
||||
green = 160;
|
||||
blue = 160;
|
||||
break;
|
||||
case 14:
|
||||
red = 255;
|
||||
green = 0;
|
||||
blue = 255;
|
||||
break;
|
||||
case 15:
|
||||
red = 160;
|
||||
green = 0;
|
||||
blue = 255;
|
||||
break;
|
||||
case 16:
|
||||
red = 250;
|
||||
green = 222;
|
||||
blue = 85;
|
||||
break;
|
||||
case 17:
|
||||
red = 210;
|
||||
green = 95;
|
||||
blue = 18;
|
||||
break;
|
||||
case 18:
|
||||
red = 34;
|
||||
green = 85;
|
||||
blue = 28;
|
||||
break;
|
||||
case 19:
|
||||
red = 22;
|
||||
green = 57;
|
||||
blue = 51;
|
||||
break;
|
||||
case 20:
|
||||
red = 114;
|
||||
green = 120;
|
||||
blue = 154;
|
||||
break;
|
||||
case 21:
|
||||
red = 83;
|
||||
green = 123;
|
||||
blue = 9;
|
||||
break;
|
||||
case 22:
|
||||
red = 44;
|
||||
green = 66;
|
||||
blue = 5;
|
||||
break;
|
||||
case 23:
|
||||
red = 98;
|
||||
green = 139;
|
||||
blue = 23;
|
||||
break;
|
||||
case 24:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 48;
|
||||
break;
|
||||
case 25:
|
||||
red = 162;
|
||||
green = 162;
|
||||
blue = 132;
|
||||
break;
|
||||
case 26:
|
||||
red = 250;
|
||||
green = 240;
|
||||
blue = 192;
|
||||
break;
|
||||
case 27:
|
||||
red = 48;
|
||||
green = 116;
|
||||
blue = 68;
|
||||
break;
|
||||
case 28:
|
||||
red = 31;
|
||||
green = 5;
|
||||
blue = 50;
|
||||
break;
|
||||
case 29:
|
||||
red = 64;
|
||||
green = 81;
|
||||
blue = 26;
|
||||
break;
|
||||
case 30:
|
||||
red = 49;
|
||||
green = 85;
|
||||
blue = 74;
|
||||
break;
|
||||
case 31:
|
||||
red = 36;
|
||||
green = 63;
|
||||
blue = 54;
|
||||
break;
|
||||
case 32:
|
||||
red = 89;
|
||||
green = 102;
|
||||
blue = 81;
|
||||
break;
|
||||
case 33:
|
||||
red = 69;
|
||||
green = 7;
|
||||
blue = 62;
|
||||
break;
|
||||
case 34:
|
||||
red = 80;
|
||||
green = 112;
|
||||
blue = 80;
|
||||
break;
|
||||
case 35:
|
||||
red = 189;
|
||||
green = 18;
|
||||
blue = 95;
|
||||
break;
|
||||
case 36:
|
||||
red = 167;
|
||||
green = 157;
|
||||
blue = 100;
|
||||
break;
|
||||
case 37:
|
||||
red = 217;
|
||||
green = 69;
|
||||
blue = 21;
|
||||
break;
|
||||
case 38:
|
||||
red = 17;
|
||||
green = 151;
|
||||
blue = 101;
|
||||
break;
|
||||
case 39:
|
||||
red = 202;
|
||||
green = 140;
|
||||
blue = 101;
|
||||
break;
|
||||
case 40:
|
||||
red = 128;
|
||||
green = 128;
|
||||
blue = 255;
|
||||
break;
|
||||
case 41:
|
||||
red = 128;
|
||||
green = 128;
|
||||
blue = 255;
|
||||
break;
|
||||
case 42:
|
||||
red = 128;
|
||||
green = 128;
|
||||
blue = 255;
|
||||
break;
|
||||
case 43:
|
||||
red = 128;
|
||||
green = 128;
|
||||
blue = 255;
|
||||
break;
|
||||
case 44:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 172;
|
||||
break;
|
||||
case 45:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 144;
|
||||
break;
|
||||
case 46:
|
||||
red = 32;
|
||||
green = 32;
|
||||
blue = 112;
|
||||
break;
|
||||
case 47:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 80;
|
||||
break;
|
||||
case 48:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 64;
|
||||
break;
|
||||
case 49:
|
||||
red = 32;
|
||||
green = 32;
|
||||
blue = 56;
|
||||
break;
|
||||
case 50:
|
||||
red = 64;
|
||||
green = 64;
|
||||
blue = 144;
|
||||
break;
|
||||
case 127:
|
||||
red = 0;
|
||||
green = 0;
|
||||
blue = 0;
|
||||
break;
|
||||
case 129:
|
||||
red = 181;
|
||||
green = 219;
|
||||
blue = 136;
|
||||
break;
|
||||
case 130:
|
||||
red = 255;
|
||||
green = 188;
|
||||
blue = 64;
|
||||
break;
|
||||
case 131:
|
||||
red = 136;
|
||||
green = 136;
|
||||
blue = 136;
|
||||
break;
|
||||
case 132:
|
||||
red = 45;
|
||||
green = 142;
|
||||
blue = 73;
|
||||
break;
|
||||
case 133:
|
||||
red = 51;
|
||||
green = 142;
|
||||
blue = 19;
|
||||
break;
|
||||
case 134:
|
||||
red = 47;
|
||||
green = 255;
|
||||
blue = 18;
|
||||
break;
|
||||
case 140:
|
||||
red = 180;
|
||||
green = 20;
|
||||
blue = 220;
|
||||
break;
|
||||
case 149:
|
||||
red = 123;
|
||||
green = 13;
|
||||
blue = 49;
|
||||
break;
|
||||
case 151:
|
||||
red = 138;
|
||||
green = 179;
|
||||
blue = 63;
|
||||
break;
|
||||
case 155:
|
||||
red = 88;
|
||||
green = 156;
|
||||
blue = 108;
|
||||
break;
|
||||
case 156:
|
||||
red = 71;
|
||||
green = 15;
|
||||
blue = 90;
|
||||
break;
|
||||
case 157:
|
||||
red = 104;
|
||||
green = 121;
|
||||
blue = 66;
|
||||
break;
|
||||
case 158:
|
||||
red = 89;
|
||||
green = 125;
|
||||
blue = 114;
|
||||
break;
|
||||
case 160:
|
||||
red = 129;
|
||||
green = 142;
|
||||
blue = 121;
|
||||
break;
|
||||
case 161:
|
||||
red = 109;
|
||||
green = 119;
|
||||
blue = 102;
|
||||
break;
|
||||
case 162:
|
||||
red = 120;
|
||||
green = 52;
|
||||
blue = 120;
|
||||
break;
|
||||
case 163:
|
||||
red = 229;
|
||||
green = 218;
|
||||
blue = 135;
|
||||
break;
|
||||
case 164:
|
||||
red = 207;
|
||||
green = 197;
|
||||
blue = 140;
|
||||
break;
|
||||
case 165:
|
||||
red = 255;
|
||||
green = 109;
|
||||
blue = 61;
|
||||
break;
|
||||
case 166:
|
||||
red = 216;
|
||||
green = 191;
|
||||
blue = 141;
|
||||
break;
|
||||
case 167:
|
||||
red = 242;
|
||||
green = 180;
|
||||
blue = 141;
|
||||
break;
|
||||
case 168:
|
||||
red = 118;
|
||||
green = 142;
|
||||
blue = 20;
|
||||
break;
|
||||
case 169:
|
||||
red = 59;
|
||||
green = 71;
|
||||
blue = 10;
|
||||
break;
|
||||
case 170:
|
||||
red = 82;
|
||||
green = 41;
|
||||
blue = 33;
|
||||
break;
|
||||
case 171:
|
||||
red = 221;
|
||||
green = 8;
|
||||
blue = 8;
|
||||
break;
|
||||
case 172:
|
||||
red = 73;
|
||||
green = 144;
|
||||
blue = 123;
|
||||
break;
|
||||
default:
|
||||
red = 255;
|
||||
green = 0;
|
||||
blue = 0;
|
||||
}
|
||||
return new Color(red, green, blue);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@ package com.seibel.lod.util;
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
|
||||
import com.seibel.lod.objects.LodRegion;
|
||||
import com.seibel.lod.objects.RegionPos;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
@@ -96,8 +95,8 @@ public class LodUtil
|
||||
public static RegionPos convertChunkPosToRegionPos(ChunkPos pos)
|
||||
{
|
||||
RegionPos rPos = new RegionPos();
|
||||
rPos.x = pos.x / LodRegion.SIZE;
|
||||
rPos.z = pos.z / LodRegion.SIZE;
|
||||
rPos.x = pos.x / 512;
|
||||
rPos.z = pos.z / 512;
|
||||
|
||||
// prevent issues if X/Z is negative and less than 16
|
||||
if (pos.x < 0)
|
||||
@@ -109,6 +108,9 @@ public class LodUtil
|
||||
rPos.z = (Math.abs(rPos.z) * -1) - 1;
|
||||
}
|
||||
|
||||
//rPos.x = (Math.floorDiv(pos.x, (int) (512/Math.pow(LodQuadTreeNode.CHUNK_LEVEL,2))));
|
||||
//rPos.z = (Math.floorDiv(pos.z, (int) (512/Math.pow(LodQuadTreeNode.CHUNK_LEVEL,2))));
|
||||
|
||||
return rPos;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user