diff --git a/core/src/main/java/com/volmit/iris/core/gui/BiomePalettePlacement.java b/core/src/main/java/com/volmit/iris/core/gui/BiomePalettePlacement.java index 8110c8b51..8b61d812c 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/BiomePalettePlacement.java +++ b/core/src/main/java/com/volmit/iris/core/gui/BiomePalettePlacement.java @@ -295,7 +295,7 @@ abstract class BiomePalettePlacement { notes.add("Where other decorators of this biome could place too, the world picks one of them at random."); } if (placesTwoBlockTallPlants()) { - notes.add("Two block tall plants show both halves squeezed into one block."); + notes.add("Two block tall plants stand up on the map: their top half covers the block above them."); } if (decorator.isStacking()) { notes.add("It stacks " + decorator.getStackMin() + "–" + decorator.getStackMax() diff --git a/core/src/main/java/com/volmit/iris/core/gui/BlockTextures.java b/core/src/main/java/com/volmit/iris/core/gui/BlockTextures.java index 79dfdd80c..9bf7212c7 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/BlockTextures.java +++ b/core/src/main/java/com/volmit/iris/core/gui/BlockTextures.java @@ -86,18 +86,21 @@ final class BlockTextures { /** * Like {@link #topTexture}, but plants keep their sprite, so single decorations on the ground stay recognisable. - * Two block tall plants show both halves squeezed into one block. + * Two block tall plants give their bottom half, the part standing on the ground. */ Optional iconTexture(BlockData block) { + BlockData standing = block instanceof Bisected ? withHalf(block, Bisected.Half.BOTTOM) : block; + return resolve(standing).map(ResolvedTexture::texture); + } + + /** + * The sprite of a two block tall plant's top half, or nothing for any other block. + */ + Optional upperHalfIcon(BlockData block) { if (!(block instanceof Bisected)) { - return resolve(block).map(ResolvedTexture::texture); + return Optional.empty(); } - Optional bottom = resolve(withHalf(block, Bisected.Half.BOTTOM)); - Optional top = resolve(withHalf(block, Bisected.Half.TOP)); - if (bottom.isEmpty() || top.isEmpty() || !bottom.get().isSideOnSprite() || !top.get().isSideOnSprite()) { - return resolve(block).map(ResolvedTexture::texture); - } - return Optional.of(BlockTexture.stacked(top.get().texture(), bottom.get().texture())); + return resolve(withHalf(block, Bisected.Half.TOP)).filter(ResolvedTexture::isSideOnSprite).map(ResolvedTexture::texture); } private static BlockData withHalf(BlockData block, Bisected.Half half) { @@ -431,32 +434,6 @@ final class BlockTextures { return new BlockTexture(size, pixels, opaque, average, biomeTint); } - /** - * The top texture above the bottom one, each at half height, or just the bottom when their sizes differ. - */ - static BlockTexture stacked(BlockTexture top, BlockTexture bottom) { - if (top.size != bottom.size) { - return bottom; - } - int size = bottom.size; - int[] pixels = new int[size * size]; - boolean[] opaque = new boolean[size * size]; - for (int z = 0; z < size; z++) { - BlockTexture half = z < size / 2 ? top : bottom; - int sourceRow = (z * 2) % size; - System.arraycopy(half.pixels, sourceRow * size, pixels, z * size, size); - System.arraycopy(half.opaque, sourceRow * size, opaque, z * size, size); - } - return new BlockTexture(size, pixels, opaque, averageOf(top.averageColor, bottom.averageColor), bottom.biomeTint); - } - - private static int averageOf(int first, int second) { - int red = (((first >>> 16) & 0xFF) + ((second >>> 16) & 0xFF)) / 2; - int green = (((first >>> 8) & 0xFF) + ((second >>> 8) & 0xFF)) / 2; - int blue = ((first & 0xFF) + (second & 0xFF)) / 2; - return red << 16 | green << 8 | blue; - } - BlockTexture flattened() { int[] flat = new int[pixels.length]; Arrays.fill(flat, averageColor); diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java index 1a61daa55..e3e9763e1 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteLayerPreview.java @@ -29,8 +29,10 @@ import com.volmit.iris.util.math.RNG; import org.bukkit.block.data.BlockData; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; final class PaletteLayerPreview { @@ -40,6 +42,7 @@ final class PaletteLayerPreview { private static final int MISSING_BLOCK_COLOR = 0x5A5F6B; private static final double FAR_FROM_WEIGHT_FACTOR = 2D; private static final int BLOCK_CACHE_SIZE = 1 << 16; + private static final int NO_OVERHANG = -1; private final BiomePalette palette; private final KList blocks; @@ -51,6 +54,8 @@ final class PaletteLayerPreview { private final int[] entryBySlot; private final BiomeTintField biomeTints; private final BlockTexture[] textureByEntry; + private final BlockTexture[] upperHalfByEntry; + private final boolean hasTallPlants; private final BlockTexture[] legendTextureByEntry; private final int[] legendColorByEntry; private final List entries; @@ -74,7 +79,9 @@ final class PaletteLayerPreview { this.biomeTints = biomeTints; this.showsGroundByEntry = resolveGroundShowingEntries(); this.textureByEntry = resolveEntryTextures(textures); - this.legendTextureByEntry = tintForLegend(textureByEntry); + this.upperHalfByEntry = resolveUpperHalves(textures); + this.hasTallPlants = Arrays.stream(upperHalfByEntry).anyMatch(Objects::nonNull); + this.legendTextureByEntry = tintForLegend(legendTextures()); this.legendColorByEntry = resolveLegendColors(); Statistics statistics = measure(); @@ -146,6 +153,10 @@ final class PaletteLayerPreview { } int blockColorAt(double noise, double worldX, double worldZ, boolean showTexturePixels) { + int overhang = showTexturePixels ? overhangColorAt(worldX, worldZ) : NO_OVERHANG; + if (overhang != NO_OVERHANG) { + return overhang; + } int entry = entryIndexForNoise(noise); if (showsGroundByEntry[entry]) { return bareColorAt(worldX, worldZ, showTexturePixels); @@ -158,18 +169,43 @@ final class PaletteLayerPreview { return groundColorAt(worldX, worldZ, true); } int untintedColor = showTexturePixels ? texture.texelAt(worldX, worldZ) : texture.averageColor(); - if (texture.biomeTint() == null) { - return untintedColor; - } - int tint = biomeTints.colorAt(texture.biomeTint(), (int) Math.floor(worldX), (int) Math.floor(worldZ)); - return BlockTexture.multiply(untintedColor, tint); + return tinted(texture, untintedColor, worldX, worldZ); } /** * The colour where the palette places nothing: the ground under a decorator, or plain dark otherwise. */ int bareColorAt(double worldX, double worldZ, boolean showTexturePixels) { - return ground == null ? PaletteLayerScale.ABSENT_COLOR : groundColorAt(worldX, worldZ, showTexturePixels); + if (ground == null) { + return PaletteLayerScale.ABSENT_COLOR; + } + int overhang = showTexturePixels ? overhangColorAt(worldX, worldZ) : NO_OVERHANG; + return overhang != NO_OVERHANG ? overhang : groundColorAt(worldX, worldZ, showTexturePixels); + } + + // A two block tall plant stands up on the map: its top half covers the block above it, which is one block north. + private int overhangColorAt(double worldX, double worldZ) { + if (!hasTallPlants) { + return NO_OVERHANG; + } + double plantZ = worldZ + 1D; + double noise = sample(worldX, plantZ); + if (Double.isNaN(noise)) { + return NO_OVERHANG; + } + BlockTexture upperHalf = upperHalfByEntry[entryIndexForNoise(noise)]; + if (upperHalf == null || !upperHalf.isOpaqueAt(worldX, worldZ)) { + return NO_OVERHANG; + } + return tinted(upperHalf, upperHalf.texelAt(worldX, worldZ), worldX, plantZ); + } + + private int tinted(BlockTexture texture, int untintedColor, double worldX, double worldZ) { + if (texture.biomeTint() == null) { + return untintedColor; + } + int tint = biomeTints.colorAt(texture.biomeTint(), (int) Math.floor(worldX), (int) Math.floor(worldZ)); + return BlockTexture.multiply(untintedColor, tint); } private int groundColorAt(double worldX, double worldZ, boolean showTexturePixels) { @@ -259,6 +295,29 @@ final class PaletteLayerPreview { return resolved; } + private BlockTexture[] resolveUpperHalves(BlockTextures textures) { + BlockTexture[] resolved = new BlockTexture[blocks.size()]; + if (textures == null || palette.kind() != BiomePalette.Kind.DECORATOR) { + return resolved; + } + for (int entry = 0; entry < blocks.size(); entry++) { + BlockData block = blocks.get(entry).getBlockData(data); + resolved[entry] = block == null ? null : textures.upperHalfIcon(block).orElse(null); + } + return resolved; + } + + // Tall plants are known by their top half, the way Minecraft draws them in the inventory. + private BlockTexture[] legendTextures() { + BlockTexture[] legend = textureByEntry.clone(); + for (int entry = 0; entry < legend.length; entry++) { + if (upperHalfByEntry[entry] != null) { + legend[entry] = upperHalfByEntry[entry]; + } + } + return legend; + } + private Optional textureOf(BlockData block, BlockTextures textures) { return palette.kind() == BiomePalette.Kind.DECORATOR ? textures.iconTexture(block) : textures.topTexture(block); }