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:
@@ -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)
|
||||
*/
|
||||
}
|
||||
Reference in New Issue
Block a user