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 aad55a84b..77c76360b 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,20 +23,17 @@ 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.PhantomArrayListCheckout; 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.coreapi.util.MathUtil; +import it.unimi.dsi.fastutil.longs.LongArrayList; 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); @@ -47,8 +44,6 @@ public class ColumnBox */ private static final byte SKYLIGHT_COVERED = -1; - public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Column Box"); - @@ -57,7 +52,7 @@ public class ColumnBox //=========// public static void addBoxQuadsToBuilder( - LodQuadBuilder builder, IDhClientLevel clientLevel, + LodQuadBuilder builder, PhantomArrayListCheckout phantomArrayCheckout, IDhClientLevel clientLevel, short width, short yHeight, short minX, short minY, short minZ, int color, byte irisBlockMaterialId, byte skyLight, byte blockLight, @@ -122,20 +117,26 @@ public class ColumnBox // add top and bottom faces // //==========================// - boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) - && (RenderDataPointUtil.getYMin(topData) == maxY) - && !isTopTransparent; - if (!skipTop) + // top face { - builder.addQuadUp(minX, maxY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.UP)), irisBlockMaterialId, skyLightTop, blockLight); + boolean skipTop = RenderDataPointUtil.doesDataPointExist(topData) + && (RenderDataPointUtil.getYMin(topData) == maxY) + && !isTopTransparent; + if (!skipTop) + { + 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; - if (!skipBottom) + // bottom face { - builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); + boolean skipBottom = RenderDataPointUtil.doesDataPointExist(bottomData) + && (RenderDataPointUtil.getYMax(bottomData) == minY) + && !isBottomTransparent; + if (!skipBottom) + { + builder.addQuadDown(minX, minY, minZ, width, width, ColorUtil.applyShade(color, MC.getShade(EDhDirection.DOWN)), irisBlockMaterialId, skyLightBot, blockLight); + } } @@ -146,84 +147,119 @@ public class ColumnBox // NORTH face { - ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2? - boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.ordinal() - 2]; + ColumnArrayView adjCol = adjData[EDhDirection.NORTH.compassIndex]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.NORTH.compassIndex]; // if the adjacent column is null that generally means the adjacent area hasn't been generated yet if (adjCol == null) { // Add an adjacent face if this is opaque face or transparent over the void. if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.NORTH, minX, minY, minZ, width, yHeight, 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, width, yHeight, + makeAdjVerticalQuad( + builder, phantomArrayCheckout, + adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.NORTH, + minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } // SOUTH face { - ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2]; - boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2]; + ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.compassIndex]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.SOUTH.compassIndex]; if (adjCol == null) { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.SOUTH, minX, minY, maxZ, width, yHeight, 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, width, yHeight, + makeAdjVerticalQuad( + builder, phantomArrayCheckout, + adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.SOUTH, + minX, minY, maxZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } // WEST face { - ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2]; - boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.ordinal() - 2]; + ColumnArrayView adjCol = adjData[EDhDirection.WEST.compassIndex]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.WEST.compassIndex]; if (adjCol == null) { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.WEST, minX, minY, minZ, width, yHeight, 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, width, yHeight, + makeAdjVerticalQuad( + builder, phantomArrayCheckout, + adjCol, adjSameDetailLevel, caveCullingMaxY, EDhDirection.WEST, + minX, minY, minZ, width, yHeight, color, irisBlockMaterialId, blockLight); } } // EAST face { - ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2]; - boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.ordinal() - 2]; + ColumnArrayView adjCol = adjData[EDhDirection.EAST.compassIndex]; + boolean adjSameDetailLevel = isAdjDataSameDetailLevel[EDhDirection.EAST.compassIndex]; if (adjCol == null) { if (!isTransparent || overVoid) { - builder.addQuadAdj(EDhDirection.EAST, maxX, minY, minZ, width, yHeight, 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, width, yHeight, + makeAdjVerticalQuad( + builder, phantomArrayCheckout, + 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, PhantomArrayListCheckout phantomArrayCheckout, + @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) { + // pooled arrays + LongArrayList segments = phantomArrayCheckout.getLongArray(0, 0); + LongArrayList newSegments = phantomArrayCheckout.getLongArray(1, 0); + + + //==================// // create face with // // no adjacent data // @@ -240,30 +276,29 @@ public class ColumnBox - //===========================// - // Build Y-range segments // - // with their sky light // - //===========================// + //=================================// + // determine face visibility/light // + //=================================// boolean transparencyEnabled = Config.Client.Advanced.Graphics.Quality.transparency.get().transparencyEnabled; boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && transparencyEnabled; short yMax = (short) (yMin + ySize); - // List to store segments: [startY, endY, skyLight] - ArrayList segments = new ArrayList<>(); int adjCount = adjColumnView.size(); // Start with the entire range at max light - segments.add(new YSegment(yMin, yMax, LodUtil.MAX_MC_LIGHT)); + segments.add(YSegmentUtil.encode(yMin, yMax, LodUtil.MAX_MC_LIGHT)); - // Process each adjacent datapoint and split/update segments + // Process each adjacent datapoint and split segments as needed for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) { long adjPoint = adjColumnView.get(adjIndex); short adjMinY = RenderDataPointUtil.getYMin(adjPoint); short adjMaxY = RenderDataPointUtil.getYMax(adjPoint); + // skip empty adjacent points + // or points below this one if (!RenderDataPointUtil.doesDataPointExist(adjPoint) || RenderDataPointUtil.hasZeroHeight(adjPoint) || yMax <= adjMinY) @@ -271,6 +306,7 @@ public class ColumnBox continue; } + long adjAbovePoint = (adjIndex != 0) ? adjColumnView.get(adjIndex - 1) : RenderDataPointUtil.EMPTY_DATA; long adjBelowPoint = (adjIndex + 1 < adjCount) ? adjColumnView.get(adjIndex + 1) : RenderDataPointUtil.EMPTY_DATA; @@ -306,13 +342,13 @@ public class ColumnBox // Apply light to the range [adjMinY, adjMaxY) - applyLightToRange(segments, adjMinY, adjMaxY, lightToApply); + applyLightToRange(segments, newSegments, adjMinY, adjMaxY, lightToApply); // Fill overhang area [adjMaxY, adjAboveMinY) with adjSkyLight - int adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); + short adjAboveMinY = RenderDataPointUtil.getYMin(adjAbovePoint); if (adjMaxY < adjAboveMinY) { - applyLightToRange(segments, adjMaxY, adjAboveMinY, adjSkyLight); + applyLightToRange(segments, newSegments, adjMaxY, adjAboveMinY, adjSkyLight); } } @@ -323,27 +359,42 @@ public class ColumnBox // from segments // //=======================// - for (YSegment seg : segments) + for (int i = 0; i < segments.size(); i++) { + long segment = segments.getLong(i); tryAddVerticalFaceWithSkyLightToBuilder( builder, direction, x, z, horizontalWidth, color, irisBlockMaterialId, blockLight, - seg.skyLight, inputTransparent, seg.endY, seg.startY + YSegmentUtil.getSkyLight(segment), inputTransparent, YSegmentUtil.getEndY(segment), YSegmentUtil.getStartY(segment) ); } } - // Apply a light value to a Y range, splitting segments as needed - private static void applyLightToRange(ArrayList segments, int rangeStart, int rangeEnd, byte newLight) + /** + * Apply the new light value over the given y range, + * splitting segments as needed + *

+ * source: claude.ai + */ + private static void applyLightToRange( + LongArrayList segments, LongArrayList newSegments, + short rangeStart, short rangeEnd, + byte newLight) { - ArrayList newSegments = new ArrayList<>(); + // clear the pooled array that the new segments will go into + newSegments.clear(); - for (YSegment seg : segments) + for (int i = 0; i < segments.size(); i++) { + long seg = segments.getLong(i); + short endY = YSegmentUtil.getEndY(seg); + short startY = YSegmentUtil.getStartY(seg); + byte skyLight = YSegmentUtil.getSkyLight(seg); + // No overlap - if (seg.endY <= rangeStart - || seg.startY >= rangeEnd) + if (endY <= rangeStart + || startY >= rangeEnd) { newSegments.add(seg); continue; @@ -352,21 +403,21 @@ public class ColumnBox // Partial or complete overlap - need to split // Part before the range - if (seg.startY < rangeStart) + if (startY < rangeStart) { - newSegments.add(new YSegment(seg.startY, rangeStart, seg.skyLight)); + newSegments.add(YSegmentUtil.encode(startY, rangeStart, 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)); + short overlapStart = (short)Math.max(startY, rangeStart); + short overlapEnd = (short)Math.min(endY, rangeEnd); + byte minLight = (byte) Math.min(newLight, skyLight); + newSegments.add(YSegmentUtil.encode(overlapStart, overlapEnd, minLight)); // Part after the range - if (seg.endY > rangeEnd) + if (endY > rangeEnd) { - newSegments.add(new YSegment(rangeEnd, seg.endY, seg.skyLight)); + newSegments.add(YSegmentUtil.encode(rangeEnd, endY, skyLight)); } } @@ -412,23 +463,13 @@ public class ColumnBox - 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; - } - } + //================// + // helper classes // + //================// - - /** - * @see com.seibel.distanthorizons.core.util.FullDataPointUtil + /** + * encodes height/light data into a long + * to reduce object allocations. */ private static class YSegmentUtil { @@ -456,11 +497,10 @@ public class ColumnBox 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); } + public static byte getSkyLight(long data) { return (byte) ((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 7d8cd325f..41c3ace35 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 @@ -27,6 +27,8 @@ import com.seibel.distanthorizons.core.dataObjects.render.ColumnRenderSource; import com.seibel.distanthorizons.core.level.IDhClientLevel; import com.seibel.distanthorizons.core.logging.DhLogger; import com.seibel.distanthorizons.core.logging.DhLoggerBuilder; +import com.seibel.distanthorizons.core.pooling.PhantomArrayListCheckout; +import com.seibel.distanthorizons.core.pooling.PhantomArrayListPool; import com.seibel.distanthorizons.core.pos.blockPos.DhBlockPos; import com.seibel.distanthorizons.core.pos.DhSectionPos; import com.seibel.distanthorizons.core.render.glObject.GLProxy; @@ -34,7 +36,6 @@ 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.dataObjects.render.columnViews.ColumnArrayView; -import com.seibel.distanthorizons.coreapi.util.BitShiftUtil; import java.util.concurrent.CompletableFuture; @@ -47,6 +48,8 @@ public class ColumnRenderBufferBuilder { private static final DhLogger LOGGER = new DhLoggerBuilder().build(); + public static final PhantomArrayListPool ARRAY_LIST_POOL = new PhantomArrayListPool("Column Buffer Builder"); + //==============// @@ -104,182 +107,186 @@ public class ColumnRenderBufferBuilder // build each column // //===================// - byte thisDetailLevel = renderSource.getDataDetailLevel(); - for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++) + // pooled arrays for ColumnBox use + try (PhantomArrayListCheckout phantomArrayCheckout = ARRAY_LIST_POOL.checkoutArrays(0, 0, 2)) { - for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++) + byte thisDetailLevel = renderSource.getDataDetailLevel(); + for (int relX = 0; relX < ColumnRenderSource.WIDTH; relX++) { - // ignore empty/null columns - ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ); - if (columnRenderData.size() == 0 - || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) - || RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0))) + for (int relZ = 0; relZ < ColumnRenderSource.WIDTH; relZ++) { - continue; - } - - - - //=============// - // debug limit // - //=============// - - // can be used to limit the buffer building to a specific relative position. - // useful for debugging a single column - if (columnBuilderDebugEnabled) - { - int wantedX = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugXRow.get(); - if (wantedX >= 0 && relX != wantedX) + // ignore empty/null columns + ColumnArrayView columnRenderData = renderSource.getVerticalDataPointView(relX, relZ); + if (columnRenderData.size() == 0 + || !RenderDataPointUtil.doesDataPointExist(columnRenderData.get(0)) + || RenderDataPointUtil.hasZeroHeight(columnRenderData.get(0))) { continue; } - int wantedZ = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugZRow.get(); - if (wantedZ >= 0 && relZ != wantedZ) + + + + //=============// + // debug limit // + //=============// + + // can be used to limit the buffer building to a specific relative position. + // useful for debugging a single column + if (columnBuilderDebugEnabled) { - continue; - } - } - - - - //==================================// - // get adjacent render data columns // - //==================================// - - ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.CARDINAL_COMPASS.length]; - for (EDhDirection lodDirection : EDhDirection.CARDINAL_COMPASS) - { - try - { - int xAdj = relX + lodDirection.normal.x; - int zAdj = relZ + lodDirection.normal.z; - boolean isCrossRenderSourceBoundary = - (xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) || - (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH); - - ColumnRenderSource adjRenderSource; - byte adjDetailLevel; - - - - //=========================// - // get the adjacent render // - // source if present // - //=========================// - - if (!isCrossRenderSourceBoundary) + int wantedX = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugXRow.get(); + if (wantedX >= 0 && relX != wantedX) { - // the adjacent position is inside this same render source - adjRenderSource = renderSource; - adjDetailLevel = thisDetailLevel; + continue; } - else - { - // the adjacent position is outside this render source - - // skip empty sections - adjRenderSource = adjRegions[lodDirection.ordinal() - 2]; - if (adjRenderSource == null) - { - continue; - } - - adjDetailLevel = adjRenderSource.getDataDetailLevel(); - if (adjDetailLevel == thisDetailLevel) - { - // if the adjacent position is outside this render source, - // wrap the position around so it's inside the adjacent source - - if (xAdj < 0) - { - xAdj += ColumnRenderSource.WIDTH; - } - if (xAdj >= ColumnRenderSource.WIDTH) - { - xAdj -= ColumnRenderSource.WIDTH; - } - - if (zAdj < 0) - { - zAdj += ColumnRenderSource.WIDTH; - } - if (zAdj >= ColumnRenderSource.WIDTH) - { - zAdj -= ColumnRenderSource.WIDTH; - } - } - } - - - - //========================// - // get the adjacent views // - //========================// - - // the old logic handled additional cases, but they never appeared to fire, - // so just these two cases should be fine - boolean expectedDetailLevels = (adjDetailLevel == thisDetailLevel) || (adjDetailLevel > thisDetailLevel); - if (!expectedDetailLevels) - { - LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this."); - } - - adjColumnViews[lodDirection.ordinal() - 2] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj); - } - catch (RuntimeException e) - { - LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + lodDirection + "], Error: [" + e.getMessage() + "].", e); - } - } // for adjacent directions - - - - //==========================// - // build this render column // - //==========================// - - ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ); - - 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) + int wantedZ = Config.Client.Advanced.Debugging.ColumnBuilderDebugging.columnBuilderDebugZRow.get(); + if (wantedZ >= 0 && relZ != wantedZ) { continue; } } - 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.hasZeroHeight(data) - || !RenderDataPointUtil.doesDataPointExist(data)) + + + //==================================// + // get adjacent render data columns // + //==================================// + + ColumnArrayView[] adjColumnViews = new ColumnArrayView[EDhDirection.CARDINAL_COMPASS.length]; + for (EDhDirection direction : EDhDirection.CARDINAL_COMPASS) { - break; + try + { + int xAdj = relX + direction.normal.x; + int zAdj = relZ + direction.normal.z; + boolean isCrossRenderSourceBoundary = + (xAdj < 0 || xAdj >= ColumnRenderSource.WIDTH) || + (zAdj < 0 || zAdj >= ColumnRenderSource.WIDTH); + + ColumnRenderSource adjRenderSource; + byte adjDetailLevel; + + + + //=========================// + // get the adjacent render // + // source if present // + //=========================// + + if (!isCrossRenderSourceBoundary) + { + // the adjacent position is inside this same render source + adjRenderSource = renderSource; + adjDetailLevel = thisDetailLevel; + } + else + { + // the adjacent position is outside this render source + + // skip empty sections + adjRenderSource = adjRegions[direction.compassIndex]; + if (adjRenderSource == null) + { + continue; + } + + adjDetailLevel = adjRenderSource.getDataDetailLevel(); + if (adjDetailLevel == thisDetailLevel) + { + // if the adjacent position is outside this render source, + // wrap the position around so it's inside the adjacent source + + if (xAdj < 0) + { + xAdj += ColumnRenderSource.WIDTH; + } + if (xAdj >= ColumnRenderSource.WIDTH) + { + xAdj -= ColumnRenderSource.WIDTH; + } + + if (zAdj < 0) + { + zAdj += ColumnRenderSource.WIDTH; + } + if (zAdj >= ColumnRenderSource.WIDTH) + { + zAdj -= ColumnRenderSource.WIDTH; + } + } + } + + + + //========================// + // get the adjacent views // + //========================// + + // the old logic handled additional cases, but they never appeared to fire, + // so just these two cases should be fine + boolean expectedDetailLevels = (adjDetailLevel == thisDetailLevel) || (adjDetailLevel > thisDetailLevel); + if (!expectedDetailLevels) + { + LodUtil.assertNotReach("Mismatch between adjacent detail level ["+adjDetailLevel+"] and this render source's detail level ["+thisDetailLevel+"]. Detail levels should be adj >= this."); + } + + adjColumnViews[direction.compassIndex] = adjRenderSource.getVerticalDataPointView(xAdj, zAdj); + } + catch (RuntimeException e) + { + LOGGER.warn("Failed to get adj data for relative pos: [" + thisDetailLevel + ":" + relX + "," + relZ + "] at [" + direction + "], Error: [" + e.getMessage() + "].", e); + } + } // for adjacent directions + + + + //==========================// + // build this render column // + //==========================// + + ColumnRenderSource.DebugSourceFlag debugSourceFlag = renderSource.debugGetFlag(relX, relZ); + + 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) + { + continue; + } + } + + 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.hasZeroHeight(data) + || !RenderDataPointUtil.doesDataPointExist(data)) + { + break; + } + + 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; + + addRenderDataPointToBuilder( + clientLevel, phantomArrayCheckout, + data, topDataPoint, bottomDataPoint, + adjColumnViews, isSameDetailLevel, + thisDetailLevel, relX, relZ, + quadBuilder, debugSourceFlag); } - 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; - - addRenderDataPointToBuilder( - clientLevel, - data, topDataPoint, bottomDataPoint, - adjColumnViews, isSameDetailLevel, - thisDetailLevel, relX, relZ, - quadBuilder, debugSourceFlag); - } - - }// for z - }// for x + }// for z + }// for x + }// phantom checkout quadBuilder.mergeQuads(); } private static void addRenderDataPointToBuilder( - IDhClientLevel clientLevel, + IDhClientLevel clientLevel, PhantomArrayListCheckout phantomArrayCheckout, long renderData, long topRenderData, long bottomRenderData, ColumnArrayView[] adjColumnViews, boolean[] isSameDetailLevel, byte detailLevel, int renderSourceOffsetPosX, int renderSourceOffsetPosZ, @@ -411,7 +418,7 @@ public class ColumnRenderBufferBuilder } ColumnBox.addBoxQuadsToBuilder( - quadBuilder, clientLevel, + quadBuilder, phantomArrayCheckout, clientLevel, blockWidth, blockMaxY, blockMinX, blockMinY, blockMinZ, color, 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 cb4100455..ef8813c05 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 @@ -175,9 +175,7 @@ public class FullDataToRenderDataTransformer 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 { @@ -190,8 +188,6 @@ public class FullDataToRenderDataTransformer int blockX, int blockZ, ColumnArrayView renderColumnData, LongArrayList fullColumnData) { - PerfRecorder.Timer prep = LodQuadTree.TRANSFORM_PERF_RECORDER.start("prep"); - //===============// // config values // //===============// @@ -223,8 +219,6 @@ public class FullDataToRenderDataTransformer int blocklightToApplyToNextBlock = -1; int renderDataIndex = 0; - prep.end(); - //==================================// @@ -238,8 +232,6 @@ 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); @@ -275,8 +267,6 @@ public class FullDataToRenderDataTransformer continue; } - fullParse.end(); - //====================// @@ -284,57 +274,48 @@ public class FullDataToRenderDataTransformer // cave culling check // //====================// - PerfRecorder.Timer caveCull = LodQuadTree.TRANSFORM_PERF_RECORDER.start("caveCull"); - - try + 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) { - 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) + 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()) { - 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()) + // 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) { - // 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; + // replace the previous block with new bottom + long columnData = renderColumnData.get(renderDataIndex - 1); + columnData = RenderDataPointUtil.setYMin(columnData, bottomY); + renderColumnData.set(renderDataIndex - 1, columnData); } - - if (ignoreBlock) - { - // this is a merged block and a cave block, so it should never be rendered - continue; - } + continue; } - else if (ignoreBlock) + + + if (ignoreBlock) { - // this is an ignored block, but shouldn't be merged like a cave block + // this is a merged block and a cave block, so it should never be rendered continue; } } - finally + else if (ignoreBlock) { - caveCull.end(); + // this is an ignored block, but shouldn't be merged like a cave block + continue; } @@ -350,9 +331,7 @@ public class FullDataToRenderDataTransformer { 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 @@ -372,10 +351,8 @@ 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 { @@ -391,7 +368,6 @@ 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 @@ -413,8 +389,6 @@ public class FullDataToRenderDataTransformer } lastBottom = bottomY; lastColor = color; - - mergeSame.end(); } 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 3fed7c1a5..b28a14105 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 @@ -97,8 +97,6 @@ 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 maxLeafRenderDetailLevel; @@ -133,8 +131,6 @@ 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(); } @@ -160,8 +156,6 @@ 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 diff --git a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java index 4984bdb7b..284c8ded7 100644 --- a/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java +++ b/core/src/main/java/com/seibel/distanthorizons/core/render/LodRenderSection.java @@ -287,16 +287,16 @@ public class LodRenderSection implements IDebugRenderable, AutoCloseable ColumnRenderSource westRenderSource = adjacentLoadFutures[3].get()) { ColumnRenderSource[] adjacentRenderSections = new ColumnRenderSource[EDhDirection.CARDINAL_COMPASS.length]; - adjacentRenderSections[EDhDirection.NORTH.ordinal() - 2] = northRenderSource; - adjacentRenderSections[EDhDirection.SOUTH.ordinal() - 2] = southRenderSource; - adjacentRenderSections[EDhDirection.EAST.ordinal() - 2] = eastRenderSource; - adjacentRenderSections[EDhDirection.WEST.ordinal() - 2] = westRenderSource; + adjacentRenderSections[EDhDirection.NORTH.compassIndex] = northRenderSource; + adjacentRenderSections[EDhDirection.SOUTH.compassIndex] = southRenderSource; + adjacentRenderSections[EDhDirection.EAST.compassIndex] = eastRenderSource; + adjacentRenderSections[EDhDirection.WEST.compassIndex] = westRenderSource; boolean[] adjIsSameDetailLevel = new boolean[EDhDirection.CARDINAL_COMPASS.length]; - adjIsSameDetailLevel[EDhDirection.NORTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.NORTH); - adjIsSameDetailLevel[EDhDirection.SOUTH.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.SOUTH); - adjIsSameDetailLevel[EDhDirection.EAST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.EAST); - adjIsSameDetailLevel[EDhDirection.WEST.ordinal() - 2] = this.isAdjacentPosSameDetailLevel(EDhDirection.WEST); + adjIsSameDetailLevel[EDhDirection.NORTH.compassIndex] = this.isAdjacentPosSameDetailLevel(EDhDirection.NORTH); + adjIsSameDetailLevel[EDhDirection.SOUTH.compassIndex] = this.isAdjacentPosSameDetailLevel(EDhDirection.SOUTH); + adjIsSameDetailLevel[EDhDirection.EAST.compassIndex] = this.isAdjacentPosSameDetailLevel(EDhDirection.EAST); + adjIsSameDetailLevel[EDhDirection.WEST.compassIndex] = this.isAdjacentPosSameDetailLevel(EDhDirection.WEST); // the render sources are only needed by this synchronous method, // then they can be closed