Create a new lighting engine
This commit is contained in:
+1
-1
@@ -52,7 +52,7 @@ public class DhApiWorldGenerationConfig implements IDhApiWorldGenerationConfig
|
||||
|
||||
@Override
|
||||
public IDhApiConfigValue<ELightGenerationMode> lightingEngine()
|
||||
{ return new DhApiConfigValue<>(WorldGenerator.lightingEngine); }
|
||||
{ return new DhApiConfigValue<>(WorldGenerator.worldGenLightingEngine); }
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -644,15 +644,15 @@ public class Config
|
||||
*/
|
||||
.build();
|
||||
|
||||
public static ConfigEntry<ELightGenerationMode> lightingEngine = new ConfigEntry.Builder<ELightGenerationMode>()
|
||||
.set(ELightGenerationMode.MINECRAFT)
|
||||
public static ConfigEntry<ELightGenerationMode> worldGenLightingEngine = new ConfigEntry.Builder<ELightGenerationMode>()
|
||||
.set(ELightGenerationMode.DISTANT_HORIZONS)
|
||||
.comment(""
|
||||
+ " How should distant generation chunk lighting be generated? \n"
|
||||
+ " How should Distant Horizons world generation chunk lighting be handled? \n"
|
||||
+ "\n"
|
||||
+ ELightGenerationMode.MINECRAFT + ": Use Minecraft's lighting engine to generate chunk lighting. \n"
|
||||
+ " Generally higher quality; but may crash MC's lighting engine if there is an issue. \n"
|
||||
+ ELightGenerationMode.DISTANT_HORIZONS + ": Uses Distant Horizons' lighting engine to estimate chunk lighting. \n"
|
||||
+ " Generally lower quality; but more stable for large numbers of world generator threads. \n"
|
||||
+ ELightGenerationMode.DISTANT_HORIZONS + ": Uses Distant Horizons' lighting engine to generate chunk lighting. \n"
|
||||
+ " May not exactly match MC's, but is more stable for large numbers of world generator threads. \n"
|
||||
+ "\n"
|
||||
+ "This will effect generation speed, but not rendering performance.")
|
||||
.build();
|
||||
|
||||
@@ -29,7 +29,7 @@ import com.seibel.distanthorizons.coreapi.util.math.Vec3i;
|
||||
|
||||
/**
|
||||
* An (almost) exact copy of Minecraft's
|
||||
* Direction enum.
|
||||
* Direction enum. <Br><Br>
|
||||
*
|
||||
* Up <Br>
|
||||
* Down <Br>
|
||||
@@ -49,7 +49,12 @@ public enum ELodDirection
|
||||
SOUTH(3, 2, 0, "south", ELodDirection.AxisDirection.POSITIVE, ELodDirection.Axis.Z, new Vec3i(0, 0, 1)),
|
||||
WEST(4, 5, 1, "west", ELodDirection.AxisDirection.NEGATIVE, ELodDirection.Axis.X, new Vec3i(-1, 0, 0)),
|
||||
EAST(5, 4, 3, "east", ELodDirection.AxisDirection.POSITIVE, ELodDirection.Axis.X, new Vec3i(1, 0, 0));
|
||||
public static final ELodDirection[] DIRECTIONS = new ELodDirection[] {
|
||||
|
||||
/**
|
||||
* Up, Down, West, East, North, South <br>
|
||||
* Similar to {@link ELodDirection#OPPOSITE_DIRECTIONS}, just with a different order
|
||||
*/
|
||||
public static final ELodDirection[] CARDINAL_DIRECTIONS = new ELodDirection[] {
|
||||
ELodDirection.UP,
|
||||
ELodDirection.DOWN,
|
||||
ELodDirection.WEST,
|
||||
@@ -57,6 +62,10 @@ public enum ELodDirection
|
||||
ELodDirection.NORTH,
|
||||
ELodDirection.SOUTH };
|
||||
|
||||
/**
|
||||
* Up, Down, South, North, East, West <br>
|
||||
* Similar to {@link ELodDirection#CARDINAL_DIRECTIONS}, just with a different order
|
||||
*/
|
||||
public static final ELodDirection[] OPPOSITE_DIRECTIONS = new ELodDirection[] {
|
||||
ELodDirection.UP,
|
||||
ELodDirection.DOWN,
|
||||
@@ -64,12 +73,14 @@ public enum ELodDirection
|
||||
ELodDirection.NORTH,
|
||||
ELodDirection.EAST,
|
||||
ELodDirection.WEST };
|
||||
/** North, South, East, West */
|
||||
|
||||
/** North, South, East, West */ // TODO rename to state this is just X/Z or flat directions
|
||||
public static final ELodDirection[] ADJ_DIRECTIONS = new ELodDirection[] {
|
||||
ELodDirection.EAST,
|
||||
ELodDirection.WEST,
|
||||
ELodDirection.SOUTH,
|
||||
ELodDirection.NORTH };
|
||||
|
||||
// private final int data3d;
|
||||
// private final int oppositeIndex;
|
||||
// private final int data2d;
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package com.seibel.distanthorizons.core.generation;
|
||||
|
||||
import com.seibel.distanthorizons.core.enums.ELodDirection;
|
||||
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.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This logic was roughly based on
|
||||
* <a href="https://github.com/PaperMC/Starlight/blob/acc8ed9634bbe27ec68e8842e420948bfa9707e7/TECHNICAL_DETAILS.md">Starlight's technical documentation</a>
|
||||
* although there were some changes due to how our lighting engine works in comparison to Minecraft's.
|
||||
*/
|
||||
public class DhLightingEngine
|
||||
{
|
||||
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
|
||||
public static final DhLightingEngine INSTANCE = new DhLightingEngine();
|
||||
|
||||
|
||||
private DhLightingEngine() { }
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)}
|
||||
* the light values may be stored in the wrapper itself instead of the wrapped chunk object.
|
||||
* If that is the case unwrapping the chunk will undo any work this method did.
|
||||
*
|
||||
* @param centerChunk the chunk we want to apply lighting to
|
||||
* @param nearbyChunkList should also contain centerChunk
|
||||
* @param maxSkyLight should be a value between 0 and 15
|
||||
*/
|
||||
public void lightChunks(IChunkWrapper centerChunk, List<IChunkWrapper> nearbyChunkList, int maxSkyLight)
|
||||
{
|
||||
DhChunkPos centerChunkPos = centerChunk.getChunkPos();
|
||||
|
||||
HashMap<DhChunkPos, IChunkWrapper> chunksByChunkPos = new HashMap<>(9);
|
||||
LinkedList<LightPos> blockLightPosQueue = new LinkedList<>();
|
||||
LinkedList<LightPos> skyLightPosQueue = new LinkedList<>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// find all adjacent chunks
|
||||
// and get any necessary info from them
|
||||
for (IChunkWrapper chunk : nearbyChunkList)
|
||||
{
|
||||
if (chunk != null && requestedAdjacentPositions.contains(chunk.getChunkPos()))
|
||||
{
|
||||
// remove the newly found position
|
||||
requestedAdjacentPositions.remove(chunk.getChunkPos());
|
||||
|
||||
// add the adjacent chunk
|
||||
chunksByChunkPos.put(chunk.getChunkPos(), chunk);
|
||||
|
||||
|
||||
|
||||
// get and set the adjacent chunk's initial block lights
|
||||
List<DhBlockPos> blockLightPosList = chunk.getBlockLightPosList();
|
||||
for (DhBlockPos blockLightPos : blockLightPosList)
|
||||
{
|
||||
// get the light
|
||||
DhBlockPos relLightBlockPos = blockLightPos.convertToChunkRelativePos();
|
||||
IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos);
|
||||
int lightValue = blockState.getLightEmission();
|
||||
blockLightPosQueue.add(new LightPos(blockLightPos, lightValue));
|
||||
|
||||
// set the light
|
||||
DhBlockPos relBlockPos = blockLightPos.convertToChunkRelativePos();
|
||||
chunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, lightValue);
|
||||
}
|
||||
|
||||
|
||||
// get and set the adjacent chunk's initial skylights,
|
||||
// if the dimension has skylights
|
||||
if (maxSkyLight > 0)
|
||||
{
|
||||
// get the adjacent chunk's sky lights
|
||||
for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) // relative block pos
|
||||
{
|
||||
for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++)
|
||||
{
|
||||
// get the light
|
||||
int maxY = chunk.getLightBlockingHeightMapValue(relX, relZ);
|
||||
DhBlockPos skyLightPos = new DhBlockPos(chunk.getMinX() + relX, maxY, chunk.getMinZ() + relZ);
|
||||
skyLightPosQueue.add(new LightPos(skyLightPos, maxSkyLight));
|
||||
|
||||
// set the light
|
||||
DhBlockPos relBlockPos = skyLightPos.convertToChunkRelativePos();
|
||||
chunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, maxSkyLight);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (requestedAdjacentPositions.isEmpty())
|
||||
{
|
||||
// we found every chunk we needed, we don't need to keep iterating
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// validate that at least 1 chunk was found
|
||||
if (chunksByChunkPos.size() == 0)
|
||||
{
|
||||
LOGGER.warn("Attempted to generate lighting for position ["+centerChunkPos+"], but neither that chunk nor any adjacent chunks were found. No chunk lighting was performed.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// block light
|
||||
this.propagateLightPosList(blockLightPosQueue, chunksByChunkPos,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
|
||||
|
||||
// sky light
|
||||
this.propagateLightPosList(skyLightPosQueue, chunksByChunkPos,
|
||||
(neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z),
|
||||
(neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.x, relBlockPos.y, relBlockPos.z, newLightValue));
|
||||
|
||||
|
||||
LOGGER.trace("Finished generating lighting for chunk: ["+centerChunkPos+"]");
|
||||
}
|
||||
|
||||
/** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */
|
||||
private void propagateLightPosList(
|
||||
LinkedList<LightPos> lightPosQueue, HashMap<DhChunkPos, IChunkWrapper> chunksByChunkPos,
|
||||
IGetLightFunc getLightFunc, ISetLightFunc setLightFunc)
|
||||
{
|
||||
// update each light position
|
||||
while (!lightPosQueue.isEmpty())
|
||||
{
|
||||
LightPos lightPos = lightPosQueue.poll();
|
||||
DhBlockPos pos = lightPos.pos;
|
||||
int lightValue = lightPos.lightValue;
|
||||
|
||||
// propagate the lighting in each cardinal direction, IE: -x, +x, -y, +y, -z, +z
|
||||
for (ELodDirection direction : ELodDirection.CARDINAL_DIRECTIONS)
|
||||
{
|
||||
DhBlockPos neighbourBlockPos = pos.offset(direction);
|
||||
DhChunkPos neighbourChunkPos = new DhChunkPos(neighbourBlockPos);
|
||||
// converting the block pos into a relative position is necessary for accessing the light values in the chunk
|
||||
DhBlockPos relNeighbourBlockPos = neighbourBlockPos.convertToChunkRelativePos();
|
||||
|
||||
|
||||
// only continue if the light position is inside one of our chunks
|
||||
IChunkWrapper neighbourChunk = chunksByChunkPos.get(neighbourChunkPos);
|
||||
if (neighbourChunk == null)
|
||||
{
|
||||
// the light pos is outside our generator's range, ignore it
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
int currentBlockLight = getLightFunc.getLight(neighbourChunk, relNeighbourBlockPos);
|
||||
if (currentBlockLight >= (lightValue - 1))
|
||||
{
|
||||
// short circuit for when the light value at this position
|
||||
// is already greater-than what we could set it
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
IBlockStateWrapper neighbourBlockState = neighbourChunk.getBlockState(relNeighbourBlockPos);
|
||||
// Math.max(1, ...) is used so that the propagated light level always drops by at least 1, preventing infinite cycles.
|
||||
int targetLevel = lightValue - Math.max(1, neighbourBlockState.getOpacity());
|
||||
if (targetLevel > currentBlockLight)
|
||||
{
|
||||
// this position is darker than the new light value, update/set it
|
||||
setLightFunc.setLight(neighbourChunk, relNeighbourBlockPos, targetLevel);
|
||||
|
||||
// now that light has been propagated to this blockPos
|
||||
// we need to queue it up so its neighbours can be propagated as well
|
||||
lightPosQueue.add(new LightPos(neighbourBlockPos, targetLevel));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propagation complete
|
||||
}
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper classes //
|
||||
//================//
|
||||
|
||||
@FunctionalInterface
|
||||
interface IGetLightFunc { int getLight(IChunkWrapper chunk, DhBlockPos pos); }
|
||||
@FunctionalInterface
|
||||
interface ISetLightFunc { void setLight(IChunkWrapper chunk, DhBlockPos pos, int lightValue); }
|
||||
|
||||
private static class LightPos
|
||||
{
|
||||
public final DhBlockPos pos;
|
||||
public final int lightValue;
|
||||
|
||||
public LightPos(DhBlockPos pos, int lightValue)
|
||||
{
|
||||
this.pos = pos;
|
||||
this.lightValue = lightValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -19,6 +19,9 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.pos;
|
||||
|
||||
import com.seibel.distanthorizons.core.enums.ELodDirection;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class DhBlockPos {
|
||||
@@ -110,10 +113,23 @@ public class DhBlockPos {
|
||||
return asLong(x, y, z);
|
||||
}
|
||||
|
||||
public DhBlockPos offset(int x, int y, int z)
|
||||
{
|
||||
return new DhBlockPos(this.x + x, this.y + y, this.z + z);
|
||||
}
|
||||
public DhBlockPos offset(ELodDirection direction) { return this.offset(direction.getNormal().x, direction.getNormal().y, direction.getNormal().z); }
|
||||
public DhBlockPos offset(int x, int y, int z) { return new DhBlockPos(this.x + x, this.y + y, this.z + z); }
|
||||
|
||||
/** Limits the block position to a value between 0 and 15 (inclusive) */
|
||||
public DhBlockPos convertToChunkRelativePos()
|
||||
{
|
||||
// move the position into the range -15 and +15
|
||||
int relX = (this.x % LodUtil.CHUNK_WIDTH);
|
||||
// if the position is negative move it into the range 0 and 15
|
||||
relX = (relX < 0) ? (relX + LodUtil.CHUNK_WIDTH) : relX;
|
||||
|
||||
int relZ = (this.z % LodUtil.CHUNK_WIDTH);
|
||||
relZ = (relZ < 0) ? (relZ + LodUtil.CHUNK_WIDTH) : relZ;
|
||||
|
||||
// the y value shouldn't need to be changed
|
||||
return new DhBlockPos(relX, this.y, relZ);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to quickly determine the rough distance between two points<Br>
|
||||
|
||||
+9
-6
@@ -2,14 +2,17 @@ package com.seibel.distanthorizons.core.wrapperInterfaces.block;
|
||||
|
||||
import com.seibel.distanthorizons.api.interfaces.block.IDhApiBlockStateWrapper;
|
||||
|
||||
/**
|
||||
* A Minecraft version independent way of handling Blocks.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 2022-11-12
|
||||
*/
|
||||
/** A Minecraft version independent way of handling Blocks. */
|
||||
public interface IBlockStateWrapper extends IDhApiBlockStateWrapper
|
||||
{
|
||||
String serialize();
|
||||
|
||||
/**
|
||||
* Returning a value of 0 means the block is completely transparent. <br.
|
||||
* Returning a value of 15 means the block is completely opaque.
|
||||
*/
|
||||
int getOpacity();
|
||||
|
||||
int getLightEmission();
|
||||
|
||||
}
|
||||
|
||||
+31
-7
@@ -19,14 +19,20 @@
|
||||
|
||||
package com.seibel.distanthorizons.core.wrapperInterfaces.chunk;
|
||||
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos;
|
||||
import com.seibel.distanthorizons.core.pos.DhBlockPos2D;
|
||||
import com.seibel.distanthorizons.core.pos.DhChunkPos;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.distanthorizons.coreapi.interfaces.dependencyInjection.IBindable;
|
||||
import com.seibel.distanthorizons.core.util.LodUtil;
|
||||
import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface IChunkWrapper extends IBindable
|
||||
{
|
||||
DhChunkPos getChunkPos();
|
||||
|
||||
default int getHeight() { return this.getMaxBuildHeight() - this.getMinBuildHeight(); }
|
||||
int getMinBuildHeight();
|
||||
int getMaxBuildHeight();
|
||||
@@ -46,18 +52,35 @@ public interface IChunkWrapper extends IBindable
|
||||
|
||||
long getLongChunkPos();
|
||||
|
||||
void setIsDhLightCorrect(boolean isDhLightCorrect);
|
||||
boolean isLightCorrect();
|
||||
|
||||
default int getBlockLight(int x, int y, int z) {return -1;}
|
||||
|
||||
default int getSkyLight(int x, int y, int z) {return -1;}
|
||||
int getDhSkyLight(int relX, int relY, int relZ);
|
||||
void setDhSkyLight(int relX, int relY, int relZ, int lightValue);
|
||||
|
||||
int getDhBlockLight(int relX, int relY, int relZ);
|
||||
void setDhBlockLight(int relX, int relY, int relZ, int lightValue);
|
||||
|
||||
int getBlockLight(int relX, int relY, int relZ);
|
||||
int getSkyLight(int relX, int relY, int relZ);
|
||||
|
||||
|
||||
List<DhBlockPos> getBlockLightPosList();
|
||||
|
||||
|
||||
default boolean blockPosInsideChunk(DhBlockPos blockPos) { return this.blockPosInsideChunk(blockPos.x, blockPos.y, blockPos.z); }
|
||||
default boolean blockPosInsideChunk(int x, int y, int z)
|
||||
{
|
||||
return (x >= this.getMinX() && x <= this.getMaxX()
|
||||
&& y >= this.getMinBuildHeight() && y < this.getMaxBuildHeight()
|
||||
&& z >= this.getMinZ() && z <= this.getMaxZ());
|
||||
}
|
||||
default boolean blockPosInsideChunk(DhBlockPos2D blockPos)
|
||||
{
|
||||
return (blockPos.x >= this.getMinX() && blockPos.x <= this.getMaxX()
|
||||
&& blockPos.z >= this.getMinZ() && blockPos.z <= this.getMaxZ());
|
||||
}
|
||||
|
||||
boolean doesNearbyChunksExist();
|
||||
String toString();
|
||||
@@ -78,11 +101,12 @@ public interface IChunkWrapper extends IBindable
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
IBlockStateWrapper getBlockState(int x, int y, int z);
|
||||
IBiomeWrapper getBiome(int x, int y, int z);
|
||||
|
||||
DhChunkPos getChunkPos();
|
||||
|
||||
|
||||
default IBlockStateWrapper getBlockState(DhBlockPos pos) { return this.getBlockState(pos.x, pos.y, pos.z); }
|
||||
IBlockStateWrapper getBlockState(int relX, int relY, int relZ);
|
||||
|
||||
IBiomeWrapper getBiome(int relX, int relY, int relZ);
|
||||
|
||||
boolean isStillValid();
|
||||
}
|
||||
|
||||
@@ -275,9 +275,9 @@
|
||||
"Distance Generator Mode",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.distantGeneratorMode.@tooltip":
|
||||
"How complicated the generation should be when generating LODs outside the vanilla render distance.\n\n§6§6Fastest:§r Biome only\n§6Best Quality:§r Features (suggested)",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.lightingEngine":
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine":
|
||||
"Lighting Engine",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.lightingEngine.@tooltip":
|
||||
"distanthorizons.config.client.advanced.worldGenerator.worldGenLightingEngine.@tooltip":
|
||||
"§6Minecraft:§r use Minecraft's lighting engine, gives accurate lighting.\n§6Distant Horizons:§r estimates lighting, shadows won't be as smooth, but is more stable.\n\nIf the LODs appear black, set this to §6Distant Horizons§r.",
|
||||
"distanthorizons.config.client.advanced.worldGenerator.generationPriority":
|
||||
"Generation Priority",
|
||||
|
||||
Reference in New Issue
Block a user