diff --git a/_Misc Files/test files/Lighting engine test chunk data.7z b/_Misc Files/test files/Lighting engine test chunk data.7z deleted file mode 100644 index 09a8dda5f..000000000 Binary files a/_Misc Files/test files/Lighting engine test chunk data.7z and /dev/null differ diff --git a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java index 15bca5429..4df42a8d3 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/api/internal/SharedApi.java @@ -316,41 +316,8 @@ public class SharedApi } - // chunk light baking is disabled since profiling revealed it used - // roughly the same amount of time as generating the lighting ourselves and - // was much more likely to have issues with corrupt (all black or all bright) chunks - boolean tryUsingMcLightingEngine = false; - if (tryUsingMcLightingEngine) - { - // 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 - boolean chunkLightPopulated = false; - boolean onlyUseDhLighting = Config.Client.Advanced.LodBuilding.onlyUseDhLightingEngine.get(); - if (!onlyUseDhLighting && chunkWrapper.isLightCorrect()) - { - // If MC's lighting engine isn't thread safe this may cause the server thread to lag - chunkLightPopulated = chunkWrapper.bakeDhLightingUsingMcLightingEngine(dhLevel.getLevelWrapper()); - if (!chunkLightPopulated) - { - // clear any existing data to prevent partial or corrupt lighting - // when re-generating it - chunkWrapper.clearDhBlockLighting(); - chunkWrapper.clearDhSkyLighting(); - } - } - - // something went wrong during the baking process so we have to generate the lighting ourselves - if (!chunkLightPopulated) - { - DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); - } - } - else - { - DhLightingEngine.INSTANCE.lightChunk(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); - } - - + // sky lighting is populated later at the data source level + DhLightingEngine.INSTANCE.bakeChunkBlockLighting(chunkWrapper, nearbyChunkList, dhLevel.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); dhLevel.updateBeaconBeamsForChunk(chunkWrapper, nearbyChunkList); dhLevel.updateChunkAsync(chunkWrapper, newChunkHash); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java index a727c05c5..00d4d4175 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/fullData/sources/FullDataSourceV2.java @@ -144,7 +144,7 @@ public class FullDataSourceV2 implements IDataSource this.columnWorldCompressionMode = columnWorldCompressionMode; } - public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createGeneratedDataSource(chunkWrapper); } + public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { return LodDataBuilder.createFromChunk(chunkWrapper); } public static FullDataSourceV2 createFromLegacyDataSourceV1(FullDataSourceV1 legacyData) { diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java index 97c76104b..203f92837 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/LodDataBuilder.java @@ -59,12 +59,10 @@ public class LodDataBuilder // converters // //============// - public static FullDataSourceV2 createGeneratedDataSource(IChunkWrapper chunkWrapper) + public static FullDataSourceV2 createFromChunk(IChunkWrapper chunkWrapper) { - if (!canGenerateLodFromChunk(chunkWrapper)) - { - return null; - } + // only block lighting is needed here, sky lighting is populated at the data source stage + LodUtil.assertTrue(chunkWrapper.isDhBlockLightingCorrect()); @@ -153,8 +151,8 @@ public class LodDataBuilder if (lastY < chunkWrapper.getMaxBuildHeight()) { // FIXME: The lastY +1 offset is to reproduce the old behavior. Remove this when we get per-face lighting - blockLight = (byte) chunkWrapper.getBlockLight(relBlockX, lastY + 1, relBlockZ); - skyLight = (byte) chunkWrapper.getSkyLight(relBlockX, lastY + 1, relBlockZ); + blockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, lastY + 1, relBlockZ); + skyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, lastY + 1, relBlockZ); } else { @@ -195,8 +193,8 @@ public class LodDataBuilder { IBiomeWrapper newBiome = chunkWrapper.getBiome(relBlockX, y, relBlockZ); IBlockStateWrapper newBlockState = previousBlockState = chunkWrapper.getBlockState(relBlockX, y, relBlockZ, mcBlockPos, previousBlockState); - byte newBlockLight = (byte) chunkWrapper.getBlockLight(relBlockX, y + 1, relBlockZ); - byte newSkyLight = (byte) chunkWrapper.getSkyLight(relBlockX, y + 1, relBlockZ); + byte newBlockLight = (byte) chunkWrapper.getDhBlockLight(relBlockX, y + 1, relBlockZ); + byte newSkyLight = (byte) chunkWrapper.getDhSkyLight(relBlockX, y + 1, relBlockZ); // save the biome/block change if (!newBiome.equals(biome) || !newBlockState.equals(blockState)) @@ -439,8 +437,6 @@ public class LodDataBuilder // helper methods // //================// - public static boolean canGenerateLodFromChunk(IChunkWrapper chunk) { return chunk != null && chunk.isLightCorrect(); } - public static int getXOrZSectionPosFromChunkPos(int chunkXOrZPos) { // get the section position diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java index 37c6ee221..be7b14207 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/fullDatafile/GeneratedFullDataSourceProvider.java @@ -23,6 +23,7 @@ import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGeneratio import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.file.structure.AbstractSaveStructure; +import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.generation.IFullDataSourceRetrievalQueue; import com.seibel.distanthorizons.core.generation.tasks.IWorldGenTaskTracker; import com.seibel.distanthorizons.core.generation.tasks.WorldGenResult; @@ -394,7 +395,14 @@ public class GeneratedFullDataSourceProvider extends FullDataSourceProviderV2 im } } private void onDataSourceSave(FullDataSourceV2 fullDataSource) - { GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); } + { + // block lights should have been populated at the chunkWrapper stage + // waiting to populate the data source's skylight at this stage prevents re-lighting and + // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once + DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, LodUtil.MAX_MC_LIGHT); + + GeneratedFullDataSourceProvider.this.updateDataSourceAsync(fullDataSource); + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java index 10bd1b3fa..91b1c76d6 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/file/subDimMatching/SubDimensionLevelMatcher.java @@ -184,11 +184,6 @@ public class SubDimensionLevelMatcher implements AutoCloseable DhLightingEngine.INSTANCE.lightChunk(newlyLoadedChunk, new ArrayList<>(), MC_CLIENT.getWrappedClientLevel().hasSkyLight() ? 15 : 0); // build the chunk LOD - if (!LodDataBuilder.canGenerateLodFromChunk(newlyLoadedChunk)) - { - LOGGER.warn("unable to build lod for chunk:"+newlyLoadedChunk.getChunkPos()); - return null; - } FullDataSourceV2 newChunkSizedFullDataView = FullDataSourceV2.createFromChunk(newlyLoadedChunk); // convert to a data source for easier comparing FullDataSourceV2 newDataSource = FullDataSourceV2.createEmpty(DhSectionPos.encodeContaining(DhSectionPos.SECTION_BLOCK_DETAIL_LEVEL, this.playerData.playerBlockPos)); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java index 380defd71..bfb18c5fa 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/DhLightingEngine.java @@ -60,11 +60,23 @@ public class DhLightingEngine private static final ThreadLocal PRIMARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable()); private static final ThreadLocal SECONDARY_BLOCK_POS_REF = ThreadLocal.withInitial(() -> new DhBlockPosMutable()); - /** if enabled will render each block light value when the lighting engine is run */ + /** if enabled will render each block light value when the chunk lighting engine is run */ private static final boolean RENDER_BLOCK_LIGHT_WIREFRAME = false; - /** if enabled will render each sky light value when the lighting engine is run */ + /** if enabled will render each sky light value when the chunk lighting engine is run */ private static final boolean RENDER_SKY_LIGHT_WIREFRAME = false; + /** + * Used for dataSource lighting.
+ * Packed as alternating x and z offsets. + */ + private static final byte[] ADJACENT_DIRECTION_OFFSETS = new byte[] + { + -1, 0, + +1, 0, + 0, -1, + 0, +1 + }; + //=============// @@ -75,9 +87,28 @@ public class DhLightingEngine - //=========// - // methods // - //=========// + //================// + // chunk lighting // + //================// + + /** + * Populates both block and sky lighting. + * @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean) + */ + public void lightChunk( + @NotNull IChunkWrapper centerChunk, @NotNull ArrayList nearbyChunkList, + int maxSkyLight) + { this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, true); } + + /** + * Only populates block lights. + * @see DhLightingEngine#lightChunk(IChunkWrapper, ArrayList, int, boolean, boolean) + */ + public void bakeChunkBlockLighting( + @NotNull IChunkWrapper centerChunk, @NotNull ArrayList nearbyChunkList, + int maxSkyLight) + { this.lightChunk(centerChunk, nearbyChunkList, maxSkyLight, true, false); } + /** * Note: depending on the implementation of {@link IChunkWrapper#setDhBlockLight(int, int, int, int)} and {@link IChunkWrapper#setDhSkyLight(int, int, int, int)} @@ -88,13 +119,13 @@ public class DhLightingEngine * @param nearbyChunkList should also contain centerChunk * @param maxSkyLight should be a value between 0 and 15 */ - public void lightChunk(@NotNull IChunkWrapper centerChunk, @NotNull ArrayList nearbyChunkList, int maxSkyLight) + private void lightChunk( + @NotNull IChunkWrapper centerChunk, @NotNull ArrayList nearbyChunkList, + int maxSkyLight, boolean updateBlockLight, boolean updateSkyLight) { DhChunkPos centerChunkPos = centerChunk.getChunkPos(); AdjacentChunkHolder adjacentChunkHolder = new AdjacentChunkHolder(centerChunk); - long startTimeNs = System.nanoTime(); - // try-finally to handle the stableArray resources StableLightPosStack blockLightWorldPosQueue = null; @@ -121,7 +152,6 @@ public class DhLightingEngine // find all adjacent chunks // and get any necessary info from them - boolean warningLogged = false; 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); @@ -133,28 +163,31 @@ public class DhLightingEngine // add the adjacent chunk adjacentChunkHolder.add(chunk); + // get and set the adjacent chunk's initial block lights + final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get(); + //==================// // set block lights // //==================// - // get and set the adjacent chunk's initial block lights - final DhBlockPosMutable relLightBlockPos = PRIMARY_BLOCK_POS_REF.get(); - - ArrayList blockLightPosList = chunk.getWorldBlockLightPosList(); - for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead + if (updateBlockLight) { - DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex); - blockLightPos.mutateToChunkRelativePos(relLightBlockPos); - - // get the light - IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos); - int lightValue = blockState.getLightEmission(); - blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue); - - // set the light - chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue); + ArrayList blockLightPosList = chunk.getWorldBlockLightPosList(); + for (int blockLightIndex = 0; blockLightIndex < blockLightPosList.size(); blockLightIndex++) // using iterators in high traffic areas can cause GC issues due to allocating a bunch of iterators, use an indexed for-loop instead + { + DhBlockPos blockLightPos = blockLightPosList.get(blockLightIndex); + blockLightPos.mutateToChunkRelativePos(relLightBlockPos); + + // get the light + IBlockStateWrapper blockState = chunk.getBlockState(relLightBlockPos); + int lightValue = blockState.getLightEmission(); + blockLightWorldPosQueue.push(blockLightPos.getX(), blockLightPos.getY(), blockLightPos.getZ(), lightValue); + + // set the light + chunk.setDhBlockLight(relLightBlockPos.getX(), relLightBlockPos.getY(), relLightBlockPos.getZ(), lightValue); + } } @@ -165,7 +198,7 @@ public class DhLightingEngine // get and set the adjacent chunk's initial skylights, // if the dimension has skylights - if (maxSkyLight > 0) + if (updateSkyLight && maxSkyLight > 0) { IMutableBlockPosWrapper mcBlockPos = chunk.getMutableBlockPosWrapper(); IBlockStateWrapper previousBlockState = null; @@ -211,16 +244,22 @@ public class DhLightingEngine } // block light - this.propagateLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, - (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), - (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), - true); + if (updateBlockLight) + { + this.propagateChunkLightPosList(blockLightWorldPosQueue, adjacentChunkHolder, + (neighbourChunk, relBlockPos) -> neighbourChunk.getDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), + (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhBlockLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), + true); + } // sky light - this.propagateLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, - (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), - (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), - false); + if (updateSkyLight) + { + this.propagateChunkLightPosList(skyLightWorldPosQueue, adjacentChunkHolder, + (neighbourChunk, relBlockPos) -> neighbourChunk.getDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ()), + (neighbourChunk, relBlockPos, newLightValue) -> neighbourChunk.setDhSkyLight(relBlockPos.getX(), relBlockPos.getY(), relBlockPos.getZ(), newLightValue), + false); + } } catch (Exception e) { @@ -233,17 +272,18 @@ public class DhLightingEngine } - - centerChunk.setIsDhLightCorrect(true); - centerChunk.setUseDhLighting(true); - - long endTimeNs = System.nanoTime(); - float totalTimeMs = (endTimeNs - startTimeNs) / 1_000_000.0f; - //LOGGER.trace("Finished generating lighting for chunk: [" + centerChunkPos + "] in ["+totalTimeMs+"] milliseconds"); + if (updateBlockLight) + { + centerChunk.setIsDhBlockLightCorrect(true); + } + if (updateSkyLight) + { + centerChunk.setIsDhSkyLightCorrect(true); + } } /** Applies each {@link LightPos} from the queue to the given set of {@link IChunkWrapper}'s. */ - private void propagateLightPosList( + private void propagateChunkLightPosList( StableLightPosStack lightPosQueue, AdjacentChunkHolder adjacentChunkHolder, IGetLightFunc getLightFunc, ISetLightFunc setLightFunc, boolean propagatingBlockLights) @@ -339,10 +379,239 @@ public class DhLightingEngine + //======================// + // data source lighting // + //======================// + + /** @author BuilderB0y */ + public void bakeDataSourceSkyLight(FullDataSourceV2 dataSource, int maxSkyLight) + { + // create a cache of all the IDs which are completely transparent. + // FullDataPointIdMap is thread-safe with locks, and is also a map lookup, + // and both of these things add a bit of overhead which is not necessary + // in this context. + // note: since IDs map to both biomes and blocks, there can be more than + // one ID which corresponds to air. + BitSet airIDs = new BitSet(dataSource.mapping.size()); + for (int id = 0, size = dataSource.mapping.size(); id < size; id++) + { + if (dataSource.mapping.getBlockStateWrapper(id).getOpacity() == 0) + { + airIDs.set(id, true); + } + } + + + for (int z = 0; z < FullDataSourceV2.WIDTH; z++) + { + for (int x = 0; x < FullDataSourceV2.WIDTH; x++) + { + LongArrayList dataPoints = dataSource.get(x, z); + if (dataPoints != null && !dataPoints.isEmpty()) + { + // iterate through the data points in this column top-down + // until we reach light level 0 in some way. at this point, + // no more propagation needs to be performed for this column. + int size = dataPoints.size(); + for (int index = 0; index < size; index++) + { + long point = dataPoints.getLong(index); + // if the data point in the column is transparent, + // then fill it with light and then propagate + // that light both horizontally and downwards. + if (airIDs.get(FullDataPointUtil.getId(point))) + { + int skylight; + if (index == 0) + { + // top-most data point in the column. + skylight = maxSkyLight; + } + else + { + // handle down propagation here. sort of. + // down propagation is also handled partially elsewhere. + // basically if the data point above is transparent, + // we copy its light level. + // otherwise, if the data point above is opaque, + // then no light can propagate downwards from it. + // therefore, this data point should be light level 0* + // and no more propagation needs to be performed for this column. + // + // *unless light propagates into it horizontally, + // but that is handled separately. + long above = dataPoints.getLong(index - 1); + if (airIDs.get(FullDataPointUtil.getId(above))) + { + skylight = FullDataPointUtil.getSkyLight(above); + } + else + { + continue; + } + } + + // update the data point to contain the correct starting skylight level. + point = FullDataPointUtil.setSkyLight(point, skylight); + dataPoints.set(index, point); + // now for the propagation. + recursivelyLightAdjacentDataPoints(dataSource, airIDs, x, z, point); + } + } + } + } + } + + + // at this point, all transparent data points have been lit, + // but opaque ones still have light level 0. + // in this loop we make opaque data points copy the light level + // above them if, and only if, the data point above is translucent. + // with one exception: if the data point above is only partially translucent, + // we use a slightly different way of computing how much light it absorbed. + // this is how we handle water and ocean floors. + // note that this alternate logic assumes the + // data point above is being lit from the top. + // this is a fine assumption for water and oceans. + for (LongArrayList list : dataSource.dataPoints) + { + if (list != null) + { + for (int index = 0, size = list.size(); index < size; index++) + { + long dataPoint = list.getLong(index); + if (index == 0) + { + // top data point, assume "above" has the max sky light. + dataPoint = FullDataPointUtil.setSkyLight(dataPoint, maxSkyLight); + list.set(index, dataPoint); + } + else + { + // there is another data point above this one. + // check to see how opaque this data point is first. + // we will check the above one after that. + if (!airIDs.get(FullDataPointUtil.getId(dataPoint))) + { + // this data point is not transparent. + // it should be lit from above. + long above = list.getLong(index - 1); + int aboveLight = FullDataPointUtil.getSkyLight(above); + if (airIDs.get(FullDataPointUtil.getId(above))) + { + // the above data point is transparent, + // and does not absorb any light. + // its light level can be copied as-is. + dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight); + list.set(index, dataPoint); + } + else + { + // determine how much light should be absorbed by this column + int absorption = dataSource.mapping.getBlockStateWrapper(FullDataPointUtil.getId(above)).getOpacity() * FullDataPointUtil.getHeight(above); + if (absorption < aboveLight) + { + // the above data point is partially translucent, + // and absorbs some light. however, it did not absorb + // enough light to bring the light level down to 0. + // so, the remaining light can still be copied. + dataPoint = FullDataPointUtil.setSkyLight(dataPoint, aboveLight - absorption); + list.set(index, dataPoint); + } + } + } + } + } + } + } + } + + /** @author BuilderB0y */ + public void recursivelyLightAdjacentDataPoints( + FullDataSourceV2 chunk, + BitSet airIDs, + int relativeX, + int relativeZ, + long dataPoint + ) + { + int lightLevel = FullDataPointUtil.getSkyLight(dataPoint); + // early exit condition: + // in this case, propagating light is guaranteed to be 0 at adjacent positions, + // and therefore we do not need to waste time propagating it. + if (lightLevel <= 1) + { + return; + } + + + + int minY = FullDataPointUtil.getBottomY(dataPoint); + int maxY = FullDataPointUtil.getHeight(dataPoint) + minY; + // try to propagate in all 4 directions. + for (int offsetIndex = 0; offsetIndex < ADJACENT_DIRECTION_OFFSETS.length; ) + { + int adjacentX = relativeX + ADJACENT_DIRECTION_OFFSETS[offsetIndex++]; + int adjacentZ = relativeZ + ADJACENT_DIRECTION_OFFSETS[offsetIndex++]; + + // check if the adjacent position is within the bounds of this data source... + if (adjacentX >= 0 && adjacentX < FullDataSourceV2.WIDTH && adjacentZ >= 0 && adjacentZ < FullDataSourceV2.WIDTH) + { + LongArrayList adjacentDataPoints = chunk.get(adjacentX, adjacentZ); + // ...and also check to make sure we have some data points + // (potentially transparent ones) to propagate through in the adjacent column. + if (adjacentDataPoints != null) + { + // try to find adjacent data points we can propagate into. + // we go top-down for this, which will be important for some + // later conditions. + int size = adjacentDataPoints.size(); + for (int adjacentIndex = 0; adjacentIndex < size; adjacentIndex++) + { + long adjacentDataPoint = adjacentDataPoints.getLong(adjacentIndex); + int adjacentMinY = FullDataPointUtil.getBottomY(adjacentDataPoint); + int adjacentMaxY = FullDataPointUtil.getHeight(adjacentDataPoint) + adjacentMinY; + if (adjacentMinY >= maxY) + { + // if the adjacent data point is completely above this one, + // then there is no overlap between this one and the adjacent one, + // and therefore light cannot propagate here. + // try to propagate to the next data point down from the adjacent one. + continue; + } + else if (adjacentMaxY <= minY) + { + // if the adjacent data point is completely below this one, + // then it also has no overlap and can't propagate, + // but since we're going top-down, neither can any subsequent adjacent data points. + break; + } + else if (!airIDs.get(FullDataPointUtil.getId(adjacentDataPoint))) + { + // assume for now that we cannot propagate into non-transparent data points. + continue; // TODO how does this work with water? Do we care? + } + else + { + // now we can try to propagate. + int adjacentLightLevel = FullDataPointUtil.getSkyLight(adjacentDataPoint); + // if the resulting light level after propagation would INCREASE + // the light level of the adjacent data point, then propagate to it. + // otherwise, don't do that. + if (lightLevel - 1 > adjacentLightLevel) + { + adjacentDataPoint = FullDataPointUtil.setSkyLight(adjacentDataPoint, lightLevel - 1); + adjacentDataPoints.set(adjacentIndex, adjacentDataPoint); + // if propagation succeeded, recursively propagate again starting at the adjacent data point. + recursivelyLightAdjacentDataPoints(chunk, airIDs, adjacentX, adjacentZ, adjacentDataPoint); + } + } + } + } + } + } + } - // - // test - // //===========// diff --git a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java index 426d3fc0d..d4814d412 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/generation/WorldGenerationQueue.java @@ -467,7 +467,7 @@ public class WorldGenerationQueue implements IFullDataSourceRetrievalQueue, IDeb try { IChunkWrapper chunk = WRAPPER_FACTORY.createChunkWrapper(generatedObjectArray); - FullDataSourceV2 dataSource = LodDataBuilder.createGeneratedDataSource(chunk); + FullDataSourceV2 dataSource = LodDataBuilder.createFromChunk(chunk); LodUtil.assertTrue(dataSource != null); chunkDataConsumer.accept(dataSource); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java index 064158e57..f4d27ab9c 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/level/AbstractDhLevel.java @@ -22,6 +22,7 @@ package com.seibel.distanthorizons.core.level; import com.seibel.distanthorizons.api.methods.events.abstractEvents.DhApiChunkModifiedEvent; import com.seibel.distanthorizons.core.dataObjects.fullData.sources.FullDataSourceV2; import com.seibel.distanthorizons.core.file.fullDatafile.DelayedFullDataSourceSaveCache; +import com.seibel.distanthorizons.core.generation.DhLightingEngine; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pos.DhChunkPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; @@ -32,6 +33,7 @@ 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; @@ -168,6 +170,12 @@ public abstract class AbstractDhLevel implements IDhLevel private void onDataSourceSave(FullDataSourceV2 fullDataSource) { + // block lights should have been populated at the chunkWrapper stage + // waiting to populate the data source's skylight at this stage prevents re-lighting and + // allows us to reduce cross-chunk lighting issues by lighting the whole 4x4 LOD at once + DhLightingEngine.INSTANCE.bakeDataSourceSkyLight(fullDataSource, this.hasSkyLight() ? LodUtil.MAX_MC_LIGHT : LodUtil.MIN_MC_LIGHT); + + this.updateDataSourcesAsync(fullDataSource).thenRun(() -> { HashSet updatedChunkPosSet = this.updatedChunkPosSetBySectionPos.remove(fullDataSource.getPos()); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java index 2b30f66c2..1b8fa5b5b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/FullDataPointUtil.java @@ -157,6 +157,7 @@ public class FullDataPointUtil public static int getSkyLight(long data) { return (int) ((data >> SKY_LIGHT_OFFSET) & SKY_LIGHT_MASK); } public static long setBlockLight(long data, byte blockLight) { return (data & ~((long) BLOCK_LIGHT_MASK << BLOCK_LIGHT_OFFSET) | (long) blockLight << BLOCK_LIGHT_OFFSET); } + public static long setSkyLight(long data, int skyLight) { return (data & ~((long) SKY_LIGHT_MASK << SKY_LIGHT_OFFSET) | (long) skyLight << SKY_LIGHT_OFFSET); } public static String toString(long data) { return "[ID:" + getId(data) + ",Y:" + getBottomY(data) + ",Height:" + getHeight(data) + ",BlockLight:" + getBlockLight(data) + ",SkyLight:" + getSkyLight(data) + "]"; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java index 440e6b47d..c615d4e96 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -75,9 +75,11 @@ public interface IChunkWrapper extends IBindable int getMinBlockX(); int getMinBlockZ(); - void setIsDhLightCorrect(boolean isDhLightCorrect); - void setUseDhLighting(boolean useDhLighting); - boolean isLightCorrect(); + void setIsDhBlockLightCorrect(boolean isDhLightCorrect); + void setIsDhSkyLightCorrect(boolean isDhLightCorrect); + + boolean isDhBlockLightingCorrect(); + boolean isDhSkyLightCorrect(); int getDhSkyLight(int relX, int relY, int relZ); @@ -88,9 +90,6 @@ public interface IChunkWrapper extends IBindable void setDhBlockLight(int relX, int relY, int relZ, int lightValue); void clearDhBlockLighting(); - int getBlockLight(int relX, int relY, int relZ); - int getSkyLight(int relX, int relY, int relZ); - /** Note: don't modify this array, it will only be generated once and then shared between uses */ ArrayList getWorldBlockLightPosList(); @@ -160,85 +159,6 @@ public interface IChunkWrapper extends IBindable } - /** - * Populates DH's saved lighting using MC's lighting engine. - * This is generally done in cases where MC's lighting is correct now, but may not be later (like when a chunk is unloading). - * - * @throws IllegalStateException if the chunk's lighting isn't valid. This is done to prevent accidentally baking broken lighting. - * @return true if the chunk's lighting was successfully populated, false otherwise - */ - @Deprecated - default boolean bakeDhLightingUsingMcLightingEngine(ILevelWrapper levelWrapper) throws IllegalStateException - { - if (!this.isLightCorrect()) - { - return false; - } - - //=======================// - // get lighting for each // - // relative block pos // - //=======================// - - boolean lightingFound = false; - // if the level doesn't have sky lights, then this check can be ignored - // since all sky light values will be 0 anyway - boolean skyLightingFound = !levelWrapper.hasSkyLight(); - - for (int relX = 0; relX < LodUtil.CHUNK_WIDTH; relX++) - { - for (int relZ = 0; relZ < LodUtil.CHUNK_WIDTH; relZ++) - { - for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) - { - int skyLight = this.getSkyLight(relX, y, relZ); - this.setDhSkyLight(relX, y, relZ, skyLight); - int blockLight = this.getBlockLight(relX, y, relZ); - this.setDhBlockLight(relX, y, relZ, blockLight); - - // MC defaults to max sky light and no block light, including underground blocks. - // If any position has something different then those default values, it's likely that the - // lighting was properly populated for at least part of the chunk - if (!lightingFound && - (skyLight != LodUtil.MAX_MC_LIGHT || blockLight != LodUtil.MIN_MC_LIGHT)) - { - lightingFound = true; - } - - if (!skyLightingFound - && skyLight != LodUtil.MIN_MC_LIGHT) - { - skyLightingFound = true; - } - } - } - } - - - - //=================// - // validate result // - //=================// - - // if no lighting was found or the sky is always black, the lighting is likely broken - if (!lightingFound || !skyLightingFound - // if lighting is no longer correct or doesn't match the saved values - // its very likely it broke halfway through and will need regenerating - || !this.isLightCorrect() - || this.getSkyLight(0, 0, 0) != this.getDhSkyLight(0,0,0) - || this.getBlockLight(0, 0, 0) != this.getDhBlockLight(0,0,0)) - { - return false; - } - - - // lighting is valid - this.setIsDhLightCorrect(true); - this.setUseDhLighting(true); - return true; - } - - /** * Converts a 3D position into a 1D array index.

* diff --git a/core/src/test/java/testItems/lightingEngine/LightingTestBlockStateWrapper.java b/core/src/test/java/testItems/lightingEngine/LightingTestBlockStateWrapper.java deleted file mode 100644 index cb5a719a3..000000000 --- a/core/src/test/java/testItems/lightingEngine/LightingTestBlockStateWrapper.java +++ /dev/null @@ -1,121 +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 . - */ - -package testItems.lightingEngine; - -import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; -import tests.LightingEngineTest; - -import java.awt.*; - -/** - * @see LightingEngineTest - * @see LightingTestChunkWrapper - */ -public class LightingTestBlockStateWrapper implements IBlockStateWrapper -{ - private int opacity = -1; - private int lightEmission = -1; - - - - //=============// - // constructor // - //=============// - - public LightingTestBlockStateWrapper(int opacity, int lightEmission) - { - this.opacity = opacity; - this.lightEmission = lightEmission; - } - - - - //=================// - // wrapper methods // - //=================// - - @Override - public int getOpacity() { return this.opacity; } - - @Override - public int getLightEmission() { return this.lightEmission; } - - - - - //===============// - // unimplemented // - //===============// - - //@Override - //public boolean equals(Object obj) - //{ - // if (this == obj) - // { - // return true; - // } - // - // if (obj == null || this.getClass() != obj.getClass()) - // { - // return false; - // } - // - // BlockStateTestWrapper that = (BlockStateTestWrapper) obj; - // // the serialized value is used so we can test the contents instead of the references - // return Objects.equals(this.getSerialString(), that.getSerialString()); - //} - - //@Override - //public int hashCode() { return this.hashCode; } - //@Override - //public String toString() { return this.getSerialString(); } - - - @Override - public String getSerialString() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public Object getWrappedMcObject() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public boolean isAir() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public boolean isSolid() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public boolean isLiquid() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public byte getMaterialId() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public boolean isBeaconBlock() { throw new UnsupportedOperationException("Not Implemented"); } - @Override - public boolean isBeaconBaseBlock() { throw new UnsupportedOperationException("Not Implemented"); } - - @Override - public Color getMapColor() { throw new UnsupportedOperationException("Not Implemented"); } - @Override - public boolean isBeaconTintBlock() { throw new UnsupportedOperationException("Not Implemented"); } - @Override - public Color getBeaconTintColor() { throw new UnsupportedOperationException("Not Implemented"); } - -} diff --git a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java b/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java deleted file mode 100644 index ad58e69fd..000000000 --- a/core/src/test/java/testItems/lightingEngine/LightingTestChunkWrapper.java +++ /dev/null @@ -1,435 +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 . - */ - -package testItems.lightingEngine; - -import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; -import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; -import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.ChunkLightStorage; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.misc.IMutableBlockPosWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.world.IBiomeWrapper; -import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; -import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; -import org.apache.logging.log4j.Logger; -import tests.LightingEngineTest; - -import java.io.*; -import java.util.ArrayList; - -/** - * @see LightingEngineTest - * @see LightingTestBlockStateWrapper - */ -public class LightingTestChunkWrapper implements IChunkWrapper -{ - private static final Logger LOGGER = DhLoggerBuilder.getLogger(); - - - // chunk values // - - private final DhChunkPos chunkPos; - private ChunkLightStorage blockLightStorage; - private ChunkLightStorage skyLightStorage; - - private ArrayList blockLightPosList = null; - - private boolean useDhLighting; - - private int minNonEmptyHeight = Integer.MIN_VALUE; - private int maxNonEmptyHeight = Integer.MAX_VALUE; - - - // test values // - - private final Int2IntOpenHashMap blockOpacityStorage; - private final Int2IntOpenHashMap blockEmissionStorage; - private final int[][] solidHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; - private final int[][] lightBlockingHeightMap = new int[LodUtil.CHUNK_WIDTH][LodUtil.CHUNK_WIDTH]; - - - - - //=============// - // constructor // - //=============// - - public LightingTestChunkWrapper(IChunkWrapper chunkWrapper) - { - this.chunkPos = chunkWrapper.getChunkPos(); - - this.blockOpacityStorage = new Int2IntOpenHashMap(); - this.blockEmissionStorage = new Int2IntOpenHashMap(); - this.blockLightPosList = new ArrayList<>(); - - for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) - { - for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) - { - for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) - { - IBlockStateWrapper block = chunkWrapper.getBlockState(x,y,z); - - int opacity = block.getOpacity(); - if (opacity >= LodUtil.BLOCK_FULLY_OPAQUE) - { - opacity = 3; - } - - this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), opacity); - this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), block.getLightEmission()); - - if (block.getLightEmission() != 0) - { - this.blockLightPosList.add(new DhBlockPos(x,y,z)); - } - } - - this.lightBlockingHeightMap[x][z] = chunkWrapper.getLightBlockingHeightMapValue(x, z); - this.solidHeightMap[x][z] = chunkWrapper.getSolidHeightMapValue(x, z); - - } - } - } - - - - //===============// - // file handling // - //===============// - - public LightingTestChunkWrapper(DhChunkPos pos, File saveFile) throws DataCorruptedException - { - this.chunkPos = pos; - - this.blockOpacityStorage = new Int2IntOpenHashMap(); - this.blockEmissionStorage = new Int2IntOpenHashMap(); - this.blockLightPosList = new ArrayList<>(); - - try(FileInputStream inputStream = new FileInputStream(saveFile); - BufferedInputStream bufferedStream = new BufferedInputStream(inputStream); - DataInputStream stream = new DataInputStream(bufferedStream)) - { - for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) - { - for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) - { - for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) - { - this.blockOpacityStorage.put(new DhBlockPos(x, y, z).hashCode(), stream.readInt()); - - int blockEmission = stream.readInt(); - this.blockEmissionStorage.put(new DhBlockPos(x, y, z).hashCode(), blockEmission); - if (blockEmission != 0) - { - this.blockLightPosList.add(new DhBlockPos(x,y,z)); - } - } - - if (stream.readChar() != ';') - { - throw new DataCorruptedException("bad height map"); - } - - this.lightBlockingHeightMap[x][z] = stream.readInt(); - this.solidHeightMap[x][z] = stream.readInt(); - - if (stream.readChar() != '\n') - { - throw new DataCorruptedException(" bad col ending"); - } - } - } - } - catch (IOException e) - { - LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e); - } - } - public void writeToFile(File file) - { - try(FileOutputStream fileStream = new FileOutputStream(file); - BufferedOutputStream bufferedStream = new BufferedOutputStream(fileStream); - DataOutputStream stream = new DataOutputStream(bufferedStream)) - { - for (int x = 0; x < LodUtil.CHUNK_WIDTH; x++) - { - for (int z = 0; z < LodUtil.CHUNK_WIDTH; z++) - { - for (int y = this.getMinBuildHeight(); y < this.getMaxBuildHeight(); y++) - { - stream.writeInt(this.blockOpacityStorage.get(new DhBlockPos(x, y, z).hashCode())); - stream.writeInt(this.blockEmissionStorage.get(new DhBlockPos(x, y, z).hashCode())); - } - - stream.writeChar(';'); - - stream.writeInt(this.lightBlockingHeightMap[x][z]); - stream.writeInt(this.solidHeightMap[x][z]); - - stream.writeChar('\n'); - } - } - - stream.flush(); - } - catch (IOException e) - { - LOGGER.error("Unable to write to file: ["+e.getMessage()+"].", e); - } - } - - /** - * Can be added into {@link com.seibel.distanthorizons.core.api.internal.SharedApi#applyChunkUpdate(IChunkWrapper, ILevelWrapper, boolean)} - * to save chunks to file for future testing. - */ - public void tryConvertingAndSavingChunkWrapper(IChunkWrapper chunkWrapper) - { - try - { - File chunkFile = new File(LightingEngineTest.TEST_DATA_PATH + "/" + chunkWrapper.getChunkPos().toString()); - if (!chunkFile.exists()) - { - LightingTestChunkWrapper testWrapper = new LightingTestChunkWrapper(chunkWrapper); - testWrapper.writeToFile(chunkFile); - } - } - catch (Exception e) - { - LOGGER.error(e.getMessage(), e); - } - } - - - - //===============// - // chunk methods // - //===============// - - @Override - public int getHeight() { return 255; } - - @Override - public int getMinBuildHeight() { return -64; } - @Override - public int getMaxBuildHeight() { return 255; } - - @Override - public int getMinNonEmptyHeight() - { - if (this.minNonEmptyHeight != Integer.MIN_VALUE) - { - return this.minNonEmptyHeight; - } - - - // default if every section is empty or missing - this.minNonEmptyHeight = this.getMinBuildHeight(); - - // determine the lowest empty section (bottom up) - int maxYHeight = this.getMaxBuildHeight(); - for (int y = this.getMinBuildHeight(); y < maxYHeight; y++) - { - if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0) - { - // -16 to simulate having to populate the full chunk section - this.minNonEmptyHeight = Math.min(y - 16, maxYHeight); - break; - } - } - - return this.minNonEmptyHeight; - } - - - @Override - public int getMaxNonEmptyHeight() - { - if (this.maxNonEmptyHeight != Integer.MAX_VALUE) - { - return this.maxNonEmptyHeight; - } - - - // default if every section is empty or missing - this.maxNonEmptyHeight = this.getMaxBuildHeight(); - - // determine the highest empty section (top down) - int minYHeight = this.getMinBuildHeight(); - for (int y = this.getMaxBuildHeight(); y >= minYHeight; y--) - { - if (this.blockOpacityStorage.get(new DhBlockPos(0, y, 0).hashCode()) != 0) - { - // -16 to simulate having to populate the full chunk section - this.maxNonEmptyHeight = Math.max(y - 16, minYHeight); - break; - } - } - - return this.maxNonEmptyHeight; - } - - - @Override - public int getSolidHeightMapValue(int xRel, int zRel) { return this.solidHeightMap[xRel][zRel]; } - - @Override - public int getLightBlockingHeightMapValue(int xRel, int zRel) { return this.lightBlockingHeightMap[xRel][zRel]; } - - - - @Override - public IBiomeWrapper getBiome(int relX, int relY, int relZ) { throw new UnsupportedOperationException("Not implemented"); } - - @Override - public DhChunkPos getChunkPos() { return this.chunkPos; } - - @Override - public int getMaxBlockX() { return 0; } - @Override - public int getMaxBlockZ() { return 0; } - @Override - public int getMinBlockX() { return LodUtil.CHUNK_WIDTH; } - @Override - public int getMinBlockZ() { return LodUtil.CHUNK_WIDTH; } - - @Override - public void setIsDhLightCorrect(boolean isDhLightCorrect) { } - - @Override - public void setUseDhLighting(boolean useDhLighting) { this.useDhLighting = useDhLighting; } - - - - @Override - public boolean isLightCorrect() { return false; } - - - @Override - public int getDhBlockLight(int relX, int y, int relZ) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - return this.getBlockLightStorage().get(relX, y, relZ); - } - @Override - public void setDhBlockLight(int relX, int y, int relZ, int lightValue) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - this.getBlockLightStorage().set(relX, y, relZ, lightValue); - } - - private ChunkLightStorage getBlockLightStorage() - { - if (this.blockLightStorage == null) - { - this.blockLightStorage = new ChunkLightStorage( - // +/- 16 is to fix an issue with the test chunk where the storage isn't big enough, - // James probably just screwed up the min/max height slightly - this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16, - // positions above and below the handled area should be unlit - LodUtil.MIN_MC_LIGHT, LodUtil.MIN_MC_LIGHT); - } - return this.blockLightStorage; - } - @Override - public void clearDhBlockLighting() { throw new UnsupportedOperationException("Not implemented"); } - - - @Override - public int getDhSkyLight(int relX, int y, int relZ) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - return this.getSkyLightStorage().get(relX, y, relZ); - } - @Override - public void setDhSkyLight(int relX, int y, int relZ, int lightValue) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - this.getSkyLightStorage().set(relX, y, relZ, lightValue); - } - @Override - public void clearDhSkyLighting() { throw new UnsupportedOperationException("Not implemented"); } - - private ChunkLightStorage getSkyLightStorage() - { - if (this.skyLightStorage == null) - { - this.skyLightStorage = new ChunkLightStorage( - // +/- 16 is to fix an issue with the test chunk where the storage isn't big enough, - // James probably just screwed up the min/max height slightly - this.getMinBuildHeight() - 16, this.getMaxBuildHeight() + 16, - // positions above should be lit but positions below should be unlit - LodUtil.MAX_MC_LIGHT, LodUtil.MIN_MC_LIGHT); - } - return this.skyLightStorage; - } - - - @Override - public int getBlockLight(int relX, int y, int relZ) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - return this.getBlockLightStorage().get(relX, y, relZ); - } - - @Override - public int getSkyLight(int relX, int y, int relZ) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, y, relZ); - return this.getSkyLightStorage().get(relX, y, relZ); - } - - @Override - public ArrayList getWorldBlockLightPosList() { return this.blockLightPosList; } - - @Override - public boolean doNearbyChunksExist() { return false; } - - @Override - public String toString() { return this.chunkPos.toString(); } - - @Override - public IBlockStateWrapper getBlockState(int relX, int relY, int relZ) - { - this.throwIndexOutOfBoundsIfRelativePosOutsideChunkBounds(relX, relY, relZ); - - int opacity = this.blockOpacityStorage.get(new DhBlockPos(relX, relY, relZ).hashCode()); - int lightEmission = this.blockEmissionStorage.get(new DhBlockPos(relX, relY, relZ).hashCode()); - return new LightingTestBlockStateWrapper(opacity, lightEmission); - } - @Override - public IBlockStateWrapper getBlockState(int relX, int relY, int relZ, IMutableBlockPosWrapper mcBlockPos, IBlockStateWrapper guess) - { return this.getBlockState(relX, relY, relZ); } - - @Override - public IMutableBlockPosWrapper getMutableBlockPosWrapper() - { - return null; - } - - @Override - public boolean isStillValid() { return true; } - - - -} diff --git a/core/src/test/java/tests/LightingEngineTest.java b/core/src/test/java/tests/LightingEngineTest.java deleted file mode 100644 index cca1f5ff9..000000000 --- a/core/src/test/java/tests/LightingEngineTest.java +++ /dev/null @@ -1,96 +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 . - */ - -package tests; - -import com.seibel.distanthorizons.core.generation.DhLightingEngine; -import com.seibel.distanthorizons.core.pos.DhChunkPos; -import com.seibel.distanthorizons.core.util.LodUtil; -import com.seibel.distanthorizons.core.util.objects.DataCorruptedException; -import com.seibel.distanthorizons.core.wrapperInterfaces.chunk.IChunkWrapper; -import testItems.lightingEngine.LightingTestChunkWrapper; - -import java.io.File; -import java.util.ArrayList; - -/** - * Can be used to A/B Test lighting engine performance changes.

- * - * normal - chunks: [1595], total Time: [1490], avg time: [0.9341692789968652]
- * only surface light prop - chunks: [1595], total Time: [984], avg time: [0.6169278996865204]
- * - * @author James Seibel - * @version 2024-6-11 - */ -public class LightingEngineTest -{ - /** - * There should be test data in the following core repo folder:
- * Core\_Misc Files\test files\Lighting engine test chunk data.7z - */ - public static final String TEST_DATA_PATH = "C:/Users/James_Seibel/Desktop/test chunk data"; - - - //@Test - public void TestLightingEngine() throws DataCorruptedException - { - long totalNanoTime = 0; - int chunkCount = 0; - - File testFolder = new File(TEST_DATA_PATH); - File[] chunkFiles = testFolder.listFiles(); - for (int i = 0; i < chunkFiles.length; i++) - { - // chunk file parsing // - - File chunkFile = chunkFiles[i]; - - String fileName = chunkFile.getName(); // C[0,-3] - fileName = fileName.replace("C", "").replace("[", "").replace("]", ""); - int xPos = Integer.parseInt(fileName.split(",")[0]); - int zPos = Integer.parseInt(fileName.split(",")[1]); - DhChunkPos pos = new DhChunkPos(xPos, zPos); - - if (i % 100 == 0) - { - System.out.println(i + "/" + chunkFiles.length); - } - - LightingTestChunkWrapper chunk = new LightingTestChunkWrapper(pos, chunkFile); - chunkCount++; - - ArrayList nearbyChunkList = new ArrayList<>(1); - nearbyChunkList.add(chunk); - - - - // lighting // - - long startTime = System.nanoTime(); - DhLightingEngine.INSTANCE.lightChunk(chunk, nearbyChunkList, LodUtil.MAX_MC_LIGHT); - long endTime = System.nanoTime(); - totalNanoTime += endTime - startTime; - } - long timeMs = totalNanoTime / 1_000_000; - - - System.out.println("chunks: ["+chunkCount+"], total Time: ["+timeMs+"], avg time: ["+(timeMs/(double)chunkCount)+"]"); - } - -}