diff --git a/core/src/main/java/com/volmit/iris/core/gui/BiomePalette.java b/core/src/main/java/com/volmit/iris/core/gui/BiomePalette.java index 326f490c3..8c0b95929 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/BiomePalette.java +++ b/core/src/main/java/com/volmit/iris/core/gui/BiomePalette.java @@ -20,31 +20,40 @@ package com.volmit.iris.core.gui; import com.volmit.iris.engine.object.IrisBiome; import com.volmit.iris.engine.object.IrisBiomePaletteLayer; +import com.volmit.iris.engine.object.IrisBlockData; +import com.volmit.iris.engine.object.IrisDecorator; +import com.volmit.iris.util.collection.KList; import java.util.ArrayList; import java.util.List; import java.util.Locale; /** - * One of the block palettes a biome places: a surface, sea or cave ceiling layer, or its wall or slab blocks. + * One of the block palettes a biome places: a surface, sea or cave ceiling layer, its wall or slab blocks, or a + * decorator. */ record BiomePalette(String biomeKey, Kind kind, int index) { private static final String ID_PREFIX = "palette:"; private static final char ID_SEPARATOR = '#'; enum Kind { - LAYER("Layer", true), - SEA_LAYER("Sea layer", true), - CAVE_CEILING_LAYER("Cave ceiling layer", true), - WALL("Wall", false), - SLAB("Slab", false); + LAYER("Layer", "layers", true, true), + SEA_LAYER("Sea layer", "seaLayers", true, true), + CAVE_CEILING_LAYER("Cave ceiling layer", "caveCeilingLayers", true, true), + WALL("Wall", "wall", false, false), + SLAB("Slab", "slab", false, false), + DECORATOR("Decorator", "decorators", false, true); private final String label; + private final String jsonKey; private final boolean stacked; + private final boolean numbered; - Kind(String label, boolean stacked) { + Kind(String label, String jsonKey, boolean stacked, boolean numbered) { this.label = label; + this.jsonKey = jsonKey; this.stacked = stacked; + this.numbered = numbered; } /** @@ -54,24 +63,42 @@ record BiomePalette(String biomeKey, Kind kind, int index) { return stacked; } + /** + * The biome file's key holding these palettes: a list of them, or a single one for walls and slabs. + */ + String jsonKey() { + return jsonKey; + } + // A biome without wall or slab blocks still has those palettes, just empty. - List palettesOf(IrisBiome biome) { + List layersOf(IrisBiome biome) { return switch (this) { case LAYER -> biome.getLayers(); case SEA_LAYER -> biome.getSeaLayers(); case CAVE_CEILING_LAYER -> biome.getCaveCeilingLayers(); case WALL -> biome.getWall().getPalette().isEmpty() ? List.of() : List.of(biome.getWall()); case SLAB -> biome.getSlab().getPalette().isEmpty() ? List.of() : List.of(biome.getSlab()); + case DECORATOR -> List.of(); }; } + + // A decorator snippet that couldn't be found loads as an empty entry, which places nothing. + List> blockListsOf(IrisBiome biome) { + if (this != DECORATOR) { + return layersOf(biome).stream().map(IrisBiomePaletteLayer::getPalette).toList(); + } + return biome.getDecorators().stream() + .map(decorator -> decorator == null ? new KList() : decorator.getPalette()) + .toList(); + } } static List allOf(String biomeKey, IrisBiome biome) { List palettes = new ArrayList<>(); for (Kind kind : Kind.values()) { - List layers = kind.palettesOf(biome); - for (int index = 0; index < layers.size(); index++) { - if (!layers.get(index).getPalette().isEmpty()) { + List> blockLists = kind.blockListsOf(biome); + for (int index = 0; index < blockLists.size(); index++) { + if (!blockLists.get(index).isEmpty()) { palettes.add(new BiomePalette(biomeKey, kind, index)); } } @@ -104,7 +131,7 @@ record BiomePalette(String biomeKey, Kind kind, int index) { } String label() { - return kind.isStacked() ? kind.label + " " + index : kind.label; + return kind.numbered ? kind.label + " " + index : kind.label; } String lowercaseLabel() { @@ -112,10 +139,18 @@ record BiomePalette(String biomeKey, Kind kind, int index) { } /** - * The palette in the given copy of the biome, or null when a hotload removed it. + * The palette's blocks in the given copy of the biome, or null when a hotload removed it. */ - IrisBiomePaletteLayer resolve(IrisBiome biome) { - List layers = kind.palettesOf(biome); - return index < layers.size() ? layers.get(index) : null; + KList blocksIn(IrisBiome biome) { + List> blockLists = kind.blockListsOf(biome); + return index < blockLists.size() ? blockLists.get(index) : null; + } + + IrisBiomePaletteLayer layerIn(IrisBiome biome) { + return kind.layersOf(biome).get(index); + } + + IrisDecorator decoratorIn(IrisBiome biome) { + return biome.getDecorators().get(index); } } 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 c18e34336..e6f932e8a 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 @@ -19,42 +19,49 @@ package com.volmit.iris.core.gui; import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.engine.decorator.IrisEngineDecorator; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; import com.volmit.iris.engine.object.IrisBiomePaletteLayer; +import com.volmit.iris.engine.object.IrisBlockData; +import com.volmit.iris.engine.object.IrisDecorationPart; +import com.volmit.iris.engine.object.IrisDecorator; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.noise.CNG; +import java.util.ArrayList; +import java.util.List; + /** * How thick one biome palette is at a spot and which noise picks its block there, sampled exactly the way the * generator does for that kind of palette. The generator seeds each palette's noise from the first call it makes, - * so these use the same random sources the terrain, carving and post passes use. + * so these use the same random sources the terrain, carving, post and decorator passes use. */ abstract class BiomePalettePlacement { - protected final IrisBiomePaletteLayer layer; protected final IrisData data; - private BiomePalettePlacement(IrisBiomePaletteLayer layer, IrisData data) { - this.layer = layer; + private BiomePalettePlacement(IrisData data) { this.data = data; } - static BiomePalettePlacement of(Engine engine, IrisBiome biome, BiomePalette palette, IrisBiomePaletteLayer layer) { + static BiomePalettePlacement of(Engine engine, IrisBiome biome, BiomePalette palette) { IrisData data = engine.getData(); RNG terrainRandom = new RNG(engine.getSeedManager().getTerrain()); RNG carveRandom = new RNG(engine.getSeedManager().getCarve()); RNG postRandom = new RNG(engine.getSeedManager().getPost()); int seaLevel = seaLevel(engine.getDimension()); return switch (palette.kind()) { - case LAYER -> new SurfaceLayer(biome, palette.index(), layer, terrainRandom, data); - case SEA_LAYER -> new LegacyStackedLayer(layer, data, terrainRandom.nextParallelRNG(palette.index()), + case LAYER -> new SurfaceLayer(biome, palette.index(), palette.layerIn(biome), terrainRandom, data); + case SEA_LAYER -> new LegacyStackedLayer(palette.layerIn(biome), data, terrainRandom.nextParallelRNG(palette.index()), biome.getLayerSeaHeightGenerators(terrainRandom, data), palette.index()); - case CAVE_CEILING_LAYER -> new LegacyStackedLayer(layer, data, carveRandom.nextParallelRNG(palette.index()), + case CAVE_CEILING_LAYER -> new LegacyStackedLayer(palette.layerIn(biome), data, carveRandom.nextParallelRNG(palette.index()), biome.getLayerHeightGenerators(terrainRandom, data), palette.index()); - case WALL -> new SingleBlocks(layer, data, carveRandom, seaLevel); - case SLAB -> new SingleBlocks(layer, data, postRandom, seaLevel); + case WALL -> new SingleBlocks(palette.layerIn(biome), data, carveRandom, seaLevel); + case SLAB -> new SingleBlocks(palette.layerIn(biome), data, postRandom, seaLevel); + case DECORATOR -> new Decorations(biome, palette.decoratorIn(biome), engine.getSeedManager().getComponent(), + seaLevel, data); }; } @@ -62,6 +69,20 @@ abstract class BiomePalettePlacement { return dimension == null ? 0 : dimension.getFluidHeight(); } + abstract KList blocks(); + + abstract double zoom(); + + int minThickness() { + return 1; + } + + int maxThickness() { + return 1; + } + + abstract boolean hasSlopeCondition(); + /** * How many blocks of this palette are stacked at the spot. */ @@ -76,7 +97,41 @@ abstract class BiomePalettePlacement { return null; } - private static final class SurfaceLayer extends BiomePalettePlacement { + private abstract static class LayerPlacement extends BiomePalettePlacement { + protected final IrisBiomePaletteLayer layer; + + private LayerPlacement(IrisBiomePaletteLayer layer, IrisData data) { + super(data); + this.layer = layer; + } + + @Override + KList blocks() { + return layer.getPalette(); + } + + @Override + double zoom() { + return layer.getZoom(); + } + + @Override + int minThickness() { + return layer.getMinHeight(); + } + + @Override + int maxThickness() { + return layer.getMaxHeight(); + } + + @Override + boolean hasSlopeCondition() { + return !layer.getSlopeCondition().isDefault(); + } + } + + private static final class SurfaceLayer extends LayerPlacement { private final IrisBiome biome; private final int index; private final RNG random; @@ -103,7 +158,7 @@ abstract class BiomePalettePlacement { * Sea and cave ceiling layers go through the older sampling path, which divides by the zoom twice and borrows * its thickness noise from another list: a cave ceiling layer uses the surface layer at the same position's. */ - private static final class LegacyStackedLayer extends BiomePalettePlacement { + private static final class LegacyStackedLayer extends LayerPlacement { private final RNG random; private final CNG thicknessNoise; @@ -141,7 +196,7 @@ abstract class BiomePalettePlacement { /** * Wall and slab blocks are picked one at a time with noise that also changes with height. */ - private static final class SingleBlocks extends BiomePalettePlacement { + private static final class SingleBlocks extends LayerPlacement { private final RNG random; private final int sampleHeight; @@ -151,6 +206,16 @@ abstract class BiomePalettePlacement { this.sampleHeight = sampleHeight; } + @Override + int minThickness() { + return 1; + } + + @Override + int maxThickness() { + return 1; + } + @Override int thickness(int blockX, int blockZ) { return 1; @@ -167,4 +232,85 @@ abstract class BiomePalettePlacement { return "These blocks also change with height; the preview shows them at sea level (Y " + sampleHeight + ")."; } } + + /** + * A decorator places where its style noise stays under its chance, and its variance noise picks the block there. + * The generator seeds the variance noise from whichever block it decorates first, so the preview reuses that noise + * once the world has generated some decorations. + */ + private static final class Decorations extends BiomePalettePlacement { + private final IrisDecorator decorator; + private final CNG placementNoise; + private final CNG blockNoise; + private final int sampleHeight; + private final boolean sharesSpotsWithOtherDecorators; + + private Decorations(IrisBiome biome, IrisDecorator decorator, long componentSeed, int sampleHeight, IrisData data) { + super(data); + this.decorator = decorator; + RNG partRandom = new RNG(IrisEngineDecorator.partSeed(componentSeed, decorator.getPartOf())); + this.placementNoise = decorator.getGenerator(partRandom, data); + this.blockNoise = decorator.getVarianceGenerator(partRandom, data); + this.sampleHeight = sampleHeight; + this.sharesSpotsWithOtherDecorators = biome.getDecorators().stream() + .anyMatch(other -> other != null && other != decorator && other.getPartOf() == decorator.getPartOf()); + } + + @Override + KList blocks() { + return decorator.getPalette(); + } + + @Override + double zoom() { + return decorator.getStyle().getZoom(); + } + + @Override + boolean hasSlopeCondition() { + return !decorator.getSlopeCondition().isDefault(); + } + + @Override + int thickness(int blockX, int blockZ) { + double zoom = zoom(); + return placementNoise.fitDouble(0D, 1D, blockX / zoom, blockZ / zoom) <= decorator.getChance() ? 1 : 0; + } + + // The generator swaps X and Z when it picks the block. + @Override + double noise(int blockX, int blockZ, int depth) { + return blockNoise.noise(blockZ, sampleHeight, blockX); + } + + @Override + String simplification() { + List notes = new ArrayList<>(); + String partNote = partNote(decorator.getPartOf()); + if (partNote != null) { + notes.add(partNote); + } + if (sharesSpotsWithOtherDecorators) { + notes.add("Where other decorators of this biome could place too, the world picks one of them at random."); + } + if (decorator.isStacking()) { + notes.add("It stacks " + decorator.getStackMin() + "–" + decorator.getStackMax() + + " blocks high; the preview shows the bottom block."); + } + if (blocks().size() > 1) { + notes.add("The block also changes with height; the preview picks it at sea level (Y " + sampleHeight + ")."); + } + return notes.isEmpty() ? null : String.join(" ", notes); + } + + private static String partNote(IrisDecorationPart part) { + return switch (part) { + case NONE -> null; + case SHORE_LINE -> "It only places along shore lines."; + case SEA_SURFACE -> "It only places on the sea surface."; + case SEA_FLOOR -> "It only places on the sea floor."; + case CEILING -> "It places on cave ceilings and the undersides of overhangs, not on the ground."; + }; + } + } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java index 3925308bb..851acc6d7 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java +++ b/core/src/main/java/com/volmit/iris/core/gui/NoiseExplorerGUI.java @@ -613,10 +613,11 @@ public final class NoiseExplorerGUI extends JPanel { if (biome == null) { continue; } - for (BiomePalette palette : BiomePalette.allOf(key, biome)) { + for (PaletteCatalog.PaletteSummary summary : PaletteCatalog.summarizePalettes(key, biome)) { + BiomePalette palette = summary.palette(); sources.add(new SourceItem( palette.id(), - palette.label() + " · " + PaletteCatalog.summarizeBlocks(palette.resolve(biome)), + summary.text(), key, NoiseCategory.PACK_PALETTES, false, diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java index 088ee46ad..62ded2dbe 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteCatalog.java @@ -18,12 +18,21 @@ package com.volmit.iris.core.gui; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisBiomePaletteLayer; +import com.volmit.iris.engine.object.IrisBlockData; import com.volmit.iris.engine.object.IrisRegion; +import com.volmit.iris.util.collection.KList; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -73,8 +82,8 @@ record PaletteCatalog(List regions, List biomesOutsideRegio return regions.stream().filter(region -> region.key().equals(key)).findFirst().orElse(null); } - static String summarizeBlocks(IrisBiomePaletteLayer layer) { - List names = layer.getPalette().stream() + static String summarizeBlocks(KList blocks) { + List names = blocks.stream() .map(block -> block.getBlock().replace("minecraft:", "").toLowerCase(Locale.ROOT)) .distinct() .toList(); @@ -117,12 +126,40 @@ record PaletteCatalog(List regions, List biomesOutsideRegio return parents; } - private static List summarizePalettes(String key, IrisBiome biome) { + static List summarizePalettes(String key, IrisBiome biome) { + JsonObject biomeSource = readSource(biome.getLoadFile()); return BiomePalette.allOf(key, biome).stream() - .map(palette -> new PaletteSummary(palette, summarizeBlocks(palette.resolve(biome)))) + .map(palette -> new PaletteSummary(palette, snippetName(biomeSource, palette), + summarizeBlocks(palette.blocksIn(biome)))) .toList(); } + // Snippets are copied into the biome when it loads, so only its file still tells which palettes came from one. + private static String snippetName(JsonObject biomeSource, BiomePalette palette) { + JsonElement element = biomeSource == null ? null : biomeSource.get(palette.kind().jsonKey()); + if (element instanceof JsonArray list) { + element = palette.index() < list.size() ? list.get(palette.index()) : null; + } + if (element == null || !element.isJsonPrimitive()) { + return null; + } + String reference = element.getAsString(); + return reference.substring(reference.lastIndexOf('/') + 1); + } + + private static JsonObject readSource(File file) { + if (file == null || !file.isFile()) { + return null; + } + try (JsonReader reader = new JsonReader(new FileReader(file))) { + reader.setLenient(true); + JsonElement source = JsonParser.parseReader(reader); + return source.isJsonObject() ? source.getAsJsonObject() : null; + } catch (IOException | RuntimeException e) { + return null; + } + } + private static RegionEntry describeRegion(String key, IrisRegion region, Map biomes) { List groups = new ArrayList<>(); addGroup(groups, "Land", region.getLandBiomes(), biomes); @@ -173,9 +210,14 @@ record PaletteCatalog(List regions, List biomesOutsideRegio } } - record PaletteSummary(BiomePalette palette, String blocks) { + /** + * @param snippet the name of the snippet file the palette comes from, or null when the biome defines it itself + */ + record PaletteSummary(BiomePalette palette, String snippet, String blocks) { String text() { - return palette.label() + " · " + blocks; + return snippet == null + ? palette.label() + " · " + blocks + : palette.label() + " · " + snippet + " snippet · " + blocks; } } } diff --git a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java index 1c538ef9b..823bb3c40 100644 --- a/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java +++ b/core/src/main/java/com/volmit/iris/core/gui/PaletteInspectorPanel.java @@ -409,8 +409,7 @@ final class PaletteInspectorPanel extends JPanel { PaletteLayerPreview.STATISTICS_GRID_SIZE, PaletteLayerPreview.STATISTICS_GRID_SIZE, PaletteLayerPreview.statisticsAreaBlocks())); if (preview.absentShare() > 0D) { - notes.add("The layer doesn't reach this depth on " + percent(preview.absentShare()) - + " of the ground. That ground is dark on the map and left out of the shares."); + notes.add(absentText(preview) + " That ground is dark on the map and left out of the shares."); } if (preview.outOfRangeShare() > 0D) { notes.add(percent(preview.outOfRangeShare()) + " of the noise values fall outside 0 to 1." @@ -420,11 +419,26 @@ final class PaletteInspectorPanel extends JPanel { notes.add(preview.simplification()); } if (preview.hasSlopeCondition()) { - notes.add("This layer has a slope condition, which the preview ignores."); + notes.add("This " + paletteNoun(preview) + " has a slope condition, which the preview ignores."); } return notes; } + private static String absentText(PaletteLayerPreview preview) { + if (isDecorator(preview)) { + return "The decorator leaves " + percent(preview.absentShare()) + " of the ground bare."; + } + return "The layer doesn't reach this depth on " + percent(preview.absentShare()) + " of the ground."; + } + + private static String paletteNoun(PaletteLayerPreview preview) { + return isDecorator(preview) ? "decorator" : "layer"; + } + + private static boolean isDecorator(PaletteLayerPreview preview) { + return preview.palette().kind() == BiomePalette.Kind.DECORATOR; + } + private static String actualText(PaletteLayerPreview.Entry entry) { if (entry.isFarBelowWeight()) { return "▼ " + percent(entry.actualShare()); 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 a4e6a24ca..ad206e41f 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 @@ -41,7 +41,7 @@ final class PaletteLayerPreview { private static final int BLOCK_CACHE_SIZE = 1 << 16; private final BiomePalette palette; - private final IrisBiomePaletteLayer paletteLayer; + private final KList blocks; private final BiomePalettePlacement placement; private final int depth; private final IrisData data; @@ -56,14 +56,14 @@ final class PaletteLayerPreview { private final double absentShare; private final double outOfRangeShare; - private PaletteLayerPreview(BiomePalette palette, IrisBiomePaletteLayer paletteLayer, BiomePalettePlacement placement, - int depth, IrisData data, BlockTextures textures, BiomeTintField biomeTints) { + private PaletteLayerPreview(BiomePalette palette, BiomePalettePlacement placement, int depth, IrisData data, + BlockTextures textures, BiomeTintField biomeTints) { this.palette = palette; - this.paletteLayer = paletteLayer; + this.blocks = placement.blocks(); this.placement = placement; this.depth = depth; this.data = data; - this.entryBySlot = paletteLayer.getPaletteEntryPerSlot(data).stream().mapToInt(Integer::intValue).toArray(); + this.entryBySlot = IrisBiomePaletteLayer.paletteEntryPerSlot(blocks, data).stream().mapToInt(Integer::intValue).toArray(); if (entryBySlot.length == 0) { throw new IllegalStateException("No block of " + palette.biomeKey() + " " + palette.lowercaseLabel() + " could be found"); } @@ -85,15 +85,14 @@ final class PaletteLayerPreview { } IrisData data = engine.getData(); IrisBiome biome = data.getBiomeLoader().load(palette.biomeKey()); - IrisBiomePaletteLayer paletteLayer = biome == null ? null : palette.resolve(biome); - if (paletteLayer == null) { + if (biome == null || palette.blocksIn(biome) == null) { throw new IllegalStateException("Palette removed during hotload: " + palette.biomeKey() + " " + palette.lowercaseLabel()); } - BiomePalettePlacement placement = BiomePalettePlacement.of(engine, biome, palette, paletteLayer); + BiomePalettePlacement placement = BiomePalettePlacement.of(engine, biome, palette); BiomeTintField biomeTints = textures == null ? null : new BiomeTintField(biome, new RNG(engine.getSeedManager().getBiome()), textures.biomeColors()); - return new PaletteLayerPreview(palette, paletteLayer, placement, depth, data, textures, biomeTints); + return new PaletteLayerPreview(palette, placement, depth, data, textures, biomeTints); } // The world picks one block per whole coordinate, and zoomed in, many pixels fall on the same block. @@ -161,19 +160,19 @@ final class PaletteLayerPreview { } int minThickness() { - return palette.kind().isStacked() ? paletteLayer.getMinHeight() : 1; + return placement.minThickness(); } int maxThickness() { - return palette.kind().isStacked() ? paletteLayer.getMaxHeight() : 1; + return placement.maxThickness(); } double zoom() { - return paletteLayer.getZoom(); + return placement.zoom(); } boolean hasSlopeCondition() { - return !paletteLayer.getSlopeCondition().isDefault(); + return placement.hasSlopeCondition(); } List entries() { @@ -201,13 +200,12 @@ final class PaletteLayerPreview { } private BlockTexture[] resolveEntryTextures(BlockTextures textures) { - KList palette = paletteLayer.getPalette(); - BlockTexture[] resolved = new BlockTexture[palette.size()]; + BlockTexture[] resolved = new BlockTexture[blocks.size()]; if (textures == null) { return resolved; } - for (int entry = 0; entry < palette.size(); entry++) { - BlockData block = palette.get(entry).getBlockData(data); + for (int entry = 0; entry < blocks.size(); entry++) { + BlockData block = blocks.get(entry).getBlockData(data); resolved[entry] = block == null ? null : textures.topTexture(block).orElse(null); } return resolved; @@ -227,18 +225,17 @@ final class PaletteLayerPreview { // Blocks without a texture, like custom blocks or everything before the textures load, fall back to map colours. private int[] resolveLegendColors() { - KList palette = paletteLayer.getPalette(); List untexturedBlocks = new ArrayList<>(); - for (int entry = 0; entry < palette.size(); entry++) { - BlockData block = palette.get(entry).getBlockData(data); + for (int entry = 0; entry < blocks.size(); entry++) { + BlockData block = blocks.get(entry).getBlockData(data); if (block != null && textureByEntry[entry] == null) { untexturedBlocks.add(block); } } Map mapColorsByBlock = BlockPreviewColors.colorsByBlock(untexturedBlocks); - int[] colors = new int[palette.size()]; - for (int entry = 0; entry < palette.size(); entry++) { - BlockData block = palette.get(entry).getBlockData(data); + int[] colors = new int[blocks.size()]; + for (int entry = 0; entry < blocks.size(); entry++) { + BlockData block = blocks.get(entry).getBlockData(data); if (block == null) { colors[entry] = MISSING_BLOCK_COLOR; } else if (legendTextureByEntry[entry] != null) { @@ -252,7 +249,7 @@ final class PaletteLayerPreview { private Statistics measure() { long[] values = new long[HISTOGRAM_BINS]; - long[] samplesByEntry = new long[paletteLayer.getPalette().size()]; + long[] samplesByEntry = new long[blocks.size()]; long present = 0; long absent = 0; long outOfRange = 0; @@ -281,11 +278,10 @@ final class PaletteLayerPreview { } private List describeEntries(double[] sharesByEntry) { - KList palette = paletteLayer.getPalette(); int slotCount = entryBySlot.length; - List described = new ArrayList<>(palette.size()); - for (int entry = 0; entry < palette.size(); entry++) { - IrisBlockData block = palette.get(entry); + List described = new ArrayList<>(blocks.size()); + for (int entry = 0; entry < blocks.size(); entry++) { + IrisBlockData block = blocks.get(entry); int firstSlot = firstSlotOf(entry); int lastSlot = lastSlotOf(entry); boolean placed = firstSlot >= 0; diff --git a/core/src/main/java/com/volmit/iris/engine/decorator/IrisEngineDecorator.java b/core/src/main/java/com/volmit/iris/engine/decorator/IrisEngineDecorator.java index 68660c955..199988714 100644 --- a/core/src/main/java/com/volmit/iris/engine/decorator/IrisEngineDecorator.java +++ b/core/src/main/java/com/volmit/iris/engine/decorator/IrisEngineDecorator.java @@ -45,11 +45,18 @@ public abstract class IrisEngineDecorator extends EngineAssignedComponent implem public IrisEngineDecorator(Engine engine, String name, IrisDecorationPart part) { super(engine, name + " Decorator"); this.part = part; - this.seed = getSeed() + 29356788 - (part.ordinal() * 10439677L); + this.seed = partSeed(getSeed(), part); this.modX = 29356788 ^ (part.ordinal() + 6); this.modZ = 10439677 ^ (part.ordinal() + 1); } + /** + * The seed each decorator of the given part decides where it places with. + */ + public static long partSeed(long componentSeed, IrisDecorationPart part) { + return componentSeed + 29356788 - (part.ordinal() * 10439677L); + } + @BlockCoordinates protected RNG getRNG(int x, int z) { return new RNG(x * modX + z * modZ + seed); diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomePaletteLayer.java b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomePaletteLayer.java index 334e63cfd..96c9d32d3 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisBiomePaletteLayer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisBiomePaletteLayer.java @@ -130,6 +130,13 @@ public class IrisBiomePaletteLayer { } public KList getPaletteEntryPerSlot(IrisData data) { + return paletteEntryPerSlot(palette, data); + } + + /** + * Each block fills as many slots of the noise range as its weight; blocks that can't be found get none. + */ + public static KList paletteEntryPerSlot(KList palette, IrisData data) { KList entries = new KList<>(); for (int entry = 0; entry < palette.size(); entry++) { IrisBlockData block = palette.get(entry);