From c374bf7ca8fe9e7ae5cea74405c58d63f1f1ca73 Mon Sep 17 00:00:00 2001 From: James Seibel Date: Sat, 8 Nov 2025 08:14:03 -0600 Subject: [PATCH] test --- .../render/ColumnRenderSource.java | 41 +- .../render/bufferBuilding/ColumnBox.java | 407 ++++++++++-------- .../ColumnRenderBufferBuilder.java | 74 ++-- .../render/bufferBuilding/LodQuadBuilder.java | 17 +- .../FullDataToRenderDataTransformer.java | 119 ++--- .../core/render/LodQuadTree.java | 16 +- .../core/util/RenderDataPointUtil.java | 9 +- 7 files changed, 360 insertions(+), 323 deletions(-) diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java index af9647c48..ff7badd15 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/ColumnRenderSource.java @@ -23,17 +23,13 @@ import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; import com.seibel.distanthorizons.core.pooling.AbstractPhantomArrayList; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pos.DhSectionPos; -import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnQuadView; -import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import it.unimi.dsi.fastutil.longs.LongArrayList; import com.seibel.distanthorizons.core.logging.DhLogger; -import java.util.concurrent.atomic.AtomicLong; - /** * Stores the render data used to generate OpenGL buffers. * @@ -43,10 +39,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList { private static final DhLogger LOGGER = new DhLoggerBuilder().build(); - public static final boolean DO_SAFETY_CHECKS = ModInfo.IS_DEV_BUILD; - public static final byte SECTION_SIZE_OFFSET = DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL; - /** width of this data in columns */ - public static final int SECTION_SIZE = BitShiftUtil.powerOfTwo(SECTION_SIZE_OFFSET); // 64 + /** measured in data columns */ + public static final int WIDTH = 64; public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Render Source"); @@ -63,8 +57,6 @@ public class ColumnRenderSource extends AbstractPhantomArrayList private boolean isEmpty = true; - public AtomicLong localVersion = new AtomicLong(0); // used to track changes to the data source, so that buffers can be updated when necessary - //==============// @@ -88,9 +80,9 @@ public class ColumnRenderSource extends AbstractPhantomArrayList this.verticalDataCount = maxVerticalSize; - this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, SECTION_SIZE * SECTION_SIZE * this.verticalDataCount); + this.renderDataContainer = this.pooledArraysCheckout.getLongArray(0, WIDTH * WIDTH * this.verticalDataCount); - this.debugSourceFlags = new DebugSourceFlag[SECTION_SIZE * SECTION_SIZE]; + this.debugSourceFlags = new DebugSourceFlag[WIDTH * WIDTH]; } @@ -99,19 +91,19 @@ public class ColumnRenderSource extends AbstractPhantomArrayList // datapoint manipulation // //========================// - public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); } + public long getDataPoint(int posX, int posZ, int verticalIndex) { return this.renderDataContainer.getLong(posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount + verticalIndex); } public ColumnArrayView getVerticalDataPointView(int posX, int posZ) { - int offset = posX * SECTION_SIZE * this.verticalDataCount + posZ * this.verticalDataCount; + int offset = posX * WIDTH * this.verticalDataCount + posZ * this.verticalDataCount; // don't allow returning views that are outside this render source's bounds if (offset >= this.renderDataContainer.size()) { return null; } - else if (posX < 0 || posX >= SECTION_SIZE - || posZ < 0 || posZ >= SECTION_SIZE) + else if (posX < 0 || posX >= WIDTH + || posZ < 0 || posZ >= WIDTH) { return null; } @@ -120,8 +112,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList offset, this.verticalDataCount); } - public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, SECTION_SIZE, SECTION_SIZE); } - public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, SECTION_SIZE, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); } + public ColumnQuadView getFullQuadView() { return this.getQuadViewOverRange(0, 0, WIDTH, WIDTH); } + public ColumnQuadView getQuadViewOverRange(int quadX, int quadZ, int quadXSize, int quadZSize) { return new ColumnQuadView(this.renderDataContainer, WIDTH, this.verticalDataCount, quadX, quadZ, quadXSize, quadZSize); } @@ -131,9 +123,8 @@ public class ColumnRenderSource extends AbstractPhantomArrayList public Long getPos() { return this.pos; } public Long getKey() { return this.pos; } - public String getKeyDisplayString() { return DhSectionPos.toString(this.pos); } - public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - SECTION_SIZE_OFFSET); } + public byte getDataDetailLevel() { return (byte) (DhSectionPos.getDetailLevel(this.pos) - DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); } public boolean isEmpty() { return this.isEmpty; } public void markNotEmpty() { this.isEmpty = false; } @@ -147,15 +138,15 @@ public class ColumnRenderSource extends AbstractPhantomArrayList } - for (int x = 0; x < SECTION_SIZE; x++) + for (int x = 0; x < WIDTH; x++) { - for (int z = 0; z < SECTION_SIZE; z++) + for (int z = 0; z < WIDTH; z++) { ColumnArrayView columnArrayView = this.getVerticalDataPointView(x,z); for (int i = 0; i < columnArrayView.size; i++) { long dataPoint = columnArrayView.get(i); - if (!RenderDataPointUtil.isVoid(dataPoint)) + if (!RenderDataPointUtil.hasZeroHeight(dataPoint)) { return true; } @@ -179,12 +170,12 @@ public class ColumnRenderSource extends AbstractPhantomArrayList { for (int z = zStart; z < zStart + zWidth; z++) { - this.debugSourceFlags[x * SECTION_SIZE + z] = flag; + this.debugSourceFlags[x * WIDTH + z] = flag; } } } - public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * SECTION_SIZE + oz]; } + public DebugSourceFlag debugGetFlag(int ox, int oz) { return this.debugSourceFlags[ox * WIDTH + oz]; } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java index 041c09f1c..aad55a84b 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnBox.java @@ -23,38 +23,32 @@ import com.seibel.distanthorizons.core.config.Config; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; import com.seibel.distanthorizons.core.level.IDhClientLevel; +import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; +import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.LodUtil; +import com.seibel.distanthorizons.core.util.PerfRecorder; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; import com.seibel.distanthorizons.core.wrapperInterfaces.minecraft.IMinecraftClientWrapper; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; -import com.seibel.distanthorizons.core.render.renderer.LodRenderer; import com.seibel.distanthorizons.coreapi.util.MathUtil; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Arrays; public class ColumnBox { private static final IMinecraftClientWrapper MC = SingletonInjector.INSTANCE.get(IMinecraftClientWrapper.class); - /** - * if the skylight has this value that means - * no data is expected - */ - private static final byte SKYLIGHT_EMPTY = -1; /** * if the skylight has this value that means - * that block position is covered/occuled by an adjacent block/column. + * that block position is covered/occluded by an adjacent block/column. */ - private static final byte SKYLIGHT_COVERED = -2; + private static final byte SKYLIGHT_COVERED = -1; + + public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Column Box"); - private static final ThreadLocal THREAD_LOCAL_SKY_LIGHT_ARRAY = ThreadLocal.withInitial(() -> - { - byte[] array = new byte[RenderDataPointUtil.MAX_WORLD_Y_SIZE]; - Arrays.fill(array, SKYLIGHT_EMPTY); - return array; - }); @@ -64,7 +58,7 @@ public class ColumnBox public static void addBoxQuadsToBuilder( LodQuadBuilder builder, IDhClientLevel clientLevel, - short xSize, short ySize, short zSize, + short width, short yHeight, short minX, short minY, short minZ, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, long topData, long bottomData, ColumnArrayView[] adjData, boolean[] isAdjDataSameDetailLevel) @@ -73,9 +67,9 @@ public class ColumnBox // variable setup // //================// - short maxX = (short) (minX + xSize); - short maxY = (short) (minY + ySize); - short maxZ = (short) (minZ + zSize); + short maxX = (short) (minX + width); + short maxY = (short) (minY + yHeight); + short maxZ = (short) (minZ + width); byte skyLightTop = skyLight; byte skyLightBot = RenderDataPointUtil.doesDataPointExist(bottomData) ? RenderDataPointUtil.getLightSky(bottomData) : 0; @@ -111,15 +105,15 @@ public class ColumnBox if (!isTransparent && isTopTransparent && RenderDataPointUtil.doesDataPointExist(topData)) { skyLightTop = (byte) MathUtil.clamp(0, 15 - (RenderDataPointUtil.getYMax(topData) - minY), 15); - ySize = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1); + yHeight = (short) (RenderDataPointUtil.getYMax(topData) - minY - 1); } else if (isTransparent && !isBottomTransparent && RenderDataPointUtil.doesDataPointExist(bottomData)) { - minY = (short) (minY + ySize - 1); - ySize = 1; + minY = (short) (minY + yHeight - 1); + yHeight = 1; } - maxY = (short) (minY + ySize); + maxY = (short) (minY + yHeight); } @@ -128,16 +122,20 @@ public class ColumnBox // add top and bottom faces // //==========================// - boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) && (RenderDataPointUtil.getYMin(topData) == maxY) && !isTopTransparent; + boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) + && (RenderDataPointUtil.getYMin(topData) == maxY) + && !isTopTransparent; if (!skipTop) { - builder.addQuadUp(minX, maxY, minZ, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); + builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); } - boolean skipBottom = RenderDataPointUtil.doesDataPointExist(bottomData) && (RenderDataPointUtil.getYMax(bottomData) == minY) && !isBottomTransparent; + boolean skipBottom = RenderDataPointUtil.doesDataPointExist(bottomData) + && (RenderDataPointUtil.getYMax(bottomData) == minY) + && !isBottomTransparent; if (!skipBottom) { - builder.addQuadDown(minX, minY, minZ, xSize, zSize, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); + builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); } @@ -156,12 +154,12 @@ public class ColumnBox // Add an adjacent face if this is opaque face or transparent over the void. if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.NORTH, minX, minY, minZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); + builder.addQuadAdj(EDhDirection.NORTH, minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); } } else { - makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, minX, minY, minZ, xSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } @@ -174,12 +172,12 @@ public class ColumnBox { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.SOUTH, minX, minY, maxZ, xSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); + builder.addQuadAdj(EDhDirection.SOUTH, minX, minY, maxZ, width, yHeight, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); } } else { - makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, minX, minY, maxZ, xSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, minX, minY, maxZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } @@ -192,12 +190,12 @@ public class ColumnBox { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.WEST, minX, minY, minZ, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); + builder.addQuadAdj(EDhDirection.WEST, minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); } } else { - makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, minX, minY, minZ, zSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } @@ -210,19 +208,19 @@ public class ColumnBox { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.EAST, maxX, minY, minZ, zSize, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); + builder.addQuadAdj(EDhDirection.EAST, maxX, minY, minZ, width, yHeight, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); } } else { - makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, minZ, zSize, ySize, + makeAdjVerticalQuad(builder, adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.EAST, maxX, minY, minZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } } private static void makeAdjVerticalQuad( - LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, + LodQuadBuilder builder, @NotNull ColumnArrayView adjColumnView, boolean adjacentIsSameDetailLevel, int caveCullingMaxY, EDhDirection direction, short x, short yMin, short z, short horizontalWidth, short ySize, int color, byte irisBlockMaterialId, byte blockLight) { @@ -233,11 +231,9 @@ public class ColumnBox color = ColorUtil.applyShade(color, MC.getShade(direction)); - // if there isn't any data adjacent to this LOD, - // just add the full vertical quad - if (adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0))) + if (adjColumnView.size == 0 + || RenderDataPointUtil.hasZeroHeight(adjColumnView.get(0))) { - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); return; } @@ -245,165 +241,139 @@ public class ColumnBox //===========================// - // Determine face visibility // - // based on it's neighbors // + // Build Y-range segments // + // with their sky light // //===========================// boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; + boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; + short yMax = (short) (yMin + ySize); - short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive - byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get(); + // List to store segments: [startY, endY, skyLight] + ArrayList segments = new ArrayList<>(); - try + int adjCount = adjColumnView.size(); + + // Start with the entire range at max light + segments.add(new YSegment(yMin, yMax, LodUtil.MAX_MC_LIGHT)); + + // Process each adjacent datapoint and split/update segments + for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) { - // set the initial sky-lights for this face, - // if nothing overlaps or overhangs the face should have max sky light - Arrays.fill(skyLightAtInputPos, yMin, yMax, LodUtil.MAX_MC_LIGHT); + long adjPoint = adjColumnView.get(adjIndex); + short adjMinY = RenderDataPointUtil.getYMin(adjPoint); + short adjMaxY = RenderDataPointUtil.getYMax(adjPoint); - // iterate top down - int adjCount = adjColumnView.size(); - for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) + if (!RenderDataPointUtil.doesDataPointExist(adjPoint) + || RenderDataPointUtil.hasZeroHeight(adjPoint) + || yMax <= adjMinY) { - long adjPoint = adjColumnView.get(adjIndex); - short adjMinY = RenderDataPointUtil.getYMin(adjPoint); - short adjMaxY = RenderDataPointUtil.getYMax(adjPoint); - - // skip empty adjacent datapoints - if (!RenderDataPointUtil.doesDataPointExist(adjPoint) - || RenderDataPointUtil.isVoid(adjPoint)) - { - continue; - } - - // skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point) - if (yMax <= adjMinY) - { - continue; - } - - - long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA; - long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA; - - // if the adjacent data point is over the void - // don't consider it as transparent - boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint); - boolean adjTransparent = !adjOverVoid - && RenderDataPointUtil.getAlpha(adjPoint) < 255 - && transparencyEnabled; - - - - //=================================// - // set sky light based on adjacent // - //=================================// - - // set light based on overlapping adjacent - if (!adjTransparent) - { - // adj opaque - // mark positions adjacent is covering - byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint); - for (int i = adjMinY; i < adjMaxY; i++) - { - byte skyLightAtPos = skyLightAtInputPos[i]; - - // if the adjacent is a different detail level, we want to render adjacent opaque - // faces to try and reduce the chance of holes on detail level borders - boolean adjacentCoversThis = - // if the adjacent is the same detail level, no special handling is necessary - !adjacentIsSameDetailLevel - // if the adjacent face is underground we probably don't need it - && RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY - // check if this face is on a border - && - ( - (x == 0 && direction == EDhDirection.WEST) - || (z == 0 && direction == EDhDirection.NORTH) - // TODO why does 256 represent a border? aren't LODs only 64 datapoints wide? - || (x == 256 && direction == EDhDirection.EAST) - || (z == 256 && direction == EDhDirection.SOUTH) - ); - - byte newSkyLightAtPos = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; - skyLightAtInputPos[i] = (byte) Math.min(newSkyLightAtPos, skyLightAtPos); - } - } - else - { - // adjacent is transparent, - // use datapoint below adjacent for lighting - byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint); - for (int i = adjMinY; i < adjMaxY; i++) - { - byte skyLightAtPos = skyLightAtInputPos[i]; - skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos); - } - } - - - // fill in sky light up to the next DP, - // this is done to handle overhangs - byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint); - int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); - for (int i = adjMaxY; i < adjAboveMinY; i++) - { - byte skyLightAtPos = skyLightAtInputPos[i]; - skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos); - } + continue; } + long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA; + long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA; + boolean adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBelowPoint); + boolean adjTransparent = !adjOverVoid + && RenderDataPointUtil.getAlpha(adjPoint) < 255 + && transparencyEnabled; - //=======================// - // create vertical faces // - //=======================// + byte adjSkyLight = RenderDataPointUtil.getLightSky(adjPoint); + byte lightToApply; - boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; - byte lastSkyLight = skyLightAtInputPos[yMin]; - int quadBottomY = yMin; - int quadTopY = -1; - - // walk up the sky lights and create a new face - // whenever the light changes to different valid value - for (int i = yMin; i < yMax; i++) + if (!adjTransparent) { - byte skyLight = skyLightAtInputPos[i]; - if (skyLight != lastSkyLight) - { - // the sky light changed, create the in-progress face - tryAddVerticalFaceWithSkyLightToBuilder( - builder, direction, - x, z, horizontalWidth, - color, irisBlockMaterialId, blockLight, - lastSkyLight, inputTransparent, quadTopY, quadBottomY + // Adjacent is opaque + boolean adjacentCoversThis = + !adjacentIsSameDetailLevel + && RenderDataPointUtil.getYMax(adjPoint) >= caveCullingMaxY + && + ( + (x == 0 && direction == EDhDirection.WEST) + || (z == 0 && direction == EDhDirection.NORTH) + || (x == 256 && direction == EDhDirection.EAST) + || (z == 256 && direction == EDhDirection.SOUTH) ); - - lastSkyLight = skyLight; - quadBottomY = i; - } - quadTopY = (i + 1); + lightToApply = adjacentCoversThis ? adjSkyLight : SKYLIGHT_COVERED; + } + else + { + // Adjacent is transparent, use below light + lightToApply = RenderDataPointUtil.getLightSky(adjBelowPoint); } - // add the in-progress face if present - if (quadTopY != -1) + + // Apply light to the range [adjMinY, adjMaxY) + applyLightToRange(segments, adjMinY, adjMaxY, lightToApply); + + // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight + int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); + if (adjMaxY < adjAboveMinY) { - tryAddVerticalFaceWithSkyLightToBuilder( - builder, direction, - x, z, horizontalWidth, - color, irisBlockMaterialId, blockLight, - lastSkyLight, inputTransparent, quadTopY, quadBottomY - ); + applyLightToRange(segments, adjMaxY, adjAboveMinY, adjSkyLight); } } - finally + + + + //=======================// + // Create vertical faces // + // from segments // + //=======================// + + for (YSegment seg : segments) { - // clean up the array before the next thread uses it - // (may be unnecessary since we only work between the yMin-yMax anyway, but is helpful for debugging) - Arrays.fill(skyLightAtInputPos, yMin, yMax, SKYLIGHT_EMPTY); + tryAddVerticalFaceWithSkyLightToBuilder( + builder, direction, + x, z, horizontalWidth, + color, irisBlockMaterialId, blockLight, + seg.skyLight, inputTransparent, seg.endY, seg.startY + ); } } + + // Apply a light value to a Y range, splitting segments as needed + private static void applyLightToRange(ArrayList segments, int rangeStart, int rangeEnd, byte newLight) + { + ArrayList newSegments = new ArrayList<>(); + + for (YSegment seg : segments) + { + // No overlap + if (seg.endY <= rangeStart + || seg.startY >= rangeEnd) + { + newSegments.add(seg); + continue; + } + + // Partial or complete overlap - need to split + + // Part before the range + if (seg.startY < rangeStart) + { + newSegments.add(new YSegment(seg.startY, rangeStart, seg.skyLight)); + } + + // Overlapping part - take minimum light + int overlapStart = Math.max(seg.startY, rangeStart); + int overlapEnd = Math.min(seg.endY, rangeEnd); + byte minLight = (byte) Math.min(newLight, seg.skyLight); + newSegments.add(new YSegment(overlapStart, overlapEnd, minLight)); + + // Part after the range + if (seg.endY > rangeEnd) + { + newSegments.add(new YSegment(rangeEnd, seg.endY, seg.skyLight)); + } + } + + segments.clear(); + segments.addAll(newSegments); + } + private static void tryAddVerticalFaceWithSkyLightToBuilder( LodQuadBuilder builder, EDhDirection direction, short x, short z, short horizontalWidth, @@ -412,24 +382,85 @@ public class ColumnBox ) { // invalid positions will have a negative skylight - if (lastSkyLight >= 0) + if (lastSkyLight < 0) { - // Don't add transparent vertical faces - // unless the adjacent position is empty. - // This is done to prevent walls between water blocks in the ocean. - if (!inputTransparent - || (lastSkyLight == LodUtil.MAX_MC_LIGHT)) - { - // don't add negative/empty height faces - short height = (short) (quadTopY - quadBottomY); - if (height > 0) - { - builder.addQuadAdj(direction, x, (short) quadBottomY, z, horizontalWidth, height, color, irisBlockMaterialId, lastSkyLight, blockLight); - } - } + return; } + + // Don't add transparent vertical faces + // unless the adjacent position is empty. + // This is done to prevent walls between water blocks in the ocean. + if (inputTransparent + && (lastSkyLight != LodUtil.MAX_MC_LIGHT)) + { + return; + } + + // don't add negative/empty height faces + short height = (short) (quadTopY - quadBottomY); + if (height <= 0) + { + return; + } + + builder.addQuadAdj( + direction, + x, (short) quadBottomY, z, + horizontalWidth, height, + color, irisBlockMaterialId, lastSkyLight, blockLight); } + private static class YSegment + { + int startY; + int endY; + byte skyLight; + + YSegment(int startY, int endY, byte skyLight) + { + this.startY = startY; + this.endY = endY; + this.skyLight = skyLight; + } + } + + + /** + * @see com.seibel.distanthorizons.core.util.FullDataPointUtil + */ + private static class YSegmentUtil + { + private static final int HEIGHT_WIDTH = Short.SIZE; + private static final int SKY_LIGHT_WIDTH = Byte.SIZE; + + private static final int START_Y_MASK = (int) Math.pow(2, HEIGHT_WIDTH) - 1; + private static final int END_Y_MASK = (int) Math.pow(2, HEIGHT_WIDTH) - 1; + private static final int SKY_LIGHT_MASK = (int) Math.pow(2, SKY_LIGHT_WIDTH) - 1; + + private static final int START_Y_OFFSET = 0; + private static final int END_Y_OFFSET = START_Y_OFFSET + HEIGHT_WIDTH; + private static final int SKY_LIGHT_OFFSET = END_Y_OFFSET + HEIGHT_WIDTH; + + + + public static long encode(short startY, short endY, byte skyLight) + { + long data = 0L; + data |= (long) (startY & START_Y_MASK) << START_Y_OFFSET; + data |= (long) (endY & END_Y_MASK) << END_Y_OFFSET; + data |= (long) (skyLight & SKY_LIGHT_MASK) << SKY_LIGHT_OFFSET; + return data; + } + + public static short getStartY(long data) { return (short) ((data >> START_Y_OFFSET) & START_Y_MASK); } + public static short getEndY(long data) { return (short) ((data >> END_Y_OFFSET) & END_Y_MASK); } + public static short getSkyLight(long data) { return (short) ((data >> SKY_LIGHT_OFFSET) & SKY_LIGHT_MASK); } + + } + + + + } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java index a75b1566d..7d8cd325f 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/ColumnRenderBufferBuilder.java @@ -33,7 +33,6 @@ import com.seibel.distanthorizons.core.render.glObject.GLProxy; import com.seibel.distanthorizons.core.util.ColorUtil; import com.seibel.distanthorizons.core.util.LodUtil; import com.seibel.distanthorizons.core.util.RenderDataPointUtil; -import com.seibel.distanthorizons.core.util.objects.UncheckedInterruptedException; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; @@ -106,18 +105,15 @@ public class ColumnRenderBufferBuilder //===================// byte thisDetailLevel = renderSource.getDataDetailLevel(); - for (int relX = 0; relX < ColumnRenderSource.SECTION_SIZE; relX++) + for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++) { - for (int relZ = 0; relZ < ColumnRenderSource.SECTION_SIZE; relZ++) + for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++) { - // stop the builder if requested - UncheckedInterruptedException.throwIfInterrupted(); - // ignore empty/null columns ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ); if (columnRenderData.size() == 0 - || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) - || RenderDataPointUtil.isVoid(columnRenderData.get(0))) + || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) + || RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0))) { continue; } @@ -158,8 +154,8 @@ public class ColumnRenderBufferBuilder int xAdj = relX + lodDirection.normal.x; int zAdj = relZ + lodDirection.normal.z; boolean isCrossRenderSourceBoundary = - (xAdj < 0 || xAdj >= ColumnRenderSource.SECTION_SIZE) || - (zAdj < 0 || zAdj >= ColumnRenderSource.SECTION_SIZE); + (xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) || + (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH); ColumnRenderSource adjRenderSource; byte adjDetailLevel; @@ -196,20 +192,20 @@ public class ColumnRenderBufferBuilder if (xAdj < 0) { - xAdj += ColumnRenderSource.SECTION_SIZE; + xAdj += ColumnRenderSource.WIDTH; } - if (xAdj >= ColumnRenderSource.SECTION_SIZE) + if (xAdj >= ColumnRenderSource.WIDTH) { - xAdj -= ColumnRenderSource.SECTION_SIZE; + xAdj -= ColumnRenderSource.WIDTH; } if (zAdj < 0) { - zAdj += ColumnRenderSource.SECTION_SIZE; + zAdj += ColumnRenderSource.WIDTH; } - if (zAdj >= ColumnRenderSource.SECTION_SIZE) + if (zAdj >= ColumnRenderSource.WIDTH) { - zAdj -= ColumnRenderSource.SECTION_SIZE; + zAdj -= ColumnRenderSource.WIDTH; } } } @@ -244,15 +240,14 @@ public class ColumnRenderBufferBuilder ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ); - // We render every vertical lod present in this position - // We only stop when we find a block that is void or non-existing block for (int i = 0; i < columnRenderData.size(); i++) { // can be uncommented to limit which vertical LOD is generated if (Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugEnable.get()) { int wantedColumnIndex = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugColumnIndex.get(); - if (wantedColumnIndex >= 0 && i != wantedColumnIndex) + if (wantedColumnIndex >= 0 + && i != wantedColumnIndex) { continue; } @@ -261,7 +256,8 @@ public class ColumnRenderBufferBuilder long data = columnRenderData.get(i); // If the data is not render-able (Void or non-existing) we stop since there is // no data left in this position - if (RenderDataPointUtil.isVoid(data) || !RenderDataPointUtil.doesDataPointExist(data)) + if (RenderDataPointUtil.hasZeroHeight(data) + || !RenderDataPointUtil.doesDataPointExist(data)) { break; } @@ -269,7 +265,7 @@ public class ColumnRenderBufferBuilder long topDataPoint = (i - 1) >= 0 ? columnRenderData.get(i - 1) : RenderDataPointUtil.EMPTY_DATA; long bottomDataPoint = (i + 1) < columnRenderData.size() ? columnRenderData.get(i + 1) : RenderDataPointUtil.EMPTY_DATA; - addLodToBuffer( + addRenderDataPointToBuilder( clientLevel, data, topDataPoint, bottomDataPoint, adjColumnViews, isSameDetailLevel, @@ -282,31 +278,31 @@ public class ColumnRenderBufferBuilder quadBuilder.mergeQuads(); } - private static void addLodToBuffer( + private static void addRenderDataPointToBuilder( IDhClientLevel clientLevel, - long data, long topData, long bottomData, + long renderData, long topRenderData, long bottomRenderData, ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, LodQuadBuilder quadBuilder, ColumnRenderSource.DebugSourceFlag debugSource) { long sectionPos = DhSectionPos.encode(detailLevel, renderSourceOffsetPosX, renderSourceOffsetPosZ); - short width = (short) BitShiftUtil.powerOfTwo(detailLevel); - short xMin = (short) DhSectionPos.getMinCornerBlockX(sectionPos); - short yMin = RenderDataPointUtil.getYMin(data); - short zMin = (short) DhSectionPos.getMinCornerBlockZ(sectionPos); - short ySize = (short) (RenderDataPointUtil.getYMax(data) - yMin); + short blockWidth = (short) DhSectionPos.getDetailLevelWidthInBlocks(detailLevel); + short blockMinX = (short) DhSectionPos.getMinCornerBlockX(sectionPos); + short blockMinY = RenderDataPointUtil.getYMin(renderData); + short blockMinZ = (short) DhSectionPos.getMinCornerBlockZ(sectionPos); + short blockMaxY = (short) (RenderDataPointUtil.getYMax(renderData) - blockMinY); - if (ySize == 0) + if (blockMaxY == 0) { return; } - else if (ySize < 0) + else if (blockMaxY < 0) { - throw new IllegalArgumentException("Negative y size for the data! Data: [" + RenderDataPointUtil.toString(data) + "]."); + throw new IllegalArgumentException("Negative y size for the renderDataPoint! Data: [" + RenderDataPointUtil.toString(renderData) + "]."); } - byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(data); + byte blockMaterialId = RenderDataPointUtil.getBlockMaterialId(renderData); @@ -321,11 +317,11 @@ public class ColumnRenderBufferBuilder float brightnessMultiplier = Config.Client.Advanced.Graphics.Quality.brightnessMultiplier.get().floatValue(); if (saturationMultiplier == 1.0 && brightnessMultiplier == 1.0) { - color = RenderDataPointUtil.getColor(data); + color = RenderDataPointUtil.getColor(renderData); } else { - float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(data)); + float[] ahsv = ColorUtil.argbToAhsv(RenderDataPointUtil.getColor(renderData)); color = ColorUtil.ahsvToArgb(ahsv[0], ahsv[1], ahsv[2] * saturationMultiplier, ahsv[3] * brightnessMultiplier); } break; @@ -416,13 +412,13 @@ public class ColumnRenderBufferBuilder ColumnBox.addBoxQuadsToBuilder( quadBuilder, clientLevel, - width, ySize, width, - xMin, yMin, zMin, + blockWidth, blockMaxY, + blockMinX, blockMinY, blockMinZ, color, blockMaterialId, - RenderDataPointUtil.getLightSky(data), - fullBright ? 15 : RenderDataPointUtil.getLightBlock(data), - topData, bottomData, adjColumnViews, isSameDetailLevel); + RenderDataPointUtil.getLightSky(renderData), + fullBright ? LodUtil.MAX_MC_LIGHT : RenderDataPointUtil.getLightBlock(renderData), + topRenderData, bottomRenderData, adjColumnViews, isSameDetailLevel); } } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java index 463e171b8..2ea274322 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/render/bufferBuilding/LodQuadBuilder.java @@ -138,7 +138,8 @@ public class LodQuadBuilder //===========// public void addQuadAdj( - EDhDirection dir, short x, short y, short z, + EDhDirection dir, + short x, short y, short z, short widthEastWest, short widthNorthSouthOrUpDown, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight) { @@ -149,11 +150,11 @@ public class LodQuadBuilder BufferQuad quad = new BufferQuad(x, y, z, widthEastWest, widthNorthSouthOrUpDown, color, irisBlockMaterialId, skyLight, blockLight, dir); ArrayList quadList = (this.doTransparency && ColorUtil.getAlpha(color) < 255) ? this.transparentQuads[dir.ordinal()] : this.opaqueQuads[dir.ordinal()]; - if (!quadList.isEmpty() && - ( - quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) - || quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) - ) + if (!quadList.isEmpty() + && ( + quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.EastWest) + || quadList.get(quadList.size() - 1).tryMerge(quad, BufferMergeDirectionEnum.NorthSouthOrUpDown)) + ) { this.premergeCount++; return; @@ -174,8 +175,8 @@ public class LodQuadBuilder public void addQuadDown(short x, short y, short z, short width, short wz, int color, byte irisBlockMaterialId, byte skylight, byte blocklight) { BufferQuad quad = new BufferQuad(x, y, z, width, wz, color, irisBlockMaterialId, skylight, blocklight, EDhDirection.DOWN); - ArrayList qs = (doTransparency && ColorUtil.getAlpha(color) < 255) - ? transparentQuads[EDhDirection.DOWN.ordinal()] : opaqueQuads[EDhDirection.DOWN.ordinal()]; + ArrayList qs = (this.doTransparency && ColorUtil.getAlpha(color) < 255) + ? this.transparentQuads[EDhDirection.DOWN.ordinal()] : this.opaqueQuads[EDhDirection.DOWN.ordinal()]; qs.add(quad); } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java index 52fa961e0..cb4100455 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/dataObjects/transformers/FullDataToRenderDataTransformer.java @@ -31,6 +31,7 @@ import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPosMutable; +import com.seibel.distanthorizons.core.render.LodQuadTree; import com.seibel.distanthorizons.core.util.*; import com.seibel.distanthorizons.core.wrapperInterfaces.IWrapperFactory; import com.seibel.distanthorizons.core.wrapperInterfaces.block.IBlockStateWrapper; @@ -138,7 +139,7 @@ public class FullDataToRenderDataTransformer } } - columnSource.fillDebugFlag(0, 0, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.SECTION_SIZE, ColumnRenderSource.DebugSourceFlag.FULL); + columnSource.fillDebugFlag(0, 0, ColumnRenderSource.WIDTH, ColumnRenderSource.WIDTH, ColumnRenderSource.DebugSourceFlag.FULL); return columnSource; } @@ -173,7 +174,10 @@ public class FullDataToRenderDataTransformer // expand the ColumnArrayView to fit the new larger max vertical size ColumnArrayView newColumnArrayView = new ColumnArrayView(dataArrayList, fullDataLength, 0, fullDataLength); setRenderColumnView(levelWrapper, fullDataSource, blockX, blockZ, newColumnArrayView, fullDataColumn); + + PerfRecorder.Timer vertSize = LodQuadTree.TRANSFORM_PERF_RECORDER.start("vertSize"); columnArrayView.changeVerticalSizeFrom(newColumnArrayView); + vertSize.end(); } finally { @@ -186,6 +190,8 @@ public class FullDataToRenderDataTransformer int blockX, int blockZ, ColumnArrayView renderColumnData, LongArrayList fullColumnData) { + PerfRecorder.Timer prep = LodQuadTree.TRANSFORM_PERF_RECORDER.start("prep"); + //===============// // config values // //===============// @@ -217,6 +223,8 @@ public class FullDataToRenderDataTransformer int blocklightToApplyToNextBlock = -1; int renderDataIndex = 0; + prep.end(); + //==================================// @@ -230,6 +238,8 @@ public class FullDataToRenderDataTransformer // goes from the top down for (int fullDataIndex = 0; fullDataIndex < fullColumnData.size(); fullDataIndex++) { + PerfRecorder.Timer fullParse = LodQuadTree.TRANSFORM_PERF_RECORDER.start("fullParse"); + long fullData = fullColumnData.getLong(fullDataIndex); int bottomY = FullDataPointUtil.getBottomY(fullData); @@ -265,6 +275,8 @@ public class FullDataToRenderDataTransformer continue; } + fullParse.end(); + //====================// @@ -272,48 +284,57 @@ public class FullDataToRenderDataTransformer // cave culling check // //====================// - boolean ignoreBlock = blockStatesToIgnore.contains(block); - boolean caveBlock = caveBlockStatesToIgnore.contains(block); // TODO caves should also ignore transparent/non-solid blocks (IE grass and plants) wthout each being defined - if (caveBlock) + PerfRecorder.Timer caveCull = LodQuadTree.TRANSFORM_PERF_RECORDER.start("caveCull"); + + try { - if (caveCullingEnabled - // assume this data point is underground if it has no sky-light - && skyLight == LodUtil.MIN_MC_LIGHT - // ignore caves above a certain height to prevent floating islands from having walls underneath them - && topY < caveCullingMaxY - // cave culling shouldn't happen when at the top of the world - && renderDataIndex != 0 && fullDataIndex != 0 - // cave culling can't happen when at the bottom of the world - && (fullDataIndex+1) < fullColumnData.size()) + boolean ignoreBlock = blockStatesToIgnore.contains(block); + boolean caveBlock = caveBlockStatesToIgnore.contains(block); // TODO caves should also ignore transparent/non-solid blocks (IE grass and plants) wthout each being defined + if (caveBlock) { - // we need to get the next sky/block lights because - // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. - long nextFullData = fullColumnData.getLong(fullDataIndex+1); - int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); - - if (nextSkyLight == LodUtil.MIN_MC_LIGHT - && ColorUtil.getAlpha(lastColor) == 255) + if (caveCullingEnabled + // assume this data point is underground if it has no sky-light + && skyLight == LodUtil.MIN_MC_LIGHT + // ignore caves above a certain height to prevent floating islands from having walls underneath them + && topY < caveCullingMaxY + // cave culling shouldn't happen when at the top of the world + && renderDataIndex != 0 && fullDataIndex != 0 + // cave culling can't happen when at the bottom of the world + && (fullDataIndex + 1) < fullColumnData.size()) { - // replace the previous block with new bottom - long columnData = renderColumnData.get(renderDataIndex - 1); - columnData = RenderDataPointUtil.setYMin(columnData, bottomY); - renderColumnData.set(renderDataIndex - 1, columnData); + // we need to get the next sky/block lights because + // the air block here will always have a light of 0/0 due to only the top of the LOD's light being saved. + long nextFullData = fullColumnData.getLong(fullDataIndex + 1); + int nextSkyLight = FullDataPointUtil.getSkyLight(nextFullData); + + if (nextSkyLight == LodUtil.MIN_MC_LIGHT + && ColorUtil.getAlpha(lastColor) == 255) + { + // replace the previous block with new bottom + long columnData = renderColumnData.get(renderDataIndex - 1); + columnData = RenderDataPointUtil.setYMin(columnData, bottomY); + renderColumnData.set(renderDataIndex - 1, columnData); + } + + continue; } - continue; + + if (ignoreBlock) + { + // this is a merged block and a cave block, so it should never be rendered + continue; + } } - - - if (ignoreBlock) + else if (ignoreBlock) { - // this is a merged block and a cave block, so it should never be rendered + // this is an ignored block, but shouldn't be merged like a cave block continue; } } - else if (ignoreBlock) + finally { - // this is an ignored block, but shouldn't be merged like a cave block - continue; + caveCull.end(); } @@ -322,20 +343,22 @@ public class FullDataToRenderDataTransformer // non-solid block check // //=======================// - if (ignoreNonCollidingBlocks - && !block.isSolid() - && !block.isLiquid() - && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE) + if (ignoreNonCollidingBlocks + && !block.isSolid() + && !block.isLiquid() + && block.getOpacity() != LodUtil.BLOCK_FULLY_OPAQUE) { if (colorBelowWithAvoidedBlocks) { + PerfRecorder.Timer nonSolid = LodQuadTree.TRANSFORM_PERF_RECORDER.start("color-NonSolid"); int tempColor = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); + nonSolid.end(); // don't transfer the color when alpha is 0 // this prevents issues if grass is transparent if (ColorUtil.getAlpha(tempColor) != 0) { - colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor,255); + colorToApplyToNextBlock = ColorUtil.setAlpha(tempColor, 255); skylightToApplyToNextBlock = skyLight; blocklightToApplyToNextBlock = blockLight; } @@ -349,8 +372,10 @@ public class FullDataToRenderDataTransformer int color; if (colorToApplyToNextBlock == -1) { + PerfRecorder.Timer colorTimer = LodQuadTree.TRANSFORM_PERF_RECORDER.start("color"); // use this block's color color = levelWrapper.getBlockColor(mutableBlockPos, biome, fullDataSource, block); + colorTimer.end(); } else { @@ -366,6 +391,7 @@ public class FullDataToRenderDataTransformer //=============================// // merge same-colored adjacent // //=============================// + PerfRecorder.Timer mergeSame = LodQuadTree.TRANSFORM_PERF_RECORDER.start("mergeSame"); // check if they share a top-bottom face and if they have same color if (color == lastColor @@ -387,6 +413,8 @@ public class FullDataToRenderDataTransformer } lastBottom = bottomY; lastColor = color; + + mergeSame.end(); } @@ -398,21 +426,4 @@ public class FullDataToRenderDataTransformer - //================// - // helper methods // - //================// - - /** - * Called in loops that may run for an extended period of time.
- * This is necessary to allow canceling these transformers since running - * them after the client has left a given world will throw exceptions. - */ - private static void throwIfThreadInterrupted() throws InterruptedException - { - if (Thread.interrupted()) - { - throw new InterruptedException(FullDataToRenderDataTransformer.class.getSimpleName() + " task interrupted."); - } - } - } diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java index 4191cc995..3fed7c1a5 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodQuadTree.java @@ -59,8 +59,6 @@ import java.util.concurrent.locks.ReentrantLock; */ public class LodQuadTree extends QuadTree implements IDebugRenderable, AutoCloseable { - public static final byte TREE_LOWEST_DETAIL_LEVEL = ColumnRenderSource.SECTION_SIZE_OFFSET; - private static final DhLogger LOGGER = new DhLoggerBuilder().build(); /** there should only ever be one {@link LodQuadTree} so having the thread static should be fine */ private static final ThreadPoolExecutor FULL_DATA_RETRIEVAL_QUEUE_THREAD = ThreadUtil.makeSingleThreadPool("QuadTree Full Data Retrieval Queue Populator"); @@ -99,11 +97,13 @@ public class LodQuadTree extends QuadTree implements IDebugRen // TODO should be removed once James is done testing @Deprecated public static final PerfRecorder FILE_PERF_RECORDER = new PerfRecorder("File"); + @Deprecated + public static final PerfRecorder TRANSFORM_PERF_RECORDER = new PerfRecorder("Transform"); /** the smallest numerical detail level number that can be rendered */ - private byte maxRenderDetailLevel; + private byte maxLeafRenderDetailLevel; /** the largest numerical detail level number that can be rendered */ - private byte minRenderDetailLevel; + private byte minRootRenderDetailLevel; /** used to calculate when a detail drop will occur */ private double detailDropOffDistanceUnit; @@ -121,7 +121,7 @@ public class LodQuadTree extends QuadTree implements IDebugRen int initialPlayerBlockX, int initialPlayerBlockZ, FullDataSourceProviderV2 fullDataSourceProvider) { - super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), TREE_LOWEST_DETAIL_LEVEL); + super(viewDiameterInBlocks, new DhBlockPos2D(initialPlayerBlockX, initialPlayerBlockZ), DhSectionPos.SECTION_MINIMUM_DETAIL_LEVEL); DebugRenderer.register(this, Config.Client.Advanced.Debugging.DebugWireframe.showQuadTreeRenderStatus); @@ -133,6 +133,8 @@ public class LodQuadTree extends QuadTree implements IDebugRen this.beaconRenderHandler = (genericObjectRenderer != null) ? new BeaconRenderHandler(genericObjectRenderer) : null; FILE_PERF_RECORDER.clear(); + TRANSFORM_PERF_RECORDER.clear(); + COL_BOX_PERF_RECORDER.clear(); } @@ -157,6 +159,10 @@ public class LodQuadTree extends QuadTree implements IDebugRen } + FILE_PERF_RECORDER.tryLog(); + TRANSFORM_PERF_RECORDER.tryLog(); + COL_BOX_PERF_RECORDER.tryLog(); + // this shouldn't be updated while the tree is being iterated through this.updateDetailLevelVariables(); diff --git a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java index 59504ed39..b7dc0ec37 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/util/RenderDataPointUtil.java @@ -24,6 +24,7 @@ import com.seibel.distanthorizons.core.level.AbstractDhLevel; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.ColumnArrayView; import com.seibel.distanthorizons.core.dataObjects.render.columnViews.IColumnDataView; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.wrapperInterfaces.world.ILevelWrapper; import com.seibel.distanthorizons.coreapi.ModInfo; import com.seibel.distanthorizons.core.logging.DhLogger; @@ -202,9 +203,9 @@ public class RenderDataPointUtil return dataPoint & ~(HEIGHT_SHIFTED_MASK | DEPTH_SHIFTED_MASK) | height | depth; } - /** AKA the ending/top/highest Y value above {@link AbstractDhLevel#getMinY()} */ + /** AKA the ending/top/highest Y value above {@link ILevelWrapper#getMinHeight()} ()} */ public static short getYMax(long dataPoint) { return (short) ((dataPoint >>> HEIGHT_SHIFT) & HEIGHT_MASK); } - /** AKA the starting/bottom/lowest Y value above {@link AbstractDhLevel#getMinY()} */ + /** AKA the starting/bottom/lowest Y value above {@link ILevelWrapper#getMinHeight()} */ public static short getYMin(long dataPoint) { return (short) ((dataPoint >>> DEPTH_SHIFT) & DEPTH_MASK); } public static long setYMin(long dataPoint, int depth) { return (long) ((dataPoint & ~(DEPTH_MASK << DEPTH_SHIFT)) | (depth & DEPTH_MASK) << DEPTH_SHIFT); } @@ -219,7 +220,7 @@ public class RenderDataPointUtil public static byte getBlockMaterialId(long dataPoint) { return (byte) ((dataPoint >>> IRIS_BLOCK_MATERIAL_ID_SHIFT) & IRIS_BLOCK_MATERIAL_ID_MASK); } - public static boolean isVoid(long dataPoint) { return (((dataPoint >>> DEPTH_SHIFT) & HEIGHT_DEPTH_MASK) == 0); } + public static boolean hasZeroHeight(long dataPoint) { return (((dataPoint >>> DEPTH_SHIFT) & HEIGHT_DEPTH_MASK) == 0); } public static boolean doesDataPointExist(long dataPoint) { return dataPoint != EMPTY_DATA; } @@ -240,7 +241,7 @@ public class RenderDataPointUtil { return "null"; } - else if (isVoid(dataPoint)) + else if (hasZeroHeight(dataPoint)) { return "void"; }