Fixing stuff towards successful builds. Now stuck on thinking how the world gen stuff should return and apply the gen chunk to data though...

This commit is contained in:
TomTheFurry
2022-07-21 17:40:27 +08:00
parent 109f2ea7a4
commit 5b1568e9a6
20 changed files with 121 additions and 1009 deletions
@@ -1,6 +1,8 @@
package com.seibel.lod.core.a7.datatype.column;
import com.seibel.lod.core.a7.datatype.LodDataSource;
import com.seibel.lod.core.a7.datatype.full.FullDataSource;
import com.seibel.lod.core.a7.datatype.transform.FullToColumnTransformer;
import com.seibel.lod.core.a7.level.IClientLevel;
import com.seibel.lod.core.a7.datatype.LodRenderSource;
import com.seibel.lod.core.a7.datatype.RenderSourceLoader;
@@ -27,7 +29,9 @@ public class ColumnRenderLoader extends RenderSourceLoader {
@Override
public LodRenderSource createRender(LodDataSource dataSource, IClientLevel level) {
//TODO
if (dataSource instanceof FullDataSource) {
return FullToColumnTransformer.transformFullDataToColumnData(level, (FullDataSource) dataSource);
}
return null;
}
@@ -3,11 +3,12 @@ package com.seibel.lod.core.a7.datatype.column.render;
import com.seibel.lod.core.a7.datatype.column.ColumnRenderSource;
import com.seibel.lod.core.a7.datatype.column.accessor.ColumnArrayView;
import com.seibel.lod.core.a7.level.IClientLevel;
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
import com.seibel.lod.core.a7.render.a7LodRenderer;
import com.seibel.lod.core.a7.util.UncheckedInterruptedException;
import com.seibel.lod.core.a7.render.RenderBuffer;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.CubicLodTemplate;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodQuadBuilder;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.enums.config.EGpuUploadMethod;
@@ -15,6 +16,7 @@ import com.seibel.lod.core.enums.rendering.EDebugMode;
import com.seibel.lod.core.enums.rendering.EGLProxyContext;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.DHBlockPos;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
@@ -45,8 +47,10 @@ public class ColumnRenderBuffer extends RenderBuffer {
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
GLVertexBuffer[] vbos;
public final DHBlockPos pos;
public ColumnRenderBuffer() {
public ColumnRenderBuffer(DHBlockPos pos) {
this.pos = pos;
vbos = new GLVertexBuffer[0];
}
@@ -67,7 +71,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
int size = bb.limit() - bb.position();
try {
vbo.bind();
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, LodBufferBuilderFactory.FULL_SIZED_BUFFER);
vbo.uploadBuffer(bb, size/LodUtil.LOD_VERTEX_FORMAT.getByteSize(), method, FULL_SIZED_BUFFER);
} catch (Exception e) {
vbos[i-1] = null;
vbo.close();
@@ -136,22 +140,14 @@ public class ColumnRenderBuffer extends RenderBuffer {
}
@Override
public boolean render(LodRenderProgram shaderProgram) {
public boolean render(a7LodRenderer renderContext) {
boolean hasRendered = false;
renderContext.setupOffset(pos);
for (GLVertexBuffer vbo : vbos) {
if (vbo == null) continue;
if (vbo.getVertexCount() == 0) continue;
hasRendered = true;
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
//FIXME: Fix this! Need to pass this down
if (/*LodRenderer.ENABLE_IBO*/ true) {
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6,
GL32.GL_INT //FIXME: Fix this! Need to pass this as argument down the chains!
, 0);
} else {
GL32.glDrawArrays(GL32.GL_TRIANGLES, 0, vbo.getVertexCount());
}
renderContext.drawVbo(vbo);
//LodRenderer.tickLogger.info("Vertex buffer: {}", vbo);
}
return hasRendered;
@@ -164,7 +160,7 @@ public class ColumnRenderBuffer extends RenderBuffer {
for (GLVertexBuffer b : vbos) {
if (b == null) continue;
statsMap.incStat("VBOs");
if (b.getSize() == LodBufferBuilderFactory.FULL_SIZED_BUFFER) {
if (b.getSize() == FULL_SIZED_BUFFER) {
statsMap.incStat("FullsizedVBOs");
}
if (b.getSize() == 0) GL_LOGGER.warn("VBO with size 0");
@@ -211,7 +207,10 @@ public class ColumnRenderBuffer extends RenderBuffer {
EGpuUploadMethod method = GLProxy.getInstance().getGpuUploadMethod();
EGLProxyContext oldContext = glProxy.getGlContext();
glProxy.setGlContext(EGLProxyContext.LOD_BUILDER);
ColumnRenderBuffer buffer = usedBuffer!=null ? usedBuffer : new ColumnRenderBuffer();
ColumnRenderBuffer buffer = usedBuffer!=null ? usedBuffer :
new ColumnRenderBuffer(
new DHBlockPos(data.sectionPos.getCorner().getCorner(), clientLevel.getMinY())
);
try {
buffer.uploadBuffer(builder, method);
EVENT_LOGGER.trace("RenderRegion end Upload @ {}", data.sectionPos);
@@ -5,7 +5,7 @@ import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
import java.io.IOException;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
@@ -77,5 +77,27 @@ public class IdBiomeBlockStateMap {
return mapper;
}
//TODO: Serialization & Deserialization
void serialize(OutputStream os) {
try (DataOutputStream dos = new DataOutputStream(os)) {
dos.writeInt(entries.size());
for (Entry e : entries) {
dos.writeUTF(e.serialize());
}
} catch (IOException e) {
e.printStackTrace();
}
}
static IdBiomeBlockStateMap deserialize(InputStream is) {
try (DataInputStream dis = new DataInputStream(is)) {
int size = dis.readInt();
IdBiomeBlockStateMap map = new IdBiomeBlockStateMap();
for (int i = 0; i < size; i++) {
map.entries.add(Entry.deserialize(dis.readUTF()));
}
return map;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
@@ -11,7 +11,7 @@ import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.a7LodRenderer;
import com.seibel.lod.core.a7.render.a7LodRenderer;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -11,7 +11,7 @@ import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.render.a7LodRenderer;
import com.seibel.lod.core.a7.render.a7LodRenderer;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IProfilerWrapper;
@@ -1,9 +1,13 @@
package com.seibel.lod.core.a7.level;
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
import com.seibel.lod.core.a7.pos.DhSectionPos;
import com.seibel.lod.core.a7.util.FileScanner;
import com.seibel.lod.core.a7.save.io.file.LocalDataFileHandler;
import com.seibel.lod.core.a7.save.structure.LocalSaveStructure;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.objects.DHChunkPos;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.Logger;
@@ -52,6 +56,13 @@ public class DhServerLevel implements IServerLevel {
public void doWorldGen() {
}
@Override
public void submitChunkData(DHChunkPos chunkPos, ChunkSizedData data) {
DhSectionPos sectionPos = new DhSectionPos((byte)4, chunkPos.x, chunkPos.z);
dataFileHandler.write(sectionPos, data);
}
@Override
public ILevelWrapper getLevelWrapper() {
return level;
@@ -1,6 +1,9 @@
package com.seibel.lod.core.a7.level;
import com.seibel.lod.core.a7.datatype.full.ChunkSizedData;
public interface IServerLevel extends ILevel {
void serverTick();
void doWorldGen();
void submitChunkData(ChunkSizedData data);
}
@@ -20,6 +20,7 @@
package com.seibel.lod.core.a7.render;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.util.StatsMap;
public abstract class RenderBuffer implements AutoCloseable
@@ -31,7 +32,7 @@ public abstract class RenderBuffer implements AutoCloseable
// ========== Called by render thread ==========
/* Called on... well... rendering.
* Return false if nothing rendered. (Optional) */
public abstract boolean render(LodRenderProgram shaderProgram);
public abstract boolean render(a7LodRenderer renderContext);
// ========== Called by any thread. (thread safe) ==========
@@ -47,6 +48,14 @@ public abstract class RenderBuffer implements AutoCloseable
* thread), or by others when the object is not being used. (not in build,
* upload, or render state). */
public abstract void close();
public static final int DEFAULT_MEMORY_ALLOCATION = (LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 3) * 8;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
}
@@ -22,7 +22,7 @@ public class RenderBufferHandler {
this.pos = pos;
}
public void render(LodRenderProgram renderContext) {
public void render(a7LodRenderer renderContext) {
RenderBuffer buff = renderBufferSlot.get();
if (buff != null) {
buff.render(renderContext);
@@ -109,7 +109,7 @@ public class RenderBufferHandler {
renderBufferNodes = new MovableGridRingList<>(referenceList.getHalfSize(), center);
}
public void render(LodRenderProgram renderContext) {
public void render(a7LodRenderer renderContext) {
//TODO: This might get locked by update() causing move() call. Is there a way to avoid this?
// Maybe dupe the base list and use atomic swap on render? Or is this not worth it?
//TODO: Directional culling
@@ -17,11 +17,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.render;
package com.seibel.lod.core.a7.render;
import com.seibel.lod.core.a7.level.IClientLevel;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.builders.lodBuilding.bufferBuilding.LodBufferBuilderFactory;
import com.seibel.lod.core.config.types.ConfigEntry;
import com.seibel.lod.core.enums.rendering.EDebugMode;
import com.seibel.lod.core.enums.rendering.EFogColorMode;
@@ -29,11 +28,14 @@ import com.seibel.lod.core.handlers.dependencyInjection.SingletonInjector;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.logging.ConfigBasedSpamLogger;
import com.seibel.lod.core.objects.DHBlockPos;
import com.seibel.lod.core.a7.render.RenderBufferHandler;
import com.seibel.lod.core.objects.math.Mat4f;
import com.seibel.lod.core.objects.math.Vec3d;
import com.seibel.lod.core.objects.math.Vec3f;
import com.seibel.lod.core.render.GLProxy;
import com.seibel.lod.core.render.LodFogConfig;
import com.seibel.lod.core.render.LodRenderProgram;
import com.seibel.lod.core.render.objects.GLState;
import com.seibel.lod.core.render.objects.GLVertexBuffer;
import com.seibel.lod.core.render.objects.QuadElementBuffer;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -66,6 +68,18 @@ public class a7LodRenderer
public static final long DRAW_LAG_SPIKE_THRESHOLD_NS = TimeUnit.NANOSECONDS.convert(20, TimeUnit.MILLISECONDS);
public static final boolean ENABLE_IBO = true;
public void setupOffset(DHBlockPos pos) {
shaderProgram.setModelPos(new Vec3f(pos.x, pos.y, pos.z));
}
public void drawVbo(GLVertexBuffer vbo) {
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount()/4)*6,
quadIBO.getType(), 0);
}
public static class LagSpikeCatcher {
long timer = System.nanoTime();
public LagSpikeCatcher() {}
@@ -219,7 +233,7 @@ public class a7LodRenderer
int drawCount = 0;
//TODO: Directional culling
bufferHandler.render(shaderProgram);
bufferHandler.render(this);
//if (drawCall==0)
// tickLogger.info("DrawCall Count: {}", drawCount);
@@ -267,7 +281,7 @@ public class a7LodRenderer
shaderProgram = new LodRenderProgram(LodFogConfig.generateFogConfig());
if (ENABLE_IBO) {
quadIBO = new QuadElementBuffer();
quadIBO.reserve(LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
quadIBO.reserve(RenderBuffer.MAX_QUADS_PER_BUFFER);
}
EVENT_LOGGER.info("Renderer setup complete");
}
@@ -20,7 +20,6 @@
package com.seibel.lod.core.api.external.methods.config.client;
import com.seibel.lod.core.api.external.items.interfaces.config.IDhApiConfig;
import com.seibel.lod.core.api.external.methods.config.objects.enums.*;
import com.seibel.lod.core.api.external.items.enums.config.*;
import com.seibel.lod.core.api.implementation.objects.GenericEnumConverter;
import com.seibel.lod.core.api.implementation.objects.RenderModeEnabledConverter;
@@ -20,7 +20,6 @@
package com.seibel.lod.core.api.external.methods.config.client;
import com.seibel.lod.core.api.external.items.interfaces.config.IDhApiConfig;
import com.seibel.lod.core.api.external.methods.config.objects.enums.*;
import com.seibel.lod.core.api.external.items.enums.config.*;
import com.seibel.lod.core.api.implementation.objects.GenericEnumConverter;
import com.seibel.lod.core.api.implementation.wrappers.DhApiConfig;
@@ -1,569 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.builders.lodBuilding;
#if ABC
import java.util.ConcurrentModificationException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.seibel.lod.core.config.Config;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.enums.config.EBlocksToAvoid;
import com.seibel.lod.core.enums.config.EDistanceGenerationMode;
import com.seibel.lod.core.handlers.dependencyInjection.SingletonHandler;
import com.seibel.lod.core.logging.ConfigBasedLogger;
import com.seibel.lod.core.objects.DHBlockPos;
import com.seibel.lod.core.objects.lod.LodRegion;
import com.seibel.lod.core.objects.lod.LodWorld;
import com.seibel.lod.core.util.DataPointUtil;
import com.seibel.lod.core.util.DetailDistanceUtil;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodThreadFactory;
import com.seibel.lod.core.util.LodUtil;
import com.seibel.lod.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.lod.core.wrapperInterfaces.block.IBlockDetailWrapper;
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.lod.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.IDimensionTypeWrapper;
import com.seibel.lod.core.wrapperInterfaces.world.ILevelWrapper;
import org.apache.logging.log4j.LogManager;
/**
* This object is in charge of creating Lod related objects.
*
* @author Cola
* @author Leonardo Amato
* @author James Seibel
* @version 12-11-2021
*/
@SuppressWarnings("GrazieInspection")
public class LodBuilder
{
private static final IMinecraftClientWrapper MC = SingletonHandler.get(IMinecraftClientWrapper.class);
private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class);
public static final ConfigBasedLogger EVENT_LOGGER = new ConfigBasedLogger(LogManager.getLogger(LodBuilder.class),
() -> Config.Client.Advanced.Debugging.DebugSwitch.logLodBuilderEvent.get());
/** This cannot be final! Different world have different height, and in menu, this causes Null Exceptions*/
//public static final short MIN_WORLD_HEIGHT = MC.getWrappedClientWorld().getMinHeight();
public static short MIN_WORLD_HEIGHT = 0; // Currently modified in EventApi.onWorldLoaded(...)
/** Minecraft's max light value */
public static final short DEFAULT_MAX_LIGHT = 15;
//public static final ExecutorService lodGenThreadPool = Executors.newFixedThreadPool(8, new ThreadFactoryBuilder().setNameFormat("Lod-Builder-%d").build());
private final ExecutorService lodGenThreadPool = Executors.newSingleThreadExecutor(
new LodThreadFactory(this.getClass().getSimpleName(), Thread.NORM_PRIORITY-1));
/**
* How wide LodDimensions should be in regions <br>
* Is automatically set before the first frame in ClientProxy.
*/
public int defaultDimensionWidthInRegions = 1;
//public static final boolean useExperimentalLighting = true;
public LodBuilder()
{
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim, boolean genAll)
{
// Block change event
generateLodNodeAsync(chunk, lodWorld, dim, EDistanceGenerationMode.FULL, true, genAll, ()->{},
()->{generateLodNodeAsync(chunk,lodWorld,dim, genAll);});
}
public void generateLodNodeAsync(IChunkWrapper chunk, LodWorld lodWorld, IDimensionTypeWrapper dim,
EDistanceGenerationMode generationMode, boolean override, boolean genAll, Runnable endCallback, Runnable retryCallback)
{
if (lodWorld == null || lodWorld.getIsWorldNotLoaded()) {
endCallback.run();
return;
}
// don't try to create an LOD object
// if for some reason we aren't
// given a valid chunk object
if (chunk == null) {
endCallback.run();
return;
}
Runnable thread = () ->
{
boolean retryNeeded = false;
try
{
// we need a loaded client world in order to
// get the textures for blocks
if (MC.getWrappedClientWorld() == null)
return;
// don't try to generate LODs if the user isn't in the world anymore
// (this happens a lot when the user leaves a world/server)
if (!MC.hasSinglePlayerServer() && !MC.connectedToServer())
return;
// make sure the dimension exists
// if not, it prob means that player left
LodDimension lodDim = lodWorld.getLodDimension(dim);
if (lodDim == null) return;
retryNeeded = !generateLodNodeFromChunk(lodDim, chunk, new LodBuilderConfig(generationMode), override, genAll);
}
catch (RuntimeException e)
{
EVENT_LOGGER.error("LodBuilder Thread Uncaught Exception: ", e);
// if the world changes while LODs are being generated
// they will throw errors as they try to access things that no longer
// exist.
} finally {
if (!retryNeeded)
endCallback.run();
else
retryCallback.run();
}
};
lodGenThreadPool.execute(thread);
}
/**
* Creates a LodNode for a chunk in the given world.
* @throws IllegalArgumentException thrown if either the chunk or world is null.
*/
public boolean generateLodNodeFromChunk(LodDimension lodDim, IChunkWrapper chunk, LodBuilderConfig config, boolean override, boolean genAll)
{
try {
if (chunk == null)
throw new IllegalArgumentException("generateLodFromChunk given a null chunk");
LodRegion region = lodDim.getRegion(chunk.getRegionPosX(), chunk.getRegionPosZ());
if (region == null)
return false;
// this happens if a LOD is generated after the user leaves the world.
if (MC.getWrappedClientWorld() == null)
return false;
if (!canGenerateLodFromChunk(chunk))
return false;
// generate the LODs
int maxVerticalData = DetailDistanceUtil.getMaxVerticalData((byte)0);
long[] data = new long[maxVerticalData*16*16];
boolean isAllVoid = true;
if (!config.quickFillWithVoid) {
for (int i = 0; i < 16*16; i++)
{
int subX = i/16;
int subZ = i%16;
writeVerticalData(data, i*maxVerticalData, maxVerticalData, chunk, config, subX, subZ);
isAllVoid &= DataPointUtil.isVoid(data[i*maxVerticalData]);
if (!DataPointUtil.doesItExist(data[i*maxVerticalData]))
throw new RuntimeException("writeVerticalData result: Datapoint does not exist at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
if (DataPointUtil.getGenerationMode(data[i*maxVerticalData]) != config.distanceGenerationMode.complexity)
throw new RuntimeException("writeVerticalData result: Datapoint invalid at "+ chunk.getMinX()+subX +", "+ chunk.getMinZ()+subZ);
}
} else {
for (int i = 0; i < 16*16; i++)
{
data[i*maxVerticalData] = DataPointUtil.createVoidDataPoint(config.distanceGenerationMode.complexity);
}
}
if (isAllVoid) EVENT_LOGGER.debug("The chunk {} is completely void.", chunk);
// This MUST be done after the data is generated, to ensure that during the generation, the data is valid.
if (!canGenerateLodFromChunk(chunk)) // TODO Why are we calling this again? - James
return false; // Answer: Because concurrency change may cause the chunk to have invalid data, like light.
if (genAll) {
return writeAllLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
} else {
return writePartialLodNodeData(lodDim, region, chunk.getChunkPosX(), chunk.getChunkPosZ(), data, config, override);
}
} catch (RuntimeException e) {
EVENT_LOGGER.error("LodBuilder encountered an error on building lod: ", e);
return false;
}
}
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk)
{
return chunk != null && chunk.isLightCorrect() && chunk.doesNearbyChunksExist();
}
private boolean writeAllLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
if (region.getMinDetailLevel()!= 0) {
if (!LodUtil.checkRamUsage(0.05, 16)) {
EVENT_LOGGER.debug("LodBuilder: Not enough RAM available for loading files to build lods! Returning...");
return false;
}
LodRegion newRegion = lodDim.getRegionFromFile(region, (byte)0, region.getVerticalQuality());
if (region!=newRegion)
throw new RuntimeException();
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
region.addChunkOfData((byte)0, chunkX*16, chunkZ*16, 16, 16, data, data.length/16/16, override);
region.regenerateLodFromArea((byte)0, chunkX*16, chunkZ*16, 16, 16);
if (!region.doesDataExist((byte)0, chunkX*16, chunkZ*16, config.distanceGenerationMode))
throw new RuntimeException("data at detail 0 is still null after writes to it!");
if (!region.doesDataExist(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ, config.distanceGenerationMode))
throw new RuntimeException("data at chunk detail level is still null after writes to it!");
} finally {
region.isWriting.decrementAndGet();
}
return true;
}
private boolean writePartialLodNodeData(LodDimension lodDim, LodRegion region, int chunkX, int chunkZ,
long[] data, LodBuilderConfig config, boolean override)
{
region.isWriting.incrementAndGet();
try {
byte targetLevel = region.getMinDetailLevel();
int vertQual = DetailDistanceUtil.getMaxVerticalData(targetLevel);
int lodCount = (targetLevel >= LodUtil.CHUNK_DETAIL_LEVEL) ?
1 : 1 << (LodUtil.CHUNK_DETAIL_LEVEL - targetLevel);
if (targetLevel != 0) {
int lodWidth = 16/lodCount;
int inputVertQual = data.length/16/16;
long[] mergedData = new long[vertQual*lodCount*lodCount];
for (int subX=0; subX<lodCount; subX++) {
for (int subZ=0; subZ<lodCount; subZ++) {
long[] toBeMerged = DataPointUtil.extractDataArray(
data, 16, 16, subX*lodWidth, subZ*lodWidth, lodWidth, lodWidth);
if(toBeMerged.length != lodWidth*lodWidth*inputVertQual) throw new RuntimeException();
long[] merged = DataPointUtil.mergeMultiData(toBeMerged, inputVertQual, vertQual);
if (merged.length != vertQual) throw new RuntimeException();
if (!DataPointUtil.doesItExist(merged[0]) ||
DataPointUtil.getGenerationMode(merged[0]) != config.distanceGenerationMode.complexity)
throw new RuntimeException();
System.arraycopy(merged, 0, mergedData, (subZ+subX*lodCount)*vertQual, vertQual);
}
}
data = mergedData;
}
if (lodCount*lodCount*vertQual != data.length) throw new RuntimeException();
for (int i=0; i<data.length; i+=vertQual) {
if (!DataPointUtil.doesItExist(data[i]) ||
DataPointUtil.getGenerationMode(data[i]) != config.distanceGenerationMode.complexity) {
EVENT_LOGGER.error("NULL data at {}, detail {}, vertQual {}, lodCount {}, chunkPos [{},{}]\n"
+ "Data: {}",
i, targetLevel, vertQual, lodCount, chunkX, chunkZ, DataPointUtil.toString(data[i]));
throw new RuntimeException("Null data!");
}
}
//ApiShared.LOGGER.info("Generate chunk: {}, {} ({}, {}) at genMode {}",
// chunk.getChunkPosX(), chunk.getChunkPosZ(), chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode);
if (targetLevel != region.getMinDetailLevel()) {
//Concurrency issues happened.
throw new ConcurrentModificationException("Min detail level changed while writing data");
}
region.addChunkOfData(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount, data, vertQual, override);
region.regenerateLodFromArea(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
lodCount, lodCount);
if (!region.doesDataExist(targetLevel,
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, targetLevel),
LevelPosUtil.convert(LodUtil.CHUNK_DETAIL_LEVEL, chunkZ, targetLevel),
config.distanceGenerationMode))
throw new RuntimeException("data at detail "+ targetLevel+" is still null after writes to it!");
} catch (Exception e) {
EVENT_LOGGER.error("LodBuilder encountered an error on writePartialLodNodeData: ", e);
} finally {
region.isWriting.decrementAndGet();
}
return true;
}
/** creates a vertical DataPoint */
private void writeVerticalData(long[] data, int dataOffset, int maxVerticalData,
IChunkWrapper chunk, LodBuilderConfig config, int chunkSubPosX, int chunkSubPosZ)
{
int totalVerticalData = (chunk.getHeight());
long[] dataToMerge = new long[totalVerticalData];
boolean hasCeiling = MC.getWrappedClientWorld().getDimensionType().hasCeiling();
boolean hasSkyLight = MC.getWrappedClientWorld().getDimensionType().hasSkyLight();
byte generation = config.distanceGenerationMode.complexity;
int count = 0;
// FIXME: This yAbs is just messy!
int x = chunk.getMinX() + chunkSubPosX;
int z = chunk.getMinZ() + chunkSubPosZ;
int y = chunk.getMaxY(x, z);
boolean topBlock = true;
if (y < chunk.getMinBuildHeight())
dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
int maxConnectedLods = Config.Client.Graphics.Quality.verticalQuality.get().maxVerticalData[0];
while (y >= chunk.getMinBuildHeight()) {
int height = determineHeightPointFrom(chunk, config, x, y, z);
// If the lod is at the default height, it must be void data
if (height < chunk.getMinBuildHeight()) {
if (topBlock) dataToMerge[0] = DataPointUtil.createVoidDataPoint(generation);
break;
}
y = height - 1;
// We search light on above air block
int depth = determineBottomPointFrom(chunk, config, x, y, z,
count < maxConnectedLods && (!hasCeiling || !topBlock));
if (hasCeiling && topBlock)
y = depth;
int light = getLightValue(chunk, x, y, z, hasCeiling, hasSkyLight, topBlock);
int color = generateLodColor(chunk, config, x, y, z);
int lightBlock = light & 0b1111;
int lightSky = (light >> 4) & 0b1111;
dataToMerge[count] = DataPointUtil.createDataPoint(height-chunk.getMinBuildHeight(), depth-chunk.getMinBuildHeight(),
color, lightSky, lightBlock, generation);
topBlock = false;
y = depth - 1;
count++;
}
long[] result = DataPointUtil.mergeMultiData(dataToMerge, totalVerticalData, maxVerticalData);
if (result.length != maxVerticalData) throw new ArrayIndexOutOfBoundsException();
System.arraycopy(result, 0, data, dataOffset, maxVerticalData);
}
public static final ELodDirection[] DIRECTIONS = new ELodDirection[] {
ELodDirection.UP,
ELodDirection.DOWN,
ELodDirection.WEST,
ELodDirection.EAST,
ELodDirection.NORTH,
ELodDirection.SOUTH };
private boolean hasCliffFace(IChunkWrapper chunk, int x, int y, int z) {
for (ELodDirection dir : DIRECTIONS) {
IBlockDetailWrapper block = chunk.getBlockDetailAtFace(x, y, z, dir);
if (block == null || !block.hasFaceCullingFor(ELodDirection.OPPOSITE_DIRECTIONS[dir.ordinal()]))
return true;
}
return false;
}
/**
* Find the lowest valid point from the bottom.
* Used when creating a vertical LOD.
*/
private int determineBottomPointFrom(IChunkWrapper chunk, LodBuilderConfig builderConfig, int xAbs, int yAbs, int zAbs, boolean strictEdge)
{
int depth = chunk.getMinBuildHeight();
IBlockDetailWrapper currentBlockDetail = null;
if (strictEdge)
{
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(xAbs, yAbs + 1, zAbs);
if (blockAbove != null && Config.Client.WorldGenerator.tintWithAvoidedBlocks.get() && !blockAbove.shouldRender(Config.Client.WorldGenerator.blocksToAvoid.get()))
{ // The above block is skipped. Lets use its skipped color for current block
currentBlockDetail = blockAbove;
}
if (currentBlockDetail == null) currentBlockDetail = chunk.getBlockDetail(xAbs, yAbs, zAbs);
}
for (int y = yAbs - 1; y >= chunk.getMinBuildHeight(); y--)
{
IBlockDetailWrapper nextBlock = chunk.getBlockDetail(xAbs, y, zAbs);
if (isLayerValidLodPoint(nextBlock)) {
if (!strictEdge) continue;
if (currentBlockDetail.equals(nextBlock)) continue;
if (!hasCliffFace(chunk, xAbs, y, zAbs)) continue;
}
depth = (y + 1);
break;
}
return depth;
}
/** Find the highest valid point from the Top */
private int determineHeightPointFrom(IChunkWrapper chunk, LodBuilderConfig config, int xAbs, int yAbs, int zAbs)
{
//TODO find a way to skip bottom of the world
int height = chunk.getMinBuildHeight()-1;
for (int y = yAbs; y >= chunk.getMinBuildHeight(); y--)
{
if (isLayerValidLodPoint(chunk, xAbs, y, zAbs))
{
height = (y + 1);
break;
}
}
return height;
}
// =====================//
// constructor helpers //
// =====================//
/**
* Generate the color for the given chunk using biome water color, foliage
* color, and grass color.
*/
private int generateLodColor(IChunkWrapper chunk, LodBuilderConfig builderConfig, int x, int y, int z)
{
int colorInt;
if (builderConfig.useBiomeColors)
{
// 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.
colorInt = chunk.getBiome(x, y, z).getColorForBiome(x, z);
}
else
{
// if we are skipping non-full and non-solid blocks that means we ignore
// snow, flowers, etc. Get the above block so we can still get the color
// of the snow, flower, etc. that may be above this block
colorInt = 0;
if (chunk.blockPosInsideChunk(x, y+1, z)) {
IBlockDetailWrapper blockAbove = chunk.getBlockDetail(x, y+1, z);
if (blockAbove != null && Config.Client.WorldGenerator.tintWithAvoidedBlocks.get() && !blockAbove.shouldRender(Config.Client.WorldGenerator.blocksToAvoid.get()))
{ // The above block is skipped. Lets use its skipped color for current block
colorInt = blockAbove.getAndResolveFaceColor(null, chunk, new DHBlockPos(x, y+1, z));
}
}
// override this block's color if there was a block above this
// and we were avoiding non-full/non-solid blocks
if (colorInt == 0) {
IBlockDetailWrapper detail = chunk.getBlockDetail(x, y, z);
colorInt = detail.getAndResolveFaceColor(null, chunk, new DHBlockPos(x, y, z));
}
}
return colorInt;
}
/** Gets the light value for the given block position */
private int getLightValue(IChunkWrapper chunk, int x, int y, int z, boolean hasCeiling, boolean hasSkyLight, boolean topBlock)
{
int skyLight;
int blockLight;
int blockBrightness = chunk.getEmittedBrightness(x, y, z);
// get the air block above or below this block
if (hasCeiling && topBlock)
y--;
else
y++;
blockLight = chunk.getBlockLight(x, y, z);
skyLight = hasSkyLight ? chunk.getSkyLight(x, y, z) : 0;
if (blockLight == -1 || skyLight == -1)
{
ILevelWrapper world = MC.getWrappedServerWorld();
if (world != null)
{
// server world sky light (always accurate)
blockLight = world.getBlockLight(x, y, z);
if (topBlock && !hasCeiling && hasSkyLight)
skyLight = DEFAULT_MAX_LIGHT;
else
skyLight = hasSkyLight ? world.getSkyLight(x, y, z) : 0;
if (!topBlock && skyLight == 15)
{
// we are on predicted terrain, and we don't know what the light here is,
// lets just take a guess
skyLight = 12;
}
}
else
{
world = MC.getWrappedClientWorld();
if (world == null)
{
blockLight = 0;
skyLight = 12;
}
else
{
// client world sky light (almost never accurate)
blockLight = world.getBlockLight(x, y, z);
// estimate what the lighting should be
if (hasSkyLight || !hasCeiling)
{
if (topBlock)
skyLight = DEFAULT_MAX_LIGHT;
else
{
if (hasSkyLight)
skyLight = world.getSkyLight(x, y, z);
//else
// skyLight = 0;
if (!chunk.isLightCorrect() && (skyLight == 0 || skyLight == 15))
{
// we don't know what the light here is,
// lets just take a guess
skyLight = 12;
}
}
}
}
}
}
blockLight = LodUtil.clamp(0, Math.max(blockLight, blockBrightness), DEFAULT_MAX_LIGHT);
return blockLight + (skyLight << 4);
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IBlockDetailWrapper blockDetail)
{
EBlocksToAvoid avoid = Config.Client.WorldGenerator.blocksToAvoid.get();
return blockDetail != null && blockDetail.shouldRender(avoid);
}
/** Is the block at the given blockPos a valid LOD point? */
private boolean isLayerValidLodPoint(IChunkWrapper chunk, int x, int y, int z)
{
EBlocksToAvoid avoid = Config.Client.WorldGenerator.blocksToAvoid.get();
IBlockDetailWrapper block = chunk.getBlockDetail(x, y, z);
return block != null && block.shouldRender(avoid);
}
}
#endif
@@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import com.seibel.lod.core.a7.render.RenderBuffer;
import com.seibel.lod.core.enums.ELodDirection;
import com.seibel.lod.core.enums.ELodDirection.Axis;
import com.seibel.lod.core.enums.config.EGpuUploadMethod;
@@ -34,6 +35,8 @@ import com.seibel.lod.core.util.ColorUtil;
import com.seibel.lod.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
//TODO: Recheck this class for refactoring
/**
* Used to create the quads before they are converted to renderable buffers.
*
@@ -94,80 +97,16 @@ public class LodQuadBuilder
{ 0, 0 }, // 3
},
};
public static final int[][][] DIRECTION_VERTEX_QUAD = new int[][][]
{
// X,Z //
{ // UP
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
{ // DOWN
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
// X,Y //
{ // NORTH
{ 0, 0 }, // 0
{ 0, 1 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 1, 0 }, // 3
},
{ // SOUTH
{ 1, 0 }, // 0
{ 1, 1 }, // 1
{ 0, 1 }, // 2
{ 1, 0 }, // 0
{ 0, 1 }, // 2
{ 0, 0 }, // 3
},
// Z,Y //
{ // WEST
{ 0, 0 }, // 0
{ 1, 0 }, // 1
{ 1, 1 }, // 2
{ 0, 0 }, // 0
{ 1, 1 }, // 2
{ 0, 1 }, // 3
},
{ // EAST
{ 0, 1 }, // 0
{ 1, 1 }, // 1
{ 1, 0 }, // 2
{ 0, 1 }, // 0
{ 1, 0 }, // 2
{ 0, 0 }, // 3
},
};
private int premergeCount = 0;
private final boolean useIBO;
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean useIBO)
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow)
{
for (int i = 0; i < 6; i++)
quads[i] = new ArrayList<>();
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = skyLightCullingBelow;
this.useIBO = useIBO;
}
@@ -259,9 +198,9 @@ public class LodQuadBuilder
bb.put(a);
}
private static void putQuad(boolean useIBO, ByteBuffer bb, BufferQuad quad)
private static void putQuad(ByteBuffer bb, BufferQuad quad)
{
int[][] quadBase = useIBO ? DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()] : DIRECTION_VERTEX_QUAD[quad.direction.ordinal()];
int[][] quadBase = DIRECTION_VERTEX_IBO_QUAD[quad.direction.ordinal()];
short widthEastWest = quad.widthEastWest;
short widthNorthSouth = quad.widthNorthSouthOrUpDown;
Axis axis = quad.direction.getAxis();
@@ -366,7 +305,7 @@ public class LodQuadBuilder
{
return new Iterator<ByteBuffer>()
{
final ByteBuffer bb = ByteBuffer.allocateDirect(LodBufferBuilderFactory.FULL_SIZED_BUFFER)
final ByteBuffer bb = ByteBuffer.allocateDirect(RenderBuffer.FULL_SIZED_BUFFER)
.order(ByteOrder.nativeOrder());
int dir = skipEmpty(0);
int quad = 0;
@@ -392,7 +331,7 @@ public class LodQuadBuilder
return null;
}
bb.clear();
bb.limit(LodBufferBuilderFactory.FULL_SIZED_BUFFER);
bb.limit(RenderBuffer.FULL_SIZED_BUFFER);
while (bb.hasRemaining() && dir < 6)
{
writeData();
@@ -411,7 +350,7 @@ public class LodQuadBuilder
{
break;
}
putQuad(useIBO, bb, quads[dir].get(i));
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
@@ -450,26 +389,26 @@ public class LodQuadBuilder
}
int numOfQuads = _countRemainingQuads();
if (numOfQuads > LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER)
numOfQuads = LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER;
if (numOfQuads > RenderBuffer.MAX_QUADS_PER_BUFFER)
numOfQuads = RenderBuffer.MAX_QUADS_PER_BUFFER;
if (numOfQuads == 0)
{
vbo.setVertexCount(0);
return false;
}
ByteBuffer bb = vbo.mapBuffer(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE, method,
LodBufferBuilderFactory.FULL_SIZED_BUFFER);
ByteBuffer bb = vbo.mapBuffer(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE, method,
RenderBuffer.FULL_SIZED_BUFFER);
if (bb == null)
throw new NullPointerException("mapBuffer returned null");
bb.clear();
bb.limit(numOfQuads * LodBufferBuilderFactory.QUADS_BYTE_SIZE);
bb.limit(numOfQuads * RenderBuffer.QUADS_BYTE_SIZE);
while (bb.hasRemaining() && dir < 6)
{
writeData(bb);
}
bb.rewind();
vbo.unmapBuffer();
vbo.setVertexCount(useIBO ? numOfQuads*4 : numOfQuads*6);
vbo.setVertexCount(numOfQuads*4);
return dir < 6;
}
@@ -494,7 +433,7 @@ public class LodQuadBuilder
{
break;
}
putQuad(useIBO, bb, quads[dir].get(i));
putQuad(bb, quads[dir].get(i));
}
if (i >= quads[dir].size())
@@ -525,7 +464,7 @@ public class LodQuadBuilder
/** Returns how many Buffers will be needed to render everything in this builder. */
public int getCurrentNeededVertexBufferCount()
{
return LodUtil.ceilDiv(getCurrentQuadsCount(), LodBufferBuilderFactory.MAX_QUADS_PER_BUFFER);
return LodUtil.ceilDiv(getCurrentQuadsCount(), RenderBuffer.MAX_QUADS_PER_BUFFER);
}
}
@@ -24,7 +24,8 @@ import java.nio.file.Path;
* @version 2022-5-26
*/
public class ConfigFileHandling {
public static final Path ConfigPath = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class).getInstallationDirectory().toPath().resolve("config").resolve(ModInfo.NAME+".toml");
public static final Path ConfigPath = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class)
.getInstallationDirectory().toPath().resolve("config").resolve(ModInfo.NAME+".toml");
/** Saves the config to the file */
public static void saveToFile() {
@@ -147,7 +147,7 @@ public class DependencyInjector<BindableType extends IBindable>
* @see #get(Class, boolean)
*/
@SuppressWarnings("unchecked")
public <T extends BindableType> T get(Class<? extends BindableType> interfaceClass) throws ClassCastException
public <T extends BindableType> T get(Class<T> interfaceClass) throws ClassCastException
{
return (T) getInternalLogic(interfaceClass, false).get(0);
}
@@ -103,7 +103,7 @@ public class OverrideInjector
*
* @see DependencyInjector#get(Class, boolean)
*/
public <T extends IDhApiOverrideable> T get(Class<? extends IDhApiOverrideable> interfaceClass) throws ClassCastException
public <T extends IDhApiOverrideable> T get(Class<T> interfaceClass) throws ClassCastException
{
T value = primaryInjector.get(interfaceClass);
if (value == null)
@@ -126,7 +126,7 @@ public class OverrideInjector
*
* @see DependencyInjector#get(Class, boolean)
*/
public <T extends IDhApiOverrideable> T get(Class<? extends IDhApiOverrideable> interfaceClass, EApiOverridePriority overridePriority) throws ClassCastException
public <T extends IDhApiOverrideable> T get(Class<T> interfaceClass, EApiOverridePriority overridePriority) throws ClassCastException
{
T value;
if (overridePriority == EApiOverridePriority.PRIMARY)
@@ -19,6 +19,8 @@
package com.seibel.lod.core.objects;
import com.seibel.lod.core.a7.pos.DhBlockPos2D;
import java.util.Objects;
public class DHBlockPos {
@@ -56,6 +58,10 @@ public class DHBlockPos {
this(pos.x, pos.y, pos.z);
}
public DHBlockPos(DhBlockPos2D pos, int y) {
this(pos.x, y, pos.z);
}
@Deprecated
public int getX()
{
@@ -1,182 +0,0 @@
/*
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2022 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.lod.core.objects;
#if abc
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import com.seibel.lod.core.api.internal.InternalApiShared;
import com.seibel.lod.core.logging.DhLoggerBuilder;
import com.seibel.lod.core.util.LevelPosUtil;
import com.seibel.lod.core.util.LodUtil;
import org.apache.logging.log4j.Logger;
/**
* Holds a levelPos that needs to be rendered.
*
* @author Leonardo Amato
* @version 9-18-2021
*/
public class PosToRenderContainer
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger(MethodHandles.lookup().lookupClass().getSimpleName());
public byte minDetail;
private int regionPosX;
private int regionPosZ;
private int numberOfPosToRender;
private int[] posToRender;
private byte[][] population;
static class LodPos {
byte detail;
int posX;
int posZ;
}
private LodPos[] lodPosList;
public PosToRenderContainer(byte minDetail, int regionPosX, int regionPosZ)
{
this.minDetail = minDetail;
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
posToRender = new int[size * size * 3];
population = new byte[size][size];
lodPosList = new LodPos[size * size];
}
public void addPosToRender(byte detailLevel, int posX, int posZ)
{
// When rapidly changing dimensions the bufferBuilder can cause this,
// James isn't sure why, but this will prevent an exception at
// the very least (while stilling logging the problem).
if (numberOfPosToRender >= posToRender.length)
{
// This is might be due to dimensions having a different width
// when first loading in
LOGGER.error("Unable to addPosToRender. numberOfPosToRender [" + numberOfPosToRender + "] detailLevel [" + detailLevel + "] Pos [" + posX + "," + posZ + "]");
numberOfPosToRender++; // incrementing so we can see how many pos over the limit we would go
return;
}
//if(numberOfPosToRender >= posToRender.length)
// posToRender = Arrays.copyOf(posToRender, posToRender.length*2);
lodPosList[numberOfPosToRender] = new LodPos();
lodPosList[numberOfPosToRender].detail = detailLevel;
lodPosList[numberOfPosToRender].posX = posX;
lodPosList[numberOfPosToRender].posZ = posZ;
//posToRender[numberOfPosToRender * 3] = detailLevel;
//posToRender[numberOfPosToRender * 3 + 1] = posX;
//posToRender[numberOfPosToRender * 3 + 2] = posZ;
numberOfPosToRender++;
population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] = (byte) (detailLevel + 1);
}
public boolean contains(byte detailLevel, int posX, int posZ)
{
if (LevelPosUtil.getRegion(detailLevel, posX) == regionPosX && LevelPosUtil.getRegion(detailLevel, posZ) == regionPosZ)
return (population[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posX, minDetail))]
[LevelPosUtil.getRegionModule(minDetail, LevelPosUtil.convert(detailLevel, posZ, minDetail))] == (detailLevel + 1));
else
return false;
}
public void clear(byte minDetail, int regionPosX, int regionPosZ)
{
this.numberOfPosToRender = 0;
this.regionPosX = regionPosX;
this.regionPosZ = regionPosZ;
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - minDetail);
if (this.minDetail == minDetail)
{
Arrays.fill(posToRender, 0);
for (byte[] bytes : population)
Arrays.fill(bytes, (byte) 0);
}
else
{
this.minDetail = minDetail;
posToRender = new int[size * size * 3];
population = new byte[size][size];
}
lodPosList = new LodPos[size * size];
}
public int getNumberOfPos()
{
return numberOfPosToRender;
}
public byte getNthDetailLevel(int n)
{
return lodPosList[n].detail;
//return (byte) posToRender[n * 3];
}
public int getNthPosX(int n)
{
return lodPosList[n].posX;
//return posToRender[n * 3 + 1];
}
public int getNthPosZ(int n)
{
return lodPosList[n].posZ;
//return posToRender[n * 3 + 2];
}
public void sort() {
Arrays.sort(lodPosList, 0, numberOfPosToRender,
(a,b) -> {
if (a.detail != b.detail) return a.detail - b.detail;
if (a.posX != b.posX) return a.posX - b.posX;
return a.posZ - b.posZ;
}
);
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("To render ");
builder.append(numberOfPosToRender);
builder.append('\n');
for (int i = 0; i < numberOfPosToRender; i++)
{
builder.append(posToRender[i * 3]);
builder.append(" ");
builder.append(posToRender[i * 3 + 1]);
builder.append(" ");
builder.append(posToRender[i * 3 + 2]);
builder.append('\n');
}
builder.append('\n');
return builder.toString();
}
}
#endif
@@ -318,17 +318,7 @@ public class LodRegion {
}
return (byte) Math.max(getMinDetailLevel(), renderLevel);
}
public void getPosToRender(PosToRenderContainer posToRender, int playerPosX,
int playerPosZ)
{
// use FAR_FIRST on local worlds and NEAR_FIRST on servers
EGenerationPriority generationPriority = getResolvedGenerationPriority();
EDropoffQuality dropoffQuality = CONFIG.client().graphics().quality().getResolvedDropoffQuality();
getPosToRender(posToRender, playerPosX, playerPosZ, generationPriority, dropoffQuality);
}
private EGenerationPriority getResolvedGenerationPriority() {
EGenerationPriority priority = Config.Client.WorldGenerator.generationPriority.get();
@@ -346,140 +336,7 @@ public class LodRegion {
return dropoffQuality;
}
/**
* This method will fill the posToRender array with all levelPos that are
* render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to
* understand
*/
private void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ,
EGenerationPriority priority, EDropoffQuality dropoffQuality) {
double minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, playerPosZ);
byte targetLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
if (targetLevel <= dropoffQuality.fastModeSwitch) {
getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ,
priority);
} else {
// FarModeSwitchLevel or above is the level where a giant block of lod is not acceptable even if not all child data exist.
double centerDistance = LevelPosUtil.centerDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, playerPosZ);
targetLevel = DetailDistanceUtil.getDetailLevelFromDistance(centerDistance);
byte farModeSwitchLevel = (priority == EGenerationPriority.NEAR_FIRST) ? 0 :
calculateFarModeSwitch(targetLevel);
if (priority == EGenerationPriority.FAR_FIRST) farModeSwitchLevel = 8;
getPosToRenderFlat(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, targetLevel, farModeSwitchLevel);
}
}
/**
* This method will fill the posToRender array with all levelPos that are
* render-able.
* <p>
* TODO why don't we return the posToRender, it would make this easier to
* understand TODO this needs some more comments, James was only able to figure
* out part of it
*/
private void getPosToRender(PosToRenderContainer posToRender, byte detailLevel, int offsetPosX, int offsetPosZ, int playerPosX,
int playerPosZ, EGenerationPriority priority) {
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
// calculate the LevelPos that are in range
double minDistance = LevelPosUtil.minDistance(detailLevel, offsetPosX + regionPosX*size, offsetPosZ + regionPosZ*size, playerPosX, playerPosZ);
byte minLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance);
// FarModeSwitchLevel or above is the level where a giant block of lod is not acceptable even if not all child data exist.
byte farModeSwitchLevel = (priority == EGenerationPriority.NEAR_FIRST) ? 0 : calculateFarModeSwitch(minLevel);
if (priority == EGenerationPriority.FAR_FIRST) farModeSwitchLevel = 8;
if (detailLevel <= minLevel) {
posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size);
} else // case where (detailLevel > desiredLevel)
{
int childPosX = (offsetPosX + regionPosX*size) * 2;
int childPosZ = (offsetPosZ + regionPosZ*size) * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
if (detailLevel > farModeSwitchLevel) {
// Giant block is not acceptable. So leave empty void if data doesn't exist.
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, EDistanceGenerationMode.NONE)) {
getPosToRender(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, playerPosX,
playerPosZ, priority);
}
}
}
} else {
// Giant block is acceptable. So use this level lod if not all child data exist.
int childrenCount = 0;
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, EDistanceGenerationMode.NONE)) {
childrenCount++;
}
}
}
// If all the four children exist go deeper
if (childrenCount == 4) {
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToRender(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, playerPosX,
playerPosZ, priority);
} else {
posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size);
}
}
}
}
/**
* This method will fill the posToRender array with all levelPos that are render-able,
* but the entire region try to use the same detail level.
*/
private void getPosToRenderFlat(PosToRenderContainer posToRender, byte detailLevel, int offsetPosX, int offsetPosZ,
byte targetLevel, byte farModeSwitchLevel) {
// equivalent to 2^(...)
int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel);
if (detailLevel == targetLevel) {
posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size);
} else // case where (detailLevel > desiredLevel)
{
int childPosX = (offsetPosX + regionPosX*size) * 2;
int childPosZ = (offsetPosZ + regionPosZ*size) * 2;
byte childDetailLevel = (byte) (detailLevel - 1);
if (detailLevel > farModeSwitchLevel) {
// Giant block is not acceptable. So leave empty void if data doesn't exist.
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, EDistanceGenerationMode.NONE)) {
getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, farModeSwitchLevel);
}
}
}
} else {
// Giant block is acceptable. So use this level lod if not all child data exist.
int childrenCount = 0;
for (int x = 0; x <= 1; x++) {
for (int z = 0; z <= 1; z++) {
if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, EDistanceGenerationMode.RENDERABLE)) {
childrenCount++;
}
}
}
// If all the four children exist go deeper
if (childrenCount == 4) {
for (int x = 0; x <= 1; x++)
for (int z = 0; z <= 1; z++)
getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, farModeSwitchLevel);
} else {
posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size);
}
}
}
}
public static final class LevelPos {
public final byte detail;
public final int posX;