This commit is contained in:
s809
2024-07-27 21:43:21 +05:00
147 changed files with 6344 additions and 1802 deletions
@@ -23,7 +23,9 @@ import com.seibel.distanthorizons.api.interfaces.events.IDhApiEventInjector;
import com.seibel.distanthorizons.api.interfaces.factories.IDhApiWrapperFactory;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.override.worldGenerator.IDhApiWorldGeneratorOverrideRegister;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderProxy;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiAfterDhInitEvent;
import com.seibel.distanthorizons.api.methods.override.DhApiWorldGeneratorOverrideRegister;
import com.seibel.distanthorizons.coreapi.ModInfo;
@@ -127,6 +129,12 @@ public class DhApi
*/
public static IDhApiWrapperFactory wrapperFactory = null;
/**
* Used to create custom renderable objects. <br>
* These objects can be added to the renderer in {@link IDhApiLevelWrapper}
* @since API 3.0.0
*/
public static IDhApiCustomRenderObjectFactory customRenderObjectFactory = null;
}
@@ -0,0 +1,67 @@
package com.seibel.distanthorizons.api.enums.rendering;
/**
* contains the indices used by shaders to determine
* how different block types should be rendered. <br><br>
*
* UNKNOWN, <br>
* LEAVES, <br>
* STONE, <br>
* WOOD, <br>
* METAL, <br>
* DIRT, <br>
* LAVA, <br>
* DEEPSLATE, <br>
* SNOW, <br>
* SAND, <br>
* TERRACOTTA, <br>
* NETHER_STONE, <br>
* WATER, <br>
* GRASS, <br>
* AIR, <br>
* ILLUMINATED, <br>
*
* @author James Seibel
* @since API 3.0.0
* @version 2024-7-11
*/
public enum EDhApiBlockMaterial
{
UNKNOWN(0),
LEAVES(1),
STONE(2),
WOOD(3),
METAL(4),
DIRT(5),
LAVA(6),
DEEPSLATE(7),
SNOW(8),
SAND(9),
TERRACOTTA(10),
NETHER_STONE(11),
WATER(12),
GRASS(13),
/** shouldn't normally be needed, but just in case */
AIR(14),
ILLUMINATED(15); // Max value
public final byte index;
EDhApiBlockMaterial(int index) { this.index = (byte)index;}
public static EDhApiBlockMaterial getFromIndex(int index)
{
for(EDhApiBlockMaterial material : EDhApiBlockMaterial.values())
{
if (material.index == index)
{
return material;
}
}
return EDhApiBlockMaterial.UNKNOWN;
}
}
@@ -30,12 +30,21 @@ import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
*/
public interface IDhApiBlockStateWrapper extends IDhApiUnsafeWrapper
{
/** @since API 1.0.0 */
boolean isAir();
/** @since API 1.0.0 */
boolean isSolid();
/** @since API 1.0.0 */
boolean isLiquid();
// TODO:
// boolean hasNoCollision();
// boolean noFaceIsFullFace();
/**
* Returns the full serialized form of the given block
* as defined by DH's serialization methods.
* @since API 3.0.0
*/
String getSerialString();
/** @since API 3.0.0 */
byte getMaterialId();
}
@@ -0,0 +1,46 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.interfaces.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigGroup;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
/**
* Distant Horizons' generic rendering configuration. <br><br>
*
* @author James Seibel
* @version 2024-7-11
* @since API 3.0.0
*/
public interface IDhApiGenericRenderingConfig extends IDhApiConfigGroup
{
/**
* If enabled DH will render generic objects into its terrain pass. <br>
* This includes: clouds, beacons, and API added objects.
*/
IDhApiConfigValue<Boolean> renderingEnabled();
/** If enabled DH will render beacon beams. */
IDhApiConfigValue<Boolean> beaconRenderingEnabled();
/** If enabled DH will render clouds. */
IDhApiConfigValue<Boolean> cloudRenderingEnabled();
}
@@ -41,6 +41,7 @@ public interface IDhApiGraphicsConfig extends IDhApiConfigGroup
IDhApiFogConfig fog();
IDhApiAmbientOcclusionConfig ambientOcclusion();
IDhApiNoiseTextureConfig noiseTexture();
IDhApiGenericRenderingConfig genericRendering();
@@ -0,0 +1,21 @@
package com.seibel.distanthorizons.api.interfaces.data;
/**
* Can be used to drastically speed up repeat read operations in {@link IDhApiTerrainDataRepo}.
*
* @see IDhApiTerrainDataRepo
*
* @author James Seibel
* @version 2024-7-14
* @since API 3.0.0
*/
public interface IDhApiTerrainDataCache
{
/**
* Removes any data that's currently stored in this cache.
* This cane be done to free up memory or invalidate
* the cache so fresh data can be pulled in.
*/
void clear();
}
@@ -29,6 +29,8 @@ import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
/**
* Used to interface with Distant Horizons' terrain data.
*
* @see IDhApiTerrainDataCache
*
* @author James Seibel
* @version 2023-6-22
* @since API 1.0.0
@@ -40,29 +42,50 @@ public interface IDhApiTerrainDataRepo
// getters //
//=========//
/** Returns the terrain datapoint at the given block position, at/or containing the given Y position. */
DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ);
/** Returns every datapoint in the column located at the given block X and Z position top to bottom. */
DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ);
/** @see IDhApiTerrainDataRepo#getSingleDataPointAtBlockPos(IDhApiLevelWrapper, int, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ) { return this.getSingleDataPointAtBlockPos(levelWrapper, blockPosX, blockPosY, blockPosZ, null); }
/**
* Returns the terrain datapoint at the given block position, at/or containing the given Y position.
* @since API 3.0.0
*/
DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getColumnDataAtBlockPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ) { return this.getColumnDataAtBlockPos(levelWrapper, blockPosX, blockPosZ, null); }
/**
* Returns every datapoint in the column located at the given block X and Z position top to bottom.
* @since API 3.0.0
*/
DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtChunkPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ) { return this.getAllTerrainDataAtChunkPos(levelWrapper, chunkPosX, chunkPosZ, null); }
/**
* Returns every datapoint in the given chunk's X and Z position. <br><br>
*
* The returned array is ordered: [relativeBlockX][relativeBlockZ][columnIndex] <br>
* RelativeBlockX/Z are relative to the block position closest to negative infinity in the chunk's position. <br>
* The column data is ordered from top to bottom. Note: each column may have a different number of values. <br>
*
* @since API 3.0.0
*/
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ);
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtRegionPos(IDhApiLevelWrapper, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ) { return this.getAllTerrainDataAtRegionPos(levelWrapper, regionPosX, regionPosZ, null); }
/**
* Returns every datapoint in the given region's X and Z position. <br><br>
*
* The returned array is ordered: [relativeBlockX][relativeBlockZ][columnIndex] <br>
* RelativeBlockX/Z are relative to the block position closest to negative infinity in the region's position. <br>
* The column data is ordered from top to bottom. Note: each column may have a different number of values. <br>
*
* @since API 3.0.0
*/
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ);
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper, byte, int, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ) { return this.getAllTerrainDataAtDetailLevelAndPos(levelWrapper, detailLevel, posX, posZ, null); }
/**
* Returns every datapoint in the column located at the given detail level and X/Z position. <br>
* This can be used to return terrain data for non-standard sizes (IE 2x2 blocks or 2x2 chunks).
@@ -71,20 +94,42 @@ public interface IDhApiTerrainDataRepo
* Every increase doubles the width of the returned area. <br>
* Example values: 0 = block, 1 = 2x2 blocks, 2 = 4x4 blocks, ... 4 = chunk (16x16 blocks), ... 9 = region (512x512 blocks) <br>
* See {@link EDhApiDetailLevel} for more information.
*
* @since API 3.0.0
*/
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ);
DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, IDhApiTerrainDataCache dataCache);
/** @see IDhApiTerrainDataRepo#raycast(IDhApiLevelWrapper, double, double, double, float, float, float, int, IDhApiTerrainDataCache) */
default DhApiResult<DhApiRaycastResult> raycast(
IDhApiLevelWrapper levelWrapper,
double rayOriginX, double rayOriginY, double rayOriginZ,
float rayDirectionX, float rayDirectionY, float rayDirectionZ,
int maxRayBlockLength)
{
return this.raycast(
levelWrapper,
rayOriginX, rayOriginY, rayOriginZ,
rayDirectionX, rayDirectionY, rayDirectionZ,
maxRayBlockLength,
null);
}
/**
* Returns the datapoint and position of the LOD
* at the end of the given ray. <br><br>
*
* Will return "success" with a null datapoint if the ray reaches the max length without finding any data.
*
* @since API 3.0.0
*/
DhApiResult<DhApiRaycastResult> raycast(
IDhApiLevelWrapper levelWrapper,
double rayOriginX, double rayOriginY, double rayOriginZ,
float rayDirectionX, float rayDirectionY, float rayDirectionZ,
int maxRayBlockLength);
int maxRayBlockLength,
IDhApiTerrainDataCache dataCache);
@@ -98,15 +143,27 @@ public interface IDhApiTerrainDataRepo
* Notes: <br>
* - Only works if the given {@link IDhApiLevelWrapper} points to a loaded level. <br>
* - If the player travels to this chunk, or the chunk is updated in some other way; your data will be replaced
* by whatever the current chunk is. <br>
* - This method may not update the LOD data immediately. Any other chunks have
* been queued to update, they will be handled first.
* by whatever the current chunk is. <br>
* - This method may not update the LOD data immediately. Any other chunks that have
* been queued to update will be handled first.
*
* @param levelWrapper the level wrapper that the chunk should be saved to.
* @param chunkObjectArray see {@link IDhApiWorldGenerator#generateChunks} for what objects are expected.
* @throws ClassCastException if chunkObjectArray doesn't contain the right objects.
* The exception will contain the expected object(s).
*/
public DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException;
DhApiResult<Void> overwriteChunkDataAsync(IDhApiLevelWrapper levelWrapper, Object[] chunkObjectArray) throws ClassCastException;
//=========//
// helpers //
//=========//
/**
* @return a {@link IDhApiTerrainDataCache} backed by {@link java.lang.ref.SoftReference}'s.
* @since API 3.0.0
*/
IDhApiTerrainDataCache getSoftCache();
}
@@ -79,11 +79,34 @@ public interface IDhApiWrapperFactory
///**
// * Specifically designed to be used with the API.
// *
// * @throws ClassCastException with instructions on expected objects if the object couldn't be cast
// */
//IChunkWrapper createChunkWrapper(Object[] objectArray) throws ClassCastException;
/**
* Constructs a {@link IDhApiBiomeWrapper} for use by other DhApi methods.
*
* @param resourceLocationString example: "minecraft:plains"
*
* @param levelWrapper Expects a {@link IDhApiLevelWrapper} returned by one of DH's {@link DhApi.Delayed#worldProxy} methods. <br>
* A custom implementation of {@link IDhApiLevelWrapper} will not be accepted.
*
* @throws IOException if the resourceLocationString wasn't able to be parsed or converted into a valid {@link IDhApiBiomeWrapper}
* @throws ClassCastException if the wrong levelWrapper type was given
*
* @since API 3.0.0
*/
IDhApiBiomeWrapper getBiomeWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException;
/**
* Constructs a {@link IDhApiBlockStateWrapper} for use by other DhApi methods.
* This returns the default blockstate for the given resource location.
*
* @param resourceLocationString examples: "minecraft:bedrock", "minecraft:stone", "minecraft:grass_block"
* @param levelWrapper Expects a {@link IDhApiBlockStateWrapper} returned by one of DH's {@link DhApi.Delayed#worldProxy} methods. <br>
* A custom implementation of {@link IDhApiBlockStateWrapper} will not be accepted.
*
* @throws IOException if the resourceLocationString wasn't able to be parsed or converted into a valid {@link IDhApiBlockStateWrapper}
* @throws ClassCastException if the wrong levelWrapper type was given
*
* @since API 3.0.0
*/
IDhApiBlockStateWrapper getDefaultBlockStateWrapper(String resourceLocationString, IDhApiLevelWrapper levelWrapper) throws IOException, ClassCastException;
}
@@ -21,7 +21,7 @@ package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
/**
* Used to determine if a LOD should be rendered or is outside the
@@ -41,7 +41,7 @@ public interface IDhApiCullingFrustum extends IDhApiOverrideable
* @param worldMaxBlockY the highest block position this level allows.
* @param worldViewProjection the projection matrix used in this render pass.
*/
void update(int worldMinBlockY, int worldMaxBlockY, Mat4f worldViewProjection);
void update(int worldMinBlockY, int worldMaxBlockY, DhApiMat4f worldViewProjection);
/**
* returns true if the LOD bounds intersect this frustum
@@ -0,0 +1,80 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
/**
* @see IDhApiShaderProgram
*
* @author James Seibel
* @version 2024-7-11
* @since API 3.0.0
*/
public interface IDhApiGenericObjectShaderProgram extends IDhApiOverrideable
{
/**
* If this method is called that means this program has the highest priority as defined by {@link IDhApiOverrideable#getPriority()}
* and gets to decide if it wants to be used to render this frame or not. <br><br>
*
* If this method returns true then this program will be used for this frame. <br>
* If this returns false then the default DH {@link IDhApiGenericObjectShaderProgram} will be used instead.
*/
boolean overrideThisFrame();
/** @return the OpenGL ID for this shader program */
int getId();
/** Free any OpenGL objects owned by this program. */
void free();
/** Runs any necessary binding this program needs so rendering can be done. */
void bind(DhApiRenderParam renderEventParam);
/** Runs any necessary unbinding this program needs so rendering can be done by another program. */
void unbind();
/** Binds the given Vertex Buffer Object to this shader program for rendering. */
void bindVertexBuffer(int vbo);
/** sets up the necessary uniforms for rendering */
void fillIndirectUniformData(
DhApiRenderParam renderParameters,
DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup,
DhApiVec3d camPos);
/** sets up the necessary uniforms for rendering */
void fillSharedDirectUniformData(
DhApiRenderParam renderParameters,
DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup,
DhApiVec3d camPos);
void fillDirectUniformData(
DhApiRenderParam renderParameters,
IDhApiRenderableBoxGroup boxGroup, DhApiRenderableBox box,
DhApiVec3d camPos);
}
@@ -21,10 +21,11 @@ package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
/**
* @see IDhApiGenericObjectShaderProgram
*
* @author James Seibel
* @version 2024-1-24
* @since API 2.0.0
@@ -57,7 +58,7 @@ public interface IDhApiShaderProgram extends IDhApiOverrideable
void fillUniformData(DhApiRenderParam renderParameters);
/** sets the vec3 that all DH verticies should be offset by when rendering */
void setModelOffsetPos(Vec3f modelPos);
void setModelOffsetPos(DhApiVec3f modelPos);
/** Binds the given Vertex Buffer Object to this shader program for rendering. */
void bindVertexBuffer(int vbo);
@@ -19,10 +19,6 @@
package com.seibel.distanthorizons.api.interfaces.override.rendering;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
/**
* The culling frustum used during Distant Horizons' shadow pass
* if another mod has enabled Distant Horizons' shadow
@@ -22,6 +22,8 @@ package com.seibel.distanthorizons.api.interfaces.override.worldGenerator;
import com.seibel.distanthorizons.api.enums.EDhApiDetailLevel;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.api.interfaces.override.IDhApiOverrideable;
import com.seibel.distanthorizons.api.objects.data.DhApiChunk;
import com.seibel.distanthorizons.api.objects.data.DhApiTerrainDataPoint;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import java.io.Closeable;
@@ -77,13 +79,41 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
}, worldGeneratorThreadPool);
}
@Override
public final CompletableFuture<Void> generateApiChunks(
int chunkPosMinX,
int chunkPosMinZ,
byte granularity,
byte targetDataDetail,
EDhApiDistantGeneratorMode generatorMode,
ExecutorService worldGeneratorThreadPool,
Consumer<DhApiChunk> resultConsumer
)
{
return CompletableFuture.runAsync(() ->
{
// TODO what does this mean?
int genChunkWidth = BitShiftUtil.powerOfTwo(granularity - 4);
for (int chunkX = chunkPosMinX; chunkX < chunkPosMinX + genChunkWidth; chunkX++)
{
for (int chunkZ = chunkPosMinZ; chunkZ < chunkPosMinZ + genChunkWidth; chunkZ++)
{
DhApiChunk apiChunk = this.generateApiChunk(chunkX, chunkZ, generatorMode);
resultConsumer.accept(apiChunk);
}
}
}, worldGeneratorThreadPool);
}
/**
* This method is called to generate terrain over a given area
* from a thread defined by Distant Horizons. <br><br>
*
* @param chunkPosX the chunk X position in the level (not to be confused with the chunk's BlockPos in the level)
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
* @param generatorMode how far into the world gen pipeline this method run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
*
* @return See {@link IDhApiWorldGenerator#generateChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer) IDhApiWorldGenerator.generateChunks}
* for the list of Object's this method should return along with additional documentation.
@@ -92,4 +122,21 @@ public abstract class AbstractDhApiChunkWorldGenerator implements Closeable, IDh
*/
public abstract Object[] generateChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
/**
* This method is called to generate terrain over a given area
* from a thread defined by Distant Horizons. <br><br>
*
* @param chunkPosX the chunk X position in the level (not to be confused with the chunk's BlockPos in the level)
* @param chunkPosZ the chunk Z position in the level (not to be confused with the chunk's BlockPos in the level)
* @param generatorMode how far into the world gen pipeline this method should run. See {@link EDhApiDistantGeneratorMode} for additional documentation.
*
* @return A {@link DhApiChunk} with the generated {@link DhApiTerrainDataPoint} including air blocks.
* Note: if air blocks aren't included with the proper lighting, lower detail levels will appear as black/unlit.
*
* @see IDhApiWorldGenerator#generateApiChunks(int, int, byte, byte, EDhApiDistantGeneratorMode, ExecutorService, Consumer)
*
* @since API 3.0.0
*/
public abstract DhApiChunk generateApiChunk(int chunkPosX, int chunkPosZ, EDhApiDistantGeneratorMode generatorMode);
}
@@ -154,6 +154,7 @@ public interface IDhApiWorldGenerator extends Closeable, IDhApiOverrideable
*
* After the {@link DhApiChunk} has been generated, it should be passed into the
* resultConsumer's {@link Consumer#accept(Object)} method.
* Note: if air blocks aren't included in the with the {@link DhApiChunk} with proper lighting, lower detail levels will appear as black/unlit.
*
* @implNote the default implementation of this method throws an {@link UnsupportedOperationException},
* and must be overridden when {@link #getReturnType()} returns {@link EDhApiWorldGeneratorReturnType#API_CHUNKS}.
@@ -0,0 +1,67 @@
package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import java.util.List;
/**
* Handles creating
* {@link IDhApiRenderableBoxGroup} objects,
* which can be added via a {@link IDhApiCustomRenderRegister}.
*
* @see IDhApiCustomRenderRegister
* @see IDhApiRenderableBoxGroup
*
* @author James Seibel
* @version 2024-7-3
* @since API 3.0.0
*/
public interface IDhApiCustomRenderObjectFactory
{
/**
* Creates a {@link IDhApiRenderableBoxGroup} from for the given {@link DhApiRenderableBox}
* where the box is positioned relative to the level's origin.
*
* @param resourceLocation A colon separated Resource Location string, similar to vanilla Minecraft, for example: "DistantHorizons:Clouds"
*
* @see DhApiRenderableBox
* @see IDhApiRenderableBoxGroup#getResourceLocationNamespace()
* @see IDhApiRenderableBoxGroup#getResourceLocationPath()
*
* @throws IllegalArgumentException if <code>resourceLocation</code> is null, isn't separated by a colon, or has multiple colons.
*/
IDhApiRenderableBoxGroup createForSingleBox(String resourceLocation, DhApiRenderableBox cube) throws IllegalArgumentException;
/**
* Creates a {@link IDhApiRenderableBoxGroup} from the given list of {@link DhApiRenderableBox} where each
* one is positioned relative to given <code>originBlockPos</code>, which in turn is relative to the level's origin.
*
* @param resourceLocation A colon separated Resource Location string, similar to vanilla Minecraft, for example: "DistantHorizons:Clouds"
* @param originBlockPos The starting position for this {@link IDhApiRenderableBoxGroup}, can be changed during runtime.
*
*
* @see DhApiRenderableBox
* @see IDhApiRenderableBoxGroup#getResourceLocationNamespace()
* @see IDhApiRenderableBoxGroup#getResourceLocationPath()
*
* @throws IllegalArgumentException if <code>resourceLocation</code> is null, isn't separated by a colon, or has multiple colons.
*/
IDhApiRenderableBoxGroup createRelativePositionedGroup(String resourceLocation, DhApiVec3d originBlockPos, List<DhApiRenderableBox> cubeList);
/**
* Creates a {@link IDhApiRenderableBoxGroup} from the given list of {@link DhApiRenderableBox} where each
* one is positioned relative to the level's origin.
*
* @param resourceLocation A colon separated Resource Location string, similar to vanilla Minecraft, for example: "DistantHorizons:Clouds"
*
* @see DhApiRenderableBox
* @see IDhApiRenderableBoxGroup#getResourceLocationNamespace()
* @see IDhApiRenderableBoxGroup#getResourceLocationPath()
*
* @throws IllegalArgumentException if <code>resourceLocation</code> is null, isn't separated by a colon, or has multiple colons.
*/
IDhApiRenderableBoxGroup createAbsolutePositionedGroup(String resourceLocation, List<DhApiRenderableBox> cubeList);
}
@@ -0,0 +1,30 @@
package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiWorldProxy;
/**
* Handles adding and removing
* {@link IDhApiRenderableBoxGroup} objects,
* from DH's renderer. <br><br>
*
* Can be accessed in
* {@link DhApi.Delayed#worldProxy} -> {@link IDhApiLevelWrapper}.
*
* @see IDhApiCustomRenderObjectFactory
* @see IDhApiRenderableBoxGroup
* @see IDhApiWorldProxy
* @see IDhApiLevelWrapper
*
* @author James Seibel
* @version 2024-7-3
* @since API 3.0.0
*/
public interface IDhApiCustomRenderRegister
{
void add(IDhApiRenderableBoxGroup cubeGroup) throws IllegalArgumentException;
IDhApiRenderableBoxGroup remove(long id);
}
@@ -0,0 +1,98 @@
package com.seibel.distanthorizons.api.interfaces.render;
import com.seibel.distanthorizons.api.enums.config.EDhApiLodShading;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import java.util.List;
import java.util.function.Consumer;
/**
* A list of {@link DhApiRenderableBox}'s that
* can be rendered to DH's terrain pass.
*
* @see DhApiRenderableBox
*
* @author James Seibel
* @version 2024-6-30
* @since API 3.0.0
*/
public interface IDhApiRenderableBoxGroup extends List<DhApiRenderableBox>
{
/**
* A unique numerical ID used by DH during rendering.
* This can also be used to bind/unbind specific {@link IDhApiRenderableBoxGroup}'s from the renderer.
* @return the ID for this specific group
*/
long getId();
/**
* Used to determine which mods have added what to the DH renderer.
* This can be used both by the F3 pie chart so you as a mod developer can profile your code
* or by shader developers who want to render your objects differently. <br><br>
*
* Should be used the same as a vanilla Minecraft ResourceLocation.
* For example if your mod named "Heavy Thunder" adds additional clouds named "Storm Front",
* your Resource Location would be something like "HeavyThunder:StormFront"
* and this method would return "HeavyThunder".
*/
String getResourceLocationNamespace();
/**
* Used to determine what type of object mods have added what to the DH renderer.
* This can be used both by the F3 pie chart so you as a mod developer can profile your code
* or by shader developers who want to render your objects differently. <br><br>
*
* Should be used the same as a vanilla Minecraft ResourceLocation.
* For example if your mod named "Heavy Thunder" adds additional clouds named "Storm Front",
* your Resource Location would be something like "HeavyThunder:StormFront"
* and this method would return "StormFront".
*/
String getResourceLocationPath();
/** Sets whether this group should render or not. */
void setActive(boolean active);
/** @return if active this group will render. */
boolean isActive();
/** Sets whether this group should render with Screen Space Ambient Occlusioning. */
void setSsaoEnabled(boolean ssaoEnabled);
/** @return if active this group will render with Screen Space Ambient Occlusioning. */
boolean isSsaoEnabled();
/** Sets where this group will render in the level. */
void setOriginBlockPos(DhApiVec3d pos);
/** @return the block position in the level that all {@see DhApiRenderableBox} will render relative to. */
DhApiVec3d getOriginBlockPos();
/**
* Called right before this group is rendered. <br>
* This is a good place to change the origin or notify of any box changes.
*/
void setPreRenderFunc(Consumer<DhApiRenderParam> renderEventParam);
void setPostRenderFunc(Consumer<DhApiRenderParam> renderEventParam); // TODO name?
/**
* If a cube's color, position, or other property is changed this method
* must be called for those changes to render. <br><br>
*
* Note: changing the group's position via {@link #setOriginBlockPos} doesn't
* require calling this method.
*/
void triggerBoxChange();
/** Only accepts values between 0 and 15 */
void setSkyLight(int skyLight);
int getSkyLight();
/** Only accepts values between 0 and 15 */
void setBlockLight(int blockLight);
int getBlockLight();
void setShading(DhApiRenderableBoxGroupShading shading);
DhApiRenderableBoxGroupShading getShading();
}
@@ -0,0 +1,18 @@
package com.seibel.distanthorizons.api.interfaces.util;
/**
* Used for objects that need deep clones. <br>
* Replacement for {@link Cloneable}.
*
* @see Cloneable
*
* @author James Seibel
* @version 2024-7-12
* @since API 3.0.0
*/
public interface IDhApiCopyable
{
/** Returns a deep clone of all parameters whenever possible. */
IDhApiCopyable copy();
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.api.interfaces.world;
import com.seibel.distanthorizons.api.interfaces.IDhApiUnsafeWrapper;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiLevelType;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
/**
* Can be either a Server or Client level.<br>
@@ -43,7 +44,7 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
boolean hasSkyLight();
/** Returns the max block height of the level(?) */
int getHeight();
int getMaxHeight();
/**
* Returns the lowest possible block position for the level. <br>
@@ -51,4 +52,10 @@ public interface IDhApiLevelWrapper extends IDhApiUnsafeWrapper
*/
default int getMinHeight() { return 0; }
/**
* Will return null if called on the server,
* or if called before the renderer has been set up.
*/
IDhApiCustomRenderRegister getRenderRegister();
}
@@ -26,17 +26,20 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
/**
* Fired after Distant Horizons finishes rendering a frame. <br>
* At this point DH will have also finished cleaning up any modifications it
* did to the OpenGL state, so the state should be back to Minecraft's defaults.
* did to the OpenGL state, so the state should be back to Minecraft's defaults. <br><br>
*
* Note: as of API v 3.0.0 no {@link DhApiRenderParam} is included in this event
* because the specific parameters may change
* depending on whether deferred rendering is enabled or not.
*
* @author James Seibel
* @version 2024-1-31
* @see DhApiRenderParam
* @version 2024-7-14
* @since API 1.0.0
*/
public abstract class DhApiAfterRenderEvent implements IDhApiEvent<DhApiRenderParam>
public abstract class DhApiAfterRenderEvent implements IDhApiEvent<Void>
{
/** Fired after Distant Horizons finishes rendering fake chunks. */
public abstract void afterRender(DhApiEventParam<DhApiRenderParam> event);
public abstract void afterRender(DhApiEventParam<Void> event);
//=========================//
@@ -44,6 +47,6 @@ public abstract class DhApiAfterRenderEvent implements IDhApiEvent<DhApiRenderPa
//=========================//
@Override
public final void fireEvent(DhApiEventParam<DhApiRenderParam> event) { this.afterRender(event); }
public final void fireEvent(DhApiEventParam<Void> event) { this.afterRender(event); }
}
@@ -20,9 +20,10 @@
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
/**
* Called before Distant Horizons starts rendering a buffer. <br>
@@ -52,20 +53,29 @@ public abstract class DhApiBeforeBufferRenderEvent implements IDhApiEvent<DhApiB
// parameter object //
//==================//
public static class EventParam extends DhApiRenderParam
public static class EventParam extends DhApiRenderParam implements IDhApiEventParam
{
/**
* Measured in blocks.
* Should be applied to the model view matrix to move the buffer into its proper place.
*/
public final Vec3f modelPos;
public final DhApiVec3f modelPos;
public EventParam(DhApiRenderParam parent, Vec3f modelPos)
public EventParam(DhApiRenderParam parent, DhApiVec3f modelPos)
{
super(parent);
this.modelPos = modelPos;
}
@Override
public EventParam copy()
{
return new EventParam(
this, this.modelPos.copy()
);
}
}
}
@@ -0,0 +1,97 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiCancelableEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
/**
* Called before Distant Horizons starts rendering a generic object. <br>
* Canceling this event will prevent the triggering {@link IDhApiRenderableBoxGroup} from rendering this frame.
*
* @author James Seibel
* @version 2024-7-11
* @since API 3.0.0
*/
public abstract class DhApiBeforeGenericObjectRenderEvent implements IDhApiCancelableEvent<DhApiBeforeGenericObjectRenderEvent.EventParam>
{
/** Fired before Distant Horizons renders a generic object. */
public abstract void beforeRender(DhApiCancelableEventParam<EventParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiCancelableEventParam<EventParam> input) { this.beforeRender(input); }
//==================//
// parameter object //
//==================//
public static class EventParam extends DhApiRenderParam implements IDhApiEventParam
{
public final long boxGroupId;
public final String resourceLocationNamespace;
public final String resourceLocationPath;
public EventParam(
DhApiRenderParam renderParam,
IDhApiRenderableBoxGroup boxGroup
)
{
super(renderParam);
this.boxGroupId = boxGroup.getId();
this.resourceLocationNamespace = boxGroup.getResourceLocationNamespace();
this.resourceLocationPath = boxGroup.getResourceLocationPath();
}
public EventParam(
DhApiRenderParam renderParam,
long boxGroupId, String resourceLocationNamespace, String resourceLocationPath
)
{
super(renderParam);
this.boxGroupId = boxGroupId;
this.resourceLocationNamespace = resourceLocationNamespace;
this.resourceLocationPath = resourceLocationPath;
}
@Override
public EventParam copy()
{
return new EventParam(
this,
this.boxGroupId, this.resourceLocationNamespace, this.resourceLocationPath
);
}
}
}
@@ -0,0 +1,47 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
/**
* Called before Distant Horizons starts the cleanup process done after rendering generic objects. <br>
* This is called after all generic objects have finished rendering.
*
* @author James Seibel
* @version 2024-7-13
* @since API 3.0.0
*/
public abstract class DhApiBeforeGenericRenderCleanupEvent implements IDhApiEvent<DhApiRenderParam>
{
/** Fired before Distant Horizons starts the cleanup process once rendering has finished. */
public abstract void beforeCleanup(DhApiEventParam<DhApiRenderParam> event);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<DhApiRenderParam> event) { this.beforeCleanup(event); }
}
@@ -0,0 +1,50 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
/**
* Called before Distant Horizons has started setting up OpenGL objects for rendering generic objects. <br>
* If you want to modify already bound DH OpenGL objects try using {@link DhApiBeforeGenericObjectRenderEvent}.
*
* @author James Seibel
* @version 2024-7-12
* @since API 3.0.0
*
* @see DhApiBeforeGenericObjectRenderEvent
*/
public abstract class DhApiBeforeGenericRenderSetupEvent implements IDhApiEvent<DhApiRenderParam>
{
/** Fired before Distant Horizons has started setting up OpenGL objects for rendering generic objects. */
public abstract void beforeSetup(DhApiEventParam<DhApiRenderParam> input);
//=========================//
// internal DH API methods //
//=========================//
@Override
public final void fireEvent(DhApiEventParam<DhApiRenderParam> input) { this.beforeSetup(input); }
}
@@ -33,7 +33,7 @@ import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhAp
*/
public abstract class DhApiBeforeRenderCleanupEvent implements IDhApiEvent<DhApiRenderParam>
{
/** Fired before Distant Horizons renders LODs. */
/** Fired before Distant Horizons starts the cleanup process once rendering has finished. */
public abstract void beforeCleanup(DhApiEventParam<DhApiRenderParam> event);
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataRepo;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
@@ -53,7 +54,7 @@ public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkM
// parameter object //
//==================//
public static class EventParam
public static class EventParam implements IDhApiEventParam
{
/** The saved level. */
public final IDhApiLevelWrapper levelWrapper;
@@ -71,6 +72,15 @@ public abstract class DhApiChunkModifiedEvent implements IDhApiEvent<DhApiChunkM
this.chunkZ = chunkZ;
}
@Override
public EventParam copy()
{
return new EventParam(
this.levelWrapper,
this.chunkX, this.chunkZ
);
}
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
@@ -48,7 +49,7 @@ public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<
// parameter object //
//==================//
public static class EventParam
public static class EventParam implements IDhApiEventParam
{
/** Measured in pixels */
public final int previousWidth;
@@ -72,6 +73,16 @@ public abstract class DhApiColorDepthTextureCreatedEvent implements IDhApiEvent<
this.newHeight = newHeight;
}
@Override
public EventParam copy()
{
return new EventParam(
this.previousWidth, this.previousHeight,
this.newWidth, this.newHeight
);
}
}
}
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
@@ -49,13 +50,16 @@ public abstract class DhApiLevelLoadEvent implements IDhApiEvent<DhApiLevelLoadE
// parameter object //
//==================//
public static class EventParam
public static class EventParam implements IDhApiEventParam
{
/** The newly loaded level. */
public final IDhApiLevelWrapper levelWrapper;
public EventParam(IDhApiLevelWrapper newLevelWrapper) { this.levelWrapper = newLevelWrapper; }
@Override
public EventParam copy() { return new EventParam(this.levelWrapper); }
}
}
@@ -19,9 +19,9 @@
package com.seibel.distanthorizons.api.methods.events.abstractEvents;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
/**
@@ -49,13 +49,16 @@ public abstract class DhApiLevelUnloadEvent implements IDhApiEvent<DhApiLevelUnl
// parameter object //
//==================//
public static class EventParam
public static class EventParam implements IDhApiEventParam
{
/** The recently unloaded level. */
public final IDhApiLevelWrapper levelWrapper;
public EventParam(IDhApiLevelWrapper newLevelWrapper) { this.levelWrapper = newLevelWrapper; }
@Override
public EventParam copy() { return new EventParam(this.levelWrapper); }
}
}
@@ -0,0 +1,13 @@
package com.seibel.distanthorizons.api.methods.events.interfaces;
import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
/**
* @author James Seibel
* @version 2024-7-12
* @since API 3.0.0
*/
public interface IDhApiEventParam extends IDhApiCopyable
{
}
@@ -20,7 +20,8 @@
package com.seibel.distanthorizons.api.methods.events.sharedParameterObjects;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
/**
* Contains information relevant to Distant Horizons and Minecraft rendering.
@@ -29,7 +30,7 @@ import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
* @version 2024-1-31
* @since API 1.0.0
*/
public class DhApiRenderParam
public class DhApiRenderParam implements IDhApiEventParam
{
/** Indicates what render pass DH is currently rendering */
public final EDhApiRenderPass renderPass;
@@ -49,14 +50,14 @@ public class DhApiRenderParam
public final float farClipPlane;
/** The projection matrix Minecraft is using to render this frame. */
public final Mat4f mcProjectionMatrix;
public final DhApiMat4f mcProjectionMatrix;
/** The model view matrix Minecraft is using to render this frame. */
public final Mat4f mcModelViewMatrix;
public final DhApiMat4f mcModelViewMatrix;
/** The projection matrix Distant Horizons is using to render this frame. */
public final Mat4f dhProjectionMatrix;
public final DhApiMat4f dhProjectionMatrix;
/** The model view matrix Distant Horizons is using to render this frame. */
public final Mat4f dhModelViewMatrix;
public final DhApiMat4f dhModelViewMatrix;
public final int worldYOffset;
@@ -66,24 +67,24 @@ public class DhApiRenderParam
// constructors //
//==============//
public DhApiRenderParam(DhApiRenderParam parent)
{
this(
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix, parent.mcModelViewMatrix,
parent.dhProjectionMatrix, parent.dhModelViewMatrix,
parent.worldYOffset
);
parent.renderPass,
parent.partialTicks,
parent.nearClipPlane, parent.farClipPlane,
parent.mcProjectionMatrix.copy(), parent.mcModelViewMatrix.copy(),
parent.dhProjectionMatrix.copy(), parent.dhModelViewMatrix.copy(),
parent.worldYOffset
);
}
public DhApiRenderParam(
EDhApiRenderPass renderPass,
float newPartialTicks,
float nearClipPlane, float farClipPlane,
Mat4f newMcProjectionMatrix, Mat4f newMcModelViewMatrix,
Mat4f newDhProjectionMatrix, Mat4f newDhModelViewMatrix,
DhApiMat4f newMcProjectionMatrix, DhApiMat4f newMcModelViewMatrix,
DhApiMat4f newDhProjectionMatrix, DhApiMat4f newDhModelViewMatrix,
int worldYOffset
)
{
@@ -104,4 +105,16 @@ public class DhApiRenderParam
}
//================//
// base overrides //
//================//
@Override
public DhApiRenderParam copy()
{
return new DhApiRenderParam(this);
}
}
@@ -29,7 +29,7 @@ import java.util.List;
* Contains a list of {@link DhApiTerrainDataPoint} representing the blocks in a Minecraft chunk.
*
* @author Builderb0y, James Seibel
* @version 2023-12-21
* @version 2024-7-21
* @since API 2.0.0
*
* @see IDhApiWrapperFactory
@@ -41,8 +41,8 @@ public class DhApiChunk
public final int chunkPosX;
public final int chunkPosZ;
public final int topYBlockPos;
public final int bottomYBlockPos;
public final int topYBlockPos;
private final List<List<DhApiTerrainDataPoint>> dataPoints;
@@ -52,12 +52,12 @@ public class DhApiChunk
// constructors //
//==============//
public DhApiChunk(int chunkPosX, int chunkPosZ, int topYBlockPos, int bottomYBlockPos)
public DhApiChunk(int chunkPosX, int chunkPosZ, int bottomYBlockPos, int topYBlockPos)
{
this.chunkPosX = chunkPosX;
this.chunkPosZ = chunkPosZ;
this.topYBlockPos = topYBlockPos;
this.bottomYBlockPos = bottomYBlockPos;
this.topYBlockPos = topYBlockPos;
// populate the array to prevent null pointers
this.dataPoints = new ArrayList<>(16 * 16); // 256
@@ -20,7 +20,6 @@
package com.seibel.distanthorizons.api.objects.data;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
/**
* Holds a single datapoint of terrain data
@@ -47,7 +46,7 @@ public class DhApiRaycastResult
public DhApiRaycastResult(DhApiTerrainDataPoint dataPoint, Vec3i blockPos)
public DhApiRaycastResult(DhApiTerrainDataPoint dataPoint, DhApiVec3i blockPos)
{
this.dataPoint = dataPoint;
this.pos = blockPos;
@@ -26,7 +26,7 @@ import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
* Holds a single datapoint of terrain data.
*
* @author James Seibel
* @version 2022-11-13
* @version 2024-7-20
* @since API 1.0.0
*/
public class DhApiTerrainDataPoint
@@ -42,22 +42,22 @@ public class DhApiTerrainDataPoint
public final int blockLightLevel;
public final int skyLightLevel;
public final int topYBlockPos;
public final int bottomYBlockPos;
public final int topYBlockPos;
public final IDhApiBlockStateWrapper blockStateWrapper;
public final IDhApiBiomeWrapper biomeWrapper;
public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int topYBlockPos, int bottomYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
public DhApiTerrainDataPoint(byte detailLevel, int blockLightLevel, int skyLightLevel, int bottomYBlockPos, int topYBlockPos, IDhApiBlockStateWrapper blockStateWrapper, IDhApiBiomeWrapper biomeWrapper)
{
this.detailLevel = detailLevel;
this.blockLightLevel = blockLightLevel;
this.skyLightLevel = skyLightLevel;
this.topYBlockPos = topYBlockPos;
this.bottomYBlockPos = bottomYBlockPos;
this.topYBlockPos = topYBlockPos;
this.blockStateWrapper = blockStateWrapper;
this.biomeWrapper = biomeWrapper;
@@ -17,46 +17,55 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.coreapi.util.math;
package com.seibel.distanthorizons.api.objects.math;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
import java.nio.FloatBuffer;
import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
/**
* An (almost) exact copy of Minecraft's 1.16.5
* implementation of a 4x4 float matrix.
* implementation of a 4x4 float matrix. <br><br>
*
* <code>
* m00, m10, m20, m30, <br>
* m01, m11, m21, m31, <br>
* m02, m12, m22, m32, <br>
* m03, m13, m23, m33 <br>
* </code>
*
* @author James Seibel
* @version 11-11-2021
* @version 2024-6-30
*/
public class Mat4f
public class DhApiMat4f implements IDhApiCopyable
{
private float m00;
private float m01;
private float m02;
private float m03;
private float m10;
private float m11;
private float m12;
private float m13;
private float m20;
private float m21;
private float m22;
private float m23;
private float m30;
private float m31;
private float m32;
private float m33;
public float m00;
public float m01;
public float m02;
public float m03;
public float m10;
public float m11;
public float m12;
public float m13;
public float m20;
public float m21;
public float m22;
public float m23;
public float m30;
public float m31;
public float m32;
public float m33;
public Mat4f()
{
}
public Mat4f(Mat4f sourceMatrix)
//==============//
// constructors //
//==============//
public DhApiMat4f() { /* all values are 0 */ }
public DhApiMat4f(DhApiMat4f sourceMatrix)
{
this.m00 = sourceMatrix.m00;
this.m01 = sourceMatrix.m01;
@@ -76,163 +85,36 @@ public class Mat4f
this.m33 = sourceMatrix.m33;
}
public Mat4f(Matrix4fc sourceMatrix) { this(convertJomlMatrixToArray(sourceMatrix)); }
private static float[] convertJomlMatrixToArray(Matrix4fc sourceMatrix)
/** Expects the values of the input array to be in row major order (AKA rows then columns) */
public DhApiMat4f(float[] values)
{
FloatBuffer buffer = FloatBuffer.allocate(16);
m00 = values[0];
m01 = values[1];
m02 = values[2];
m03 = values[3];
buffer.put(bufferIndex(0, 0), sourceMatrix.m00());
buffer.put(bufferIndex(0, 1), sourceMatrix.m01());
buffer.put(bufferIndex(0, 2), sourceMatrix.m02());
buffer.put(bufferIndex(0, 3), sourceMatrix.m03());
buffer.put(bufferIndex(1, 0), sourceMatrix.m10());
buffer.put(bufferIndex(1, 1), sourceMatrix.m11());
buffer.put(bufferIndex(1, 2), sourceMatrix.m12());
buffer.put(bufferIndex(1, 3), sourceMatrix.m13());
buffer.put(bufferIndex(2, 0), sourceMatrix.m20());
buffer.put(bufferIndex(2, 1), sourceMatrix.m21());
buffer.put(bufferIndex(2, 2), sourceMatrix.m22());
buffer.put(bufferIndex(2, 3), sourceMatrix.m23());
buffer.put(bufferIndex(3, 0), sourceMatrix.m30());
buffer.put(bufferIndex(3, 1), sourceMatrix.m31());
buffer.put(bufferIndex(3, 2), sourceMatrix.m32());
buffer.put(bufferIndex(3, 3), sourceMatrix.m33());
m10 = values[4];
m11 = values[5];
m12 = values[6];
m13 = values[7];
return buffer.array();
}
/* Quaternions are not currently needed/implemented
public Matrix4float(Quaternion p_i48104_1_)
{
float f = p_i48104_1_.i();
float f1 = p_i48104_1_.j();
float f2 = p_i48104_1_.k();
float f3 = p_i48104_1_.r();
float f4 = 2.0F * f * f;
float f5 = 2.0F * f1 * f1;
float f6 = 2.0F * f2 * f2;
this.m00 = 1.0F - f5 - f6;
this.m11 = 1.0F - f6 - f4;
this.m22 = 1.0F - f4 - f5;
this.m33 = 1.0F;
float f7 = f * f1;
float f8 = f1 * f2;
float f9 = f2 * f;
float f10 = f * f3;
float f11 = f1 * f3;
float f12 = f2 * f3;
this.m10 = 2.0F * (f7 + f12);
this.m01 = 2.0F * (f7 - f12);
this.m20 = 2.0F * (f9 - f11);
this.m02 = 2.0F * (f9 + f11);
this.m21 = 2.0F * (f8 + f10);
this.m12 = 2.0F * (f8 - f10);
}
*/
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj != null && this.getClass() == obj.getClass())
{
Mat4f otherMatrix = (Mat4f) obj;
return Float.compare(otherMatrix.m00, this.m00) == 0
&& Float.compare(otherMatrix.m01, this.m01) == 0
&& Float.compare(otherMatrix.m02, this.m02) == 0
&& Float.compare(otherMatrix.m03, this.m03) == 0
&& Float.compare(otherMatrix.m10, this.m10) == 0
&& Float.compare(otherMatrix.m11, this.m11) == 0
&& Float.compare(otherMatrix.m12, this.m12) == 0
&& Float.compare(otherMatrix.m13, this.m13) == 0
&& Float.compare(otherMatrix.m20, this.m20) == 0
&& Float.compare(otherMatrix.m21, this.m21) == 0
&& Float.compare(otherMatrix.m22, this.m22) == 0
&& Float.compare(otherMatrix.m23, this.m23) == 0
&& Float.compare(otherMatrix.m30, this.m30) == 0
&& Float.compare(otherMatrix.m31, this.m31) == 0
&& Float.compare(otherMatrix.m32, this.m32) == 0
&& Float.compare(otherMatrix.m33, this.m33) == 0;
}
else
{
return false;
}
}
@Override
public int hashCode()
{
int i = this.m00 != 0.0F ? Float.floatToIntBits(this.m00) : 0;
i = 31 * i + (this.m01 != 0.0F ? Float.floatToIntBits(this.m01) : 0);
i = 31 * i + (this.m02 != 0.0F ? Float.floatToIntBits(this.m02) : 0);
i = 31 * i + (this.m03 != 0.0F ? Float.floatToIntBits(this.m03) : 0);
i = 31 * i + (this.m10 != 0.0F ? Float.floatToIntBits(this.m10) : 0);
i = 31 * i + (this.m11 != 0.0F ? Float.floatToIntBits(this.m11) : 0);
i = 31 * i + (this.m12 != 0.0F ? Float.floatToIntBits(this.m12) : 0);
i = 31 * i + (this.m13 != 0.0F ? Float.floatToIntBits(this.m13) : 0);
i = 31 * i + (this.m20 != 0.0F ? Float.floatToIntBits(this.m20) : 0);
i = 31 * i + (this.m21 != 0.0F ? Float.floatToIntBits(this.m21) : 0);
i = 31 * i + (this.m22 != 0.0F ? Float.floatToIntBits(this.m22) : 0);
i = 31 * i + (this.m23 != 0.0F ? Float.floatToIntBits(this.m23) : 0);
i = 31 * i + (this.m30 != 0.0F ? Float.floatToIntBits(this.m30) : 0);
i = 31 * i + (this.m31 != 0.0F ? Float.floatToIntBits(this.m31) : 0);
i = 31 * i + (this.m32 != 0.0F ? Float.floatToIntBits(this.m32) : 0);
return 31 * i + (this.m33 != 0.0F ? Float.floatToIntBits(this.m33) : 0);
m20 = values[8];
m21 = values[9];
m22 = values[10];
m23 = values[11];
m30 = values[12];
m31 = values[13];
m32 = values[14];
m33 = values[15];
}
@Override
public String toString()
{
return "Matrix4f:\n" +
this.m00 + " " + this.m01 + " " + this.m02 + " " + this.m03 + "\n" +
this.m10 + " " + this.m11 + " " + this.m12 + " " + this.m13 + "\n" +
this.m20 + " " + this.m21 + " " + this.m22 + " " + this.m23 + "\n" +
this.m30 + " " + this.m31 + " " + this.m32 + " " + this.m33 + "\n";
}
public void store(FloatBuffer floatBuffer)
{
floatBuffer.put(bufferIndex(0, 0), this.m00);
floatBuffer.put(bufferIndex(0, 1), this.m01);
floatBuffer.put(bufferIndex(0, 2), this.m02);
floatBuffer.put(bufferIndex(0, 3), this.m03);
floatBuffer.put(bufferIndex(1, 0), this.m10);
floatBuffer.put(bufferIndex(1, 1), this.m11);
floatBuffer.put(bufferIndex(1, 2), this.m12);
floatBuffer.put(bufferIndex(1, 3), this.m13);
floatBuffer.put(bufferIndex(2, 0), this.m20);
floatBuffer.put(bufferIndex(2, 1), this.m21);
floatBuffer.put(bufferIndex(2, 2), this.m22);
floatBuffer.put(bufferIndex(2, 3), this.m23);
floatBuffer.put(bufferIndex(3, 0), this.m30);
floatBuffer.put(bufferIndex(3, 1), this.m31);
floatBuffer.put(bufferIndex(3, 2), this.m32);
floatBuffer.put(bufferIndex(3, 3), this.m33);
}
public Matrix4f createJomlMatrix()
{
return new Matrix4f(
this.m00, this.m10, this.m20, this.m30,
this.m01, this.m11, this.m21, this.m31,
this.m02, this.m12, this.m22, this.m32,
this.m03, this.m13, this.m23, this.m33
);
}
private static int bufferIndex(int xIndex, int zIndex)
{
return (zIndex * 4) + xIndex;
}
//=========//
// methods //
//=========//
public void setIdentity()
{
@@ -341,7 +223,7 @@ public class Mat4f
}
}
public void multiply(Mat4f multMatrix)
public void multiply(DhApiMat4f multMatrix)
{
float f = this.m00 * multMatrix.m00 + this.m01 * multMatrix.m10 + this.m02 * multMatrix.m20 + this.m03 * multMatrix.m30;
float f1 = this.m00 * multMatrix.m01 + this.m01 * multMatrix.m11 + this.m02 * multMatrix.m21 + this.m03 * multMatrix.m31;
@@ -377,13 +259,6 @@ public class Mat4f
this.m33 = f15;
}
/* Quaternions aren't currently needed/implemented
public void multiply(Quaternion p_226596_1_)
{
this.multiply(new Matrix4f(p_226596_1_));
}
*/
public void multiply(float scalar)
{
this.m00 *= scalar;
@@ -404,81 +279,6 @@ public class Mat4f
this.m33 *= scalar;
}
public static Mat4f perspective(double fov, float widthHeightRatio, float nearClipPlane, float farClipPlane)
{
float f = (float) (1.0D / Math.tan(fov * ((float) Math.PI / 180F) / 2.0D));
Mat4f matrix = new Mat4f();
matrix.m00 = f / widthHeightRatio;
matrix.m11 = f;
matrix.m22 = (farClipPlane + nearClipPlane) / (nearClipPlane - farClipPlane);
matrix.m32 = -1.0F;
matrix.m23 = 2.0F * farClipPlane * nearClipPlane / (nearClipPlane - farClipPlane);
return matrix;
}
/* not currently needed/implemented
* Also the parameter names should be double checked as they may be incorrect
public static Matrix4Float orthographic(float left, float right, float top, float bottom)
{
Matrix4Float matrix4f = new Matrix4Float();
matrix4f.m00 = 2.0F / left;
matrix4f.m11 = 2.0F / right;
float f = bottom - top;
matrix4f.m22 = -2.0F / f;
matrix4f.m33 = 1.0F;
matrix4f.m03 = -1.0F;
matrix4f.m13 = -1.0F;
matrix4f.m23 = -(bottom + top) / f;
return matrix4f;
}
*/
/**
* TODO: what kind of translation is this?
* and how is this different from "multiplyTranslationMatrix"?
* Answer: This is faster and direct (but only if this is pure translation matrix without rotate)
*/
public void translate(Vec3f vec)
{
this.m03 += vec.x;
this.m13 += vec.y;
this.m23 += vec.z;
}
/** originally "translate" from Minecraft's MatrixStack */
public void multiplyTranslationMatrix(double x, double y, double z)
{
multiply(createTranslateMatrix((float) x, (float) y, (float) z));
}
public Mat4f copy()
{
return new Mat4f(this);
}
public static Mat4f createScaleMatrix(float x, float y, float z)
{
Mat4f matrix = new Mat4f();
matrix.m00 = x;
matrix.m11 = y;
matrix.m22 = z;
matrix.m33 = 1.0F;
return matrix;
}
public static Mat4f createTranslateMatrix(float x, float y, float z)
{
Mat4f matrix = new Mat4f();
matrix.m00 = 1.0F;
matrix.m11 = 1.0F;
matrix.m22 = 1.0F;
matrix.m33 = 1.0F;
matrix.m03 = x;
matrix.m13 = y;
matrix.m23 = z;
return matrix;
}
@@ -487,6 +287,8 @@ public class Mat4f
// methods //
//==================//
private static int getArrayIndex(int xIndex, int zIndex) { return (zIndex * 4) + xIndex; }
/** Returns the values of this matrix in row major order (AKA rows then columns) */
public float[] getValuesAsArray()
{
@@ -513,113 +315,77 @@ public class Mat4f
};
}
public Vec3f asNonNormalizedLookForwardVector()
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
{
return new Vec3f(this.m02, this.m12, this.m22);
if (this == obj)
{
return true;
}
else if (obj != null && this.getClass() == obj.getClass())
{
DhApiMat4f otherMatrix = (DhApiMat4f) obj;
return Float.compare(otherMatrix.m00, this.m00) == 0
&& Float.compare(otherMatrix.m01, this.m01) == 0
&& Float.compare(otherMatrix.m02, this.m02) == 0
&& Float.compare(otherMatrix.m03, this.m03) == 0
&& Float.compare(otherMatrix.m10, this.m10) == 0
&& Float.compare(otherMatrix.m11, this.m11) == 0
&& Float.compare(otherMatrix.m12, this.m12) == 0
&& Float.compare(otherMatrix.m13, this.m13) == 0
&& Float.compare(otherMatrix.m20, this.m20) == 0
&& Float.compare(otherMatrix.m21, this.m21) == 0
&& Float.compare(otherMatrix.m22, this.m22) == 0
&& Float.compare(otherMatrix.m23, this.m23) == 0
&& Float.compare(otherMatrix.m30, this.m30) == 0
&& Float.compare(otherMatrix.m31, this.m31) == 0
&& Float.compare(otherMatrix.m32, this.m32) == 0
&& Float.compare(otherMatrix.m33, this.m33) == 0;
}
else
{
return false;
}
}
//===============//
// Forge methods //
//===============//
public Mat4f(float[] values)
@Override
public int hashCode()
{
m00 = values[0];
m01 = values[1];
m02 = values[2];
m03 = values[3];
m10 = values[4];
m11 = values[5];
m12 = values[6];
m13 = values[7];
m20 = values[8];
m21 = values[9];
m22 = values[10];
m23 = values[11];
m30 = values[12];
m31 = values[13];
m32 = values[14];
m33 = values[15];
int i = this.m00 != 0.0F ? Float.floatToIntBits(this.m00) : 0;
i = 31 * i + (this.m01 != 0.0F ? Float.floatToIntBits(this.m01) : 0);
i = 31 * i + (this.m02 != 0.0F ? Float.floatToIntBits(this.m02) : 0);
i = 31 * i + (this.m03 != 0.0F ? Float.floatToIntBits(this.m03) : 0);
i = 31 * i + (this.m10 != 0.0F ? Float.floatToIntBits(this.m10) : 0);
i = 31 * i + (this.m11 != 0.0F ? Float.floatToIntBits(this.m11) : 0);
i = 31 * i + (this.m12 != 0.0F ? Float.floatToIntBits(this.m12) : 0);
i = 31 * i + (this.m13 != 0.0F ? Float.floatToIntBits(this.m13) : 0);
i = 31 * i + (this.m20 != 0.0F ? Float.floatToIntBits(this.m20) : 0);
i = 31 * i + (this.m21 != 0.0F ? Float.floatToIntBits(this.m21) : 0);
i = 31 * i + (this.m22 != 0.0F ? Float.floatToIntBits(this.m22) : 0);
i = 31 * i + (this.m23 != 0.0F ? Float.floatToIntBits(this.m23) : 0);
i = 31 * i + (this.m30 != 0.0F ? Float.floatToIntBits(this.m30) : 0);
i = 31 * i + (this.m31 != 0.0F ? Float.floatToIntBits(this.m31) : 0);
i = 31 * i + (this.m32 != 0.0F ? Float.floatToIntBits(this.m32) : 0);
return 31 * i + (this.m33 != 0.0F ? Float.floatToIntBits(this.m33) : 0);
}
public Mat4f(FloatBuffer buffer)
@Override
public String toString()
{
this(buffer.array());
return "Matrix4f:\n" +
this.m00 + " " + this.m01 + " " + this.m02 + " " + this.m03 + "\n" +
this.m10 + " " + this.m11 + " " + this.m12 + " " + this.m13 + "\n" +
this.m20 + " " + this.m21 + " " + this.m22 + " " + this.m23 + "\n" +
this.m30 + " " + this.m31 + " " + this.m32 + " " + this.m33 + "\n";
}
public void set(Mat4f mat)
{
this.m00 = mat.m00;
this.m01 = mat.m01;
this.m02 = mat.m02;
this.m03 = mat.m03;
this.m10 = mat.m10;
this.m11 = mat.m11;
this.m12 = mat.m12;
this.m13 = mat.m13;
this.m20 = mat.m20;
this.m21 = mat.m21;
this.m22 = mat.m22;
this.m23 = mat.m23;
this.m30 = mat.m30;
this.m31 = mat.m31;
this.m32 = mat.m32;
this.m33 = mat.m33;
}
public void add(Mat4f other)
{
m00 += other.m00;
m01 += other.m01;
m02 += other.m02;
m03 += other.m03;
m10 += other.m10;
m11 += other.m11;
m12 += other.m12;
m13 += other.m13;
m20 += other.m20;
m21 += other.m21;
m22 += other.m22;
m23 += other.m23;
m30 += other.m30;
m31 += other.m31;
m32 += other.m32;
m33 += other.m33;
}
public void multiplyBackward(Mat4f other)
{
Mat4f copy = other.copy();
copy.multiply(this);
this.set(copy);
}
public void setTranslation(float x, float y, float z)
{
this.m00 = 1.0F;
this.m11 = 1.0F;
this.m22 = 1.0F;
this.m33 = 1.0F;
this.m03 = x;
this.m13 = y;
this.m23 = z;
}
/**
* Changes the values that store the clipping planes.
* Formula for calculating matrix values is the same that OpenGL uses when making matrices.
*
* @param nearClip New near clipping plane value.
* @param farClip New far clipping plane value.
*/
public void setClipPlanes(float nearClip, float farClip)
{
//convert to matrix values, formula copied from a textbook / openGL specification.
float matNearClip = -((farClip + nearClip) / (farClip - nearClip));
float matFarClip = -((2 * farClip * nearClip) / (farClip - nearClip));
//set new values for the clip planes.
this.m22 = matNearClip;
this.m23 = matFarClip;
}
@Override
public DhApiMat4f copy() { return new DhApiMat4f(this); }
}
@@ -0,0 +1,94 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.objects.math;
/**
* Often used to store block positions or any other
* position in 3D space.
*
* @author James Seibel
* @version 2024-7-9
* @since API 3.0.0
*/
public class DhApiVec3d
{
public double x;
public double y;
public double z;
/** creates a Vec3 at (0,0,0) */
public DhApiVec3d()
{
this.x = 0;
this.y = 0;
this.z = 0;
}
public DhApiVec3d(double x, double y, double z)
{
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj != null && this.getClass() == obj.getClass())
{
DhApiVec3d Vec3d = (DhApiVec3d) obj;
if (Double.compare(Vec3d.x, this.x) != 0)
{
return false;
}
else if (Double.compare(Vec3d.y, this.y) != 0)
{
return false;
}
else
{
return Double.compare(Vec3d.z, this.z) == 0;
}
}
else
{
return false;
}
}
@Override
public int hashCode()
{
long i = Double.doubleToLongBits(this.x);
i = 31 * i + Double.doubleToLongBits(this.y);
i = 31 * i + Double.doubleToLongBits(this.z);
return Long.hashCode(i);
}
@Override
public String toString() { return "[" + this.x + ", " + this.y + ", " + this.z + "]"; }
}
@@ -0,0 +1,98 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.api.objects.math;
import com.seibel.distanthorizons.api.interfaces.util.IDhApiCopyable;
/**
* Often used to store block positions or any other
* position in 3D space.
*
* @author James Seibel
* @version 2024-6-3
* @since API 2.2.0
*/
public class DhApiVec3f implements IDhApiCopyable
{
public float x;
public float y;
public float z;
/** creates a Vec3 at (0,0,0) */
public DhApiVec3f()
{
this.x = 0;
this.y = 0;
this.z = 0;
}
public DhApiVec3f(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj != null && this.getClass() == obj.getClass())
{
DhApiVec3f Vec3f = (DhApiVec3f) obj;
if (Float.compare(Vec3f.x, this.x) != 0)
{
return false;
}
else if (Float.compare(Vec3f.y, this.y) != 0)
{
return false;
}
else
{
return Float.compare(Vec3f.z, this.z) == 0;
}
}
else
{
return false;
}
}
@Override
public int hashCode()
{
int i = Float.floatToIntBits(this.x);
i = 31 * i + Float.floatToIntBits(this.y);
return 31 * i + Float.floatToIntBits(this.z);
}
@Override
public String toString() { return "[" + this.x + ", " + this.y + ", " + this.z + "]"; }
@Override
public DhApiVec3f copy() { return new DhApiVec3f(this.x, this.y, this.z); }
}
@@ -0,0 +1,52 @@
package com.seibel.distanthorizons.api.objects.render;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import java.awt.*;
/**
* @see IDhApiRenderableBoxGroup
*
* @author James Seibel
* @version 2024-6-30
* @since API 3.0.0
*/
public class DhApiRenderableBox
{
/** the position closest to (-inf,-inf) */
public DhApiVec3d minPos;
/** the position closest to (+inf,+inf) */
public DhApiVec3d maxPos;
public Color color;
public byte material;
//==============//
// constructors //
//==============//
public DhApiRenderableBox(DhApiVec3d minPos, float width, Color color, EDhApiBlockMaterial material)
{
this(minPos, new DhApiVec3d(
minPos.x + width,
minPos.y + width,
minPos.z + width
), color, material);
}
public DhApiRenderableBox(DhApiVec3d minPos, DhApiVec3d maxPos, Color color, EDhApiBlockMaterial material)
{
this.minPos = minPos;
this.maxPos = maxPos;
this.color = color;
this.material = material.index;
}
}
@@ -0,0 +1,93 @@
package com.seibel.distanthorizons.api.objects.render;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import java.awt.*;
/**
* @see IDhApiRenderableBoxGroup
*
* Shading values are multiplied against the color for each direction,
* for example: <br>
* A shading value of 1.0 indicates the color is unchanged. <br>
* A shading value of 0.0 changes the color to black. <br>
*
* @author James Seibel
* @version 2024-7-7
* @since API 3.0.0
*/
public class DhApiRenderableBoxGroupShading
{
/** negative X */
public float north = 1.0f;
/** positive X */
public float south = 1.0f;
/** positive X */
public float east = 1.0f;
/** negative X */
public float west = 1.0f;
/** positive Y */
public float top = 1.0f;
/** negative Y */
public float bottom = 1.0f;
//==============//
// constructors //
//==============//
public static DhApiRenderableBoxGroupShading getDefaultShaded()
{
DhApiRenderableBoxGroupShading shading = new DhApiRenderableBoxGroupShading();
shading.setDefaultShaded();
return shading;
}
public static DhApiRenderableBoxGroupShading getUnshaded()
{
DhApiRenderableBoxGroupShading shading = new DhApiRenderableBoxGroupShading();
shading.setUnshaded();
return shading;
}
//=========//
// methods //
//=========//
/**
* Directions will have different brightness similar to Minecraft blocks. <br>
* This is a good default for un-lit objects.
*/
public void setDefaultShaded()
{
this.north = 0.8f;
this.south = 0.8f;
this.east = 0.6f;
this.west = 0.6f;
this.top = 1.0f;
this.bottom = 0.5f;
}
/**
* All directions render with the same brightness. <br>
* This is best used for glowing objects like beacons.
*/
public void setUnshaded()
{
this.north = 1.0f;
this.south = 1.0f;
this.east = 1.0f;
this.west = 1.0f;
this.top = 1.0f;
this.bottom = 1.0f;
}
}
@@ -22,6 +22,7 @@ package com.seibel.distanthorizons.coreapi.DependencyInjection;
import com.seibel.distanthorizons.api.interfaces.events.IDhApiEventInjector;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiCancelableEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEvent;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiEventParam;
import com.seibel.distanthorizons.api.methods.events.interfaces.IDhApiOneTimeEvent;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiCancelableEventParam;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiEventParam;
@@ -140,7 +141,26 @@ public class ApiEventInjector extends DependencyInjector<IDhApiEvent> implements
// fire each event and record if any of them
// request to cancel the event.
DhApiEventParam<T> eventParam = createEventParamWrapper(event, eventInput);
// attempt to clone the event input if possible
// this is done to reduce the likely hood that one event listener
// will make change the event parameter for other listeners
T input = eventInput;
if (eventInput instanceof IDhApiEventParam)
{
try
{
//noinspection unchecked
input = (T) ((IDhApiEventParam) eventInput).copy();
}
catch (Exception e)
{
LOGGER.error("Unable to clone event parameter ["+eventInput.getClass().getSimpleName()+"], error: ["+e.getMessage()+"].", e);
}
}
DhApiEventParam<T> eventParam = createEventParamWrapper(event, input);
event.fireEvent(eventParam);
if (eventParam instanceof DhApiCancelableEventParam)
@@ -47,9 +47,9 @@ public final class ModInfo
public static boolean IS_DEV_BUILD = VERSION.toLowerCase().contains("dev");
/** This version should only be updated when breaking changes are introduced to the DH API */
public static final int API_MAJOR_VERSION = 2;
public static final int API_MAJOR_VERSION = 3;
/** This version should be updated whenever new methods are added to the DH API */
public static final int API_MINOR_VERSION = 1;
public static final int API_MINOR_VERSION = 0;
/** This version should be updated whenever non-breaking fixes are added to the DH API */
public static final int API_PATH_VERSION = 0;
@@ -67,10 +67,11 @@ public interface IConfigEntry<T>
* Checks if the option is valid
*
* 0 == valid
* 2 == invalid
* 1 == number too high
* -1 == number too low
*/
byte isValid();
byte isValid(); // TODO replace with an enum
/** Checks if a value is valid */
byte isValid(T value);
@@ -1,263 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.coreapi.util.math;
import com.seibel.distanthorizons.coreapi.util.MathUtil;
/**
* A (almost) exact copy of Minecraft's 1.16.5
* implementation of a 3 element float vector.
*
* @author James Seibel
* @version 11-11-2021
*/
public class Vec3f
{
public static Vec3f XNeg = new Vec3f(-1.0F, 0.0F, 0.0F);
public static Vec3f XPos = new Vec3f(1.0F, 0.0F, 0.0F);
public static Vec3f YNeg = new Vec3f(0.0F, -1.0F, 0.0F);
public static Vec3f YPos = new Vec3f(0.0F, 1.0F, 0.0F);
public static Vec3f ZNeg = new Vec3f(0.0F, 0.0F, -1.0F);
public static Vec3f ZPos = new Vec3f(0.0F, 0.0F, 1.0F);
public float x;
public float y;
public float z;
public Vec3f()
{
}
public Vec3f(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object obj)
{
if (this == obj)
{
return true;
}
else if (obj != null && this.getClass() == obj.getClass())
{
Vec3f Vec3f = (Vec3f) obj;
if (Float.compare(Vec3f.x, this.x) != 0)
{
return false;
}
else if (Float.compare(Vec3f.y, this.y) != 0)
{
return false;
}
else
{
return Float.compare(Vec3f.z, this.z) == 0;
}
}
else
{
return false;
}
}
@Override
public int hashCode()
{
int i = Float.floatToIntBits(this.x);
i = 31 * i + Float.floatToIntBits(this.y);
return 31 * i + Float.floatToIntBits(this.z);
}
public void mul(float scalar)
{
this.x *= scalar;
this.y *= scalar;
this.z *= scalar;
}
public void mul(float x, float y, float z)
{
this.x *= x;
this.y *= y;
this.z *= z;
}
public void clamp(float min, float max)
{
this.x = MathUtil.clamp(min, this.x, max);
this.y = MathUtil.clamp(min, this.y, max);
this.z = MathUtil.clamp(min, this.z, max);
}
public void set(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
}
public void add(float x, float y, float z)
{
this.x += x;
this.y += y;
this.z += z;
}
public void add(Vec3f vector)
{
this.x += vector.x;
this.y += vector.y;
this.z += vector.z;
}
public void subtract(Vec3f vector)
{
this.x -= vector.x;
this.y -= vector.y;
this.z -= vector.z;
}
public float dotProduct(Vec3f vector)
{
return this.x * vector.x + this.y * vector.y + this.z * vector.z;
}
/** Returns true if normalization had to be done */
public boolean normalize()
{
float squaredSum = this.x * this.x + this.y * this.y + this.z * this.z;
if (squaredSum < 1.0E-5D)
{
return false;
}
else
{
float f1 = MathUtil.fastInvSqrt(squaredSum);
this.x *= f1;
this.y *= f1;
this.z *= f1;
return true;
}
}
public void crossProduct(Vec3f vector)
{
float f = this.x;
float f1 = this.y;
float f2 = this.z;
float f3 = vector.x;
float f4 = vector.y;
float f5 = vector.z;
this.x = f1 * f5 - f2 * f4;
this.y = f2 * f3 - f * f5;
this.z = f * f4 - f1 * f3;
}
/* Matrix3f is not currently needed/implemented
public void transform(Matrix3f p_229188_1_)
{
float f = this.x;
float f1 = this.y;
float f2 = this.z;
this.x = p_229188_1_.m00 * f + p_229188_1_.m01 * f1 + p_229188_1_.m02 * f2;
this.y = p_229188_1_.m10 * f + p_229188_1_.m11 * f1 + p_229188_1_.m12 * f2;
this.z = p_229188_1_.m20 * f + p_229188_1_.m21 * f1 + p_229188_1_.m22 * f2;
}
*/
/* Quaternions are not currently needed/implemented
public void transform(Quaternion p_214905_1_)
{
Quaternion quaternion = new Quaternion(p_214905_1_);
quaternion.mul(new Quaternion(this.x(), this.y(), this.z(), 0.0F));
Quaternion quaternion1 = new Quaternion(p_214905_1_);
quaternion1.conj();
quaternion.mul(quaternion1);
this.set(quaternion.i(), quaternion.j(), quaternion.k());
}
*/
/* not currently needed
* percent may actually be partial ticks (which is available when rendering)
public void linearInterp(Vec3f resultingVector, float percent)
{
float f = 1.0F - percent;
this.x = this.x * f + resultingVector.x * percent;
this.y = this.y * f + resultingVector.y * percent;
this.z = this.z * f + resultingVector.z * percent;
}
*/
/* Quaternions are not currently needed/implemented
public Quaternion rotation(float p_229193_1_)
{
return new Quaternion(this, p_229193_1_, false);
}
@OnlyIn(Dist.CLIENT)
public Quaternion rotationDegrees(float p_229187_1_)
{
return new Quaternion(this, p_229187_1_, true);
}
*/
public Vec3f copy()
{
return new Vec3f(this.x, this.y, this.z);
}
/* not currently needed/implemented
public void map(Float2FloatFunction p_229191_1_)
{
this.x = p_229191_1_.get(this.x);
this.y = p_229191_1_.get(this.y);
this.z = p_229191_1_.get(this.z);
}
*/
@Override
public String toString()
{
return "[" + this.x + ", " + this.y + ", " + this.z + "]";
}
// Forge start
public Vec3f(float[] values)
{
set(values);
}
public void set(float[] values)
{
this.x = values[0];
this.y = values[1];
this.z = values[2];
}
}
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.DatabaseUpdater;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -96,6 +97,7 @@ public class Initializer
DhApi.Delayed.terrainRepo = DhApiTerrainDataRepo.INSTANCE;
DhApi.Delayed.worldProxy = DhApiWorldProxy.INSTANCE;
DhApi.Delayed.renderProxy = DhApiRenderProxy.INSTANCE;
DhApi.Delayed.customRenderObjectFactory = GenericRenderObjectFactory.INSTANCE;
DhApi.Delayed.wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (DhApi.Delayed.wrapperFactory == null)
{
@@ -0,0 +1,46 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGenericRenderingConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiNoiseTextureConfig;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.core.config.Config;
public class DhApiGenericRenderingConfig implements IDhApiGenericRenderingConfig
{
public static DhApiGenericRenderingConfig INSTANCE = new DhApiGenericRenderingConfig();
private DhApiGenericRenderingConfig() { }
@Override
public IDhApiConfigValue<Boolean> renderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableRendering); }
@Override
public IDhApiConfigValue<Boolean> beaconRenderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering); }
@Override
public IDhApiConfigValue<Boolean> cloudRenderingEnabled()
{ return new DhApiConfigValue<Boolean, Boolean>(Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering); }
}
@@ -22,10 +22,7 @@ package com.seibel.distanthorizons.core.api.external.methods.config.client;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiTransparency;
import com.seibel.distanthorizons.api.interfaces.config.IDhApiConfigValue;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiAmbientOcclusionConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiFogConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiGraphicsConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.IDhApiNoiseTextureConfig;
import com.seibel.distanthorizons.api.interfaces.config.client.*;
import com.seibel.distanthorizons.api.objects.config.DhApiConfigValue;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.config.Config;
@@ -42,9 +39,14 @@ public class DhApiGraphicsConfig implements IDhApiGraphicsConfig
// inner layers //
//==============//
@Override
public IDhApiFogConfig fog() { return DhApiFogConfig.INSTANCE; }
@Override
public IDhApiAmbientOcclusionConfig ambientOcclusion() { return DhApiAmbientOcclusionConfig.INSTANCE; }
@Override
public IDhApiNoiseTextureConfig noiseTexture() { return DhApiNoiseTextureConfig.INSTANCE; }
@Override
public IDhApiGenericRenderingConfig genericRendering() { return DhApiGenericRenderingConfig.INSTANCE; }
@@ -0,0 +1,87 @@
package com.seibel.distanthorizons.core.api.external.methods.data;
import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import it.unimi.dsi.fastutil.longs.Long2ReferenceOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongSet;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.SoftReference;
public class DhApiTerrainDataCache implements IDhApiTerrainDataCache
{
private final Object modificationLock = new Object();
private Long2ReferenceOpenHashMap<SoftReference<FullDataSourceV2>> posToFullDataRef = new Long2ReferenceOpenHashMap<>();
private static final Logger LOGGER = LogManager.getLogger(DhApiTerrainDataCache.class.getSimpleName());
//==================//
// internal methods //
//==================//
public void add(long pos, FullDataSourceV2 dataSource)
{
synchronized (this.modificationLock)
{
this.posToFullDataRef.put(pos, new SoftReference<>(dataSource));
}
}
@Nullable
public FullDataSourceV2 get(long pos)
{
synchronized (this.modificationLock)
{
SoftReference<FullDataSourceV2> ref = this.posToFullDataRef.get(pos);
if (ref != null)
{
return ref.get();
}
else
{
return null;
}
}
}
//=============//
// API methods //
//=============//
@Override
public void clear()
{
synchronized (this.modificationLock)
{
LongSet keySet = this.posToFullDataRef.keySet();
for (long pos : keySet)
{
SoftReference<FullDataSourceV2> dataRef = this.posToFullDataRef.remove(pos);
if (dataRef != null)
{
FullDataSourceV2 dataSource = dataRef.get();
if (dataSource != null)
{
try
{
dataSource.close();
}
catch (Exception e)
{
LOGGER.warn("Unable to close data source, error: [" + e.getMessage() + "].", e);
}
}
}
}
}
}
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.api.external.methods.data;
import com.seibel.distanthorizons.api.interfaces.data.IDhApiTerrainDataCache;
import com.seibel.distanthorizons.api.interfaces.world.IDhApiLevelWrapper;
import com.seibel.distanthorizons.api.objects.DhApiResult;
import com.seibel.distanthorizons.api.objects.data.DhApiRaycastResult;
@@ -35,6 +36,7 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RayCastUtil;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -43,12 +45,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRen
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3i;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
@@ -56,9 +58,6 @@ import java.util.concurrent.ExecutionException;
/**
* Allows interfacing with the terrain data Distant Horizons has stored.
*
* @author James Seibel
* @version 2022-11-19
*/
public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
{
@@ -68,12 +67,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
// debugging values
private static volatile boolean debugThreadRunning = false;
private static String currentDebugBiomeName = "";
private static int currentDebugBlockColorInt = -1;
private static DhApiTerrainDataCache debugDataCache = new DhApiTerrainDataCache();
private static DhApiVec3i currentDebugVec3i = new Vec3i();
//=============//
// constructor //
//=============//
private DhApiTerrainDataRepo()
{
@@ -86,41 +88,32 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//================//
@Override
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ)
{
return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY);
}
public DhApiResult<DhApiTerrainDataPoint> getSingleDataPointAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosY, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ)
{
return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null);
}
public DhApiResult<DhApiTerrainDataPoint[]> getColumnDataAtBlockPos(IDhApiLevelWrapper levelWrapper, int blockPosX, int blockPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ)
{
return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ));
}
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtChunkPos(IDhApiLevelWrapper levelWrapper, int chunkPosX, int chunkPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.CHUNK_DETAIL_LEVEL, chunkPosX, chunkPosZ), dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ)
{
return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ));
}
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtRegionPos(IDhApiLevelWrapper levelWrapper, int regionPosX, int regionPosZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ), dataCache); }
@Override
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ)
{
return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ));
}
public DhApiResult<DhApiTerrainDataPoint[][][]> getAllTerrainDataAtDetailLevelAndPos(IDhApiLevelWrapper levelWrapper, byte detailLevel, int posX, int posZ, @Nullable IDhApiTerrainDataCache dataCache)
{ return getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, new DhLodPos(detailLevel, posX, posZ), dataCache); }
// private getters //
/** Returns a single API terrain datapoint that contains the given Y block position */
private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos)
private static DhApiResult<DhApiTerrainDataPoint> getTerrainDataAtBlockYPos(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer blockYPos, @Nullable IDhApiTerrainDataCache dataCache)
{
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos);
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, requestedColumnPos, blockYPos, dataCache);
if (result.success && result.payload.length > 0)
{
return DhApiResult.createSuccess(result.message, result.payload[0]);
@@ -140,7 +133,9 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*
* will stop and return the in progress data if any errors are encountered.
*/
private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel(IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos)
private static DhApiResult<DhApiTerrainDataPoint[][][]> getTerrainDataOverAreaForPositionDetailLevel(
IDhApiLevelWrapper levelWrapper, DhLodPos requestedAreaPos,
@Nullable IDhApiTerrainDataCache dataCache)
{
DhLodPos startingBlockPos = requestedAreaPos.getCornerLodPos(LodUtil.BLOCK_DETAIL_LEVEL);
int widthOfAreaInBlocks = BitShiftUtil.powerOfTwo(requestedAreaPos.detailLevel);
@@ -154,7 +149,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
for (int z = 0; z < widthOfAreaInBlocks; z++)
{
DhLodPos blockColumnPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, startingBlockPos.x + x, startingBlockPos.z + z);
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null);
DhApiResult<DhApiTerrainDataPoint[]> result = getTerrainDataColumnArray(levelWrapper, blockColumnPos, null, dataCache);
if (result.success)
{
returnArray[x][z] = result.payload;
@@ -177,8 +172,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* If the ApiResult is successful there will be an array of data. <br>
* The returned array will be empty if no data could be retrieved.
*/
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(IDhApiLevelWrapper levelWrapper, DhLodPos requestedColumnPos, Integer nullableBlockYPos)
private static DhApiResult<DhApiTerrainDataPoint[]> getTerrainDataColumnArray(
IDhApiLevelWrapper levelWrapper,
DhLodPos requestedColumnPos, Integer nullableBlockYPos,
@Nullable IDhApiTerrainDataCache apiDataCache)
{
//============//
// validation //
//============//
AbstractDhWorld currentWorld = SharedApi.getAbstractDhWorld();
if (currentWorld == null)
{
@@ -194,6 +196,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
ILevelWrapper coreLevelWrapper = (ILevelWrapper) levelWrapper;
if (!(apiDataCache instanceof DhApiTerrainDataCache))
{
// custom level wrappers aren't supported,
// the API user must get a level wrapper from our code somewhere
return DhApiResult.createFail("Unsupported [" + IDhApiTerrainDataCache.class.getSimpleName() + "] implementation, only the core class [" + DhApiTerrainDataCache.class.getSimpleName() + "] is a valid parameter.");
}
DhApiTerrainDataCache dataCache = (DhApiTerrainDataCache) apiDataCache;
IDhLevel level = currentWorld.getLevel(coreLevelWrapper);
if (level == null)
{
@@ -209,70 +220,96 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
DhLodPos relativePos = requestedColumnPos.getDhSectionRelativePositionForDetailLevel();
//=====================//
// get the data source //
//=====================//
try
{
// attempt to get/generate the data source for this section
FullDataSourceV2 dataSource = level.getFullDataProvider().getAsync(sectionPos).get();
FullDataSourceV2 dataSource = null;
// try using the cached data if possible
if (dataCache != null)
{
dataSource = dataCache.get(sectionPos);
}
if (dataSource == null)
{
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
}
else
{
// attempt to get the LOD data from the data source
FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z);
if (dataColumn != null)
// attempt to get/generate the data source for this section
dataSource = level.getFullDataProvider().getAsync(sectionPos).get();
if (dataSource == null)
{
int dataColumnIndexCount = dataColumn.size();
DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount];
long dataPoint;
return DhApiResult.createFail("Unable to find/generate any data at the " + DhSectionPos.class.getSimpleName() + " [" + DhSectionPos.toString(sectionPos) + "].");
}
dataCache.add(sectionPos, dataSource);
}
//===============================//
// get LOD data from data source //
//===============================//
FullDataPointIdMap mapping = dataSource.mapping;
LongArrayList dataColumn = dataSource.get(relativePos.x, relativePos.z);
if (dataColumn != null)
{
int dataColumnIndexCount = dataColumn.size();
DhApiTerrainDataPoint[] returnArray = new DhApiTerrainDataPoint[dataColumnIndexCount];
long dataPoint;
boolean getSpecificYCoordinate = nullableBlockYPos != null;
int levelMinimumHeight = levelWrapper.getMinHeight();
// search for a datapoint that contains the block y position
for (int i = 0; i < dataColumnIndexCount; i++)
{
dataPoint = dataColumn.getLong(i);
boolean getSpecificYCoordinate = nullableBlockYPos != null;
int levelMinimumHeight = levelWrapper.getMinHeight();
// search for a datapoint that contains the block y position
for (int i = 0; i < dataColumnIndexCount; i++)
if (!getSpecificYCoordinate)
{
dataPoint = dataColumn.getLong(i);
if (!getSpecificYCoordinate)
// if we aren't look for a specific datapoint, add each datapoint to the return array
returnArray[i] = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
}
else
{
// we are looking for a specific datapoint,
// don't look at null ones
if (dataPoint != 0)
{
// if we aren't look for a specific datapoint, add each datapoint to the return array
returnArray[i] = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
}
else
{
// we are looking for a specific datapoint,
// don't look at null ones
if (dataPoint != 0)
int requestedY = nullableBlockYPos;
int bottomY = FullDataPointUtil.getBottomY(dataPoint) + levelMinimumHeight;
int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height;
// does this datapoint contain the requested Y position?
if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
int requestedY = nullableBlockYPos;
int bottomY = FullDataPointUtil.getBottomY(dataPoint) + levelMinimumHeight;
int height = FullDataPointUtil.getHeight(dataPoint);
int topY = bottomY + height;
// does this datapoint contain the requested Y position?
if (bottomY <= requestedY && requestedY < topY) // blockPositions start from the bottom of the block, thus "<=" for bottomY, just "<" for topY
{
// this datapoint contains the requested block position, return it
DhApiTerrainDataPoint apiTerrainData = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData});
}
// this datapoint contains the requested block position, return it
DhApiTerrainDataPoint apiTerrainData = generateApiDatapoint(levelWrapper, mapping, requestedDetailLevel, dataPoint);
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[]{apiTerrainData});
}
}
}
// return all collected data
return DhApiResult.createSuccess(returnArray);
}
// the requested data wasn't present in this column (and/or the column wasn't able to be accessed/generated)
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[0]);
// return all collected data
return DhApiResult.createSuccess(returnArray);
}
// the requested data wasn't present in this column (and/or the column wasn't able to be accessed/generated)
return DhApiResult.createSuccess(new DhApiTerrainDataPoint[0]);
}
catch (InterruptedException | ExecutionException e)
{
// shouldn't normally happen, but just in case
LOGGER.error("getTerrainDataColumnArray operation canceled. Error: [" + e.getMessage() + "]", e);
return DhApiResult.createFail("Operation cancled before it could complete: [" + e.getMessage() + "].");
}
catch (Exception e)
{
// shouldn't normally happen, but just in case
LOGGER.error("Unexpected exception in getTerrainDataColumnArray. Error: [" + e.getMessage() + "]", e);
@@ -306,9 +343,11 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
IDhApiLevelWrapper levelWrapper,
double rayOriginX, double rayOriginY, double rayOriginZ,
float rayDirectionX, float rayDirectionY, float rayDirectionZ,
int maxRayBlockLength)
int maxRayBlockLength,
@Nullable
IDhApiTerrainDataCache dataCache)
{
return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength);
return this.raycastLodData(levelWrapper, new Vec3d(rayOriginX, rayOriginY, rayOriginZ), new Vec3f(rayDirectionX, rayDirectionY, rayDirectionZ), maxRayBlockLength, dataCache);
}
/**
@@ -317,12 +356,17 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
* Works by walking through the world and attempting to get the LOD <br>
* data present at each step.
*/
private DhApiResult<DhApiRaycastResult> raycastLodData(IDhApiLevelWrapper levelWrapper, Vec3d rayOrigin, Vec3f rayDirection, int maxRayBlockLength)
private DhApiResult<DhApiRaycastResult> raycastLodData(
IDhApiLevelWrapper levelWrapper,
Vec3d rayOrigin, Vec3f rayDirection,
int maxRayBlockLength,
@Nullable
IDhApiTerrainDataCache dataCache)
{
rayDirection.normalize();
int minBlockHeight = levelWrapper.getMinHeight();
int maxBlockHeight = levelWrapper.getHeight();
int maxBlockHeight = levelWrapper.getMaxHeight();
@@ -346,7 +390,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
for (Vec3i columnPos : columnPositions)
{
// check each column
DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z);
DhApiResult<DhApiTerrainDataPoint[]> result = this.getColumnDataAtBlockPos(levelWrapper, columnPos.x, columnPos.z, dataCache);
if (!result.success)
{
// if there was an error, stop and return it
@@ -414,7 +458,7 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
*/
private static ArrayList<Vec3i> getIntersectingColumnsAtPosition(Vec3i rayEndingPos, Vec3f rayDirection)
{
ArrayList<Vec3i> returnList = new ArrayList<Vec3i>(9);
ArrayList<Vec3i> returnList = new ArrayList<>(9);
for (int x = -1; x <= 1; x++)
{
@@ -469,6 +513,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
//=============//
// API helpers //
//=============//
@Override
public IDhApiTerrainDataCache getSoftCache() { return new DhApiTerrainDataCache(); }
//===============//
// debug methods //
//===============//
@@ -485,15 +538,15 @@ public class DhApiTerrainDataRepo implements IDhApiTerrainDataRepo
Thread thread = new Thread(() -> {
try
{
DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY);
DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null);
DhApiResult<DhApiTerrainDataPoint> single = getTerrainDataAtBlockYPos(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), blockPosY, debugDataCache);
DhApiResult<DhApiTerrainDataPoint[]> column = getTerrainDataColumnArray(levelWrapper, new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ), null, debugDataCache);
DhLodPos chunkPos = new DhLodPos(LodUtil.BLOCK_DETAIL_LEVEL, blockPosX, blockPosZ).convertToDetailLevel(LodUtil.CHUNK_DETAIL_LEVEL);
DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos);
DhApiResult<DhApiTerrainDataPoint[][][]> area = getTerrainDataOverAreaForPositionDetailLevel(levelWrapper, chunkPos, debugDataCache);
IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
DhApiResult<DhApiRaycastResult> rayCast = INSTANCE.raycastLodData(levelWrapper, MC_RENDER.getCameraExactPosition(), MC_RENDER.getLookAtVector(), 1000);
DhApiResult<DhApiRaycastResult> rayCast = INSTANCE.raycastLodData(levelWrapper, MC_RENDER.getCameraExactPosition(), MC_RENDER.getLookAtVector(), 1000, debugDataCache);
if (rayCast.payload != null && !rayCast.payload.pos.equals(currentDebugVec3i))
{
currentDebugVec3i = rayCast.payload.pos;
@@ -23,6 +23,12 @@ import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRenderPass;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.core.file.structure.ClientOnlySaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.network.messages.NetworkMessage;
import com.seibel.distanthorizons.core.network.session.Session;
@@ -30,17 +36,14 @@ import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiRendererMode;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhClientLevel;
import com.seibel.distanthorizons.core.level.IServerKeyedClientLevel;
import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.SpamReducedLogger;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.DhApiRenderProxy;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.renderer.TestRenderer;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.core.world.DhClientServerWorld;
import com.seibel.distanthorizons.core.world.DhClientWorld;
@@ -49,13 +52,12 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW;
import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Queue;
@@ -124,9 +126,28 @@ public class ClientApi
public synchronized void onClientOnlyConnected()
{
// only continue if the client is connected to a different server
if (MC.clientConnectedToDedicatedServer())
boolean connectedToServer = MC.clientConnectedToDedicatedServer();
boolean connectedToReplay = MC.connectedToReplay();
if (connectedToServer || connectedToReplay)
{
LOGGER.info("Client on ClientOnly mode connecting.");
if (connectedToServer)
{
LOGGER.info("Client on ClientOnly mode connecting.");
}
else
{
LOGGER.info("Replay on ClientServer mode connecting.");
if (Config.Client.Advanced.Logging.showReplayWarningOnStartup.get())
{
MC.sendChatMessage("\u00A76" + "Distant Horizons: Replay detected." + "\u00A7r"); // gold color
MC.sendChatMessage("DH may behave strangely or have missing functionality.");
MC.sendChatMessage("In order to use pre-generated LODs, put your DH database(s) in:");
MC.sendChatMessage("\u00A77"+".Minecraft" + File.separator + ClientOnlySaveStructure.SERVER_DATA_FOLDER_NAME + File.separator + ClientOnlySaveStructure.REPLAY_SERVER_FOLDER_NAME + File.separator + "DIMENSION_NAME"+"\u00A7r"); // light gray color
MC.sendChatMessage("This can be disabled in DH's config under Advanced -> Logging.");
MC.sendChatMessage("");
}
}
// firing after clientLevelLoadEvent
// TODO if level has prepped to load it should fire level load event
@@ -447,7 +468,7 @@ public class ClientApi
if (!DhApi.Delayed.renderProxy.getDeferTransparentRendering())
{
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, renderEventParam);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
}
}
else if (Config.Client.Advanced.Debugging.rendererMode.get() == EDhApiRendererMode.DEBUG)
@@ -468,7 +489,7 @@ public class ClientApi
if (DhApi.Delayed.renderProxy.getDeferTransparentRendering())
{
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, renderEventParam);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiAfterRenderEvent.class, null);
}
}
}
@@ -479,6 +500,7 @@ public class ClientApi
MC.sendChatMessage("\u00A74\u00A7l\u00A7uERROR: Distant Horizons renderer has encountered an exception!");
MC.sendChatMessage("\u00A74Renderer is now disabled to prevent further issues.");
MC.sendChatMessage("\u00A74Please restart your game to re-enable Distant Horizons' LOD rendering.");
MC.sendChatMessage("\u00A74Exception detail: " + e);
}
finally
@@ -138,7 +138,7 @@ public class ServerApi
//=======================//
public void serverChunkLoadEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, false); }
public void serverChunkSaveEvent(IChunkWrapper chunkWrapper, ILevelWrapper level) { SharedApi.INSTANCE.applyChunkUpdate(chunkWrapper, level, true); }
@@ -29,7 +29,8 @@ import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.AbstractDhRepo;
import com.seibel.distanthorizons.core.util.TimerUtil;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
@@ -56,7 +57,7 @@ public class SharedApi
private static final Set<DhChunkPos> UPDATING_CHUNK_POS_SET = ConcurrentHashMap.newKeySet();
/** how many chunks can be queued for updating per thread, used to prevent updates from infinitely pilling up if the user flys around extremely fast */
private static final int MAX_UPDATING_CHUNK_COUNT_PER_THREAD = 500;
private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 5_000;
private static final int MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE = 30_000;
private static final Timer CHUNK_UPDATE_TIMER = TimerUtil.CreateTimer("ChunkUpdateTimer");
@@ -101,7 +102,9 @@ public class SharedApi
{
DebugRenderer.clearRenderables();
MC_RENDER.clearTargetFrameBuffer();
// needs to be closed on world shutdown to clear out un-processed chunks
// shouldn't be necessary, but if we missed closing one of the connections this should make sure they're all closed
AbstractDhRepo.closeAllConnections();
// needs to be closed on world shutdown to clear out un-processed chunks
UPDATING_CHUNK_POS_SET.clear();
}
@@ -138,7 +141,8 @@ public class SharedApi
* Used to prevent getting a full chunk from MC if it isn't necessary. <br>
* This is important since asking MC for a chunk is slow and may block the render thread.
*/
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ) { return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
public static boolean isChunkAtBlockPosAlreadyUpdating(int blockPosX, int blockPosZ)
{ return UPDATING_CHUNK_POS_SET.contains(new DhChunkPos(new DhBlockPos2D(blockPosX, blockPosZ))); }
/** handles both block place and break events */
@@ -215,7 +219,13 @@ public class SharedApi
if (msBetweenLastLog >= MIN_MS_BETWEEN_OVERLOADED_LOG_MESSAGE)
{
lastOverloadedLogMessageMsTime = System.currentTimeMillis();
LOGGER.warn("Too many chunks queued for updating, max queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread). This may result in holes in your LODs. Please move through the world slower, decrease your vanilla render distance, slow down your world pre-generator, or increase the CPU load config.");
String message = "Distant Horizons overloaded, too many chunks queued for updating. " +
"\nThis may result in holes in your LODs. " +
"\nPlease move through the world slower, decrease your vanilla render distance, slow down your world pre-generator, or increase the Distant Horizons' CPU load config. " +
"\nMax queue count ["+maxQueueCount+"] (["+MAX_UPDATING_CHUNK_COUNT_PER_THREAD+"] per thread).";
ClientApi.INSTANCE.showChatMessageNextFrame(message);
LOGGER.warn(message);
}
return;
@@ -310,6 +320,18 @@ public class SharedApi
}
// having a list of the nearby chunks is needed for lighting and beacon generation
ArrayList<IChunkWrapper> nearbyChunkList;
if (neighbourChunkList != null)
{
nearbyChunkList = neighbourChunkList;
}
else
{
nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunkWrapper);
}
// Save or populate the chunk wrapper's lighting
// this is done so we don't have to worry about MC unloading the lighting data for this chunk
@@ -319,7 +341,7 @@ public class SharedApi
try
{
// If MC's lighting engine isn't thread safe this may cause the server thread to lag
chunkWrapper.bakeDhLightingUsingMcLightingEngine(); // TODO handle unlit chunks, would pulling in the chunk from disk be a good idea? Look at ChunkLoader in the world gen code for an example
chunkWrapper.bakeDhLightingUsingMcLightingEngine();
}
catch (IllegalStateException e)
{
@@ -329,21 +351,16 @@ public class SharedApi
else
{
// generate the chunk's lighting, using neighboring chunks if present
ArrayList<IChunkWrapper> nearbyChunkList;
if (neighbourChunkList != null)
{
nearbyChunkList = neighbourChunkList;
}
else
{
nearbyChunkList = new ArrayList<>(1);
nearbyChunkList.add(chunkWrapper);
}
DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? 15 : 0);
}
// get this chunk's active beacons
List<BeaconBeamDTO> beaconBeamList = chunkWrapper.getAllActiveBeacons(nearbyChunkList);
dhLevel.setBeaconBeamsForChunk(chunkWrapper.getChunkPos(), beaconBeamList);
dhLevel.updateChunkAsync(chunkWrapper);
dhLevel.setChunkHash(chunkWrapper.getChunkPos(), newChunkHash);
}
@@ -20,17 +20,20 @@
package com.seibel.distanthorizons.core.config;
import com.seibel.distanthorizons.api.DhApi;
import com.seibel.distanthorizons.api.enums.config.*;
import com.seibel.distanthorizons.api.enums.config.quickOptions.*;
import com.seibel.distanthorizons.api.enums.rendering.*;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiDistantGeneratorMode;
import com.seibel.distanthorizons.core.config.eventHandlers.*;
import com.seibel.distanthorizons.core.config.eventHandlers.presets.*;
import com.seibel.distanthorizons.core.config.listeners.ConfigChangeListener;
import com.seibel.distanthorizons.core.config.types.*;
import com.seibel.distanthorizons.core.config.types.enums.*;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftSharedWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import com.seibel.distanthorizons.coreapi.util.StringUtil;
@@ -136,6 +139,7 @@ public class Config
public static ConfigCategory fog = new ConfigCategory.Builder().set(Fog.class).build();
public static ConfigCategory ssao = new ConfigCategory.Builder().set(Ssao.class).build();
public static ConfigCategory noiseTextureSettings = new ConfigCategory.Builder().set(NoiseTextureSettings.class).build();
public static ConfigCategory genericRendering = new ConfigCategory.Builder().set(GenericRendering.class).build();
public static ConfigCategory advancedGraphics = new ConfigCategory.Builder().set(AdvancedGraphics.class).build();
@@ -288,7 +292,7 @@ public class Config
private static final Double FOG_RANGE_MAX = Math.sqrt(2.0);
public static ConfigEntry<Double> farFogStart = new ConfigEntry.Builder<Double>()
.setMinDefaultMax(FOG_RANGE_MIN, 0.0, FOG_RANGE_MAX)
.setMinDefaultMax(FOG_RANGE_MIN, 0.4, FOG_RANGE_MAX)
.comment(""
+ "At what distance should the far fog start? \n"
+ "\n"
@@ -519,7 +523,8 @@ public class Config
public static ConfigEntry<Integer> noiseSteps = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(1, 4, null)
.comment(""
+ "How many steps of noise should be applied to LODs?")
+ "How many steps of noise should be applied to LODs?"
+ "")
.build();
public static ConfigEntry<Double> noiseIntensity = new ConfigEntry.Builder<Double>() // TODO: Make this a float (the ClassicConfigGUI doesn't support floats)
@@ -532,7 +537,34 @@ public class Config
.setMinDefaultMax(0, 1024, null)
.comment(""
+ "Defines how far should the noise texture render before it fades away. (in blocks) \n"
+ "Set to 0 to disable noise from fading away")
+ "Set to 0 to disable noise from fading away \n"
+ "")
.build();
}
public static class GenericRendering
{
public static ConfigEntry<Boolean> enableRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true non terrain objects will be rendered in DH's terrain. \n"
+ "This includes beacon beams and clouds. \n"
+ "")
.build();
public static ConfigEntry<Boolean> enableBeaconRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true LOD beacon beams will be rendered. \n"
+ "")
.build();
public static ConfigEntry<Boolean> enableCloudRendering = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If true LOD clouds will be rendered. \n"
+ "")
.build();
}
@@ -589,8 +621,10 @@ public class Config
+ " does not have a ceiling.")
.build();
@Deprecated
public static ConfigEntry<Integer> caveCullingHeight = new ConfigEntry.Builder<Integer>()
.setMinDefaultMax(-4096, 40, 4096)
.setAppearance(EConfigEntryAppearance.ONLY_IN_API)
.comment(""
+ "At what Y value should cave culling start?")
.build();
@@ -761,6 +795,20 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> pullLightingForPregeneratedChunks = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true LOD generation for pre-existing chunks will attempt to pull the lighting data \n"
+ "saved in Minecraft's Region files. \n"
+ "If false DH will pull in chunks without lighting and re-light them. \n"
+ " \n"
+ "Setting this to true will result in faster LOD generation \n"
+ "for already generated worlds, but is broken by most lighting mods. \n"
+ " \n"
+ "Set this to false if LODs are black. \n"
+ "")
.build();
public static ConfigEntry<EDhApiDataCompressionMode> dataCompression = new ConfigEntry.Builder<EDhApiDataCompressionMode>()
.set(EDhApiDataCompressionMode.LZMA2)
.comment(""
@@ -809,13 +857,47 @@ public class Config
+ "")
.build();
//public static ConfigEntry<Boolean> showMigrationChatWarning = new ConfigEntry.Builder<Boolean>()
// .set(true)
// .comment(""
// + "Determines if a message should be displayed in the chat when LOD migration starts. \n"
// + "")
// .build();
public static ConfigEntry<String> ignoredRenderBlockCsv = new ConfigEntry.Builder<String>()
.set("minecraft:barrier,minecraft:structure_void,minecraft:light,minecraft:tripwire")
.comment(""
+ "A comma separated list of block resource locations that won't be rendered by DH. \n"
+ "Note: air is always included in this list. \n"
+ "")
.build();
public static ConfigEntry<String> ignoredRenderCaveBlockCsv = new ConfigEntry.Builder<String>()
.set("minecraft:glow_lichen,minecraft:rail,minecraft:water,minecraft:lava,minecraft:bubble_column")
.comment(""
+ "A comma separated list of block resource locations that shouldn't be rendered \n"
+ "if they are in a 0 sky light underground area. \n"
+ "Note: air is always included in this list. \n"
+ "")
.build();
static
{
ignoredRenderBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderBlockCsv,
(blockCsv) ->
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetRendererIgnoredBlocksSet();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}));
ignoredRenderCaveBlockCsv.addListener(new ConfigChangeListener<String>(Config.Client.Advanced.LodBuilding.ignoredRenderCaveBlockCsv,
(blockCsv) ->
{
IWrapperFactory wrapperFactory = SingletonInjector.INSTANCE.get(IWrapperFactory.class);
if (wrapperFactory != null)
{
wrapperFactory.resetRendererIgnoredCaveBlocks();
DhApi.Delayed.renderProxy.clearRenderDataCache();
}
}));
}
}
public static class Multiplayer
@@ -1192,7 +1274,7 @@ public class Config
// TODO default to error chat and info file
public static ConfigEntry<EDhApiLoggerMode> logWorldGenEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenEvent")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1200,7 +1282,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logWorldGenPerformance = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log performance about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1208,7 +1290,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logWorldGenLoadEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logWorldGenPerformance")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about the world generation process. \n"
+ "This can be useful for debugging.")
@@ -1216,21 +1298,21 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logLodBuilderEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logLodBuilderEvent")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about the LOD generation process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererBufferEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about the renderer buffer process. \n"
+ "This can be useful for debugging.")
.build();
public static ConfigEntry<EDhApiLoggerMode> logRendererGLEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about the renderer OpenGL process. \n"
+ "This can be useful for debugging.")
@@ -1238,7 +1320,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logFileReadWriteEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileReadWriteEvent")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about file read/write operations. \n"
+ "This can be useful for debugging.")
@@ -1246,7 +1328,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logFileSubDimEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logFileSubDimEvent")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about file sub-dimension operations. \n"
+ "This can be useful for debugging.")
@@ -1254,7 +1336,7 @@ public class Config
public static ConfigEntry<EDhApiLoggerMode> logNetworkEvent = new ConfigEntry.Builder<EDhApiLoggerMode>()
.setServersideShortName("logNetworkEvent")
.set(EDhApiLoggerMode.LOG_WARNING_TO_CHAT_AND_INFO_TO_FILE)
.set(EDhApiLoggerMode.LOG_ERROR_TO_CHAT)
.comment(""
+ "If enabled, the mod will log information about network operations. \n"
+ "This can be useful for debugging.")
@@ -1268,6 +1350,13 @@ public class Config
+ "memory allocated to run DH well.")
.build();
public static ConfigEntry<Boolean> showReplayWarningOnStartup = new ConfigEntry.Builder<Boolean>()
.set(true)
.comment(""
+ "If enabled, a chat message will be displayed when a replay is started \n"
+ "giving some basic information about how DH will function.")
.build();
}
public static class Debugging
@@ -1335,6 +1424,15 @@ public class Config
+ "")
.build();
public static ConfigEntry<Boolean> logBufferGarbageCollection = new ConfigEntry.Builder<Boolean>()
.set(false)
.comment(""
+ "If true OpenGL Buffer garbage collection will be logged \n"
+ "this also includes the number of live buffers. \n"
+ "")
.build();
// Note: This will reset on game restart, and should have a warning on the tooltip
public static ConfigEntry<Boolean> allowUnsafeValues = new ConfigEntry.Builder<Boolean>()
.set(false)
@@ -260,20 +260,38 @@ public class ConfigEntry<T> extends AbstractConfigType<T, ConfigEntry<T>> implem
public byte isValid(T value, T min, T max)
{
if (this.configBase.disableMinMax)
{
return 0;
if (value == null || this.value == null || value.getClass() != this.value.getClass()) // If the 2 variables aren't the same type then it will be invalid
}
else if (min == null && max == null)
{
// no validation is needed for this field
return 0;
}
else if (value == null || this.value == null
|| value.getClass() != this.value.getClass())
{
// If the 2 variables aren't the same type then it will be invalid
return 2;
if (Number.class.isAssignableFrom(value.getClass()))
{ // Only check min max if it is a number
}
else if (Number.class.isAssignableFrom(value.getClass()))
{
// Only check min max if it is a number
if (max != null && NumberUtil.greaterThan((Number) value, (Number) max))
{
return 1;
}
if (min != null && NumberUtil.lessThan((Number) value, (Number) min))
{
return -1;
}
return 0;
}
return 0;
else
{
return 0;
}
}
/** This should normally not be called since set() automatically calls this */
@@ -27,6 +27,7 @@ import com.seibel.distanthorizons.core.config.types.enums.EConfigEntryAppearance
*
* @author coolGi
*/
@Deprecated // FIXME doesn't work with localization
public class ConfigLinkedEntry extends AbstractConfigType<AbstractConfigType<?, ?>, ConfigLinkedEntry>
{
public ConfigLinkedEntry(AbstractConfigType<?, ?> value)
@@ -62,6 +62,8 @@ public class FullDataSourceV2 implements IDataSource<IDhLevel>
/** measured in data columns */
public static final int WIDTH = 64;
/** how many chunks wide this datasource is. */
public static final int NUMB_OF_CHUNKS_WIDE = WIDTH / LodUtil.CHUNK_WIDTH;
public static final byte DATA_FORMAT_VERSION = 1;
@@ -182,7 +182,7 @@ public class ColumnRenderSource implements IDataSource<IDhClientLevel>
EDhApiWorldGenerationStep worldGenStep = inputFullDataSource.getWorldGenStepAtRelativePos(x, z);
if (dataColumn != null && worldGenStep != EDhApiWorldGenerationStep.EMPTY)
{
FullDataToRenderDataTransformer.convertColumnData(
FullDataToRenderDataTransformer.updateRenderDataViewWithFullDataColumn(
level, inputFullDataSource.mapping,
minBlockPos.x + x,
minBlockPos.z + z,
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.ColorUtil;
@@ -475,10 +476,10 @@ public class ColumnBox
throw new RuntimeException("Loop error");
if (previousAdjDepth > adjYMax)
{
if (irisBlockMaterialId == IBlockStateWrapper.IrisBlockMaterial.GRASS)
if (irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index)
{
// this LOD is underneath another, grass will never show here
irisBlockMaterialId = IBlockStateWrapper.IrisBlockMaterial.DIRT;
irisBlockMaterialId = EDhApiBlockMaterial.DIRT.index;
}
builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId,
@@ -50,8 +50,11 @@ public class ColumnRenderBuffer implements AutoCloseable
private static final long MAX_BUFFER_UPLOAD_TIMEOUT_NANOSECONDS = 1_000_000;
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4; // TODO what does the 4 represent
public static final int MAX_QUADS_PER_BUFFER = (1024 * 1024 * 1) / QUADS_BYTE_SIZE; // TODO what do these multiples represent?
/** number of bytes a single quad takes */
public static final int QUADS_BYTE_SIZE = LodUtil.LOD_VERTEX_FORMAT.getByteSize() * 4;
/** how big a single VBO can be in bytes */
public static final int MAX_VBO_BYTE_SIZE = 10 * 1024 * 1024; // 10 MB
public static final int MAX_QUADS_PER_BUFFER = MAX_VBO_BYTE_SIZE / QUADS_BYTE_SIZE;
public static final int FULL_SIZED_BUFFER = MAX_QUADS_PER_BUFFER * QUADS_BYTE_SIZE;
@@ -54,11 +54,6 @@ public class ColumnRenderBufferBuilder
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final int MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD = 3;
public static int maxNumberOfConcurrentCalls = MAX_NUMBER_OF_CONCURRENT_CALLS_PER_THREAD;
//==============//
// vbo building //
@@ -87,33 +82,14 @@ public class ColumnRenderBufferBuilder
{
boolean enableTransparency = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled;
//EVENT_LOGGER.trace("RenderRegion start QuadBuild @ " + renderSource.sectionPos);
boolean enableSkyLightCulling =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& (
// dimensions with a ceiling will be all caves so we don't want cave culling
!clientLevel.getLevelWrapper().hasCeiling()
// the end has a lot of overhangs with 0 lighting above the void, which look broken with
// the current cave culling logic (this could probably be improved, but just skipping it works best for now)
&& !clientLevel.getLevelWrapper().getDimensionType().isTheEnd()
// FIXME temporary fix
// Cave culling is currently broken for any detail level above 0
&& DhSectionPos.getDetailLevel(renderSource.pos) == DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL
);
int skyLightCullingBelow = Config.Client.Advanced.Graphics.AdvancedGraphics.caveCullingHeight.get();
// FIXME: Clamp also to the max world height.
skyLightCullingBelow = Math.max(skyLightCullingBelow, clientLevel.getMinY());
long builderStartTime = System.currentTimeMillis();
LodQuadBuilder builder = new LodQuadBuilder(enableSkyLightCulling, (short) (skyLightCullingBelow - clientLevel.getMinY()), enableTransparency, clientLevel.getClientLevelWrapper());
LodQuadBuilder builder = new LodQuadBuilder(enableTransparency, clientLevel.getClientLevelWrapper());
makeLodRenderData(builder, renderSource, adjData);
long builderEndTime = System.currentTimeMillis();
long buildMs = builderEndTime - builderStartTime;
LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.pos + " took: " + buildMs);
//LOGGER.debug("RenderRegion end QuadBuild @ " + renderSource.pos + " took: " + buildMs);
return builder;
}
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource;
import com.seibel.distanthorizons.core.pos.DhLodPos;
@@ -95,53 +96,54 @@ public class CubicLodTemplate
}
case SHOW_BLOCK_MATERIAL:
{
switch (blockMaterialId)
switch (EDhApiBlockMaterial.getFromIndex(blockMaterialId))
{
case IBlockStateWrapper.IrisBlockMaterial.UNKOWN:
case IBlockStateWrapper.IrisBlockMaterial.AIR: // shouldn't normally be rendered, but just in case
case UNKNOWN:
case AIR: // shouldn't normally be rendered, but just in case
color = ColorUtil.HOT_PINK;
break;
case IBlockStateWrapper.IrisBlockMaterial.LEAVES:
case LEAVES:
color = ColorUtil.DARK_GREEN;
break;
case IBlockStateWrapper.IrisBlockMaterial.STONE:
case STONE:
color = ColorUtil.GRAY;
break;
case IBlockStateWrapper.IrisBlockMaterial.WOOD:
case WOOD:
color = ColorUtil.BROWN;
break;
case IBlockStateWrapper.IrisBlockMaterial.METAL:
case METAL:
color = ColorUtil.DARK_GRAY;
break;
case IBlockStateWrapper.IrisBlockMaterial.DIRT:
case DIRT:
color = ColorUtil.LIGHT_BROWN;
break;
case IBlockStateWrapper.IrisBlockMaterial.LAVA:
case LAVA:
color = ColorUtil.ORANGE;
break;
case IBlockStateWrapper.IrisBlockMaterial.DEEPSLATE:
case DEEPSLATE:
color = ColorUtil.BLACK;
break;
case IBlockStateWrapper.IrisBlockMaterial.SNOW:
case SNOW:
color = ColorUtil.WHITE;
break;
case IBlockStateWrapper.IrisBlockMaterial.SAND:
case SAND:
color = ColorUtil.TAN;
break;
case IBlockStateWrapper.IrisBlockMaterial.TERRACOTTA:
case TERRACOTTA:
color = ColorUtil.DARK_ORANGE;
break;
case IBlockStateWrapper.IrisBlockMaterial.NETHER_STONE:
case NETHER_STONE:
color = ColorUtil.DARK_RED;
break;
case IBlockStateWrapper.IrisBlockMaterial.WATER:
case WATER:
color = ColorUtil.BLUE;
break;
case IBlockStateWrapper.IrisBlockMaterial.GRASS:
case GRASS:
color = ColorUtil.GREEN;
break;
case IBlockStateWrapper.IrisBlockMaterial.ILLUMINATED:
case ILLUMINATED:
color = ColorUtil.YELLOW;
break;
@@ -24,6 +24,7 @@ import java.nio.ByteOrder;
import java.util.*;
import com.seibel.distanthorizons.api.enums.config.EDhApiGrassSideRendering;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiDebugRendering;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
@@ -50,7 +51,9 @@ public class LodQuadBuilder
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
@Deprecated
public final boolean skipQuadsWithZeroSkylight;
@Deprecated
public final short skyLightCullingBelow;
@SuppressWarnings("unchecked")
@@ -122,7 +125,7 @@ public class LodQuadBuilder
// constructor //
//=============//
public LodQuadBuilder(boolean enableSkylightCulling, short skyLightCullingBelow, boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
public LodQuadBuilder(boolean doTransparency, IClientLevelWrapper clientLevelWrapper)
{
this.doTransparency = doTransparency;
for (int i = 0; i < 6; i++)
@@ -131,8 +134,8 @@ public class LodQuadBuilder
this.transparentQuads[i] = new ArrayList<>();
}
this.skipQuadsWithZeroSkylight = enableSkylightCulling;
this.skyLightCullingBelow = skyLightCullingBelow;
this.skipQuadsWithZeroSkylight = false;
this.skyLightCullingBelow = 0;
this.clientLevelWrapper = clientLevelWrapper;
this.debugRenderingMode = Config.Client.Advanced.Debugging.debugRendering.get();
@@ -273,7 +276,7 @@ public class LodQuadBuilder
int color = quad.color;
// use custom side color logic for grass blocks
if (quad.irisBlockMaterialId == IBlockStateWrapper.IrisBlockMaterial.GRASS)
if (quad.irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index)
{
// only use dirt colors if debug rendering is disabled
if (this.debugRenderingMode == EDhApiDebugRendering.OFF)
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.dataObjects.transformers;
import com.seibel.distanthorizons.api.enums.config.EDhApiBlocksToAvoid;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.FullDataPointIdMap;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
@@ -32,6 +33,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.ColorUtil;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderDataPointUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
@@ -109,74 +111,85 @@ public class FullDataToRenderDataTransformer
}
columnSource.markNotEmpty();
int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
if (dataDetail == columnSource.getDataDetailLevel())
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
{
int baseX = DhSectionPos.getMinCornerBlockX(pos);
int baseZ = DhSectionPos.getMinCornerBlockZ(pos);
for (int x = 0; x < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); x++)
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
{
for (int z = 0; z < DhSectionPos.getWidthCountForLowerDetailedSection(pos, dataDetail); z++)
{
throwIfThreadInterrupted();
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
LongArrayList dataColumn = fullDataSource.get(x, z);
convertColumnData(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
}
throwIfThreadInterrupted();
ColumnArrayView columnArrayView = columnSource.getVerticalDataPointView(x, z);
LongArrayList dataColumn = fullDataSource.get(x, z);
updateRenderDataViewWithFullDataColumn(level, fullDataSource.mapping, baseX + x, baseZ + z, columnArrayView, dataColumn);
}
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
}
else
{
throw new UnsupportedOperationException("To be implemented");
//FIXME: Implement different size creation of renderData
}
columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL);
return columnSource;
}
//================//
// helper methods //
//================//
/**
* Called in loops that may run for an extended period of time. <br>
* This is necessary to allow canceling these transformers since running
* them after the client has left a given world will throw exceptions.
*/
private static void throwIfThreadInterrupted() throws InterruptedException
/** Updates the given {@link ColumnArrayView} to match the incoming Full data {@link LongArrayList} */
public static void updateRenderDataViewWithFullDataColumn(
IDhClientLevel level,
FullDataPointIdMap fullDataMapping, int blockX, int blockZ,
ColumnArrayView columnArrayView,
LongArrayList fullDataColumn)
{
if (Thread.interrupted())
if (fullDataColumn == null || fullDataColumn.size() == 0)
{
throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted.");
return;
}
int dataTotalLength = fullDataColumn.size();
if (dataTotalLength > columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new LongArrayList(new long[dataTotalLength]), dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, fullDataMapping, blockX, blockZ, totalColumnData, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
}
else
{
iterateAndConvert(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
}
}
// TODO what does this mean?
private static void iterateAndConvert(
IDhClientLevel level, FullDataPointIdMap fullDataMapping,
int blockX, int blockZ,
IDhClientLevel level, FullDataPointIdMap fullDataMapping,
int blockX, int blockZ,
ColumnArrayView renderColumnData, LongArrayList fullColumnData)
{
boolean avoidSolidBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean ignoreNonCollidingBlocks = (Config.Client.Advanced.Graphics.Quality.blocksToIgnore.get() == EDhApiBlocksToAvoid.NON_COLLIDING);
boolean colorBelowWithAvoidedBlocks = Config.Client.Advanced.Graphics.Quality.tintWithAvoidedBlocks.get();
HashSet<IBlockStateWrapper> blockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredBlocks(level.getLevelWrapper());
HashSet<IBlockStateWrapper> caveBlockStatesToIgnore = WRAPPER_FACTORY.getRendererIgnoredCaveBlocks(level.getLevelWrapper());
boolean caveCullingEnabled =
Config.Client.Advanced.Graphics.AdvancedGraphics.enableCaveCulling.get()
&& (
// dimensions with a ceiling will be all caves so we don't want cave culling
!level.getLevelWrapper().hasCeiling()
// the end has a lot of overhangs with 0 lighting above the void, which look broken with
// the current cave culling logic (this could probably be improved, but just skipping it works best for now)
&& !level.getLevelWrapper().getDimensionType().isTheEnd()
);
boolean isVoid = true;
int colorToApplyToNextBlock = -1;
int lastColor = 0;
int lastBottom = -10000;
int skylightToApplyToNextBlock = -1;
int blocklightToApplyToNextBlock = -1;
int columnOffset = 0;
IBiomeWrapper biome = null;
IBlockStateWrapper block = null;
// goes from the top down
for (int i = 0; i < fullColumnData.size(); i++)
{
@@ -187,23 +200,6 @@ public class FullDataToRenderDataTransformer
int blockLight = FullDataPointUtil.getBlockLight(fullData);
int skyLight = FullDataPointUtil.getSkyLight(fullData);
// TODO how should corrupted data be handled?
// TODO why is the full data corrupted in the first place? FullDataPointUtil hasn't been changed in a long time, could one of the full data point objects be corrupted?
// TODO if either of these happen the ID might also be invalid
//if (bottomY + blockHeight > 300)
//{
// // this data point is too tall, it's probably a monolith
// int k = 0;
// throw new RuntimeException();
//}
//if (light > 16 || light < 0)
//{
// // light is out of range
// throw new RuntimeException();
//}
IBiomeWrapper biome;
IBlockStateWrapper block;
try
{
biome = fullDataMapping.getBiomeWrapper(id);
@@ -228,28 +224,72 @@ public class FullDataToRenderDataTransformer
}
if (blockStatesToIgnore.contains(block))
//====================//
// ignored block and //
// cave culling check //
//====================//
boolean ignoreBlock = blockStatesToIgnore.contains(block);
boolean caveBlock = caveBlockStatesToIgnore.contains(block);
if (caveBlock)
{
// Don't render: air, barriers, light blocks, etc.
if (caveCullingEnabled
// assume this data point is underground if it has no sky-light
&& skyLight == LodUtil.MIN_MC_LIGHT
// cave culling shouldn't happen when at the top of the world
&& columnOffset != 0
// cave culling can't happen when at the bottom of the world
&& columnOffset != fullColumnData.size())
{
// we need to get the next sky/block lights because
// the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved.
long nextFullData = fullColumnData.getLong(i+1);
int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData);
if (nextSkyLight == LodUtil.MIN_MC_LIGHT
&& ColorUtil.getAlpha(lastColor) == 255)
{
// replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
}
continue;
}
if (ignoreBlock)
{
// this is a merged block and a cave block, so it should never be rendered
continue;
}
}
else if (ignoreBlock)
{
// this is an ignored block, but shouldn't be merged like a cave block
continue;
}
// solid block check
if (avoidSolidBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != IBlockStateWrapper.FULLY_OPAQUE)
//===================//
// solid block check //
//===================//
if (ignoreNonCollidingBlocks && !block.isSolid() && !block.isLiquid() && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE)
{
if (colorBelowWithAvoidedBlocks)
{
int tempColor = level.computeBaseColor(new DhBlockPos(blockX, bottomY + level.getMinY(), blockZ), biome, block);
if (ColorUtil.getAlpha(tempColor) == 0)
// don't transfer the color when alpha is 0
if (ColorUtil.getAlpha(tempColor) != 0)
{
//make sure to not transfer the color when alpha is 0
continue;
// don't transfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
//mare sure to not trnasfer alpha if for some reason grass is semi transparent
colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255);
skylightToApplyToNextBlock = skyLight;
blocklightToApplyToNextBlock = blockLight;
}
// don't add this block
@@ -271,11 +311,11 @@ public class FullDataToRenderDataTransformer
skyLight = skylightToApplyToNextBlock;
blockLight = blocklightToApplyToNextBlock;
}
//check if they share a top-bottom face and if they have same collor
//check if they share a top-bottom face and if they have same color
if (color == lastColor && bottomY + blockHeight == lastBottom && columnOffset > 0)
{
//replace the previus block with new bottom
//replace the previous block with new bottom
long columnData = renderColumnData.get(columnOffset - 1);
columnData = RenderDataPointUtil.setYMin(columnData, bottomY);
renderColumnData.set(columnOffset - 1, columnData);
@@ -284,13 +324,13 @@ public class FullDataToRenderDataTransformer
{
// add the block
isVoid = false;
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getIrisBlockMaterialId());
long columnData = RenderDataPointUtil.createDataPoint(bottomY + blockHeight, bottomY, color, skyLight, blockLight, block.getMaterialId());
renderColumnData.set(columnOffset, columnData);
columnOffset++;
}
lastBottom = bottomY;
lastColor = color;
}
@@ -300,24 +340,22 @@ public class FullDataToRenderDataTransformer
}
}
// TODO what does this mean?
public static void convertColumnData(IDhClientLevel level, FullDataPointIdMap fullDataMapping, int blockX, int blockZ, ColumnArrayView columnArrayView, LongArrayList fullDataColumn)
//================//
// helper methods //
//================//
/**
* Called in loops that may run for an extended period of time. <br>
* This is necessary to allow canceling these transformers since running
* them after the client has left a given world will throw exceptions.
*/
private static void throwIfThreadInterrupted() throws InterruptedException
{
if (fullDataColumn == null || fullDataColumn.size() == 0)
if (Thread.interrupted())
{
return;
}
int dataTotalLength = fullDataColumn.size();
if (dataTotalLength > columnArrayView.verticalSize())
{
ColumnArrayView totalColumnData = new ColumnArrayView(new LongArrayList(new long[dataTotalLength]), dataTotalLength, 0, dataTotalLength);
iterateAndConvert(level, fullDataMapping, blockX, blockZ, totalColumnData, fullDataColumn);
columnArrayView.changeVerticalSizeFrom(totalColumnData);
}
else
{
iterateAndConvert(level, fullDataMapping, blockX, blockZ, columnArrayView, fullDataColumn); //Directly use the arrayView since it fits.
throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted.");
}
}
@@ -31,7 +31,6 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.FullDataPointUtil;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -47,8 +46,6 @@ public class LodDataBuilder
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IBlockStateWrapper AIR = SingletonInjector.INSTANCE.get(IWrapperFactory.class).getAirBlockStateWrapper();
/** how many chunks wide the {@link FullDataSourceV2} is. */
private static final int NUMB_OF_CHUNKS_WIDE = FullDataSourceV2.WIDTH / LodUtil.CHUNK_WIDTH;
private static boolean getTopErrorLogged = false;
@@ -67,12 +64,8 @@ public class LodDataBuilder
// get the section position
int sectionPosX = chunkWrapper.getChunkPos().x;
// negative positions start at -1 so the logic there is slightly different
sectionPosX = (sectionPosX < 0) ? ((sectionPosX + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosX / NUMB_OF_CHUNKS_WIDE);
int sectionPosZ = chunkWrapper.getChunkPos().z;
sectionPosZ = (sectionPosZ < 0) ? ((sectionPosZ + 1) / NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPosZ / NUMB_OF_CHUNKS_WIDE);
int sectionPosX = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().x);
int sectionPosZ = getXOrZSectionPosFromChunkPos(chunkWrapper.getChunkPos().z);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
@@ -98,30 +91,30 @@ public class LodDataBuilder
// -3 -> 1
// -4 -> 0 ---
// -5 -> 3
chunkOffsetX = ((chunkOffsetX) % NUMB_OF_CHUNKS_WIDE);
chunkOffsetX = ((chunkOffsetX) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetX != 0)
{
chunkOffsetX += NUMB_OF_CHUNKS_WIDE;
chunkOffsetX += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetX %= NUMB_OF_CHUNKS_WIDE;
chunkOffsetX %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetX *= LodUtil.CHUNK_WIDTH;
int chunkOffsetZ = chunkWrapper.getChunkPos().z;
if (chunkWrapper.getChunkPos().z < 0)
{
chunkOffsetZ = ((chunkOffsetZ) % NUMB_OF_CHUNKS_WIDE);
chunkOffsetZ = ((chunkOffsetZ) % FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
if (chunkOffsetZ != 0)
{
chunkOffsetZ += NUMB_OF_CHUNKS_WIDE;
chunkOffsetZ += FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
}
else
{
chunkOffsetZ %= NUMB_OF_CHUNKS_WIDE;
chunkOffsetZ %= FullDataSourceV2.NUMB_OF_CHUNKS_WIDE;
}
chunkOffsetZ *= LodUtil.CHUNK_WIDTH;
@@ -212,6 +205,7 @@ public class LodDataBuilder
longs.add(FullDataPointUtil.encode(mappedId, lastY - y, y + 1 - chunkWrapper.getMinBuildHeight(), blockLight, skyLight));
biome = newBiome;
blockState = newBlockState;
mappedId = dataSource.mapping.addIfNotPresentAndGetId(biome, blockState);
blockLight = newBlockLight;
skyLight = newSkyLight;
@@ -296,19 +290,28 @@ public class LodDataBuilder
// this block isn't on a chunk boundary, check if it is next to a transparent/air block
IBlockStateWrapper blockState = chunkWrapper.getBlockState(testBlockPos);
return blockState.isAir() || blockState.getOpacity() != IBlockStateWrapper.FULLY_OPAQUE;
return blockState.isAir() || blockState.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE;
}
/** @throws ClassCastException if an API user returns the wrong object type(s) */
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk dataPoints) throws ClassCastException, DataCorruptedException
public static FullDataSourceV2 createFromApiChunkData(DhApiChunk apiChunk) throws ClassCastException, DataCorruptedException
{
FullDataSourceV2 accessor = FullDataSourceV2.createEmpty(DhSectionPos.encode(new DhChunkPos(dataPoints.chunkPosX, dataPoints.chunkPosZ)));
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
// get the section position
int sectionPosX = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosX);
int sectionPosZ = getXOrZSectionPosFromChunkPos(apiChunk.chunkPosZ);
long pos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
// chunk relative block position in the data source
int relSourceBlockX = Math.floorMod(apiChunk.chunkPosX, 4) * LodUtil.CHUNK_WIDTH;
int relSourceBlockZ = Math.floorMod(apiChunk.chunkPosZ, 4) * LodUtil.CHUNK_WIDTH;
FullDataSourceV2 dataSource = FullDataSourceV2.createEmpty(pos);
for (int relBlockZ = 0; relBlockZ < LodUtil.CHUNK_WIDTH; relBlockZ++)
{
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++)
for (int relBlockX = 0; relBlockX < LodUtil.CHUNK_WIDTH; relBlockX++)
{
List<DhApiTerrainDataPoint> columnDataPoints = dataPoints.getDataPoints(relX, relZ);
List<DhApiTerrainDataPoint> columnDataPoints = apiChunk.getDataPoints(relBlockX, relBlockZ);
// this null check does 2 nice things at the same time:
@@ -322,7 +325,7 @@ public class LodDataBuilder
{
DhApiTerrainDataPoint dataPoint = columnDataPoints.get(index);
int id = accessor.mapping.addIfNotPresentAndGetId(
int id = dataSource.mapping.addIfNotPresentAndGetId(
(IBiomeWrapper) (dataPoint.biomeWrapper),
(IBlockStateWrapper) (dataPoint.blockStateWrapper)
);
@@ -330,7 +333,7 @@ public class LodDataBuilder
packedDataPoints.set(index, FullDataPointUtil.encode(
id,
dataPoint.topYBlockPos - dataPoint.bottomYBlockPos,
dataPoint.bottomYBlockPos - dataPoints.topYBlockPos,
dataPoint.bottomYBlockPos - apiChunk.bottomYBlockPos,
(byte) (dataPoint.blockLightLevel),
(byte) (dataPoint.skyLightLevel)
));
@@ -338,11 +341,14 @@ public class LodDataBuilder
// TODO add the ability for API users to define a different compression mode
// or add a "unkown" compression mode
accessor.setSingleColumn(packedDataPoints, relX, relZ, EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
dataSource.setSingleColumn(
packedDataPoints,
relBlockX + relSourceBlockX, relBlockZ + relSourceBlockZ,
EDhApiWorldGenerationStep.LIGHT, EDhApiWorldCompressionMode.MERGE_SAME_BLOCKS);
dataSource.isEmpty = false;
}
}
return accessor;
return dataSource;
}
@@ -353,4 +359,13 @@ public class LodDataBuilder
public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); }
public static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos)
{
// get the section position
int sectionPos = chunkXOrZPos;
// negative positions start at -1 so the logic there is slightly different
sectionPos = (sectionPos < 0) ? ((sectionPos + 1) / FullDataSourceV2.NUMB_OF_CHUNKS_WIDE) - 1 : (sectionPos / FullDataSourceV2.NUMB_OF_CHUNKS_WIDE);
return sectionPos;
}
}
@@ -25,7 +25,7 @@ import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
import com.seibel.distanthorizons.core.util.math.Vec3i;
/**
* An (almost) exact copy of Minecraft's
@@ -41,13 +41,17 @@ import java.util.*;
*/
public class ClientOnlySaveStructure extends AbstractSaveStructure
{
final File folder;
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
public static final String SERVER_DATA_FOLDER_NAME = "Distant_Horizons_server_data";
public static final String REPLAY_SERVER_FOLDER_NAME = "REPLAY";
public static final String INVALID_FILE_CHARACTERS_REGEX = "[\\\\/:*?\"<>|]";
SubDimensionLevelMatcher subDimMatcher = null;
final HashMap<ILevelWrapper, File> levelWrapperToFileMap = new HashMap<>();
private static final IMinecraftClientWrapper MC_CLIENT = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftSharedWrapper MC_SHARED = SingletonInjector.INSTANCE.get(IMinecraftSharedWrapper.class);
private SubDimensionLevelMatcher subDimMatcher = null;
private final File folder;
private final HashMap<ILevelWrapper, File> levelWrapperToFileMap = new HashMap<>();
@@ -237,7 +241,7 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
private static String getSaveStructureFolderPath()
{
String path = MC_SHARED.getInstallationDirectory().getPath() + File.separatorChar
+ "Distant_Horizons_server_data" + File.separatorChar
+ SERVER_DATA_FOLDER_NAME + File.separatorChar
+ getServerFolderName();
return path;
}
@@ -245,6 +249,14 @@ public class ClientOnlySaveStructure extends AbstractSaveStructure
/** Generated from the server the client is currently connected to. */
private static String getServerFolderName()
{
// if connected to a replay we won't have any server info
// use the dedicated replay server folder
if (MC_CLIENT.connectedToReplay())
{
return REPLAY_SERVER_FOLDER_NAME;
}
// parse the current server's IP
ParsedIp parsedIp = new ParsedIp(MC_CLIENT.getCurrentServerIp());
String serverIpCleaned = parsedIp.ip.replaceAll(INVALID_FILE_CHARACTERS_REGEX, "");
@@ -190,7 +190,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
}
FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk);
// convert to a data source for easier comparing
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encode(this.playerData.playerBlockPos));
FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos));
newDataSource.update(newChunkSizedFullDataView);
@@ -215,7 +215,7 @@ public class SubDimensionLevelMatcher implements AutoCloseable
// get the data source to compare against
try (IDhLevel tempLevel = new DhClientLevel(new ClientOnlySaveStructure(), this.currentClientLevel, testLevelFolder, false, null))
{
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encode(this.playerData.playerBlockPos)).join();
testFullDataSource = tempLevel.getFullDataProvider().getAsync(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos)).join();
if (testFullDataSource == null)
{
continue;
@@ -0,0 +1,117 @@
package com.seibel.distanthorizons.core.generation;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
/** holds adjacent chunks without having to create new Pos objects */
public class AdjacentChunkHolder
{
final IChunkWrapper[] chunkArray = new IChunkWrapper[9];
//==============//
// constructors //
//==============//
public AdjacentChunkHolder(IChunkWrapper centerWrapper) { this.chunkArray[4] = centerWrapper; }
public AdjacentChunkHolder(IChunkWrapper centerWrapper, @NotNull ArrayList<IChunkWrapper> nearbyChunkList)
{
this.chunkArray[4] = centerWrapper;
DhChunkPos centerChunkPos = centerWrapper.getChunkPos();
// generate the list of chunk pos we need,
// currently a 3x3 grid
HashSet<DhChunkPos> requestedAdjacentPositions = new HashSet<>(9);
for (int xOffset = -1; xOffset <= 1; xOffset++)
{
for (int zOffset = -1; zOffset <= 1; zOffset++)
{
DhChunkPos adjacentPos = new DhChunkPos(centerChunkPos.x + xOffset, centerChunkPos.z + zOffset);
requestedAdjacentPositions.add(adjacentPos);
}
}
for (int chunkIndex = 0; chunkIndex < nearbyChunkList.size(); chunkIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead
{
IChunkWrapper chunk = nearbyChunkList.get(chunkIndex);
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos()))
{
// remove the newly found position
requestedAdjacentPositions.remove(chunk.getChunkPos());
// add the adjacent chunk
this.add(chunk);
}
if (requestedAdjacentPositions.isEmpty())
{
// we found every chunk we needed, we don't need to keep iterating
break;
}
}
}
//=========//
// methods //
//=========//
public void add(IChunkWrapper centerWrapper)
{
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
DhChunkPos offsetPos = centerWrapper.getChunkPos();
int offsetX = offsetPos.x - centerPos.x;
if (offsetX < -1 || offsetX > 1)
{
return;
}
int offsetZ = offsetPos.z - centerPos.z;
if (offsetZ < -1 || offsetZ > 1)
{
return;
}
// equivalent to 4 + offsetX + (offsetZ * 3).
this.chunkArray[4 + offsetX + offsetZ + (offsetZ << 1)] = centerWrapper;
}
public IChunkWrapper getByBlockPos(int blockX, int blockZ)
{
int chunkX = BitShiftUtil.divideByPowerOfTwo(blockX, 4);
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
IChunkWrapper centerChunk = this.chunkArray[4];
DhChunkPos centerPos = centerChunk.getChunkPos();
if (centerPos.x == chunkX && centerPos.z == chunkZ)
{
return centerChunk;
}
int offsetX = chunkX - centerPos.x;
if (offsetX < -1 || offsetX > 1)
{
return null;
}
int offsetZ = chunkZ - centerPos.z;
if (offsetZ < -1 || offsetZ > 1)
{
return null;
}
// equivalent to 4 + offsetX + (offsetZ * 3).
return this.chunkArray[4 + offsetX + offsetZ + (offsetZ << 1)];
}
}
@@ -26,7 +26,6 @@ import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
import org.apache.logging.log4j.Logger;
import java.util.*;
@@ -152,7 +151,7 @@ public class DhLightingEngine
for (int y = maxY; y >= minY; y--)
{
IBlockStateWrapper block = chunk.getBlockState(relX, y, relZ);
if (block != null && block.getOpacity() != IBlockStateWrapper.FULLY_TRANSPARENT)
if (block != null && block.getOpacity() != LodUtil.BLOCK_FULLY_TRANSPARENT)
{
// keep moving down until we find a non-transparent block
break;
@@ -306,64 +305,6 @@ public class DhLightingEngine
}
/** holds the adjacent chunks without having to create new Pos objects */
private static class AdjacentChunkHolder
{
final IChunkWrapper[] chunkArray = new IChunkWrapper[9];
public AdjacentChunkHolder(IChunkWrapper centerWrapper) { this.chunkArray[4] = centerWrapper; }
public void add(IChunkWrapper centerWrapper)
{
DhChunkPos centerPos = this.chunkArray[4].getChunkPos();
DhChunkPos offsetPos = centerWrapper.getChunkPos();
int offsetX = offsetPos.x - centerPos.x;
if (offsetX < -1 || offsetX > 1)
{
return;
}
int offsetZ = offsetPos.z - centerPos.z;
if (offsetZ < -1 || offsetZ > 1)
{
return;
}
// equivalent to 4 + offsetX + (offsetZ * 3).
this.chunkArray[4 + offsetX + offsetZ + (offsetZ << 1)] = centerWrapper;
}
public IChunkWrapper getByBlockPos(int blockX, int blockZ)
{
int chunkX = BitShiftUtil.divideByPowerOfTwo(blockX, 4);
int chunkZ = BitShiftUtil.divideByPowerOfTwo(blockZ, 4);
IChunkWrapper centerChunk = this.chunkArray[4];
DhChunkPos centerPos = centerChunk.getChunkPos();
if (centerPos.x == chunkX && centerPos.z == chunkZ)
{
return centerChunk;
}
int offsetX = chunkX - centerPos.x;
if (offsetX < -1 || offsetX > 1)
{
return null;
}
int offsetZ = chunkZ - centerPos.z;
if (offsetZ < -1 || offsetZ > 1)
{
return null;
}
// equivalent to 4 + offsetX + (offsetZ * 3).
return this.chunkArray[4 + offsetX + offsetZ + (offsetZ << 1)];
}
}
/**
* Holds all potential {@link LightPos} objects a lighting task may need.
* This is done so existing {@link LightPos} objects can be repurposed instead of destroyed,
@@ -79,7 +79,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// TODO this logic isn't great and can cause a limit to how many threads could be used for world generation,
// however it won't cause duplicate requests or concurrency issues, so it will be good enough for now.
// A good long term fix may be to either:
// 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller one
// 1. allow the generator to deal with larger sections (let the generator threads split up larger tasks into smaller ones
// 2. batch requests better. instead of sending 4 individual tasks of detail level N, send 1 task of detail level n+1
private final ExecutorService queueingThread = ThreadUtil.makeSingleThreadPool("World Gen Queue");
private boolean generationQueueRunning = false;
@@ -227,6 +227,9 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
catch (Exception e)
{
LOGGER.error("queueing exception: " + e.getMessage(), e);
}
finally
{
this.generationQueueRunning = false;
}
});
@@ -374,7 +377,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
// don't log the shutdown exceptions
if (!LodUtil.isInterruptOrReject(exception))
{
LOGGER.error("Error generating data for section " + taskPos, exception);
LOGGER.error("Error generating data for pos: " + DhSectionPos.toString(taskPos), exception);
}
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateFail()));
@@ -384,11 +387,11 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb
newTaskGroup.group.worldGenTasks.forEach(worldGenTask -> worldGenTask.future.complete(WorldGenResult.CreateSuccess(DhSectionPos.encode(granularity, DhSectionPos.getX(taskPos), DhSectionPos.getZ(taskPos)))));
}
boolean worked = this.inProgressGenTasksByLodPos.remove(taskPos, newTaskGroup);
LodUtil.assertTrue(worked);
LodUtil.assertTrue(worked, "Unable to find in progress generator task with position ["+DhSectionPos.toString(taskPos)+"]");
}
catch (Exception e)
{
LOGGER.error("Unexpected error completing world gen task: "+taskPos, e);
LOGGER.error("Unexpected error completing world gen task at pos: ["+DhSectionPos.toString(taskPos)+"].", e);
}
});
@@ -19,15 +19,28 @@
package com.seibel.distanthorizons.core.level;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.dataObjects.transformers.ChunkToLodBuilder;
import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.generic.BeaconRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.CloudRenderHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericRenderObjectFactory;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.dto.ChunkHashDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.sql.repo.ChunkHashRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import org.apache.logging.log4j.Logger;
@@ -35,8 +48,12 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class AbstractDhLevel implements IDhLevel
{
@@ -47,21 +64,37 @@ public abstract class AbstractDhLevel implements IDhLevel
/** if this is null then the other handler is probably null too, but just in case */
@Nullable
public ChunkHashRepo chunkHashRepo;
/** if this is null then the other handler is probably null too, but just in case */
@Nullable
public BeaconBeamRepo beaconBeamRepo;
protected final DelayedFullDataSourceSaveCache delayedFullDataSourceSaveCache = new DelayedFullDataSourceSaveCache(this::onDataSourceSave, 2_000);
/** contains the {@link DhChunkPos} for each {@link DhSectionPos} that are queued to save via {@link AbstractDhLevel#delayedFullDataSourceSaveCache} */
protected final ConcurrentHashMap<Long, HashSet<DhChunkPos>> updatedChunkPosSetBySectionPos = new ConcurrentHashMap<>();
/** Will be null if clouds shouldn't be rendered for this level. */
@Nullable
protected CloudRenderHandler cloudRenderHandler;
protected BeaconRenderHandler beaconRenderHandler;
//=============//
// constructor //
//=============//
protected AbstractDhLevel() { this.chunkToLodBuilder = new ChunkToLodBuilder(); }
protected AbstractDhLevel()
{
this.chunkToLodBuilder = new ChunkToLodBuilder();
}
protected void createAndSetChunkHashRepo(File databaseFile)
/**
* Creating the repos requires access to the level file, which isn't
* available at constructor time.
*/
protected void createAndSetSupportingRepos(File databaseFile)
{
// chunk hash
ChunkHashRepo newChunkHashRepo = null;
try
{
@@ -72,6 +105,41 @@ public abstract class AbstractDhLevel implements IDhLevel
LOGGER.error("Unable to create [ChunkHashRepo], error: ["+e.getMessage()+"].", e);
}
this.chunkHashRepo = newChunkHashRepo;
// beacon beam
BeaconBeamRepo newBeaconBeamRepo = null;
try
{
newBeaconBeamRepo = new BeaconBeamRepo("jdbc:sqlite", databaseFile);
}
catch (SQLException e)
{
LOGGER.error("Unable to create [BeaconBeamRepo], error: ["+e.getMessage()+"].", e);
}
this.beaconBeamRepo = newBeaconBeamRepo;
}
/** handles any setup that needs the repos to be created */
protected void runRepoReliantSetup()
{
GenericObjectRenderer genericRenderer = this.getGenericRenderer();
if (genericRenderer != null)
{
// only add clouds for certain dimension types
if (!this.getLevelWrapper().hasCeiling()
&& !this.getLevelWrapper().getDimensionType().isTheEnd())
{
this.cloudRenderHandler = new CloudRenderHandler(this, genericRenderer);
}
// shouldn't happen, but just in case
if (this.beaconBeamRepo != null)
{
this.beaconRenderHandler = new BeaconRenderHandler(this.beaconBeamRepo, genericRenderer);
}
}
}
@@ -126,6 +194,13 @@ public abstract class AbstractDhLevel implements IDhLevel
}
//=======//
// repos //
//=======//
// chunk hash //
@Override
public int getChunkHash(DhChunkPos pos)
{
@@ -148,11 +223,55 @@ public abstract class AbstractDhLevel implements IDhLevel
//=================//
// beacon handling //
//=================//
@Override
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
{
if (this.beaconRenderHandler != null)
{
this.beaconRenderHandler.setBeaconBeamsForChunk(chunkPos, newBeamList);
}
}
@Override
public void loadBeaconBeamsInPos(long pos)
{
if (this.beaconRenderHandler != null)
{
this.beaconRenderHandler.loadBeaconBeamsInPos(pos);
}
}
@Override
public void unloadBeaconBeamsInPos(long pos)
{
if (this.beaconRenderHandler != null)
{
this.beaconRenderHandler.unloadBeaconBeamsInPos(pos);
}
}
//================//
// base overrides //
//================//
@Override
public void close() { this.chunkToLodBuilder.close(); }
public void close()
{
this.chunkToLodBuilder.close();
if (this.chunkHashRepo != null)
{
this.chunkHashRepo.close();
}
if (this.beaconBeamRepo != null)
{
this.beaconBeamRepo.close();
}
}
}
@@ -27,13 +27,12 @@ import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.AbstractDataSourceHandler;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.LodQuadTree;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
@@ -41,9 +40,7 @@ import org.apache.logging.log4j.Logger;
import javax.annotation.WillNotClose;
import java.io.Closeable;
import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicReference;
public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.IDataSourceUpdateFunc<FullDataSourceV2>
@@ -56,6 +53,14 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
@WillNotClose
public final FullDataSourceProviderV2 fullDataSourceProvider;
public final AtomicReference<ClientRenderState> ClientRenderStateRef = new AtomicReference<>();
/**
* This is handled outside of the {@link ClientRenderState} to prevent destroying
* the {@link GenericObjectRenderer} when changing render distances or enabling/disabling rendering. <br><br>
*
* Destroying the {@link GenericObjectRenderer} would cause any existing bindings to be
* erroneously removed.
*/
public final GenericObjectRenderer genericRenderer = new GenericObjectRenderer();
@@ -108,7 +113,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
}
clientRenderState.close();
clientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider());
clientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider(), this.genericRenderer);
if (!this.ClientRenderStateRef.compareAndSet(null, clientRenderState))
{
//FIXME: How to handle this?
@@ -128,7 +133,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
}
if (isBuffersDirty)
{
clientRenderState.renderer.bufferHandler.MarkAllBuffersDirty();
clientRenderState.lodRenderer.bufferHandler.MarkAllBuffersDirty();
}
}
@@ -141,7 +146,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
public boolean startRenderer(IClientLevelWrapper clientLevelWrapper)
{
// TODO why are we passing in a level wrapper? Our client level is already defined.
ClientRenderState ClientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider());
ClientRenderState ClientRenderState = new ClientRenderState(this.clientLevel, clientLevelWrapper, this.clientLevel.getFullDataProvider(), this.genericRenderer);
if (!this.ClientRenderStateRef.compareAndSet(null, ClientRenderState))
{
LOGGER.warn("Failed to start renderer due to concurrency");
@@ -167,7 +172,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.renderer.drawLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
ClientRenderState.lodRenderer.drawLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
}
public void renderDeferred(DhApiRenderParam renderEventParam, IProfilerWrapper profiler)
@@ -178,7 +183,7 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
// either the renderer hasn't been started yet, or is being reloaded
return;
}
ClientRenderState.renderer.drawDeferredLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
ClientRenderState.lodRenderer.drawDeferredLods(ClientRenderState.clientLevelWrapper, renderEventParam, profiler);
}
public void stopRenderer()
@@ -275,15 +280,25 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
// helper classes //
//================//
public static class ClientRenderState
public static class ClientRenderState implements Closeable
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public final IClientLevelWrapper clientLevelWrapper;
public final LodQuadTree quadtree;
public final LodRenderer renderer;
public final RenderBufferHandler renderBufferHandler;
public final LodRenderer lodRenderer;
public ClientRenderState(IDhClientLevel dhClientLevel, IClientLevelWrapper clientLevelWrapper, FullDataSourceProviderV2 fullDataSourceProvider)
//=============//
// constructor //
//=============//
public ClientRenderState(
IDhClientLevel dhClientLevel, IClientLevelWrapper clientLevelWrapper,
FullDataSourceProviderV2 fullDataSourceProvider,
GenericObjectRenderer genericRenderer)
{
this.clientLevelWrapper = clientLevelWrapper;
@@ -292,17 +307,22 @@ public class ClientLevelModule implements Closeable, AbstractDataSourceHandler.I
0, 0,
fullDataSourceProvider);
RenderBufferHandler renderBufferHandler = new RenderBufferHandler(this.quadtree);
this.renderer = new LodRenderer(renderBufferHandler);
this.renderBufferHandler = new RenderBufferHandler(this.quadtree);
this.lodRenderer = new LodRenderer(this.renderBufferHandler, genericRenderer);
}
//================//
// base overrides //
//================//
@Override
public void close()
{
LOGGER.info("Shutting down " + ClientRenderState.class.getSimpleName());
this.renderer.close();
this.lodRenderer.close();
this.quadtree.close();
}
@@ -34,6 +34,8 @@ import com.seibel.distanthorizons.core.multiplayer.client.FullDataRefreshQueue;
import com.seibel.distanthorizons.core.network.event.ScopedNetworkEventSource;
import com.seibel.distanthorizons.core.network.messages.fullData.FullDataPartialUpdateMessage;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.sql.dto.FullDataSourceV2DTO;
@@ -96,6 +98,7 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
LOGGER.warn("unable to create data folder.");
}
this.levelWrapper = clientLevelWrapper;
this.levelWrapper.setParentLevel(this);
this.saveStructure = saveStructure;
this.networkState = networkState;
@@ -117,7 +120,8 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.clientside = new ClientLevelModule(this);
this.createAndSetChunkHashRepo(this.dataFileHandler.repo.databaseFile);
this.createAndSetSupportingRepos(this.dataFileHandler.repo.databaseFile);
this.runRepoReliantSetup();
if (enableRendering)
{
@@ -298,10 +302,11 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
this.eventSource.close();
}
this.levelWrapper.setParentLevel(null);
this.clientside.close();
super.close();
this.dataFileHandler.close();
LOGGER.info("Closed " + DhClientLevel.class.getSimpleName() + " for " + this.levelWrapper);
LOGGER.info("Closed [" + DhClientLevel.class.getSimpleName() + "] for [" + this.levelWrapper + "]");
}
@Override
@@ -314,6 +319,15 @@ public class DhClientLevel extends AbstractDhLevel implements IDhClientLevel
public boolean hasSkyLight() { return this.levelWrapper.hasSkyLight(); }
@Override
public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override
public RenderBufferHandler getRenderBufferHandler()
{
ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
return (renderState != null) ? renderState.renderBufferHandler : null;
}
@Override
public void onWorldGenTaskComplete(long pos)
{
@@ -24,11 +24,13 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
@@ -66,9 +68,11 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
LOGGER.warn("unable to create data folder.");
}
this.serverLevelWrapper = serverLevelWrapper;
this.serverLevelWrapper.setParentLevel(this);
this.serverside = new ServerLevelModule(this, saveStructure);
this.clientside = new ClientLevelModule(this);
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseFile);
this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
this.runRepoReliantSetup();
LOGGER.info("Started " + DhClientServerLevel.class.getSimpleName() + " for " + serverLevelWrapper + " with saves at " + saveStructure);
}
@@ -102,20 +106,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
if (shouldDoWorldGen && !isWorldGenRunning)
{
// start world gen
// create a new queue
this.serverside.worldGenModule.startWorldGen(this.serverside.fullDataFileHandler, new ServerLevelModule.WorldGenState(this));
// TODO I think this used to queue the world gen
// is it still needed?
// populate the queue based on the current rendering tree
//ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
//Iterator<QuadNode<LodRenderSection>> iterator = renderState.quadtree.leafNodeIterator();
//while (iterator.hasNext())
//{
// QuadNode<LodRenderSection> node = iterator.next();
// //this.serverside.dataFileHandler.getAsync(node.sectionPos);
//}
}
else if (!shouldDoWorldGen && isWorldGenRunning)
{
@@ -160,7 +151,7 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
}
@Override
public IClientLevelWrapper getClientLevelWrapper() { return this.serverLevelWrapper.tryGetClientLevelWrapper(); }
public IClientLevelWrapper getClientLevelWrapper() { return MC_CLIENT.getWrappedClientLevel(); }
@Override
public void clearRenderCache()
@@ -232,6 +223,16 @@ public class DhClientServerLevel extends AbstractDhLevel implements IDhClientLev
}
@Override
public GenericObjectRenderer getGenericRenderer() { return this.clientside.genericRenderer; }
@Override
public RenderBufferHandler getRenderBufferHandler()
{
ClientLevelModule.ClientRenderState renderState = this.clientside.ClientRenderStateRef.get();
return (renderState != null) ? renderState.renderBufferHandler : null;
}
//===============//
// data handling //
@@ -40,9 +40,11 @@ import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IServerPlayerWrapper;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IServerLevelWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import org.apache.logging.log4j.Logger;
import java.text.MessageFormat;
@@ -75,6 +77,10 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
private final ConcurrentMap<Long, DataSourceRequestGroup> requestGroupsByFutureId = new ConcurrentHashMap<>();
//=============//
// constructor //
//=============//
public DhServerLevel(AbstractSaveStructure saveStructure, IServerLevelWrapper serverLevelWrapper, RemotePlayerConnectionHandler remotePlayerConnectionHandler)
{
if (saveStructure.getFullDataFolder(serverLevelWrapper).mkdirs())
@@ -83,7 +89,8 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
}
this.serverLevelWrapper = serverLevelWrapper;
this.serverside = new ServerLevelModule(this, saveStructure);
this.createAndSetChunkHashRepo(this.serverside.fullDataFileHandler.repo.databaseFile);
this.createAndSetSupportingRepos(this.serverside.fullDataFileHandler.repo.databaseFile);
this.runRepoReliantSetup();
LOGGER.info("Started DHLevel for {} with saves at {}", serverLevelWrapper, saveStructure);
@@ -245,6 +252,10 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
this.worldGenPlayerCenteringQueue.add(serverPlayer);
}
//=========//
// methods //
//=========//
public void removePlayer(IServerPlayerWrapper serverPlayer)
{
this.worldGenPlayerCenteringQueue.remove(serverPlayer);
@@ -444,6 +455,18 @@ public class DhServerLevel extends AbstractDhLevel implements IDhServerLevel
}
}
@Override
public GenericObjectRenderer getGenericRenderer()
{
// server-only levels don't support rendering
return null;
}
@Override
public RenderBufferHandler getRenderBufferHandler()
{
// server-only levels don't support rendering
return null;
}
//===========//
@@ -23,8 +23,12 @@ import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSour
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataSourceProviderV2;
import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.concurrent.CompletableFuture;
@@ -44,6 +48,10 @@ public interface IDhLevel extends AutoCloseable
void setChunkHash(DhChunkPos pos, int chunkHash);
void updateChunkAsync(IChunkWrapper chunk);
void loadBeaconBeamsInPos(long pos);
void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> beamList);
void unloadBeaconBeamsInPos(long pos);
FullDataSourceProviderV2 getFullDataProvider();
AbstractSaveStructure getSaveStructure();
@@ -60,5 +68,17 @@ public interface IDhLevel extends AutoCloseable
void addDebugMenuStringsToList(List<String> messageList);
/**
* Will return null if the renderer isn't set up yet. <br>
* Not supported on the server-side.
*/
@Nullable
GenericObjectRenderer getGenericRenderer();
/**
* Will return null if the renderer isn't set up yet. <br>
* Not supported on the server-side.
*/
@Nullable
RenderBufferHandler getRenderBufferHandler();
}
@@ -22,14 +22,13 @@ package com.seibel.distanthorizons.core.logging.f3;
import com.seibel.distanthorizons.core.api.internal.SharedApi;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.render.RenderBufferHandler;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.util.threading.ThreadPoolUtil;
import com.seibel.distanthorizons.core.world.AbstractDhWorld;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.text.NumberFormat;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
@@ -42,23 +41,6 @@ public class F3Screen
//============//
// properties //
//============//
private static WeakReference<RenderBufferHandler> renderBufferHandlerRef = new WeakReference<>(null);
public static void setRenderBufferHandler(@Nullable RenderBufferHandler renderBufferHandler)
{
if (renderBufferHandler != null && renderBufferHandlerRef.get() != null)
{
LOGGER.warn("multiple RenderBufferHandlers are active at once, the F3 menu may not be accurate.");
}
renderBufferHandlerRef = new WeakReference<>(renderBufferHandler);
}
//=================//
// injection point //
//=================//
@@ -106,23 +88,28 @@ public class F3Screen
// chunk updates
messageList.add(SharedApi.INSTANCE.getDebugMenuString());
messageList.add("");
// rendering
RenderBufferHandler renderBufferHandler = renderBufferHandlerRef.get();
if (renderBufferHandler != null)
{
messageList.add(renderBufferHandler.getVboRenderDebugMenuString());
String showPassString = renderBufferHandler.getShadowPassRenderDebugMenuString();
if (showPassString != null)
{
messageList.add(showPassString);
}
messageList.add("");
}
// world / levels
world.addDebugMenuStringsToList(messageList);
for (IDhLevel level : levelIterator)
{
level.addDebugMenuStringsToList(messageList);
// LOD rendering
RenderBufferHandler renderBufferHandler = level.getRenderBufferHandler();
if (renderBufferHandler != null)
{
messageList.add(renderBufferHandler.getVboRenderDebugMenuString());
String showPassString = renderBufferHandler.getShadowPassRenderDebugMenuString();
if (showPassString != null)
{
messageList.add(showPassString);
}
}
// Generic rendering
GenericObjectRenderer genericRenderer = level.getGenericRenderer();
if (genericRenderer != null)
{
messageList.add(genericRenderer.getVboRenderDebugMenuString());
}
}
}
@@ -19,7 +19,8 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Vec3d;
public class DhChunkPos
{
@@ -31,6 +32,10 @@ public class DhChunkPos
//==============//
// constructors //
//==============//
public DhChunkPos(int x, int z)
{
this.x = x;
@@ -57,6 +62,10 @@ public class DhChunkPos
//=========//
// methods //
//=========//
public DhBlockPos center() { return new DhBlockPos(8 + this.x << 4, 0, 8 + this.z << 4); }
public DhBlockPos corner() { return new DhBlockPos(this.x << 4, 0, this.z << 4); }
@@ -70,6 +79,17 @@ public class DhChunkPos
public DhBlockPos2D getMinBlockPos() { return new DhBlockPos2D(this.x << 4, this.z << 4); }
public boolean contains(DhBlockPos pos)
{
int minBlockX = this.getMinBlockX();
int minBlockZ = this.getMinBlockZ();
int maxBlockX = minBlockX + LodUtil.CHUNK_WIDTH;
int maxBlockZ = minBlockZ + LodUtil.CHUNK_WIDTH;
return minBlockX <= pos.x && pos.x <= maxBlockX
&& minBlockZ <= pos.z && pos.z <= maxBlockZ;
}
public long getLong() { return toLong(this.x, this.z); }
public double distance(DhChunkPos other)
@@ -77,6 +97,11 @@ public class DhChunkPos
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(z - other.z, 2));
}
//================//
// base overrides //
//================//
@Override
public boolean equals(Object obj)
{
@@ -19,6 +19,7 @@
package com.seibel.distanthorizons.core.pos;
import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2;
import com.seibel.distanthorizons.core.enums.EDhDirection;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.coreapi.util.BitShiftUtil;
@@ -101,21 +102,32 @@ public class DhSectionPos
return data;
}
public static long encode(DhBlockPos pos) { return encodeBlockPos(pos.x, pos.z); }
public static long encode(DhBlockPos2D pos) { return encodeBlockPos(pos.x, pos.z); }
public static long encodeBlockPos(int blockX, int blockZ)
/** Returns the section pos at the requested detail level containing the given BlockPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhBlockPos pos)
{
long pos = encode(LodUtil.BLOCK_DETAIL_LEVEL, blockX, blockZ);
pos = convertToDetailLevel(pos, DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL);
return pos;
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, false);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, false);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel);
}
public static long encode(DhChunkPos pos) { return encodeChunkPos(pos.x, pos.z); }
public static long encodeChunkPos(int chunkX, int chunkZ)
/** Returns the section pos at the requested detail level containing the given ChunkPos */
public static long encodeContaining(byte outputSectionDetailLevel, DhChunkPos pos)
{
long pos = encode(LodUtil.CHUNK_DETAIL_LEVEL, chunkX, chunkZ);
pos = convertToDetailLevel(pos, DhSectionPos.SECTION_CHUNK_DETAIL_LEVEL);
return pos;
int sectionPosX = getXOrZSectionPosFromChunkOrBlockPos(pos.x, true);
int sectionPosZ = getXOrZSectionPosFromChunkOrBlockPos(pos.z, true);
long blockPos = DhSectionPos.encode(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, sectionPosX, sectionPosZ);
return convertToDetailLevel(blockPos, outputSectionDetailLevel);
}
private static int getXOrZSectionPosFromChunkOrBlockPos(int chunkXOrZPos, boolean isChunkPos)
{
int sectionPos = chunkXOrZPos;
int fullDataSourceWidth = isChunkPos ? FullDataSourceV2.NUMB_OF_CHUNKS_WIDE : (FullDataSourceV2.NUMB_OF_CHUNKS_WIDE * LodUtil.CHUNK_WIDTH);
// negative positions start at -1 so the logic there is slightly different
sectionPos = (sectionPos < 0)
? ((sectionPos + 1) / fullDataSourceWidth) - 1
: (sectionPos / fullDataSourceWidth);
return sectionPos;
}
@@ -1,8 +1,9 @@
package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
@@ -35,12 +36,12 @@ public class DhFrustumBounds implements IDhApiCullingFrustum
//=========//
@Override
public void update(int worldMinBlockY, int worldMaxBlockY, Mat4f dhWorldViewProjection)
public void update(int worldMinBlockY, int worldMaxBlockY, DhApiMat4f dhWorldViewProjection)
{
this.worldMinY = worldMinBlockY;
this.worldMaxY = worldMaxBlockY;
Matrix4f worldViewProjection = new Matrix4f(dhWorldViewProjection.createJomlMatrix());
Matrix4f worldViewProjection = new Matrix4f(Mat4f.createJomlMatrix(dhWorldViewProjection));
this.frustum.set(worldViewProjection);
Matrix4fc matWorldViewProjectionInv = new Matrix4f(worldViewProjection).invert();
@@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.render.renderer.DebugRenderer;
import com.seibel.distanthorizons.core.render.renderer.IDebugRenderable;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.ThreadUtil;
import com.seibel.distanthorizons.core.util.objects.quadTree.QuadNode;
@@ -329,6 +330,10 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
}
// all child positions are loaded, disable this section and enable its children.
if (renderSection.renderingEnabled)
{
this.level.unloadBeaconBeamsInPos(renderSection.pos);
}
renderSection.renderingEnabled = false;
// walk back down the tree and enable the child sections //TODO there are probably more efficient ways of doing this, but this will work for now
@@ -383,6 +388,7 @@ public class LodQuadTree extends QuadTree<LodRenderSection> implements IDebugRen
if (!renderSection.renderingEnabled)
{
renderSection.renderingEnabled = true;
this.level.loadBeaconBeamsInPos(renderSection.pos);
// delete/disable children, all of them will be a lower detail level than requested
quadNode.deleteAllChildren((childRenderSection) ->
@@ -292,7 +292,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}
catch (Exception e)
{
LOGGER.warn("Unable to get render source " + this.pos + ", error: " + e.getMessage(), e);
LOGGER.warn("Unable to get render source " + DhSectionPos.toString(this.pos) + ", error: " + e.getMessage(), e);
this.renderSourceLoadingRefFuture = null;
return null;
}
@@ -416,6 +416,7 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable
}
this.level.unloadBeaconBeamsInPos(this.pos);
if (this.renderBuffer != null)
{
@@ -2,8 +2,9 @@ package com.seibel.distanthorizons.core.render;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiCullingFrustum;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShadowCullingFrustum;
import com.seibel.distanthorizons.api.objects.math.DhApiMat4f;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
/**
* Dummy {@link IDhApiCullingFrustum} that allows everything through. <br>
@@ -24,7 +25,7 @@ public class NeverCullFrustum implements IDhApiCullingFrustum, IDhApiShadowCulli
//=========//
@Override
public void update(int worldMinBlockY, int worldMaxBlockY, Mat4f dhWorldViewProjection) { /* update isn't needed */ }
public void update(int worldMinBlockY, int worldMaxBlockY, DhApiMat4f dhWorldViewProjection) { /* update isn't needed */ }
@Override
public boolean intersects(int lodBlockPosMinX, int lodBlockPosMinZ, int lodBlockWidth, int lodDetailLevel) { return true; }
@@ -41,9 +41,9 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRen
import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccessor;
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IOverrideInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix4f;
import org.joml.Matrix4fc;
@@ -100,9 +100,6 @@ public class RenderBufferHandler implements AutoCloseable
{
DhApi.overrides.bind(IDhApiShadowCullingFrustum.class, new NeverCullFrustum());
}
F3Screen.setRenderBufferHandler(this);
}
@@ -247,7 +244,7 @@ public class RenderBufferHandler implements AutoCloseable
if (enableFrustumCulling)
{
int worldMinY = clientLevelWrapper.getMinHeight();
int worldHeight = clientLevelWrapper.getHeight();
int worldHeight = clientLevelWrapper.getMaxHeight();
Vec3d cameraPos = MC_RENDER.getCameraExactPosition();
@@ -417,8 +414,6 @@ public class RenderBufferHandler implements AutoCloseable
renderSection.close();
}
}
F3Screen.setRenderBufferHandler(null);
}
@@ -173,7 +173,7 @@ public class LodFogConfig
str.append("" +
"float getNearFogThickness(float dist) \n" +
"{ \n" +
" return linearFog(dist, nearFogStart, nearFogLength, 0.0, 1.0); \n" +
" return linearFog(dist, uNearFogStart, uNearFogLength, 0.0, 1.0); \n" +
"} \n");
@@ -182,7 +182,7 @@ public class LodFogConfig
str.append("\n" +
"float getFarFogThickness(float dist) { return 0.0; } \n" +
"float getHeightFogThickness(float dist) { return 0.0; } \n" +
"float calculateFarFogDepth(float horizontal, float dist, float nearFogStart) { return 0.0; } \n" +
"float calculateFarFogDepth(float horizontal, float dist, float uNearFogStart) { return 0.0; } \n" +
"float calculateHeightFogDepth(float vertical, float realY) { return 0.0; } \n" +
"float mixFogThickness(float near, float far, float height) \n" +
"{ \n" +
@@ -215,13 +215,13 @@ public class LodFogConfig
"} \n");
// Generate method: calculateFarFogDepth(float horizontal, float dist, float nearFogStart);
// Generate method: calculateFarFogDepth(float horizontal, float dist, float uNearFogStart);
str.append("" +
"float calculateFarFogDepth(float horizontal, float dist, float nearFogStart) \n" +
"float calculateFarFogDepth(float horizontal, float dist, float uNearFogStart) \n" +
"{ \n" +
" return " + (heightFogMixMode == EDhApiHeightFogMixMode.BASIC ?
"(dist - nearFogStart)/(1.0 - nearFogStart);" :
"(horizontal - nearFogStart)/(1.0 - nearFogStart);") +
"(dist - uNearFogStart)/(1.0 - uNearFogStart);" :
"(horizontal - uNearFogStart)/(1.0 - uNearFogStart);") +
"} \n");
// Generate method: float mixFogThickness(float near, float far, float height);
@@ -27,7 +27,6 @@ import com.seibel.distanthorizons.core.logging.ConfigBasedLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.objects.GLMessage;
import com.seibel.distanthorizons.core.util.objects.GLMessageOutputStream;
import com.seibel.distanthorizons.core.util.objects.Pair;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
@@ -40,8 +39,6 @@ import org.lwjgl.opengl.GLUtil;
import java.io.PrintStream;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
@@ -76,7 +73,9 @@ public class GLProxy
public boolean namedObjectSupported = false; // ~OpenGL 4.5 (UNUSED CURRENTLY)
public boolean bufferStorageSupported = false; // ~OpenGL 4.4
public boolean VertexAttributeBufferBindingSupported = false; // ~OpenGL 4.3
public boolean vertexAttributeBufferBindingSupported = false; // ~OpenGL 4.3
public boolean instancedArraysSupported = false;
public boolean vertexAttribDivisorSupported = false; // OpenGL 3.3 or newer
private final EDhApiGpuUploadMethod preferredUploadMethod;
@@ -91,17 +90,15 @@ public class GLProxy
private GLProxy() throws IllegalStateException
{
// this must be created on minecraft's render context to work correctly
GL_LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see there must have been an OpenGL error.");
GL_LOGGER.info("Lod Render OpenGL version [" + GL32.glGetString(GL32.GL_VERSION) + "].");
// getting Minecraft's context has to be done on the render thread,
// where the GL context is
if (GLFW.glfwGetCurrentContext() == 0L)
{
throw new IllegalStateException(GLProxy.class.getSimpleName() + " was created outside the render thread!");
}
GL_LOGGER.info("Creating " + GLProxy.class.getSimpleName() + "... If this is the last message you see there must have been an OpenGL error.");
GL_LOGGER.info("Lod Render OpenGL version [" + GL32.glGetString(GL32.GL_VERSION) + "].");
//============================//
@@ -135,21 +132,26 @@ public class GLProxy
// get GPU capabilities //
//======================//
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
this.VertexAttributeBufferBindingSupported = this.glCapabilities.glBindVertexBuffer != 0L; // Nullptr
// UNUSED currently
// Check if we can use the named version of all calls, which is available in GL4.5 or after
this.namedObjectSupported = this.glCapabilities.glNamedBufferData != 0L; //Nullptr
// get specific capabilities
// Check if we can use the Buffer Storage, which is available in GL4.4 or after
this.bufferStorageSupported = this.glCapabilities.glBufferStorage != 0L; // Nullptr
if (!this.bufferStorageSupported)
{
GL_LOGGER.warn("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
GL_LOGGER.info("This GPU doesn't support Buffer Storage (OpenGL 4.4), falling back to using other methods.");
}
// Check if we can use the make-over version of Vertex Attribute, which is available in GL4.3 or after
this.vertexAttributeBufferBindingSupported = this.glCapabilities.glBindVertexBuffer != 0L; // Nullptr
// used by instanced rendering
this.vertexAttribDivisorSupported = this.glCapabilities.OpenGL33;
// denotes if ARBInstancedArrays.glVertexAttribDivisorARB() is available or not
// can be used as a backup if MC didn't create a GL 3.3+ context
this.instancedArraysSupported = this.glCapabilities.GL_ARB_instanced_arrays;
// get the best automatic upload method
String vendor = GL32.glGetString(GL32.GL_VENDOR).toUpperCase(); // example return: "NVIDIA CORPORATION"
if (vendor.contains("NVIDIA") || vendor.contains("GEFORCE"))
@@ -20,6 +20,7 @@
package com.seibel.distanthorizons.core.render.glObject.buffer;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.LodUtil;
@@ -140,7 +141,10 @@ public class GLBuffer implements AutoCloseable
GL32.glDeleteBuffers(id);
bufferCount.decrementAndGet();
//LOGGER.info("destroyed buffer ["+id+"], remaining: ["+BUFFER_ID_TO_PHANTOM.size()+"]");
if (Config.Client.Advanced.Debugging.logBufferGarbageCollection.get())
{
LOGGER.info("destroyed buffer [" + id + "], remaining: [" + BUFFER_ID_TO_PHANTOM.size() + "]");
}
}
});
}
@@ -26,12 +26,13 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
import org.lwjgl.opengl.GL32;
import org.lwjgl.system.MemoryStack;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
/**
@@ -154,7 +155,7 @@ public class ShaderProgram
* @return Location of the Uniform
* @throws RuntimeException if uniform not found
*/
public int getUniformLocation(CharSequence name)
public int getUniformLocation(CharSequence name) throws RuntimeException
{
int i = GL32.glGetUniformLocation(id, name);
if (i == -1)
@@ -195,11 +196,10 @@ public class ShaderProgram
{
GL32.glUniform3f(location, value.x, value.y, value.z);
}
/** Requires ShaderProgram binded. */
public void setUniform(int location, Vec3d value)
public void setUniform(int location, DhApiVec3i value)
{
GL32.glUniform3f(location, (float) value.x, (float) value.y, (float) value.z);
GL32.glUniform3i(location, value.x, value.y, value.z);
}
/** Requires ShaderProgram binded. */
@@ -48,7 +48,7 @@ public abstract class AbstractVertexAttribute
public static AbstractVertexAttribute create()
{
if (GLProxy.getInstance().VertexAttributeBufferBindingSupported)
if (GLProxy.getInstance().vertexAttributeBufferBindingSupported)
{
return new VertexAttributePostGL43();
}
@@ -36,9 +36,9 @@ import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.apache.logging.log4j.LogManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -64,13 +64,13 @@ public class DebugRenderer
// rendering setup
private ShaderProgram basicShader;
private GLVertexBuffer boxBuffer;
private GLElementBuffer boxOutlineBuffer;
private GLVertexBuffer vertexBuffer;
private GLElementBuffer outlineIndexBuffer;
private AbstractVertexAttribute va;
private boolean init = false;
// used when rendering
private Mat4f transformThiFrame;
private Mat4f transformationMatrixThisFrame;
private Vec3f camPosFloatThisFrame;
@@ -79,8 +79,8 @@ public class DebugRenderer
// A box from 0,0,0 to 1,1,1
private static final float[] box_vertices = {
/** A box from 0,0,0 to 1,1,1 */
private static final float[] BOX_VERTICES = {
// Pos x y z
0, 0, 0,
1, 0, 0,
@@ -92,7 +92,7 @@ public class DebugRenderer
0, 1, 1,
};
private static final int[] box_outline_indices = {
private static final int[] BOX_OUTLINE_INDICES = {
0, 1,
1, 2,
2, 3,
@@ -115,7 +115,47 @@ public class DebugRenderer
// constructor //
//=============//
public DebugRenderer() { }
private DebugRenderer() { }
public void init()
{
if (this.init)
{
return;
}
this.init = true;
this.va = AbstractVertexAttribute.create();
this.va.bind();
// Pos
this.va.setVertexAttribute(0, 0, VertexPointer.addVec3Pointer(false));
this.va.completeAndCheck(Float.BYTES * 3);
this.basicShader = new ShaderProgram("shaders/debug/vert.vert", "shaders/debug/frag.frag",
"fragColor", new String[]{"vPosition"});
this.createBuffer();
}
private void createBuffer()
{
// box vertices
ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.order(ByteOrder.nativeOrder());
boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES);
boxVerticesBuffer.rewind();
this.vertexBuffer = new GLVertexBuffer(false);
this.vertexBuffer.bind();
this.vertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES);
// outline vertex indexes
ByteBuffer boxOutlineBuffer = ByteBuffer.allocateDirect(BOX_OUTLINE_INDICES.length * Integer.BYTES);
boxOutlineBuffer.order(ByteOrder.nativeOrder());
boxOutlineBuffer.asIntBuffer().put(BOX_OUTLINE_INDICES);
boxOutlineBuffer.rewind();
this.outlineIndexBuffer = new GLElementBuffer(false);
this.outlineIndexBuffer.uploadBuffer(boxOutlineBuffer, EDhApiGpuUploadMethod.DATA, BOX_OUTLINE_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
}
@@ -141,53 +181,13 @@ public class DebugRenderer
//===========//
// rendering //
//===========//
public void init()
{
if (this.init)
{
return;
}
this.init = true;
this.va = AbstractVertexAttribute.create();
this.va.bind();
// Pos
this.va.setVertexAttribute(0, 0, VertexPointer.addVec3Pointer(false));
this.va.completeAndCheck(Float.BYTES * 3);
this.basicShader = new ShaderProgram("shaders/debug/vert.vert", "shaders/debug/frag.frag",
"fragColor", new String[]{"vPosition"});
this.createBuffer();
}
private void createBuffer()
{
ByteBuffer buffer = ByteBuffer.allocateDirect(box_vertices.length * Float.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asFloatBuffer().put(box_vertices);
buffer.rewind();
this.boxBuffer = new GLVertexBuffer(false);
this.boxBuffer.bind();
this.boxBuffer.uploadBuffer(buffer, 8, EDhApiGpuUploadMethod.DATA, box_vertices.length * Float.BYTES);
buffer = ByteBuffer.allocateDirect(box_outline_indices.length * Integer.BYTES);
buffer.order(ByteOrder.nativeOrder());
buffer.asIntBuffer().put(box_outline_indices);
buffer.rewind();
this.boxOutlineBuffer = new GLElementBuffer(false);
this.boxOutlineBuffer.uploadBuffer(buffer, EDhApiGpuUploadMethod.DATA, box_outline_indices.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
}
public void render(Mat4f transform)
{
this.transformThiFrame = transform;
this.transformationMatrixThisFrame = transform;
Vec3d camPos = MC_RENDER.getCameraExactPosition();
this.camPosFloatThisFrame = new Vec3f((float) camPos.x, (float) camPos.y, (float) camPos.z);
@@ -199,22 +199,23 @@ public class DebugRenderer
this.basicShader.bind();
this.va.bind();
this.va.bindBufferToAllBindingPoints(this.boxBuffer.getId());
this.va.bindBufferToAllBindingPoints(this.vertexBuffer.getId());
this.boxOutlineBuffer.bind();
this.outlineIndexBuffer.bind();
this.rendererLists.render(this);
BoxParticle head = null;
while ((head = this.particles.poll()) != null && head.isDead(System.nanoTime()))
{
}
{ /* remove dead particles */ }
if (head != null)
{
// re-add the popped off head
this.particles.add(head);
}
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
for (BoxParticle particle : this.particles)
{
this.renderBox(particle.getBox());
@@ -226,13 +227,13 @@ public class DebugRenderer
public void renderBox(Box box)
{
Mat4f boxTransform = Mat4f.createTranslateMatrix(box.a.x - this.camPosFloatThisFrame.x, box.a.y - this.camPosFloatThisFrame.y, box.a.z - this.camPosFloatThisFrame.z);
boxTransform.multiply(Mat4f.createScaleMatrix(box.b.x - box.a.x, box.b.y - box.a.y, box.b.z - box.a.z));
Mat4f t = this.transformThiFrame.copy();
Mat4f boxTransform = Mat4f.createTranslateMatrix(box.minPos.x - this.camPosFloatThisFrame.x, box.minPos.y - this.camPosFloatThisFrame.y, box.minPos.z - this.camPosFloatThisFrame.z);
boxTransform.multiply(Mat4f.createScaleMatrix(box.maxPos.x - box.minPos.x, box.maxPos.y - box.minPos.y, box.maxPos.z - box.minPos.z));
Mat4f t = this.transformationMatrixThisFrame.copy();
t.multiply(boxTransform);
this.basicShader.setUniform(this.basicShader.getUniformLocation("transform"), t);
this.basicShader.setUniform(this.basicShader.getUniformLocation("uTransform"), t);
this.basicShader.setUniform(this.basicShader.getUniformLocation("uColor"), box.color);
GL32.glDrawElements(GL32.GL_LINES, box_outline_indices.length, GL32.GL_UNSIGNED_INT, 0);
GL32.glDrawElements(GL32.GL_LINES, BOX_OUTLINE_INDICES.length, GL32.GL_UNSIGNED_INT, 0);
}
@@ -243,25 +244,25 @@ public class DebugRenderer
public static final class Box
{
public Vec3f a;
public Vec3f b;
public Vec3f minPos;
public Vec3f maxPos;
public Color color;
public Box(Vec3f a, Vec3f b, Color color)
public Box(Vec3f minPos, Vec3f maxPos, Color color)
{
this.a = a;
this.b = b;
this.minPos = minPos;
this.maxPos = maxPos;
this.color = color;
}
public Box(Vec3f a, Vec3f b, Color color, Vec3f margin)
public Box(Vec3f minPos, Vec3f maxPos, Color color, Vec3f margin)
{
this.a = a;
this.a.add(margin);
this.b = b;
this.b.subtract(margin);
this.minPos = minPos;
this.minPos.add(margin);
this.maxPos = maxPos;
this.maxPos.subtract(margin);
this.color = color;
}
@@ -272,8 +273,8 @@ public class DebugRenderer
float edge = pos.getBlockWidth() * marginPercent;
Vec3f a = new Vec3f(blockMin.x + edge, minY, blockMin.z + edge);
Vec3f b = new Vec3f(blockMax.x - edge, maxY, blockMax.z - edge);
this.a = a;
this.b = b;
this.minPos = a;
this.maxPos = b;
this.color = color;
}
@@ -285,8 +286,8 @@ public class DebugRenderer
float edge = pos.getBlockWidth() * marginPercent;
Vec3f a = new Vec3f(blockMin.x + edge, hashY, blockMin.z + edge);
Vec3f b = new Vec3f(blockMax.x - edge, hashY, blockMax.z - edge);
this.a = a;
this.b = b;
this.minPos = a;
this.maxPos = b;
this.color = color;
}
@@ -334,7 +335,7 @@ public class DebugRenderer
float percent = (now - this.startTime) / (float) this.duration;
percent = (float) Math.pow(percent, 4);
float yDiff = this.yChange * percent;
return new Box(new Vec3f(this.box.a.x, this.box.a.y + yDiff, this.box.a.z), new Vec3f(this.box.b.x, this.box.b.y + yDiff, this.box.b.z), this.box.color);
return new Box(new Vec3f(this.box.minPos.x, this.box.minPos.y + yDiff, this.box.minPos.z), new Vec3f(this.box.maxPos.x, this.box.maxPos.y + yDiff, this.box.maxPos.z), this.box.color);
}
public boolean isDead(long time) { return (time - this.startTime) > this.duration; }
@@ -350,7 +351,7 @@ public class DebugRenderer
public BoxWithLife(Box box, long ns, float yChange, Color deathColor)
{
this.box = box;
this.particaleOnClose = new BoxParticle(new Box(box.a, box.b, deathColor), -1, ns, yChange);
this.particaleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), -1, ns, yChange);
register(this, null);
}
@@ -360,7 +361,7 @@ public class DebugRenderer
public BoxWithLife(Box box, double s, float yChange, Color deathColor)
{
this.box = box;
this.particaleOnClose = new BoxParticle(new Box(box.a, box.b, deathColor), s, yChange);
this.particaleOnClose = new BoxParticle(new Box(box.minPos, box.maxPos, deathColor), s, yChange);
}
public BoxWithLife(Box box, double s, float yChange) { this(box, s, yChange, box.color); }
@@ -21,6 +21,7 @@ package com.seibel.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiShaderProgram;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.shader.Shader;
@@ -31,39 +32,38 @@ import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexAtt
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.RenderUtil;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3f;
public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgram
/**
* Handles rendering the normal LOD terrain.
*/
public class DhTerrainShaderProgram extends ShaderProgram implements IDhApiShaderProgram
{
public static final String VERTEX_SHADER_PATH = "shaders/standard.vert";
public static final String VERTEX_CURVE_SHADER_PATH = "shaders/curve.vert";
public static final String FRAGMENT_SHADER_PATH = "shaders/flat_shaded.frag";
public final AbstractVertexAttribute vao;
// Uniforms
public final int combinedMatUniform;
public final int modelOffsetUniform;
public final int worldYOffsetUniform;
public final int uCombinedMatrix;
public final int uModelOffset;
public final int uWorldYOffset;
public final int mircoOffsetUniform;
public final int uMircoOffset;
public final int earthRadiusUniform;
public final int uEarthRadius;
public final int lightMapUniform;
public final int uLightMap;
// Fog/Clip Uniforms
public final int clipDistanceUniform;
public final int uClipDistance;
// Noise Uniforms
public final int noiseEnabledUniform;
public final int noiseStepsUniform;
public final int noiseIntensityUniform;
public final int noiseDropoffUniform;
public final int uNoiseEnabled;
public final int uNoiseSteps;
public final int uNoiseIntensity;
public final int uNoiseDropoff;
// Debug Uniform
public final int whiteWorldUniform;
public final int uWhiteWorld;
@@ -72,50 +72,57 @@ public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgr
//=============//
// This will bind AbstractVertexAttribute
public LodRenderProgram()
public DhTerrainShaderProgram()
{
super(() -> Shader.loadFile(Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get() != 0 ? VERTEX_CURVE_SHADER_PATH : VERTEX_SHADER_PATH,
super(
() -> Shader.loadFile(Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get() != 0
? "shaders/curve.vert"
: "shaders/standard.vert",
false, new StringBuilder()).toString(),
() -> Shader.loadFile(FRAGMENT_SHADER_PATH, false, new StringBuilder()).toString(),
() -> Shader.loadFile("shaders/flat_shaded.frag", false, new StringBuilder()).toString(),
"fragColor", new String[]{"vPosition", "color"});
combinedMatUniform = getUniformLocation("combinedMatrix");
modelOffsetUniform = getUniformLocation("modelOffset");
worldYOffsetUniform = tryGetUniformLocation("worldYOffset");
mircoOffsetUniform = getUniformLocation("mircoOffset");
earthRadiusUniform = tryGetUniformLocation("earthRadius");
this.uCombinedMatrix = this.getUniformLocation("uCombinedMatrix");
this.uModelOffset = this.getUniformLocation("uModelOffset");
this.uWorldYOffset = this.tryGetUniformLocation("uWorldYOffset");
this.uMircoOffset = this.getUniformLocation("uMircoOffset");
this.uEarthRadius = this.tryGetUniformLocation("uEarthRadius");
lightMapUniform = getUniformLocation("lightMap");
this.uLightMap = this.getUniformLocation("uLightMap");
// Fog/Clip Uniforms
clipDistanceUniform = getUniformLocation("clipDistance");
this.uClipDistance = this.getUniformLocation("uClipDistance");
// Noise Uniforms
noiseEnabledUniform = getUniformLocation("noiseEnabled");
noiseStepsUniform = getUniformLocation("noiseSteps");
noiseIntensityUniform = getUniformLocation("noiseIntensity");
noiseDropoffUniform = getUniformLocation("noiseDropoff");
this.uNoiseEnabled = this.getUniformLocation("uNoiseEnabled");
this.uNoiseSteps = this.getUniformLocation("uNoiseSteps");
this.uNoiseIntensity = this.getUniformLocation("uNoiseIntensity");
this.uNoiseDropoff = this.getUniformLocation("uNoiseDropoff");
// Debug Uniform
whiteWorldUniform = getUniformLocation("whiteWorld");
this.uWhiteWorld = this.getUniformLocation("uWhiteWorld");
// TODO: Add better use of the LODFormat thing
int vertexByteCount = LodUtil.LOD_VERTEX_FORMAT.getByteSize();
if (GLProxy.getInstance().VertexAttributeBufferBindingSupported)
vao = new VertexAttributePostGL43(); // also binds AbstractVertexAttribute
if (GLProxy.getInstance().vertexAttributeBufferBindingSupported)
{
this.vao = new VertexAttributePostGL43(); // also binds AbstractVertexAttribute
}
else
vao = new VertexAttributePreGL43(); // also binds AbstractVertexAttribute
vao.bind();
{
this.vao = new VertexAttributePreGL43(); // also binds AbstractVertexAttribute
}
this.vao.bind();
// TODO comment what each attribute represents
vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos
vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ?
vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID
this.vao.setVertexAttribute(0, 0, VertexPointer.addUnsignedShortsPointer(4, false, true)); // 2+2+2+2 // TODO probably color, blockpos
this.vao.setVertexAttribute(0, 1, VertexPointer.addUnsignedBytesPointer(4, true, false)); // +4 // TODO ?
this.vao.setVertexAttribute(0, 2, VertexPointer.addUnsignedBytesPointer(4, true, true)); // +4 // TODO probably normal index and Iris block ID
try
{
vao.completeAndCheck(vertexByteCount);
this.vao.completeAndCheck(vertexByteCount);
}
catch (RuntimeException e)
{
@@ -123,15 +130,15 @@ public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgr
throw e;
}
if (earthRadiusUniform != -1) setUniform(earthRadiusUniform,
if (this.uEarthRadius != -1) this.setUniform(this.uEarthRadius,
/*6371KM*/ 6371000.0f / Config.Client.Advanced.Graphics.AdvancedGraphics.earthCurveRatio.get());
// Noise Uniforms
setUniform(noiseEnabledUniform, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseEnabled.get());
setUniform(noiseStepsUniform, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseSteps.get());
setUniform(noiseIntensityUniform, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseIntensity.get().floatValue());
setUniform(noiseDropoffUniform, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseDropoff.get());
this.setUniform(this.uNoiseEnabled, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseEnabled.get());
this.setUniform(this.uNoiseSteps, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseSteps.get());
this.setUniform(this.uNoiseIntensity, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseIntensity.get().floatValue());
this.setUniform(this.uNoiseDropoff, Config.Client.Advanced.Graphics.NoiseTextureSettings.noiseDropoff.get());
}
@@ -144,27 +151,25 @@ public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgr
public void bind()
{
super.bind();
vao.bind();
this.vao.bind();
}
@Override
public void unbind()
{
super.unbind();
vao.unbind();
this.vao.unbind();
}
@Override
public void free()
{
vao.free();
this.vao.free();
super.free();
}
@Override
public void bindVertexBuffer(int vbo) { this.vao.bindBufferToAllBindingPoints(vbo); }
public void unbindVertexBuffer() { this.vao.unbindBuffersFromAllBindingPoint(); }
@Override
public void fillUniformData(DhApiRenderParam renderParameters)
{
@@ -174,16 +179,16 @@ public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgr
super.bind();
// uniforms
setUniform(combinedMatUniform, combinedMatrix);
setUniform(mircoOffsetUniform, 0.01f); // 0.01 block offset
this.setUniform(this.uCombinedMatrix, combinedMatrix);
this.setUniform(this.uMircoOffset, 0.01f); // 0.01 block offset
// setUniform(skyLightUniform, skyLight);
setUniform(lightMapUniform, 0); // TODO this should probably be passed in
this.setUniform(this.uLightMap, 0); // TODO this should probably be passed in
if (worldYOffsetUniform != -1) setUniform(worldYOffsetUniform, (float) renderParameters.worldYOffset);
if (this.uWorldYOffset != -1) this.setUniform(this.uWorldYOffset, (float) renderParameters.worldYOffset);
// Debug
setUniform(whiteWorldUniform, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
this.setUniform(this.uWhiteWorld, Config.Client.Advanced.Debugging.enableWhiteWorld.get());
// Clip Uniform
float dhNearClipDistance = RenderUtil.getNearClipPlaneDistanceInBlocks(renderParameters.partialTicks);
@@ -197,11 +202,11 @@ public class LodRenderProgram extends ShaderProgram implements IDhApiShaderProgr
{
dhNearClipDistance = 1.0f;
}
this.setUniform(this.clipDistanceUniform, dhNearClipDistance);
this.setUniform(this.uClipDistance, dhNearClipDistance);
}
@Override
public void setModelOffsetPos(Vec3f modelOffsetPos) { this.setUniform(this.modelOffsetUniform, modelOffsetPos); }
public void setModelOffsetPos(DhApiVec3f modelOffsetPos) { this.setUniform(this.uModelOffset, new Vec3f(modelOffsetPos)); }
@Override
public int getId() { return this.id; }
@@ -0,0 +1,135 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.renderer.shaders.FogApplyShader;
import com.seibel.distanthorizons.core.render.renderer.shaders.FogShader;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
/**
* Handles adding SSAO via {@link FogShader} and {@link FogApplyShader}. <br><br>
*
* {@link FogShader} - draws the Fog to a texture. <br>
* {@link FogApplyShader} - draws the Fog texture to DH's FrameBuffer. <br>
*/
public class FogRenderer
{
public static FogRenderer INSTANCE = new FogRenderer();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private boolean init = false;
private int width = -1;
private int height = -1;
private int fogFramebuffer = -1;
private int fogTexture = -1;
//=============//
// constructor //
//=============//
private FogRenderer() { }
public void init()
{
if (this.init) return;
this.init = true;
FogShader.INSTANCE.init();
FogApplyShader.INSTANCE.init();
}
private void createFramebuffer(int width, int height)
{
if (this.fogFramebuffer != -1)
{
GL32.glDeleteFramebuffers(this.fogFramebuffer);
this.fogFramebuffer = -1;
}
if (this.fogTexture != -1)
{
GL32.glDeleteTextures(this.fogTexture);
this.fogTexture = -1;
}
this.fogFramebuffer = GL32.glGenFramebuffers();
GL32.glBindFramebuffer(GL32.GL_FRAMEBUFFER, this.fogFramebuffer);
this.fogTexture = GL32.glGenTextures();
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.fogTexture);
GL32.glTexImage2D(GL32.GL_TEXTURE_2D, 0, GL32.GL_RGBA16, width, height, 0, GL32.GL_RGBA, GL32.GL_UNSIGNED_SHORT_4_4_4_4, (ByteBuffer) null);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MIN_FILTER, GL32.GL_LINEAR);
GL32.glTexParameteri(GL32.GL_TEXTURE_2D, GL32.GL_TEXTURE_MAG_FILTER, GL32.GL_LINEAR);
GL32.glFramebufferTexture2D(GL32.GL_FRAMEBUFFER, GL32.GL_COLOR_ATTACHMENT0, GL32.GL_TEXTURE_2D, this.fogTexture, 0);
}
//========//
// render //
//========//
public void render(GLState primaryState, Mat4f projectionMatrix, float partialTicks)
{
GLState state = new GLState();
this.init();
// resize the framebuffer if necessary
int width = MC_RENDER.getTargetFrameBufferViewportWidth();
int height = MC_RENDER.getTargetFrameBufferViewportHeight();
if (this.width != width || this.height != height)
{
this.width = width;
this.height = height;
this.createFramebuffer(width, height);
}
FogShader.INSTANCE.frameBuffer = this.fogFramebuffer;
FogShader.INSTANCE.setProjectionMatrix(projectionMatrix);
FogShader.INSTANCE.render(partialTicks);
// restored so we can write the SSAO texture to the main frame buffer
primaryState.restore();
FogApplyShader.INSTANCE.fogTexture = this.fogTexture;
FogApplyShader.INSTANCE.render(partialTicks);
state.restore();
}
public void free()
{
FogShader.INSTANCE.free();
FogApplyShader.INSTANCE.free();
}
}
@@ -38,6 +38,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.render.glObject.buffer.QuadElementBuffer;
import com.seibel.distanthorizons.core.render.glObject.texture.*;
import com.seibel.distanthorizons.core.render.renderer.generic.GenericObjectRenderer;
import com.seibel.distanthorizons.core.render.renderer.shaders.*;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
@@ -50,16 +51,13 @@ import com.seibel.distanthorizons.core.wrapperInterfaces.modAccessor.IIrisAccess
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IClientLevelWrapper;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.coreapi.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.util.math.Vec3f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.apache.logging.log4j.LogManager;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@@ -97,90 +95,14 @@ public class LodRenderer
private int cachedWidth;
private int cachedHeight;
/** called by each {@link ColumnRenderBuffer} before rendering */
public void setModelViewMatrixOffset(DhBlockPos pos, DhApiRenderParam renderEventParam) throws IllegalStateException
{
Vec3d cam = MC_RENDER.getCameraExactPosition();
Vec3f modelPos = new Vec3f((float) (pos.x - cam.x), (float) (pos.y - cam.y), (float) (pos.z - cam.z));
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
if (!GL32.glIsProgram(shaderProgram.getId()))
{
throw new IllegalStateException("No GL program exists with the ID: [" + shaderProgram.getId() + "]. This either means a shader program was freed while it was still in use or was never created.");
}
shaderProgram.bind();
shaderProgram.setModelOffsetPos(modelPos);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos));
}
public void drawVbo(GLVertexBuffer vbo)
{
//// can be uncommented to add additional debug validation to prevent crashes if invalid buffers are being created
//// shouldn't be used in production due to the performance hit
//if (GL32.glIsBuffer(vbo.getId()))
{
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount() / 4) * 6, // TODO what does the 4 and 6 here represent?
this.quadIBO.getType(), 0);
vbo.unbind();
}
//else
//{
// // will spam the log if uncommented, but helpful for validation
// //LOGGER.warn("Unable to draw VBO: "+vbo.getId());
//}
}
public static class LagSpikeCatcher
{
long timer = System.nanoTime();
public LagSpikeCatcher() { }
public void end(String source)
{
if (!ENABLE_DRAW_LAG_SPIKE_LOGGING)
{
return;
}
this.timer = System.nanoTime() - this.timer;
if (this.timer > DRAW_LAG_SPIKE_THRESHOLD_NS)
{
//4 ms
EVENT_LOGGER.debug("NOTE: " + source + " took " + Duration.ofNanos(this.timer) + "!");
}
}
}
private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private final ReentrantLock setupLock = new ReentrantLock();
public final RenderBufferHandler bufferHandler;
public final GenericObjectRenderer genericObjectRenderer;
// The shader program
IDhApiShaderProgram lodRenderProgram = null;
@@ -205,9 +127,10 @@ public class LodRenderer
// constructor //
//=============//
public LodRenderer(RenderBufferHandler bufferHandler)
public LodRenderer(RenderBufferHandler bufferHandler, GenericObjectRenderer genericObjectRenderer)
{
this.bufferHandler = bufferHandler;
this.genericObjectRenderer = genericObjectRenderer;
}
private boolean rendererClosed = false;
@@ -374,16 +297,41 @@ public class LodRenderer
// Disable blending for opaque rendering
GL32.glDisable(GL32.GL_BLEND);
// terrain
profiler.popPush("LOD Opaque");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeRenderPassEvent.class, renderEventParam);
// TODO: Directional culling
this.bufferHandler.renderOpaque(this, renderEventParam);
// custom objects with SSAO
if (Config.Client.Advanced.Graphics.GenericRendering.enableRendering.get())
{
profiler.popPush("Custom Objects");
this.genericObjectRenderer.render(renderEventParam, profiler, true);
}
// SSAO
if (Config.Client.Advanced.Graphics.Ssao.enabled.get())
{
profiler.popPush("LOD SSAO");
SSAORenderer.INSTANCE.render(minecraftGlState, renderEventParam.dhProjectionMatrix, renderEventParam.partialTicks);
SSAORenderer.INSTANCE.render(minecraftGlState, new Mat4f(renderEventParam.dhProjectionMatrix), renderEventParam.partialTicks);
}
// custom objects without SSAO
if (Config.Client.Advanced.Graphics.GenericRendering.enableRendering.get())
{
profiler.popPush("Custom Objects");
this.genericObjectRenderer.render(renderEventParam, profiler, false);
}
//DarkShader.INSTANCE.render(partialTicks); // A test shader to make the world darker
if (!deferTransparentRendering && Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
{
this.renderTransparentBuffers(profiler, renderEventParam, renderEventParam.partialTicks);
}
@@ -394,16 +342,9 @@ public class LodRenderer
Mat4f combinedMatrix = new Mat4f(renderEventParam.dhProjectionMatrix);
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
FogShader.INSTANCE.setModelViewProjectionMatrix(combinedMatrix);
FogShader.INSTANCE.render(renderEventParam.partialTicks);
FogRenderer.INSTANCE.render(minecraftGlState, combinedMatrix, renderEventParam.partialTicks);
}
//DarkShader.INSTANCE.render(partialTicks); // A test shader to make the world darker
if (!deferTransparentRendering && Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
{
this.renderTransparentBuffers(profiler, renderEventParam, renderEventParam.partialTicks);
}
drawLagSpikeCatcher.end("LodDraw");
@@ -422,9 +363,10 @@ public class LodRenderer
// Note: this can be very slow if a lot of boxes are being rendered
DebugRenderer.INSTANCE.render(combinedMatrix);
profiler.popPush("LOD cleanup");
}
profiler.popPush("LOD cleanup");
if (this.usingMcFrameBuffer)
@@ -462,6 +404,17 @@ public class LodRenderer
if (Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled)
{
this.renderTransparentBuffers(profiler, renderEventParam, renderEventParam.partialTicks);
if (Config.Client.Advanced.Graphics.Fog.drawMode.get() != EDhApiFogDrawMode.FOG_DISABLED)
{
profiler.popPush("LOD Fog");
Mat4f combinedMatrix = new Mat4f(renderEventParam.dhProjectionMatrix);
combinedMatrix.multiply(renderEventParam.dhModelViewMatrix);
FogRenderer.INSTANCE.render(minecraftGlState, combinedMatrix, renderEventParam.partialTicks);
}
}
drawLagSpikeCatcher.end("LodTranslucentDraw");
@@ -516,14 +469,56 @@ public class LodRenderer
this.bufferHandler.renderTransparent(this, renderEventParam);
GL32.glDepthMask(true); // Apparently the depth mask state is stored in the FBO, so glState fails to restore it...
if (Config.Client.Advanced.Graphics.Fog.drawMode.get() != EDhApiFogDrawMode.FOG_DISABLED)
{
profiler.popPush("LOD Fog");
FogShader.INSTANCE.render(partialTicks);
}
}
/** called by each {@link ColumnRenderBuffer} before rendering */
public void setModelViewMatrixOffset(DhBlockPos pos, DhApiRenderParam renderEventParam) throws IllegalStateException
{
Vec3d cam = MC_RENDER.getCameraExactPosition();
Vec3f modelPos = new Vec3f((float) (pos.x - cam.x), (float) (pos.y - cam.y), (float) (pos.z - cam.z));
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
shaderProgram.bind();
shaderProgram.setModelOffsetPos(modelPos);
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeBufferRenderEvent.class, new DhApiBeforeBufferRenderEvent.EventParam(renderEventParam, modelPos));
}
public void drawVbo(GLVertexBuffer vbo)
{
//// can be uncommented to add additional debug validation to prevent crashes if invalid buffers are being created
//// shouldn't be used in production due to the performance hit
//if (GL32.glIsBuffer(vbo.getId()))
{
IDhApiShaderProgram shaderProgram = this.lodRenderProgram;
IDhApiShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
vbo.bind();
shaderProgram.bindVertexBuffer(vbo.getId());
GL32.glDrawElements(GL32.GL_TRIANGLES, (vbo.getVertexCount() / 4) * 6, // TODO what does the 4 and 6 here represent?
this.quadIBO.getType(), 0);
vbo.unbind();
}
//else
//{
// // will spam the log if uncommented, but helpful for validation
// //LOGGER.warn("Unable to draw VBO: "+vbo.getId());
//}
}
//=================//
@@ -616,6 +611,7 @@ public class LodRenderer
boolean renderWireframe = Config.Client.Advanced.Debugging.renderWireframe.get();
if (renderWireframe)
{
// TODO fix
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_LINE);
//GL32.glDisable(GL32.GL_CULL_FACE);
}
@@ -650,7 +646,7 @@ public class LodRenderer
this.fogConfig = newFogConfig;
this.lodRenderProgram.free();
this.lodRenderProgram = new LodRenderProgram();
this.lodRenderProgram = new DhTerrainShaderProgram();
FogShader.INSTANCE.free();
FogShader.INSTANCE = new FogShader(newFogConfig);
@@ -690,7 +686,7 @@ public class LodRenderer
EVENT_LOGGER.info("Setting up renderer");
this.isSetupComplete = true;
this.lodRenderProgram = new LodRenderProgram();
this.lodRenderProgram = new DhTerrainShaderProgram();
if (ENABLE_IBO)
{
this.quadIBO = new QuadElementBuffer();
@@ -868,4 +864,35 @@ public class LodRenderer
}
}
//================//
// helper classes //
//================//
public static class LagSpikeCatcher
{
long timer = System.nanoTime();
public LagSpikeCatcher() { }
public void end(String source)
{
if (!ENABLE_DRAW_LAG_SPIKE_LOGGING)
{
return;
}
this.timer = System.nanoTime() - this.timer;
if (this.timer > DRAW_LAG_SPIKE_THRESHOLD_NS)
{
//4 ms
EVENT_LOGGER.debug("NOTE: " + source + " took " + Duration.ofNanos(this.timer) + "!");
}
}
}
}
@@ -24,7 +24,7 @@ import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.renderer.shaders.SSAOApplyShader;
import com.seibel.distanthorizons.core.render.renderer.shaders.SSAOShader;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import org.lwjgl.opengl.GL32;
import java.nio.ByteBuffer;
@@ -51,6 +51,7 @@ public class SSAORenderer
private int ssaoTexture = -1;
//=============//
// constructor //
//=============//
@@ -92,6 +93,7 @@ public class SSAORenderer
}
//========//
// render //
//========//
@@ -99,12 +101,11 @@ public class SSAORenderer
public void render(GLState primaryState, Mat4f projectionMatrix, float partialTicks)
{
GLState state = new GLState();
this.init();
// resize the framebuffer if necessary
int width = MC_RENDER.getTargetFrameBufferViewportWidth();
int height = MC_RENDER.getTargetFrameBufferViewportHeight();
if (this.width != width || this.height != height)
{
this.width = width;
@@ -116,6 +117,7 @@ public class SSAORenderer
SSAOShader.INSTANCE.setProjectionMatrix(projectionMatrix);
SSAOShader.INSTANCE.render(partialTicks);
// restored so we can write the SSAO texture to the main frame buffer
primaryState.restore();
SSAOApplyShader.INSTANCE.ssaoTexture = this.ssaoTexture;
@@ -129,4 +131,5 @@ public class SSAORenderer
SSAOShader.INSTANCE.free();
SSAOApplyShader.INSTANCE.free();
}
}
@@ -0,0 +1,241 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.pos.DhBlockPos;
import com.seibel.distanthorizons.core.pos.DhChunkPos;
import com.seibel.distanthorizons.core.sql.dto.BeaconBeamDTO;
import com.seibel.distanthorizons.core.sql.repo.BeaconBeamRepo;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class BeaconRenderHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final int BEAM_TOP_Y = 6_000;
/** if this is null then the other handler is probably null too, but just in case */
private final BeaconBeamRepo beaconBeamRepo;
private final IDhApiRenderableBoxGroup beaconBoxGroup;
private final HashMap<DhBlockPos, AtomicInteger> beaconRefCountByBlockPos = new HashMap<>();
//=============//
// constructor //
//=============//
public BeaconRenderHandler(@NotNull BeaconBeamRepo beaconBeamRepo, @NotNull GenericObjectRenderer renderer)
{
this.beaconBeamRepo = beaconBeamRepo;
this.beaconBoxGroup = GenericRenderObjectFactory.INSTANCE.createAbsolutePositionedGroup(ModInfo.NAME+":Beacons", new ArrayList<>(0));
this.beaconBoxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
this.beaconBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
this.beaconBoxGroup.setSsaoEnabled(false);
this.beaconBoxGroup.setShading(DhApiRenderableBoxGroupShading.getUnshaded());
this.beaconBoxGroup.setPreRenderFunc((renderEventParam) -> this.beaconBoxGroup.setActive(Config.Client.Advanced.Graphics.GenericRendering.enableBeaconRendering.get()));
renderer.add(this.beaconBoxGroup);
}
//=========================//
// level loading/unloading //
//=========================//
public void setBeaconBeamsForChunk(DhChunkPos chunkPos, List<BeaconBeamDTO> newBeamList)
{
// synchronized to prevent two threads from updating the same chunk at the same time
synchronized (this)
{
HashSet<DhBlockPos> allPosSet = new HashSet<>();
// sort new beams
HashMap<DhBlockPos, BeaconBeamDTO> newBeamByPos = new HashMap<>(newBeamList.size());
for (int i = 0; i < newBeamList.size(); i++)
{
BeaconBeamDTO beam = newBeamList.get(i);
newBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
}
// get existing beams
List<BeaconBeamDTO> existingBeamList = this.beaconBeamRepo.getAllBeamsForPos(chunkPos);
HashMap<DhBlockPos, BeaconBeamDTO> existingBeamByPos = new HashMap<>(existingBeamList.size());
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
existingBeamByPos.put(beam.pos, beam);
allPosSet.add(beam.pos);
}
for (DhBlockPos beaconPos : allPosSet)
{
if (!chunkPos.contains(beaconPos))
{
// don't update beacons outside the updated chunk
continue;
}
BeaconBeamDTO existingBeam = existingBeamByPos.get(beaconPos);
BeaconBeamDTO newBeam = newBeamByPos.get(beaconPos);
if (existingBeam != null && newBeam != null)
{
// beam still exists in chunk
if (!existingBeam.color.equals(newBeam.color))
{
// beam colors were changed
this.beaconBeamRepo.save(newBeam);
this.updateBeaconColor(newBeam);
}
}
else if (existingBeam == null && newBeam != null)
{
// new beam found, add to DB
this.beaconBeamRepo.save(newBeam);
this.startRenderingBeacon(newBeam);
}
else if (existingBeam != null && newBeam == null)
{
// beam no longer exists at position, remove from DB
this.beaconBeamRepo.deleteWithKey(beaconPos); // TODO broken when updating adjacent chunks
this.stopRenderingBeaconAtPos(beaconPos);
}
}
}
}
public void loadBeaconBeamsInPos(long pos)
{
// get all beams in pos
List<BeaconBeamDTO> existingBeamList = this.beaconBeamRepo.getAllBeamsForPos(pos);
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO newBeam = existingBeamList.get(i);
this.startRenderingBeacon(newBeam);
}
}
public void unloadBeaconBeamsInPos(long pos)
{
// get all beams in pos
List<BeaconBeamDTO> existingBeamList = this.beaconBeamRepo.getAllBeamsForPos(pos);
for (int i = 0; i < existingBeamList.size(); i++)
{
BeaconBeamDTO beam = existingBeamList.get(i);
this.stopRenderingBeaconAtPos(beam.pos);
}
}
//=================//
// render handling //
//=================//
private void startRenderingBeacon(BeaconBeamDTO beacon)
{
this.beaconRefCountByBlockPos.compute(beacon.pos, (beamPos, beaconRefCount) ->
{
if (beaconRefCount == null) { beaconRefCount = new AtomicInteger(); }
if (beaconRefCount.getAndIncrement() == 0)
{
DhApiRenderableBox beaconBox = new DhApiRenderableBox(
new DhApiVec3d(beacon.pos.x, beacon.pos.y+1, beacon.pos.z),
new DhApiVec3d(beacon.pos.x+1, BEAM_TOP_Y, beacon.pos.z+1),
beacon.color,
EDhApiBlockMaterial.ILLUMINATED
);
this.beaconBoxGroup.add(beaconBox);
this.beaconBoxGroup.triggerBoxChange();
}
return beaconRefCount;
});
}
private void stopRenderingBeaconAtPos(DhBlockPos beaconPos)
{
this.beaconRefCountByBlockPos.compute(beaconPos, (pos, beaconRefCount) ->
{
if (beaconRefCount != null
&& beaconRefCount.decrementAndGet() <= 0)
{
this.beaconBoxGroup.removeIf((box) ->
box.minPos.x == beaconPos.x
&& box.minPos.y == beaconPos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == beaconPos.z
);
this.beaconBoxGroup.triggerBoxChange();
return null;
}
else
{
return beaconRefCount;
}
});
}
private void updateBeaconColor(BeaconBeamDTO newBeam)
{
DhBlockPos pos = newBeam.pos;
for (int i = 0; i < this.beaconBoxGroup.size(); i++)
{
DhApiRenderableBox box = this.beaconBoxGroup.get(i);
if (box.minPos.x == pos.x
&& box.minPos.y == pos.y+1 // plus 1 because the beam starts above the beacon
&& box.minPos.z == pos.z)
{
box.color = newBeam.color;
this.beaconBoxGroup.triggerBoxChange();
break;
}
}
}
}
@@ -0,0 +1,403 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.config.Config;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.level.IDhLevel;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.Logger;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class CloudRenderHandler
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
private static final String CLOUD_RESOURCE_TEXTURE_PATH = "assets/distanthorizons/textures/clouds.png";
// FIXME transparency sorting makes having transparent clouds impossible
private static final Color CLOUD_COLOR = new Color(255,255,255,255);
private static final boolean DEBUG_BORDER_COLORS = false;
/**
* How wide an individual box is. <br>
* Measured in blocks.
*/
private static final int CLOUD_BOX_WIDTH = 64;
/** measured in blocks */
private static final int CLOUD_BOX_THICKNESS = 16;
private final IDhApiRenderableBoxGroup[][] boxGroupByOffset = new IDhApiRenderableBoxGroup[3][3];
private final IDhLevel level;
private final GenericObjectRenderer renderer;
private float moveSpeedInBlocksPerSecond = 3.0f;
private boolean disabledWarningLogged = false;
//=============//
// constructor //
//=============//
public CloudRenderHandler(IDhLevel level, GenericObjectRenderer renderer)
{
this.level = level;
this.renderer = renderer;
//=======================//
// get the cloud texture //
//=======================//
// default to a single empty slot in case the texture is broken
boolean[][] cloudLocations = new boolean[1][1];
try
{
cloudLocations = getCloudsFromTexture();
}
catch (FileNotFoundException e)
{
LOGGER.error(e.getMessage(), e);
}
catch (IOException e)
{
LOGGER.error("Unexpected issue getting cloud texture, error: ["+e.getMessage()+"].", e);
}
if (cloudLocations.length != 0 &&
cloudLocations.length != cloudLocations[0].length)
{
LOGGER.warn("Non-square cloud texture found, some parts of the texture will be clipped off.");
}
//===================//
// parse the texture //
//===================//
int textureWidth = cloudLocations.length;
ArrayList<DhApiRenderableBox> boxList = new ArrayList<>(512);
for (int x = 0; x < textureWidth; x ++)
{
for (int z = 0; z < textureWidth; z ++)
{
if (cloudLocations[x][z])
{
// start a new box in Z direction
int startZ = z;
int startX = x;
int endZ = startZ;
int endX = x+1;
//==========================//
// merge in the Z direction //
//==========================//
// Find the cloud's length in the Z direction
while (endZ < textureWidth
&& cloudLocations[x][endZ])
{
endZ++;
}
// update the z iterator so we can skip over everything included in this cloud
z = endZ - 1;
//==========================//
// merge in the X direction //
//==========================//
for (int currentX = startX + 1; currentX < textureWidth; currentX++)
{
boolean canMergeInXDir = true;
// check if all locations in this column are true
for (int adjacentZ = startZ; adjacentZ < endZ; adjacentZ++)
{
if (!cloudLocations[currentX][adjacentZ])
{
// at least one pixel in the texture is false,
// so we can't merge in this direction
canMergeInXDir = false;
break;
}
}
if (canMergeInXDir)
{
// mark the adjacent column as processed
for (int currentZ = startZ; currentZ < endZ; currentZ++)
{
// by flipping all the pixels in the adjacent column to false,
// we don't have to worry about adding another cloud
cloudLocations[currentX][currentZ] = false;
}
endX = (currentX + 1);
}
else
{
break;
}
}
//============================//
// Create the renderable box //
//============================//
// endZ contains the last cloud index
// so the cloud now goes from startZ to endZ (inclusive)
int minXBlockPos = startX * CLOUD_BOX_WIDTH;
int minZBlockPos = startZ * CLOUD_BOX_WIDTH;
int maxXBlockPos = endX * CLOUD_BOX_WIDTH;
int maxZBlockPos = endZ * CLOUD_BOX_WIDTH;
Color color = CLOUD_COLOR;
if (DEBUG_BORDER_COLORS)
{
// equals is included so the board is 2 blocks wide, it makes it easier to see
if (startX <= 1) { color = Color.RED; }
else if (startX >= textureWidth - 2) { color = Color.GREEN; }
if (startZ <= 1) { color = Color.BLUE; }
else if (endZ >= textureWidth - 2) { color = Color.BLACK; }
}
DhApiRenderableBox box = new DhApiRenderableBox(
new DhApiVec3d(minXBlockPos, 0, minZBlockPos),
new DhApiVec3d(maxXBlockPos, CLOUD_BOX_THICKNESS, maxZBlockPos),
color,
EDhApiBlockMaterial.UNKNOWN
);
boxList.add(box);
}
}
}
//========================//
// create the renderables //
//========================//
// slightly lighter shading than the default
DhApiRenderableBoxGroupShading cloudShading = DhApiRenderableBoxGroupShading.getUnshaded();
cloudShading.north = cloudShading.south = 0.9f;
cloudShading.east = cloudShading.west = 0.8f;
cloudShading.top = 1.0f;
cloudShading.bottom = 0.7f;
// 3x3 area so we clouds should always be overhead
for (int x = -1; x <= 1; x++)
{
for (int z = -1; z <= 1; z++)
{
IDhApiRenderableBoxGroup boxGroup = GenericRenderObjectFactory.INSTANCE.createRelativePositionedGroup(
ModInfo.NAME + ":Clouds",
new DhApiVec3d(0, 0, 0), // the offset will be set during rendering
boxList);
boxGroup.setBlockLight(LodUtil.MIN_MC_LIGHT);
boxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
boxGroup.setSsaoEnabled(false);
boxGroup.setShading(cloudShading);
CloudParams params = new CloudParams(textureWidth, x, z);
boxGroup.setPreRenderFunc((renderParam) -> this.preRender(params));
renderer.add(boxGroup);
this.boxGroupByOffset[x+1][z+1] = boxGroup;
}
}
}
private void preRender(CloudParams clouds)
{
IDhApiRenderableBoxGroup boxGroup = this.boxGroupByOffset[clouds.instanceOffsetX+1][clouds.instanceOffsetZ+1];
boolean renderClouds = Config.Client.Advanced.Graphics.GenericRendering.enableCloudRendering.get();
boxGroup.setActive(renderClouds);
if(!renderClouds)
{
return;
}
if (!this.renderer.getUseInstancedRendering())
{
if (!this.disabledWarningLogged)
{
this.disabledWarningLogged = true;
LOGGER.warn("Instanced rendering unavailable, cloud rendering disabled.");
}
boxGroup.setActive(false);
return;
}
//================//
// cloud movement //
//================//
long currentTime = System.currentTimeMillis();
float deltaTime = (currentTime - clouds.lastFrameTime) / 1000.0f; // Delta time in seconds
clouds.lastFrameTime = currentTime;
float deltaX = this.moveSpeedInBlocksPerSecond * deltaTime;
// negative delta is to match vanilla's cloud movement
clouds.xOffset -= deltaX;
// wrap the cloud around after reaching the edge
clouds.xOffset %= clouds.widthInBlocks;
//============================//
// camera movement and offset //
//============================//
// camera position
int cameraPosX = (int)MC_RENDER.getCameraExactPosition().x;
int cameraPosZ = (int)MC_RENDER.getCameraExactPosition().z;
// offset the camera position by negative 1 width when below zero to fix off-by-one errors in the negative direction
if (cameraPosX < 0) { cameraPosX -= clouds.widthInBlocks; }
if (cameraPosZ < 0) { cameraPosZ -= clouds.widthInBlocks; }
// determine how many cloud instances away from the origin we are
int cloudInstanceOffsetX = cameraPosX / (int)clouds.widthInBlocks;
int cloudInstanceOffsetZ = cameraPosZ / (int)clouds.widthInBlocks;
// calculate the new offset
float xOffset = (cloudInstanceOffsetX * clouds.widthInBlocks);
float zOffset = (cloudInstanceOffsetZ * clouds.widthInBlocks);
//==============//
// update group //
//==============//
boxGroup.setOriginBlockPos(
new DhApiVec3d(
clouds.xOffset + (clouds.instanceOffsetX * clouds.widthInBlocks) + xOffset + clouds.halfWidthInBlocks,
this.level.getLevelWrapper().getMaxHeight() + 200,
clouds.zOffset + (clouds.instanceOffsetZ * clouds.widthInBlocks) + zOffset + clouds.halfWidthInBlocks
)
);
}
//==================//
// texture handling //
//==================//
private static boolean[][] getCloudsFromTexture() throws FileNotFoundException, IOException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
boolean[][] whitePixels = null;
try(InputStream imageInputStream = loader.getResourceAsStream(CLOUD_RESOURCE_TEXTURE_PATH))
{
if (imageInputStream == null)
{
throw new FileNotFoundException("Unable to find cloud texture at resource path: ["+CLOUD_RESOURCE_TEXTURE_PATH+"].");
}
BufferedImage image = ImageIO.read(imageInputStream);
int width = image.getWidth();
int height = image.getHeight();
whitePixels = new boolean[width][height];
for (int x = 0; x < width; x ++)
{
for (int z = 0; z < width; z ++)
{
Color color = new Color(image.getRGB(x,z));
whitePixels[x][z] = color.equals(Color.WHITE);
}
}
}
return whitePixels;
}
//================//
// helper classes //
//================//
private static class CloudParams
{
public final float textureWidth;
public final float widthInBlocks;
public final float halfWidthInBlocks;
public final int instanceOffsetX;
public final int instanceOffsetZ;
public float xOffset = 0;
public float zOffset = 0;
public long lastFrameTime = System.currentTimeMillis();
// constructor //
public CloudParams(float textureWidth, int instanceOffsetX, int instanceOffsetZ)
{
this.textureWidth = textureWidth;
this.widthInBlocks = (this.textureWidth * CLOUD_BOX_WIDTH);
this.halfWidthInBlocks = this.widthInBlocks / 2;
this.instanceOffsetX = instanceOffsetX;
this.instanceOffsetZ = instanceOffsetZ;
}
}
}
@@ -0,0 +1,607 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.enums.config.EDhApiGpuUploadMethod;
import com.seibel.distanthorizons.api.enums.config.EDhApiLoggerMode;
import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiGenericObjectShaderProgram;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.api.methods.events.abstractEvents.*;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector;
import com.seibel.distanthorizons.core.logging.ConfigBasedSpamLogger;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.logging.f3.F3Screen;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.render.glObject.GLState;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLElementBuffer;
import com.seibel.distanthorizons.core.render.glObject.buffer.GLVertexBuffer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftRenderWrapper;
import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IProfilerWrapper;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.coreapi.DependencyInjection.ApiEventInjector;
import com.seibel.distanthorizons.coreapi.DependencyInjection.OverrideInjector;
import com.seibel.distanthorizons.coreapi.ModInfo;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.ARBInstancedArrays;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL33;
import java.awt.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Handles rendering generic groups of {@link DhApiRenderableBox}.
*
* @see IDhApiCustomRenderRegister
* @see DhApiRenderableBox
*/
public class GenericObjectRenderer implements IDhApiCustomRenderRegister
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final ConfigBasedSpamLogger SPAM_LOGGER = new ConfigBasedSpamLogger(LogManager.getLogger(GenericObjectRenderer.class), () -> EDhApiLoggerMode.LOG_ALL_TO_CHAT, 1);
private static final IMinecraftRenderWrapper MC_RENDER = SingletonInjector.INSTANCE.get(IMinecraftRenderWrapper.class);
/**
* Can be used to troubleshoot the renderer.
* If enabled several debug objects will render around (0,150,0).
*/
public static final boolean RENDER_DEBUG_OBJECTS = false;
// rendering setup
private boolean init = false;
private IDhApiGenericObjectShaderProgram shaderProgram;
private GLVertexBuffer boxVertexBuffer;
private GLElementBuffer boxIndexBuffer;
private boolean useInstancedRendering;
private boolean vertexAttribDivisorSupported;
private boolean instancedArraysSupported;
private final ConcurrentHashMap<Long, RenderableBoxGroup> boxGroupById = new ConcurrentHashMap<>();
/** A box from 0,0,0 to 1,1,1 */
private static final float[] BOX_VERTICES = {
// Pos x y z
// min X, vertical face
0, 0, 0,
1, 0, 0,
1, 1, 0,
0, 1, 0,
// max X, vertical face
0, 1, 1,
1, 1, 1,
1, 0, 1,
0, 0, 1,
// min Z, vertical face
0, 0, 1,
0, 0, 0,
0, 1, 0,
0, 1, 1,
// max Z, vertical face
1, 0, 1,
1, 1, 1,
1, 1, 0,
1, 0, 0,
// min Y, horizontal face
0, 0, 1,
1, 0, 1,
1, 0, 0,
0, 0, 0,
// max Y, horizontal face
0, 1, 1,
1, 1, 1,
1, 1, 0,
0, 1, 0,
};
private static final int[] BOX_INDICES = {
// min X, vertical face
2, 1, 0,
0, 3, 2,
// max X, vertical face
6, 5, 4,
4, 7, 6,
// min Z, vertical face
10, 9, 8,
8, 11, 10,
// max Z, vertical face
14, 13, 12,
12, 15, 14,
// min Y, horizontal face
18, 17, 16,
16, 19, 18,
// max Y, horizontal face
20, 21, 22,
22, 23, 20,
};
//=============//
// constructor //
//=============//
public GenericObjectRenderer() { }
public void init()
{
if (this.init)
{
return;
}
this.init = true;
this.vertexAttribDivisorSupported = GLProxy.getInstance().vertexAttribDivisorSupported;
this.instancedArraysSupported = GLProxy.getInstance().instancedArraysSupported;
this.useInstancedRendering = this.vertexAttribDivisorSupported || this.instancedArraysSupported;
if (!this.useInstancedRendering)
{
LOGGER.warn("Instanced rendering not supported by this GPU, falling back to direct rendering. Generic object rendering will be slow.");
}
this.shaderProgram = new GenericObjectShaderProgram(this.useInstancedRendering);
this.createBuffers();
if (RENDER_DEBUG_OBJECTS)
{
this.addGenericDebugObjects();
}
}
private void createBuffers()
{
// box vertices
ByteBuffer boxVerticesBuffer = ByteBuffer.allocateDirect(BOX_VERTICES.length * Float.BYTES);
boxVerticesBuffer.order(ByteOrder.nativeOrder());
boxVerticesBuffer.asFloatBuffer().put(BOX_VERTICES);
boxVerticesBuffer.rewind();
this.boxVertexBuffer = new GLVertexBuffer(false);
this.boxVertexBuffer.bind();
this.boxVertexBuffer.uploadBuffer(boxVerticesBuffer, 8, EDhApiGpuUploadMethod.DATA, BOX_VERTICES.length * Float.BYTES);
// box vertex indexes
ByteBuffer solidIndexBuffer = ByteBuffer.allocateDirect(BOX_INDICES.length * Integer.BYTES);
solidIndexBuffer.order(ByteOrder.nativeOrder());
solidIndexBuffer.asIntBuffer().put(BOX_INDICES);
solidIndexBuffer.rewind();
this.boxIndexBuffer = new GLElementBuffer(false);
this.boxIndexBuffer.uploadBuffer(solidIndexBuffer, EDhApiGpuUploadMethod.DATA, BOX_INDICES.length * Integer.BYTES, GL32.GL_STATIC_DRAW);
this.boxIndexBuffer.bind();
}
private void addGenericDebugObjects()
{
GenericRenderObjectFactory factory = GenericRenderObjectFactory.INSTANCE;
// single giant box
IDhApiRenderableBoxGroup singleGiantBoxGroup = factory.createForSingleBox(
ModInfo.NAME + ":CyanChunkBox",
new DhApiRenderableBox(
new DhApiVec3d(0,0,0), new DhApiVec3d(16,190,16),
new Color(Color.CYAN.getRed(), Color.CYAN.getGreen(), Color.CYAN.getBlue(), 125),
EDhApiBlockMaterial.WATER)
);
singleGiantBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
singleGiantBoxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
this.add(singleGiantBoxGroup);
// single slender box
IDhApiRenderableBoxGroup singleTallBoxGroup = factory.createForSingleBox(
ModInfo.NAME + ":GreenBeacon",
new DhApiRenderableBox(
new DhApiVec3d(16,0,31), new DhApiVec3d(17,2000,32),
new Color(Color.GREEN.getRed(), Color.GREEN.getGreen(), Color.GREEN.getBlue(), 125),
EDhApiBlockMaterial.ILLUMINATED)
);
singleTallBoxGroup.setSkyLight(LodUtil.MAX_MC_LIGHT);
singleTallBoxGroup.setBlockLight(LodUtil.MAX_MC_LIGHT);
this.add(singleTallBoxGroup);
// absolute box group
ArrayList<DhApiRenderableBox> absBoxList = new ArrayList<>();
for (int i = 0; i < 18; i++)
{
absBoxList.add(new DhApiRenderableBox(
new DhApiVec3d(i,150+i,24), new DhApiVec3d(1+i,151+i,25),
new Color(Color.ORANGE.getRed(), Color.ORANGE.getGreen(), Color.ORANGE.getBlue()),
EDhApiBlockMaterial.LAVA
)
);
}
IDhApiRenderableBoxGroup absolutePosBoxGroup = factory.createAbsolutePositionedGroup(ModInfo.NAME + ":OrangeStairs", absBoxList);
this.add(absolutePosBoxGroup);
// relative box group
ArrayList<DhApiRenderableBox> relBoxList = new ArrayList<>();
for (int i = 0; i < 8; i+=2)
{
relBoxList.add(new DhApiRenderableBox(
new DhApiVec3d(0,i,0), new DhApiVec3d(1,1+i,1),
new Color(Color.MAGENTA.getRed(), Color.MAGENTA.getGreen(), Color.MAGENTA.getBlue()),
EDhApiBlockMaterial.METAL
)
);
}
IDhApiRenderableBoxGroup relativePosBoxGroup = factory.createRelativePositionedGroup(
ModInfo.NAME + ":MovingMagentaGroup",
new DhApiVec3d(24, 140, 24),
relBoxList);
relativePosBoxGroup.setPreRenderFunc((event) ->
{
DhApiVec3d pos = relativePosBoxGroup.getOriginBlockPos();
pos.x += event.partialTicks / 2;
pos.x %= 32;
relativePosBoxGroup.setOriginBlockPos(pos);
});
this.add(relativePosBoxGroup);
// massive relative box group
ArrayList<DhApiRenderableBox> massRelBoxList = new ArrayList<>();
for (int x = 0; x < 50*2; x+=2)
{
for (int z = 0; z < 50*2; z+=2)
{
massRelBoxList.add(new DhApiRenderableBox(
new DhApiVec3d(-x, 0, -z), new DhApiVec3d(1-x, 1, 1-z),
new Color(Color.RED.getRed(), Color.RED.getGreen(), Color.RED.getBlue()),
EDhApiBlockMaterial.TERRACOTTA
)
);
}
}
IDhApiRenderableBoxGroup massRelativePosBoxGroup = factory.createRelativePositionedGroup(
ModInfo.NAME + ":MassRedGroup",
new DhApiVec3d(-25, 140, 0),
massRelBoxList);
massRelativePosBoxGroup.setPreRenderFunc((event) ->
{
DhApiVec3d blockPos = massRelativePosBoxGroup.getOriginBlockPos();
blockPos.y += event.partialTicks / 4;
if (blockPos.y > 150f)
{
blockPos.y = 140f;
Color newColor = (massRelativePosBoxGroup.get(0).color == Color.RED) ? Color.RED.darker() : Color.RED;
massRelativePosBoxGroup.forEach((box) -> { box.color = newColor; });
massRelativePosBoxGroup.triggerBoxChange();
}
massRelativePosBoxGroup.setOriginBlockPos(blockPos);
});
this.add(massRelativePosBoxGroup);
}
//==============//
// registration //
//==============//
@Override
public void add(IDhApiRenderableBoxGroup iBoxGroup) throws IllegalArgumentException
{
if (!(iBoxGroup instanceof RenderableBoxGroup))
{
throw new IllegalArgumentException("Box group must be of type ["+ RenderableBoxGroup.class.getSimpleName()+"], type received: ["+(iBoxGroup != null ? iBoxGroup.getClass() : "NULL")+"].");
}
RenderableBoxGroup boxGroup = (RenderableBoxGroup) iBoxGroup;
long id = boxGroup.getId();
if (this.boxGroupById.containsKey(id))
{
throw new IllegalArgumentException("A box group with the ID [" + id + "] is already present.");
}
this.boxGroupById.put(id, boxGroup);
}
@Override
public IDhApiRenderableBoxGroup remove(long id) { return this.boxGroupById.remove(id); }
public void clear() { this.boxGroupById.clear(); }
//===========//
// rendering //
//===========//
/**
* @param renderingWithSsao
* if true that means this render call is happening before the SSAO pass
* and any objects rendered in this pass will have SSAO applied to them.
*/
public void render(DhApiRenderParam renderEventParam, IProfilerWrapper profiler, boolean renderingWithSsao)
{
// render setup //
profiler.push("setup");
GLState glState = new GLState();
this.init();
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericRenderSetupEvent.class, renderEventParam);
GL32.glPolygonMode(GL32.GL_FRONT_AND_BACK, GL32.GL_FILL);
GL32.glEnable(GL32.GL_DEPTH_TEST);
GL32.glEnable(GL32.GL_BLEND);
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GL32.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
IDhApiGenericObjectShaderProgram shaderProgram = this.shaderProgram;
IDhApiGenericObjectShaderProgram shaderProgramOverride = OverrideInjector.INSTANCE.get(IDhApiGenericObjectShaderProgram.class);
if (shaderProgramOverride != null && shaderProgram.overrideThisFrame())
{
shaderProgram = shaderProgramOverride;
}
shaderProgram.bind(renderEventParam);
shaderProgram.bindVertexBuffer(this.boxVertexBuffer.getId());
this.boxIndexBuffer.bind();
Vec3d camPos = MC_RENDER.getCameraExactPosition();
// rendering //
Collection<RenderableBoxGroup> boxList = this.boxGroupById.values();
for (RenderableBoxGroup boxGroup : boxList)
{
// skip boxes that shouldn't render this pass
if (boxGroup.ssaoEnabled == renderingWithSsao)
{
profiler.popPush("render prep");
boxGroup.preRender(renderEventParam);
// ignore inactive groups
if (boxGroup.active)
{
boolean cancelRendering = ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericObjectRenderEvent.class, new DhApiBeforeGenericObjectRenderEvent.EventParam(renderEventParam, boxGroup));
if (!cancelRendering)
{
profiler.popPush("rendering");
profiler.push(boxGroup.getResourceLocationNamespace());
profiler.push(boxGroup.getResourceLocationPath());
if (this.useInstancedRendering)
{
this.renderBoxGroupInstanced(shaderProgram, renderEventParam, boxGroup, camPos);
}
else
{
this.renderBoxGroupDirect(shaderProgram, renderEventParam, boxGroup, camPos);
}
profiler.pop(); // resource path
profiler.pop(); // resource namespace
boxGroup.postRender(renderEventParam);
}
}
}
}
// clean up //
profiler.popPush("cleanup");
ApiEventInjector.INSTANCE.fireAllEvents(DhApiBeforeGenericRenderCleanupEvent.class, renderEventParam);
shaderProgram.unbind();
glState.restore();
profiler.pop();
}
//=====================//
// instanced rendering //
//=====================//
private void renderBoxGroupInstanced(IDhApiGenericObjectShaderProgram shaderProgram, DhApiRenderParam renderEventParam, RenderableBoxGroup boxGroup, Vec3d camPos)
{
// update instance data //
boxGroup.updateVertexAttributeData();
DhApiRenderableBoxGroupShading shading = boxGroup.shading;
if (shading == null)
{
shading = DhApiRenderableBoxGroupShading.getUnshaded();
}
shaderProgram.fillIndirectUniformData(
renderEventParam,
shading, boxGroup,
camPos);
// Bind instance data //
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceColorVbo);
GL32.glEnableVertexAttribArray(1);
GL32.glVertexAttribPointer(1, 4, GL32.GL_FLOAT, false, 4 * Float.BYTES, 0);
this.vertexAttribDivisor(1, 1);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceScaleVbo);
GL32.glEnableVertexAttribArray(2);
this.vertexAttribDivisor(2, 1);
GL32.glVertexAttribPointer(2, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceChunkPosVbo);
GL32.glEnableVertexAttribArray(3);
this.vertexAttribDivisor(3, 1);
GL32.glVertexAttribIPointer(3, 3, GL32.GL_INT, 3 * Integer.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceSubChunkPosVbo);
GL32.glEnableVertexAttribArray(4);
this.vertexAttribDivisor(4, 1);
GL32.glVertexAttribPointer(4, 3, GL32.GL_FLOAT, false, 3 * Float.BYTES, 0);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, boxGroup.instanceMaterialVbo);
GL32.glEnableVertexAttribArray(5);
this.vertexAttribDivisor(5, 1);
GL32.glVertexAttribIPointer(5, 1, GL32.GL_BYTE, Byte.BYTES, 0);
// Draw instanced
if (boxGroup.uploadedBoxCount > 0)
{
GL32.glDrawElementsInstanced(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0, boxGroup.uploadedBoxCount);
}
// Clean up
GL32.glDisableVertexAttribArray(1);
GL32.glDisableVertexAttribArray(2);
GL32.glDisableVertexAttribArray(3);
GL32.glDisableVertexAttribArray(4);
GL32.glDisableVertexAttribArray(5);
}
/**
* Clean way to handle both {@link GL33#glVertexAttribDivisor} and {@link ARBInstancedArrays#glVertexAttribDivisorARB}
* based on which one is supported.
*/
private void vertexAttribDivisor(int index, int divisor)
{
if (this.vertexAttribDivisorSupported)
{
GL33.glVertexAttribDivisor(index, divisor);
}
else if(this.instancedArraysSupported)
{
ARBInstancedArrays.glVertexAttribDivisorARB(index, divisor);
}
else
{
throw new IllegalStateException("Instanced rendering isn't supported by this machine. Direct rendering should have been used instead.");
}
}
//==================//
// direct rendering //
//==================//
private void renderBoxGroupDirect(IDhApiGenericObjectShaderProgram shaderProgram, DhApiRenderParam renderEventParam, RenderableBoxGroup boxGroup, Vec3d camPos)
{
DhApiRenderableBoxGroupShading shading = boxGroup.shading;
if (shading == null)
{
shading = DhApiRenderableBoxGroupShading.getUnshaded();
}
shaderProgram.fillSharedDirectUniformData(renderEventParam, shading, boxGroup, camPos);
for (DhApiRenderableBox box : boxGroup)
{
this.renderBox(shaderProgram, renderEventParam, boxGroup, box, camPos);
}
}
private void renderBox(
IDhApiGenericObjectShaderProgram shaderProgram,
DhApiRenderParam renderEventParam,
RenderableBoxGroup boxGroup, DhApiRenderableBox box,
Vec3d camPos)
{
shaderProgram.fillDirectUniformData(renderEventParam, boxGroup, box, camPos);
GL32.glDrawElements(GL32.GL_TRIANGLES, BOX_INDICES.length, GL32.GL_UNSIGNED_INT, 0);
}
//=========//
// getters //
//=========//
/** @throws IllegalStateException if {@link #init()} function hasn't been called yet */
public boolean getUseInstancedRendering() throws IllegalStateException
{
if (!this.init)
{
throw new IllegalStateException("GL initialization hasn't been completed.");
}
return this.useInstancedRendering;
}
//=========//
// F3 menu //
//=========//
public String getVboRenderDebugMenuString()
{
// get counts
int totalCount = this.boxGroupById.size();
int activeCount = 0;
for (long key : this.boxGroupById.keySet())
{
RenderableBoxGroup renderGroup = this.boxGroupById.get(key);
if (renderGroup.active)
{
activeCount++;
}
}
String totalCountText = F3Screen.NUMBER_FORMAT.format(totalCount);
String activeCountText = F3Screen.NUMBER_FORMAT.format(activeCount);
return LodUtil.formatLog("Generic Obj Count: " + activeCountText + "/" + totalCountText);
}
}
@@ -0,0 +1,229 @@
package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.interfaces.override.rendering.IDhApiGenericObjectShaderProgram;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3i;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.AbstractVertexAttribute;
import com.seibel.distanthorizons.core.render.glObject.vertexAttribute.VertexPointer;
import com.seibel.distanthorizons.core.util.LodUtil;
import com.seibel.distanthorizons.core.util.math.Mat4f;
import com.seibel.distanthorizons.core.util.math.Vec3f;
public class GenericObjectShaderProgram extends ShaderProgram implements IDhApiGenericObjectShaderProgram
{
public static final String VERTEX_SHADER_INSTANCED_PATH = "shaders/genericObject/instanced/vert.vert";
public static final String VERTEX_SHADER_DIRECT_PATH = "shaders/genericObject/direct/vert.vert";
public static final String FRAGMENT_SHADER_INSTANCED_PATH = "shaders/genericObject/instanced/frag.frag";
public static final String FRAGMENT_SHADER_DIRECT_PATH = "shaders/genericObject/direct/frag.frag";
public final AbstractVertexAttribute va;
// shader uniforms
private final int directShaderTransformUniform;
private final int directShaderColorUniform;
private final int instancedShaderOffsetChunkUniform;
private final int instancedShaderOffsetSubChunkUniform;
private final int instancedShaderCameraChunkPosUniform;
private final int instancedShaderCameraSubChunkPosUniform;
private final int instancedShaderProjectionModelViewMatrixUniform;
private final int lightMapUniform;
private final int skyLightUniform;
private final int blockLightUniform;
private final int northShadingUniform;
private final int southShadingUniform;
private final int eastShadingUniform;
private final int westShadingUniform;
private final int topShadingUniform;
private final int bottomShadingUniform;
//=============//
// constructor //
//=============//
public GenericObjectShaderProgram(boolean useInstancedRendering)
{
super(
useInstancedRendering ? VERTEX_SHADER_INSTANCED_PATH : VERTEX_SHADER_DIRECT_PATH,
useInstancedRendering ? FRAGMENT_SHADER_INSTANCED_PATH : FRAGMENT_SHADER_DIRECT_PATH,
"fragColor", new String[]{"vPosition"});
this.va = AbstractVertexAttribute.create();
this.va.bind();
// Pos
this.va.setVertexAttribute(0, 0, VertexPointer.addVec3Pointer(false));
this.va.completeAndCheck(Float.BYTES * 3);
this.directShaderTransformUniform = this.tryGetUniformLocation("uTransform");
this.directShaderColorUniform = this.tryGetUniformLocation("uColor");
this.instancedShaderOffsetChunkUniform = this.tryGetUniformLocation("uOffsetChunk");
this.instancedShaderOffsetSubChunkUniform = this.tryGetUniformLocation("uOffsetSubChunk");
this.instancedShaderCameraChunkPosUniform = this.tryGetUniformLocation("uCameraPosChunk");
this.instancedShaderCameraSubChunkPosUniform = this.tryGetUniformLocation("uCameraPosSubChunk");
this.instancedShaderProjectionModelViewMatrixUniform = this.tryGetUniformLocation("uProjectionMvm");
this.lightMapUniform = this.getUniformLocation("uLightMap");
this.skyLightUniform = this.getUniformLocation("uSkyLight");
this.blockLightUniform = this.getUniformLocation("uBlockLight");
this.northShadingUniform = this.getUniformLocation("uNorthShading");
this.southShadingUniform = this.getUniformLocation("uSouthShading");
this.eastShadingUniform = this.getUniformLocation("uEastShading");
this.westShadingUniform = this.getUniformLocation("uWestShading");
this.topShadingUniform = this.getUniformLocation("uTopShading");
this.bottomShadingUniform = this.getUniformLocation("uBottomShading");
}
//=========//
// methods //
//=========//
@Override
public void bind(DhApiRenderParam renderEventParam)
{
super.bind();
this.va.bind();
}
@Override
public void unbind()
{
super.unbind();
this.va.unbind();
}
@Override
public void free()
{
this.va.free();
super.free();
}
@Override
public void bindVertexBuffer(int vbo) { this.va.bindBufferToAllBindingPoints(vbo); }
@Override
public void fillIndirectUniformData(
DhApiRenderParam renderParameters,
DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup,
DhApiVec3d camPos
)
{
Mat4f projectionMvmMatrix = new Mat4f(renderParameters.dhProjectionMatrix);
projectionMvmMatrix.multiply(renderParameters.dhModelViewMatrix);
super.bind();
this.setUniform(this.instancedShaderOffsetChunkUniform,
new DhApiVec3i(
LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().x),
LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().y),
LodUtil.getChunkPosFromDouble(boxGroup.getOriginBlockPos().z)
));
this.setUniform(this.instancedShaderOffsetSubChunkUniform,
new Vec3f(
LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().x),
LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().y),
LodUtil.getSubChunkPosFromDouble(boxGroup.getOriginBlockPos().z)
));
this.setUniform(this.instancedShaderCameraChunkPosUniform,
new DhApiVec3i(
LodUtil.getChunkPosFromDouble(camPos.x),
LodUtil.getChunkPosFromDouble(camPos.y),
LodUtil.getChunkPosFromDouble(camPos.z)
));
this.setUniform(this.instancedShaderCameraSubChunkPosUniform,
new Vec3f(
LodUtil.getSubChunkPosFromDouble(camPos.x),
LodUtil.getSubChunkPosFromDouble(camPos.y),
LodUtil.getSubChunkPosFromDouble(camPos.z)
));
this.setUniform(this.instancedShaderProjectionModelViewMatrixUniform, projectionMvmMatrix);
this.setUniform(this.lightMapUniform, 0); // TODO this should probably be passed in
this.setUniform(this.skyLightUniform, boxGroup.getSkyLight());
this.setUniform(this.blockLightUniform, boxGroup.getBlockLight());
this.setUniform(this.northShadingUniform, shading.north);
this.setUniform(this.southShadingUniform, shading.south);
this.setUniform(this.eastShadingUniform, shading.east);
this.setUniform(this.westShadingUniform, shading.west);
this.setUniform(this.topShadingUniform, shading.top);
this.setUniform(this.bottomShadingUniform, shading.bottom);
}
@Override
public void fillSharedDirectUniformData(
DhApiRenderParam renderParameters,
DhApiRenderableBoxGroupShading shading, IDhApiRenderableBoxGroup boxGroup,
DhApiVec3d camPos)
{
this.setUniform(this.lightMapUniform, 0); // TODO this should probably be passed in
this.setUniform(this.skyLightUniform, boxGroup.getSkyLight());
this.setUniform(this.blockLightUniform, boxGroup.getBlockLight());
this.setUniform(this.northShadingUniform, shading.north);
this.setUniform(this.southShadingUniform, shading.south);
this.setUniform(this.eastShadingUniform, shading.east);
this.setUniform(this.westShadingUniform, shading.west);
this.setUniform(this.topShadingUniform, shading.top);
this.setUniform(this.bottomShadingUniform, shading.bottom);
}
public void fillDirectUniformData(
DhApiRenderParam renderParameters,
IDhApiRenderableBoxGroup boxGroup, DhApiRenderableBox box,
DhApiVec3d camPos)
{
Mat4f projectionMvmMatrix = new Mat4f(renderParameters.dhProjectionMatrix);
projectionMvmMatrix.multiply(renderParameters.dhModelViewMatrix);
Mat4f boxTransform = Mat4f.createTranslateMatrix(
(float) (box.minPos.x + boxGroup.getOriginBlockPos().x - camPos.x),
(float) (box.minPos.y + boxGroup.getOriginBlockPos().y - camPos.y),
(float) (box.minPos.z + boxGroup.getOriginBlockPos().z - camPos.z));
boxTransform.multiply(Mat4f.createScaleMatrix(
(float) (box.maxPos.x - box.minPos.x),
(float) (box.maxPos.y - box.minPos.y),
(float) (box.maxPos.z - box.minPos.z)));
projectionMvmMatrix.multiply(boxTransform);
this.setUniform(this.directShaderTransformUniform, projectionMvmMatrix);
this.setUniform(this.directShaderColorUniform, box.color);
}
@Override
public int getId() { return this.id; }
/** The base DH render program should always render */
@Override
public boolean overrideThisFrame() { return true; }
}
@@ -0,0 +1,78 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderObjectFactory;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiCustomRenderRegister;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3f;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.math.Vec3d;
import com.seibel.distanthorizons.core.util.math.Vec3f;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.*;
/**
* Handles creating {@link DhApiRenderableBox}.
*
* @see IDhApiCustomRenderRegister
* @see DhApiRenderableBox
*/
public class GenericRenderObjectFactory implements IDhApiCustomRenderObjectFactory
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
public static final GenericRenderObjectFactory INSTANCE = new GenericRenderObjectFactory();
//=============//
// constructor //
//=============//
private GenericRenderObjectFactory() { }
//================//
// group creation //
//================//
@Override
public IDhApiRenderableBoxGroup createForSingleBox(String resourceLocation, DhApiRenderableBox box)
{
ArrayList<DhApiRenderableBox> list = new ArrayList<>();
list.add(box);
return this.createAbsolutePositionedGroup(resourceLocation, list);
}
@Override
public IDhApiRenderableBoxGroup createRelativePositionedGroup(String resourceLocation, DhApiVec3d originBlockPos, List<DhApiRenderableBox> boxList)
{ return new RenderableBoxGroup(resourceLocation, new DhApiVec3d(originBlockPos.x, originBlockPos.y, originBlockPos.z), boxList, true); }
@Override
public IDhApiRenderableBoxGroup createAbsolutePositionedGroup(String resourceLocation, List<DhApiRenderableBox> boxList)
{ return new RenderableBoxGroup(resourceLocation, new DhApiVec3d(0, 0, 0), boxList, false); }
}
@@ -0,0 +1,354 @@
package com.seibel.distanthorizons.core.render.renderer.generic;
import com.seibel.distanthorizons.api.interfaces.render.IDhApiRenderableBoxGroup;
import com.seibel.distanthorizons.api.methods.events.sharedParameterObjects.DhApiRenderParam;
import com.seibel.distanthorizons.api.objects.math.DhApiVec3d;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBox;
import com.seibel.distanthorizons.api.objects.render.DhApiRenderableBoxGroupShading;
import com.seibel.distanthorizons.core.render.glObject.GLProxy;
import com.seibel.distanthorizons.core.util.LodUtil;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL32;
import java.awt.*;
import java.io.Closeable;
import java.util.*;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
public class RenderableBoxGroup
extends AbstractList<DhApiRenderableBox>
implements IDhApiRenderableBoxGroup, Closeable
{
public final static AtomicInteger NEXT_ID_ATOMIC_INT = new AtomicInteger(0);
public final long id;
public final String resourceLocationNamespace;
public final String resourceLocationPath;
/** If false the boxes will be positioned relative to the level's origin */
public final boolean positionBoxesRelativeToGroupOrigin;
private final ArrayList<DhApiRenderableBox> boxList;
private final DhApiVec3d originBlockPos;
public boolean active = true;
public boolean ssaoEnabled = true;
private boolean vertexDataDirty = true;
public int skyLight = 15;
public int blockLight = 0;
public DhApiRenderableBoxGroupShading shading = DhApiRenderableBoxGroupShading.getDefaultShaded();
@Nullable
public Consumer<DhApiRenderParam> beforeRenderFunc;
public Consumer<DhApiRenderParam> afterRenderFunc;
// instance data
public int instanceColorVbo = 0;
public int instanceMaterialVbo = 0;
public int instanceScaleVbo = 0;
public int instanceChunkPosVbo = 0;
public int instanceSubChunkPosVbo = 0;
public int uploadedBoxCount = -1;
// setters/getters //
@Override
public long getId() { return this.id; }
@Override
public String getResourceLocationNamespace() { return this.resourceLocationNamespace; }
@Override
public String getResourceLocationPath() { return this.resourceLocationPath; }
@Override
public void setOriginBlockPos(DhApiVec3d pos)
{
this.originBlockPos.x = pos.x;
this.originBlockPos.y = pos.y;
this.originBlockPos.z = pos.z;
}
@Override
public DhApiVec3d getOriginBlockPos() { return new DhApiVec3d(this.originBlockPos.x, this.originBlockPos.y, this.originBlockPos.z); }
@Override
public void setSkyLight(int skyLight)
{
if (skyLight < LodUtil.MIN_MC_LIGHT || skyLight > LodUtil.MAX_MC_LIGHT)
{
throw new IllegalArgumentException("Sky light ["+skyLight+"] must be between ["+LodUtil.MIN_MC_LIGHT+"] and ["+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
}
this.skyLight = skyLight;
}
@Override
public int getSkyLight() { return this.skyLight; }
@Override
public void setBlockLight(int blockLight)
{
if (blockLight < LodUtil.MIN_MC_LIGHT || blockLight > LodUtil.MAX_MC_LIGHT)
{
throw new IllegalArgumentException("Block light ["+blockLight+"] must be between ["+LodUtil.MIN_MC_LIGHT+"] and ["+LodUtil.MAX_MC_LIGHT+"] (inclusive).");
}
this.blockLight = blockLight;
}
@Override
public int getBlockLight() { return this.blockLight; }
//=============//
// constructor //
//=============//
public RenderableBoxGroup(
String resourceLocation,
DhApiVec3d originBlockPos, List<DhApiRenderableBox> boxList,
boolean positionBoxesRelativeToGroupOrigin) throws IllegalArgumentException
{
String[] splitResourceLocation = resourceLocation.split(":");
if (splitResourceLocation.length != 2)
{
throw new IllegalArgumentException("Resource Location must be a string that's separated by a single colon, for example: [DistantHorizons:Beacons], your namespace ["+resourceLocation+"], contains ["+(splitResourceLocation.length-1)+"] colons.");
}
this.resourceLocationNamespace = splitResourceLocation[0];
this.resourceLocationPath = splitResourceLocation[1];
this.id = NEXT_ID_ATOMIC_INT.getAndIncrement();
this.boxList = new ArrayList<>(boxList);
this.originBlockPos = originBlockPos;
this.positionBoxesRelativeToGroupOrigin = positionBoxesRelativeToGroupOrigin;
}
// methods //
@Override
public boolean add(DhApiRenderableBox box) { return this.boxList.add(box); }
@Override
public void setPreRenderFunc(Consumer<DhApiRenderParam> func) { this.beforeRenderFunc = func; }
@Override
public void setPostRenderFunc(Consumer<DhApiRenderParam> func) { this.afterRenderFunc = func; }
@Override
public void triggerBoxChange() { this.vertexDataDirty = true; }
@Override
public void setActive(boolean active) { this.active = active; }
@Override
public boolean isActive() { return this.active; }
@Override
public void setSsaoEnabled(boolean ssaoEnabled) { this.ssaoEnabled = ssaoEnabled; }
@Override
public boolean isSsaoEnabled() { return this.ssaoEnabled; }
/**
* This is called before every frame, even if {@link this#isActive()} returns false. <br>
* {@link this#isActive()} can be changed at this point before the object is rendered to the frame.
*/
public void preRender(DhApiRenderParam renderEventParam)
{
if (this.beforeRenderFunc != null)
{
this.beforeRenderFunc.accept(renderEventParam);
}
}
/**
* Called after rendering is completed. <br>
* Can be used to handle any necessary cleanup.
*/
public void postRender(DhApiRenderParam renderEventParam)
{
if (this.afterRenderFunc != null)
{
this.afterRenderFunc.accept(renderEventParam);
}
}
@Override
public void setShading(DhApiRenderableBoxGroupShading shading) { this.shading = shading; }
@Override
public DhApiRenderableBoxGroupShading getShading() { return this.shading; }
//================//
// List Overrides //
//================//
@Override
public DhApiRenderableBox get(int index) { return this.boxList.get(index); }
@Override
public int size() { return this.boxList.size(); }
@Override
public boolean removeIf(Predicate<? super DhApiRenderableBox> filter) { return this.boxList.removeIf(filter); }
@Override
public void replaceAll(UnaryOperator<DhApiRenderableBox> operator) { this.boxList.replaceAll(operator); }
@Override
public void sort(Comparator<? super DhApiRenderableBox> c) { this.boxList.sort(c); }
@Override
public void forEach(Consumer<? super DhApiRenderableBox> action) { this.boxList.forEach(action); }
@Override
public Spliterator<DhApiRenderableBox> spliterator() { return this.boxList.spliterator(); }
@Override
public Stream<DhApiRenderableBox> stream() { return this.boxList.stream(); }
@Override
public Stream<DhApiRenderableBox> parallelStream() { return this.boxList.parallelStream(); }
//===================//
// vertex attributes //
//===================//
/** Does nothing if the vertex data is already up-to-date */
public void updateVertexAttributeData()
{
if (!this.vertexDataDirty)
{
return;
}
this.vertexDataDirty = false;
if (this.instanceChunkPosVbo == 0)
{
this.instanceChunkPosVbo = GL32.glGenBuffers();
this.instanceSubChunkPosVbo = GL32.glGenBuffers();
this.instanceScaleVbo = GL32.glGenBuffers();
this.instanceColorVbo = GL32.glGenBuffers();
this.instanceMaterialVbo = GL32.glGenBuffers();
}
int boxCount = this.size();
this.uploadedBoxCount = boxCount;
// transformation / scaling //
int[] chunkPosData = new int[boxCount * 3];
float[] subChunkPosData = new float[boxCount * 3];
float[] scalingData = new float[boxCount * 3];
for (int i = 0; i < boxCount; i++)
{
DhApiRenderableBox box = this.get(i);
int dataIndex = i * 3;
chunkPosData[dataIndex] = LodUtil.getChunkPosFromDouble(box.minPos.x);
chunkPosData[dataIndex + 1] = LodUtil.getChunkPosFromDouble(box.minPos.y);
chunkPosData[dataIndex + 2] = LodUtil.getChunkPosFromDouble(box.minPos.z);
subChunkPosData[dataIndex] = LodUtil.getSubChunkPosFromDouble(box.minPos.x);
subChunkPosData[dataIndex + 1] = LodUtil.getSubChunkPosFromDouble(box.minPos.y);
subChunkPosData[dataIndex + 2] = LodUtil.getSubChunkPosFromDouble(box.minPos.z);
scalingData[dataIndex] = (float) (box.maxPos.x - box.minPos.x);
scalingData[dataIndex + 1] = (float) (box.maxPos.y - box.minPos.y);
scalingData[dataIndex + 2] = (float) (box.maxPos.z - box.minPos.z);
}
// colors/materials //
float[] colorData = new float[boxCount * 4];
byte[] materialData = new byte[boxCount];
for (int i = 0; i < boxCount; i++)
{
DhApiRenderableBox box = this.get(i);
Color color = box.color;
int colorIndex = i * 4;
colorData[colorIndex] = color.getRed() / 255.0f;
colorData[colorIndex + 1] = color.getGreen() / 255.0f;
colorData[colorIndex + 2] = color.getBlue() / 255.0f;
colorData[colorIndex + 3] = color.getAlpha() / 255.0f;
materialData[i] = box.material;
}
// Upload transformation matrices
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceChunkPosVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, chunkPosData ,GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceSubChunkPosVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, subChunkPosData ,GL32.GL_DYNAMIC_DRAW);
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceScaleVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, scalingData, GL32.GL_DYNAMIC_DRAW);
// Upload colors
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceColorVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, colorData, GL32.GL_DYNAMIC_DRAW);
// Upload materials
GL32.glBindBuffer(GL32.GL_ARRAY_BUFFER, this.instanceMaterialVbo);
GL32.glBufferData(GL32.GL_ARRAY_BUFFER, colorData, GL32.GL_DYNAMIC_DRAW);
}
//================//
// base overrides //
//================//
@Override
public String toString() { return "ID:["+this.id+"], pos:["+this.originBlockPos.x+","+this.originBlockPos.y+","+this.originBlockPos.z+"], size:["+this.size()+"], active:["+this.active+"]"; }
@Override
public void close()
{
GLProxy.getInstance().queueRunningOnRenderThread(() ->
{
if (this.instanceChunkPosVbo != 0)
{
GL32.glDeleteBuffers(this.instanceChunkPosVbo);
this.instanceChunkPosVbo = 0;
}
if (this.instanceSubChunkPosVbo != 0)
{
GL32.glDeleteBuffers(this.instanceSubChunkPosVbo);
this.instanceSubChunkPosVbo = 0;
}
if (this.instanceScaleVbo != 0)
{
GL32.glDeleteBuffers(this.instanceScaleVbo);
this.instanceScaleVbo = 0;
}
if (this.instanceColorVbo != 0)
{
GL32.glDeleteBuffers(this.instanceColorVbo);
this.instanceColorVbo = 0;
}
if (this.instanceMaterialVbo != 0)
{
GL32.glDeleteBuffers(this.instanceMaterialVbo);
this.instanceMaterialVbo = 0;
}
});
}
}
@@ -0,0 +1,104 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 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.distanthorizons.core.render.renderer.shaders;
import com.seibel.distanthorizons.core.render.glObject.shader.ShaderProgram;
import com.seibel.distanthorizons.core.render.renderer.FogRenderer;
import com.seibel.distanthorizons.core.render.renderer.LodRenderer;
import com.seibel.distanthorizons.core.render.renderer.ScreenQuad;
import org.lwjgl.opengl.GL32;
/**
* Draws the Fog texture onto DH's FrameBuffer. <br><br>
*
* See Also: <br>
* {@link FogRenderer} - Parent to this shader. <br>
* {@link FogShader} - draws the Fog texture. <br>
*/
public class FogApplyShader extends AbstractShaderRenderer
{
public static FogApplyShader INSTANCE = new FogApplyShader();
public int fogTexture;
// uniforms
public int colorTextureUniform;
public int depthTextureUniform;
//=============//
// constructor //
//=============//
@Override
public void onInit()
{
this.shader = new ShaderProgram(
"shaders/normal.vert",
"shaders/fog/apply.frag",
"fragColor",
new String[]{ "vPosition" });
// uniform setup
this.colorTextureUniform = this.shader.getUniformLocation("uColorTexture");
this.depthTextureUniform = this.shader.getUniformLocation("uDepthTexture");
}
//=============//
// render prep //
//=============//
@Override
protected void onApplyUniforms(float partialTicks)
{
GL32.glActiveTexture(GL32.GL_TEXTURE0);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, this.fogTexture);
GL32.glUniform1i(this.colorTextureUniform, 0);
GL32.glActiveTexture(GL32.GL_TEXTURE1);
GL32.glBindTexture(GL32.GL_TEXTURE_2D, LodRenderer.getActiveDepthTextureId());
GL32.glUniform1i(this.depthTextureUniform, 1);
}
//========//
// render //
//========//
@Override
protected void onRender()
{
GL32.glEnable(GL32.GL_BLEND);
GL32.glBlendEquation(GL32.GL_FUNC_ADD);
GL32.glBlendFuncSeparate(GL32.GL_SRC_ALPHA, GL32.GL_ONE_MINUS_SRC_ALPHA, GL32.GL_ONE, GL32.GL_ONE_MINUS_SRC_ALPHA);
// apply the rendered Fog to DH's framebuffer
GL32.glBindFramebuffer(GL32.GL_READ_FRAMEBUFFER, FogShader.INSTANCE.frameBuffer);
GL32.glBindFramebuffer(GL32.GL_DRAW_FRAMEBUFFER, LodRenderer.getActiveFramebufferId());
ScreenQuad.INSTANCE.render();
}
}

Some files were not shown because too many files have changed in this diff Show More