diff --git a/core/src/main/java/art/arcane/iris/engine/object/formation/FormationBlockResolver.java b/core/src/main/java/art/arcane/iris/engine/object/formation/FormationBlockResolver.java index b801ef582..de02aeec8 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/formation/FormationBlockResolver.java +++ b/core/src/main/java/art/arcane/iris/engine/object/formation/FormationBlockResolver.java @@ -25,35 +25,48 @@ import art.arcane.iris.engine.object.IrisProceduralBlocks; import art.arcane.iris.spi.PlatformBlockState; import art.arcane.iris.util.common.math.Vector3i; import art.arcane.volmlib.util.math.RNG; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; public final class FormationBlockResolver { - private FormationBlockResolver() { + private final IrisFormation formation; + private final IrisData data; + private final RNG paletteRng; + private final Int2ObjectOpenHashMap strataRngs = new Int2ObjectOpenHashMap<>(); + + public FormationBlockResolver(IrisFormation formation, IrisData data) { + this.formation = formation; + this.data = data; + paletteRng = new RNG(formation.getSeed()); } - public static PlatformBlockState resolve(IrisFormation f, IrisData data, FormationCanvas.Role role, Vector3i raw) { + public PlatformBlockState resolve(FormationCanvas.Role role, Vector3i raw) { int x = raw.getBlockX(); int y = raw.getBlockY(); int z = raw.getBlockZ(); - RNG paletteRng = new RNG(f.getSeed()); - if (role == FormationCanvas.Role.CAP && capDefined(f)) { - PlatformBlockState cap = IrisProceduralBlocks.resolve(f.getCapBlock(), f.getCapPalette(), data, x, y, z, paletteRng); + if (role == FormationCanvas.Role.CAP && capDefined(formation)) { + PlatformBlockState cap = IrisProceduralBlocks.resolve(formation.getCapBlock(), formation.getCapPalette(), data, x, y, z, paletteRng); if (cap != null) { return cap; } } - if (strataDefined(f)) { - IrisMaterialPalette strata = f.getStrataPalette(); - int thickness = Math.max(1, f.getStrataThickness()); + if (strataDefined(formation)) { + IrisMaterialPalette strata = formation.getStrataPalette(); + int thickness = Math.max(1, formation.getStrataThickness()); int band = Math.floorDiv(y, thickness); - PlatformBlockState strataState = strata.get(new RNG(f.getSeed() + (band * 31L)), x, band, z, data); + RNG strataRng = strataRngs.get(band); + if (strataRng == null) { + strataRng = new RNG(formation.getSeed() + band * 31L); + strataRngs.put(band, strataRng); + } + PlatformBlockState strataState = strata.get(strataRng, x, band, z, data); if (strataState != null) { return strataState; } } - return IrisProceduralBlocks.resolve(f.getBlock(), f.getBlockPalette(), data, x, y, z, paletteRng); + return IrisProceduralBlocks.resolve(formation.getBlock(), formation.getBlockPalette(), data, x, y, z, paletteRng); } private static boolean capDefined(IrisFormation f) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/formation/FormationGenerator.java b/core/src/main/java/art/arcane/iris/engine/object/formation/FormationGenerator.java index e6fe4e577..60ae3377f 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/formation/FormationGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/object/formation/FormationGenerator.java @@ -62,8 +62,9 @@ public final class FormationGenerator { } Map resolved = new HashMap<>(); + FormationBlockResolver resolver = new FormationBlockResolver(f, data); for (Map.Entry entry : canvas.getCells().entrySet()) { - PlatformBlockState bd = FormationBlockResolver.resolve(f, data, entry.getValue(), entry.getKey()); + PlatformBlockState bd = resolver.resolve(entry.getValue(), entry.getKey()); if (bd == null) { continue; } diff --git a/core/src/test/java/art/arcane/iris/engine/object/formation/FormationBlockResolverTest.java b/core/src/test/java/art/arcane/iris/engine/object/formation/FormationBlockResolverTest.java new file mode 100644 index 000000000..d5f535eed --- /dev/null +++ b/core/src/test/java/art/arcane/iris/engine/object/formation/FormationBlockResolverTest.java @@ -0,0 +1,79 @@ +package art.arcane.iris.engine.object.formation; + +import art.arcane.iris.engine.object.IrisBlockData; +import art.arcane.iris.engine.object.IrisFormation; +import art.arcane.iris.engine.object.IrisMaterialPalette; +import art.arcane.iris.spi.PlatformBlockState; +import art.arcane.iris.util.common.math.Vector3i; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class FormationBlockResolverTest { + @Test + public void sharedResolverMatchesFreshPaletteSeedsAcrossBands() { + IrisFormation formation = new IrisFormation().setSeed(43191L).setStrataThickness(3); + IrisMaterialPalette body = palette("stone", "andesite"); + IrisMaterialPalette cap = palette("granite", "diorite"); + IrisMaterialPalette strata = palette("terracotta", "red_terracotta"); + formation.setBlockPalette(body); + List positions = new ArrayList<>(); + for (int x = -8; x <= 8; x++) { + for (int y = -12; y <= 24; y++) { + positions.add(new Vector3i(x, y, x - y)); + } + } + Set materials = new HashSet<>(); + for (boolean withStrata : new boolean[]{false, true}) { + formation.setStrataPalette(withStrata ? strata : null); + for (boolean withCap : new boolean[]{false, true}) { + formation.setCapPalette(withCap ? cap : null); + FormationBlockResolver resolver = new FormationBlockResolver(formation, null); + for (int pass = 0; pass < 2; pass++) { + for (Vector3i position : positions) { + FormationCanvas.Role role = position.getBlockX() % 2 == 0 + ? FormationCanvas.Role.CAP : FormationCanvas.Role.BODY; + IrisMaterialPalette expectedPalette = withCap && role == FormationCanvas.Role.CAP + ? cap : withStrata ? strata : body; + int sampleY = expectedPalette == strata + ? Math.floorDiv(position.getBlockY(), formation.getStrataThickness()) : position.getBlockY(); + long sampleSeed = formation.getSeed() + (expectedPalette == strata ? sampleY * 31L : 0L); + RNG fresh = new RNG(sampleSeed); + PlatformBlockState expected = expectedPalette.get(fresh, + position.getBlockX(), sampleY, position.getBlockZ(), null); + assertSame(expected, resolver.resolve(role, position)); + assertEquals(new RNG(sampleSeed).nextLong(), fresh.nextLong()); + materials.add(expected.key()); + } + Collections.reverse(positions); + } + } + } + assertTrue(materials.size() == 6); + } + + private static IrisMaterialPalette palette(String first, String second) { + return new IrisMaterialPalette().setPalette(new KList<>(block(first), block(second))); + } + + private static IrisBlockData block(String material) { + PlatformBlockState state = mock(PlatformBlockState.class); + when(state.key()).thenReturn("minecraft:" + material); + IrisBlockData block = mock(IrisBlockData.class); + when(block.getWeight()).thenReturn(1); + when(block.getBlockData(null)).thenReturn(state); + return block; + } +}