mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-07-23 15:20:53 +00:00
speed pass
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package art.arcane.iris.core;
|
||||
|
||||
import art.arcane.volmlib.util.nbt.io.NBTDeserializer;
|
||||
import art.arcane.volmlib.util.nbt.io.NBTSerializer;
|
||||
import art.arcane.volmlib.util.nbt.io.NamedTag;
|
||||
import art.arcane.volmlib.util.nbt.tag.CompoundTag;
|
||||
import art.arcane.volmlib.util.nbt.tag.IntTag;
|
||||
import art.arcane.volmlib.util.nbt.tag.ListTag;
|
||||
import art.arcane.volmlib.util.nbt.tag.Tag;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ExternalDataPackPipelineNbtRewriteTest {
|
||||
@Test
|
||||
public void rewritesOnlyJigsawPoolReferencesForCompressedAndUncompressedNbt() throws Exception {
|
||||
for (boolean compressed : new boolean[]{false, true}) {
|
||||
byte[] source = encodeStructureNbt(compressed, true);
|
||||
Map<String, String> remapped = new HashMap<>();
|
||||
remapped.put("minecraft:witch_hut/foundation", "iris_external_1:witch_hut/foundation");
|
||||
|
||||
byte[] rewritten = invokeRewrite(source, remapped);
|
||||
CompoundTag root = decodeRoot(rewritten, compressed);
|
||||
ListTag<?> blocks = root.getListTag("blocks");
|
||||
|
||||
CompoundTag jigsawBlock = (CompoundTag) blocks.get(0);
|
||||
CompoundTag nonJigsawBlock = (CompoundTag) blocks.get(1);
|
||||
assertEquals("iris_external_1:witch_hut/foundation", jigsawBlock.getCompoundTag("nbt").getString("pool"));
|
||||
assertEquals("minecraft:witch_hut/foundation", nonJigsawBlock.getCompoundTag("nbt").getString("pool"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonJigsawPayloadIsLeftUnchanged() throws Exception {
|
||||
byte[] source = encodeStructureNbt(false, false);
|
||||
Map<String, String> remapped = new HashMap<>();
|
||||
remapped.put("minecraft:witch_hut/foundation", "iris_external_1:witch_hut/foundation");
|
||||
|
||||
byte[] rewritten = invokeRewrite(source, remapped);
|
||||
assertArrayEquals(source, rewritten);
|
||||
}
|
||||
|
||||
private byte[] invokeRewrite(byte[] input, Map<String, String> remappedKeys) {
|
||||
return StructureNbtJigsawPoolRewriter.rewrite(input, remappedKeys);
|
||||
}
|
||||
|
||||
private byte[] encodeStructureNbt(boolean compressed, boolean includeJigsaw) throws Exception {
|
||||
CompoundTag root = new CompoundTag();
|
||||
ListTag<CompoundTag> palette = new ListTag<>(CompoundTag.class);
|
||||
|
||||
CompoundTag firstPalette = new CompoundTag();
|
||||
firstPalette.putString("Name", includeJigsaw ? "minecraft:jigsaw" : "minecraft:stone");
|
||||
palette.add(firstPalette);
|
||||
|
||||
CompoundTag secondPalette = new CompoundTag();
|
||||
secondPalette.putString("Name", "minecraft:stone");
|
||||
palette.add(secondPalette);
|
||||
root.put("palette", palette);
|
||||
|
||||
ListTag<CompoundTag> blocks = new ListTag<>(CompoundTag.class);
|
||||
blocks.add(blockTag(0, "minecraft:witch_hut/foundation"));
|
||||
blocks.add(blockTag(1, "minecraft:witch_hut/foundation"));
|
||||
root.put("blocks", blocks);
|
||||
|
||||
NamedTag named = new NamedTag("test", root);
|
||||
return new NBTSerializer(compressed).toBytes(named);
|
||||
}
|
||||
|
||||
private CompoundTag blockTag(int state, String pool) {
|
||||
CompoundTag block = new CompoundTag();
|
||||
block.putInt("state", state);
|
||||
CompoundTag nbt = new CompoundTag();
|
||||
nbt.putString("pool", pool);
|
||||
block.put("nbt", nbt);
|
||||
ListTag<IntTag> pos = new ListTag<>(IntTag.class);
|
||||
pos.add(new IntTag(0));
|
||||
pos.add(new IntTag(0));
|
||||
pos.add(new IntTag(0));
|
||||
block.put("pos", pos);
|
||||
return block;
|
||||
}
|
||||
|
||||
private CompoundTag decodeRoot(byte[] bytes, boolean compressed) throws Exception {
|
||||
NamedTag namedTag = new NBTDeserializer(compressed).fromStream(new ByteArrayInputStream(bytes));
|
||||
Tag<?> rootTag = namedTag.getTag();
|
||||
return (CompoundTag) rootTag;
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package art.arcane.iris.core.pregenerator;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class PregenTaskInterleavedTraversalTest {
|
||||
@Test
|
||||
public void interleavedTraversalIsDeterministicAndComplete() {
|
||||
PregenTask task = PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(1024)
|
||||
.radiusZ(1024)
|
||||
.build();
|
||||
|
||||
KList<Long> baseline = new KList<>();
|
||||
task.iterateAllChunks((x, z) -> baseline.add(asKey(x, z)));
|
||||
|
||||
KList<Long> firstInterleaved = new KList<>();
|
||||
task.iterateAllChunksInterleaved((regionX, regionZ, chunkX, chunkZ, firstChunkInRegion, lastChunkInRegion) -> {
|
||||
firstInterleaved.add(asKey(chunkX, chunkZ));
|
||||
return true;
|
||||
});
|
||||
|
||||
KList<Long> secondInterleaved = new KList<>();
|
||||
task.iterateAllChunksInterleaved((regionX, regionZ, chunkX, chunkZ, firstChunkInRegion, lastChunkInRegion) -> {
|
||||
secondInterleaved.add(asKey(chunkX, chunkZ));
|
||||
return true;
|
||||
});
|
||||
|
||||
assertEquals(baseline.size(), firstInterleaved.size());
|
||||
assertEquals(firstInterleaved, secondInterleaved);
|
||||
assertEquals(asSet(baseline), asSet(firstInterleaved));
|
||||
}
|
||||
|
||||
private Set<Long> asSet(KList<Long> values) {
|
||||
Set<Long> set = new HashSet<>();
|
||||
for (Long value : values) {
|
||||
set.add(value);
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private long asKey(int x, int z) {
|
||||
long high = (long) x << 32;
|
||||
long low = z & 0xFFFFFFFFL;
|
||||
return high | low;
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
package art.arcane.iris.core.pregenerator.methods;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class AsyncPregenMethodConcurrencyCapTest {
|
||||
@Test
|
||||
public void paperLikeRecommendedCapTracksWorkerThreads() {
|
||||
assertEquals(8, AsyncPregenMethod.computePaperLikeRecommendedCap(1));
|
||||
assertEquals(8, AsyncPregenMethod.computePaperLikeRecommendedCap(4));
|
||||
assertEquals(24, AsyncPregenMethod.computePaperLikeRecommendedCap(12));
|
||||
assertEquals(96, AsyncPregenMethod.computePaperLikeRecommendedCap(80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void foliaRecommendedCapTracksWorkerThreads() {
|
||||
assertEquals(64, AsyncPregenMethod.computeFoliaRecommendedCap(1));
|
||||
assertEquals(64, AsyncPregenMethod.computeFoliaRecommendedCap(12));
|
||||
assertEquals(80, AsyncPregenMethod.computeFoliaRecommendedCap(20));
|
||||
assertEquals(192, AsyncPregenMethod.computeFoliaRecommendedCap(80));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void runtimeCapUsesGlobalCeilingAndWorkerRecommendation() {
|
||||
assertEquals(80, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 20));
|
||||
assertEquals(12, AsyncPregenMethod.applyRuntimeConcurrencyCap(12, true, 20));
|
||||
assertEquals(64, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, true, 8));
|
||||
assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8));
|
||||
assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40));
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineMetrics;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.engine.mantle.MantleWriter;
|
||||
import art.arcane.iris.engine.object.IrisCaveProfile;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisGeneratorStyle;
|
||||
import art.arcane.iris.engine.object.IrisRange;
|
||||
import art.arcane.iris.engine.object.IrisStyledRange;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.engine.object.NoiseStyle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisCaveCarver3DNearParityTest {
|
||||
@BeforeClass
|
||||
public static void setupBukkit() {
|
||||
if (Bukkit.getServer() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Server server = mock(Server.class);
|
||||
BlockData emptyBlockData = mock(BlockData.class);
|
||||
doReturn(Logger.getLogger("IrisTest")).when(server).getLogger();
|
||||
doReturn("IrisTestServer").when(server).getName();
|
||||
doReturn("1.0").when(server).getVersion();
|
||||
doReturn("1.0").when(server).getBukkitVersion();
|
||||
doReturn(emptyBlockData).when(server).createBlockData(any(Material.class));
|
||||
doReturn(emptyBlockData).when(server).createBlockData(anyString());
|
||||
Bukkit.setServer(server);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carvedCellDistributionStableAcrossEquivalentCarvers() {
|
||||
Engine engine = createEngine(128, 92);
|
||||
|
||||
IrisCaveCarver3D firstCarver = new IrisCaveCarver3D(engine, createProfile());
|
||||
WriterCapture firstCapture = createWriterCapture(128);
|
||||
int firstCarved = firstCarver.carve(firstCapture.writer, 7, -3);
|
||||
|
||||
IrisCaveCarver3D secondCarver = new IrisCaveCarver3D(engine, createProfile());
|
||||
WriterCapture secondCapture = createWriterCapture(128);
|
||||
int secondCarved = secondCarver.carve(secondCapture.writer, 7, -3);
|
||||
|
||||
assertTrue(firstCarved > 0);
|
||||
assertEquals(firstCarved, secondCarved);
|
||||
assertEquals(firstCapture.carvedCells, secondCapture.carvedCells);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void latticePathCarvesChunkEdgesAndRespectsWorldHeightClipping() {
|
||||
Engine engine = createEngine(48, 46);
|
||||
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, createProfile());
|
||||
WriterCapture capture = createWriterCapture(48);
|
||||
double[] columnWeights = new double[256];
|
||||
Arrays.fill(columnWeights, 1D);
|
||||
int[] precomputedSurfaceHeights = new int[256];
|
||||
Arrays.fill(precomputedSurfaceHeights, 46);
|
||||
|
||||
int carved = carver.carve(capture.writer, 0, 0, columnWeights, 0D, 0D, new IrisRange(0D, 80D), precomputedSurfaceHeights);
|
||||
|
||||
assertTrue(carved > 0);
|
||||
assertTrue(hasX(capture.carvedCells, 14));
|
||||
assertTrue(hasX(capture.carvedCells, 15));
|
||||
assertTrue(hasZ(capture.carvedCells, 14));
|
||||
assertTrue(hasZ(capture.carvedCells, 15));
|
||||
assertTrue(maxY(capture.carvedCells) <= 47);
|
||||
assertTrue(minY(capture.carvedCells) >= 0);
|
||||
}
|
||||
|
||||
private Engine createEngine(int worldHeight, int sampledHeight) {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
SeedManager seedManager = new SeedManager(942_337_445L);
|
||||
EngineMetrics metrics = new EngineMetrics(16);
|
||||
IrisWorld world = IrisWorld.builder().minHeight(0).maxHeight(worldHeight).build();
|
||||
|
||||
doReturn(data).when(engine).getData();
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
doReturn(seedManager).when(engine).getSeedManager();
|
||||
doReturn(metrics).when(engine).getMetrics();
|
||||
doReturn(world).when(engine).getWorld();
|
||||
doReturn(sampledHeight).when(engine).getHeight(anyInt(), anyInt());
|
||||
|
||||
doReturn(18).when(dimension).getCaveLavaHeight();
|
||||
doReturn(64).when(dimension).getFluidHeight();
|
||||
|
||||
return engine;
|
||||
}
|
||||
|
||||
private IrisCaveProfile createProfile() {
|
||||
IrisCaveProfile profile = new IrisCaveProfile();
|
||||
profile.setEnabled(true);
|
||||
profile.setVerticalRange(new IrisRange(0D, 120D));
|
||||
profile.setVerticalEdgeFade(14);
|
||||
profile.setVerticalEdgeFadeStrength(0.21D);
|
||||
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.07D));
|
||||
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.17D));
|
||||
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.12D));
|
||||
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.09D));
|
||||
profile.setBaseWeight(1D);
|
||||
profile.setDetailWeight(0.48D);
|
||||
profile.setWarpStrength(0.37D);
|
||||
profile.setDensityThreshold(new IrisStyledRange(1D, 1D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
|
||||
profile.setThresholdBias(0D);
|
||||
profile.setSampleStep(2);
|
||||
profile.setMinCarveCells(0);
|
||||
profile.setRecoveryThresholdBoost(0D);
|
||||
profile.setSurfaceClearance(5);
|
||||
profile.setAllowSurfaceBreak(true);
|
||||
profile.setSurfaceBreakNoiseThreshold(0.16D);
|
||||
profile.setSurfaceBreakDepth(12);
|
||||
profile.setSurfaceBreakThresholdBoost(0.17D);
|
||||
profile.setAllowWater(true);
|
||||
profile.setWaterMinDepthBelowSurface(8);
|
||||
profile.setWaterRequiresFloor(false);
|
||||
profile.setAllowLava(true);
|
||||
return profile;
|
||||
}
|
||||
|
||||
private WriterCapture createWriterCapture(int worldHeight) {
|
||||
MantleWriter writer = mock(MantleWriter.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Map<Integer, Matter> sections = new HashMap<>();
|
||||
Map<Integer, Map<Integer, MatterCavern>> sectionCells = new HashMap<>();
|
||||
Set<String> carvedCells = new HashSet<>();
|
||||
|
||||
doReturn(mantle).when(writer).getMantle();
|
||||
doReturn(worldHeight).when(mantle).getWorldHeight();
|
||||
doReturn(chunk).when(writer).acquireChunk(anyInt(), anyInt());
|
||||
doAnswer(invocation -> {
|
||||
int sectionIndex = invocation.getArgument(0);
|
||||
Matter section = sections.get(sectionIndex);
|
||||
if (section != null) {
|
||||
return section;
|
||||
}
|
||||
|
||||
Matter created = createSection(sectionIndex, sectionCells, carvedCells);
|
||||
sections.put(sectionIndex, created);
|
||||
return created;
|
||||
}).when(chunk).getOrCreate(anyInt());
|
||||
|
||||
return new WriterCapture(writer, carvedCells);
|
||||
}
|
||||
|
||||
private Matter createSection(int sectionIndex, Map<Integer, Map<Integer, MatterCavern>> sectionCells, Set<String> carvedCells) {
|
||||
Matter matter = mock(Matter.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
MatterSlice<MatterCavern> slice = mock(MatterSlice.class);
|
||||
Map<Integer, MatterCavern> localCells = sectionCells.computeIfAbsent(sectionIndex, key -> new HashMap<>());
|
||||
|
||||
doReturn(slice).when(matter).slice(MatterCavern.class);
|
||||
doAnswer(invocation -> {
|
||||
int localX = invocation.getArgument(0);
|
||||
int localY = invocation.getArgument(1);
|
||||
int localZ = invocation.getArgument(2);
|
||||
return localCells.get(packLocal(localX, localY, localZ));
|
||||
}).when(slice).get(anyInt(), anyInt(), anyInt());
|
||||
doAnswer(invocation -> {
|
||||
int localX = invocation.getArgument(0);
|
||||
int localY = invocation.getArgument(1);
|
||||
int localZ = invocation.getArgument(2);
|
||||
MatterCavern value = invocation.getArgument(3);
|
||||
localCells.put(packLocal(localX, localY, localZ), value);
|
||||
int worldY = (sectionIndex << 4) + localY;
|
||||
carvedCells.add(cellKey(localX, worldY, localZ));
|
||||
return null;
|
||||
}).when(slice).set(anyInt(), anyInt(), anyInt(), any(MatterCavern.class));
|
||||
|
||||
return matter;
|
||||
}
|
||||
|
||||
private int packLocal(int x, int y, int z) {
|
||||
return (x << 8) | (y << 4) | z;
|
||||
}
|
||||
|
||||
private String cellKey(int x, int y, int z) {
|
||||
return x + ":" + y + ":" + z;
|
||||
}
|
||||
|
||||
private boolean hasX(Set<String> carvedCells, int x) {
|
||||
for (String cell : carvedCells) {
|
||||
String[] split = cell.split(":");
|
||||
if (Integer.parseInt(split[0]) == x) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean hasZ(Set<String> carvedCells, int z) {
|
||||
for (String cell : carvedCells) {
|
||||
String[] split = cell.split(":");
|
||||
if (Integer.parseInt(split[2]) == z) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private int maxY(Set<String> carvedCells) {
|
||||
int max = Integer.MIN_VALUE;
|
||||
for (String cell : carvedCells) {
|
||||
String[] split = cell.split(":");
|
||||
int y = Integer.parseInt(split[1]);
|
||||
if (y > max) {
|
||||
max = y;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
private int minY(Set<String> carvedCells) {
|
||||
int min = Integer.MAX_VALUE;
|
||||
for (String cell : carvedCells) {
|
||||
String[] split = cell.split(":");
|
||||
int y = Integer.parseInt(split[1]);
|
||||
if (y < min) {
|
||||
min = y;
|
||||
}
|
||||
}
|
||||
return min;
|
||||
}
|
||||
|
||||
private static final class WriterCapture {
|
||||
private final MantleWriter writer;
|
||||
private final Set<String> carvedCells;
|
||||
|
||||
private WriterCapture(MantleWriter writer, Set<String> carvedCells) {
|
||||
this.writer = writer;
|
||||
this.carvedCells = carvedCells;
|
||||
}
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisCaveProfile;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MantleCarvingComponentTop2BlendTest {
|
||||
private static Constructor<?> weightedProfileConstructor;
|
||||
private static Method limitMethod;
|
||||
private static Method expandTileMethod;
|
||||
private static Field profileField;
|
||||
private static Field columnWeightsField;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
Class<?> weightedProfileClass = Class.forName("art.arcane.iris.engine.mantle.components.MantleCarvingComponent$WeightedProfile");
|
||||
weightedProfileConstructor = weightedProfileClass.getDeclaredConstructor(IrisCaveProfile.class, double[].class, double.class, Class.forName("art.arcane.iris.engine.object.IrisRange"));
|
||||
weightedProfileConstructor.setAccessible(true);
|
||||
limitMethod = MantleCarvingComponent.class.getDeclaredMethod("limitAndMergeBlendedProfiles", List.class, int.class, int.class);
|
||||
limitMethod.setAccessible(true);
|
||||
expandTileMethod = MantleCarvingComponent.class.getDeclaredMethod("expandTileWeightsToColumns", double[].class);
|
||||
expandTileMethod.setAccessible(true);
|
||||
profileField = weightedProfileClass.getDeclaredField("profile");
|
||||
profileField.setAccessible(true);
|
||||
columnWeightsField = weightedProfileClass.getDeclaredField("columnWeights");
|
||||
columnWeightsField.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoProfilesAreKeptAndDroppedWeightsAreMergedIntoDominantColumns() throws Exception {
|
||||
WeightedInput input = createWeightedProfiles();
|
||||
List<?> limited = invokeLimit(input.weightedProfiles(), 2);
|
||||
assertEquals(2, limited.size());
|
||||
|
||||
Map<IrisCaveProfile, double[]> byProfile = extractWeightsByProfile(limited);
|
||||
IrisCaveProfile first = input.profiles().first();
|
||||
IrisCaveProfile second = input.profiles().second();
|
||||
|
||||
assertEquals(1.0D, byProfile.get(first)[1], 0D);
|
||||
assertEquals(1.0D, byProfile.get(second)[0], 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoMergeIsDeterministicAcrossRuns() throws Exception {
|
||||
WeightedInput firstInput = createWeightedProfiles();
|
||||
WeightedInput secondInput = createWeightedProfiles();
|
||||
List<?> first = invokeLimit(firstInput.weightedProfiles(), 2);
|
||||
List<?> second = invokeLimit(secondInput.weightedProfiles(), 2);
|
||||
|
||||
Map<IrisCaveProfile, double[]> firstByProfile = extractWeightsByProfile(first);
|
||||
Map<IrisCaveProfile, double[]> secondByProfile = extractWeightsByProfile(second);
|
||||
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().first())[0], secondByProfile.get(secondInput.profiles().first())[0], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().first())[1], secondByProfile.get(secondInput.profiles().first())[1], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().second())[0], secondByProfile.get(secondInput.profiles().second())[0], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().second())[1], secondByProfile.get(secondInput.profiles().second())[1], 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tileWeightsExpandIntoFourColumnsPerTile() throws Exception {
|
||||
double[] tileWeights = new double[64];
|
||||
tileWeights[0] = 0.42D;
|
||||
tileWeights[9] = 0.73D;
|
||||
double[] expanded = invokeExpand(tileWeights);
|
||||
|
||||
assertEquals(0.42D, expanded[(0 << 4) | 0], 0D);
|
||||
assertEquals(0.42D, expanded[(0 << 4) | 1], 0D);
|
||||
assertEquals(0.42D, expanded[(1 << 4) | 0], 0D);
|
||||
assertEquals(0.42D, expanded[(1 << 4) | 1], 0D);
|
||||
|
||||
assertEquals(0.73D, expanded[(2 << 4) | 2], 0D);
|
||||
assertEquals(0.73D, expanded[(2 << 4) | 3], 0D);
|
||||
assertEquals(0.73D, expanded[(3 << 4) | 2], 0D);
|
||||
assertEquals(0.73D, expanded[(3 << 4) | 3], 0D);
|
||||
}
|
||||
|
||||
private WeightedInput createWeightedProfiles() throws Exception {
|
||||
IrisCaveProfile first = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.31D);
|
||||
IrisCaveProfile second = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.17D);
|
||||
IrisCaveProfile third = new IrisCaveProfile().setEnabled(true).setBaseWeight(0.93D);
|
||||
Profiles profiles = new Profiles(first, second, third);
|
||||
|
||||
double[] firstWeights = new double[64];
|
||||
firstWeights[0] = 0.2D;
|
||||
firstWeights[1] = 0.8D;
|
||||
|
||||
double[] secondWeights = new double[64];
|
||||
secondWeights[0] = 0.7D;
|
||||
secondWeights[1] = 0.1D;
|
||||
|
||||
double[] thirdWeights = new double[64];
|
||||
thirdWeights[0] = 0.3D;
|
||||
thirdWeights[1] = 0.4D;
|
||||
|
||||
List<Object> weighted = new ArrayList<>();
|
||||
weighted.add(weightedProfileConstructor.newInstance(first, firstWeights, average(firstWeights), null));
|
||||
weighted.add(weightedProfileConstructor.newInstance(second, secondWeights, average(secondWeights), null));
|
||||
weighted.add(weightedProfileConstructor.newInstance(third, thirdWeights, average(thirdWeights), null));
|
||||
return new WeightedInput(weighted, profiles);
|
||||
}
|
||||
|
||||
private List<?> invokeLimit(List<Object> weightedProfiles, int limit) throws Exception {
|
||||
return (List<?>) limitMethod.invoke(null, weightedProfiles, limit, 64);
|
||||
}
|
||||
|
||||
private double[] invokeExpand(double[] tileWeights) throws Exception {
|
||||
return (double[]) expandTileMethod.invoke(null, (Object) tileWeights);
|
||||
}
|
||||
|
||||
private Map<IrisCaveProfile, double[]> extractWeightsByProfile(List<?> weightedProfiles) throws Exception {
|
||||
Map<IrisCaveProfile, double[]> byProfile = new IdentityHashMap<>();
|
||||
for (Object weightedProfile : weightedProfiles) {
|
||||
IrisCaveProfile profile = (IrisCaveProfile) profileField.get(weightedProfile);
|
||||
double[] weights = (double[]) columnWeightsField.get(weightedProfile);
|
||||
byProfile.put(profile, weights);
|
||||
}
|
||||
return byProfile;
|
||||
}
|
||||
|
||||
private double average(double[] weights) {
|
||||
double total = 0D;
|
||||
for (double weight : weights) {
|
||||
total += weight;
|
||||
}
|
||||
return total / weights.length;
|
||||
}
|
||||
|
||||
private record Profiles(IrisCaveProfile first, IrisCaveProfile second, IrisCaveProfile third) {
|
||||
}
|
||||
|
||||
private record WeightedInput(List<Object> weightedProfiles, Profiles profiles) {
|
||||
}
|
||||
}
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisCarveModifierZoneParityTest {
|
||||
private static Constructor<?> columnMaskConstructor;
|
||||
private static Method addMethod;
|
||||
private static Method nextSetBitMethod;
|
||||
private static Method clearMethod;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
Class<?> columnMaskClass = Class.forName("art.arcane.iris.engine.modifier.IrisCarveModifier$ColumnMask");
|
||||
columnMaskConstructor = columnMaskClass.getDeclaredConstructor();
|
||||
addMethod = columnMaskClass.getDeclaredMethod("add", int.class);
|
||||
nextSetBitMethod = columnMaskClass.getDeclaredMethod("nextSetBit", int.class);
|
||||
clearMethod = columnMaskClass.getDeclaredMethod("clear");
|
||||
columnMaskConstructor.setAccessible(true);
|
||||
addMethod.setAccessible(true);
|
||||
nextSetBitMethod.setAccessible(true);
|
||||
clearMethod.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void randomColumnZonesMatchLegacySortedResolver() throws Exception {
|
||||
Object columnMask = columnMaskConstructor.newInstance();
|
||||
Random random = new Random(913_447L);
|
||||
int maxHeight = 320;
|
||||
|
||||
for (int scenario = 0; scenario < 400; scenario++) {
|
||||
clearMethod.invoke(columnMask);
|
||||
|
||||
int sampleSize = 1 + random.nextInt(180);
|
||||
Set<Integer> uniqueHeights = new HashSet<>();
|
||||
while (uniqueHeights.size() < sampleSize) {
|
||||
uniqueHeights.add(random.nextInt(480) - 80);
|
||||
}
|
||||
|
||||
int[] heights = toIntArray(uniqueHeights);
|
||||
for (int index = 0; index < heights.length; index++) {
|
||||
addMethod.invoke(columnMask, heights[index]);
|
||||
}
|
||||
|
||||
List<String> expectedZones = legacyZones(heights, maxHeight);
|
||||
List<String> actualZones = bitsetZones(columnMask, maxHeight);
|
||||
assertEquals("scenario=" + scenario, expectedZones, actualZones);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void edgeColumnsMatchLegacySortedResolver() throws Exception {
|
||||
Object columnMask = columnMaskConstructor.newInstance();
|
||||
int maxHeight = 320;
|
||||
int[][] scenarios = new int[][]{
|
||||
{-10, -1, 0, 1, 2, 5, 6, 9, 10, 11, 12, 200, 201, 205},
|
||||
{300, 301, 302, 304, 305, 307, 308, 309, 310, 400, 401},
|
||||
{0, 2, 4, 6, 8, 10, 12},
|
||||
{10, 11, 12, 13, 14, 15, 16, 17}
|
||||
};
|
||||
|
||||
for (int scenario = 0; scenario < scenarios.length; scenario++) {
|
||||
clearMethod.invoke(columnMask);
|
||||
int[] heights = Arrays.copyOf(scenarios[scenario], scenarios[scenario].length);
|
||||
for (int index = 0; index < heights.length; index++) {
|
||||
addMethod.invoke(columnMask, heights[index]);
|
||||
}
|
||||
|
||||
List<String> expectedZones = legacyZones(heights, maxHeight);
|
||||
List<String> actualZones = bitsetZones(columnMask, maxHeight);
|
||||
assertEquals("edge-scenario=" + scenario, expectedZones, actualZones);
|
||||
}
|
||||
}
|
||||
|
||||
private int[] toIntArray(Set<Integer> values) {
|
||||
int[] array = new int[values.size()];
|
||||
int index = 0;
|
||||
for (Integer value : values) {
|
||||
array[index++] = value;
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
private List<String> legacyZones(int[] heights, int maxHeight) {
|
||||
List<String> zones = new ArrayList<>();
|
||||
if (heights.length == 0) {
|
||||
return zones;
|
||||
}
|
||||
|
||||
int[] sorted = Arrays.copyOf(heights, heights.length);
|
||||
Arrays.sort(sorted);
|
||||
int floor = sorted[0];
|
||||
int ceiling = -1;
|
||||
int buf = sorted[0] - 1;
|
||||
for (int index = 0; index < sorted.length; index++) {
|
||||
int y = sorted[index];
|
||||
if (y < 0 || y > maxHeight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (y == buf + 1) {
|
||||
buf = y;
|
||||
ceiling = buf;
|
||||
} else if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
} else {
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
}
|
||||
}
|
||||
|
||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
private List<String> bitsetZones(Object columnMask, int maxHeight) throws Exception {
|
||||
List<String> zones = new ArrayList<>();
|
||||
int firstHeight = nextSetBit(columnMask, 0);
|
||||
if (firstHeight < 0) {
|
||||
return zones;
|
||||
}
|
||||
|
||||
int floor = firstHeight;
|
||||
int ceiling = -1;
|
||||
int buf = firstHeight - 1;
|
||||
int y = firstHeight;
|
||||
while (y >= 0) {
|
||||
if (y >= 0 && y <= maxHeight) {
|
||||
if (y == buf + 1) {
|
||||
buf = y;
|
||||
ceiling = buf;
|
||||
} else if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
} else {
|
||||
floor = y;
|
||||
ceiling = -1;
|
||||
buf = y;
|
||||
}
|
||||
}
|
||||
|
||||
y = nextSetBit(columnMask, y + 1);
|
||||
}
|
||||
|
||||
if (isValidZone(floor, ceiling, maxHeight)) {
|
||||
zones.add(zoneKey(floor, ceiling));
|
||||
}
|
||||
|
||||
return zones;
|
||||
}
|
||||
|
||||
private int nextSetBit(Object columnMask, int fromBit) throws Exception {
|
||||
return (Integer) nextSetBitMethod.invoke(columnMask, fromBit);
|
||||
}
|
||||
|
||||
private boolean isValidZone(int floor, int ceiling, int maxHeight) {
|
||||
return floor < ceiling
|
||||
&& floor >= 0
|
||||
&& ceiling <= maxHeight
|
||||
&& ((ceiling - floor) - 1) > 0;
|
||||
}
|
||||
|
||||
private String zoneKey(int floor, int ceiling) {
|
||||
return floor + ":" + ceiling;
|
||||
}
|
||||
}
|
||||
+34
@@ -103,6 +103,27 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tileAnchoredChunkPlanResolutionIsStableAcrossRepeatedBuilds() {
|
||||
Fixture fixture = createMixedDepthFixture();
|
||||
IrisDimensionCarvingResolver.State state = new IrisDimensionCarvingResolver.State();
|
||||
IrisDimensionCarvingEntry root = legacyResolveRootEntry(fixture.engine, 80);
|
||||
|
||||
for (int chunkX = -24; chunkX <= 24; chunkX += 6) {
|
||||
for (int chunkZ = -24; chunkZ <= 24; chunkZ += 6) {
|
||||
IrisDimensionCarvingEntry[] firstPlan = buildTilePlan(fixture.engine, root, chunkX, chunkZ, state);
|
||||
IrisDimensionCarvingEntry[] secondPlan = buildTilePlan(fixture.engine, root, chunkX, chunkZ, state);
|
||||
for (int tileIndex = 0; tileIndex < firstPlan.length; tileIndex++) {
|
||||
assertSame(
|
||||
"tile plan mismatch at chunkX=" + chunkX + " chunkZ=" + chunkZ + " tileIndex=" + tileIndex,
|
||||
firstPlan[tileIndex],
|
||||
secondPlan[tileIndex]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Fixture createFixture() {
|
||||
IrisBiome rootLowBiome = mock(IrisBiome.class);
|
||||
IrisBiome rootHighBiome = mock(IrisBiome.class);
|
||||
@@ -251,6 +272,19 @@ public class IrisDimensionCarvingResolverParityTest {
|
||||
return new Fixture(engine);
|
||||
}
|
||||
|
||||
private IrisDimensionCarvingEntry[] buildTilePlan(Engine engine, IrisDimensionCarvingEntry rootEntry, int chunkX, int chunkZ, IrisDimensionCarvingResolver.State state) {
|
||||
IrisDimensionCarvingEntry[] plan = new IrisDimensionCarvingEntry[64];
|
||||
for (int tileX = 0; tileX < 8; tileX++) {
|
||||
int worldX = (chunkX << 4) + (tileX << 1);
|
||||
for (int tileZ = 0; tileZ < 8; tileZ++) {
|
||||
int worldZ = (chunkZ << 4) + (tileZ << 1);
|
||||
int tileIndex = (tileX * 8) + tileZ;
|
||||
plan[tileIndex] = IrisDimensionCarvingResolver.resolveFromRoot(engine, rootEntry, worldX, worldZ, state);
|
||||
}
|
||||
}
|
||||
return plan;
|
||||
}
|
||||
|
||||
private IrisDimensionCarvingEntry buildEntry(String id, String biome, IrisRange worldRange, int depth, List<String> children) {
|
||||
IrisDimensionCarvingEntry entry = new IrisDimensionCarvingEntry();
|
||||
entry.setId(id);
|
||||
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
package art.arcane.iris.util.project.context;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class ChunkContextPrefillPlanTest {
|
||||
@Test
|
||||
public void noCavePrefillSkipsCaveCacheFill() {
|
||||
AtomicInteger caveCalls = new AtomicInteger();
|
||||
AtomicInteger heightCalls = new AtomicInteger();
|
||||
AtomicInteger biomeCalls = new AtomicInteger();
|
||||
AtomicInteger rockCalls = new AtomicInteger();
|
||||
AtomicInteger fluidCalls = new AtomicInteger();
|
||||
AtomicInteger regionCalls = new AtomicInteger();
|
||||
ChunkContext context = createContext(
|
||||
ChunkContext.PrefillPlan.NO_CAVE,
|
||||
caveCalls,
|
||||
heightCalls,
|
||||
biomeCalls,
|
||||
rockCalls,
|
||||
fluidCalls,
|
||||
regionCalls
|
||||
);
|
||||
|
||||
assertEquals(256, heightCalls.get());
|
||||
assertEquals(256, biomeCalls.get());
|
||||
assertEquals(256, rockCalls.get());
|
||||
assertEquals(256, fluidCalls.get());
|
||||
assertEquals(256, regionCalls.get());
|
||||
assertEquals(0, caveCalls.get());
|
||||
|
||||
assertEquals(34051D, context.getHeight().get(2, 3), 0D);
|
||||
context.getCave().get(2, 3);
|
||||
context.getCave().get(2, 3);
|
||||
assertEquals(1, caveCalls.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void allPrefillIncludesCaveCacheFill() {
|
||||
AtomicInteger caveCalls = new AtomicInteger();
|
||||
AtomicInteger heightCalls = new AtomicInteger();
|
||||
AtomicInteger biomeCalls = new AtomicInteger();
|
||||
AtomicInteger rockCalls = new AtomicInteger();
|
||||
AtomicInteger fluidCalls = new AtomicInteger();
|
||||
AtomicInteger regionCalls = new AtomicInteger();
|
||||
ChunkContext context = createContext(
|
||||
ChunkContext.PrefillPlan.ALL,
|
||||
caveCalls,
|
||||
heightCalls,
|
||||
biomeCalls,
|
||||
rockCalls,
|
||||
fluidCalls,
|
||||
regionCalls
|
||||
);
|
||||
|
||||
assertEquals(256, heightCalls.get());
|
||||
assertEquals(256, biomeCalls.get());
|
||||
assertEquals(256, rockCalls.get());
|
||||
assertEquals(256, fluidCalls.get());
|
||||
assertEquals(256, regionCalls.get());
|
||||
assertEquals(256, caveCalls.get());
|
||||
|
||||
context.getCave().get(1, 1);
|
||||
assertEquals(256, caveCalls.get());
|
||||
}
|
||||
|
||||
private ChunkContext createContext(
|
||||
ChunkContext.PrefillPlan prefillPlan,
|
||||
AtomicInteger caveCalls,
|
||||
AtomicInteger heightCalls,
|
||||
AtomicInteger biomeCalls,
|
||||
AtomicInteger rockCalls,
|
||||
AtomicInteger fluidCalls,
|
||||
AtomicInteger regionCalls
|
||||
) {
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
|
||||
doAnswer(invocation -> {
|
||||
heightCalls.incrementAndGet();
|
||||
double worldX = invocation.getArgument(0);
|
||||
double worldZ = invocation.getArgument(1);
|
||||
return (worldX * 1000D) + worldZ;
|
||||
}).when(heightStream).get(anyDouble(), anyDouble());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<IrisBiome> biomeStream = mock(ProceduralStream.class);
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
doAnswer(invocation -> {
|
||||
biomeCalls.incrementAndGet();
|
||||
return biome;
|
||||
}).when(biomeStream).get(anyDouble(), anyDouble());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<IrisBiome> caveStream = mock(ProceduralStream.class);
|
||||
IrisBiome caveBiome = mock(IrisBiome.class);
|
||||
doAnswer(invocation -> {
|
||||
caveCalls.incrementAndGet();
|
||||
return caveBiome;
|
||||
}).when(caveStream).get(anyDouble(), anyDouble());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<BlockData> rockStream = mock(ProceduralStream.class);
|
||||
BlockData rock = mock(BlockData.class);
|
||||
doAnswer(invocation -> {
|
||||
rockCalls.incrementAndGet();
|
||||
return rock;
|
||||
}).when(rockStream).get(anyDouble(), anyDouble());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<BlockData> fluidStream = mock(ProceduralStream.class);
|
||||
BlockData fluid = mock(BlockData.class);
|
||||
doAnswer(invocation -> {
|
||||
fluidCalls.incrementAndGet();
|
||||
return fluid;
|
||||
}).when(fluidStream).get(anyDouble(), anyDouble());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<IrisRegion> regionStream = mock(ProceduralStream.class);
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doAnswer(invocation -> {
|
||||
regionCalls.incrementAndGet();
|
||||
return region;
|
||||
}).when(regionStream).get(anyDouble(), anyDouble());
|
||||
|
||||
doReturn(heightStream).when(complex).getHeightStream();
|
||||
doReturn(biomeStream).when(complex).getTrueBiomeStream();
|
||||
doReturn(caveStream).when(complex).getCaveBiomeStream();
|
||||
doReturn(rockStream).when(complex).getRockStream();
|
||||
doReturn(fluidStream).when(complex).getFluidStream();
|
||||
doReturn(regionStream).when(complex).getRegionStream();
|
||||
|
||||
return new ChunkContext(32, 48, complex, true, prefillPlan, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CNGFastPathParityTest {
|
||||
@Test
|
||||
public void identityFastPathMatchesLegacyAcrossSeedAndCoordinateGrid() {
|
||||
for (long seed = 3L; seed <= 11L; seed++) {
|
||||
CNG generator = createIdentityGenerator(seed);
|
||||
assertFastPathParity("identity-seed-" + seed, generator);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformedGeneratorsMatchLegacyAcrossSeedAndCoordinateGrid() {
|
||||
for (long seed = 21L; seed <= 27L; seed++) {
|
||||
List<CNG> generators = createTransformedGenerators(seed);
|
||||
for (int index = 0; index < generators.size(); index++) {
|
||||
assertFastPathParity("transformed-seed-" + seed + "-case-" + index, generators.get(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertFastPathParity(String label, CNG generator) {
|
||||
for (int x = -320; x <= 320; x += 19) {
|
||||
for (int z = -320; z <= 320; z += 23) {
|
||||
double expected = generator.noise(x, z);
|
||||
double actual = generator.noiseFast2D(x, z);
|
||||
assertEquals(label + " 2D x=" + x + " z=" + z, expected, actual, 1.0E-12D);
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = -128; x <= 128; x += 17) {
|
||||
for (int y = -96; y <= 96; y += 13) {
|
||||
for (int z = -128; z <= 128; z += 19) {
|
||||
double expected = generator.noise(x, y, z);
|
||||
double actual = generator.noiseFast3D(x, y, z);
|
||||
assertEquals(label + " 3D x=" + x + " y=" + y + " z=" + z, expected, actual, 1.0E-12D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CNG createIdentityGenerator(long seed) {
|
||||
DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D));
|
||||
return new CNG(new RNG(seed), generator, 1D, 1).bake();
|
||||
}
|
||||
|
||||
private List<CNG> createTransformedGenerators(long seed) {
|
||||
List<CNG> generators = new ArrayList<>();
|
||||
|
||||
CNG powerTransformed = createIdentityGenerator(seed).pow(1.27D);
|
||||
generators.add(powerTransformed);
|
||||
|
||||
CNG offsetTransformed = createIdentityGenerator(seed + 1L).up(0.08D).down(0.03D).patch(0.91D);
|
||||
generators.add(offsetTransformed);
|
||||
|
||||
CNG fractured = createIdentityGenerator(seed + 2L).fractureWith(createIdentityGenerator(seed + 300L), 12.5D);
|
||||
generators.add(fractured);
|
||||
|
||||
CNG withChildren = createIdentityGenerator(seed + 3L);
|
||||
withChildren.child(createIdentityGenerator(seed + 400L));
|
||||
withChildren.child(createIdentityGenerator(seed + 401L));
|
||||
generators.add(withChildren);
|
||||
|
||||
return generators;
|
||||
}
|
||||
|
||||
private static class DeterministicNoiseGenerator implements NoiseGenerator {
|
||||
private final double offset;
|
||||
|
||||
private DeterministicNoiseGenerator(double offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x) {
|
||||
double angle = (x * 0.011D) + offset;
|
||||
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
double angle = (x * 0.013D) + (z * 0.017D) + offset;
|
||||
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
double angle = (x * 0.007D) + (y * 0.015D) + (z * 0.019D) + offset;
|
||||
return 0.2D + (((Math.sin(angle) + 1D) * 0.5D) * 0.6D);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user