Add a basic lighting engine for generated chunks
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* This file is part of the Distant Horizons mod (formerly the LOD Mod),
|
||||
* licensed under the GNU LGPL v3 License.
|
||||
*
|
||||
* Copyright (C) 2020-2022 James Seibel
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.seibel.lod.common.wrappers;
|
||||
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
|
||||
/**
|
||||
* Stores any variables or code that
|
||||
* may be shared between wrapper objects.
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 11-20-2021
|
||||
*/
|
||||
public class WrapperUtil {
|
||||
/** If we ever need to use a heightmap for any reason, use this one. */
|
||||
public static final Heightmap.Types DEFAULT_HEIGHTMAP = Heightmap.Types.WORLD_SURFACE_WG;
|
||||
}
|
||||
@@ -20,12 +20,14 @@
|
||||
package com.seibel.lod.common.wrappers.chunk;
|
||||
|
||||
import com.seibel.lod.common.wrappers.block.BlockStateWrapper;
|
||||
import com.seibel.lod.core.pos.DhBlockPos;
|
||||
import com.seibel.lod.core.pos.DhChunkPos;
|
||||
import com.seibel.lod.core.pos.Pos2D;
|
||||
import com.seibel.lod.core.util.LodUtil;
|
||||
import com.seibel.lod.core.wrapperInterfaces.block.IBlockStateWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.chunk.IChunkWrapper;
|
||||
import com.seibel.lod.core.wrapperInterfaces.world.IBiomeWrapper;
|
||||
|
||||
import com.seibel.lod.common.wrappers.WrapperUtil;
|
||||
import com.seibel.lod.common.wrappers.block.BiomeWrapper;
|
||||
import com.seibel.lod.common.wrappers.worldGeneration.mimicObject.LightedWorldGenRegion;
|
||||
|
||||
@@ -36,6 +38,7 @@ import net.minecraft.core.QuartPos;
|
||||
#endif
|
||||
import net.minecraft.world.level.LevelReader;
|
||||
import net.minecraft.world.level.LightLayer;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.ChunkStatus;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
@@ -43,13 +46,9 @@ import net.minecraft.world.level.levelgen.Heightmap;
|
||||
|
||||
// Which nullable should be used???
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
//import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author James Seibel
|
||||
* @version 3-5-2022
|
||||
*/
|
||||
import java.util.HashMap;
|
||||
|
||||
public class ChunkWrapper implements IChunkWrapper
|
||||
{
|
||||
private final ChunkAccess chunk;
|
||||
@@ -57,21 +56,31 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
private final LevelReader lightSource;
|
||||
private final ILevelWrapper wrappedLevel;
|
||||
|
||||
private final HashMap<BlockPos, BlockState> blockStateByBlockPosCache = new HashMap<>();
|
||||
|
||||
|
||||
|
||||
public ChunkWrapper(ChunkAccess chunk, LevelReader lightSource, @Nullable ILevelWrapper wrappedLevel)
|
||||
{
|
||||
this.chunk = chunk;
|
||||
this.lightSource = lightSource;
|
||||
this.wrappedLevel = wrappedLevel;
|
||||
chunkPos = new DhChunkPos(chunk.getPos().x, chunk.getPos().z);
|
||||
this.chunkPos = new DhChunkPos(chunk.getPos().x, chunk.getPos().z);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//=========//
|
||||
// methods //
|
||||
//=========//
|
||||
|
||||
@Override
|
||||
public int getHeight(){
|
||||
public int getHeight()
|
||||
{
|
||||
#if PRE_MC_1_17_1
|
||||
return 255;
|
||||
#else
|
||||
return chunk.getHeight();
|
||||
return this.chunk.getHeight();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -81,20 +90,20 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
#if PRE_MC_1_17_1
|
||||
return 0;
|
||||
#else
|
||||
return chunk.getMinBuildHeight();
|
||||
return this.chunk.getMinBuildHeight();
|
||||
#endif
|
||||
}
|
||||
@Override
|
||||
public int getMaxBuildHeight()
|
||||
{
|
||||
return chunk.getMaxBuildHeight();
|
||||
}
|
||||
public int getMaxBuildHeight() { return this.chunk.getMaxBuildHeight(); }
|
||||
|
||||
|
||||
@Override
|
||||
public int getHeightMapValue(int xRel, int zRel)
|
||||
{
|
||||
return chunk.getOrCreateHeightmapUnprimed(WrapperUtil.DEFAULT_HEIGHTMAP).getFirstAvailable(xRel, zRel);
|
||||
}
|
||||
public int getSolidHeightMapValue(int xRel, int zRel) { return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.OCEAN_FLOOR).getFirstAvailable(xRel, zRel); }
|
||||
|
||||
@Override
|
||||
public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.chunk.getOrCreateHeightmapUnprimed(Heightmap.Types.MOTION_BLOCKING).getFirstAvailable(xRel, zRel); }
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public IBiomeWrapper getBiome(int x, int y, int z)
|
||||
@@ -117,101 +126,218 @@ public class ChunkWrapper implements IChunkWrapper
|
||||
}
|
||||
|
||||
@Override
|
||||
public DhChunkPos getChunkPos() {
|
||||
return chunkPos;
|
||||
}
|
||||
public DhChunkPos getChunkPos() { return this.chunkPos; }
|
||||
|
||||
public ChunkAccess getChunk() {
|
||||
return chunk;
|
||||
}
|
||||
public ChunkAccess getChunk() { return this.chunk; }
|
||||
|
||||
@Override
|
||||
public int getMaxY(int x, int z) {
|
||||
return chunk.getHeight(Heightmap.Types.WORLD_SURFACE, Math.floorMod(x, 16), Math.floorMod(z, 16));
|
||||
}
|
||||
public int getMaxX() { return this.chunk.getPos().getMaxBlockX(); }
|
||||
@Override
|
||||
public int getMaxZ() { return this.chunk.getPos().getMaxBlockZ(); }
|
||||
@Override
|
||||
public int getMinX() { return this.chunk.getPos().getMinBlockX(); }
|
||||
@Override
|
||||
public int getMinZ() { return this.chunk.getPos().getMinBlockZ(); }
|
||||
|
||||
@Override
|
||||
public int getMaxX(){
|
||||
return chunk.getPos().getMaxBlockX();
|
||||
}
|
||||
@Override
|
||||
public int getMaxZ(){
|
||||
return chunk.getPos().getMaxBlockZ();
|
||||
}
|
||||
@Override
|
||||
public int getMinX(){
|
||||
return chunk.getPos().getMinBlockX();
|
||||
}
|
||||
@Override
|
||||
public int getMinZ() {
|
||||
return chunk.getPos().getMinBlockZ();
|
||||
}
|
||||
public long getLongChunkPos() { return this.chunk.getPos().toLong(); }
|
||||
|
||||
@Override
|
||||
public long getLongChunkPos() {
|
||||
return chunk.getPos().toLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLightCorrect(){
|
||||
public boolean isLightCorrect()
|
||||
{
|
||||
#if PRE_MC_1_18_1
|
||||
return true;
|
||||
#else
|
||||
if (chunk instanceof LevelChunk)
|
||||
if (this.chunk instanceof LevelChunk)
|
||||
{
|
||||
// called when connected to a server (and sometimes when in a singleplayer world)
|
||||
return ((LevelChunk) chunk).isClientLightReady() || chunk.isLightCorrect();
|
||||
return ((LevelChunk) this.chunk).isClientLightReady() || this.chunk.isLightCorrect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// called when in a single player world
|
||||
return chunk.isLightCorrect();
|
||||
return this.chunk.isLightCorrect();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBlockLight(int x, int y, int z) {
|
||||
if (lightSource == null) return -1;
|
||||
return lightSource.getBrightness(LightLayer.BLOCK, new BlockPos(x + getMinX(),y,z + getMinZ()));
|
||||
public int getBlockLight(int x, int y, int z)
|
||||
{
|
||||
// TODO is this the best way to differentiate between when we are generating chunks and when MC gave us a chunk?
|
||||
if (this.lightSource.getClass() != LightedWorldGenRegion.class)
|
||||
{
|
||||
// MC lighting method
|
||||
// (used when the chunks are within render distance)
|
||||
return this.lightSource.getBrightness(LightLayer.BLOCK, new BlockPos(x+this.getMinX(), y, z+this.getMinZ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// DH lighting method
|
||||
return this.getMaxBlockLightAtBlockPos(new DhBlockPos(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSkyLight(int x, int y, int z) {
|
||||
if (lightSource == null) return -1;
|
||||
return lightSource.getBrightness(LightLayer.SKY, new BlockPos(x + getMinX(),y,z + getMinZ()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesNearbyChunksExist() {
|
||||
if (lightSource instanceof LightedWorldGenRegion) return true;
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dz = -1; dz <= 1; dz++) {
|
||||
if (dx==0 && dz==0) continue;
|
||||
if (lightSource.getChunk(dx+chunk.getPos().x, dz+chunk.getPos().z, ChunkStatus.BIOMES, false) == null) return false;
|
||||
/**
|
||||
* Note: this doesn't take into account blocks outside this chunk's borders <br>
|
||||
* AKA: there will be sharp shadow cut-offs on chunk boundaries
|
||||
*
|
||||
* @param inputPos a relative position for this chunk (IE between [0,0] and [15,15])
|
||||
* @apiNote TODO DH lighting should be moved into its own class in Core
|
||||
*/
|
||||
private int getMaxBlockLightAtBlockPos(DhBlockPos inputPos)
|
||||
{
|
||||
// how many blocks (in a square) to take into account when generating lighting
|
||||
// higher numbers will make the lighting more accurate but will take significantly longer
|
||||
int width = 4;
|
||||
|
||||
int maxBlockLight = this.getCachedBlockState(new BlockPos(inputPos.x, inputPos.y, inputPos.z)).getLightEmission();
|
||||
int inputMaxYPos = this.getSolidHeightMapValue(inputPos.x, inputPos.z);
|
||||
|
||||
// min and max calls to clamp the position to this chunk
|
||||
for (int relX = Math.max(0, inputPos.x-width); relX < Math.min(inputPos.x+width, LodUtil.CHUNK_WIDTH); relX++)
|
||||
{
|
||||
for (int relZ = Math.max(0, inputPos.z-width); relZ < Math.min(inputPos.z+width, LodUtil.CHUNK_WIDTH); relZ++)
|
||||
{
|
||||
int lightYPos = this.getSolidHeightMapValue(relX, relZ);
|
||||
|
||||
if (inputPos.y < lightYPos && inputPos.y < inputMaxYPos)
|
||||
{
|
||||
// input is below the light
|
||||
// and another block that isn't the light, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
// the max(height, height-1) is to fix an edge case involving torches
|
||||
int lightValue = Math.max(
|
||||
this.getCachedBlockState(new BlockPos(relX, lightYPos, relZ)).getLightEmission(),
|
||||
this.getCachedBlockState(new BlockPos(relX, lightYPos-1, relZ)).getLightEmission());
|
||||
|
||||
|
||||
int centerDistanceFromLight = new Pos2D(inputPos.x, inputPos.z).manhattanDist(new Pos2D(relX, relZ));
|
||||
// multiply falloff by 2 to make light fade faster to reduce the number of hard edges caused by going outside of chunk boundaries
|
||||
// (This could be removed if we add the ability to access blocks outside this chunk)
|
||||
centerDistanceFromLight *= 2;
|
||||
|
||||
lightValue = lightValue - centerDistanceFromLight;
|
||||
maxBlockLight = Math.max(maxBlockLight, lightValue);
|
||||
}
|
||||
}
|
||||
|
||||
return maxBlockLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSkyLight(int x, int y, int z)
|
||||
{
|
||||
// TODO is this the best way to differentiate between when we are generating chunks and when MC gave us a chunk?
|
||||
if (this.lightSource.getClass() != LightedWorldGenRegion.class)
|
||||
{
|
||||
// MC lighting method
|
||||
// (used when the chunks are within render distance)
|
||||
return this.lightSource.getBrightness(LightLayer.SKY, new BlockPos(x+this.getMinX(), y, z+this.getMinZ()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// DH lighting method
|
||||
return this.getMaxSkyLightAtBlockPos(new DhBlockPos(x,y,z));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Note: this doesn't take into account blocks outside this chunk's borders <br>
|
||||
* AKA: there will be sharp shadow cut-offs on chunk boundaries
|
||||
*
|
||||
* @param inputPos a relative position for this chunk (IE between [0,0] and [15,15])
|
||||
* @apiNote TODO DH lighting should be moved into its own class in Core
|
||||
*/
|
||||
private int getMaxSkyLightAtBlockPos(DhBlockPos inputPos)
|
||||
{
|
||||
// Note: this doesn't take into account blocks outside this chunk's borders
|
||||
// AKA: there will be sharp shadow cut-offs on chunk boundaries
|
||||
|
||||
// how many blocks (in a square) to take into account when generating lighting
|
||||
// higher numbers will make the lighting more accurate but will take longer
|
||||
int width = 4;
|
||||
|
||||
|
||||
int maxSkyLight = 0;
|
||||
Pos2D centerPos = new Pos2D(inputPos.x,inputPos.z);
|
||||
|
||||
// min and max calls to clamp the position to this chunk
|
||||
for (int relX = Math.max(0, inputPos.x-width); relX < Math.min(inputPos.x+width, LodUtil.CHUNK_WIDTH); relX++)
|
||||
{
|
||||
for (int relZ = Math.max(0, inputPos.z-width); relZ < Math.min(inputPos.z+width, LodUtil.CHUNK_WIDTH); relZ++)
|
||||
{
|
||||
int heightAtPos = this.getLightBlockingHeightMapValue(relX, relZ);
|
||||
int lightAtRelPos = (inputPos.y >= heightAtPos) ? 15 : 0; // 15 if it can see the sky, 0 otherwise
|
||||
|
||||
int centerDistanceFromLight = centerPos.manhattanDist(new Pos2D(relX, relZ));
|
||||
int centerLight = Math.max(0, lightAtRelPos - centerDistanceFromLight);
|
||||
|
||||
maxSkyLight = Math.max(maxSkyLight, centerLight);
|
||||
}
|
||||
}
|
||||
|
||||
return maxSkyLight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean doesNearbyChunksExist()
|
||||
{
|
||||
if (this.lightSource instanceof LightedWorldGenRegion)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int dx = -1; dx <= 1; dx++)
|
||||
{
|
||||
for (int dz = -1; dz <= 1; dz++)
|
||||
{
|
||||
if (dx == 0 && dz == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (this.lightSource.getChunk(dx + this.chunk.getPos().x, dz + this.chunk.getPos().z, ChunkStatus.BIOMES, false) == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public LevelReader getColorResolver()
|
||||
public LevelReader getColorResolver() { return this.lightSource; }
|
||||
|
||||
@Override
|
||||
public String toString() { return this.chunk.getClass().getSimpleName() + this.chunk.getPos(); }
|
||||
|
||||
@Override
|
||||
public IBlockStateWrapper getBlockState(int x, int y, int z)
|
||||
{
|
||||
return lightSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return chunk.getClass().getSimpleName() + chunk.getPos();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBlockStateWrapper getBlockState(int x, int y, int z) {
|
||||
//if (wrappedLevel != null) return wrappedLevel.getBlockState(new DhBlockPos(x + getMinX(), y, z + getMinZ()));
|
||||
return BlockStateWrapper.fromBlockState(chunk.getBlockState(new BlockPos(x,y,z)));
|
||||
return BlockStateWrapper.fromBlockState(this.chunk.getBlockState(new BlockPos(x,y,z)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStillValid() {
|
||||
return wrappedLevel == null || wrappedLevel.tryGetChunk(chunkPos) == this;
|
||||
public boolean isStillValid() { return this.wrappedLevel == null || this.wrappedLevel.tryGetChunk(this.chunkPos) == this; }
|
||||
|
||||
|
||||
|
||||
//================//
|
||||
// helper methods //
|
||||
//================//
|
||||
|
||||
/** can be used to speed up operations that need to interact with blockstates often */
|
||||
private BlockState getCachedBlockState(BlockPos pos)
|
||||
{
|
||||
if (!this.blockStateByBlockPosCache.containsKey(pos))
|
||||
{
|
||||
BlockState blockState = this.chunk.getBlockState(pos);
|
||||
this.blockStateByBlockPosCache.put(pos, blockState);
|
||||
}
|
||||
|
||||
return this.blockStateByBlockPosCache.get(pos);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+40
-19
@@ -255,36 +255,57 @@ public class LightedWorldGenRegion extends WorldGenRegion
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
// Override force use of my own light engine
|
||||
|
||||
/** Overriding allows us to use our own lighting engine */
|
||||
@Override
|
||||
public LevelLightEngine getLightEngine() {
|
||||
return light;
|
||||
}
|
||||
|
||||
// Override force use of my own light engine
|
||||
/** Overriding allows us to use our own lighting engine */
|
||||
@Override
|
||||
public int getBrightness(LightLayer lightLayer, BlockPos blockPos) {
|
||||
if (lightMode != ELightGenerationMode.FAST) {
|
||||
return light.getLayerListener(lightLayer).getLightValue(blockPos);
|
||||
public int getBrightness(LightLayer lightLayer, BlockPos blockPos)
|
||||
{
|
||||
if (this.lightMode != ELightGenerationMode.FAST)
|
||||
{
|
||||
// MC lighting method
|
||||
return this.light.getLayerListener(lightLayer).getLightValue(blockPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback DH lighting methods
|
||||
|
||||
if (lightLayer == LightLayer.BLOCK)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
BlockPos heightmapPos = super.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos);
|
||||
return (heightmapPos.getY() <= blockPos.getY()) ? this.getMaxLightLevel() : 0;
|
||||
}
|
||||
}
|
||||
if (lightLayer == LightLayer.BLOCK)
|
||||
return 0;
|
||||
BlockPos p = super.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos);
|
||||
return (p.getY() <= blockPos.getY()) ? getMaxLightLevel() : 0;
|
||||
}
|
||||
|
||||
// Override force use of my own light engine
|
||||
|
||||
/** Overriding allows us to use our own lighting engine */
|
||||
@Override
|
||||
public int getRawBrightness(BlockPos blockPos, int i) {
|
||||
if (lightMode != ELightGenerationMode.FAST) {
|
||||
return light.getRawBrightness(blockPos, i);
|
||||
public int getRawBrightness(BlockPos blockPos, int i)
|
||||
{
|
||||
if (this.lightMode != ELightGenerationMode.FAST)
|
||||
{
|
||||
// MC lighting method
|
||||
return this.light.getRawBrightness(blockPos, i);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fallback DH lighting methods
|
||||
|
||||
BlockPos heightmapPos = super.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos);
|
||||
return (heightmapPos.getY() <= blockPos.getY()) ? this.getMaxLightLevel() : 0;
|
||||
}
|
||||
BlockPos p = super.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES, blockPos);
|
||||
return (p.getY() <= blockPos.getY()) ? getMaxLightLevel() : 0;
|
||||
}
|
||||
|
||||
// Override force use of my own light engine
|
||||
|
||||
/** Overriding allows us to use our own lighting engine */
|
||||
@Override
|
||||
public boolean canSeeSky(BlockPos blockPos) {
|
||||
return (getBrightness(LightLayer.SKY, blockPos) >= getMaxLightLevel());
|
||||
|
||||
+1
-1
Submodule coreSubProjects updated: 013a293052...dc5086a29f
Reference in New Issue
Block a user