mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Simplified procedural feature generation
- Fungi build stem roles directly without intermediate maps or coordinate arrays - Tree palettes reuse one random source per object - Persistent leaves skip distance traversal when plausibility is disabled - Tree support scans avoid copied coordinate sets - Coral arm samples reuse their endpoint array - Sea decorators share scaled stack limits and respect output bounds - Unresolved sea floor palette entries preserve existing water - Tests cover stem geometry persistent leaves and sea stack limits
This commit is contained in:
@@ -474,7 +474,7 @@ final class DecoratorCore {
|
|||||||
&& (!caveSkipFluid || !B.isFluid(lower) && !B.isFluid(upper));
|
&& (!caveSkipFluid || !B.isFluid(lower) && !B.isFluid(upper));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int computeStack(IrisDecorator decorator, RNG rng, double realX, double realZ,
|
static int computeStack(IrisDecorator decorator, RNG rng, double realX, double realZ,
|
||||||
IrisData irisData, int max) {
|
IrisData irisData, int max) {
|
||||||
int stack = decorator.getHeight(rng, realX, realZ, irisData);
|
int stack = decorator.getHeight(rng, realX, realZ, irisData);
|
||||||
if (decorator.isScaleStack()) {
|
if (decorator.isScaleStack()) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
|||||||
@Override
|
@Override
|
||||||
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
|
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
|
||||||
Hunk<PlatformBlockState> data, IrisBiome biome, int height, int max) {
|
Hunk<PlatformBlockState> data, IrisBiome biome, int height, int max) {
|
||||||
|
int limit = Math.min(max, Math.min(getEngine().getHeight(), data.getHeight()));
|
||||||
|
if (height < 0 || height >= limit) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
RNG rng = getRNG(realX, realZ);
|
RNG rng = getRNG(realX, realZ);
|
||||||
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
|
IrisDecorator decorator = DecoratorCore.pickDecorator(biome, getPart(), partRNG, rng, getData(), realX, realZ);
|
||||||
|
|
||||||
@@ -51,34 +55,25 @@ public class IrisSeaFloorDecorator extends IrisEngineDecorator {
|
|||||||
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
|
&& !decorator.getSlopeCondition().isValid(getComplex().getSlopeStream().get(realX, realZ))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (height >= 0 && height < getEngine().getHeight()) {
|
PlatformBlockState block = decorator.getBlockData100(biome, rng, realX, height, realZ, getData());
|
||||||
data.set(x, height, z, decorator.getBlockData100(biome, rng, realX, height, realZ, getData()));
|
if (block != null) {
|
||||||
|
data.set(x, height, z, block);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int stack = decorator.getHeight(rng, realX, realZ, getData());
|
int stack = Math.min(limit - height,
|
||||||
if (decorator.isScaleStack()) {
|
DecoratorCore.computeStack(decorator, rng, realX, realZ, getData(), max - height));
|
||||||
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
|
|
||||||
} else {
|
|
||||||
stack = Math.min(stack, max - height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack == 1) {
|
|
||||||
data.set(x, height, z, decorator.getBlockDataForTop(biome, rng, realX, height, realZ, getData()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int engineHeight = getEngine().getHeight();
|
|
||||||
for (int i = 0; i < stack; i++) {
|
for (int i = 0; i < stack; i++) {
|
||||||
int h = height + i;
|
int h = height + i;
|
||||||
if (h > max || h > engineHeight) {
|
double threshold = stack == 1 ? 1.0 : ((double) i) / (stack - 1);
|
||||||
continue;
|
PlatformBlockState block = threshold >= decorator.getTopThreshold()
|
||||||
}
|
|
||||||
double threshold = ((double) i) / (stack - 1);
|
|
||||||
data.set(x, h, z, threshold >= decorator.getTopThreshold()
|
|
||||||
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
|
? decorator.getBlockDataForTop(biome, rng, realX, h, realZ, getData())
|
||||||
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData()));
|
: decorator.getBlockData100(biome, rng, realX, h, realZ, getData());
|
||||||
|
if (block == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data.set(x, h, z, block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,7 @@ public class IrisSeaSurfaceDecorator extends IrisEngineDecorator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int stack = decorator.getHeight(rng, realX, realZ, getData());
|
int stack = DecoratorCore.computeStack(decorator, rng, realX, realZ, getData(), max - height);
|
||||||
if (decorator.isScaleStack()) {
|
|
||||||
stack = (int) Math.ceil((double) (max - height) * ((double) stack / 100));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stack == 1) {
|
if (stack == 1) {
|
||||||
int targetY = height + 1;
|
int targetY = height + 1;
|
||||||
|
|||||||
@@ -170,7 +170,9 @@ public final class CoralGenerator {
|
|||||||
int y = (int) Math.round(oy + dy * t);
|
int y = (int) Math.round(oy + dy * t);
|
||||||
int z = (int) Math.round(oz + dz * t - sway);
|
int z = (int) Math.round(oz + dz * t - sway);
|
||||||
canvas.set(x, y, z, CoralCanvas.Role.STRUCTURE);
|
canvas.set(x, y, z, CoralCanvas.Role.STRUCTURE);
|
||||||
tip = new double[]{x, y, z};
|
tip[0] = x;
|
||||||
|
tip[1] = y;
|
||||||
|
tip[2] = z;
|
||||||
}
|
}
|
||||||
return tip;
|
return tip;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,11 +63,7 @@ public final class FungusGenerator {
|
|||||||
|
|
||||||
private static void buildUpright(Map<Vector3i, FungusCellRole> roles, IrisFungus fungus, int height, RNG rng, long baseSeed) {
|
private static void buildUpright(Map<Vector3i, FungusCellRole> roles, IrisFungus fungus, int height, RNG rng, long baseSeed) {
|
||||||
int stemHeight = Math.max(1, height);
|
int stemHeight = Math.max(1, height);
|
||||||
Map<Vector3i, PlatformBlockState> stemCells = new HashMap<>();
|
double[] top = FungusStemBuilder.build(roles, fungus, stemHeight, baseSeed);
|
||||||
double[] top = FungusStemBuilder.build(stemCells, fungus, stemHeight, baseSeed);
|
|
||||||
for (Vector3i v : stemCells.keySet()) {
|
|
||||||
roles.put(v, FungusCellRole.STEM);
|
|
||||||
}
|
|
||||||
|
|
||||||
int radius = pickRadius(fungus, rng);
|
int radius = pickRadius(fungus, rng);
|
||||||
double cx = top[0];
|
double cx = top[0];
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ package art.arcane.iris.engine.object.fungi;
|
|||||||
|
|
||||||
import art.arcane.iris.engine.object.IrisFungus;
|
import art.arcane.iris.engine.object.IrisFungus;
|
||||||
import art.arcane.iris.engine.object.tree.TreeFunctions;
|
import art.arcane.iris.engine.object.tree.TreeFunctions;
|
||||||
import art.arcane.iris.spi.PlatformBlockState;
|
|
||||||
import art.arcane.iris.util.common.math.Vector3i;
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -29,7 +28,7 @@ public final class FungusStemBuilder {
|
|||||||
private FungusStemBuilder() {
|
private FungusStemBuilder() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static double[] build(Map<Vector3i, PlatformBlockState> stemCells, IrisFungus fungus, int stemHeight, long seed) {
|
public static double[] build(Map<Vector3i, FungusCellRole> roles, IrisFungus fungus, int stemHeight, long seed) {
|
||||||
int width = Math.max(1, Math.min(3, fungus.getStemWidth()));
|
int width = Math.max(1, Math.min(3, fungus.getStemWidth()));
|
||||||
double maxLean = stemHeight * Math.tan(Math.toRadians(Math.max(0.0, fungus.getStemCurve())));
|
double maxLean = stemHeight * Math.tan(Math.toRadians(Math.max(0.0, fungus.getStemCurve())));
|
||||||
double leanRad = Math.toRadians(fungus.getStemLeanAzimuth());
|
double leanRad = Math.toRadians(fungus.getStemLeanAzimuth());
|
||||||
@@ -40,6 +39,7 @@ public final class FungusStemBuilder {
|
|||||||
double waveAzimuth = Math.toRadians(fungus.getStemLeanAzimuth() + 90.0);
|
double waveAzimuth = Math.toRadians(fungus.getStemLeanAzimuth() + 90.0);
|
||||||
double waveX = Math.sin(waveAzimuth);
|
double waveX = Math.sin(waveAzimuth);
|
||||||
double waveZ = Math.cos(waveAzimuth);
|
double waveZ = Math.cos(waveAzimuth);
|
||||||
|
double wavePhase = TreeFunctions.valueNoise1D(seed, seed) * Math.PI * 2.0;
|
||||||
|
|
||||||
double topCx = 0.0;
|
double topCx = 0.0;
|
||||||
double topCz = 0.0;
|
double topCz = 0.0;
|
||||||
@@ -48,12 +48,16 @@ public final class FungusStemBuilder {
|
|||||||
for (int y = 0; y < stemHeight; y++) {
|
for (int y = 0; y < stemHeight; y++) {
|
||||||
double t = stemHeight <= 1 ? 0.0 : y / (double) (stemHeight - 1);
|
double t = stemHeight <= 1 ? 0.0 : y / (double) (stemHeight - 1);
|
||||||
double lean = maxLean * (t * t);
|
double lean = maxLean * (t * t);
|
||||||
double wave = waveAmp * Math.sin(2.0 * Math.PI * wavePeriods * t + TreeFunctions.valueNoise1D(seed, seed) * Math.PI * 2.0);
|
double wave = waveAmp * Math.sin(2.0 * Math.PI * wavePeriods * t + wavePhase);
|
||||||
double cx = lean * leanX + wave * waveX;
|
double cx = lean * leanX + wave * waveX;
|
||||||
double cz = lean * leanZ + wave * waveZ;
|
double cz = lean * leanZ + wave * waveZ;
|
||||||
|
|
||||||
for (int[] xz : squarePositions(cx, cz, width)) {
|
int ox = (width % 2 == 1 ? (int) Math.round(cx) : (int) Math.floor(cx) + 1) - width / 2;
|
||||||
stemCells.put(new Vector3i(xz[0], y, xz[1]), null);
|
int oz = (width % 2 == 1 ? (int) Math.round(cz) : (int) Math.floor(cz) + 1) - width / 2;
|
||||||
|
for (int dx = 0; dx < width; dx++) {
|
||||||
|
for (int dz = 0; dz < width; dz++) {
|
||||||
|
roles.put(new Vector3i(ox + dx, y, oz + dz), FungusCellRole.STEM);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
topCx = cx;
|
topCx = cx;
|
||||||
@@ -62,34 +66,4 @@ public final class FungusStemBuilder {
|
|||||||
|
|
||||||
return new double[]{topCx, topY, topCz};
|
return new double[]{topCx, topY, topCz};
|
||||||
}
|
}
|
||||||
|
|
||||||
static int[][] squarePositions(double cx, double cz, int width) {
|
|
||||||
if (width % 2 == 1) {
|
|
||||||
int half = width / 2;
|
|
||||||
int icx = (int) Math.round(cx);
|
|
||||||
int icz = (int) Math.round(cz);
|
|
||||||
int[][] out = new int[width * width][2];
|
|
||||||
int idx = 0;
|
|
||||||
for (int dx = -half; dx <= half; dx++) {
|
|
||||||
for (int dz = -half; dz <= half; dz++) {
|
|
||||||
out[idx][0] = icx + dx;
|
|
||||||
out[idx][1] = icz + dz;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
int ox = (int) Math.floor(cx) - width / 2 + 1;
|
|
||||||
int oz = (int) Math.floor(cz) - width / 2 + 1;
|
|
||||||
int[][] out = new int[width * width][2];
|
|
||||||
int idx = 0;
|
|
||||||
for (int dx = 0; dx < width; dx++) {
|
|
||||||
for (int dz = 0; dz < width; dz++) {
|
|
||||||
out[idx][0] = ox + dx;
|
|
||||||
out[idx][1] = oz + dz;
|
|
||||||
idx++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,9 +66,10 @@ public final class ProceduralTreeGenerator {
|
|||||||
Map<TreeBlockCanvas.Vec, PlatformBlockState> resolved = new HashMap<>();
|
Map<TreeBlockCanvas.Vec, PlatformBlockState> resolved = new HashMap<>();
|
||||||
Set<TreeBlockCanvas.Vec> trunkPositions = new HashSet<>();
|
Set<TreeBlockCanvas.Vec> trunkPositions = new HashSet<>();
|
||||||
Set<TreeBlockCanvas.Vec> leafPositions = new HashSet<>();
|
Set<TreeBlockCanvas.Vec> leafPositions = new HashSet<>();
|
||||||
|
RNG paletteRng = new RNG(tree.getSeed());
|
||||||
for (Map.Entry<TreeBlockCanvas.Vec, TreeBlockCanvas.Cell> entry : canvas.getCells().entrySet()) {
|
for (Map.Entry<TreeBlockCanvas.Vec, TreeBlockCanvas.Cell> entry : canvas.getCells().entrySet()) {
|
||||||
TreeBlockCanvas.Cell cell = entry.getValue();
|
TreeBlockCanvas.Cell cell = entry.getValue();
|
||||||
PlatformBlockState state = TreeBlockResolver.resolve(tree, data, cell, entry.getKey());
|
PlatformBlockState state = TreeBlockResolver.resolve(tree, data, cell, entry.getKey(), paletteRng);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,10 +31,7 @@ public final class TreeBlockResolver {
|
|||||||
private TreeBlockResolver() {
|
private TreeBlockResolver() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PlatformBlockState resolve(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Cell cell, TreeBlockCanvas.Vec pos) {
|
public static PlatformBlockState resolve(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Cell cell, TreeBlockCanvas.Vec pos, RNG paletteRng) {
|
||||||
RNG paletteRng = new RNG(tree.getSeed());
|
|
||||||
RNG posRng = new RNG(tree.getSeed() ^ positionHash(pos));
|
|
||||||
|
|
||||||
switch (cell.role()) {
|
switch (cell.role()) {
|
||||||
case TRUNK -> {
|
case TRUNK -> {
|
||||||
PlatformBlockState state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
|
PlatformBlockState state = resolveBlock(tree.getTrunk(), tree.getTrunkPalette(), data, pos, paletteRng);
|
||||||
@@ -51,7 +48,7 @@ public final class TreeBlockResolver {
|
|||||||
return resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
return resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
||||||
}
|
}
|
||||||
case SECONDARY_LEAF -> {
|
case SECONDARY_LEAF -> {
|
||||||
PlatformBlockState state = resolveSecondaryLeaf(tree, data, pos, paletteRng, posRng);
|
PlatformBlockState state = resolveSecondaryLeaf(tree, data, pos, paletteRng);
|
||||||
if (state == null) {
|
if (state == null) {
|
||||||
state = resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
state = resolveBlock(tree.getLeaves(), tree.getLeavesPalette(), data, pos, paletteRng);
|
||||||
}
|
}
|
||||||
@@ -94,12 +91,12 @@ public final class TreeBlockResolver {
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PlatformBlockState resolveSecondaryLeaf(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Vec pos, RNG paletteRng, RNG posRng) {
|
private static PlatformBlockState resolveSecondaryLeaf(IrisProceduralTree tree, IrisData data, TreeBlockCanvas.Vec pos, RNG paletteRng) {
|
||||||
if (TreeTrunkBuilder.paletteSet(tree.getSecondaryLeavesPalette())) {
|
if (TreeTrunkBuilder.paletteSet(tree.getSecondaryLeavesPalette())) {
|
||||||
return tree.getSecondaryLeavesPalette().get(paletteRng, pos.x(), pos.y(), pos.z(), data);
|
return tree.getSecondaryLeavesPalette().get(paletteRng, pos.x(), pos.y(), pos.z(), data);
|
||||||
}
|
}
|
||||||
if (tree.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty()) {
|
if (tree.getWeightedSecondaryLeaves() != null && !tree.getWeightedSecondaryLeaves().isEmpty()) {
|
||||||
String picked = pickWeighted(tree, posRng);
|
String picked = pickWeighted(tree, new RNG(tree.getSeed() ^ positionHash(pos)));
|
||||||
return picked == null ? null : B.getStateOrNull(picked, false);
|
return picked == null ? null : B.getStateOrNull(picked, false);
|
||||||
}
|
}
|
||||||
if (tree.getSecondaryLeaves() != null && !tree.getSecondaryLeaves().isEmpty()) {
|
if (tree.getSecondaryLeaves() != null && !tree.getSecondaryLeaves().isEmpty()) {
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ public final class TreePlausibility {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<TreeBlockCanvas.Vec, Integer> distance = computeDistances(trunkPositions, realLeaves);
|
Map<TreeBlockCanvas.Vec, Integer> distance = tree.isPlausible()
|
||||||
|
? computeDistances(trunkPositions, realLeaves)
|
||||||
|
: Map.of();
|
||||||
|
|
||||||
for (TreeBlockCanvas.Vec v : realLeaves) {
|
for (TreeBlockCanvas.Vec v : realLeaves) {
|
||||||
PlatformBlockState leaf = resolved.get(v);
|
PlatformBlockState leaf = resolved.get(v);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package art.arcane.iris.engine.object.tree;
|
package art.arcane.iris.engine.object.tree;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public final class TreeSupport {
|
public final class TreeSupport {
|
||||||
@@ -36,7 +35,7 @@ public final class TreeSupport {
|
|||||||
TreeBlockCanvas.Vec worstWood = null;
|
TreeBlockCanvas.Vec worstWood = null;
|
||||||
int worstGap = -1;
|
int worstGap = -1;
|
||||||
|
|
||||||
for (TreeBlockCanvas.Vec leaf : new ArrayList<>(canvas.getLeaf())) {
|
for (TreeBlockCanvas.Vec leaf : canvas.getLeaf()) {
|
||||||
Integer d = dist.get(leaf);
|
Integer d = dist.get(leaf);
|
||||||
if (d != null && d <= LEGAL_DISTANCE) {
|
if (d != null && d <= LEGAL_DISTANCE) {
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public final class TreeTrunkBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void markExposedEnds(TreeBlockCanvas canvas) {
|
private static void markExposedEnds(TreeBlockCanvas canvas) {
|
||||||
for (TreeBlockCanvas.Vec v : new ArrayList<>(canvas.getTrunk())) {
|
for (TreeBlockCanvas.Vec v : canvas.getTrunk()) {
|
||||||
if (!canvas.has(v.x(), v.y() + 1, v.z()) || !canvas.has(v.x(), v.y() - 1, v.z())) {
|
if (!canvas.has(v.x(), v.y() + 1, v.z()) || !canvas.has(v.x(), v.y() - 1, v.z())) {
|
||||||
canvas.markExposed(v.x(), v.y(), v.z());
|
canvas.markExposed(v.x(), v.y(), v.z());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package art.arcane.iris.engine.decorator;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.framework.Engine;
|
||||||
|
import art.arcane.iris.engine.object.IrisBiome;
|
||||||
|
import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||||
|
import art.arcane.iris.engine.object.IrisDecorator;
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
|
import art.arcane.iris.util.project.hunk.Hunk;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyDouble;
|
||||||
|
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class IrisSeaDecoratorBoundsTest {
|
||||||
|
@Test
|
||||||
|
public void floorPercentageCannotGrowAboveTheWaterColumn() {
|
||||||
|
Fixture fixture = new Fixture(12, false);
|
||||||
|
fixture.stacking(200, 30);
|
||||||
|
|
||||||
|
fixture.decorate(4, 8);
|
||||||
|
|
||||||
|
assertSame(fixture.body, fixture.output.get(0, 4, 0));
|
||||||
|
assertSame(fixture.top, fixture.output.get(0, 7, 0));
|
||||||
|
assertNull(fixture.output.get(0, 8, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void floorPercentageRespectsAbsoluteMaximum() {
|
||||||
|
Fixture fixture = new Fixture(64, false);
|
||||||
|
fixture.stacking(100, 3);
|
||||||
|
|
||||||
|
fixture.decorate(4, 60);
|
||||||
|
|
||||||
|
assertSame(fixture.body, fixture.output.get(0, 4, 0));
|
||||||
|
assertSame(fixture.top, fixture.output.get(0, 6, 0));
|
||||||
|
assertNull(fixture.output.get(0, 7, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void surfacePercentageRespectsAbsoluteMaximum() {
|
||||||
|
Fixture fixture = new Fixture(64, true);
|
||||||
|
fixture.stacking(100, 3);
|
||||||
|
|
||||||
|
fixture.decorate(4, 64);
|
||||||
|
|
||||||
|
assertSame(fixture.body, fixture.output.get(0, 5, 0));
|
||||||
|
assertSame(fixture.top, fixture.output.get(0, 7, 0));
|
||||||
|
assertNull(fixture.output.get(0, 8, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void floorStackAtOutputCeilingUsesOneTopBlock() {
|
||||||
|
Fixture fixture = new Fixture(8, false);
|
||||||
|
fixture.stacking(200, 30);
|
||||||
|
|
||||||
|
fixture.decorate(7, 20);
|
||||||
|
|
||||||
|
assertSame(fixture.top, fixture.output.get(0, 7, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unresolvedFloorPalettePreservesExistingWater() {
|
||||||
|
Fixture fixture = new Fixture(8, false);
|
||||||
|
fixture.stacking(100, 30);
|
||||||
|
PlatformBlockState water = mock(PlatformBlockState.class);
|
||||||
|
fixture.output.set(0, 3, 0, water);
|
||||||
|
when(fixture.decorator.getBlockData100(any(), any(), anyDouble(), anyDouble(), anyDouble(), any())).thenReturn(null);
|
||||||
|
|
||||||
|
fixture.decorate(3, 8);
|
||||||
|
|
||||||
|
assertSame(water, fixture.output.get(0, 3, 0));
|
||||||
|
assertNull(fixture.output.get(0, 4, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Fixture {
|
||||||
|
private final IrisDecorator decorator = mock(IrisDecorator.class);
|
||||||
|
private final IrisBiome biome = mock(IrisBiome.class);
|
||||||
|
private final PlatformBlockState body = mock(PlatformBlockState.class);
|
||||||
|
private final PlatformBlockState top = mock(PlatformBlockState.class);
|
||||||
|
private final Hunk<PlatformBlockState> output;
|
||||||
|
private final IrisEngineDecorator placement;
|
||||||
|
|
||||||
|
private Fixture(int height, boolean surface) {
|
||||||
|
Engine engine = mock(Engine.class, RETURNS_DEEP_STUBS);
|
||||||
|
when(engine.getHeight()).thenReturn(height);
|
||||||
|
output = Hunk.newArrayHunk(1, height, 1);
|
||||||
|
IrisDecorationPart part = surface ? IrisDecorationPart.SEA_SURFACE : IrisDecorationPart.SEA_FLOOR;
|
||||||
|
when(biome.getDecoratorBucket(part)).thenReturn(new IrisDecorator[]{decorator});
|
||||||
|
when(decorator.passesChanceGate(any(), anyDouble(), anyDouble(), any())).thenReturn(true);
|
||||||
|
when(decorator.getBlockData100(any(), any(), anyDouble(), anyDouble(), anyDouble(), any())).thenReturn(body);
|
||||||
|
when(decorator.getBlockDataForTop(any(), any(), anyDouble(), anyDouble(), anyDouble(), any())).thenReturn(top);
|
||||||
|
when(decorator.getTopThreshold()).thenReturn(1D);
|
||||||
|
placement = surface ? new IrisSeaSurfaceDecorator(engine) : new IrisSeaFloorDecorator(engine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stacking(int percentage, int cap) {
|
||||||
|
when(decorator.isStacking()).thenReturn(true);
|
||||||
|
when(decorator.isScaleStack()).thenReturn(true);
|
||||||
|
when(decorator.getHeight(any(), anyDouble(), anyDouble(), any())).thenReturn(percentage);
|
||||||
|
when(decorator.getAbsoluteMaxStack()).thenReturn(cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decorate(int height, int maximum) {
|
||||||
|
placement.decorate(0, 0, 0, 0, 0, 0, 0, 0, output, biome, height, maximum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
package art.arcane.iris.engine.object.fungi;
|
||||||
|
|
||||||
|
import art.arcane.iris.engine.object.IrisFungus;
|
||||||
|
import art.arcane.iris.util.common.math.Vector3i;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
public class FungusStemBuilderTest {
|
||||||
|
@Test
|
||||||
|
public void straightStemsKeepOddAndEvenWidthAnchors() {
|
||||||
|
for (int width = 1; width <= 3; width++) {
|
||||||
|
IrisFungus fungus = new IrisFungus();
|
||||||
|
fungus.setStemWidth(width);
|
||||||
|
fungus.setStemCurve(0D);
|
||||||
|
fungus.setStemWaveAmplitude(0D);
|
||||||
|
Map<Vector3i, FungusCellRole> roles = new HashMap<>();
|
||||||
|
|
||||||
|
double[] top = FungusStemBuilder.build(roles, fungus, 7, 421L);
|
||||||
|
|
||||||
|
assertArrayEquals(new double[]{0D, 6D, 0D}, top, 0D);
|
||||||
|
assertEquals(width * width * 7, roles.size());
|
||||||
|
int origin = width == 3 ? -1 : 0;
|
||||||
|
for (int y = 0; y < 7; y++) {
|
||||||
|
for (int x = origin; x < origin + width; x++) {
|
||||||
|
for (int z = origin; z < origin + width; z++) {
|
||||||
|
assertEquals(FungusCellRole.STEM, roles.get(new Vector3i(x, y, z)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void curvedStemsKeepEveryLayerAndReturnTheirCapAnchor() {
|
||||||
|
IrisFungus fungus = new IrisFungus();
|
||||||
|
fungus.setStemWidth(3);
|
||||||
|
fungus.setStemCurve(22D);
|
||||||
|
fungus.setStemLeanAzimuth(225D);
|
||||||
|
fungus.setStemWaveAmplitude(2D);
|
||||||
|
fungus.setStemWavePeriods(1.5D);
|
||||||
|
Map<Vector3i, FungusCellRole> roles = new HashMap<>();
|
||||||
|
|
||||||
|
double[] top = FungusStemBuilder.build(roles, fungus, 24, -733L);
|
||||||
|
|
||||||
|
assertEquals(24 * 9, roles.size());
|
||||||
|
assertTrue(top[0] < 0D);
|
||||||
|
assertTrue(top[2] < 0D);
|
||||||
|
assertEquals(23D, top[1], 0D);
|
||||||
|
assertEquals(FungusCellRole.STEM, roles.get(new Vector3i(
|
||||||
|
(int) Math.round(top[0]), 23, (int) Math.round(top[2]))));
|
||||||
|
for (int y = 0; y < 24; y++) {
|
||||||
|
int count = 0;
|
||||||
|
for (Vector3i position : roles.keySet()) {
|
||||||
|
if (position.getY() == y) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(9, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -6,9 +6,11 @@ import art.arcane.iris.engine.object.IrisTreeBranches;
|
|||||||
import art.arcane.iris.engine.object.IrisTreeFunction;
|
import art.arcane.iris.engine.object.IrisTreeFunction;
|
||||||
import art.arcane.iris.engine.object.IrisTreeLeafMode;
|
import art.arcane.iris.engine.object.IrisTreeLeafMode;
|
||||||
import art.arcane.iris.engine.object.IrisTreeProfile;
|
import art.arcane.iris.engine.object.IrisTreeProfile;
|
||||||
|
import art.arcane.iris.spi.PlatformBlockState;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -18,6 +20,9 @@ import static org.junit.Assert.assertFalse;
|
|||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.assertSame;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class ProceduralTreeGeneratorTest {
|
public class ProceduralTreeGeneratorTest {
|
||||||
|
|
||||||
@@ -268,4 +273,23 @@ public class ProceduralTreeGeneratorTest {
|
|||||||
distances.get(new TreeBlockCanvas.Vec(x, 0, 0)));
|
distances.get(new TreeBlockCanvas.Vec(x, 0, 0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void disabledPlausibilityMakesDisconnectedLeavesPersistent() {
|
||||||
|
IrisProceduralTree tree = new IrisProceduralTree();
|
||||||
|
tree.setPlausible(false);
|
||||||
|
TreeBlockCanvas.Vec position = new TreeBlockCanvas.Vec(40, 20, -10);
|
||||||
|
PlatformBlockState leaf = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState persistent = mock(PlatformBlockState.class);
|
||||||
|
PlatformBlockState supported = mock(PlatformBlockState.class);
|
||||||
|
when(leaf.key()).thenReturn("minecraft:oak_leaves[persistent=false,distance=7]");
|
||||||
|
when(leaf.withProperty("persistent", "true")).thenReturn(persistent);
|
||||||
|
when(persistent.withProperty("distance", "1")).thenReturn(supported);
|
||||||
|
Map<TreeBlockCanvas.Vec, PlatformBlockState> resolved = new HashMap<>();
|
||||||
|
resolved.put(position, leaf);
|
||||||
|
|
||||||
|
TreePlausibility.apply(resolved, Set.of(new TreeBlockCanvas.Vec(0, 0, 0)), Set.of(position), tree);
|
||||||
|
|
||||||
|
assertSame(supported, resolved.get(position));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user