mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 14:00:26 +00:00
Fixing a handfull of issues!
This commit is contained in:
1 parent
1132242c58
commit
c3359e0a16
30 files changed
+1324
-282
No files matched your search
@@ -1,17 +1,26 @@
|
||||
package art.arcane.iris.generation.chunk;
|
||||
|
||||
import art.arcane.iris.platform.bukkit.nms.INMS;
|
||||
import art.arcane.iris.platform.bukkit.nms.INMSBinding;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.generator.ChunkGenerator;
|
||||
import org.junit.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ChunkDataHunkHolderTest {
|
||||
@@ -90,6 +99,53 @@ public class ChunkDataHunkHolderTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fallbackAppliesExplicitCaveAirBetweenSolidRunsWithNegativeMinimumHeight() {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(-256);
|
||||
when(chunk.getMaxHeight()).thenReturn(512);
|
||||
ChunkDataHunkHolder holder = new ChunkDataHunkHolder(chunk);
|
||||
BlockData dirt = mock(BlockData.class);
|
||||
BlockData coal = mock(BlockData.class);
|
||||
BlockData caveAir = mock(BlockData.class);
|
||||
NativeBlockState dirtState = state(dirt);
|
||||
NativeBlockState coalState = state(coal);
|
||||
NativeBlockState caveAirState = state(caveAir);
|
||||
BlockData[] applied = new BlockData[768];
|
||||
Arrays.fill(applied, dirt);
|
||||
for (int y = 0; y <= 75; y++) {
|
||||
holder.setRaw(14, y + 256, 6, y < 33 ? dirtState : y <= 54 ? caveAirState : coalState);
|
||||
}
|
||||
doAnswer(call -> {
|
||||
int y = call.getArgument(1);
|
||||
applied[y + 256] = call.getArgument(3);
|
||||
return null;
|
||||
}).when(chunk).setBlock(anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
doAnswer(call -> {
|
||||
int from = call.getArgument(1);
|
||||
int to = call.getArgument(4);
|
||||
BlockData block = call.getArgument(6);
|
||||
Arrays.fill(applied, from + 256, to + 256, block);
|
||||
return null;
|
||||
}).when(chunk).setRegion(anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), anyInt(), any(BlockData.class));
|
||||
|
||||
try (MockedStatic<INMS> nms = mockStatic(INMS.class)) {
|
||||
nms.when(INMS::get).thenReturn(mock(INMSBinding.class));
|
||||
holder.apply();
|
||||
}
|
||||
|
||||
for (int y = -256; y < 512; y++) {
|
||||
BlockData expected = y >= 33 && y <= 54 ? caveAir : y >= 55 && y <= 75 ? coal : dirt;
|
||||
assertSame("column at " + y, expected, applied[y + 256]);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeBlockState state(BlockData data) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(state.nativeHandle()).thenReturn(data);
|
||||
return state;
|
||||
}
|
||||
|
||||
private static ChunkDataHunkHolder holder(int height) {
|
||||
ChunkGenerator.ChunkData chunk = mock(ChunkGenerator.ChunkData.class);
|
||||
when(chunk.getMinHeight()).thenReturn(0);
|
||||
|
||||
+47
@@ -231,6 +231,53 @@ public class IrisCaveCarver3DNearParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sparseProfilesSampleOnlyActiveColumnsAndDiscardPreviousPassColumns() {
|
||||
for (int sampleStep : new int[]{1, 2, 3}) {
|
||||
for (boolean adaptive : new boolean[]{false, true}) {
|
||||
Engine engine = createEngine(128, 110);
|
||||
AtomicInteger heightSamples = new AtomicInteger();
|
||||
doAnswer(invocation -> {
|
||||
heightSamples.incrementAndGet();
|
||||
return 110;
|
||||
}).when(engine).getHeight(anyInt(), anyInt(), anyBoolean());
|
||||
IrisCaveProfile profile = createProfile(false, false)
|
||||
.setSampleStep(sampleStep).setAdaptiveSampling(adaptive)
|
||||
.setAllowFluid(false).setAllowSurfaceBreak(false)
|
||||
.setDensityThreshold(new IrisStyledRange(2D, 2D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
|
||||
IrisCaveCarver3D carver = new IrisCaveCarver3D(engine, profile);
|
||||
double[] weights = new double[256];
|
||||
weights[0] = 1D;
|
||||
weights[255] = 1D;
|
||||
WriterCapture first = createWriterCapture(128);
|
||||
|
||||
carver.carve(first.writer, 0, 0, weights, 0.08D, 0.24D);
|
||||
|
||||
assertEquals(2, heightSamples.get());
|
||||
assertFalse(first.carvedCells.isEmpty());
|
||||
for (String cell : first.carvedCells) {
|
||||
int x = coordinate(cell, 0);
|
||||
int z = coordinate(cell, 2);
|
||||
assertTrue(x == 0 && z == 0 || x == 15 && z == 15);
|
||||
}
|
||||
|
||||
Arrays.fill(weights, 0D);
|
||||
weights[(7 << 4) | 3] = 1D;
|
||||
heightSamples.set(0);
|
||||
WriterCapture second = createWriterCapture(128);
|
||||
|
||||
carver.carve(second.writer, 0, 0, weights, 0.08D, 0.24D);
|
||||
|
||||
assertEquals(1, heightSamples.get());
|
||||
assertFalse(second.carvedCells.isEmpty());
|
||||
for (String cell : second.carvedCells) {
|
||||
assertEquals(7, coordinate(cell, 0));
|
||||
assertEquals(3, coordinate(cell, 2));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verticalDensityPreservesTwoBlockModuleTunnelsWithoutBleedingAcrossBands() throws Exception {
|
||||
for (int step : new int[]{2, 4, 8}) {
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package art.arcane.iris.generation.mantle;
|
||||
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.cave.IrisCaveProfile;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.runtime.EngineMetrics;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.world.IrisWorld;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.stream.ProceduralStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class MantleCarvingComponentJunctionTest {
|
||||
@Test
|
||||
public void twoCaveProfilesBlendAcrossBothSidesOfAChunkBoundary() throws Exception {
|
||||
Fixture fixture = fixture(false);
|
||||
fixture.generate(false);
|
||||
|
||||
assertTrue(fixture.weight(-1, fixture.west(), 15, 12) > 0D);
|
||||
assertTrue(fixture.weight(-1, fixture.east(), 15, 12) > 0D);
|
||||
assertTrue(fixture.weight(0, fixture.west(), 0, 12) > 0D);
|
||||
assertTrue(fixture.weight(0, fixture.east(), 0, 12) > 0D);
|
||||
assertEquals(fixture.weight(-1, fixture.west(), 15, 12),
|
||||
fixture.weight(0, fixture.east(), 0, 12), 0D);
|
||||
assertEquals(fixture.weight(-1, fixture.east(), 15, 12),
|
||||
fixture.weight(0, fixture.west(), 0, 12), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void thirdProfileOutsideTheBlendRadiusDoesNotChangeBoundaryWeights() throws Exception {
|
||||
Fixture control = fixture(false);
|
||||
Fixture junction = fixture(true);
|
||||
control.generate(false);
|
||||
junction.generate(false);
|
||||
|
||||
assertEquals("A third biome outside the local blend radius must not remove the east cave from the west chunk",
|
||||
control.weight(-1, control.east(), 15, 12),
|
||||
junction.weight(-1, junction.east(), 15, 12), 0D);
|
||||
assertEquals("A third biome outside the local blend radius must not remove the west cave from the east chunk",
|
||||
control.weight(0, control.west(), 0, 12),
|
||||
junction.weight(0, junction.west(), 0, 12), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void threeWayJunctionRetainsEachProfileOnlyInsideItsLocalBlend() throws Exception {
|
||||
Fixture fixture = fixture(true);
|
||||
fixture.generate(false);
|
||||
|
||||
for (int chunkX : new int[]{-1, 0}) {
|
||||
int boundaryX = chunkX == -1 ? 15 : 0;
|
||||
assertEquals(3, fixture.captured().get(chunkX).size());
|
||||
assertTrue(fixture.weight(chunkX, fixture.west(), boundaryX, 6) > 0D);
|
||||
assertTrue(fixture.weight(chunkX, fixture.east(), boundaryX, 6) > 0D);
|
||||
assertTrue(fixture.weight(chunkX, fixture.north(), boundaryX, 6) > 0D);
|
||||
assertEquals(0D, fixture.weight(chunkX, fixture.north(), boundaryX, 12), 0D);
|
||||
assertEquals(0D, fixture.weight(chunkX, fixture.west(), boundaryX, 0), 0D);
|
||||
assertEquals(0D, fixture.weight(chunkX, fixture.east(), boundaryX, 0), 0D);
|
||||
assertEquals(1D, fixture.weight(chunkX, fixture.north(), boundaryX, 0), 0D);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void junctionWeightsRemainStableAcrossGenerationOrder() throws Exception {
|
||||
Fixture forward = fixture(true);
|
||||
Fixture reverse = fixture(true);
|
||||
forward.generate(false);
|
||||
reverse.generate(true);
|
||||
|
||||
for (int chunkX : new int[]{-1, 0}) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
for (int z = 0; z < 16; z++) {
|
||||
assertEquals(forward.weight(chunkX, forward.west(), x, z),
|
||||
reverse.weight(chunkX, reverse.west(), x, z), 0D);
|
||||
assertEquals(forward.weight(chunkX, forward.east(), x, z),
|
||||
reverse.weight(chunkX, reverse.east(), x, z), 0D);
|
||||
assertEquals(forward.weight(chunkX, forward.north(), x, z),
|
||||
reverse.weight(chunkX, reverse.north(), x, z), 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static Fixture fixture(boolean thirdProfile) throws ReflectiveOperationException {
|
||||
Engine engine = mock(Engine.class);
|
||||
EngineMantle engineMantle = mock(EngineMantle.class);
|
||||
when(engineMantle.getEngine()).thenReturn(engine);
|
||||
MantleCarvingComponent component = new MantleCarvingComponent(engineMantle);
|
||||
when(engine.getDimension()).thenReturn(new IrisDimension());
|
||||
when(engine.getWorld()).thenReturn(IrisWorld.builder().minHeight(0).maxHeight(128).build());
|
||||
when(engine.getMetrics()).thenReturn(new EngineMetrics(16));
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
when(complex.allowsMantleChunkWrite(anyInt(), anyInt())).thenReturn(true);
|
||||
ProceduralStream<Double> heights = mock(ProceduralStream.class);
|
||||
when(heights.getDouble(anyDouble(), anyDouble())).thenReturn(100D);
|
||||
when(complex.getNaturalHeightStream()).thenReturn(heights);
|
||||
ProceduralStream<IrisRegion> regions = mock(ProceduralStream.class);
|
||||
ProceduralStream<IrisBiome> surfaces = mock(ProceduralStream.class);
|
||||
ProceduralStream<IrisBiome> caves = mock(ProceduralStream.class);
|
||||
ProceduralStream<NativeBlockState> fluids = mock(ProceduralStream.class);
|
||||
when(complex.getRegionStream()).thenReturn(regions);
|
||||
when(complex.getNaturalTrueBiomeStream()).thenReturn(surfaces);
|
||||
when(complex.getCaveBiomeStream()).thenReturn(caves);
|
||||
when(complex.getFluidStream()).thenReturn(fluids);
|
||||
IrisCaveProfile west = new IrisCaveProfile().setEnabled(true);
|
||||
IrisCaveProfile east = new IrisCaveProfile().setEnabled(true);
|
||||
IrisCaveProfile north = new IrisCaveProfile().setEnabled(true);
|
||||
IrisBiome westBiome = new IrisBiome().setCaveProfile(west);
|
||||
IrisBiome eastBiome = new IrisBiome().setCaveProfile(east);
|
||||
IrisBiome northBiome = new IrisBiome().setCaveProfile(north);
|
||||
when(caves.get(anyDouble(), anyDouble())).thenAnswer(call -> {
|
||||
double x = call.getArgument(0);
|
||||
double z = call.getArgument(1);
|
||||
return thirdProfile && z < 7D ? northBiome : x < 0D ? westBiome : eastBiome;
|
||||
});
|
||||
ChunkContext context = mock(ChunkContext.class);
|
||||
when(context.getComplex()).thenReturn(complex);
|
||||
MantleWriter writer = mock(MantleWriter.class);
|
||||
Mantle<Matter> storage = mock(Mantle.class);
|
||||
when(storage.getWorldHeight()).thenReturn(128);
|
||||
when(writer.getMantle()).thenReturn(storage);
|
||||
Map<Integer, Map<IrisCaveProfile, double[]>> captured = new HashMap<>();
|
||||
Map<IrisCaveProfile, IrisCaveCarver3D> carvers = new IdentityHashMap<>();
|
||||
for (IrisCaveProfile profile : new IrisCaveProfile[]{west, east, north}) {
|
||||
IrisCaveCarver3D carver = mock(IrisCaveCarver3D.class);
|
||||
doAnswer(call -> {
|
||||
int chunkX = call.getArgument(1);
|
||||
double[] weights = call.getArgument(3);
|
||||
captured.computeIfAbsent(chunkX, ignored -> new IdentityHashMap<>()).put(profile, weights.clone());
|
||||
return 0;
|
||||
}).when(carver).carve(any(), anyInt(), anyInt(), any(), anyDouble(), anyDouble(),
|
||||
any(), any(), any(), any(), any());
|
||||
carvers.put(profile, carver);
|
||||
}
|
||||
Field profileCarvers = MantleCarvingComponent.class.getDeclaredField("profileCarvers");
|
||||
profileCarvers.setAccessible(true);
|
||||
profileCarvers.set(component, carvers);
|
||||
return new Fixture(component, context, writer, west, east, north, captured);
|
||||
}
|
||||
|
||||
private record Fixture(MantleCarvingComponent component, ChunkContext context, MantleWriter writer,
|
||||
IrisCaveProfile west, IrisCaveProfile east, IrisCaveProfile north,
|
||||
Map<Integer, Map<IrisCaveProfile, double[]>> captured) {
|
||||
private void generate(boolean reverse) {
|
||||
component.generateLayer(writer, reverse ? 0 : -1, 0, context);
|
||||
component.generateLayer(writer, reverse ? -1 : 0, 0, context);
|
||||
}
|
||||
|
||||
private double weight(int chunkX, IrisCaveProfile profile, int x, int z) {
|
||||
double[] weights = captured.get(chunkX).get(profile);
|
||||
return weights == null ? 0D : weights[(x << 4) | z];
|
||||
}
|
||||
}
|
||||
}
|
||||
-162
@@ -1,162 +0,0 @@
|
||||
package art.arcane.iris.generation.mantle;
|
||||
|
||||
import art.arcane.iris.generation.cave.IrisCaveProfile;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class MantleCarvingComponentTop2BlendTest {
|
||||
private static Constructor<?> weightedProfileConstructor;
|
||||
private static Method limitMethod;
|
||||
private static Field profileField;
|
||||
private static Field columnWeightsField;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
Class<?> weightedProfileClass = Class.forName("art.arcane.iris.generation.mantle.MantleCarvingComponent$WeightedProfile");
|
||||
weightedProfileConstructor = weightedProfileClass.getDeclaredConstructor(IrisCaveProfile.class, double[].class, double.class, Class.forName("art.arcane.iris.pack.value.IrisRange"), int.class);
|
||||
weightedProfileConstructor.setAccessible(true);
|
||||
limitMethod = MantleCarvingComponent.class.getDeclaredMethod("limitAndMergeBlendedProfiles", List.class, int.class, int.class);
|
||||
limitMethod.setAccessible(true);
|
||||
profileField = weightedProfileClass.getDeclaredField("profile");
|
||||
profileField.setAccessible(true);
|
||||
columnWeightsField = weightedProfileClass.getDeclaredField("columnWeights");
|
||||
columnWeightsField.setAccessible(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoProfilesAreKeptAndDroppedWeightsAreMergedIntoDominantColumns() throws Exception {
|
||||
WeightedInput input = createWeightedProfiles();
|
||||
List<?> limited = invokeLimit(input.weightedProfiles(), 2);
|
||||
assertEquals(2, limited.size());
|
||||
|
||||
Map<IrisCaveProfile, double[]> byProfile = extractWeightsByProfile(limited);
|
||||
IrisCaveProfile first = input.profiles().first();
|
||||
IrisCaveProfile second = input.profiles().second();
|
||||
|
||||
assertEquals(1.0D, byProfile.get(first)[1], 0D);
|
||||
assertEquals(1.0D, byProfile.get(second)[0], 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoMergeIsDeterministicAcrossRuns() throws Exception {
|
||||
WeightedInput firstInput = createWeightedProfiles();
|
||||
WeightedInput secondInput = createWeightedProfiles();
|
||||
List<?> first = invokeLimit(firstInput.weightedProfiles(), 2);
|
||||
List<?> second = invokeLimit(secondInput.weightedProfiles(), 2);
|
||||
|
||||
Map<IrisCaveProfile, double[]> firstByProfile = extractWeightsByProfile(first);
|
||||
Map<IrisCaveProfile, double[]> secondByProfile = extractWeightsByProfile(second);
|
||||
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().first())[0], secondByProfile.get(secondInput.profiles().first())[0], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().first())[1], secondByProfile.get(secondInput.profiles().first())[1], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().second())[0], secondByProfile.get(secondInput.profiles().second())[0], 0D);
|
||||
assertEquals(firstByProfile.get(firstInput.profiles().second())[1], secondByProfile.get(secondInput.profiles().second())[1], 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoMergePreservesIndependentWeightsAtHighColumnIndexes() throws Exception {
|
||||
WeightedInput input = createWeightedProfiles();
|
||||
List<?> limited = invokeLimit(input.weightedProfiles(), 2);
|
||||
|
||||
Map<IrisCaveProfile, double[]> byProfile = extractWeightsByProfile(limited);
|
||||
IrisCaveProfile first = input.profiles().first();
|
||||
IrisCaveProfile second = input.profiles().second();
|
||||
|
||||
assertEquals(1.0D, byProfile.get(first)[255], 0D);
|
||||
assertEquals(1.0D, byProfile.get(second)[254], 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void topTwoTieBreaksBySequenceNotIdentity() throws Exception {
|
||||
IrisCaveProfile first = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.0D);
|
||||
IrisCaveProfile second = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.0D);
|
||||
IrisCaveProfile third = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.0D);
|
||||
|
||||
double[] weightsA = new double[256];
|
||||
double[] weightsB = new double[256];
|
||||
double[] weightsC = new double[256];
|
||||
weightsA[0] = 0.5D;
|
||||
weightsB[0] = 0.5D;
|
||||
weightsC[0] = 0.5D;
|
||||
|
||||
List<Object> weighted = new ArrayList<>();
|
||||
weighted.add(weightedProfileConstructor.newInstance(first, weightsA, average(weightsA), null, 0));
|
||||
weighted.add(weightedProfileConstructor.newInstance(second, weightsB, average(weightsB), null, 1));
|
||||
weighted.add(weightedProfileConstructor.newInstance(third, weightsC, average(weightsC), null, 2));
|
||||
|
||||
List<?> limited = invokeLimit(weighted, 2);
|
||||
assertEquals(2, limited.size());
|
||||
|
||||
Map<IrisCaveProfile, double[]> byProfile = extractWeightsByProfile(limited);
|
||||
assertEquals(true, byProfile.containsKey(first));
|
||||
assertEquals(true, byProfile.containsKey(second));
|
||||
assertEquals(false, byProfile.containsKey(third));
|
||||
}
|
||||
|
||||
private WeightedInput createWeightedProfiles() throws Exception {
|
||||
IrisCaveProfile first = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.31D);
|
||||
IrisCaveProfile second = new IrisCaveProfile().setEnabled(true).setBaseWeight(1.17D);
|
||||
IrisCaveProfile third = new IrisCaveProfile().setEnabled(true).setBaseWeight(0.93D);
|
||||
Profiles profiles = new Profiles(first, second, third);
|
||||
|
||||
double[] firstWeights = new double[256];
|
||||
firstWeights[0] = 0.2D;
|
||||
firstWeights[1] = 0.8D;
|
||||
firstWeights[255] = 0.6D;
|
||||
|
||||
double[] secondWeights = new double[256];
|
||||
secondWeights[0] = 0.7D;
|
||||
secondWeights[1] = 0.1D;
|
||||
secondWeights[254] = 0.7D;
|
||||
|
||||
double[] thirdWeights = new double[256];
|
||||
thirdWeights[0] = 0.3D;
|
||||
thirdWeights[1] = 0.4D;
|
||||
thirdWeights[254] = 0.3D;
|
||||
thirdWeights[255] = 0.4D;
|
||||
|
||||
List<Object> weighted = new ArrayList<>();
|
||||
weighted.add(weightedProfileConstructor.newInstance(first, firstWeights, average(firstWeights), null, 0));
|
||||
weighted.add(weightedProfileConstructor.newInstance(second, secondWeights, average(secondWeights), null, 1));
|
||||
weighted.add(weightedProfileConstructor.newInstance(third, thirdWeights, average(thirdWeights), null, 2));
|
||||
return new WeightedInput(weighted, profiles);
|
||||
}
|
||||
|
||||
private List<?> invokeLimit(List<Object> weightedProfiles, int limit) throws Exception {
|
||||
return (List<?>) limitMethod.invoke(null, weightedProfiles, limit, 256);
|
||||
}
|
||||
|
||||
private Map<IrisCaveProfile, double[]> extractWeightsByProfile(List<?> weightedProfiles) throws Exception {
|
||||
Map<IrisCaveProfile, double[]> byProfile = new IdentityHashMap<>();
|
||||
for (Object weightedProfile : weightedProfiles) {
|
||||
IrisCaveProfile profile = (IrisCaveProfile) profileField.get(weightedProfile);
|
||||
double[] weights = (double[]) columnWeightsField.get(weightedProfile);
|
||||
byProfile.put(profile, weights);
|
||||
}
|
||||
return byProfile;
|
||||
}
|
||||
|
||||
private double average(double[] weights) {
|
||||
double total = 0D;
|
||||
for (double weight : weights) {
|
||||
total += weight;
|
||||
}
|
||||
return total / weights.length;
|
||||
}
|
||||
|
||||
private record Profiles(IrisCaveProfile first, IrisCaveProfile second, IrisCaveProfile third) {
|
||||
}
|
||||
|
||||
private record WeightedInput(List<Object> weightedProfiles, Profiles profiles) {
|
||||
}
|
||||
}
|
||||
+81
-1
@@ -13,6 +13,7 @@ import art.arcane.iris.structure.object.ObjectPlaceMode;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.runtime.IrisComplex;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.mantle.EngineMantle;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.iris.testsupport.PlatformBinding;
|
||||
import art.arcane.volmlib.util.math.Vector3i;
|
||||
@@ -30,7 +31,10 @@ import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class NativeStructureObjectVetoTest {
|
||||
@@ -52,6 +56,9 @@ public class NativeStructureObjectVetoTest {
|
||||
when(complex.getHeightStream()).thenReturn(heightStream);
|
||||
engine = mock(Engine.class);
|
||||
when(engine.getHeight()).thenReturn(256);
|
||||
when(engine.getHeight(anyInt(), anyInt())).thenReturn(SURFACE_Y);
|
||||
when(engine.getHeight(anyInt(), anyInt(), eq(true))).thenAnswer(invocation ->
|
||||
engine.getHeight(invocation.getArgument(0), invocation.getArgument(1)));
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(engine.getDimension()).thenReturn(mock(IrisDimension.class));
|
||||
volumes();
|
||||
@@ -67,6 +74,79 @@ public class NativeStructureObjectVetoTest {
|
||||
assertFalse(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenSkirtRejectsATreeWhoseGroundWillBeLoweredOutsideThePiece() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, 64, -1, 11, 65, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, 64, 2)), 24, 64)));
|
||||
|
||||
assertEquals(-1, place(placer));
|
||||
assertTrue(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenSkirtKeepsATreeWhereTheGradeDoesNotLowerItsGround() {
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, 96, -1, 11, 97, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, 96, 2)), 24, 64)));
|
||||
|
||||
assertTrue(place(placer) >= 0);
|
||||
assertFalse(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenCutKeepsObjectsBelowTheRemainingCaveRoof() {
|
||||
when(engine.getHeight(anyInt(), anyInt())).thenReturn(140);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, 120, -1, 11, 121, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, 120, 2)), 24, 64)));
|
||||
|
||||
assertTrue(place(placer) >= 0);
|
||||
assertFalse(placer.written().isEmpty());
|
||||
verify(engine, times(1)).getHeight(0, 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenCutKeepsObjectsAboveTheRemovedSurfaceBand() {
|
||||
when(engine.getHeight(anyInt(), anyInt())).thenReturn(40);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, 20, -1, 11, 21, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, 20, 2)), 24, 64)));
|
||||
|
||||
assertTrue(place(placer) >= 0);
|
||||
assertFalse(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenCutUsesHostGroundBelowAnUpperTerrainLayer() {
|
||||
EngineMantle mantle = mock(EngineMantle.class);
|
||||
when(engine.getMantle()).thenReturn(mantle);
|
||||
when(mantle.trueHeight(anyInt(), anyInt())).thenReturn(40);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, 20, -1, 11, 21, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, 20, 2)), 24, 64)));
|
||||
|
||||
assertTrue(place(placer) >= 0);
|
||||
assertFalse(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flattenCutConvertsTheHostGroundToNegativeWorldCoordinates() {
|
||||
when(engine.getMinHeight()).thenReturn(-256);
|
||||
RecordingPlacer placer = new RecordingPlacer(engine);
|
||||
volumes(new NativeStructureVolume("minecraft:village_plains", 1, -190, -1, 11, -189, 7,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(1, 11, -1, 7, -190, 2)), 24, 64)));
|
||||
|
||||
assertEquals(-1, place(placer));
|
||||
assertTrue(placer.written().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canopyBlockInsideAPieceRejectsTheWholeObject() {
|
||||
int canopyY = plantedTopY();
|
||||
@@ -250,7 +330,7 @@ public class NativeStructureObjectVetoTest {
|
||||
}
|
||||
|
||||
private NativeStructureVolume volume(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return new NativeStructureVolume("minecraft:village_plains", minX, minY, minZ, maxX, maxY, maxZ);
|
||||
return new NativeStructureVolume("minecraft:village_plains", minX, minY, minZ, maxX, maxY, maxZ, null);
|
||||
}
|
||||
|
||||
private static NativeBlockState state(String key, boolean solid) {
|
||||
|
||||
+1
-1
@@ -105,7 +105,7 @@ public class NativeStructureVolumeMemoTest {
|
||||
}
|
||||
|
||||
private static NativeStructureVolume volume(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||
return new NativeStructureVolume("minecraft:village_swamp", minX, minY, minZ, maxX, maxY, maxZ);
|
||||
return new NativeStructureVolume("minecraft:village_swamp", minX, minY, minZ, maxX, maxY, maxZ, null);
|
||||
}
|
||||
|
||||
private static final class RecordingHooks implements EnginePlatformHooks {
|
||||
|
||||
+46
-1
@@ -4,13 +4,16 @@ import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class NativeStructureVolumeTest {
|
||||
private static final NativeStructureVolume PIECE =
|
||||
new NativeStructureVolume("minecraft:village_plains", 10, 64, 20, 25, 78, 40);
|
||||
new NativeStructureVolume("minecraft:village_plains", 10, 64, 20, 25, 78, 40, null);
|
||||
|
||||
@Test
|
||||
public void rectQueriesIgnoreTheVerticalAxis() {
|
||||
@@ -53,4 +56,46 @@ public class NativeStructureVolumeTest {
|
||||
assertEquals(PIECE.maxY(), normalized.maxY());
|
||||
assertEquals(PIECE.maxZ(), normalized.maxZ());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void terrainInfluenceDoesNotChangePhysicalIntersection() {
|
||||
NativeStructureVolume volume = adjustedPoint(0, 90, 0, 24);
|
||||
assertTrue(volume.intersectsInfluenceRect(-1, 0, -1, 0));
|
||||
assertFalse(volume.intersects(-1, 40, 0, -1, 160, 0));
|
||||
assertFalse(volume.intersectsRect(-1, 0, -1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oppositeGradesBlendWithoutCuttingTheSurface() {
|
||||
List<NativeStructureVolume> volumes = List.of(adjustedPoint(0, 90, 0, 20), adjustedPoint(20, 110, 0, 20));
|
||||
assertEquals(100, NativeStructureVolume.surfaceCutY(volumes, 10, 0, 100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void halfBlockCutsRoundTheirSignedDepthLikeTheFitter() {
|
||||
assertEquals(99, NativeStructureVolume.surfaceCutY(List.of(adjustedPoint(0, 99, 0, 24)), 12, 0, 100));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void realVillageJunctionsLowerTheReportedPineGround() {
|
||||
int[][] junctions = {{-2591,144,-7062},{-2591,144,-7061},{-2593,141,-7055},{-2592,141,-7055},
|
||||
{-2591,163,-7070},{-2591,163,-7071},{-2608,149,-7075},{-2609,149,-7075},
|
||||
{-2591,136,-7051},{-2591,137,-7051},{-2602,126,-7047},{-2591,136,-7050},
|
||||
{-2591,137,-7050},{-2602,126,-7046},{-2602,122,-7042},{-2602,122,-7041},
|
||||
{-2624,124,-7067},{-2624,124,-7068}};
|
||||
List<NativeStructureVolume.SurfaceAnchor> anchors = new ArrayList<>();
|
||||
for (int[] junction : junctions) {
|
||||
anchors.add(new NativeStructureVolume.SurfaceAnchor(junction[0], junction[0], junction[2], junction[2], junction[1], 1));
|
||||
}
|
||||
NativeStructureVolume volume = new NativeStructureVolume("minecraft:village_plains", -2600, 144, -7070, -2590, 145, -7062,
|
||||
new NativeStructureVolume.SurfaceAdjustment(anchors, 24, 64));
|
||||
assertEquals(151, NativeStructureVolume.surfaceCutY(List.of(volume), -2601, -7063, 162));
|
||||
assertEquals(162, NativeStructureVolume.surfaceCutY(List.of(volume), -2700, -7063, 162));
|
||||
}
|
||||
|
||||
private static NativeStructureVolume adjustedPoint(int x, int groundY, int z, int padding) {
|
||||
return new NativeStructureVolume("minecraft:village_plains", x, groundY, z, x, groundY, z,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(x, x, z, z, groundY, 1)), padding, 64));
|
||||
}
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package art.arcane.iris.structure.object;
|
||||
|
||||
import art.arcane.iris.generation.block.TileData;
|
||||
import art.arcane.iris.generation.decoration.tree.TreeBlockCanvas;
|
||||
import art.arcane.iris.generation.decoration.tree.TreeGrounding;
|
||||
import art.arcane.iris.generation.geometry.IrisBlockVector;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.Vector3i;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class IrisObjectFractionalScalingTest {
|
||||
@Test
|
||||
public void fractionalReductionRetainsBothLayersWhenTwoOutputVoxelsRemain() {
|
||||
NativeBlockState stem = state("minecraft:mushroom_stem");
|
||||
NativeBlockState cap = state("minecraft:red_mushroom_block");
|
||||
IrisObject source = layers(stem, cap);
|
||||
for (boolean savedOrigin : new boolean[]{false, true}) {
|
||||
for (double factor : new double[]{0.6D, 0.75D, 0.9D}) {
|
||||
IrisObject scaled = scale(source, factor, savedOrigin);
|
||||
|
||||
assertEquals(2, scaled.getH());
|
||||
assertEquals(2, scaled.getBlocks().size());
|
||||
assertSame(stem, scaled.getBlocks().get(scaled.getSigned(0, 0, 0)));
|
||||
assertSame(cap, scaled.getBlocks().get(scaled.getSigned(0, 1, 0)));
|
||||
}
|
||||
}
|
||||
assertSame(stem, source.getBlocks().get(source.getSigned(0, 0, 0)));
|
||||
assertSame(cap, source.getBlocks().get(source.getSigned(0, 1, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalEnlargementUsesTheDominantMaterialInSharedOutputVoxels() {
|
||||
NativeBlockState lower = state("minecraft:stone");
|
||||
NativeBlockState upper = state("minecraft:dirt");
|
||||
IrisObject source = layers(lower, upper);
|
||||
for (boolean savedOrigin : new boolean[]{false, true}) {
|
||||
IrisObject scaled = scale(source, 1.75D, savedOrigin);
|
||||
|
||||
assertEquals(4, scaled.getH());
|
||||
for (int y = 0; y < 4; y++) {
|
||||
assertSame(y < 2 ? lower : upper, scaled.getBlocks().get(scaled.getSigned(0, y, 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalReductionResolvesMaterialCoverageAcrossAllThreeAxes() {
|
||||
IrisObject source = new IrisObject(2, 2, 2);
|
||||
for (int x = 0; x < 2; x++) {
|
||||
for (int y = 0; y < 2; y++) {
|
||||
for (int z = 0; z < 2; z++) {
|
||||
source.setUnsigned(x, y, z, state("test:voxel_" + x + y + z));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IrisObject scaled = source.scaled(0.75D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
assertEquals(8, scaled.getBlocks().size());
|
||||
for (int x = 0; x < 2; x++) {
|
||||
for (int y = 0; y < 2; y++) {
|
||||
for (int z = 0; z < 2; z++) {
|
||||
assertSame(source.getBlocks().get(source.getSigned(x, y, z)),
|
||||
scaled.getBlocks().get(scaled.getSigned(x, y, z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fractionalSavedOriginPreservesNegativeCoordinatesAndTheirTiles() {
|
||||
NativeBlockState lower = state("minecraft:chest");
|
||||
NativeBlockState upper = state("minecraft:barrel");
|
||||
IrisObject source = new IrisObject(1, 4, 1);
|
||||
source.setCenter(new Vector3i(0, 3, 0));
|
||||
source.setUnsigned(0, 1, 0, lower);
|
||||
source.setUnsignedTile(0, 1, 0, tile("minecraft:chest", "lower"));
|
||||
source.setUnsigned(0, 2, 0, upper);
|
||||
source.setUnsignedTile(0, 2, 0, tile("minecraft:barrel", "upper"));
|
||||
|
||||
IrisObject scaled = source.scaledAroundOrigin(0.75D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
assertEquals(new Vector3i(0, 3, 0), scaled.getCenter());
|
||||
assertEquals(-3, scaled.getAABB().min().getY());
|
||||
assertEquals(0, scaled.getAABB().max().getY());
|
||||
assertSame(lower, scaled.getBlocks().get(new IrisBlockVector(0, -2, 0)));
|
||||
assertSame(upper, scaled.getBlocks().get(new IrisBlockVector(0, -1, 0)));
|
||||
assertEquals("lower", scaled.getStates().get(new IrisBlockVector(0, -2, 0)).getProperties().get("name"));
|
||||
assertEquals("upper", scaled.getStates().get(new IrisBlockVector(0, -1, 0)).getProperties().get("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameMaterialTilesFollowTheirDominantSourceVoxel() {
|
||||
NativeBlockState chest = state("minecraft:chest");
|
||||
IrisObject source = layers(chest, chest);
|
||||
TileData lower = tile("minecraft:chest", "lower");
|
||||
TileData upper = tile("minecraft:chest", "upper");
|
||||
source.setUnsignedTile(0, 0, 0, lower);
|
||||
source.setUnsignedTile(0, 1, 0, upper);
|
||||
|
||||
IrisObject scaled = source.scaled(0.75D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
TileData scaledLower = scaled.getStates().get(scaled.getSigned(0, 0, 0));
|
||||
TileData scaledUpper = scaled.getStates().get(scaled.getSigned(0, 1, 0));
|
||||
assertEquals(lower, scaledLower);
|
||||
assertEquals(upper, scaledUpper);
|
||||
assertNotSame(lower, scaledLower);
|
||||
assertNotSame(lower.getProperties(), scaledLower.getProperties());
|
||||
assertNotSame(upper, scaledUpper);
|
||||
assertNotSame(scaledLower.getProperties(), scaledUpper.getProperties());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sameMaterialCanopyRolesAndRootContactsFollowTheDominantSourceVoxel() {
|
||||
NativeBlockState wood = state("minecraft:spruce_wood");
|
||||
IrisObject source = layers(wood, wood);
|
||||
TreeGrounding grounding = TreeGrounding.create(List.of(new TreeGrounding.Root(0, 0, 0, wood)),
|
||||
8, Set.of(new TreeBlockCanvas.Vec(0, 1, 0)));
|
||||
source.setTreeGrounding(grounding);
|
||||
for (boolean savedOrigin : new boolean[]{false, true}) {
|
||||
IrisObject scaled = scale(source, 0.75D, savedOrigin);
|
||||
int bottom = -scaled.getCenter().getY();
|
||||
TreeGrounding result = scaled.getTreeGrounding();
|
||||
|
||||
assertEquals(List.of(new TreeGrounding.Root(0, bottom, 0, wood)), result.roots());
|
||||
assertFalse(result.isCanopy(0, bottom, 0));
|
||||
assertTrue(result.isCanopy(0, bottom + 1, 0));
|
||||
assertEquals(6, result.maxExtension());
|
||||
}
|
||||
assertSame(grounding, source.getTreeGrounding());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canopyWinningAnUnresolvableRootCollisionDoesNotBecomeAGroundContact() {
|
||||
NativeBlockState wood = state("minecraft:spruce_wood");
|
||||
IrisObject source = layers(wood, wood);
|
||||
source.setTreeGrounding(TreeGrounding.create(List.of(new TreeGrounding.Root(0, 0, 0, wood)),
|
||||
8, Set.of(new TreeBlockCanvas.Vec(0, 1, 0))));
|
||||
|
||||
IrisObject scaled = source.scaled(0.5D, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
|
||||
assertEquals(1, scaled.getBlocks().size());
|
||||
assertTrue(scaled.getTreeGrounding().roots().isEmpty());
|
||||
assertTrue(scaled.getTreeGrounding().isCanopy(0, 0, 0));
|
||||
}
|
||||
|
||||
private static IrisObject layers(NativeBlockState lower, NativeBlockState upper) {
|
||||
IrisObject source = new IrisObject(1, 2, 1);
|
||||
source.setCenter(new Vector3i(0, 0, 0));
|
||||
source.setUnsigned(0, 0, 0, lower);
|
||||
source.setUnsigned(0, 1, 0, upper);
|
||||
return source;
|
||||
}
|
||||
|
||||
private static IrisObject scale(IrisObject source, double scale, boolean savedOrigin) {
|
||||
return savedOrigin ? source.scaledAroundOrigin(scale, IrisObjectPlacementScaleInterpolator.NONE)
|
||||
: source.scaled(scale, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
}
|
||||
|
||||
private static TileData tile(String material, String name) {
|
||||
KMap<String, Object> properties = new KMap<>();
|
||||
properties.put("name", name);
|
||||
return new TileData(material, properties);
|
||||
}
|
||||
|
||||
private static NativeBlockState state(String key) {
|
||||
NativeBlockState state = mock(NativeBlockState.class);
|
||||
when(state.key()).thenReturn(key);
|
||||
when(state.materialKey()).thenReturn(key);
|
||||
return state;
|
||||
}
|
||||
}
|
||||
+85
@@ -620,6 +620,91 @@ public class IrisObjectPlacementRunnerRegressionTest {
|
||||
assertSame(machine, placer.get(0, ANCHOR_Y + 9, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void organicMushroomStiltsUseTheStemAndStopAtTheCaveFloor() {
|
||||
NativeBlockState stem = state("minecraft:mushroom_stem", true);
|
||||
NativeBlockState cap = state("minecraft:red_mushroom_block", true);
|
||||
IrisObject mushroom = new IrisObject(1, 2, 1);
|
||||
mushroom.setUnsigned(0, 0, 0, stem);
|
||||
mushroom.setUnsigned(0, 1, 0, cap);
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(0, 0, 0, 30, 120, 140);
|
||||
placer.set(0, 30, 0, solid);
|
||||
placer.writes().clear();
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.ORGANIC_STILT)
|
||||
.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
|
||||
|
||||
mushroom.place(0, 60, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertEquals(1, placer.writesOf(cap).size());
|
||||
for (int y = 31; y < 60; y++) {
|
||||
assertSame(stem, placer.get(0, y, 0));
|
||||
}
|
||||
assertSame(solid, placer.get(0, 30, 0));
|
||||
assertTrue(placer.writes().stream().allMatch(write -> write.y() >= 31));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultOrganicStiltsDoNotReachAFloorMoreThanOneHundredBlocksBelow() {
|
||||
NativeBlockState stem = state("minecraft:mushroom_stem", true);
|
||||
IrisObject mushroom = new IrisObject(1, 1, 1);
|
||||
mushroom.setUnsigned(0, 0, 0, stem);
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.set(0, 30, 0, solid);
|
||||
placer.writes().clear();
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.ORGANIC_STILT)
|
||||
.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
|
||||
|
||||
mushroom.place(0, 160, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertEquals(9, placer.world.values().stream().filter(block -> block == stem).count());
|
||||
assertTrue(placer.writesOf(stem).stream().allMatch(write -> write.y() >= 152));
|
||||
assertNull(placer.get(0, 151, 0));
|
||||
assertSame(solid, placer.get(0, 30, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shrinkingTwoBlockMushroomsPreservesTheStemUsedByOrganicStilts() {
|
||||
NativeBlockState stem = state("minecraft:mushroom_stem", true);
|
||||
NativeBlockState cap = state("minecraft:red_mushroom_block", true);
|
||||
IrisObject mushroom = new IrisObject(1, 2, 1);
|
||||
mushroom.setUnsigned(0, 0, 0, stem);
|
||||
mushroom.setUnsigned(0, 1, 0, cap);
|
||||
IrisObject scaled = mushroom.scaled(0.75, IrisObjectPlacementScaleInterpolator.NONE);
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.set(0, ANCHOR_Y, 0, solid);
|
||||
placer.writes().clear();
|
||||
IrisObjectPlacement placement = placement().setMode(ObjectPlaceMode.ORGANIC_STILT)
|
||||
.setStiltSettings(new IrisStiltSettings().setOrganicJitter(0).setOrganicScratch(0));
|
||||
|
||||
scaled.place(0, ANCHOR_Y + 10, 0, placer, placement, new RNG(2L), data);
|
||||
|
||||
assertSame(stem, placer.get(0, ANCHOR_Y + 1, 0));
|
||||
assertEquals(1, placer.writesOf(cap).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void centerHeightSurfaceSupportAllowsAnImportedRootAboveALowerAdjacentColumn() {
|
||||
NativeBlockState wood = state("minecraft:spruce_wood", true);
|
||||
IrisObject tree = new IrisObject(7, 27, 7);
|
||||
tree.getBlocks().put(new IrisBlockVector(0, -13, 0), wood);
|
||||
tree.getBlocks().put(new IrisBlockVector(1, -13, 0), wood);
|
||||
tree.getBlocks().put(new IrisBlockVector(1, -13, -1), wood);
|
||||
RecordingPlacer placer = new RecordingPlacer(null);
|
||||
placer.terrain(0, 0, 0, 167);
|
||||
placer.terrain(1, 0, 0, 163);
|
||||
placer.terrain(1, -1, 0, 163);
|
||||
IrisObjectPlacement placement = new IrisObjectPlacement()
|
||||
.setRotation(IrisObjectRotation.of(0, 0, 0));
|
||||
|
||||
assertTrue(tree.place(0, -1, 0, placer, placement, new RNG(2L), data) >= 0);
|
||||
|
||||
assertSame(wood, placer.get(1, 167, 0));
|
||||
assertNull(placer.get(1, 166, 0));
|
||||
assertFalse(placer.isSurfaceSolid(1, 166, 0));
|
||||
assertEquals(163, placer.highest(1, 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stiltPaletteCannotIntroduceExcludedMaterialsOrContainers() {
|
||||
NativeBlockState calcite = state("minecraft:calcite", true);
|
||||
|
||||
@@ -10,6 +10,8 @@ import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.testsupport.PlatformBinding;
|
||||
import art.arcane.volmlib.nativelib.terrain.NativeBlockState;
|
||||
import art.arcane.volmlib.nativelib.terrain.structure.NativeStructureVolume;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
@@ -84,6 +86,30 @@ public class TreeGroundingPlacementTest {
|
||||
assertFalse(terrain.written.containsKey(new Position(2, 79, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nativeFlattenInfluenceDoesNotBlockRootExtensionsBelowItsPiece() {
|
||||
Terrain terrain = new Terrain();
|
||||
terrain.heights.put(new Column(1, 0), 77);
|
||||
Engine engine = data.getEngine();
|
||||
when(terrain.placer.getEngine()).thenReturn(engine);
|
||||
when(engine.getDimension()).thenReturn(new IrisDimension());
|
||||
when(engine.getHeight(anyInt(), anyInt(), anyBoolean())).thenAnswer(invocation ->
|
||||
terrain.surface(invocation.getArgument(0), invocation.getArgument(1)));
|
||||
NativeStructureVolume volume = new NativeStructureVolume("minecraft:village_plains",
|
||||
10, 100, 0, 12, 110, 2,
|
||||
new NativeStructureVolume.SurfaceAdjustment(List.of(
|
||||
new NativeStructureVolume.SurfaceAnchor(10, 12, 0, 2, 100, 4)), 16, 16));
|
||||
when(engine.getNativeStructureVolumes(anyInt(), anyInt(), anyInt(), anyInt()))
|
||||
.thenReturn(new KList<>(List.of(volume)));
|
||||
|
||||
assertTrue(place(tree(true), terrain, placement()) >= 0);
|
||||
|
||||
for (int y = 77; y <= 81; y++) {
|
||||
assertSame(wood, terrain.written.get(new Position(1, y, 0)));
|
||||
}
|
||||
assertFalse(terrain.written.containsKey(new Position(1, 76, 0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cliffRejectionWritesNoPartOfTheTreeOrListener() {
|
||||
Terrain terrain = new Terrain();
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package art.arcane.iris.world;
|
||||
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.platform.generation.BukkitChunkGenerator;
|
||||
import art.arcane.iris.world.lifecycle.WorldLifecycleCaller;
|
||||
import art.arcane.iris.world.lifecycle.WorldLifecycleRequest;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.WorldCreator;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.mockito.MockedConstruction;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mockConstruction;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
public class IrisWorldCreatorSeedTest {
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void explicitSeedsReachTheGeneratorAndLifecycleRequestWithoutDefaultSubstitution() throws Exception {
|
||||
for (long seed : new long[]{0L, 78264193L, -734829104958217364L, Long.MIN_VALUE, Long.MAX_VALUE}) {
|
||||
assertCreatedSeed(new IrisWorldCreator().seed(seed), seed);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void omittedSeedUsesTheCreationDefault() throws Exception {
|
||||
assertCreatedSeed(new IrisWorldCreator(), 1337L);
|
||||
}
|
||||
|
||||
private void assertCreatedSeed(IrisWorldCreator creator, long expectedSeed) throws Exception {
|
||||
NamespacedKey key = new NamespacedKey("iris", "seed-selection");
|
||||
File root = temporaryFolder.newFolder();
|
||||
IrisDimension dimension = new IrisDimension();
|
||||
dimension.setLoadKey("cohesion");
|
||||
WorldCreator bukkitCreator = WorldCreatorCompat.ofKey(key);
|
||||
List<IrisWorld> worlds = new ArrayList<>();
|
||||
try (MockedStatic<IrisWorldStorage> storage = mockStatic(IrisWorldStorage.class);
|
||||
MockedStatic<WorldCreatorCompat> compatibility = mockStatic(WorldCreatorCompat.class);
|
||||
MockedConstruction<BukkitChunkGenerator> generators = mockConstruction(BukkitChunkGenerator.class,
|
||||
(generator, context) -> worlds.add((IrisWorld) context.arguments().getFirst()))) {
|
||||
storage.when(() -> IrisWorldStorage.keyFromName("seed-selection")).thenReturn(key);
|
||||
storage.when(() -> IrisWorldStorage.dimensionRoot(key)).thenReturn(root);
|
||||
compatibility.when(() -> WorldCreatorCompat.ofKey(key)).thenReturn(bukkitCreator);
|
||||
compatibility.when(() -> WorldCreatorCompat.keyOf(bukkitCreator)).thenReturn(key);
|
||||
|
||||
WorldCreator created = creator.name("seed-selection").dimension(dimension).create();
|
||||
WorldLifecycleRequest request = WorldLifecycleRequest.fromCreator(
|
||||
created, false, false, WorldLifecycleCaller.CREATE);
|
||||
|
||||
assertEquals(1, generators.constructed().size());
|
||||
assertEquals(expectedSeed, worlds.getFirst().getRawWorldSeed());
|
||||
assertEquals(expectedSeed, created.seed());
|
||||
assertEquals(expectedSeed, request.seed());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user