mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-25 13:30:08 +00:00
Remove Hydrology
This commit is contained in:
1 parent
bdaa84975a
commit
f398c55091
188 files changed
+1291
-21010
No files matched your search
@@ -57,7 +57,7 @@ public class VisionRenderControllerTest {
|
||||
VisionRenderController.TileKey baseline = new VisionRenderController.TileKey(4L, RenderType.BIOME, 4D, 8L, 9L);
|
||||
|
||||
assertNotEquals(baseline, new VisionRenderController.TileKey(5L, RenderType.BIOME, 4D, 8L, 9L));
|
||||
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.RIVER, 4D, 8L, 9L));
|
||||
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.HEIGHT, 4D, 8L, 9L));
|
||||
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.BIOME, 4.5D, 8L, 9L));
|
||||
assertNotEquals(baseline, new VisionRenderController.TileKey(4L, RenderType.BIOME, 4D, 7L, 9L));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
public class VisionRenderTypeTest {
|
||||
@Test
|
||||
@@ -19,12 +18,6 @@ public class VisionRenderTypeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverRenderTypeUsesTheRiverNetworkLabel() {
|
||||
assertSame(DesktopUiMessages.VISION_MODE_RIVER, VisionGUI.modeKey(RenderType.RIVER));
|
||||
assertEquals("iris.desktop.vision.mode.river", DesktopUiMessages.VISION_MODE_RIVER.id());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void teleportCoordinatesFloorAcrossTheNegativeOrigin() {
|
||||
assertEquals(-1, VisionGUI.floorWorldCoordinate(-0.01D));
|
||||
|
||||
@@ -651,35 +651,6 @@ public class PackDownloaderTest {
|
||||
assertEquals(0, PackDownloader.downloadLockCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidBuiltInRiverSchemaPreservesExistingTarget() throws Exception {
|
||||
File packsFolder = temp.newFolder("invalid-river-packs");
|
||||
File target = writePack(packsFolder.toPath().resolve("overworld"), "overworld", "old");
|
||||
File extracted = writePack(temp.newFolder("invalid-river-source").toPath(), "overworld", "new");
|
||||
Files.writeString(
|
||||
extracted.toPath().resolve("dimensions/overworld.json"),
|
||||
"{\"name\":\"Overworld\",\"regions\":[\"local\"],\"logicalHeight\":256,"
|
||||
+ "\"dimensionHeight\":{\"min\":-64,\"max\":320},\"rivers\":{\"enabled\":true,"
|
||||
+ "\"terrain\":{},\"water\":{\"mode\":\"SEA_LEVEL\"}}}",
|
||||
StandardCharsets.UTF_8
|
||||
);
|
||||
|
||||
PackDownloader.PackInstallResult result = PackDownloader.installExtractedPack(
|
||||
packsFolder,
|
||||
extracted,
|
||||
true,
|
||||
"overworld",
|
||||
ignored -> {
|
||||
}
|
||||
);
|
||||
|
||||
assertNull(result);
|
||||
assertEquals("old", Files.readString(target.toPath().resolve("state.txt"), StandardCharsets.UTF_8));
|
||||
assertTrue(Files.isRegularFile(target.toPath().resolve("regions/local.json")));
|
||||
assertTransactionStateClean(packsFolder);
|
||||
assertEquals(0, PackDownloader.downloadLockCount());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void forceOverwriteReplacesEnginelessLoadedPackData() throws Exception {
|
||||
// A registered loader with no engines is a stale catalog registration (startup
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,192 +0,0 @@
|
||||
package art.arcane.iris.core.project;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.IrisRegistrant;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBlockData;
|
||||
import art.arcane.iris.engine.object.IrisExpression;
|
||||
import art.arcane.iris.engine.object.IrisRiverNetwork;
|
||||
import art.arcane.iris.engine.object.IrisRiverOverride;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.json.JSONArray;
|
||||
import art.arcane.volmlib.util.json.JSONObject;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisRiverSchemaTest {
|
||||
private IrisPlatform previousPlatform;
|
||||
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
previousPlatform = IrisPlatforms.isBound() ? IrisPlatforms.get() : null;
|
||||
if (previousPlatform != null) {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
when(registries.blockTypeKeys()).thenReturn(List.of());
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@After
|
||||
public void restorePlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
if (previousPlatform != null) {
|
||||
IrisPlatforms.bind(previousPlatform);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverNetworkSchemaExposesNestedNoiseLimitsModesAndBiomePools() {
|
||||
JSONObject schema = new SchemaBuilder(IrisRiverNetwork.class, schemaData()).construct();
|
||||
JSONObject definitions = schema.getJSONObject("definitions");
|
||||
JSONObject properties = schema.getJSONObject("properties");
|
||||
JSONObject topology = referencedProperties(definitions, properties.getJSONObject("topology"));
|
||||
JSONObject source = referencedProperties(definitions, topology.getJSONObject("source"));
|
||||
JSONObject terrainDefinition = referencedDefinition(definitions, properties.getJSONObject("terrain"));
|
||||
JSONObject terrain = terrainDefinition.getJSONObject("properties");
|
||||
JSONObject worms = terrain.getJSONObject("worms");
|
||||
JSONObject worm = referencedProperties(definitions, worms.getJSONObject("items"));
|
||||
JSONObject water = referencedProperties(definitions, properties.getJSONObject("water"));
|
||||
JSONObject biomes = referencedProperties(definitions, properties.getJSONObject("biomes"));
|
||||
JSONObject caves = referencedProperties(definitions, properties.getJSONObject("caves"));
|
||||
JSONObject deepPools = referencedProperties(definitions, caves.getJSONObject("deepPools"));
|
||||
|
||||
assertEquals("boolean", properties.getJSONObject("enabled").getString("type"));
|
||||
assertEquals(64, topology.getJSONObject("cellSize").getInt("minimum"));
|
||||
assertEquals(4096, topology.getJSONObject("cellSize").getInt("maximum"));
|
||||
assertEquals(7, topology.getJSONObject("sinkSearchReaches").getInt("maximum"));
|
||||
assertEquals(8, topology.getJSONObject("routingBasinCells").getInt("minimum"));
|
||||
assertEquals(256, topology.getJSONObject("routingBasinCells").getInt("maximum"));
|
||||
assertEquals(0D, source.getJSONObject("chance").getDouble("minimum"), 0D);
|
||||
assertEquals(1D, source.getJSONObject("chance").getDouble("maximum"), 0D);
|
||||
assertEquals(1D, terrain.getJSONObject("maxChannelWidth").getDouble("minimum"), 0D);
|
||||
assertEquals(2048D, terrain.getJSONObject("maxChannelWidth").getDouble("maximum"), 0D);
|
||||
assertEquals(0D, terrain.getJSONObject("maxBankWidth").getDouble("minimum"), 0D);
|
||||
assertEquals(512D, terrain.getJSONObject("maxDepth").getDouble("maximum"), 0D);
|
||||
assertTrue(arrayContains(terrainDefinition.getJSONArray("required"), "worms"));
|
||||
assertEquals("array", worms.getString("type"));
|
||||
assertEquals(1, worms.getInt("minItems"));
|
||||
assertEquals(0.000001D, worm.getJSONObject("weight").getDouble("minimum"), 0D);
|
||||
assertEquals(16384D, worm.getJSONObject("wavelength").getDouble("maximum"), 0D);
|
||||
assertEquals(1D, worm.getJSONObject("tortuosity").getDouble("maximum"), 0D);
|
||||
assertEquals(1024D, worm.getJSONObject("maxOffset").getDouble("maximum"), 0D);
|
||||
assertEquals(64, worm.getJSONObject("segments").getInt("maximum"));
|
||||
assertEquals(0.125D, worm.getJSONObject("widthMultiplier").getDouble("minimum"), 0D);
|
||||
assertEquals(8D, worm.getJSONObject("bankMultiplier").getDouble("maximum"), 0D);
|
||||
assertEquals(8D, worm.getJSONObject("depthMultiplier").getDouble("maximum"), 0D);
|
||||
assertEquals(8D, worm.getJSONObject("bodyWavelength").getDouble("minimum"), 0D);
|
||||
assertEquals(16384D, worm.getJSONObject("bodyDetailWavelength").getDouble("maximum"), 0D);
|
||||
assertEquals(1D, worm.getJSONObject("bodyDetailInfluence").getDouble("maximum"), 0D);
|
||||
assertEquals(0.875D, worm.getJSONObject("widthVariation").getDouble("maximum"), 0D);
|
||||
assertEquals(0.875D, worm.getJSONObject("bankVariation").getDouble("maximum"), 0D);
|
||||
assertEquals(0.875D, worm.getJSONObject("depthVariation").getDouble("maximum"), 0D);
|
||||
assertEquals(0.875D, worm.getJSONObject("roofVariation").getDouble("maximum"), 0D);
|
||||
assertEquals(8, worm.getJSONObject("branchCap").getInt("maximum"));
|
||||
assertEquals(1D, worm.getJSONObject("branchDecay").getDouble("maximum"), 0D);
|
||||
assertEquals(8D, worm.getJSONObject("confluenceMultiplier").getDouble("maximum"), 0D);
|
||||
assertEquals(1D, worm.getJSONObject("childChance").getDouble("maximum"), 0D);
|
||||
assertEquals(1D, worm.getJSONObject("branchChildChance").getDouble("maximum"), 0D);
|
||||
assertEquals("array", worm.getJSONObject("children").getString("type"));
|
||||
assertEquals(List.of("FIXED", "TERRACED"), enumValues(definitions, water.getJSONObject("mode")));
|
||||
assertEquals(-2048, water.getJSONObject("fluidHeight").getInt("minimum"));
|
||||
assertEquals(2048, water.getJSONObject("fluidHeight").getInt("maximum"));
|
||||
assertTrue(water.has("fluidPalette"));
|
||||
assertEquals(64D, terrain.getJSONObject("channelRadiusBonus").getDouble("maximum"), 0D);
|
||||
assertEquals("array", biomes.getJSONObject("channel").getString("type"));
|
||||
assertEquals("#/definitions/erzbiomes",
|
||||
biomes.getJSONObject("channel").getJSONObject("items").getString("$ref"));
|
||||
assertTrue(properties.has("terrain"));
|
||||
assertTrue(properties.has("caves"));
|
||||
assertEquals("boolean", deepPools.getJSONObject("enabled").getString("type"));
|
||||
assertEquals(-2048, deepPools.getJSONObject("minimumFluidY").getInt("minimum"));
|
||||
assertEquals(2048, deepPools.getJSONObject("maximumFluidY").getInt("maximum"));
|
||||
assertEquals(128, deepPools.getJSONObject("horizontalRadius").getInt("maximum"));
|
||||
assertEquals(64, deepPools.getJSONObject("verticalRadius").getInt("maximum"));
|
||||
assertEquals(0.75D, deepPools.getJSONObject("shapeVariation").getDouble("maximum"), 0D);
|
||||
assertEquals(64D, deepPools.getJSONObject("warpStrength").getDouble("maximum"), 0D);
|
||||
assertTrue(deepPools.has("reach"));
|
||||
assertTrue(deepPools.has("shapeStyle"));
|
||||
assertTrue(deepPools.has("warpStyle"));
|
||||
assertTrue(deepPools.has("fluidPalette"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overrideSchemaKeepsEveryFieldOptionalAndTyped() {
|
||||
JSONObject schema = new SchemaBuilder(IrisRiverOverride.class, schemaData()).construct();
|
||||
JSONObject properties = schema.getJSONObject("properties");
|
||||
|
||||
assertTrue(!schema.has("required") || schema.getJSONArray("required").length() == 0);
|
||||
assertEquals("boolean", properties.getJSONObject("allowSources").getString("type"));
|
||||
assertEquals("number", properties.getJSONObject("routingCostMultiplier").getString("type"));
|
||||
assertEquals("array", properties.getJSONObject("channelBiomes").getString("type"));
|
||||
assertEquals("#/definitions/erzbiomes",
|
||||
properties.getJSONObject("floodedCaveBiomes").getJSONObject("items").getString("$ref"));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static IrisData schemaData() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisExpression> expressionLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisBlockData> blockLoader = mock(ResourceLoader.class);
|
||||
KMap<Class<? extends IrisRegistrant>, ResourceLoader<? extends IrisRegistrant>> loaders = new KMap<>();
|
||||
loaders.put(IrisBiome.class, biomeLoader);
|
||||
loaders.put(IrisExpression.class, expressionLoader);
|
||||
when(data.getBlockLoader()).thenReturn(blockLoader);
|
||||
when(data.getLoaders()).thenReturn(loaders);
|
||||
when(data.getPossibleSnippets(anyString())).thenReturn(new KList<>());
|
||||
when(biomeLoader.getPossibleKeys()).thenReturn(new String[]{"river/channel"});
|
||||
when(biomeLoader.getFolderName()).thenReturn("biomes");
|
||||
when(biomeLoader.getResourceTypeName()).thenReturn("Biome");
|
||||
when(blockLoader.getPossibleKeys()).thenReturn(new String[0]);
|
||||
when(expressionLoader.getPossibleKeys()).thenReturn(new String[0]);
|
||||
when(expressionLoader.getFolderName()).thenReturn("expressions");
|
||||
when(expressionLoader.getResourceTypeName()).thenReturn("Expression");
|
||||
return data;
|
||||
}
|
||||
|
||||
private static JSONObject referencedProperties(JSONObject definitions, JSONObject reference) {
|
||||
return referencedDefinition(definitions, reference).getJSONObject("properties");
|
||||
}
|
||||
|
||||
private static JSONObject referencedDefinition(JSONObject definitions, JSONObject reference) {
|
||||
String key = reference.getString("$ref").substring("#/definitions/".length());
|
||||
return definitions.getJSONObject(key);
|
||||
}
|
||||
|
||||
private static boolean arrayContains(JSONArray values, String expected) {
|
||||
for (int index = 0; index < values.length(); index++) {
|
||||
if (expected.equals(values.getString(index))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static List<String> enumValues(JSONObject definitions, JSONObject reference) {
|
||||
String key = reference.getString("$ref").substring("#/definitions/".length());
|
||||
JSONArray values = definitions.getJSONObject(key).getJSONArray("oneOf");
|
||||
List<String> names = new ArrayList<>(values.length());
|
||||
for (int index = 0; index < values.length(); index++) {
|
||||
names.add(values.getJSONObject(index).getString("const"));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package art.arcane.iris.engine;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisGenerator;
|
||||
import art.arcane.iris.engine.object.IrisInterpolator;
|
||||
import art.arcane.iris.util.project.interpolation.InterpolationMethod;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisComplexGeneratorOrderingTest {
|
||||
private static final int[][] PERMUTATIONS = {
|
||||
{0, 1, 2, 3},
|
||||
{3, 2, 1, 0},
|
||||
{1, 3, 0, 2},
|
||||
{2, 0, 3, 1}
|
||||
};
|
||||
|
||||
@Test
|
||||
public void frozenGroupsUseStableOrderAcrossInsertionPermutations() {
|
||||
IrisInterpolator bilinearWide = interpolator(InterpolationMethod.BILINEAR, 16D);
|
||||
IrisInterpolator starcast = interpolator(InterpolationMethod.STARCAST_3, 8D);
|
||||
IrisInterpolator bilinearNarrow = interpolator(InterpolationMethod.BILINEAR, 4D);
|
||||
List<IrisGenerator> generators = List.of(
|
||||
generator("zeta", starcast, 1L, 0D),
|
||||
generator("beta", bilinearWide, 2L, 0D),
|
||||
generator("alpha", bilinearWide, 3L, 0D),
|
||||
generator("delta", bilinearNarrow, 4L, 0D)
|
||||
);
|
||||
|
||||
String expected = "BILINEAR@4.0[delta]|BILINEAR@16.0[alpha,beta]|STARCAST_3@8.0[zeta]";
|
||||
for (int[] permutation : PERMUTATIONS) {
|
||||
IrisComplex.GeneratorGroup[] groups = IrisComplex.freezeGeneratorGroups(
|
||||
groupByInterpolator(generators, permutation)
|
||||
);
|
||||
assertEquals(expected, signature(groups));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void frozenGeneratorOrderProducesBitIdenticalOutputAcrossInsertionPermutations() {
|
||||
IrisInterpolator interpolator = interpolator(InterpolationMethod.BILINEAR_STARCAST_6, 7D);
|
||||
List<IrisGenerator> generators = List.of(
|
||||
generator("alpha", interpolator, 1L, 1.0E16D),
|
||||
generator("beta", interpolator, 2L, -1.0E16D),
|
||||
generator("gamma", interpolator, 3L, 1D),
|
||||
generator("omega", interpolator, 4L, 0D)
|
||||
);
|
||||
long expectedBits = Double.doubleToRawLongBits(0.25D);
|
||||
|
||||
for (int[] permutation : PERMUTATIONS) {
|
||||
IrisComplex.GeneratorGroup[] groups = IrisComplex.freezeGeneratorGroups(
|
||||
groupByInterpolator(generators, permutation)
|
||||
);
|
||||
double output = 0D;
|
||||
for (IrisComplex.GeneratorGroup group : groups) {
|
||||
output += IrisComplex.averageGeneratorHeights(
|
||||
group.generators(),
|
||||
0D,
|
||||
1D,
|
||||
0D,
|
||||
0D,
|
||||
0L
|
||||
);
|
||||
}
|
||||
assertEquals(expectedBits, Double.doubleToRawLongBits(output));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyGeneratorCollectionFreezesAndAccumulatesAsEmpty() {
|
||||
Map<IrisInterpolator, Set<IrisGenerator>> generators = Map.of();
|
||||
|
||||
assertEquals(0, IrisComplex.freezeGeneratorGroups(generators).length);
|
||||
assertEquals(0D, IrisComplex.averageGeneratorHeights(
|
||||
new IrisGenerator[0],
|
||||
-1D,
|
||||
1D,
|
||||
0D,
|
||||
0D,
|
||||
0L
|
||||
), 0D);
|
||||
}
|
||||
|
||||
private IrisInterpolator interpolator(InterpolationMethod function, double horizontalScale) {
|
||||
return new IrisInterpolator()
|
||||
.setFunction(function)
|
||||
.setHorizontalScale(horizontalScale);
|
||||
}
|
||||
|
||||
private FixedGenerator generator(
|
||||
String loadKey,
|
||||
IrisInterpolator interpolator,
|
||||
long seed,
|
||||
double height
|
||||
) {
|
||||
FixedGenerator generator = new FixedGenerator(height);
|
||||
generator.setLoadKey(loadKey);
|
||||
generator.setInterpolator(interpolator);
|
||||
generator.setSeed(seed);
|
||||
return generator;
|
||||
}
|
||||
|
||||
private Map<IrisInterpolator, Set<IrisGenerator>> groupByInterpolator(
|
||||
List<IrisGenerator> generators,
|
||||
int[] permutation
|
||||
) {
|
||||
Map<IrisInterpolator, Set<IrisGenerator>> groups = new LinkedHashMap<>();
|
||||
for (int index : permutation) {
|
||||
IrisGenerator generator = generators.get(index);
|
||||
groups.computeIfAbsent(generator.getInterpolator(), ignored -> new LinkedHashSet<>()).add(generator);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
private String signature(IrisComplex.GeneratorGroup[] groups) {
|
||||
StringJoiner groupSignature = new StringJoiner("|");
|
||||
for (IrisComplex.GeneratorGroup group : groups) {
|
||||
StringJoiner generatorKeys = new StringJoiner(",");
|
||||
for (IrisGenerator generator : group.generators()) {
|
||||
generatorKeys.add(generator.getLoadKey());
|
||||
}
|
||||
groupSignature.add(group.interpolator().getFunction().name()
|
||||
+ "@" + group.interpolator().getHorizontalScale()
|
||||
+ "[" + generatorKeys + "]");
|
||||
}
|
||||
return groupSignature.toString();
|
||||
}
|
||||
|
||||
private static final class FixedGenerator extends IrisGenerator {
|
||||
private final double height;
|
||||
|
||||
private FixedGenerator(double height) {
|
||||
this.height = height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight(double x, double z, long seed) {
|
||||
return height;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,51 @@ public class IrisComplexGridBoundsCacheTest {
|
||||
assertEquals(1, interpolator.getInvocations());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void horizontalGridAxisMatchesLegacyBilerpBitForBit() throws Exception {
|
||||
IrisComplex complex = createComplex();
|
||||
Method gridSampleBounds = gridSampleBoundsMethod();
|
||||
CoordinateInterpolator interpolator = new CoordinateInterpolator();
|
||||
double x = 67D;
|
||||
double z = -32D;
|
||||
|
||||
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
|
||||
NoiseBounds expected = legacyGridSampleBounds(x, z);
|
||||
|
||||
assertBoundsBitsEqual(expected, actual);
|
||||
assertEquals(2, interpolator.getInvocations());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalGridAxisMatchesLegacyBilerpBitForBitAtNegativeCoordinates() throws Exception {
|
||||
IrisComplex complex = createComplex();
|
||||
Method gridSampleBounds = gridSampleBoundsMethod();
|
||||
CoordinateInterpolator interpolator = new CoordinateInterpolator();
|
||||
double x = -32D;
|
||||
double z = -29D;
|
||||
|
||||
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
|
||||
NoiseBounds expected = legacyGridSampleBounds(x, z);
|
||||
|
||||
assertBoundsBitsEqual(expected, actual);
|
||||
assertEquals(2, interpolator.getInvocations());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interiorGridSampleMatchesLegacyBilerpBitForBit() throws Exception {
|
||||
IrisComplex complex = createComplex();
|
||||
Method gridSampleBounds = gridSampleBoundsMethod();
|
||||
CoordinateInterpolator interpolator = new CoordinateInterpolator();
|
||||
double x = -29D;
|
||||
double z = 67D;
|
||||
|
||||
NoiseBounds actual = invokeGridSampleBounds(complex, gridSampleBounds, interpolator, x, z);
|
||||
NoiseBounds expected = legacyGridSampleBounds(x, z);
|
||||
|
||||
assertBoundsBitsEqual(expected, actual);
|
||||
assertEquals(4, interpolator.getInvocations());
|
||||
}
|
||||
|
||||
private IrisComplex createComplex() throws Exception {
|
||||
IrisComplex complex = mock(IrisComplex.class, CALLS_REAL_METHODS);
|
||||
|
||||
@@ -133,6 +178,50 @@ public class IrisComplexGridBoundsCacheTest {
|
||||
return (NoiseBounds) method.invoke(complex, null, interpolator, 0, new IrisGenerator[0], x, z);
|
||||
}
|
||||
|
||||
private NoiseBounds legacyGridSampleBounds(double x, double z) {
|
||||
int grid = 4;
|
||||
int xi = (int) Math.floor(x);
|
||||
int zi = (int) Math.floor(z);
|
||||
int mask = grid - 1;
|
||||
int gx = xi & ~mask;
|
||||
int gz = zi & ~mask;
|
||||
double fx = (x - gx) / grid;
|
||||
double fz = (z - gz) / grid;
|
||||
NoiseBounds b00 = packedCoordinateBounds(gx, gz);
|
||||
NoiseBounds b10 = packedCoordinateBounds(gx + grid, gz);
|
||||
NoiseBounds b01 = packedCoordinateBounds(gx, gz + grid);
|
||||
NoiseBounds b11 = packedCoordinateBounds(gx + grid, gz + grid);
|
||||
return new NoiseBounds(
|
||||
legacyBiLerp(b00.min(), b10.min(), b01.min(), b11.min(), fx, fz),
|
||||
legacyBiLerp(b00.max(), b10.max(), b01.max(), b11.max(), fx, fz)
|
||||
);
|
||||
}
|
||||
|
||||
private NoiseBounds packedCoordinateBounds(int x, int z) {
|
||||
return new NoiseBounds(
|
||||
(float) CoordinateInterpolator.low(x, z),
|
||||
(float) CoordinateInterpolator.high(x, z)
|
||||
);
|
||||
}
|
||||
|
||||
private double legacyBiLerp(
|
||||
double v00,
|
||||
double v10,
|
||||
double v01,
|
||||
double v11,
|
||||
double fx,
|
||||
double fz
|
||||
) {
|
||||
double a = v00 + ((v10 - v00) * fx);
|
||||
double b = v01 + ((v11 - v01) * fx);
|
||||
return a + ((b - a) * fz);
|
||||
}
|
||||
|
||||
private void assertBoundsBitsEqual(NoiseBounds expected, NoiseBounds actual) {
|
||||
assertEquals(Double.doubleToRawLongBits(expected.min()), Double.doubleToRawLongBits(actual.min()));
|
||||
assertEquals(Double.doubleToRawLongBits(expected.max()), Double.doubleToRawLongBits(actual.max()));
|
||||
}
|
||||
|
||||
private float unpackLow(long packed) {
|
||||
return Float.intBitsToFloat((int) (packed >>> 32));
|
||||
}
|
||||
@@ -159,4 +248,26 @@ public class IrisComplexGridBoundsCacheTest {
|
||||
return invocations.get();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CoordinateInterpolator extends IrisInterpolator {
|
||||
private final AtomicInteger invocations = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public NoiseBounds interpolateBounds(double x, double z, NoiseBoundsProvider provider) {
|
||||
invocations.incrementAndGet();
|
||||
return new NoiseBounds(low(x, z), high(x, z));
|
||||
}
|
||||
|
||||
private static double low(double x, double z) {
|
||||
return x * 0.125D - z * 0.0625D - 7.75D;
|
||||
}
|
||||
|
||||
private static double high(double x, double z) {
|
||||
return x * -0.03125D + z * 0.1875D + 12.5D;
|
||||
}
|
||||
|
||||
private int getInvocations() {
|
||||
return invocations.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
package art.arcane.iris.engine;
|
||||
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.iris.util.project.stream.interpolation.Interpolated;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisComplexInferredBiomeStreamCacheTest {
|
||||
@Test
|
||||
public void compilesOncePerRegionIdentityAndPreservesSampling() {
|
||||
IrisRegion first = new IrisRegion();
|
||||
IrisRegion second = new IrisRegion();
|
||||
AtomicInteger compilations = new AtomicInteger();
|
||||
IdentityHashMap<IrisRegion, EnumMap<InferredType, IrisBiome>> expected = new IdentityHashMap<>();
|
||||
List<InferredType> compilationOrder = new ArrayList<>();
|
||||
|
||||
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
|
||||
IrisComplex.compileInferredBiomeStreams(
|
||||
List.of(first, first, second),
|
||||
(region, inferredType) -> {
|
||||
compilations.incrementAndGet();
|
||||
compilationOrder.add(inferredType);
|
||||
IrisBiome biome = new IrisBiome().setInferredType(inferredType);
|
||||
expected.computeIfAbsent(region, ignored -> new EnumMap<>(InferredType.class))
|
||||
.put(inferredType, biome);
|
||||
return constant(biome);
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(8, compilations.get());
|
||||
assertEquals(List.of(
|
||||
InferredType.LAND,
|
||||
InferredType.CAVE,
|
||||
InferredType.SEA,
|
||||
InferredType.SHORE,
|
||||
InferredType.LAND,
|
||||
InferredType.CAVE,
|
||||
InferredType.SEA,
|
||||
InferredType.SHORE
|
||||
), compilationOrder);
|
||||
assertEquals(2, streams.size());
|
||||
assertNotSame(streams.get(first), streams.get(second));
|
||||
for (InferredType inferredType : InferredType.values()) {
|
||||
ProceduralStream<IrisBiome> firstStream = IrisComplex.preparedInferredBiomeStream(
|
||||
streams, first, inferredType);
|
||||
assertSame(firstStream, IrisComplex.preparedInferredBiomeStream(streams, first, inferredType));
|
||||
assertSame(expected.get(first).get(inferredType), firstStream.get(13D, 17D));
|
||||
assertSame(
|
||||
expected.get(second).get(inferredType),
|
||||
IrisComplex.preparedInferredBiomeStream(streams, second, inferredType).get(13D, 17D)
|
||||
);
|
||||
}
|
||||
assertThrows(UnsupportedOperationException.class, streams::clear);
|
||||
assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> streams.get(first).put(InferredType.LAND, constant(new IrisBiome()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsOnlyUnpreparedRegionIdentities() {
|
||||
IrisRegion prepared = new IrisRegion();
|
||||
IrisRegion unprepared = new IrisRegion();
|
||||
unprepared.setLoadKey("missing");
|
||||
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
|
||||
IrisComplex.compileInferredBiomeStreams(
|
||||
List.of(prepared),
|
||||
(region, inferredType) -> constant(new IrisBiome().setInferredType(inferredType))
|
||||
);
|
||||
|
||||
IllegalStateException error = assertThrows(
|
||||
IllegalStateException.class,
|
||||
() -> IrisComplex.preparedInferredBiomeStream(streams, unprepared, InferredType.LAND)
|
||||
);
|
||||
|
||||
assertTrue(error.getMessage().contains("missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immutableCacheSupportsConcurrentReads() throws Exception {
|
||||
IrisRegion region = new IrisRegion();
|
||||
IrisBiome biome = new IrisBiome().setInferredType(InferredType.LAND);
|
||||
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams =
|
||||
IrisComplex.compileInferredBiomeStreams(
|
||||
List.of(region),
|
||||
(preparedRegion, inferredType) -> constant(
|
||||
inferredType == InferredType.LAND
|
||||
? biome
|
||||
: new IrisBiome().setInferredType(inferredType)
|
||||
)
|
||||
);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(8);
|
||||
try {
|
||||
List<Future<IrisBiome>> reads = new ArrayList<>();
|
||||
for (int task = 0; task < 32; task++) {
|
||||
reads.add(executor.submit(() -> {
|
||||
IrisBiome sampled = null;
|
||||
for (int read = 0; read < 1_000; read++) {
|
||||
sampled = IrisComplex.preparedInferredBiomeStream(
|
||||
streams, region, InferredType.LAND).get(read, -read);
|
||||
}
|
||||
return sampled;
|
||||
}));
|
||||
}
|
||||
for (Future<IrisBiome> read : reads) {
|
||||
assertSame(biome, read.get(5, TimeUnit.SECONDS));
|
||||
}
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
assertTrue(executor.awaitTermination(5, TimeUnit.SECONDS));
|
||||
}
|
||||
}
|
||||
|
||||
private static ProceduralStream<IrisBiome> constant(IrisBiome biome) {
|
||||
return ProceduralStream.of(
|
||||
(x, z) -> biome,
|
||||
Interpolated.of(value -> 0D, value -> biome)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,162 +1,16 @@
|
||||
package art.arcane.iris.engine;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisRiverOverride;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.iris.util.project.stream.interpolation.Interpolated;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class IrisComplexSurfaceBiomeTest {
|
||||
@Test
|
||||
public void focusBiomeWithoutOverrideIgnoresOverridesOutsideFocus() {
|
||||
IrisBiome focusBiome = new IrisBiome();
|
||||
IrisBiome unreachableBiome = new IrisBiome().setRiverOverride(new IrisRiverOverride());
|
||||
|
||||
assertFalse(IrisComplex.biomeRiverOverridesPossible(focusBiome, List.of(unreachableBiome)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void focusBiomeOverrideAlwaysEnablesBiomeSampling() {
|
||||
IrisBiome focusBiome = new IrisBiome().setRiverOverride(new IrisRiverOverride());
|
||||
|
||||
assertTrue(IrisComplex.biomeRiverOverridesPossible(focusBiome, List.of()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonFocusBiomeSamplingTracksReachableOverrides() {
|
||||
assertFalse(IrisComplex.biomeRiverOverridesPossible(null, List.of(new IrisBiome())));
|
||||
assertTrue(IrisComplex.biomeRiverOverridesPossible(
|
||||
null,
|
||||
List.of(new IrisBiome().setRiverOverride(new IrisRiverOverride()))
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxIncisionCapabilityIgnoresIdentityOverrides() {
|
||||
assertFalse(IrisComplex.changesMaxIncision(null));
|
||||
assertFalse(IrisComplex.changesMaxIncision(new IrisRiverOverride()));
|
||||
assertFalse(IrisComplex.changesMaxIncision(
|
||||
new IrisRiverOverride().setMaxIncisionMultiplier(1D)));
|
||||
assertTrue(IrisComplex.changesMaxIncision(
|
||||
new IrisRiverOverride().setMaxIncisionMultiplier(0.75D)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void naturalOceanMaskTracksContinentalIntent() {
|
||||
assertTrue(IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.SEA),
|
||||
null
|
||||
).get(0D, 0D));
|
||||
assertFalse(IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.LAND),
|
||||
null
|
||||
).get(0D, 0D));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void naturalOceanMaskDoesNotSampleNaturalHeight() {
|
||||
AtomicInteger heightSamples = new AtomicInteger();
|
||||
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
|
||||
heightSamples.incrementAndGet();
|
||||
return 62D;
|
||||
});
|
||||
|
||||
Boolean ocean = IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.LAND),
|
||||
null
|
||||
).get(8D, -3D);
|
||||
|
||||
assertFalse(ocean);
|
||||
assertEquals(0, heightSamples.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void focusNaturalOceanMaskIgnoresHeightForEverySurfaceType() {
|
||||
AtomicInteger heightSamples = new AtomicInteger();
|
||||
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
|
||||
heightSamples.incrementAndGet();
|
||||
return -1_000D;
|
||||
});
|
||||
|
||||
assertTrue(IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.LAND),
|
||||
new IrisBiome().setInferredType(InferredType.SEA)
|
||||
).get(0D, 0D));
|
||||
assertFalse(IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.SEA),
|
||||
new IrisBiome().setInferredType(InferredType.LAND)
|
||||
).get(0D, 0D));
|
||||
assertFalse(IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.SEA),
|
||||
new IrisBiome().setInferredType(InferredType.SHORE)
|
||||
).get(0D, 0D));
|
||||
assertEquals(0, heightSamples.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fixedOceanMaskUsesContinentalIntentWithoutSamplingHeight() {
|
||||
AtomicInteger heightSamples = new AtomicInteger();
|
||||
ProceduralStream<Double> height = ProceduralStream.ofDouble((x, z) -> {
|
||||
heightSamples.incrementAndGet();
|
||||
return -1_000D;
|
||||
});
|
||||
|
||||
Boolean ocean = IrisComplex.createNaturalOceanStream(
|
||||
constantType(InferredType.SEA),
|
||||
null
|
||||
).get(8D, -3D);
|
||||
|
||||
assertTrue(ocean);
|
||||
assertEquals(0, heightSamples.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyRiverPoolsKeepWetChannelsAquaticAndDryChannelsLand() {
|
||||
assertEquals(InferredType.SEA, IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.CHANNEL)));
|
||||
assertEquals(InferredType.SEA, IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.MOUTH)));
|
||||
assertEquals(InferredType.LAND, IrisComplex.directRiverFallback(sample(RiverRouteState.DRY, RiverSection.DRY_BANK)));
|
||||
assertNull(IrisComplex.directRiverFallback(sample(RiverRouteState.WET, RiverSection.BANK)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void elevatedRiverBankUsesTheLocalWaterHead() {
|
||||
IrisBiome base = mock(IrisBiome.class);
|
||||
IrisBiome sea = mock(IrisBiome.class);
|
||||
IrisRegion region = mock(IrisRegion.class);
|
||||
doReturn(false).when(base).isShore();
|
||||
doReturn(false).when(base).isAquatic();
|
||||
doReturn(3D).when(region).getShoreHeight(12D, 18D);
|
||||
|
||||
IrisBiome resolved = IrisComplex.resolveSurfaceBiome(
|
||||
68D,
|
||||
base,
|
||||
region,
|
||||
12D,
|
||||
18D,
|
||||
70D,
|
||||
constant(base),
|
||||
constant(sea),
|
||||
constant(base));
|
||||
|
||||
assertSame(sea, resolved);
|
||||
}
|
||||
@Test
|
||||
public void shorelineHeightSelectsShoreBiome() {
|
||||
IrisBiome base = mock(IrisBiome.class);
|
||||
@@ -208,15 +62,4 @@ public class IrisComplexSurfaceBiomeTest {
|
||||
doReturn(biome).when(stream).get(anyDouble(), anyDouble());
|
||||
return stream;
|
||||
}
|
||||
|
||||
private static ProceduralStream<InferredType> constantType(InferredType type) {
|
||||
return ProceduralStream.of(
|
||||
(x, z) -> type,
|
||||
Interpolated.of(value -> 0D, value -> type)
|
||||
);
|
||||
}
|
||||
|
||||
private static RiverSample sample(RiverRouteState state, RiverSection section) {
|
||||
return new RiverSample(true, state, section, 0D, 0.5D, 1D, 1, 1, 10D, 5D, 3D, false, null);
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package art.arcane.iris.engine.actuator;
|
||||
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisDecorantActuatorRiverTest {
|
||||
@Test
|
||||
public void dryRiverSurfaceDoesNotRunShorelineDecoration() {
|
||||
IrisRiverSurfaceSample dry = new IrisRiverSurfaceSample(
|
||||
river(RiverRouteState.DRY, RiverSection.DRY_CHANNEL),
|
||||
70D,
|
||||
65D,
|
||||
65D,
|
||||
false,
|
||||
false
|
||||
);
|
||||
IrisRiverSurfaceSample wet = new IrisRiverSurfaceSample(
|
||||
river(RiverRouteState.WET, RiverSection.CHANNEL),
|
||||
70D,
|
||||
65D,
|
||||
65D,
|
||||
false,
|
||||
true
|
||||
);
|
||||
|
||||
assertFalse(IrisDecorantActuator.shouldDecorateShoreline(dry, 65));
|
||||
assertTrue(IrisDecorantActuator.shouldDecorateShoreline(wet, 65));
|
||||
assertTrue(IrisDecorantActuator.shouldDecorateShoreline(IrisRiverSurfaceSample.none(65D, 65D), 65));
|
||||
}
|
||||
|
||||
private static RiverSample river(RiverRouteState state, RiverSection section) {
|
||||
return new RiverSample(true, state, section, 0D, 0.5D, 1D, 1, 1, 10D, 5D, 3D, false, null);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package art.arcane.iris.engine.decorator;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.SeedManager;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
@@ -11,7 +10,6 @@ import art.arcane.iris.engine.object.IrisDecorator;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -33,11 +31,6 @@ public class IrisDecoratorCaveContextTest {
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
doReturn(63).when(dimension).getFluidHeight();
|
||||
doReturn(dimension).when(engine).getDimension();
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
ProceduralStream<Double> fluidStream = mock(ProceduralStream.class);
|
||||
doReturn(complex).when(engine).getComplex();
|
||||
doReturn(fluidStream).when(complex).getRiverWaterSurfaceStream();
|
||||
doReturn(63D).when(fluidStream).get(anyDouble(), anyDouble());
|
||||
|
||||
IrisDecorator decorator = mock(IrisDecorator.class);
|
||||
doReturn(true).when(decorator).passesChanceGate(any(), anyDouble(), anyDouble(), any());
|
||||
|
||||
@@ -98,7 +98,6 @@ public class IrisShoreLineDecoratorTest {
|
||||
IrisSlopeClip slope = mock(IrisSlopeClip.class);
|
||||
PlatformBlockState decorant = mock(PlatformBlockState.class);
|
||||
ProceduralStream<Double> heightStream = mock(ProceduralStream.class);
|
||||
ProceduralStream<Double> fluidStream = mock(ProceduralStream.class);
|
||||
|
||||
when(engine.getCacheID()).thenReturn(1);
|
||||
when(engine.getSeedManager()).thenReturn(seedManager);
|
||||
@@ -108,9 +107,7 @@ public class IrisShoreLineDecoratorTest {
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getFluidHeight()).thenReturn((double) FLUID_HEIGHT);
|
||||
when(complex.getHeightStream()).thenReturn(heightStream);
|
||||
when(complex.getRiverWaterSurfaceStream()).thenReturn(fluidStream);
|
||||
when(heightStream.get(anyDouble(), anyDouble())).thenReturn((double) FLUID_HEIGHT - 1);
|
||||
when(fluidStream.get(anyDouble(), anyDouble())).thenReturn((double) FLUID_HEIGHT);
|
||||
when(engine.getData()).thenReturn(data);
|
||||
when(biome.getDecoratorBucket(IrisDecorationPart.SHORE_LINE))
|
||||
.thenReturn(new IrisDecorator[]{decorator});
|
||||
|
||||
+1
-1
@@ -417,7 +417,7 @@ public class IrisStructureLocatorContractTest {
|
||||
|
||||
assertEquals(IrisStructureLocator.LocateStatus.SEARCH_LIMIT_REACHED, result.status());
|
||||
assertFalse(result.found());
|
||||
verify(engine, times(8_192)).getComplex();
|
||||
verify(engine, times(4_096)).getComplex();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
-17
@@ -1,6 +1,5 @@
|
||||
package art.arcane.iris.engine.framework;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructure;
|
||||
import art.arcane.iris.engine.object.IrisNativeStructureDecision;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
@@ -10,7 +9,6 @@ import art.arcane.iris.engine.object.IrisStructureTerrain;
|
||||
import art.arcane.iris.engine.object.IrisStructureTerrainMode;
|
||||
import art.arcane.iris.engine.object.NativeStructureGenerationStatus;
|
||||
import art.arcane.iris.engine.object.StructureDistribution;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
@@ -22,7 +20,6 @@ import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NativeStructurePlacementPlannerTest {
|
||||
@@ -115,20 +112,6 @@ public class NativeStructurePlacementPlannerTest {
|
||||
assertNotNull(NativeStructurePlacementPlanner.planAt(engine, placement, 0, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submergedCheckUsesTheColumnRiverHead() {
|
||||
Engine engine = engine(77L, -64, 384, 66);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> riverHead = mock(ProceduralStream.class);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getRiverWaterSurfaceStream()).thenReturn(riverHead);
|
||||
when(riverHead.get(8, 8)).thenReturn(68D);
|
||||
|
||||
assertTrue(NativeStructurePlacementPlanner.isSubmerged(engine, 8, 8));
|
||||
verify(riverHead).get(8, 8);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void duplicateSelectedStructureUsesOneDeterministicPlan() {
|
||||
Engine engine = engine(77L, -64, 384, 150);
|
||||
|
||||
-40
@@ -3,16 +3,8 @@ package art.arcane.iris.engine.framework;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisStructureAnchorMode;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -27,7 +19,6 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -36,22 +27,6 @@ import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class StructureCaveAnchorResolverTest {
|
||||
@Before
|
||||
public void bindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
PlatformBlockState block = mock(PlatformBlockState.class);
|
||||
PlatformRegistries registries = mock(PlatformRegistries.class);
|
||||
when(registries.block(anyString())).thenReturn(block);
|
||||
IrisPlatform platform = mock(IrisPlatform.class);
|
||||
when(platform.registries()).thenReturn(registries);
|
||||
IrisPlatforms.bind(platform);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unbindPlatform() {
|
||||
IrisPlatforms.unbind();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void floorRequiresSolidBoundaryAndUpwardClearance() {
|
||||
IntPredicate carved = carvedAt(10, 11, 12, 13);
|
||||
@@ -180,21 +155,6 @@ public class StructureCaveAnchorResolverTest {
|
||||
true, new MatterCavern(false, "", (byte) 0), 20, 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void wetAndSealHydrologyCannotBecomeStructureAnchors() {
|
||||
MatterCavern fluid = new MatterCavern(true, "", (byte) 1);
|
||||
MatterCavern forcedAir = new MatterCavern(true, "", (byte) 3);
|
||||
|
||||
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
|
||||
true, fluid, RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), 20, 8));
|
||||
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
|
||||
true, fluid, RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), 20, 8));
|
||||
assertFalse(StructureCaveAnchorResolver.acceptsAnchorFluid(
|
||||
true, null, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), 20, 8));
|
||||
assertTrue(StructureCaveAnchorResolver.acceptsAnchorFluid(
|
||||
false, forcedAir, RiverCaveHydrology.of(RiverCaveAction.DRY_AIR), 0, 8));
|
||||
}
|
||||
|
||||
private static Engine engineWithFloorAnchor(MatterCavern anchorCavern, int surfaceHeight) {
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
|
||||
+1
-1
@@ -66,7 +66,7 @@ public class StructurePlacementScopeTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dimensionOnlyPlacementsDoNotResolveRiverInclusiveBiomeScopes() {
|
||||
public void dimensionOnlyPlacementsDoNotResolveBiomeScopes() {
|
||||
IrisStructurePlacement dimensionPlacement = new IrisStructurePlacement();
|
||||
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||
IrisComplex complex = engine.getComplex();
|
||||
|
||||
+6
-14
@@ -71,33 +71,25 @@ public class IrisRendererPerformanceContractTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioHeightUsesNaturalTerrainWhileProtocolRenderRemainsExact() {
|
||||
public void studioAndProtocolHeightUseOrdinaryTerrain() {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> natural = mock(ProceduralStream.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> exact = mock(ProceduralStream.class);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getHeight()).thenReturn(320);
|
||||
when(complex.getNaturalHeightStream()).thenReturn(natural);
|
||||
when(complex.getHeightStream()).thenReturn(exact);
|
||||
when(natural.getDouble(0D, 0D)).thenReturn(80D);
|
||||
when(exact.getDouble(0D, 0D)).thenReturn(96D);
|
||||
IrisRenderer renderer = new IrisRenderer(engine);
|
||||
|
||||
renderer.renderStudio(0D, 0D, 1D, 1, RenderType.HEIGHT, () -> false);
|
||||
|
||||
verify(natural).getDouble(0D, 0D);
|
||||
verifyNoInteractions(exact);
|
||||
|
||||
renderer.render(0D, 0D, 1D, 1, RenderType.HEIGHT);
|
||||
|
||||
verify(exact).getDouble(0D, 0D);
|
||||
verify(exact, times(2)).getDouble(0D, 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void studioBiomeAndContinentAvoidRiverAdjustedEngineLookups() {
|
||||
public void studioBiomeAndContinentAvoidEngineBiomeLookups() {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
@@ -144,12 +136,12 @@ public class IrisRendererPerformanceContractTest {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> natural = mock(ProceduralStream.class);
|
||||
ProceduralStream<Double> exact = mock(ProceduralStream.class);
|
||||
AtomicInteger samples = new AtomicInteger();
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getHeight()).thenReturn(320);
|
||||
when(complex.getNaturalHeightStream()).thenReturn(natural);
|
||||
when(natural.getDouble(org.mockito.ArgumentMatchers.anyDouble(), org.mockito.ArgumentMatchers.anyDouble()))
|
||||
when(complex.getHeightStream()).thenReturn(exact);
|
||||
when(exact.getDouble(org.mockito.ArgumentMatchers.anyDouble(), org.mockito.ArgumentMatchers.anyDouble()))
|
||||
.thenAnswer(invocation -> {
|
||||
samples.incrementAndGet();
|
||||
double x = invocation.getArgument(0);
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
package art.arcane.iris.engine.framework.render;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverRuntime;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisRendererRiverTest {
|
||||
@Test
|
||||
public void everyRiverSectionHasItsDiagnosticColor() {
|
||||
Map<RiverSection, Integer> expected = new EnumMap<>(RiverSection.class);
|
||||
expected.put(RiverSection.CHANNEL, new Color(48, 112, 190).getRGB());
|
||||
expected.put(RiverSection.MOUTH, new Color(54, 164, 205).getRGB());
|
||||
expected.put(RiverSection.BANK, new Color(92, 146, 78).getRGB());
|
||||
expected.put(RiverSection.DRY_CHANNEL, new Color(171, 128, 68).getRGB());
|
||||
expected.put(RiverSection.DRY_BANK, new Color(132, 105, 62).getRGB());
|
||||
expected.put(RiverSection.NONE, new Color(28, 31, 38).getRGB());
|
||||
|
||||
assertEquals(RiverSection.values().length, expected.size());
|
||||
for (RiverSection section : RiverSection.values()) {
|
||||
assertEquals(section.name(), expected.get(section).intValue(), IrisRenderer.riverColor(section));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverRenderTypeSamplesOneWorldFootprintPerRenderedPixel() {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisRiverRuntime runtime = mock(IrisRiverRuntime.class);
|
||||
RiverSample river = mock(RiverSample.class);
|
||||
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getRiverRuntime()).thenReturn(runtime);
|
||||
when(runtime.sampleFootprint(12D, -7D, 20D, 1D)).thenReturn(river);
|
||||
when(river.present()).thenReturn(true);
|
||||
when(river.section()).thenReturn(RiverSection.CHANNEL);
|
||||
|
||||
BufferedImage image = new IrisRenderer(engine).renderStudio(
|
||||
12D,
|
||||
-7D,
|
||||
8D,
|
||||
1,
|
||||
RenderType.RIVER,
|
||||
() -> false
|
||||
);
|
||||
|
||||
assertEquals(new Color(48, 112, 190).getRGB(), image.getRGB(0, 0));
|
||||
verify(runtime).sampleFootprint(12D, -7D, 20D, 1D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void biomeAtlasCompositesRiverChannelsOverTheFastBaseBiome() {
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisRiverRuntime runtime = mock(IrisRiverRuntime.class);
|
||||
RiverSample river = mock(RiverSample.class);
|
||||
IrisBiome biome = mock(IrisBiome.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<IrisBiome> base = mock(ProceduralStream.class);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(complex.getBaseBiomeStream()).thenReturn(base);
|
||||
when(complex.getRiverRuntime()).thenReturn(runtime);
|
||||
when(base.get(0D, 0D)).thenReturn(biome);
|
||||
when(biome.getColor(engine, RenderType.BIOME)).thenReturn(Color.GREEN);
|
||||
when(runtime.sampleFootprint(0D, 0D, 4D, 4D)).thenReturn(river);
|
||||
when(river.present()).thenReturn(true);
|
||||
when(river.section()).thenReturn(RiverSection.CHANNEL);
|
||||
|
||||
BufferedImage image = new IrisRenderer(engine).renderStudio(
|
||||
0D,
|
||||
0D,
|
||||
4D,
|
||||
1,
|
||||
RenderType.BIOME,
|
||||
() -> false
|
||||
);
|
||||
|
||||
assertEquals(new Color(48, 112, 190).getRGB(), image.getRGB(0, 0));
|
||||
verify(base).get(0D, 0D);
|
||||
verify(runtime).sampleFootprint(0D, 0D, 4D, 4D);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package art.arcane.iris.engine.mantle;
|
||||
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.engine.framework.TreeBlockMaterial;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -138,7 +137,6 @@ public class EngineMantleCleanupTest {
|
||||
verify(chunk).deleteSlices(MatterCavern.class);
|
||||
verify(chunk, never()).deleteSlices(TileWrapper.class);
|
||||
verify(chunk, never()).deleteSlices(Identifier.class);
|
||||
verify(chunk, never()).deleteSlices(RiverCaveHydrology.class);
|
||||
InOrder order = inOrder(chunk);
|
||||
order.verify(chunk).raiseFlagUnchecked(eq(MantleFlag.CLEANED), any());
|
||||
order.verify(chunk).release();
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package art.arcane.iris.engine.mantle;
|
||||
|
||||
import art.arcane.iris.core.link.Identifier;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.IrisPlatform;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -17,7 +15,6 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
@@ -33,11 +30,9 @@ public class MantleWriterOverlayTest {
|
||||
|
||||
private MantleWriter writer;
|
||||
private Matter matter;
|
||||
private MantleChunk<Matter> chunk;
|
||||
private MatterSlice<PlatformBlockState> blockSlice;
|
||||
private MatterSlice<Identifier> identifierSlice;
|
||||
private MatterSlice<MatterCavern> cavernSlice;
|
||||
private MatterSlice<RiverCaveHydrology> hydrologySlice;
|
||||
|
||||
@Before
|
||||
@SuppressWarnings("unchecked")
|
||||
@@ -52,28 +47,22 @@ public class MantleWriterOverlayTest {
|
||||
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
chunk = mock(MantleChunk.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
matter = mock(Matter.class);
|
||||
blockSlice = mock(MatterSlice.class);
|
||||
identifierSlice = mock(MatterSlice.class);
|
||||
cavernSlice = mock(MatterSlice.class);
|
||||
hydrologySlice = mock(MatterSlice.class);
|
||||
|
||||
when(mantle.getWorldHeight()).thenReturn(64);
|
||||
when(mantle.getChunk(0, 0)).thenReturn(chunk);
|
||||
when(chunk.use()).thenReturn(chunk);
|
||||
when(chunk.getOrCreate(0)).thenReturn(matter);
|
||||
when(chunk.exists(0)).thenReturn(true);
|
||||
when(chunk.get(0)).thenReturn(matter);
|
||||
when(matter.hasSlice(PlatformBlockState.class)).thenReturn(true);
|
||||
when(matter.hasSlice(Identifier.class)).thenReturn(true);
|
||||
when(matter.hasSlice(MatterCavern.class)).thenReturn(true);
|
||||
when(matter.getSlice(PlatformBlockState.class)).thenReturn(blockSlice);
|
||||
when(matter.getSlice(Identifier.class)).thenReturn(identifierSlice);
|
||||
when(matter.getSlice(MatterCavern.class)).thenReturn(cavernSlice);
|
||||
doReturn(blockSlice).when(matter).slice(PlatformBlockState.class);
|
||||
doReturn(cavernSlice).when(matter).slice(MatterCavern.class);
|
||||
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
|
||||
|
||||
writer = new MantleWriter(engineMantle, mantle, 0, 0, 0, false);
|
||||
}
|
||||
@@ -116,41 +105,4 @@ public class MantleWriterOverlayTest {
|
||||
verify(identifierSlice).set(X, Y, Z, null);
|
||||
verify(blockSlice).set(X, Y, Z, replacement);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedHydrologyRejectsLaterBlockAndCavernWrites() {
|
||||
PlatformBlockState replacement = mock(PlatformBlockState.class);
|
||||
MatterCavern cavern = new MatterCavern(true, "", (byte) 3);
|
||||
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
|
||||
when(hydrologySlice.get(X, Y, Z))
|
||||
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD));
|
||||
|
||||
writer.setData(X, Y, Z, replacement);
|
||||
assertFalse(writer.carveDataIfAbsent(X, Y, Z, cavern));
|
||||
writer.setForcedCarve(X, Y, Z, cavern);
|
||||
|
||||
verify(blockSlice, never()).set(X, Y, Z, replacement);
|
||||
verify(blockSlice, never()).set(X, Y, Z, null);
|
||||
verify(identifierSlice, never()).set(X, Y, Z, null);
|
||||
verify(cavernSlice, never()).set(X, Y, Z, cavern);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hydrologyOverridesBaselineCarvedQueriesWithoutChangingIt() {
|
||||
MatterCavern baseline = new MatterCavern(true, "", (byte) 0);
|
||||
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
|
||||
when(cavernSlice.get(X, Y, Z)).thenReturn(baseline);
|
||||
when(hydrologySlice.get(X, Y, Z))
|
||||
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD));
|
||||
when(hydrologySlice.get(X, Y + 1, Z))
|
||||
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.DRY_AIR));
|
||||
|
||||
assertFalse(writer.isCarved(X, Y, Z));
|
||||
assertTrue(writer.isCarved(X, Y + 1, Z));
|
||||
|
||||
byte[] column = writer.getCarvedColumn(X, Z, Y + 2);
|
||||
assertEquals(0, column[Y]);
|
||||
assertEquals(1, column[Y + 1]);
|
||||
assertEquals(0, baseline.getLiquid());
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -74,7 +74,7 @@ public class MatterGeneratorCarvePassRadiusTest {
|
||||
|
||||
RecordingComponent carving = new RecordingComponent(ReservedFlag.CARVED, 0, 1);
|
||||
RecordingComponent conditional = new RecordingComponent(
|
||||
ReservedFlag.RIVER_HYDROLOGY,
|
||||
ReservedFlag.JIGSAW,
|
||||
1,
|
||||
0,
|
||||
160,
|
||||
@@ -111,7 +111,7 @@ public class MatterGeneratorCarvePassRadiusTest {
|
||||
|
||||
RecordingComponent carving = new RecordingComponent(ReservedFlag.CARVED, 0, 0);
|
||||
RecordingComponent conditional = new RecordingComponent(
|
||||
ReservedFlag.RIVER_HYDROLOGY,
|
||||
ReservedFlag.JIGSAW,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
|
||||
-17
@@ -21,8 +21,6 @@ package art.arcane.iris.engine.mantle.components;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -111,21 +109,6 @@ public class CaveObjectPlacementTransactionTest {
|
||||
assertEquals(61, MantleObjectComponent.caveAnchorScanUpperBound(128, 80, 20));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedHydrologyRejectsTheWholePlacement() {
|
||||
IObjectPlacer delegate = createPlacer(128, 80, 20, 90);
|
||||
when(delegate.getData(4, 30, 7, RiverCaveHydrology.class))
|
||||
.thenReturn(RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE));
|
||||
CaveObjectPlacementTransaction transaction = new CaveObjectPlacementTransaction(delegate, 20, 10);
|
||||
|
||||
transaction.set(4, 30, 7, mock(PlatformBlockState.class));
|
||||
transaction.setData(5, 30, 7, "object@1");
|
||||
|
||||
assertEquals(CaveObjectPlacementTransaction.CommitResult.REJECTED_HYDROLOGY, transaction.commit());
|
||||
verify(delegate, never()).set(anyInt(), anyInt(), anyInt(), any());
|
||||
verify(delegate, never()).setData(anyInt(), anyInt(), anyInt(), any());
|
||||
}
|
||||
|
||||
private IObjectPlacer createPlacer(int worldHeight, int surfaceHeight, int caveFloor, int caveCeiling) {
|
||||
Engine engine = mock(Engine.class);
|
||||
when(engine.getHeight()).thenReturn(worldHeight);
|
||||
|
||||
+5
-6
@@ -868,11 +868,10 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
Engine engine = createEngine(80, 70);
|
||||
int[] surfaceHeights = filledHeights(70);
|
||||
surfaceHeights[0] = 60;
|
||||
long[] boundaries = new long[256];
|
||||
Arrays.fill(boundaries, SurfaceFluidBoundaryPlan.boundary(
|
||||
SurfaceFluidBoundaryPlan.NO_BOUNDARY, Integer.MIN_VALUE));
|
||||
boundaries[0] = SurfaceFluidBoundaryPlan.boundary(60, 64);
|
||||
boundaries[16] = SurfaceFluidBoundaryPlan.boundary(61, 64);
|
||||
int[] boundaryStartY = new int[256];
|
||||
Arrays.fill(boundaryStartY, SurfaceFluidBoundaryPlan.NO_BOUNDARY);
|
||||
boundaryStartY[0] = 60;
|
||||
boundaryStartY[16] = 61;
|
||||
WriterCapture capture = createWriterCapture(80);
|
||||
CaveFluidSupportPlan supportPlan = new CaveFluidSupportPlan();
|
||||
|
||||
@@ -885,7 +884,7 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
0D,
|
||||
null,
|
||||
surfaceHeights,
|
||||
boundaries,
|
||||
boundaryStartY,
|
||||
null,
|
||||
supportPlan
|
||||
);
|
||||
|
||||
-14
@@ -1,8 +1,6 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -48,18 +46,6 @@ public class MantleObjectComponentCaveAnchorTest {
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void protectedRiverHydrologyCannotBecomeAnObjectAnchor() {
|
||||
MatterCavern water = new MatterCavern(true, "", (byte) 1);
|
||||
|
||||
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
|
||||
true, water, RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), 20, 8));
|
||||
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
|
||||
true, water, RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), 20, 8));
|
||||
assertFalse(MantleObjectComponent.acceptsCaveAnchorFluid(
|
||||
true, null, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), 20, 8));
|
||||
}
|
||||
|
||||
private static IrisBiome biome(String loadKey) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
biome.setLoadKey(loadKey);
|
||||
|
||||
-182
@@ -1,182 +0,0 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.river.cave.CavePosition;
|
||||
import art.arcane.iris.engine.river.cave.CaveVoxel;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.TectonicPlate;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MantleRiverCaveVoxelViewTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void absentCellsAboveLocalTerrainAreOpenAir() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegions()).thenReturn(new KMap<Long, TectonicPlate<Matter>>());
|
||||
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> x < 0 ? 20 : 60,
|
||||
(x, z) -> null,
|
||||
RiverCaveFluidKind.RIVER,
|
||||
(chunkX, chunkZ) -> {
|
||||
}
|
||||
);
|
||||
CavePosition cliffAir = new CavePosition(-1, 21, 0);
|
||||
CavePosition terrain = new CavePosition(-1, 20, 0);
|
||||
|
||||
assertEquals(CaveVoxel.CAVE_AIR, view.voxelAt(cliffAir));
|
||||
assertTrue(view.isOpenToSurface(cliffAir));
|
||||
assertEquals(CaveVoxel.SOLID, view.voxelAt(terrain));
|
||||
assertFalse(view.isOpenToSurface(terrain));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void carvingInputLoadsOnlyOncePerReadChunk() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
when(mantle.getLoadedRegions()).thenReturn(new KMap<Long, TectonicPlate<Matter>>());
|
||||
List<String> loaded = new ArrayList<>();
|
||||
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> 60,
|
||||
(x, z) -> null,
|
||||
RiverCaveFluidKind.RIVER,
|
||||
(chunkX, chunkZ) -> loaded.add(chunkX + "," + chunkZ)
|
||||
);
|
||||
|
||||
view.voxelAt(new CavePosition(0, 20, 0));
|
||||
view.voxelAt(new CavePosition(15, 21, 15));
|
||||
view.riverHydrologyAt(new CavePosition(1, 22, 1));
|
||||
view.voxelAt(new CavePosition(16, 20, 0));
|
||||
|
||||
assertEquals(List.of("0,0", "1,0"), loaded);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void publishedRiverActionsDoNotReplaceTheUnderlyingPlanningBaseline() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Matter matter = mock(Matter.class);
|
||||
MatterSlice<RiverCaveHydrology> hydrologySlice = mock(MatterSlice.class);
|
||||
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
|
||||
regions.put(Mantle.key(0, 0), plate);
|
||||
CavePosition position = new CavePosition(0, 20, 0);
|
||||
when(mantle.getLoadedRegions()).thenReturn(regions);
|
||||
when(plate.get(0, 0)).thenReturn(chunk);
|
||||
when(chunk.exists(1)).thenReturn(true);
|
||||
when(chunk.get(1)).thenReturn(matter);
|
||||
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
|
||||
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
|
||||
when(hydrologySlice.get(0, 4, 0)).thenReturn(RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE));
|
||||
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> 60,
|
||||
(x, z) -> null,
|
||||
RiverCaveFluidKind.RIVER,
|
||||
(chunkX, chunkZ) -> {
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(CaveVoxel.SOLID, view.voxelAt(position));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, view.riverHydrologyAt(position).action());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void configuredLavaRiverTreatsPublishedLavaAsCompatibleFluid() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Matter matter = mock(Matter.class);
|
||||
MatterSlice<MatterCavern> cavernSlice = mock(MatterSlice.class);
|
||||
PlatformBlockState lava = mock(PlatformBlockState.class);
|
||||
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
|
||||
regions.put(Mantle.key(0, 0), plate);
|
||||
when(mantle.getLoadedRegions()).thenReturn(regions);
|
||||
when(plate.get(0, 0)).thenReturn(chunk);
|
||||
when(chunk.exists(1)).thenReturn(true);
|
||||
when(chunk.get(1)).thenReturn(matter);
|
||||
when(matter.hasSlice(MatterCavern.class)).thenReturn(true);
|
||||
doReturn(cavernSlice).when(matter).getSlice(MatterCavern.class);
|
||||
when(cavernSlice.get(0, 4, 0)).thenReturn(new MatterCavern(true, "", (byte) 2));
|
||||
when(lava.materialKey()).thenReturn("minecraft:lava");
|
||||
MantleRiverCaveVoxelView view = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> 60,
|
||||
(x, z) -> lava,
|
||||
RiverCaveFluidKind.RIVER,
|
||||
(chunkX, chunkZ) -> {
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(CaveVoxel.COMPATIBLE_FLUID, view.voxelAt(new CavePosition(0, 20, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void planningRejectsHydrologyOwnedByTheOtherFluidKind() {
|
||||
Mantle<Matter> mantle = mock(Mantle.class);
|
||||
TectonicPlate<Matter> plate = mock(TectonicPlate.class);
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Matter matter = mock(Matter.class);
|
||||
MatterSlice<RiverCaveHydrology> hydrologySlice = mock(MatterSlice.class);
|
||||
KMap<Long, TectonicPlate<Matter>> regions = new KMap<>();
|
||||
regions.put(Mantle.key(0, 0), plate);
|
||||
CavePosition position = new CavePosition(0, 20, 0);
|
||||
when(mantle.getLoadedRegions()).thenReturn(regions);
|
||||
when(plate.get(0, 0)).thenReturn(chunk);
|
||||
when(chunk.exists(1)).thenReturn(true);
|
||||
when(chunk.get(1)).thenReturn(matter);
|
||||
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
|
||||
doReturn(hydrologySlice).when(matter).getSlice(RiverCaveHydrology.class);
|
||||
when(hydrologySlice.get(0, 4, 0)).thenReturn(RiverCaveHydrology.of(
|
||||
RiverCaveAction.WET_SOURCE,
|
||||
RiverCaveFluidKind.DEEP_POOL
|
||||
));
|
||||
MantleRiverCaveVoxelView riverView = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> 60,
|
||||
(x, z) -> null,
|
||||
RiverCaveFluidKind.RIVER,
|
||||
(chunkX, chunkZ) -> {
|
||||
}
|
||||
);
|
||||
MantleRiverCaveVoxelView deepPoolView = new MantleRiverCaveVoxelView(
|
||||
mantle,
|
||||
128,
|
||||
(x, z) -> 60,
|
||||
(x, z) -> null,
|
||||
RiverCaveFluidKind.DEEP_POOL,
|
||||
(chunkX, chunkZ) -> {
|
||||
}
|
||||
);
|
||||
|
||||
assertEquals(CaveVoxel.INCOMPATIBLE_FLUID, riverView.voxelAt(position));
|
||||
assertEquals(CaveVoxel.SOLID, deepPoolView.voxelAt(position));
|
||||
}
|
||||
}
|
||||
-1025
File diff suppressed because it is too large.
Load diff
+12
-39
@@ -25,8 +25,8 @@ public class SurfaceFluidBoundaryPlanTest {
|
||||
|
||||
int boundaryY = fixture.boundary(8, 8);
|
||||
assertEquals(60, boundaryY);
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, fixture.index(8, 8), 59));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, fixture.index(8, 8), 60));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, fixture.index(8, 8), 59, FLUID_HEIGHT));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, fixture.index(8, 8), 60, FLUID_HEIGHT));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -38,10 +38,10 @@ public class SurfaceFluidBoundaryPlanTest {
|
||||
|
||||
int columnIndex = fixture.index(8, 8);
|
||||
assertEquals(61, fixture.boundary(8, 8));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 60));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 61));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 64));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 65));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 60, FLUID_HEIGHT));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 61, FLUID_HEIGHT));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 64, FLUID_HEIGHT));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaryStartY, columnIndex, 65, FLUID_HEIGHT));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,28 +94,13 @@ public class SurfaceFluidBoundaryPlanTest {
|
||||
new int[CHUNK_SIZE * CHUNK_SIZE],
|
||||
new double[FIELD_SIZE * FIELD_SIZE],
|
||||
new boolean[FIELD_SIZE * FIELD_SIZE],
|
||||
new double[FIELD_SIZE * FIELD_SIZE],
|
||||
FIELD_SIZE,
|
||||
0,
|
||||
new long[CHUNK_SIZE * CHUNK_SIZE]
|
||||
FLUID_HEIGHT,
|
||||
new int[CHUNK_SIZE * CHUNK_SIZE]
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terracedReservoirProtectsOnlyToItsLocalHead() {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.setFieldSurface(7, 8, 60D);
|
||||
fixture.setFieldFluidHeight(7, 8, 67D);
|
||||
|
||||
fixture.resolve();
|
||||
|
||||
int columnIndex = fixture.index(8, 8);
|
||||
assertEquals(61, fixture.boundary(8, 8));
|
||||
assertEquals(67, fixture.boundaryEnd(8, 8));
|
||||
assertTrue(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 67));
|
||||
assertFalse(SurfaceFluidBoundaryPlan.protects(fixture.boundaries, columnIndex, 68));
|
||||
}
|
||||
|
||||
private void assertEdgeBoundary(int localX, int localZ, int neighborLocalX, int neighborLocalZ) {
|
||||
Fixture fixture = new Fixture();
|
||||
fixture.setFieldSurface(neighborLocalX, neighborLocalZ, 60D);
|
||||
@@ -126,14 +111,12 @@ public class SurfaceFluidBoundaryPlanTest {
|
||||
private static final class Fixture {
|
||||
private final int[] chunkSurfaceHeights = new int[CHUNK_SIZE * CHUNK_SIZE];
|
||||
private final double[] fieldSurfaceHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
private final double[] fieldFluidHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
private final boolean[] fieldHasFluid = new boolean[FIELD_SIZE * FIELD_SIZE];
|
||||
private final long[] boundaries = new long[CHUNK_SIZE * CHUNK_SIZE];
|
||||
private final int[] boundaryStartY = new int[CHUNK_SIZE * CHUNK_SIZE];
|
||||
|
||||
private Fixture() {
|
||||
Arrays.fill(chunkSurfaceHeights, 70);
|
||||
Arrays.fill(fieldSurfaceHeights, 70D);
|
||||
Arrays.fill(fieldFluidHeights, FLUID_HEIGHT);
|
||||
Arrays.fill(fieldHasFluid, true);
|
||||
}
|
||||
|
||||
@@ -154,30 +137,20 @@ public class SurfaceFluidBoundaryPlanTest {
|
||||
fieldHasFluid[(fieldX * FIELD_SIZE) + fieldZ] = hasFluid;
|
||||
}
|
||||
|
||||
private void setFieldFluidHeight(int localX, int localZ, double fluidHeight) {
|
||||
int fieldX = localX + PADDING;
|
||||
int fieldZ = localZ + PADDING;
|
||||
fieldFluidHeights[(fieldX * FIELD_SIZE) + fieldZ] = fluidHeight;
|
||||
}
|
||||
|
||||
private void resolve() {
|
||||
SurfaceFluidBoundaryPlan.fill(
|
||||
chunkSurfaceHeights,
|
||||
fieldSurfaceHeights,
|
||||
fieldHasFluid,
|
||||
fieldFluidHeights,
|
||||
FIELD_SIZE,
|
||||
PADDING,
|
||||
boundaries
|
||||
FLUID_HEIGHT,
|
||||
boundaryStartY
|
||||
);
|
||||
}
|
||||
|
||||
private int boundary(int localX, int localZ) {
|
||||
return SurfaceFluidBoundaryPlan.startY(boundaries[index(localX, localZ)]);
|
||||
}
|
||||
|
||||
private int boundaryEnd(int localX, int localZ) {
|
||||
return SurfaceFluidBoundaryPlan.endY(boundaries[index(localX, localZ)]);
|
||||
return boundaryStartY[index(localX, localZ)];
|
||||
}
|
||||
|
||||
private int index(int localX, int localZ) {
|
||||
|
||||
-44
@@ -5,8 +5,6 @@ import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
@@ -109,52 +107,10 @@ public class IrisCarveModifierBoundarySupportTest {
|
||||
assertTrue(IrisCarveModifier.hasStableCaveFloorSupport(output, 0, 6, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverGuardOnlyAcceptsStableSolidBoundaryLayers() {
|
||||
RiverCaveHydrology guard = RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD);
|
||||
PlatformBlockState stone = state("minecraft:stone", true);
|
||||
PlatformBlockState sand = state("minecraft:sand", true);
|
||||
PlatformBlockState water = state("minecraft:water", false, true);
|
||||
|
||||
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, false));
|
||||
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, stone, true));
|
||||
assertTrue(IrisCarveModifier.canReplaceRiverGuard(guard, sand, false));
|
||||
assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, sand, true));
|
||||
assertFalse(IrisCarveModifier.canReplaceRiverGuard(guard, water, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverBiomeInheritanceIsColumnCoherentAndHonorsLimits() {
|
||||
long seed = 7845123L;
|
||||
boolean cell = IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0.5D);
|
||||
for (int x = 8; x < 12; x++) {
|
||||
for (int z = 12; z < 16; z++) {
|
||||
assertTrue(cell == IrisCarveModifier.selectsParentRiverBiome(seed, x, z, 0.5D));
|
||||
}
|
||||
}
|
||||
assertFalse(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 0D));
|
||||
assertTrue(IrisCarveModifier.selectsParentRiverBiome(seed, 8, 12, 1D));
|
||||
|
||||
boolean inherited = false;
|
||||
boolean overridden = false;
|
||||
for (int x = -128; x <= 128; x += 4) {
|
||||
boolean selected = IrisCarveModifier.selectsParentRiverBiome(seed, x, 0, 0.5D);
|
||||
inherited |= selected;
|
||||
overridden |= !selected;
|
||||
}
|
||||
assertTrue(inherited);
|
||||
assertTrue(overridden);
|
||||
}
|
||||
|
||||
private PlatformBlockState state(String key, boolean solid) {
|
||||
return state(key, solid, false);
|
||||
}
|
||||
|
||||
private PlatformBlockState state(String key, boolean solid, boolean fluid) {
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
doReturn(key).when(state).key();
|
||||
doReturn(solid).when(state).isSolid();
|
||||
doReturn(fluid).when(state).isFluid();
|
||||
return state;
|
||||
}
|
||||
}
|
||||
+1
-4
@@ -15,7 +15,6 @@ import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.spi.PlatformRegistries;
|
||||
import art.arcane.iris.util.project.hunk.Hunk;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import org.junit.AfterClass;
|
||||
@@ -70,11 +69,10 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
Map<String, IrisBiome> customBiomes = new HashMap<>();
|
||||
customBiomes.put("shared", biome);
|
||||
CarveWallBuffer walls = new CarveWallBuffer(1);
|
||||
walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0), false);
|
||||
walls.put(0, 1, 0, new MatterCavern(true, "shared", (byte) 0));
|
||||
Method paintBoundaryZone = IrisCarveModifier.class.getDeclaredMethod(
|
||||
"paintBoundaryZone",
|
||||
Hunk.class,
|
||||
MantleChunk.class,
|
||||
CarveWallBuffer.class,
|
||||
int.class,
|
||||
int.class,
|
||||
@@ -90,7 +88,6 @@ public class IrisCarveModifierInferenceIsolationTest {
|
||||
paintBoundaryZone.invoke(
|
||||
modifier,
|
||||
mock(Hunk.class),
|
||||
mock(MantleChunk.class),
|
||||
walls,
|
||||
0,
|
||||
0,
|
||||
|
||||
-115
@@ -1,115 +0,0 @@
|
||||
package art.arcane.iris.engine.modifier;
|
||||
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisCarveModifierRiverHydrologyTest {
|
||||
@Test
|
||||
public void overlayCompositionDoesNotMutateBaselineCavern() {
|
||||
MatterCavern baseline = new MatterCavern(true, "baseline", (byte) 2);
|
||||
|
||||
assertSame(baseline, IrisCarveModifier.composeCavern(baseline, null));
|
||||
assertNull(IrisCarveModifier.composeCavern(
|
||||
baseline, RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD)));
|
||||
|
||||
MatterCavern wet = IrisCarveModifier.composeCavern(
|
||||
baseline, new RiverCaveHydrology(
|
||||
RiverCaveAction.WET_SOURCE,
|
||||
"iris:flooded",
|
||||
RiverCaveFluidKind.DEEP_POOL
|
||||
));
|
||||
MatterCavern dry = IrisCarveModifier.composeCavern(
|
||||
baseline, RiverCaveHydrology.of(RiverCaveAction.DRY_AIR));
|
||||
|
||||
assertEquals(1, wet.getLiquid());
|
||||
assertEquals("iris:flooded", wet.getCustomBiome());
|
||||
assertEquals(3, dry.getLiquid());
|
||||
assertEquals(2, baseline.getLiquid());
|
||||
assertEquals("baseline", baseline.getCustomBiome());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hydrologyActionsResolveDistinctFluidStates() {
|
||||
PlatformBlockState current = mock(PlatformBlockState.class);
|
||||
PlatformBlockState source = mock(PlatformBlockState.class);
|
||||
PlatformBlockState falling = mock(PlatformBlockState.class);
|
||||
PlatformBlockState air = mock(PlatformBlockState.class);
|
||||
when(source.key()).thenReturn("minecraft:water[level=0]");
|
||||
when(source.withProperty("level", "8")).thenReturn(falling);
|
||||
|
||||
assertSame(source, IrisCarveModifier.resolveHydrologyState(
|
||||
RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE), current, source, air));
|
||||
assertSame(falling, IrisCarveModifier.resolveHydrologyState(
|
||||
RiverCaveHydrology.of(RiverCaveAction.FALLING_FLUID), current, source, air));
|
||||
assertSame(air, IrisCarveModifier.resolveHydrologyState(
|
||||
RiverCaveHydrology.of(RiverCaveAction.DRY_AIR), current, source, air));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waterloggingFollowsOnlyExplicitResultingWater() {
|
||||
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
|
||||
PlatformBlockState dry = mock(PlatformBlockState.class);
|
||||
PlatformBlockState water = mock(PlatformBlockState.class);
|
||||
PlatformBlockState lava = mock(PlatformBlockState.class);
|
||||
when(waterlogged.key()).thenReturn("minecraft:seagrass[waterlogged=true]");
|
||||
when(waterlogged.withProperty("waterlogged", "false")).thenReturn(dry);
|
||||
when(water.isWater()).thenReturn(true);
|
||||
|
||||
assertSame(waterlogged, IrisCarveModifier.normalizeWaterlogging(waterlogged, water));
|
||||
assertSame(dry, IrisCarveModifier.normalizeWaterlogging(waterlogged, null));
|
||||
assertSame(dry, IrisCarveModifier.normalizeWaterlogging(waterlogged, lava));
|
||||
assertSame(dry, IrisCarveModifier.resolveHydrologyState(
|
||||
RiverCaveHydrology.of(RiverCaveAction.SEAL_GUARD), waterlogged, water, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noOverlayLeavesBaselineWaterloggingUntouched() {
|
||||
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
|
||||
PlatformBlockState water = mock(PlatformBlockState.class);
|
||||
MatterCavern flooded = new MatterCavern(true, "", (byte) 1);
|
||||
|
||||
assertSame(waterlogged, IrisCarveModifier.normalizeHydrologyWaterlogging(
|
||||
waterlogged,
|
||||
flooded,
|
||||
null,
|
||||
water
|
||||
));
|
||||
verify(waterlogged, never()).withProperty("waterlogged", "false");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitOverlayUsesComposedFluidIntentForWaterlogging() {
|
||||
PlatformBlockState waterlogged = mock(PlatformBlockState.class);
|
||||
PlatformBlockState dry = mock(PlatformBlockState.class);
|
||||
PlatformBlockState water = mock(PlatformBlockState.class);
|
||||
MatterCavern flooded = new MatterCavern(true, "", (byte) 1);
|
||||
when(waterlogged.key()).thenReturn("minecraft:seagrass[waterlogged=true]");
|
||||
when(waterlogged.withProperty("waterlogged", "false")).thenReturn(dry);
|
||||
when(water.isWater()).thenReturn(true);
|
||||
|
||||
assertSame(waterlogged, IrisCarveModifier.normalizeHydrologyWaterlogging(
|
||||
waterlogged,
|
||||
flooded,
|
||||
RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE),
|
||||
water
|
||||
));
|
||||
assertSame(dry, IrisCarveModifier.normalizeHydrologyWaterlogging(
|
||||
waterlogged,
|
||||
flooded,
|
||||
RiverCaveHydrology.of(RiverCaveAction.DRY_AIR),
|
||||
water
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -22,21 +22,19 @@ public class IrisCarveScratchTest {
|
||||
int y = 20 + index;
|
||||
int z = (index * 3) & 15;
|
||||
MatterCavern cavern = new MatterCavern(true, "cave-" + index, (byte) 0);
|
||||
buffer.put(x, y, z, cavern, (index & 1) == 0);
|
||||
buffer.put(x, y, z, cavern);
|
||||
expected.put(key(x, y, z), cavern);
|
||||
}
|
||||
|
||||
MatterCavern replacement = new MatterCavern(true, "replacement", (byte) 0);
|
||||
buffer.put(5, 25, 15, replacement, true);
|
||||
buffer.put(5, 25, 15, replacement);
|
||||
expected.put(key(5, 25, 15), replacement);
|
||||
|
||||
assertSame(replacement, buffer.get(5, 25, 15));
|
||||
assertTrue(buffer.isRiverBoundary(5, 25, 15));
|
||||
assertNull(buffer.get(5, 26, 15));
|
||||
assertFalse(buffer.isRiverBoundary(5, 26, 15));
|
||||
|
||||
Map<String, MatterCavern> actual = new HashMap<>();
|
||||
buffer.forEach((x, y, z, cavern, riverBoundary) -> actual.put(key(x, y, z), cavern));
|
||||
buffer.forEach((x, y, z, cavern) -> actual.put(key(x, y, z), cavern));
|
||||
assertEquals(expected.keySet(), actual.keySet());
|
||||
for (Map.Entry<String, MatterCavern> entry : expected.entrySet()) {
|
||||
assertSame(entry.getValue(), actual.get(entry.getKey()));
|
||||
@@ -49,7 +47,7 @@ public class IrisCarveScratchTest {
|
||||
MatterCavern cavern = new MatterCavern(true, "cave", (byte) 0);
|
||||
scratch.columnMasks[0].add(12);
|
||||
scratch.boundaryMasks[0].add(13);
|
||||
scratch.walls.put(1, 12, 2, cavern, true);
|
||||
scratch.walls.put(1, 12, 2, cavern);
|
||||
scratch.customBiomeCache.put("cave", null);
|
||||
scratch.customCaveBiomePresent = true;
|
||||
|
||||
@@ -60,7 +58,7 @@ public class IrisCarveScratchTest {
|
||||
assertTrue(scratch.customBiomeCache.isEmpty());
|
||||
assertFalse(scratch.customCaveBiomePresent);
|
||||
int[] wallCount = new int[1];
|
||||
scratch.walls.forEach((x, y, z, value, riverBoundary) -> wallCount[0]++);
|
||||
scratch.walls.forEach((x, y, z, value) -> wallCount[0]++);
|
||||
assertEquals(0, wallCount[0]);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.CALLS_REAL_METHODS;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IObjectPlacerFluidHeightTest {
|
||||
@Test
|
||||
public void missingEngineOrComplexFallsBackToThePlacersScalarHead() {
|
||||
IObjectPlacer withoutEngine = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
|
||||
when(withoutEngine.getFluidHeight()).thenReturn(63);
|
||||
when(withoutEngine.getEngine()).thenReturn(null);
|
||||
|
||||
assertEquals(63, withoutEngine.getFluidHeight(12, -7));
|
||||
|
||||
IObjectPlacer withoutComplex = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
|
||||
Engine engine = mock(Engine.class);
|
||||
when(withoutComplex.getFluidHeight()).thenReturn(63);
|
||||
when(withoutComplex.getEngine()).thenReturn(engine);
|
||||
when(engine.getComplex()).thenReturn(null);
|
||||
|
||||
assertEquals(63, withoutComplex.getFluidHeight(12, -7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void engineLocalPlacersUseTheRoundedPerColumnRiverHead() {
|
||||
IObjectPlacer placer = placer(127, 127, 130.6D);
|
||||
|
||||
assertEquals(131, placer.getFluidHeight(12, -7));
|
||||
|
||||
verify(riverHead(placer)).get(12, -7);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void worldCoordinatePlacersShiftTheLocalRiverHeadExactlyOnce() {
|
||||
IObjectPlacer placer = placer(63, 127, 130.6D);
|
||||
|
||||
assertEquals(67, placer.getFluidHeight(12, -7));
|
||||
|
||||
verify(riverHead(placer)).get(12, -7);
|
||||
}
|
||||
|
||||
private static IObjectPlacer placer(int placerFluidHeight, int dimensionFluidHeight, double riverFluidHeight) {
|
||||
IObjectPlacer placer = mock(IObjectPlacer.class, CALLS_REAL_METHODS);
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> riverHead = mock(ProceduralStream.class);
|
||||
|
||||
when(placer.getFluidHeight()).thenReturn(placerFluidHeight);
|
||||
when(placer.getEngine()).thenReturn(engine);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(dimension.getFluidHeight()).thenReturn(dimensionFluidHeight);
|
||||
when(complex.getRiverWaterSurfaceStream()).thenReturn(riverHead);
|
||||
when(riverHead.get(12, -7)).thenReturn(riverFluidHeight);
|
||||
return placer;
|
||||
}
|
||||
|
||||
private static ProceduralStream<Double> riverHead(IObjectPlacer placer) {
|
||||
return placer.getEngine().getComplex().getRiverWaterSurfaceStream();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +69,28 @@ public class IrisDepositTuningTest {
|
||||
assertEquals(firstWorld, new IrisDepositGenerator.ClumpCacheKey(41L, 4, 8));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clumpSaltIsStableAcrossEquivalentConfigInstances() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDepositGenerator first = generatorWithState(data, false, "minecraft:granite");
|
||||
IrisDepositGenerator second = generatorWithState(data, false, "minecraft:granite");
|
||||
|
||||
assertEquals(first.stableClumpSalt(data), second.stableClumpSalt(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clumpSaltIncludesAuthoredConfigAndPalette() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
IrisDepositGenerator granite = generatorWithState(data, false, "minecraft:granite");
|
||||
IrisDepositGenerator andesite = generatorWithState(data, false, "minecraft:andesite");
|
||||
IrisDepositGenerator alteredShape = generatorWithState(data, false, "minecraft:granite");
|
||||
alteredShape.setShape(IrisDepositShape.VANILLA_SCATTERED);
|
||||
|
||||
assertEquals(3112546198474861350L, granite.stableClumpSalt(data));
|
||||
assertNotEquals(granite.stableClumpSalt(data), andesite.stableClumpSalt(data));
|
||||
assertNotEquals(granite.stableClumpSalt(data), alteredShape.stableClumpSalt(data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyOreDepositPalettesReceiveBiomeTuning() {
|
||||
IrisData data = mock(IrisData.class);
|
||||
@@ -143,10 +165,15 @@ public class IrisDepositTuningTest {
|
||||
}
|
||||
|
||||
private IrisDepositGenerator generatorWithState(IrisData data, boolean ore) {
|
||||
return generatorWithState(data, ore, ore ? "minecraft:iron_ore" : "minecraft:stone");
|
||||
}
|
||||
|
||||
private IrisDepositGenerator generatorWithState(IrisData data, boolean ore, String key) {
|
||||
IrisBlockData block = mock(IrisBlockData.class);
|
||||
PlatformBlockState state = mock(PlatformBlockState.class);
|
||||
when(block.getBlockData(data)).thenReturn(state);
|
||||
when(state.isOre()).thenReturn(ore);
|
||||
when(state.key()).thenReturn(key);
|
||||
IrisDepositGenerator generator = new IrisDepositGenerator();
|
||||
generator.getPalette().add(block);
|
||||
return generator;
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public final class IrisMaterialPaletteGeneratorCacheTest {
|
||||
private static final long GENERATOR_SALT = -23_498_896L;
|
||||
|
||||
@Test
|
||||
public void callerSeedsDoNotShareAFirstWinnerGenerator() {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
|
||||
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
|
||||
assertSame(fixture.secondGenerator, fixture.palette.getLayerGenerator(new RNG(29L), fixture.data));
|
||||
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reverseInitializationOrderKeepsSeedAssignments() {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
|
||||
assertSame(fixture.secondGenerator, fixture.palette.getLayerGenerator(new RNG(29L), fixture.data));
|
||||
assertSame(fixture.firstGenerator, fixture.palette.getLayerGenerator(new RNG(11L), fixture.data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentInitializationKeepsSeedAssignments() throws Exception {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<CNG> first = executor.submit(() -> {
|
||||
assertTrue(start.await(5L, TimeUnit.SECONDS));
|
||||
return fixture.palette.getLayerGenerator(new RNG(11L), fixture.data);
|
||||
});
|
||||
Future<CNG> second = executor.submit(() -> {
|
||||
assertTrue(start.await(5L, TimeUnit.SECONDS));
|
||||
return fixture.palette.getLayerGenerator(new RNG(29L), fixture.data);
|
||||
});
|
||||
start.countDown();
|
||||
|
||||
assertSame(fixture.firstGenerator, first.get(5L, TimeUnit.SECONDS));
|
||||
assertSame(fixture.secondGenerator, second.get(5L, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GeneratorFixture {
|
||||
private final IrisData data = mock(IrisData.class);
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||
private final CNG firstGenerator = mock(CNG.class);
|
||||
private final CNG secondGenerator = mock(CNG.class);
|
||||
private final IrisMaterialPalette palette = new IrisMaterialPalette().qclear().setStyle(style);
|
||||
|
||||
private GeneratorFixture() {
|
||||
when(data.getEngine()).thenReturn(engine);
|
||||
Map<Long, CNG> generators = Map.of(
|
||||
11L + GENERATOR_SALT, firstGenerator,
|
||||
29L + GENERATOR_SALT, secondGenerator
|
||||
);
|
||||
when(style.create(any(RNG.class), same(data), same(engine))).thenAnswer(invocation -> {
|
||||
RNG rng = invocation.getArgument(0);
|
||||
return generators.get(rng.getSeed());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -43,7 +43,7 @@ public class IrisMathNoiseHotPathParityTest {
|
||||
.setOpacity(0.91D)
|
||||
.setComposite(new KList<IrisNoiseGenerator>().qadd(noiseGenerator));
|
||||
|
||||
assertEquals(0.451949817597527D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
|
||||
assertEquals(0.5671980255964687D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public final class IrisNoiseGeneratorCacheTest {
|
||||
private static final long GENERATOR_SALT = 33_955_677L;
|
||||
private static final long CONFIGURED_SEED = 7L;
|
||||
private static final int OCTAVES = 3;
|
||||
|
||||
@Test
|
||||
public void callerSeedsDoNotShareAFirstWinnerGenerator() {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
|
||||
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
|
||||
assertSame(fixture.secondGenerator, fixture.generator.getGenerator(29L, fixture.data));
|
||||
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reverseInitializationOrderKeepsSeedAssignments() {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
|
||||
assertSame(fixture.secondGenerator, fixture.generator.getGenerator(29L, fixture.data));
|
||||
assertSame(fixture.firstGenerator, fixture.generator.getGenerator(11L, fixture.data));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void concurrentInitializationKeepsSeedAssignments() throws Exception {
|
||||
GeneratorFixture fixture = new GeneratorFixture();
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<CNG> first = executor.submit(() -> {
|
||||
assertTrue(start.await(5L, TimeUnit.SECONDS));
|
||||
return fixture.generator.getGenerator(11L, fixture.data);
|
||||
});
|
||||
Future<CNG> second = executor.submit(() -> {
|
||||
assertTrue(start.await(5L, TimeUnit.SECONDS));
|
||||
return fixture.generator.getGenerator(29L, fixture.data);
|
||||
});
|
||||
start.countDown();
|
||||
|
||||
assertSame(fixture.firstGenerator, first.get(5L, TimeUnit.SECONDS));
|
||||
assertSame(fixture.secondGenerator, second.get(5L, TimeUnit.SECONDS));
|
||||
} finally {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class GeneratorFixture {
|
||||
private final IrisData data = mock(IrisData.class);
|
||||
private final Engine engine = mock(Engine.class);
|
||||
private final IrisGeneratorStyle style = mock(IrisGeneratorStyle.class);
|
||||
private final CNG firstGenerator = mock(CNG.class);
|
||||
private final CNG secondGenerator = mock(CNG.class);
|
||||
private final IrisNoiseGenerator generator = new IrisNoiseGenerator()
|
||||
.setStyle(style)
|
||||
.setSeed(CONFIGURED_SEED)
|
||||
.setOctaves(OCTAVES);
|
||||
|
||||
private GeneratorFixture() {
|
||||
when(data.getEngine()).thenReturn(engine);
|
||||
when(firstGenerator.oct(OCTAVES)).thenReturn(firstGenerator);
|
||||
when(secondGenerator.oct(OCTAVES)).thenReturn(secondGenerator);
|
||||
Map<Long, CNG> generators = Map.of(
|
||||
11L + GENERATOR_SALT - CONFIGURED_SEED, firstGenerator,
|
||||
29L + GENERATOR_SALT - CONFIGURED_SEED, secondGenerator
|
||||
);
|
||||
when(style.createNoCache(any(RNG.class), same(data))).thenAnswer(invocation -> {
|
||||
RNG rng = invocation.getArgument(0);
|
||||
return generators.get(rng.getSeed());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.loader.ResourceLoader;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import com.google.gson.Gson;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisRiverConfigurationTest {
|
||||
@Test
|
||||
public void defaultsKeepRiverGenerationDisabledAndContained() {
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
|
||||
assertNotNull(dimension.getRivers());
|
||||
assertFalse(dimension.getRivers().isEnabled());
|
||||
assertEquals(IrisRiverWaterMode.FIXED, dimension.getRivers().getWater().getMode());
|
||||
assertEquals(63, dimension.getRivers().getWater().getFluidHeight());
|
||||
assertEquals("water", dimension.getRivers().getWater().getFluidPalette().getPalette().getFirst().getBlock());
|
||||
assertFalse(dimension.getRivers().getTopology().isRequireOcean());
|
||||
assertEquals(512, dimension.getRivers().getTopology().getCellSize());
|
||||
assertEquals(16, dimension.getRivers().getTopology().getMaxRouteReaches());
|
||||
assertEquals(0, dimension.getRivers().getTopology().getMinimumSourcesPerTile());
|
||||
assertEquals(64, dimension.getRivers().getTopology().getRoutingBasinCells());
|
||||
assertEquals(8D, dimension.getRivers().getTopology().getRoutingPlateauHeight(), 0D);
|
||||
assertEquals(0.05D, dimension.getRivers().getTopology().getSource().getChance(), 0D);
|
||||
assertEquals(0.035D, dimension.getRivers().getTopology().getSource().getInfluence(), 0D);
|
||||
assertEquals(10D, dimension.getRivers().getTerrain().getMaxChannelWidth(), 0D);
|
||||
assertEquals(4D, dimension.getRivers().getTerrain().getMaxBankWidth(), 0D);
|
||||
assertEquals(10D, dimension.getRivers().getTerrain().getMaxDepth(), 0D);
|
||||
assertTrue(dimension.getRivers().getTerrain().getWorms().isEmpty());
|
||||
assertEquals(IrisRiverCaveMode.SEALED, dimension.getRivers().getCaves().getMode());
|
||||
assertEquals(IrisRiverCaveFallback.SEALED, dimension.getRivers().getCaves().getFallback());
|
||||
assertEquals(IrisRiverExistingFluidPolicy.REJECT,
|
||||
dimension.getRivers().getCaves().getExistingFluidPolicy());
|
||||
assertNotNull(dimension.getRivers().getCaves().getDeepPools());
|
||||
assertFalse(dimension.getRivers().getCaves().getDeepPools().isEnabled());
|
||||
assertEquals(-224, dimension.getRivers().getCaves().getDeepPools().getMinimumFluidY());
|
||||
assertEquals(-104, dimension.getRivers().getCaves().getDeepPools().getMaximumFluidY());
|
||||
assertEquals("lava", dimension.getRivers().getCaves().getDeepPools()
|
||||
.getFluidPalette().getPalette().getFirst().getBlock());
|
||||
assertTrue(dimension.getRivers().getBiomes().getAllBiomeIds().isEmpty());
|
||||
assertNull(new IrisRegion().getRiverOverride());
|
||||
assertNull(new IrisBiome().getRiverOverride());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializesTypedDimensionRegionAndBiomeSettings() {
|
||||
Gson gson = new Gson();
|
||||
IrisDimension dimension = gson.fromJson("""
|
||||
{
|
||||
"rivers": {
|
||||
"enabled": true,
|
||||
"topology": {
|
||||
"cellSize": 512,
|
||||
"minimumSourcesPerTile": 2,
|
||||
"routingBasinCells": 96,
|
||||
"routingPlateauHeight": 12,
|
||||
"requireOcean": false,
|
||||
"source": {"chance": 0.27, "influence": 0.4}
|
||||
},
|
||||
"terrain": {
|
||||
"channelRadiusBonus": 3,
|
||||
"maxChannelWidth": 9,
|
||||
"maxBankWidth": 2.5,
|
||||
"maxDepth": 8,
|
||||
"maxIncision": 36,
|
||||
"worms": [
|
||||
{
|
||||
"seed": 73,
|
||||
"weight": 2.5,
|
||||
"wavelength": 1536,
|
||||
"detailWavelength": 192,
|
||||
"tortuosity": 0.65,
|
||||
"detailTortuosity": 0.2,
|
||||
"maxOffset": 420,
|
||||
"segments": 56,
|
||||
"widthMultiplier": 1.4,
|
||||
"bankMultiplier": 1.2,
|
||||
"depthMultiplier": 0.8,
|
||||
"bodyWavelength": 704,
|
||||
"bodyDetailWavelength": 18,
|
||||
"bodyDetailInfluence": 0.82,
|
||||
"widthVariation": 0.75,
|
||||
"bankVariation": 0.65,
|
||||
"depthVariation": 0.55,
|
||||
"roofVariation": 0.45
|
||||
}
|
||||
],
|
||||
"terminalMode": "SUPPRESS"
|
||||
},
|
||||
"water": {
|
||||
"mode": "TERRACED",
|
||||
"fluidHeight": -48,
|
||||
"fluidPalette": {
|
||||
"palette": [{"block": "minecraft:lava"}]
|
||||
},
|
||||
"poolLength": 80
|
||||
},
|
||||
"biomes": {
|
||||
"channel": ["river/channel"],
|
||||
"floodedCave": ["river/grotto"]
|
||||
},
|
||||
"caves": {
|
||||
"mode": "FLOOD_CLOSED_COMPONENT",
|
||||
"maxFloodVolume": 2048,
|
||||
"existingFluidPolicy": "ALLOW_SAME",
|
||||
"deepPools": {
|
||||
"enabled": true,
|
||||
"reach": {
|
||||
"chance": 0.4,
|
||||
"influence": 0.1
|
||||
},
|
||||
"minimumSpacing": 896,
|
||||
"maximumPerReach": 2,
|
||||
"minimumFluidY": -220,
|
||||
"maximumFluidY": -112,
|
||||
"searchRadius": 24,
|
||||
"searchAttempts": 18,
|
||||
"horizontalRadius": 28,
|
||||
"verticalRadius": 11,
|
||||
"dryHeadroom": 5,
|
||||
"shapeVariation": 0.7,
|
||||
"warpStrength": 9,
|
||||
"maximumVolume": 65536,
|
||||
"fluidPalette": {
|
||||
"palette": [{"block": "minecraft:lava"}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""", IrisDimension.class);
|
||||
IrisRegion region = gson.fromJson("""
|
||||
{
|
||||
"riverOverride": {
|
||||
"allowSources": false,
|
||||
"routingPolicy": "AVOID",
|
||||
"widthMultiplier": 0.75,
|
||||
"bankBiomes": ["river/region-bank"]
|
||||
}
|
||||
}
|
||||
""", IrisRegion.class);
|
||||
IrisBiome biome = gson.fromJson("""
|
||||
{
|
||||
"riverOverride": {
|
||||
"routingPolicy": "BLOCK",
|
||||
"caveEntryMultiplier": 0.2,
|
||||
"floodedCaveBiomes": []
|
||||
}
|
||||
}
|
||||
""", IrisBiome.class);
|
||||
|
||||
assertTrue(dimension.getRivers().isEnabled());
|
||||
assertEquals(512, dimension.getRivers().getTopology().getCellSize());
|
||||
assertEquals(2, dimension.getRivers().getTopology().getMinimumSourcesPerTile());
|
||||
assertEquals(96, dimension.getRivers().getTopology().getRoutingBasinCells());
|
||||
assertEquals(12D, dimension.getRivers().getTopology().getRoutingPlateauHeight(), 0D);
|
||||
assertFalse(dimension.getRivers().getTopology().isRequireOcean());
|
||||
assertEquals(0.27D, dimension.getRivers().getTopology().getSource().getChance(), 0D);
|
||||
assertEquals(9D, dimension.getRivers().getTerrain().getMaxChannelWidth(), 0D);
|
||||
assertEquals(3D, dimension.getRivers().getTerrain().getChannelRadiusBonus(), 0D);
|
||||
assertEquals(2.5D, dimension.getRivers().getTerrain().getMaxBankWidth(), 0D);
|
||||
assertEquals(8D, dimension.getRivers().getTerrain().getMaxDepth(), 0D);
|
||||
assertEquals(36, dimension.getRivers().getTerrain().getMaxIncision());
|
||||
IrisRiverWorm worm = dimension.getRivers().getTerrain().getWorms().get(0);
|
||||
assertEquals(73L, worm.getSeed());
|
||||
assertEquals(2.5D, worm.getWeight(), 0D);
|
||||
assertEquals(1536D, worm.getWavelength(), 0D);
|
||||
assertEquals(192D, worm.getDetailWavelength(), 0D);
|
||||
assertEquals(0.65D, worm.getTortuosity(), 0D);
|
||||
assertEquals(0.2D, worm.getDetailTortuosity(), 0D);
|
||||
assertEquals(420D, worm.getMaxOffset(), 0D);
|
||||
assertEquals(56, worm.getSegments());
|
||||
assertEquals(1.4D, worm.getWidthMultiplier(), 0D);
|
||||
assertEquals(1.2D, worm.getBankMultiplier(), 0D);
|
||||
assertEquals(0.8D, worm.getDepthMultiplier(), 0D);
|
||||
assertEquals(704D, worm.getBodyWavelength(), 0D);
|
||||
assertEquals(18D, worm.getBodyDetailWavelength(), 0D);
|
||||
assertEquals(0.82D, worm.getBodyDetailInfluence(), 0D);
|
||||
assertEquals(0.75D, worm.getWidthVariation(), 0D);
|
||||
assertEquals(0.65D, worm.getBankVariation(), 0D);
|
||||
assertEquals(0.55D, worm.getDepthVariation(), 0D);
|
||||
assertEquals(0.45D, worm.getRoofVariation(), 0D);
|
||||
assertEquals(IrisRiverTerminalMode.SUPPRESS,
|
||||
dimension.getRivers().getTerrain().getTerminalMode());
|
||||
assertEquals(IrisRiverWaterMode.TERRACED, dimension.getRivers().getWater().getMode());
|
||||
assertEquals(-48, dimension.getRivers().getWater().getFluidHeight());
|
||||
assertEquals("minecraft:lava",
|
||||
dimension.getRivers().getWater().getFluidPalette().getPalette().getFirst().getBlock());
|
||||
assertEquals(Set.of("river/channel", "river/grotto"),
|
||||
Set.copyOf(dimension.getRivers().getBiomes().getAllBiomeIds()));
|
||||
assertEquals(IrisRiverCaveMode.FLOOD_CLOSED_COMPONENT,
|
||||
dimension.getRivers().getCaves().getMode());
|
||||
assertEquals(IrisRiverExistingFluidPolicy.ALLOW_SAME,
|
||||
dimension.getRivers().getCaves().getExistingFluidPolicy());
|
||||
IrisRiverDeepPools deepPools = dimension.getRivers().getCaves().getDeepPools();
|
||||
assertTrue(deepPools.isEnabled());
|
||||
assertEquals(0.4D, deepPools.getReach().getChance(), 0D);
|
||||
assertEquals(0.1D, deepPools.getReach().getInfluence(), 0D);
|
||||
assertEquals(896, deepPools.getMinimumSpacing());
|
||||
assertEquals(2, deepPools.getMaximumPerReach());
|
||||
assertEquals(-220, deepPools.getMinimumFluidY());
|
||||
assertEquals(-112, deepPools.getMaximumFluidY());
|
||||
assertEquals(24, deepPools.getSearchRadius());
|
||||
assertEquals(18, deepPools.getSearchAttempts());
|
||||
assertEquals(28, deepPools.getHorizontalRadius());
|
||||
assertEquals(11, deepPools.getVerticalRadius());
|
||||
assertEquals(5, deepPools.getDryHeadroom());
|
||||
assertEquals(0.7D, deepPools.getShapeVariation(), 0D);
|
||||
assertEquals(9D, deepPools.getWarpStrength(), 0D);
|
||||
assertEquals(65536, deepPools.getMaximumVolume());
|
||||
assertEquals("minecraft:lava", deepPools.getFluidPalette().getPalette().getFirst().getBlock());
|
||||
|
||||
assertEquals(Boolean.FALSE, region.getRiverOverride().getAllowSources());
|
||||
assertEquals(IrisRiverRoutingPolicy.AVOID, region.getRiverOverride().getRoutingPolicy());
|
||||
assertEquals(Double.valueOf(0.75D), region.getRiverOverride().getWidthMultiplier());
|
||||
assertNull(region.getRiverOverride().getChannelBiomes());
|
||||
assertEquals(new KList<>("river/region-bank"), region.getRiverOverride().getBankBiomes());
|
||||
|
||||
assertEquals(IrisRiverRoutingPolicy.BLOCK, biome.getRiverOverride().getRoutingPolicy());
|
||||
assertNull(biome.getRiverOverride().getChannelBiomes());
|
||||
assertNotNull(biome.getRiverOverride().getFloodedCaveBiomes());
|
||||
assertTrue(biome.getRiverOverride().getFloodedCaveBiomes().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void resolvesRiverPoolsOnlyWhenRiversAreEnabled() {
|
||||
IrisRiverBiomes dimensionBiomes = new IrisRiverBiomes()
|
||||
.setChannel(new KList<>("dimension-channel"));
|
||||
IrisRiverOverride regionOverride = new IrisRiverOverride()
|
||||
.setBankBiomes(new KList<>("region-bank"));
|
||||
IrisRiverOverride biomeOverride = new IrisRiverOverride()
|
||||
.setMouthBiomes(new KList<>("biome-mouth"))
|
||||
.setFloodedCaveBiomes(new KList<>("biome-grotto"));
|
||||
IrisDimension dimension = new IrisDimension()
|
||||
.setRegions(new KList<>("region"))
|
||||
.setRivers(new IrisRiverNetwork()
|
||||
.setEnabled(true)
|
||||
.setTerrain(new IrisRiverTerrain()
|
||||
.setWorms(new KList<>(new IrisRiverWorm())))
|
||||
.setBiomes(dimensionBiomes));
|
||||
IrisRegion region = new IrisRegion()
|
||||
.setLandBiomes(new KList<>("natural"))
|
||||
.setRiverOverride(regionOverride);
|
||||
IrisBiome natural = biome("natural").setRiverOverride(biomeOverride);
|
||||
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisRegion> regionLoader = mock(ResourceLoader.class);
|
||||
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
|
||||
when(data.getRegionLoader()).thenReturn(regionLoader);
|
||||
when(data.getBiomeLoader()).thenReturn(biomeLoader);
|
||||
when(regionLoader.load("region")).thenReturn(region);
|
||||
when(biomeLoader.load("natural")).thenReturn(natural);
|
||||
when(biomeLoader.load("dimension-channel")).thenReturn(biome("dimension-channel"));
|
||||
when(biomeLoader.load("region-bank")).thenReturn(biome("region-bank"));
|
||||
when(biomeLoader.load("biome-mouth")).thenReturn(biome("biome-mouth"));
|
||||
when(biomeLoader.load("biome-grotto")).thenReturn(biome("biome-grotto"));
|
||||
|
||||
Set<String> enabledKeys = keys(dimension.getReachableBiomes(() -> data));
|
||||
dimension.getRivers().setEnabled(false);
|
||||
Set<String> disabledKeys = keys(dimension.getReachableBiomes(() -> data));
|
||||
|
||||
assertEquals(Set.of("natural", "dimension-channel", "region-bank", "biome-mouth", "biome-grotto"),
|
||||
enabledKeys);
|
||||
assertEquals(Set.of("natural"), disabledKeys);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void separatesNaturalAndRiverOnlyRegionBiomes() {
|
||||
IrisRegion region = new IrisRegion()
|
||||
.setLandBiomes(new KList<>("natural-parent"))
|
||||
.setRiverOverride(new IrisRiverOverride()
|
||||
.setChannelBiomes(new KList<>("river-parent")));
|
||||
IrisBiome naturalParent = biome("natural-parent").setChildren(new KList<>("natural-child"));
|
||||
IrisBiome naturalChild = biome("natural-child");
|
||||
IrisBiome riverParent = biome("river-parent").setChildren(new KList<>("river-child"));
|
||||
IrisBiome riverChild = biome("river-child");
|
||||
|
||||
IrisData data = mock(IrisData.class);
|
||||
ResourceLoader<IrisBiome> biomeLoader = mock(ResourceLoader.class);
|
||||
when(data.getBiomeLoader()).thenReturn(biomeLoader);
|
||||
when(biomeLoader.load("natural-parent")).thenReturn(naturalParent);
|
||||
when(biomeLoader.load("natural-child")).thenReturn(naturalChild);
|
||||
when(biomeLoader.load("river-parent")).thenReturn(riverParent);
|
||||
when(biomeLoader.load("river-child")).thenReturn(riverChild);
|
||||
|
||||
assertEquals(Set.of("natural-parent"), Set.copyOf(region.getNaturalBiomeIds()));
|
||||
assertEquals(Set.of("natural-parent", "river-parent"), Set.copyOf(region.getAllBiomeIds()));
|
||||
assertEquals(Set.of("natural-parent", "natural-child"),
|
||||
keys(region.getNaturalBiomes(() -> data)));
|
||||
assertEquals(Set.of("natural-parent", "natural-child", "river-parent", "river-child"),
|
||||
keys(region.getAllBiomes(() -> data)));
|
||||
}
|
||||
|
||||
private static IrisBiome biome(String loadKey) {
|
||||
IrisBiome biome = new IrisBiome();
|
||||
biome.setLoadKey(loadKey);
|
||||
return biome;
|
||||
}
|
||||
|
||||
private static Set<String> keys(KList<IrisBiome> biomes) {
|
||||
return biomes.stream().map(IrisBiome::getLoadKey).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,306 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RiverTileCacheTest {
|
||||
@Test
|
||||
public void concurrentRequestsBuildOneTileOnce() throws Exception {
|
||||
int requestCount = 16;
|
||||
AtomicInteger builds = new AtomicInteger();
|
||||
CountDownLatch ready = new CountDownLatch(requestCount);
|
||||
CountDownLatch start = new CountDownLatch(1);
|
||||
CountDownLatch builderEntered = new CountDownLatch(1);
|
||||
CountDownLatch releaseBuilder = new CountDownLatch(1);
|
||||
RiverTileCache cache = new RiverTileCache(8, (tileX, tileZ) -> {
|
||||
builds.incrementAndGet();
|
||||
builderEntered.countDown();
|
||||
assertTrue(releaseBuilder.await(5, TimeUnit.SECONDS));
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
ExecutorService executor = Executors.newFixedThreadPool(requestCount);
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Future<RiverTile>[] futures = new Future[requestCount];
|
||||
for (int request = 0; request < requestCount; request++) {
|
||||
futures[request] = executor.submit(() -> {
|
||||
ready.countDown();
|
||||
assertTrue(start.await(5, TimeUnit.SECONDS));
|
||||
return cache.get(-7, -11);
|
||||
});
|
||||
}
|
||||
assertTrue(ready.await(5, TimeUnit.SECONDS));
|
||||
start.countDown();
|
||||
assertTrue(builderEntered.await(5, TimeUnit.SECONDS));
|
||||
releaseBuilder.countDown();
|
||||
|
||||
RiverTile first = futures[0].get(5, TimeUnit.SECONDS);
|
||||
for (Future<RiverTile> future : futures) {
|
||||
assertSame(first, future.get(5, TimeUnit.SECONDS));
|
||||
}
|
||||
assertEquals(-7, first.tileX());
|
||||
assertEquals(-11, first.tileZ());
|
||||
assertEquals(1, builds.get());
|
||||
assertEquals(1, cache.completedSize());
|
||||
} finally {
|
||||
releaseBuilder.countDown();
|
||||
executor.shutdownNow();
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void differentKeysBuildConcurrentlyOutsideCacheLock() throws Exception {
|
||||
CountDownLatch buildersEntered = new CountDownLatch(2);
|
||||
CountDownLatch releaseBuilders = new CountDownLatch(1);
|
||||
RiverTileCache cache = new RiverTileCache(4, (tileX, tileZ) -> {
|
||||
buildersEntered.countDown();
|
||||
assertTrue(releaseBuilders.await(5, TimeUnit.SECONDS));
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
ExecutorService executor = Executors.newFixedThreadPool(2);
|
||||
try {
|
||||
Future<RiverTile> first = executor.submit(() -> cache.get(1, 1));
|
||||
Future<RiverTile> second = executor.submit(() -> cache.get(2, 2));
|
||||
assertTrue(buildersEntered.await(5, TimeUnit.SECONDS));
|
||||
releaseBuilders.countDown();
|
||||
assertEquals(1, first.get(5, TimeUnit.SECONDS).tileX());
|
||||
assertEquals(2, second.get(5, TimeUnit.SECONDS).tileX());
|
||||
} finally {
|
||||
releaseBuilders.countDown();
|
||||
executor.shutdownNow();
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void completedEntriesUseBoundedAccessOrderEviction() {
|
||||
AtomicInteger builds = new AtomicInteger();
|
||||
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
|
||||
builds.incrementAndGet();
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
try {
|
||||
RiverTile zero = cache.get(0, 0);
|
||||
RiverTile one = cache.get(1, 0);
|
||||
assertSame(zero, cache.get(0, 0));
|
||||
cache.get(2, 0);
|
||||
assertEquals(2, cache.completedSize());
|
||||
assertSame(zero, cache.get(0, 0));
|
||||
|
||||
RiverTile rebuiltOne = cache.get(1, 0);
|
||||
assertFalse(one == rebuiltOne);
|
||||
assertEquals(4, builds.get());
|
||||
assertEquals(2, cache.completedSize());
|
||||
} finally {
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedBuildsAreRemovedAndRetryable() {
|
||||
AtomicInteger attempts = new AtomicInteger();
|
||||
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
|
||||
if (attempts.incrementAndGet() == 1) {
|
||||
throw new IllegalArgumentException("expected failure");
|
||||
}
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
try {
|
||||
IllegalArgumentException failure = assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> cache.get(-3, 4)
|
||||
);
|
||||
assertEquals("expected failure", failure.getMessage());
|
||||
assertEquals(0, cache.completedSize());
|
||||
|
||||
RiverTile retried = cache.get(-3, 4);
|
||||
assertEquals(-3, retried.tileX());
|
||||
assertEquals(2, attempts.get());
|
||||
assertEquals(1, cache.completedSize());
|
||||
} finally {
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mismatchedBuilderResultIsRemovedAndRetryable() {
|
||||
AtomicInteger attempts = new AtomicInteger();
|
||||
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> attempts.incrementAndGet() == 1
|
||||
? emptyTile(tileX + 1, tileZ)
|
||||
: emptyTile(tileX, tileZ));
|
||||
try {
|
||||
assertThrows(IllegalStateException.class, () -> cache.get(5, 6));
|
||||
RiverTile retried = cache.get(5, 6);
|
||||
assertEquals(5, retried.tileX());
|
||||
assertEquals(2, attempts.get());
|
||||
} finally {
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearAndCloseReleaseEntriesAndEnforceLifecycle() {
|
||||
AtomicInteger builds = new AtomicInteger();
|
||||
RiverTileCache cache = new RiverTileCache(4, (tileX, tileZ) -> {
|
||||
builds.incrementAndGet();
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
cache.get(0, 0);
|
||||
cache.get(1, 0);
|
||||
assertEquals(2, cache.completedSize());
|
||||
|
||||
cache.clear();
|
||||
assertEquals(0, cache.completedSize());
|
||||
cache.get(0, 0);
|
||||
assertEquals(3, builds.get());
|
||||
|
||||
cache.close();
|
||||
cache.close();
|
||||
assertTrue(cache.isClosed());
|
||||
assertEquals(0, cache.completedSize());
|
||||
assertThrows(IllegalStateException.class, () -> cache.get(0, 0));
|
||||
assertThrows(IllegalStateException.class, cache::clear);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closeInvalidatesInflightBuildWithoutRetainingItsResult() throws Exception {
|
||||
CountDownLatch builderEntered = new CountDownLatch(1);
|
||||
CountDownLatch releaseBuilder = new CountDownLatch(1);
|
||||
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
|
||||
builderEntered.countDown();
|
||||
assertTrue(releaseBuilder.await(5, TimeUnit.SECONDS));
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
Future<RiverTile> result = executor.submit(() -> cache.get(9, -9));
|
||||
assertTrue(builderEntered.await(5, TimeUnit.SECONDS));
|
||||
cache.close();
|
||||
releaseBuilder.countDown();
|
||||
|
||||
ExecutionException failure = assertThrows(
|
||||
ExecutionException.class,
|
||||
() -> result.get(5, TimeUnit.SECONDS)
|
||||
);
|
||||
assertTrue(failure.getCause() instanceof IllegalStateException);
|
||||
assertEquals(0, cache.completedSize());
|
||||
} finally {
|
||||
releaseBuilder.countDown();
|
||||
executor.shutdownNow();
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void clearInvalidatesInflightBuildAndFreshRequestPublishesNewEntry() throws Exception {
|
||||
AtomicInteger attempts = new AtomicInteger();
|
||||
CountDownLatch firstBuilderEntered = new CountDownLatch(1);
|
||||
CountDownLatch releaseFirstBuilder = new CountDownLatch(1);
|
||||
RiverTileCache cache = new RiverTileCache(2, (tileX, tileZ) -> {
|
||||
if (attempts.incrementAndGet() == 1) {
|
||||
firstBuilderEntered.countDown();
|
||||
assertTrue(releaseFirstBuilder.await(5, TimeUnit.SECONDS));
|
||||
}
|
||||
return emptyTile(tileX, tileZ);
|
||||
});
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
try {
|
||||
Future<RiverTile> invalidated = executor.submit(() -> cache.get(4, -2));
|
||||
assertTrue(firstBuilderEntered.await(5, TimeUnit.SECONDS));
|
||||
cache.clear();
|
||||
|
||||
RiverTile replacement = cache.get(4, -2);
|
||||
releaseFirstBuilder.countDown();
|
||||
|
||||
ExecutionException failure = assertThrows(
|
||||
ExecutionException.class,
|
||||
() -> invalidated.get(5, TimeUnit.SECONDS)
|
||||
);
|
||||
assertTrue(failure.getCause() instanceof IllegalStateException);
|
||||
assertEquals(4, replacement.tileX());
|
||||
assertEquals(-2, replacement.tileZ());
|
||||
assertEquals(2, attempts.get());
|
||||
assertEquals(1, cache.completedSize());
|
||||
assertSame(replacement, cache.get(4, -2));
|
||||
} finally {
|
||||
releaseFirstBuilder.countDown();
|
||||
executor.shutdownNow();
|
||||
cache.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tileProvidesConstantTimeReachIdentityLookup() {
|
||||
RiverReach reach = reach();
|
||||
RiverTile tile = new RiverTile(0, 0, 0, 0, 64, 64, List.of(reach));
|
||||
|
||||
assertSame(reach, tile.reach(reach.id()));
|
||||
assertNull(tile.reach(RiverEdgeId.of(new RiverNodeId(2L, 0L), new RiverNodeId(3L, 0L))));
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> new RiverTile(0, 0, 0, 0, 64, 64, List.of(reach, reach))
|
||||
);
|
||||
}
|
||||
|
||||
private static RiverTile emptyTile(int tileX, int tileZ) {
|
||||
int minimumX = tileX * 64;
|
||||
int minimumZ = tileZ * 64;
|
||||
return new RiverTile(tileX, tileZ, minimumX, minimumZ, minimumX + 64, minimumZ + 64, List.of());
|
||||
}
|
||||
|
||||
private static RiverReach reach() {
|
||||
RiverNode from = new RiverNode(
|
||||
new RiverNodeId(0L, 0L),
|
||||
8.0,
|
||||
8.0,
|
||||
20.0,
|
||||
20.0,
|
||||
20.0,
|
||||
20.0,
|
||||
false,
|
||||
true
|
||||
);
|
||||
RiverNode to = new RiverNode(
|
||||
new RiverNodeId(1L, 0L),
|
||||
56.0,
|
||||
8.0,
|
||||
10.0,
|
||||
10.0,
|
||||
10.0,
|
||||
10.0,
|
||||
false,
|
||||
true
|
||||
);
|
||||
return new RiverReach(
|
||||
RiverEdgeId.of(from.id(), to.id()),
|
||||
from,
|
||||
to,
|
||||
RiverRouteState.WET,
|
||||
1,
|
||||
1,
|
||||
8.0,
|
||||
4.0,
|
||||
3.0,
|
||||
RiverBodyProfile.constant(8.0, 4.0, 3.0),
|
||||
false,
|
||||
false,
|
||||
new RiverPolyline(new double[]{8.0, 56.0}, new double[]{8.0, 8.0})
|
||||
);
|
||||
}
|
||||
}
|
||||
-757
@@ -1,757 +0,0 @@
|
||||
package art.arcane.iris.engine.river.cave;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RiverCaveContainmentPlannerTest {
|
||||
private static final List<CavePosition> DIRECTIONS = List.of(
|
||||
new CavePosition(1, 0, 0),
|
||||
new CavePosition(-1, 0, 0),
|
||||
new CavePosition(0, 1, 0),
|
||||
new CavePosition(0, -1, 0),
|
||||
new CavePosition(0, 0, 1),
|
||||
new CavePosition(0, 0, -1)
|
||||
);
|
||||
|
||||
private final RiverCaveContainmentPlanner planner = new RiverCaveContainmentPlanner();
|
||||
|
||||
@Test
|
||||
public void closedComponentProducesConnectedThroatPoolAndGuards() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(1, 6, 0));
|
||||
RiverCaveSource source = source(14L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, settings());
|
||||
|
||||
assertTrue(plan.accepted());
|
||||
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 12, 0)));
|
||||
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 11, 0)));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(1, 6, 0)));
|
||||
assertTrue(plan.actions().containsValue(RiverCaveAction.SEAL_GUARD));
|
||||
assertMutationConnected(plan, source.entry(), position(1, 6, 0));
|
||||
assertEquals(plan.actions().keySet(), plan.baselinePreconditions().keySet());
|
||||
assertEquals(CaveVoxel.CAVE_AIR, plan.baselinePreconditions().get(position(1, 6, 0)).voxel());
|
||||
assertEquals(CaveVoxel.SOLID, plan.baselinePreconditions().get(position(0, 12, 0)).voxel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compatibleFluidFollowsExplicitPolicy() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
CavePosition target = position(0, 7, 0);
|
||||
view.set(CaveVoxel.COMPATIBLE_FLUID, target);
|
||||
RiverCaveSource source = source(1L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
|
||||
|
||||
RiverCavePlan allowed = planner.plan(view, source, settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE));
|
||||
RiverCavePlan rejected = planner.plan(view, source, settings(RiverCaveFluidPolicy.REJECT_EXISTING));
|
||||
|
||||
assertTrue(allowed.accepted());
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, allowed.actions().get(target));
|
||||
assertEquals(RiverCaveRejection.EXISTING_FLUID, rejected.rejection());
|
||||
assertTrue(rejected.actions().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replacementPolicyIsDistinctAndStillRejectsLava() {
|
||||
CavePosition target = position(0, 7, 0);
|
||||
TestVoxelView compatibleView = new TestVoxelView();
|
||||
compatibleView.set(CaveVoxel.COMPATIBLE_FLUID, target);
|
||||
TestVoxelView incompatibleView = new TestVoxelView();
|
||||
incompatibleView.set(CaveVoxel.INCOMPATIBLE_FLUID, target);
|
||||
TestVoxelView lavaView = new TestVoxelView();
|
||||
lavaView.set(CaveVoxel.LAVA, target);
|
||||
RiverCaveSource source = source(101L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
|
||||
|
||||
RiverCavePlan rejectedCompatible = planner.plan(
|
||||
compatibleView,
|
||||
source,
|
||||
settings(RiverCaveFluidPolicy.REJECT_EXISTING)
|
||||
);
|
||||
RiverCavePlan allowedCompatible = planner.plan(
|
||||
compatibleView,
|
||||
source,
|
||||
settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE)
|
||||
);
|
||||
RiverCavePlan rejectedIncompatible = planner.plan(
|
||||
incompatibleView,
|
||||
source,
|
||||
settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE)
|
||||
);
|
||||
RiverCavePlan replacedIncompatible = planner.plan(
|
||||
incompatibleView,
|
||||
source,
|
||||
settings(RiverCaveFluidPolicy.REPLACE_CONTAINED)
|
||||
);
|
||||
RiverCavePlan rejectedLava = planner.plan(
|
||||
lavaView,
|
||||
source,
|
||||
settings(RiverCaveFluidPolicy.REPLACE_CONTAINED)
|
||||
);
|
||||
|
||||
assertEquals(RiverCaveRejection.EXISTING_FLUID, rejectedCompatible.rejection());
|
||||
assertTrue(allowedCompatible.accepted());
|
||||
assertEquals(RiverCaveRejection.INCOMPATIBLE_FLUID, rejectedIncompatible.rejection());
|
||||
assertTrue(replacedIncompatible.accepted());
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, replacedIncompatible.actions().get(target));
|
||||
assertEquals(RiverCaveRejection.LAVA_CONTACT, rejectedLava.rejection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void throatRadiusExpandsTheConnectedBoreFootprint() {
|
||||
TestVoxelView thinView = new TestVoxelView();
|
||||
TestVoxelView thickView = new TestVoxelView();
|
||||
CavePosition target = position(0, 7, 0);
|
||||
thinView.set(CaveVoxel.CAVE_AIR, target);
|
||||
thickView.set(CaveVoxel.CAVE_AIR, target);
|
||||
RiverCaveSource source = source(102L, 10, RiverCaveMode.CLOSED_COMPONENT, target);
|
||||
RiverCavePlannerSettings thinSettings = detailedSettings(
|
||||
1,
|
||||
0,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
RiverCavePlannerSettings thickSettings = detailedSettings(
|
||||
3,
|
||||
0,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan thin = planner.plan(thinView, source, thinSettings);
|
||||
RiverCavePlan thick = planner.plan(thickView, source, thickSettings);
|
||||
|
||||
assertTrue(thin.accepted());
|
||||
assertTrue(thick.accepted());
|
||||
assertFalse(thin.actions().containsKey(position(2, 10, 0)));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, thick.actions().get(position(2, 10, 0)));
|
||||
assertTrue(mutationCount(thick) > mutationCount(thin));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedGrottoRetainsConfiguredDryHeadroom() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
RiverCaveSource source = source(103L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
|
||||
RiverCavePlannerSettings acceptedSettings = detailedSettings(
|
||||
1,
|
||||
2,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
RiverCavePlannerSettings rejectedSettings = detailedSettings(
|
||||
1,
|
||||
5,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan accepted = planner.plan(view, source, acceptedSettings);
|
||||
RiverCavePlan rejected = planner.plan(view, source, rejectedSettings);
|
||||
|
||||
assertTrue(accepted.accepted());
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, accepted.actions().get(position(0, 11, 0)));
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, accepted.actions().get(position(0, 12, 0)));
|
||||
assertEquals(RiverCaveRejection.DRY_HEADROOM_LIMIT, rejected.rejection());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configuredGrottoPredicateChangesFootprintDeterministically() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
RiverCaveSource source = source(104L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
|
||||
RiverCavePlannerSettings ellipsoid = detailedSettings(
|
||||
1,
|
||||
0,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
RiverCaveGrottoShape compactShape = (candidate, settings, dx, dy, dz) -> {
|
||||
double horizontal = settings.grottoHorizontalRadius();
|
||||
double vertical = settings.grottoVerticalRadius();
|
||||
double normalized = (dx * dx / (horizontal * horizontal))
|
||||
+ (dy * dy / (vertical * vertical))
|
||||
+ (dz * dz / (horizontal * horizontal));
|
||||
return normalized <= 0.5D;
|
||||
};
|
||||
RiverCavePlannerSettings compact = detailedSettings(
|
||||
1,
|
||||
0,
|
||||
compactShape,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan full = planner.plan(view, source, ellipsoid);
|
||||
RiverCavePlan firstCompact = planner.plan(view, source, compact);
|
||||
RiverCavePlan secondCompact = planner.plan(view, source, compact);
|
||||
|
||||
assertTrue(full.accepted());
|
||||
assertTrue(firstCompact.accepted());
|
||||
assertEquals(firstCompact, secondCompact);
|
||||
assertTrue(mutationCount(full) > mutationCount(firstCompact));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void radiusLimitRejectsWholeComponentWithoutClipping() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
for (int x = 0; x <= 3; x++) {
|
||||
view.set(CaveVoxel.CAVE_AIR, position(x, 7, 0));
|
||||
}
|
||||
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
|
||||
2,
|
||||
20,
|
||||
64,
|
||||
32,
|
||||
1,
|
||||
1,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(2L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
|
||||
settings
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.RADIUS_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void depthLimitRejectsWholeComponentWithoutClipping() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 8, 0), position(0, 7, 0));
|
||||
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
|
||||
8,
|
||||
4,
|
||||
64,
|
||||
32,
|
||||
1,
|
||||
1,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(3L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 8, 0)),
|
||||
settings
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.DEPTH_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void volumeLimitRejectsWholeComponentWithoutClipping() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
|
||||
RiverCavePlannerSettings settings = new RiverCavePlannerSettings(
|
||||
8,
|
||||
20,
|
||||
2,
|
||||
32,
|
||||
1,
|
||||
1,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
);
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(4L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
|
||||
settings
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.VOLUME_LIMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void worldBoundaryRejectsWholeComponent() {
|
||||
TestVoxelView view = new TestVoxelView(-16, 0, 0, 32, -16, 16);
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(5L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
|
||||
settings()
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.WORLD_BOUNDARY);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void openSurfaceRejectsWholeComponent() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
CavePosition target = position(0, 7, 0);
|
||||
view.set(CaveVoxel.CAVE_AIR, target);
|
||||
view.openToSurface(target);
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(6L, 10, RiverCaveMode.CLOSED_COMPONENT, target),
|
||||
settings()
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.OPEN_SURFACE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void lavaAndIncompatibleFluidContactsRejectWholeComponent() {
|
||||
TestVoxelView lavaView = new TestVoxelView();
|
||||
lavaView.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
|
||||
lavaView.set(CaveVoxel.LAVA, position(1, 7, 0));
|
||||
TestVoxelView fluidView = new TestVoxelView();
|
||||
fluidView.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
|
||||
fluidView.set(CaveVoxel.INCOMPATIBLE_FLUID, position(1, 7, 0));
|
||||
RiverCaveSource source = source(7L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
|
||||
|
||||
RiverCavePlan lavaPlan = planner.plan(lavaView, source, settings());
|
||||
RiverCavePlan fluidPlan = planner.plan(fluidView, source, settings());
|
||||
|
||||
assertRejectedWithoutPublication(lavaPlan, RiverCaveRejection.LAVA_CONTACT);
|
||||
assertRejectedWithoutPublication(fluidPlan, RiverCaveRejection.INCOMPATIBLE_FLUID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallingHazardAbovePoolRejectsWholeComponent() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
|
||||
view.set(CaveVoxel.LAVA, position(1, 11, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(15L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0)),
|
||||
settings()
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.LAVA_CONTACT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedGrottoHasBoundedWetDryAndGuardActions() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
RiverCaveSource source = source(8L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, settings());
|
||||
|
||||
assertTrue(plan.accepted());
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 12, 0)));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 8, 0)));
|
||||
assertTrue(plan.actions().containsValue(RiverCaveAction.SEAL_GUARD));
|
||||
assertEquals(plan.actions().keySet(), plan.baselinePreconditions().keySet());
|
||||
assertFalse(plan.actions().containsKey(position(0, 13, 0)));
|
||||
assertMutationConnected(plan, source.entry(), source.target());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedGrottoAcceptsItsCompleteSurfaceMouth() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
CavePosition[] wetMouth = {
|
||||
position(-1, 12, 0),
|
||||
position(1, 12, 0),
|
||||
position(0, 12, -1),
|
||||
position(0, 12, 1)
|
||||
};
|
||||
CavePosition[] dryMouth = {
|
||||
position(-1, 13, 0),
|
||||
position(1, 13, 0),
|
||||
position(0, 13, -1),
|
||||
position(0, 13, 1)
|
||||
};
|
||||
view.set(CaveVoxel.COMPATIBLE_FLUID, wetMouth);
|
||||
view.set(CaveVoxel.CAVE_AIR, dryMouth);
|
||||
for (CavePosition position : wetMouth) {
|
||||
view.openToSurface(position);
|
||||
}
|
||||
for (CavePosition position : dryMouth) {
|
||||
view.openToSurface(position);
|
||||
}
|
||||
RiverCaveSource source = source(108L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
|
||||
2,
|
||||
2,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.ALLOW_COMPATIBLE
|
||||
));
|
||||
|
||||
assertTrue(plan.rejection().toString(), plan.accepted());
|
||||
for (CavePosition position : wetMouth) {
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatedGrottoRejectsAnOpenShell() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(3, 8, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(
|
||||
view,
|
||||
source(9L, 10, RiverCaveMode.GENERATED_GROTTO, position(0, 8, 0)),
|
||||
settings()
|
||||
);
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.GROTTO_SHELL_OPEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deepPoolOpensOnlyAboveItsContainedFluidHead() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 11, 0), position(0, 13, 0));
|
||||
RiverCaveSource source = new RiverCaveSource(
|
||||
109L,
|
||||
position(0, 10, 0),
|
||||
position(0, 8, 0),
|
||||
10,
|
||||
RiverCaveMode.DEEP_POOL
|
||||
);
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
|
||||
1,
|
||||
2,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.REJECT_EXISTING
|
||||
));
|
||||
|
||||
assertTrue(plan.rejection().toString(), plan.accepted());
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
|
||||
assertEquals(RiverCaveAction.DRY_AIR, plan.actions().get(position(0, 11, 0)));
|
||||
assertFalse(plan.actions().containsKey(position(0, 13, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deepPoolRejectsCaveLeakAtOrBelowItsFluidHead() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(5, 8, 0));
|
||||
RiverCaveSource source = new RiverCaveSource(
|
||||
110L,
|
||||
position(0, 10, 0),
|
||||
position(0, 8, 0),
|
||||
10,
|
||||
RiverCaveMode.DEEP_POOL
|
||||
);
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, detailedSettings(
|
||||
1,
|
||||
2,
|
||||
RiverCaveGrottoShape.ELLIPSOID,
|
||||
RiverCaveFluidPolicy.REJECT_EXISTING
|
||||
));
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.GROTTO_SHELL_OPEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void combinedModeUsesClosedCaveOrGeneratedGrottoFromBaseline() {
|
||||
TestVoxelView caveView = new TestVoxelView();
|
||||
CavePosition target = position(0, 8, 0);
|
||||
caveView.set(CaveVoxel.CAVE_AIR, target);
|
||||
TestVoxelView solidView = new TestVoxelView();
|
||||
RiverCaveSource source = source(10L, 10, RiverCaveMode.GROTTO_OR_CLOSED_COMPONENT, target);
|
||||
|
||||
RiverCavePlan cavePlan = planner.plan(caveView, source, settings());
|
||||
RiverCavePlan grottoPlan = planner.plan(solidView, source, settings());
|
||||
|
||||
assertTrue(cavePlan.accepted());
|
||||
assertTrue(grottoPlan.accepted());
|
||||
assertEquals(CaveVoxel.CAVE_AIR, cavePlan.baselinePreconditions().get(target).voxel());
|
||||
assertEquals(CaveVoxel.SOLID, grottoPlan.baselinePreconditions().get(target).voxel());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waterfallKeepsColumnDistinctFromSourcePoolAndDryAir() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
RiverCaveSource source = source(11L, 10, RiverCaveMode.WATERFALL_POOL, position(0, 8, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, settings());
|
||||
|
||||
assertTrue(plan.accepted());
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 12, 0)));
|
||||
assertEquals(RiverCaveAction.FALLING_FLUID, plan.actions().get(position(0, 11, 0)));
|
||||
assertEquals(RiverCaveAction.WET_SOURCE, plan.actions().get(position(0, 10, 0)));
|
||||
assertFalse(plan.actions().containsValue(RiverCaveAction.DRY_AIR));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void waterfallRejectsAnUnsealedFallingColumn() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 8, 0), position(1, 11, 0));
|
||||
RiverCaveSource source = source(13L, 10, RiverCaveMode.WATERFALL_POOL, position(0, 8, 0));
|
||||
|
||||
RiverCavePlan plan = planner.plan(view, source, settings());
|
||||
|
||||
assertRejectedWithoutPublication(plan, RiverCaveRejection.WATERFALL_SHAFT_OPEN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void overlapArbitrationIsOrderIndependentAndNamesWinner() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
|
||||
RiverCaveSource lowerHead = new RiverCaveSource(
|
||||
1L,
|
||||
position(2, 12, 0),
|
||||
position(2, 7, 0),
|
||||
10,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
RiverCaveSource higherHead = new RiverCaveSource(
|
||||
20L,
|
||||
position(0, 12, 0),
|
||||
position(0, 7, 0),
|
||||
11,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
|
||||
RiverCavePlanningResult forward = planner.planAll(view, List.of(lowerHead, higherHead), settings());
|
||||
RiverCavePlanningResult reverse = planner.planAll(view, List.of(higherHead, lowerHead), settings());
|
||||
|
||||
assertEquals(forward, reverse);
|
||||
assertEquals(higherHead, forward.plans().get(0).source());
|
||||
assertTrue(forward.plans().get(0).accepted());
|
||||
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(1).rejection());
|
||||
assertEquals(20L, forward.plans().get(1).arbitrationWinnerSourceId().getAsLong());
|
||||
assertEquals(forward.actions().keySet(), forward.baselinePreconditions().keySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void equalHeadOverlapUsesLowestStableSourceId() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0), position(1, 7, 0), position(2, 7, 0));
|
||||
RiverCaveSource largerId = new RiverCaveSource(
|
||||
20L,
|
||||
position(0, 12, 0),
|
||||
position(0, 7, 0),
|
||||
10,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
RiverCaveSource smallerId = new RiverCaveSource(
|
||||
1L,
|
||||
position(2, 12, 0),
|
||||
position(2, 7, 0),
|
||||
10,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
|
||||
RiverCavePlanningResult result = planner.planAll(view, List.of(largerId, smallerId), settings());
|
||||
|
||||
assertEquals(smallerId, result.plans().get(0).source());
|
||||
assertTrue(result.plans().get(0).accepted());
|
||||
assertEquals(1L, result.plans().get(1).arbitrationWinnerSourceId().getAsLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void arbitrationUsesDirectHigherPriorityPlansEvenWhenTheyAreRejected() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(
|
||||
CaveVoxel.CAVE_AIR,
|
||||
position(0, 7, 0),
|
||||
position(2, 7, 0),
|
||||
position(4, 7, 0)
|
||||
);
|
||||
RiverCaveSource lowest = new RiverCaveSource(
|
||||
30L,
|
||||
position(0, 12, 0),
|
||||
position(0, 7, 0),
|
||||
10,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
RiverCaveSource middle = new RiverCaveSource(
|
||||
20L,
|
||||
position(2, 12, 0),
|
||||
position(2, 7, 0),
|
||||
11,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
RiverCaveSource highest = new RiverCaveSource(
|
||||
10L,
|
||||
position(4, 12, 0),
|
||||
position(4, 7, 0),
|
||||
12,
|
||||
RiverCaveMode.CLOSED_COMPONENT
|
||||
);
|
||||
|
||||
RiverCavePlanningResult forward = planner.planAll(
|
||||
view,
|
||||
List.of(lowest, middle, highest),
|
||||
settings()
|
||||
);
|
||||
RiverCavePlanningResult reverse = planner.planAll(
|
||||
view,
|
||||
List.of(highest, middle, lowest),
|
||||
settings()
|
||||
);
|
||||
|
||||
assertEquals(forward, reverse);
|
||||
assertTrue(forward.plans().get(0).accepted());
|
||||
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(1).rejection());
|
||||
assertEquals(10L, forward.plans().get(1).arbitrationWinnerSourceId().getAsLong());
|
||||
assertEquals(RiverCaveRejection.OVERLAPPING_SOURCE, forward.plans().get(2).rejection());
|
||||
assertEquals(20L, forward.plans().get(2).arbitrationWinnerSourceId().getAsLong());
|
||||
assertFalse(forward.actions().containsKey(position(0, 7, 0)));
|
||||
assertTrue(forward.actions().containsKey(position(4, 7, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void planOutputsAreImmutable() {
|
||||
TestVoxelView view = new TestVoxelView();
|
||||
view.set(CaveVoxel.CAVE_AIR, position(0, 7, 0));
|
||||
RiverCaveSource source = source(12L, 10, RiverCaveMode.CLOSED_COMPONENT, position(0, 7, 0));
|
||||
RiverCavePlanningResult result = planner.planAll(view, List.of(source), settings());
|
||||
RiverCavePlan plan = result.plans().get(0);
|
||||
|
||||
assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> plan.actions().put(position(9, 9, 9), RiverCaveAction.WET_SOURCE)
|
||||
);
|
||||
assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> plan.baselinePreconditions().put(
|
||||
position(9, 9, 9),
|
||||
new CaveVoxelPrecondition(CaveVoxel.SOLID, false)
|
||||
)
|
||||
);
|
||||
assertThrows(UnsupportedOperationException.class, () -> result.plans().add(plan));
|
||||
assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> result.actions().put(position(9, 9, 9), RiverCaveAction.WET_SOURCE)
|
||||
);
|
||||
}
|
||||
|
||||
private void assertMutationConnected(RiverCavePlan plan, CavePosition start, CavePosition expected) {
|
||||
Set<CavePosition> mutations = new HashSet<>();
|
||||
for (Map.Entry<CavePosition, RiverCaveAction> entry : plan.actions().entrySet()) {
|
||||
if (entry.getValue() != RiverCaveAction.SEAL_GUARD) {
|
||||
mutations.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
Queue<CavePosition> queue = new ArrayDeque<>();
|
||||
Set<CavePosition> visited = new HashSet<>();
|
||||
queue.add(start);
|
||||
visited.add(start);
|
||||
while (!queue.isEmpty()) {
|
||||
CavePosition position = queue.remove();
|
||||
for (CavePosition direction : DIRECTIONS) {
|
||||
CavePosition neighbor = position.offset(direction.x(), direction.y(), direction.z());
|
||||
if (mutations.contains(neighbor) && visited.add(neighbor)) {
|
||||
queue.add(neighbor);
|
||||
}
|
||||
}
|
||||
}
|
||||
assertTrue(visited.contains(expected));
|
||||
assertEquals(mutations, visited);
|
||||
}
|
||||
|
||||
private int mutationCount(RiverCavePlan plan) {
|
||||
int count = 0;
|
||||
for (RiverCaveAction action : plan.actions().values()) {
|
||||
if (action != RiverCaveAction.SEAL_GUARD) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void assertRejectedWithoutPublication(RiverCavePlan plan, RiverCaveRejection rejection) {
|
||||
assertFalse(plan.accepted());
|
||||
assertEquals(rejection, plan.rejection());
|
||||
assertTrue(plan.actions().isEmpty());
|
||||
assertTrue(plan.baselinePreconditions().isEmpty());
|
||||
}
|
||||
|
||||
private RiverCaveSource source(long sourceId, int waterHeadY, RiverCaveMode mode, CavePosition target) {
|
||||
return new RiverCaveSource(sourceId, position(0, 12, 0), target, waterHeadY, mode);
|
||||
}
|
||||
|
||||
private RiverCavePlannerSettings settings() {
|
||||
return settings(RiverCaveFluidPolicy.ALLOW_COMPATIBLE);
|
||||
}
|
||||
|
||||
private RiverCavePlannerSettings settings(RiverCaveFluidPolicy policy) {
|
||||
return new RiverCavePlannerSettings(12, 20, 64, 32, 2, 2, policy);
|
||||
}
|
||||
|
||||
private RiverCavePlannerSettings detailedSettings(
|
||||
int throatRadius,
|
||||
int dryHeadroom,
|
||||
RiverCaveGrottoShape shape,
|
||||
RiverCaveFluidPolicy policy
|
||||
) {
|
||||
return new RiverCavePlannerSettings(
|
||||
12,
|
||||
20,
|
||||
2048,
|
||||
32,
|
||||
throatRadius,
|
||||
4,
|
||||
4,
|
||||
dryHeadroom,
|
||||
policy,
|
||||
shape
|
||||
);
|
||||
}
|
||||
|
||||
private CavePosition position(int x, int y, int z) {
|
||||
return new CavePosition(x, y, z);
|
||||
}
|
||||
|
||||
private static final class TestVoxelView implements CaveVoxelView {
|
||||
private final int minX;
|
||||
private final int maxX;
|
||||
private final int minY;
|
||||
private final int maxY;
|
||||
private final int minZ;
|
||||
private final int maxZ;
|
||||
private final Map<CavePosition, CaveVoxel> voxels = new HashMap<>();
|
||||
private final Set<CavePosition> surfaceOpenings = new HashSet<>();
|
||||
|
||||
private TestVoxelView() {
|
||||
this(-32, 32, 0, 32, -32, 32);
|
||||
}
|
||||
|
||||
private TestVoxelView(int minX, int maxX, int minY, int maxY, int minZ, int maxZ) {
|
||||
this.minX = minX;
|
||||
this.maxX = maxX;
|
||||
this.minY = minY;
|
||||
this.maxY = maxY;
|
||||
this.minZ = minZ;
|
||||
this.maxZ = maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInWorld(CavePosition position) {
|
||||
return position.x() >= minX
|
||||
&& position.x() <= maxX
|
||||
&& position.y() >= minY
|
||||
&& position.y() <= maxY
|
||||
&& position.z() >= minZ
|
||||
&& position.z() <= maxZ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaveVoxel voxelAt(CavePosition position) {
|
||||
return voxels.getOrDefault(position, CaveVoxel.SOLID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpenToSurface(CavePosition position) {
|
||||
return surfaceOpenings.contains(position);
|
||||
}
|
||||
|
||||
private void set(CaveVoxel voxel, CavePosition... positions) {
|
||||
for (CavePosition position : positions) {
|
||||
voxels.put(position, voxel);
|
||||
}
|
||||
}
|
||||
|
||||
private void openToSurface(CavePosition position) {
|
||||
surfaceOpenings.add(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
package art.arcane.iris.engine.river.cave;
|
||||
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterSlice;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class RiverCaveHydrologyStorageTest {
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void presentReadDoesNotMaterializeASectionOrSlice() {
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Matter matter = mock(Matter.class);
|
||||
MatterSlice<RiverCaveHydrology> slice = mock(MatterSlice.class);
|
||||
RiverCaveHydrology expected = RiverCaveHydrology.of(RiverCaveAction.WET_SOURCE);
|
||||
when(chunk.exists(2)).thenReturn(true);
|
||||
when(chunk.get(2)).thenReturn(matter);
|
||||
when(matter.hasSlice(RiverCaveHydrology.class)).thenReturn(true);
|
||||
when(matter.getSlice(RiverCaveHydrology.class)).thenReturn(slice);
|
||||
when(slice.get(3, 1, 5)).thenReturn(expected);
|
||||
|
||||
assertSame(expected, RiverCaveHydrologyStorage.getIfPresent(chunk, 3, 33, 5));
|
||||
verify(chunk, never()).getOrCreate(2);
|
||||
verify(matter, never()).slice(RiverCaveHydrology.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void absentSliceReturnsWithoutCreatingIt() {
|
||||
MantleChunk<Matter> chunk = mock(MantleChunk.class);
|
||||
Matter matter = mock(Matter.class);
|
||||
when(chunk.exists(2)).thenReturn(true);
|
||||
when(chunk.get(2)).thenReturn(matter);
|
||||
|
||||
assertNull(RiverCaveHydrologyStorage.getIfPresent(chunk, 3, 33, 5));
|
||||
verify(matter, never()).slice(RiverCaveHydrology.class);
|
||||
verify(matter, never()).getSlice(RiverCaveHydrology.class);
|
||||
}
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package art.arcane.iris.engine.river.runtime;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisRiverBiomes;
|
||||
import art.arcane.iris.engine.object.IrisRiverNetwork;
|
||||
import art.arcane.iris.engine.object.IrisRiverOverride;
|
||||
import art.arcane.iris.engine.object.IrisRiverRoutingPolicy;
|
||||
import art.arcane.iris.engine.object.IrisRiverTerminalMode;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class EffectiveRiverSettingsTest {
|
||||
@Test
|
||||
public void appliesRegionThenBiomeOverridesWithoutChangingGraphSettings() {
|
||||
IrisRiverNetwork network = new IrisRiverNetwork()
|
||||
.setBiomes(new IrisRiverBiomes()
|
||||
.setChannel(new KList<>("dimension-channel"))
|
||||
.setBank(new KList<>("dimension-bank")));
|
||||
IrisRegion region = new IrisRegion().setRiverOverride(new IrisRiverOverride()
|
||||
.setAllowSources(false)
|
||||
.setRoutingPolicy(IrisRiverRoutingPolicy.AVOID)
|
||||
.setWidthMultiplier(1.5D)
|
||||
.setChannelBiomes(new KList<>("region-channel")));
|
||||
IrisBiome biome = new IrisBiome().setRiverOverride(new IrisRiverOverride()
|
||||
.setRoutingPolicy(IrisRiverRoutingPolicy.BLOCK)
|
||||
.setWidthMultiplier(0.5D)
|
||||
.setChannelBiomes(new KList<>())
|
||||
.setTerminalMode(IrisRiverTerminalMode.SUPPRESS));
|
||||
|
||||
EffectiveRiverSettings settings = EffectiveRiverSettings.resolve(network, region, biome);
|
||||
|
||||
assertFalse(settings.allowSources());
|
||||
assertEquals(IrisRiverRoutingPolicy.BLOCK, settings.routingPolicy());
|
||||
assertEquals(0.5D, settings.widthMultiplier(), 0D);
|
||||
assertTrue(settings.channelBiomes().isEmpty());
|
||||
assertEquals(List.of("dimension-bank"), settings.bankBiomes());
|
||||
assertEquals(IrisRiverTerminalMode.SUPPRESS, settings.terminalMode());
|
||||
assertTrue(settings.terminalModeOverridden());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void inheritsDimensionDefaultsWhenOverridesAreAbsent() {
|
||||
IrisRiverNetwork network = new IrisRiverNetwork()
|
||||
.setBiomes(new IrisRiverBiomes().setDry(new KList<>("dry")));
|
||||
|
||||
EffectiveRiverSettings settings = EffectiveRiverSettings.resolve(network, null, null);
|
||||
|
||||
assertTrue(settings.allowSources());
|
||||
assertEquals(IrisRiverRoutingPolicy.ALLOW, settings.routingPolicy());
|
||||
assertEquals(1D, settings.routingCostMultiplier(), 0D);
|
||||
assertEquals(List.of("dry"), settings.dryBiomes());
|
||||
assertEquals(IrisRiverTerminalMode.DRY_CHANNEL, settings.terminalMode());
|
||||
assertFalse(settings.terminalModeOverridden());
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
-53
@@ -1,53 +0,0 @@
|
||||
package art.arcane.iris.util.project.matter.slices;
|
||||
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
public class RiverCaveHydrologyMatterTest {
|
||||
@Test
|
||||
public void everyActionAndBiomeRoundTrips() throws IOException {
|
||||
RiverCaveHydrologyMatter matter = new RiverCaveHydrologyMatter();
|
||||
for (RiverCaveAction action : RiverCaveAction.values()) {
|
||||
for (RiverCaveFluidKind fluidKind : RiverCaveFluidKind.values()) {
|
||||
RiverCaveHydrology expected = new RiverCaveHydrology(
|
||||
action,
|
||||
"iris:flooded_grotto",
|
||||
fluidKind
|
||||
);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
matter.writeNode(expected, new DataOutputStream(bytes));
|
||||
|
||||
RiverCaveHydrology actual = matter.readNode(
|
||||
new DataInputStream(new ByteArrayInputStream(bytes.toByteArray())));
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyBiomeAndInvalidActionRemainUnambiguous() throws IOException {
|
||||
RiverCaveHydrologyMatter matter = new RiverCaveHydrologyMatter();
|
||||
RiverCaveHydrology expected = RiverCaveHydrology.of(RiverCaveAction.DRY_AIR);
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
matter.writeNode(expected, new DataOutputStream(bytes));
|
||||
|
||||
assertEquals(expected, matter.readNode(
|
||||
new DataInputStream(new ByteArrayInputStream(bytes.toByteArray()))));
|
||||
assertThrows(IOException.class, () -> matter.readNode(
|
||||
new DataInputStream(new ByteArrayInputStream(new byte[]{99}))));
|
||||
assertThrows(IOException.class, () -> matter.readNode(
|
||||
new DataInputStream(new ByteArrayInputStream(new byte[]{1, 99}))));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user