diff --git a/src/main/java/com/seibel/lod/core/api/ClientApi.java b/src/main/java/com/seibel/lod/core/api/ClientApi.java index c910b8be0..5ef431a07 100644 --- a/src/main/java/com/seibel/lod/core/api/ClientApi.java +++ b/src/main/java/com/seibel/lod/core/api/ClientApi.java @@ -19,7 +19,9 @@ package com.seibel.lod.core.api; +import java.time.Duration; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -60,6 +62,22 @@ public class ClientApi private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IWrapperFactory FACTORY = SingletonHandler.get(IWrapperFactory.class); private static final EventApi EVENT_API = EventApi.INSTANCE; + + public static final boolean ENABLE_LAG_SPIKE_LOGGING = true; + public static final long LAG_SPIKE_THRESOLD_NS = TimeUnit.NANOSECONDS.convert(16, TimeUnit.MILLISECONDS); + + public static class LagSpikeCatcher { + + long timer = System.nanoTime(); + public LagSpikeCatcher() {} + public void end(String source) { + if (!ENABLE_LAG_SPIKE_LOGGING) return; + timer = System.nanoTime() - timer; + if (timer > LAG_SPIKE_THRESOLD_NS) { + ClientApi.LOGGER.info("LagSpikeCatcher: "+source+" took "+Duration.ofNanos(timer)+"!"); + } + } + } /** * there is some setup that should only happen once, @@ -81,8 +99,10 @@ public class ClientApi public void clientChunkLoadEvent(IChunkWrapper chunk, IWorldWrapper world) { + LagSpikeCatcher clientChunkLoad = new LagSpikeCatcher(); //ClientApi.LOGGER.info("Lod Generating add: "+chunk.getLongChunkPos()); toBeLoaded.add(chunk.getLongChunkPos()); + clientChunkLoad.end("clientChunkLoad"); } //private HashSet lastFrame = new HashSet(); @@ -115,7 +135,8 @@ public class ClientApi ApiShared.lodBuilder.defaultDimensionWidthInRegions); ApiShared.lodWorld.addLodDimension(lodDim); } - + + LagSpikeCatcher updateToBeLoadedChunk = new LagSpikeCatcher(); for (long pos : toBeLoaded) { if (generating.size() >= 8) { //ClientApi.LOGGER.info("Lod Generating Full! Remining: "+toBeLoaded.size()); @@ -139,18 +160,24 @@ public class ClientApi toBeLoaded.add(pos); }); } + updateToBeLoadedChunk.end("updateToBeLoadedChunk"); - + LagSpikeCatcher updateSettings = new LagSpikeCatcher(); DetailDistanceUtil.updateSettings(); EVENT_API.viewDistanceChangedEvent(); + updateSettings.end("updateSettings"); + LagSpikeCatcher updatePlayerMove = new LagSpikeCatcher(); EVENT_API.playerMoveEvent(lodDim); + updatePlayerMove.end("updatePlayerMove"); - + + LagSpikeCatcher cutAndExpendAsync = new LagSpikeCatcher(); lodDim.cutRegionNodesAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); lodDim.expandOrLoadRegionsAsync(MC.getPlayerBlockPos().getX(), MC.getPlayerBlockPos().getZ()); + cutAndExpendAsync.end("cutAndExpendAsync"); diff --git a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java index f434b9f16..5ba95dbd9 100644 --- a/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java +++ b/src/main/java/com/seibel/lod/core/builders/lodBuilding/LodBuilder.java @@ -23,6 +23,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import com.seibel.lod.core.api.ClientApi; +import com.seibel.lod.core.enums.LodDirection; import com.seibel.lod.core.enums.config.DistanceGenerationMode; import com.seibel.lod.core.objects.lod.LodDimension; import com.seibel.lod.core.objects.lod.LodRegion; @@ -205,7 +206,7 @@ public class LodBuilder lodDim.regenDimensionBuffers = true; if (!region.doesDataExist((byte)0, chunk.getMinX(), chunk.getMinZ(), config.distanceGenerationMode)) - throw new RuntimeException(); + throw new RuntimeException("data at detail 0 is still null after writes to it!"); } catch (Exception e) { e.printStackTrace(); } finally { @@ -247,8 +248,8 @@ public class LodBuilder y = height - 1; // We search light on above air block int depth = determineBottomPointFrom(chunk, config, x, y, z, - count < this.config.client().graphics().quality().getVerticalQuality().maxConnectedLods - && !hasCeiling); + //count < this.config.client().graphics().quality().getVerticalQuality().maxConnectedLods && + (!hasCeiling || !topBlock)); if (hasCeiling && topBlock) y = depth; int light = getLightValue(chunk, x, y, z, hasCeiling, hasSkyLight, topBlock); @@ -266,6 +267,27 @@ public class LodBuilder if (result.length != maxVerticalData) throw new ArrayIndexOutOfBoundsException(); System.arraycopy(result, 0, data, dataOffset, maxVerticalData); } + + public static final LodDirection[] DIRECTIONS = new LodDirection[] { + LodDirection.UP, + LodDirection.DOWN, + LodDirection.WEST, + LodDirection.EAST, + LodDirection.NORTH, + LodDirection.SOUTH }; + + private boolean hasCliffFace(IChunkWrapper chunk, int x, int y, int z) { + for (LodDirection dir : DIRECTIONS) { + int cx = x+dir.getNormal().x; + int cy = y+dir.getNormal().y; + int cz = z+dir.getNormal().z; + if (!chunk.blockPosInsideChunk(cx, cy, cz)) continue; + IBlockShapeWrapper block = chunk.getBlockShapeWrapper(cx, cy, cz); + if (block == null || block.hasNoCollision() || block.isToAvoid() || block.isNonFull() || block.hasNoCollision()) + return true; + } + return false; + } /** * Find the lowest valid point from the bottom. @@ -293,9 +315,9 @@ public class LodBuilder for (int y = yAbs - 1; y >= chunk.getMinBuildHeight(); y--) { - if (!isLayerValidLodPoint(chunk, xAbs, y, zAbs) - || (strictEdge && colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())) + || (strictEdge && hasCliffFace(chunk, xAbs, y, zAbs) + && colorOfBlock != chunk.getBlockColorWrapper(xAbs, y, zAbs).getColor())) { depth = (short) (y + 1); break; diff --git a/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java index 1ab8b20a1..c1ac97148 100644 --- a/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java +++ b/src/main/java/com/seibel/lod/core/enums/config/VerticalQuality.java @@ -29,21 +29,23 @@ package com.seibel.lod.core.enums.config; public enum VerticalQuality { LOW( - new int[] { 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1 }, - 0 + new int[] { 4, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + 4 ), MEDIUM( - new int[] { 4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1 }, - 1 + new int[] { 8, 4, 2, 1, 1, 1, 1, 1, 1, 1, 1 }, + 8 ), HIGH( - new int[] { 8, 8, 4, 4, 2, 2, 2, 1, 1, 1, 1 }, - 2 + new int[] { 16, 8, 4, 2, 1, 1, 1, 1, 1, 1, 1 }, + 16 ); public final int[] maxVerticalData; + + @Deprecated // Will find other ways to optimize public final int maxConnectedLods; VerticalQuality(int[] maxVerticalData, int maxConnectedLods) diff --git a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java index 671393352..b51d777cd 100644 --- a/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/PosToGenerateContainer.java @@ -31,7 +31,6 @@ public class PosToGenerateContainer { private final int playerPosX; private final int playerPosZ; - public final byte farMinDetail; private int nearSize; private int farSize; @@ -39,14 +38,10 @@ public class PosToGenerateContainer private final int[][] nearPosToGenerate; private final int[][] farPosToGenerate; - - - - public PosToGenerateContainer(byte farMinDetail, int maxDataToGenerate, int playerPosX, int playerPosZ) + public PosToGenerateContainer(int maxDataToGenerate, int playerPosX, int playerPosZ) { this.playerPosX = playerPosX; this.playerPosZ = playerPosZ; - this.farMinDetail = farMinDetail; nearSize = 0; farSize = 0; nearPosToGenerate = new int[maxDataToGenerate][4]; @@ -56,65 +51,67 @@ public class PosToGenerateContainer // TODO what is going on in this method? - public void addPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort) + public void addNearPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort) { int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ); int index; - if (detailLevel >= farMinDetail) - { - // We are introducing a position in the far array - - index = farSize; - if (index == farPosToGenerate.length) { - if (LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) > 0) { - return; - } - index--; - } else farSize++; - - if (sort) { - while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0) - { - farPosToGenerate[index][0] = farPosToGenerate[index - 1][0]; - farPosToGenerate[index][1] = farPosToGenerate[index - 1][1]; - farPosToGenerate[index][2] = farPosToGenerate[index - 1][2]; - farPosToGenerate[index][3] = farPosToGenerate[index - 1][3]; - index--; - } - } - farPosToGenerate[index][0] = detailLevel + 1; - farPosToGenerate[index][1] = posX; - farPosToGenerate[index][2] = posZ; - farPosToGenerate[index][3] = distance; - } - else - { - //We are introducing a position in the near array + //We are introducing a position in the near array - index = nearSize; - if (index == nearPosToGenerate.length) { - if (LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) > 0) { - return; - } - index--; - } else nearSize++; - - if (sort) { - while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0) - { - nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0]; - nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1]; - nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2]; - nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3]; - index--; - } + index = nearSize; + if (index == nearPosToGenerate.length) { + if (LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) > 0) { + return; + } + index--; + } else nearSize++; + + if (sort) { + while (index > 0 && LevelPosUtil.compareDistance(distance, nearPosToGenerate[index - 1][3]) <= 0) + { + nearPosToGenerate[index][0] = nearPosToGenerate[index - 1][0]; + nearPosToGenerate[index][1] = nearPosToGenerate[index - 1][1]; + nearPosToGenerate[index][2] = nearPosToGenerate[index - 1][2]; + nearPosToGenerate[index][3] = nearPosToGenerate[index - 1][3]; + index--; } - nearPosToGenerate[index][0] = detailLevel + 1; - nearPosToGenerate[index][1] = posX; - nearPosToGenerate[index][2] = posZ; - nearPosToGenerate[index][3] = distance; } + nearPosToGenerate[index][0] = detailLevel + 1; + nearPosToGenerate[index][1] = posX; + nearPosToGenerate[index][2] = posZ; + nearPosToGenerate[index][3] = distance; + } + + // TODO what is going on in this method? + public void addFarPosToGenerate(byte detailLevel, int posX, int posZ, boolean sort) + { + int distance = LevelPosUtil.minDistance(detailLevel, posX, posZ, playerPosX, playerPosZ); + int index; + + // We are introducing a position in the far array + + index = farSize; + if (index == farPosToGenerate.length) { + if (LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) > 0) { + return; + } + index--; + } else farSize++; + + if (sort) { + while (index > 0 && LevelPosUtil.compareDistance(distance, farPosToGenerate[index - 1][3]) <= 0) + { + farPosToGenerate[index][0] = farPosToGenerate[index - 1][0]; + farPosToGenerate[index][1] = farPosToGenerate[index - 1][1]; + farPosToGenerate[index][2] = farPosToGenerate[index - 1][2]; + farPosToGenerate[index][3] = farPosToGenerate[index - 1][3]; + index--; + } + } + farPosToGenerate[index][0] = detailLevel + 1; + farPosToGenerate[index][1] = posX; + farPosToGenerate[index][2] = posZ; + farPosToGenerate[index][3] = distance; } public boolean isFull() { diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java index b773acca4..933e88e49 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodDimension.java @@ -398,7 +398,7 @@ public class LodDimension // and cut it if it is higher then that minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ); - detail = DetailDistanceUtil.getTreeCutDetailFromDistance(minDistance); + detail = DetailDistanceUtil.getDetailLevelFromDistance(minDistance); if (region.getMinDetailLevel() < detail) { if (region.needSaving) return; // FIXME: A crude attempt at lowering chance of race condition! region.cutTree(detail); @@ -452,8 +452,8 @@ public class LodDimension playerPosZ); maxDistance = LevelPosUtil.maxDistance(LodUtil.REGION_DETAIL_LEVEL, regionX, regionZ, playerPosX, playerPosZ); - minDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(minDistance); - maxDetail = DetailDistanceUtil.getTreeGenDetailFromDistance(maxDistance); + minDetail = DetailDistanceUtil.getDetailLevelFromDistance(minDistance); + maxDetail = DetailDistanceUtil.getDetailLevelFromDistance(maxDistance); boolean updated = false; if (region == null) { @@ -524,7 +524,7 @@ public class LodDimension GenerationPriority priority, DistanceGenerationMode genMode) { PosToGenerateContainer posToGenerate; - posToGenerate = new PosToGenerateContainer((byte) 8, maxDataToGenerate, playerBlockPosX, playerBlockPosZ); + posToGenerate = new PosToGenerateContainer(maxDataToGenerate, playerBlockPosX, playerBlockPosZ); // This ensures that we don't spawn way too much regions without finish flushing them first. diff --git a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java index 6af91d7f3..9a29b3e76 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/LodRegion.java @@ -19,7 +19,6 @@ package com.seibel.lod.core.objects.lod; -import com.seibel.lod.core.api.ClientApi; import com.seibel.lod.core.enums.config.DistanceGenerationMode; import com.seibel.lod.core.enums.config.DropoffQuality; import com.seibel.lod.core.enums.config.GenerationPriority; @@ -71,6 +70,15 @@ public class LodRegion { public volatile int needRegenBuffer = 2; public volatile boolean needSaving = false; public volatile int isWriting = 0; + + public static byte calculateFarModeSwitch(byte targetLevel) { + if (targetLevel==0) return 0; // Always use detail 0 if it's way too close + double part = targetLevel / (double)LodUtil.REGION_DETAIL_LEVEL; + byte farModeLevel = LodUtil.DETAIL_OPTIONS-(LodUtil.CHUNK_DETAIL_LEVEL+1); + farModeLevel *= part; + farModeLevel += (LodUtil.CHUNK_DETAIL_LEVEL+1); + return (byte)LodUtil.clamp(LodUtil.CHUNK_DETAIL_LEVEL+1, farModeLevel, LodUtil.DETAIL_OPTIONS - 1); + } public LodRegion(byte minDetailLevel, RegionPos regionPos, VerticalQuality verticalQuality) { this.minDetailLevel = minDetailLevel; @@ -145,7 +153,8 @@ public class LodRegion { // detailLevel changes. if (this.dataContainer[detailLevel] == null) return false;// this.dataContainer[detailLevel] = new VerticalLevelContainer(detailLevel); - if (this.dataContainer[detailLevel].getVerticalSize() != verticalSize) throw new RuntimeException(); + if (this.dataContainer[detailLevel].getVerticalSize() != verticalSize) + throw new RuntimeException("Provided data's verticalSize is different from current storage's verticalSize!"); boolean updated = this.dataContainer[detailLevel].addChunkOfData(data, posX, posZ, widthX, widthZ, override); //ClientApi.LOGGER.info("addChunkOfData(region:{}, level:{}, x:{}, z:{}, wx:{}, wz:{}, override:{}, updated:{})", @@ -161,7 +170,7 @@ public class LodRegion { } if (!doesDataExist(detailLevel, posX, posZ, DistanceGenerationMode.values()[DataPointUtil.getGenerationMode(data[0])])) { - throw new RuntimeException(); + throw new RuntimeException("Data still doesn't exist after addChunkOfData!"); } return updated; @@ -219,43 +228,44 @@ public class LodRegion { // calculate what LevelPos are in range to generate int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, playerPosZ); - + // determine this child's levelPos byte childDetailLevel = (byte) (detailLevel - 1); int childOffsetPosX = offsetPosX * 2; int childOffsetPosZ = offsetPosZ * 2; DistanceGenerationMode testerGenMode = detailLevel > LodUtil.CHUNK_DETAIL_LEVEL ? DistanceGenerationMode.NONE : genMode; - - - byte targetDetailLevel = DetailDistanceUtil.getGenerationDetailFromDistance(minDistance); + + byte targetDetailLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance); + byte farModeSwitchLevel = (priority == GenerationPriority.NEAR_FIRST) ? 0 : calculateFarModeSwitch(targetDetailLevel); + if (targetDetailLevel <= detailLevel) { if (targetDetailLevel == detailLevel) { - if (!doesDataExist(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, testerGenMode)) - posToGenerate.addPosToGenerate(detailLevel, offsetPosX + regionPosX * size, + if (!doesDataExist(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, testerGenMode)) { + if (detailLevel==farModeSwitchLevel && priority == GenerationPriority.FAR_FIRST) + posToGenerate.addFarPosToGenerate(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, shouldSort); - } else { - if (priority == GenerationPriority.FAR_FIRST && detailLevel >= posToGenerate.farMinDetail - && !doesDataExist(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, testerGenMode)) { - posToGenerate.addPosToGenerate(detailLevel, offsetPosX + regionPosX * size, + else + posToGenerate.addNearPosToGenerate(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, shouldSort); - } else if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) { - for (int x = 0; x <= 1; x++) - for (int z = 0; z <= 1; z++) - getPosToGenerate(posToGenerate, childDetailLevel, childOffsetPosX + x, childOffsetPosZ + z, playerPosX, - playerPosZ, priority, genMode, shouldSort); - } else { - // we want at max one request per chunk (since the world generator creates - // chunks). - // So for lod smaller than a chunk, only recurse down - // the top right child - getPosToGenerate(posToGenerate, childDetailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, - priority, genMode, shouldSort); } + } else if (detailLevel == farModeSwitchLevel + && !doesDataExist(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size, testerGenMode)) { + posToGenerate.addFarPosToGenerate(detailLevel, offsetPosX + regionPosX * size, + offsetPosZ + regionPosZ * size, shouldSort); + } else if (detailLevel > LodUtil.CHUNK_DETAIL_LEVEL) { + for (int x = 0; x <= 1; x++) + for (int z = 0; z <= 1; z++) + getPosToGenerate(posToGenerate, childDetailLevel, childOffsetPosX + x, childOffsetPosZ + z, playerPosX, + playerPosZ, priority, genMode, shouldSort); + } else { + // we want at max one request per chunk (since the world generator creates + // chunks). + // So for lod smaller than a chunk, only recurse down + // the top right child + getPosToGenerate(posToGenerate, childDetailLevel, childOffsetPosX, childOffsetPosZ, playerPosX, playerPosZ, + priority, genMode, shouldSort); } } - // we have gone beyond the target Detail level - // we can stop generating - } /** @@ -268,12 +278,15 @@ public class LodRegion { public void getPosToRender(PosToRenderContainer posToRender, int playerPosX, int playerPosZ, boolean requireCorrectDetailLevel, DropoffQuality dropoffQuality) { int minDistance = LevelPosUtil.minDistance(LodUtil.REGION_DETAIL_LEVEL, regionPosX, regionPosZ, playerPosX, playerPosZ); - byte targetLevel = DetailDistanceUtil.getDrawDetailFromDistance(minDistance); + byte targetLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance); + // FarModeSwitchLevel or above is the level where a giant block of lod is not acceptable even if not all child data exist. + byte farModeSwitchLevel = requireCorrectDetailLevel ? 0 : calculateFarModeSwitch(targetLevel); + if (requireCorrectDetailLevel) farModeSwitchLevel = 0; if (targetLevel <= dropoffQuality.fastModeSwitch) { getPosToRender(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, playerPosX, playerPosZ, requireCorrectDetailLevel); } else { - getPosToRenderFlat(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, targetLevel, requireCorrectDetailLevel); + getPosToRenderFlat(posToRender, LodUtil.REGION_DETAIL_LEVEL, 0, 0, targetLevel, farModeSwitchLevel); } } @@ -290,47 +303,40 @@ public class LodRegion { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); - byte desiredLevel; - int maxDistance; - int minDistance; - int childLevel; - // calculate the LevelPos that are in range - maxDistance = LevelPosUtil.maxDistance(detailLevel, offsetPosX + regionPosX*size, offsetPosZ + regionPosZ*size, playerPosX, playerPosZ); - desiredLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(maxDistance)); - minDistance = LevelPosUtil.minDistance(detailLevel, offsetPosX + regionPosX*size, offsetPosZ + regionPosZ*size, playerPosX, playerPosZ); - childLevel = DetailDistanceUtil.getLodDrawDetail(DetailDistanceUtil.getDrawDetailFromDistance(minDistance)); - - if (detailLevel == childLevel - 1) { - posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size); - } else - // if (desiredLevel > detailLevel) - // { - // we have gone beyond the target Detail level - // we can stop generating - // } else - if (desiredLevel == detailLevel) { + int minDistance = LevelPosUtil.minDistance(detailLevel, offsetPosX + regionPosX*size, offsetPosZ + regionPosZ*size, playerPosX, playerPosZ); + byte minLevel = DetailDistanceUtil.getDetailLevelFromDistance(minDistance); + // FarModeSwitchLevel or above is the level where a giant block of lod is not acceptable even if not all child data exist. + byte farModeSwitchLevel = requireCorrectDetailLevel ? 0 : calculateFarModeSwitch(minLevel); + + if (detailLevel == minLevel) { posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size); } else // case where (detailLevel > desiredLevel) { int childPosX = (offsetPosX + regionPosX*size) * 2; int childPosZ = (offsetPosZ + regionPosZ*size) * 2; byte childDetailLevel = (byte) (detailLevel - 1); - int childrenCount = 0; - - for (int x = 0; x <= 1; x++) { - for (int z = 0; z <= 1; z++) { - if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { - if (!requireCorrectDetailLevel) - childrenCount++; - else + + if (detailLevel > farModeSwitchLevel) { + // Giant block is not acceptable. So leave empty void if data doesn't exist. + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { getPosToRender(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, playerPosX, playerPosZ, requireCorrectDetailLevel); + } + } + } + } else { + // Giant block is acceptable. So use this level lod if not all child data exist. + int childrenCount = 0; + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { + childrenCount++; + } } } - } - - if (!requireCorrectDetailLevel) { // If all the four children exist go deeper if (childrenCount == 4) { for (int x = 0; x <= 1; x++) @@ -348,36 +354,42 @@ public class LodRegion { * This method will fill the posToRender array with all levelPos that are * render-able. But the entire region try use the same detail level. */ - private void getPosToRenderFlat(PosToRenderContainer posToRender, byte detailLevel, int offsetPosX, int offsetPosZ, byte targetLevel, boolean requireCorrectDetailLevel) { + private void getPosToRenderFlat(PosToRenderContainer posToRender, byte detailLevel, int offsetPosX, int offsetPosZ, + byte targetLevel, byte farModeSwitchLevel) { // equivalent to 2^(...) int size = 1 << (LodUtil.REGION_DETAIL_LEVEL - detailLevel); - if (detailLevel == targetLevel) { posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size); } else // case where (detailLevel > desiredLevel) { int childPosX = (offsetPosX + regionPosX*size) * 2; - int childPosZ = (offsetPosZ + regionPosX*size) * 2; + int childPosZ = (offsetPosZ + regionPosZ*size) * 2; byte childDetailLevel = (byte) (detailLevel - 1); - int childrenCount = 0; - - for (int x = 0; x <= 1; x++) { - for (int z = 0; z <= 1; z++) { - if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { - if (!requireCorrectDetailLevel) - childrenCount++; - else - getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, requireCorrectDetailLevel); + + if (detailLevel > farModeSwitchLevel) { + // Giant block is not acceptable. So leave empty void if data doesn't exist. + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { + getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, farModeSwitchLevel); + } + } + } + } else { + // Giant block is acceptable. So use this level lod if not all child data exist. + int childrenCount = 0; + for (int x = 0; x <= 1; x++) { + for (int z = 0; z <= 1; z++) { + if (doesDataExist(childDetailLevel, childPosX + x, childPosZ + z, DistanceGenerationMode.NONE)) { + childrenCount++; + } } } - } - - if (!requireCorrectDetailLevel) { // If all the four children exist go deeper if (childrenCount == 4) { for (int x = 0; x <= 1; x++) for (int z = 0; z <= 1; z++) - getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, requireCorrectDetailLevel); + getPosToRenderFlat(posToRender, childDetailLevel, offsetPosX*2 + x, offsetPosZ*2 + z, targetLevel, farModeSwitchLevel); } else { posToRender.addPosToRender(detailLevel, offsetPosX + regionPosX * size, offsetPosZ + regionPosZ * size); } diff --git a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java index 4a6d29ac3..b53a368f7 100644 --- a/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java +++ b/src/main/java/com/seibel/lod/core/objects/lod/VerticalLevelContainer.java @@ -1177,13 +1177,13 @@ public class VerticalLevelContainer implements LevelContainer } data = DataPointUtil.mergeMultiData(dataToMerge, lowerMaxVertical, getVerticalSize()); if (!anyDataExist) - throw new RuntimeException(); + throw new RuntimeException("Update data called but no child datapoint exist!"); if ((!DataPointUtil.doesItExist(data[0])) && anyDataExist) - throw new RuntimeException(); + throw new RuntimeException("Update data called but higher level datapoint doesn't exist even though child data does exist!"); if (DataPointUtil.getGenerationMode(data[0]) != DataPointUtil.getGenerationMode(lowerLevelContainer.getSingleData(posX*2, posZ*2))) - throw new RuntimeException(); + throw new RuntimeException("Update data called but higher level datapoint does not have the same GenerationMode as the top left corner child datapoint!"); forceWriteVerticalData(data, posX, posZ); diff --git a/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java index 61123a93c..76aee1808 100644 --- a/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java +++ b/src/main/java/com/seibel/lod/core/util/DetailDistanceUtil.java @@ -34,8 +34,11 @@ public class DetailDistanceUtil private static final ILodConfigWrapperSingleton CONFIG = SingletonHandler.get(ILodConfigWrapperSingleton.class); private static final IMinecraftRenderWrapper MC_RENDER = SingletonHandler.get(IMinecraftRenderWrapper.class); + @Deprecated private static final double genMultiplier = 1.0; + @Deprecated private static final double treeGenMultiplier = 1.0; + @Deprecated private static final double treeCutMultiplier = 1.0; private static byte minGenDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel; private static byte minDrawDetail = CONFIG.client().graphics().quality().getDrawResolution().detailLevel; @@ -44,21 +47,6 @@ public class DetailDistanceUtil private static int minDetailDistance = (int) (MC_RENDER.getRenderDistance()*16 * 1.42f); private static int maxDistance = CONFIG.client().graphics().quality().getLodChunkRenderDistance() * 16 * 2; - @Deprecated - private static final HorizontalResolution[] lodGenDetails = { - HorizontalResolution.BLOCK, - HorizontalResolution.TWO_BLOCKS, - HorizontalResolution.FOUR_BLOCKS, - HorizontalResolution.HALF_CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK, - HorizontalResolution.CHUNK }; - - public static void updateSettings() { @@ -93,44 +81,54 @@ public class DetailDistanceUtil return baseDistanceFunction(detail); } - public static byte baseInverseFunction(int distance, byte minDetail, boolean useRenderMinDistance) + public static byte baseInverseFunction(int distance, byte minDetail) { byte detail; - if (distance == 0 - || (distance < minDetailDistance && useRenderMinDistance) - || CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality()) - return minDetail; + distance -= minDetailDistance; + + if (distance < 0 || CONFIG.client().graphics().advancedGraphics().getAlwaysDrawAtMaxQuality()) + distance = 0; int distanceUnit = CONFIG.client().graphics().quality().getHorizontalScale() * 16; + double scaledDistance = distance; + scaledDistance /= distanceUnit; if (CONFIG.client().graphics().quality().getHorizontalQuality() == HorizontalQuality.LOWEST) - detail = (byte) (distance / distanceUnit); + detail = (byte) (scaledDistance); else { double base = CONFIG.client().graphics().quality().getHorizontalQuality().quadraticBase; double logBase = Math.log(base); - //noinspection IntegerDivisionInFloatingPointContext - detail = (byte) (Math.log(distance / distanceUnit) / logBase); + detail = (byte) (Math.log(scaledDistance) / logBase); } - return (byte) LodUtil.clamp(minDetail, detail, maxDetail - 1); + return (byte) LodUtil.clamp(minDetail, detail+minDetail, maxDetail - 1); } - + + public static byte getDetailLevelFromDistance(int distance) + { + return baseInverseFunction(distance, minDrawDetail); + } + + @Deprecated //Reason: All merged into `getDetailLevelFromDistance` public static byte getDrawDetailFromDistance(int distance) { - return baseInverseFunction(distance, minDrawDetail, false); + return baseInverseFunction(distance, minDrawDetail); } - + + @Deprecated //Reason: Same as 'getDrawDetailFromDistance' public static byte getGenerationDetailFromDistance(int distance) { - return baseInverseFunction((int) (distance * genMultiplier), minGenDetail, true); + return baseInverseFunction((int) (distance * genMultiplier), minGenDetail); } - + + @Deprecated //Reason: Same as 'getDrawDetailFromDistance' public static byte getTreeCutDetailFromDistance(int distance) { - return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail, true); + return baseInverseFunction((int) (distance * treeCutMultiplier), minGenDetail); } - + + @Deprecated //Reason: Same as 'getDrawDetailFromDistance' public static byte getTreeGenDetailFromDistance(int distance) { - return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail, true); + return baseInverseFunction((int) (distance * treeGenMultiplier), minGenDetail); } @@ -142,6 +140,7 @@ public class DetailDistanceUtil return CONFIG.client().worldGenerator().getDistanceGenerationMode(); }*/ + @Deprecated public static byte getLodDrawDetail(byte detail) { detail += minDrawDetail; @@ -150,27 +149,6 @@ public class DetailDistanceUtil return detail; } - @Deprecated - public static HorizontalResolution getLodGenDetail(int detail) - { - if (detail < minGenDetail) - return lodGenDetails[minGenDetail]; - else - return lodGenDetails[detail]; - } - - - @Deprecated - public static byte getCutLodDetail(int detail) - { - if (detail < minGenDetail) - return lodGenDetails[minGenDetail].detailLevel; - else if (detail == maxDetail) - return LodUtil.REGION_DETAIL_LEVEL; - else - return lodGenDetails[detail].detailLevel; - } - public static int getMaxVerticalData(int detail) { return CONFIG.client().graphics().quality().getVerticalQuality().maxVerticalData[LodUtil.clamp(minGenDetail, detail, LodUtil.REGION_DETAIL_LEVEL)]; diff --git a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java index a412b4933..66478e61b 100644 --- a/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java +++ b/src/main/java/com/seibel/lod/core/wrapperInterfaces/chunk/IChunkWrapper.java @@ -62,4 +62,10 @@ public interface IChunkWrapper default int getBlockLight(int x, int y, int z) {return -1;} default int getSkyLight(int x, int y, int z) {return -1;} + + default boolean blockPosInsideChunk(int x, int y, int z) { + return (x>=getMinX() && x<=getMaxX() + && y>=getMinBuildHeight() && y=getMinZ() && z<=getMaxZ()); + } }