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 f859bbd4b..7b2037f35 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 @@ -19,23 +19,45 @@ package com.seibel.distanthorizons.core.dataObjects.render.bufferBuilding; -import com.seibel.distanthorizons.api.enums.rendering.EDhApiBlockMaterial; import com.seibel.distanthorizons.core.dependencyInjection.SingletonInjector; import com.seibel.distanthorizons.core.enums.EDhDirection; 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.wrapperInterfaces.block.IBlockStateWrapper; 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 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. + */ + private static final byte SKYLIGHT_COVERED = -2; + 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; + }); + + + + //=========// + // builder // + //=========// public static void addBoxQuadsToBuilder( LodQuadBuilder builder, @@ -112,7 +134,6 @@ public class ColumnBox // NORTH face { ColumnArrayView adjCol = adjData[EDhDirection.NORTH.ordinal() - 2]; // TODO can we use something other than ordinal-2? - int adjOverlapNorth = ColorUtil.INVISIBLE; // can be set to a non-invisible color for debugging overlapping quads for a specific face if (adjCol == null) { // add an adjacent face if this is opaque face or transparent over the void @@ -124,15 +145,13 @@ public class ColumnBox else { makeAdjVerticalQuad(builder, adjCol, EDhDirection.NORTH, x, minY, z, xSize, ySize, - color, adjOverlapNorth, irisBlockMaterialId, skyLightTop, blockLight, - topData, bottomData); + color, irisBlockMaterialId, blockLight); } } // SOUTH face { ColumnArrayView adjCol = adjData[EDhDirection.SOUTH.ordinal() - 2]; - int adjOverlapSouth = ColorUtil.INVISIBLE; if (adjCol == null) { if (!isTransparent || overVoid) @@ -143,15 +162,13 @@ public class ColumnBox else { makeAdjVerticalQuad(builder, adjCol, EDhDirection.SOUTH, x, minY, maxZ, xSize, ySize, - color, adjOverlapSouth, irisBlockMaterialId, skyLightTop, blockLight, - topData, bottomData); + color, irisBlockMaterialId, blockLight); } } // WEST face { ColumnArrayView adjCol = adjData[EDhDirection.WEST.ordinal() - 2]; - int adjOverlapWest = ColorUtil.INVISIBLE; if (adjCol == null) { if (!isTransparent || overVoid) @@ -162,15 +179,13 @@ public class ColumnBox else { makeAdjVerticalQuad(builder, adjCol, EDhDirection.WEST, x, minY, z, zSize, ySize, - color, adjOverlapWest, irisBlockMaterialId, skyLightTop, blockLight, - topData, bottomData); + color, irisBlockMaterialId, blockLight); } } // EAST face { ColumnArrayView adjCol = adjData[EDhDirection.EAST.ordinal() - 2]; - int adjOverlapEast = ColorUtil.INVISIBLE; if (adjCol == null) { if (!isTransparent || overVoid) @@ -181,306 +196,196 @@ public class ColumnBox else { makeAdjVerticalQuad(builder, adjCol, EDhDirection.EAST, maxX, minY, z, zSize, ySize, - color, adjOverlapEast, irisBlockMaterialId, skyLightTop, blockLight, - topData, bottomData); + color, irisBlockMaterialId, blockLight); } } } - /** the overlap color can be used to see faces that shouldn't be rendered */ private static void makeAdjVerticalQuad( LodQuadBuilder builder, ColumnArrayView adjColumnView, EDhDirection direction, short x, short yMin, short z, short horizontalWidth, short ySize, - int color, int debugOverlapColor, byte irisBlockMaterialId, byte skyLightTop, byte blockLight, - long topData, long bottomData) + int color, byte irisBlockMaterialId, byte blockLight) { + //==================// + // create face with // + // no adjacent data // + //==================// + 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 == null || adjColumnView.size == 0 || RenderDataPointUtil.isVoid(adjColumnView.get(0))) { - // there isn't any data adjacent to this LOD, add the vertical quad + builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, blockLight); return; } - int yMax = yMin + ySize; - int adjIndex; - boolean firstFace = true; - boolean inputAboveAdjLods = true; - short previousAdjDepth = -1; - byte nextTopSkyLight = skyLightTop; - boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.transparencyEnabled; - boolean lastAdjWasTransparent = false; + //===========================// + // Determine face visibility // + // based on it's neighbors // + //===========================// + short yMax = (short) (yMin + ySize); // min is inclusive, max is exclusive + byte[] skyLightAtInputPos = THREAD_LOCAL_SKY_LIGHT_ARRAY.get(); - - if (!RenderDataPointUtil.doesDataPointExist(bottomData)) + try { - // there isn't anything under this LOD, - // to prevent seeing through the world, make it opaque - color = ColorUtil.setAlpha(color, 255); - } - - - // Add adjacent faces if this LOD is surrounded by transparent LODs - // (prevents invisible sides underwater) - int adjCount = adjColumnView.size(); - for (adjIndex = 0; // iterates top down - adjIndex < adjCount - && RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex)) - && !RenderDataPointUtil.isVoid(adjColumnView.get(adjIndex)); - adjIndex++) - { - long adjPoint = adjColumnView.get(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); - // if the adjacent data point is over the void - // don't consider it as transparent - // FIXME this transparency change should be applied before this point since this could affect other areas - boolean adjOverVoid = false; - if (adjIndex > 0) + // iterate top down + int adjCount = adjColumnView.size(); + for (int adjIndex = 0; adjIndex < adjCount; adjIndex++) { - long adjBellowPoint = adjColumnView.get(adjIndex-1); - adjOverVoid = !RenderDataPointUtil.doesDataPointExist(adjBellowPoint); - } - boolean adjTransparent = !adjOverVoid && RenderDataPointUtil.getAlpha(adjPoint) < 255 && LodRenderer.transparencyEnabled; - - - // continue if this data point is transparent or the adjacent point is not - if (inputTransparent || !adjTransparent) // TODO inputIsTransparent may be unnecessary - { - short adjYMin = RenderDataPointUtil.getYMin(adjPoint); - short adjYMax = RenderDataPointUtil.getYMax(adjPoint); + long adjPoint = adjColumnView.get(adjIndex); + short adjMinY = RenderDataPointUtil.getYMin(adjPoint); + short adjMaxY = RenderDataPointUtil.getYMax(adjPoint); - - // if fake transparency is enabled, allow for 1 block of transparency, - // everything under that should be opaque - if (LodRenderer.transparencyEnabled && LodRenderer.fakeOceanFloor) + // skip empty adjacent datapoints + if (!RenderDataPointUtil.doesDataPointExist(adjPoint) + || RenderDataPointUtil.isVoid(adjPoint)) { - if (lastAdjWasTransparent && !adjTransparent) - { - adjYMax = (short) (RenderDataPointUtil.getYMax(adjColumnView.get(adjIndex - 1)) - 1); - } - else if (adjTransparent && (adjIndex + 1) < adjCount) - { - if (RenderDataPointUtil.getAlpha(adjColumnView.get(adjIndex + 1)) == 255) - { - adjYMin = (short) (adjYMax - 1); - } - } - } - - - if (yMax <= adjYMin) - { - // the adjacent LOD is above the input LOD and won't affect its rendering, - // skip to the next adjacent continue; } - inputAboveAdjLods = false; - - if (adjYMax < yMin) + // skip this adjacent datapoint if it's above the input datapoint (since it can't affect the input data point) + if (yMax <= adjMinY) { - // the adjacent LOD is below the input LOD - - // getting the skylight is more complicated - // since LODs can be adjacent to water, which changes how skylight works - byte skyLight; - if (adjIndex == 0) - { - // this adj LOD is at the highest position, - // its sky lighting won't be affected by anything above it - skyLight = RenderDataPointUtil.getLightSky(adjPoint); - } - else - { - // TODO improve the comments here, this is a bit confusing - long aboveAdjPoint = adjColumnView.get(adjIndex - 1); - if (RenderDataPointUtil.getAlpha(aboveAdjPoint) != 255) - { - // above adjacent LOD is transparent... - - boolean inputMaxHigherThanAboveAdj = yMax > RenderDataPointUtil.getYMax(aboveAdjPoint); - if (inputMaxHigherThanAboveAdj) - { - // ...and higher than the input yMax, - // use its sky light - skyLight = RenderDataPointUtil.getLightSky(aboveAdjPoint); - } - else - { - // ...and at or below the input yMax, - skyLight = RenderDataPointUtil.getLightSky(adjPoint); - } - } - else - { - // LOD above adjacent is opaque, use the adj LOD's skylight - skyLight = RenderDataPointUtil.getLightSky(adjPoint); - } - } - - - if (firstFace) - { - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLight, blockLight); - } - else - { - // Now: adjMaxHeight < y < previousAdjDepth < yMax - if (previousAdjDepth == -1) - { - // TODO why is this an error? - throw new RuntimeException("Loop error"); - } - - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, skyLight, blockLight); - - previousAdjDepth = -1; - } - - - // TODO why break here? - break; + continue; } - if (adjYMin <= yMin) + 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 && LodRenderer.transparencyEnabled; + + + + //=================================// + // set sky light based on adjacent // + //=================================// + + // set light based on overlapping adjacent + if (!adjTransparent) { - // the adjacent LOD's base is at or below the input's base - - if (yMax <= adjYMax) + // adj opaque + // mark positions adjacent is covering + for (int i = adjMinY; i < adjMaxY; i++) { - // The input face is completely inside the adj's face, don't render it - if (debugOverlapColor != ColorUtil.INVISIBLE) - { - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT); - } + byte skyLightAtPos = skyLightAtInputPos[i]; + skyLightAtInputPos[i] = (byte) Math.min(SKYLIGHT_COVERED, skyLightAtPos); } - else - { - // the adj data intersects the lower part of the input data, don't render below the intersection - - if (adjYMax > yMin && debugOverlapColor != ColorUtil.INVISIBLE) - { - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (adjYMax - yMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT); - } - - // if this is the only face, use the yMax and break, - // if there was another face finish the last one and then break - if (firstFace) - { - // TODO sections next to transparent (water) need to be split up - // everything works correctly with opaque water - - builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId, - RenderDataPointUtil.getLightSky(adjPoint), blockLight); - } - else - { - // Now: depth <= y <= height <= previousAdjDepth < yMax - if (previousAdjDepth == -1) - { - // TODO why is this an error? - throw new RuntimeException("Loop error"); - } - - if (previousAdjDepth > adjYMax) - { - builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId, - RenderDataPointUtil.getLightSky(adjPoint), blockLight); - } - previousAdjDepth = -1; - } - } - - - // we don't need to check any other adjacent LODs - // since this one completely covers the input - break; - } - - - - // In here always true: y < adjYMin < yMax - // _________________&&: y < ________ (height and yMax) - - if (adjYMax >= yMax) - { - // Basically: y _______ < yMax <= height - // _______&&: y < depth < yMax - // the adj data intersects the higher part of the current data - if (debugOverlapColor != ColorUtil.INVISIBLE) - { - builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (yMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT); - } - - // we start the creation of a new face } else { - // Otherwise: y < _____ height < yMax - // _______&&: y < depth ______ < yMax - if (debugOverlapColor != ColorUtil.INVISIBLE) + // adjacent is transparent, + // use datapoint below adjacent for lighting + byte belowSkyLight = RenderDataPointUtil.getLightSky(adjBelowPoint); + for (int i = adjMinY; i < adjMaxY; i++) { - builder.addQuadAdj(direction, x, adjYMin, z, horizontalWidth, (short) (adjYMax - adjYMin), debugOverlapColor, irisBlockMaterialId, LodUtil.MAX_MC_LIGHT, LodUtil.MAX_MC_LIGHT); - } - - if (firstFace) - { - builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (yMax - adjYMax), color, irisBlockMaterialId, - RenderDataPointUtil.getLightSky(adjPoint), blockLight); - } - else - { - // Now: y < depth < height <= previousAdjDepth < yMax - if (previousAdjDepth == -1) - throw new RuntimeException("Loop error"); - if (previousAdjDepth > adjYMax) - { - if (irisBlockMaterialId == EDhApiBlockMaterial.GRASS.index) - { - // this LOD is underneath another, grass will never show here - irisBlockMaterialId = EDhApiBlockMaterial.DIRT.index; - } - - builder.addQuadAdj(direction, x, adjYMax, z, horizontalWidth, (short) (previousAdjDepth - adjYMax), color, irisBlockMaterialId, - RenderDataPointUtil.getLightSky(adjPoint), blockLight); - } - previousAdjDepth = -1; + byte skyLightAtPos = skyLightAtInputPos[i]; + skyLightAtInputPos[i] = (byte) Math.min(belowSkyLight, skyLightAtPos); } } - // set next top as current depth - previousAdjDepth = adjYMin; - firstFace = false; - nextTopSkyLight = skyLightTop; - - if (adjIndex + 1 < adjColumnView.size() && RenderDataPointUtil.doesDataPointExist(adjColumnView.get(adjIndex + 1))) + // 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++) { - nextTopSkyLight = RenderDataPointUtil.getLightSky(adjColumnView.get(adjIndex + 1)); + byte skyLightAtPos = skyLightAtInputPos[i]; + skyLightAtInputPos[i] = (byte) Math.min(adjSkyLight, skyLightAtPos); + } + } + + + + //=======================// + // create vertical faces // + //=======================// + + boolean inputTransparent = ColorUtil.getAlpha(color) < 255 && LodRenderer.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++) + { + 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 + ); + + lastSkyLight = skyLight; + quadBottomY = i; } - lastAdjWasTransparent = adjTransparent; + quadTopY = (i + 1); + } + + // add the in-progress face if present + if (quadTopY != -1) + { + tryAddVerticalFaceWithSkyLightToBuilder( + builder, direction, + x, z, horizontalWidth, + color, irisBlockMaterialId, blockLight, + lastSkyLight, inputTransparent, quadTopY, quadBottomY + ); } } - - - - if (inputAboveAdjLods) + finally { - // the input LOD is above all adjacent LODs and won't be affected - // by them, add the vertical quad using the input's lighting and height - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, ySize, color, irisBlockMaterialId, skyLightTop, blockLight); + // 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); } - else if (previousAdjDepth != -1) + } + private static void tryAddVerticalFaceWithSkyLightToBuilder( + LodQuadBuilder builder, EDhDirection direction, + short x, short z, short horizontalWidth, + int color, byte irisBlockMaterialId, byte blockLight, + byte lastSkyLight, boolean inputTransparent, int quadTopY, int quadBottomY + ) + { + // invalid positions will have a negative skylight + if (lastSkyLight >= 0) { - // We need to finish the last quad. - builder.addQuadAdj(direction, x, yMin, z, horizontalWidth, (short) (previousAdjDepth - yMin), color, irisBlockMaterialId, nextTopSkyLight, blockLight); + // 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); + } + } } } + + }