This commit is contained in:
Brian Neumann-Fopiano
2026-08-05 23:39:54 -06:00
parent ce8c4ff578
commit 967c082372
48 changed files with 2351 additions and 349 deletions
@@ -528,7 +528,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
int steps = GenerationStep.Decoration.values().length;
List<NativePlacementGroup> placementGroups = new ArrayList<>();
List<StructureStart> heightmapStarts = new ArrayList<>();
List<NativeStructureVegetationClearer.VegetationTarget> vegetationTargets = new ArrayList<>();
List<StructureStart> vegetationTargets = new ArrayList<>();
List<NativeStructureTerrainIntegrator.TerrainTarget> terrainTargets = new ArrayList<>();
for (int step = 0; step < steps; step++) {
int index = 0;
@@ -559,11 +559,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
structureId, start,
NativeStructureTerrainIntegrator.resolveNativeTerrain(
start, decision.terrain())));
boolean clearEntireFootprint = NativeStructureVegetationClearer
.shouldClearEntireVegetationFootprint(
structure.step(), decision.clearVegetation());
vegetationTargets.add(new NativeStructureVegetationClearer.VegetationTarget(
start, clearEntireFootprint));
vegetationTargets.add(start);
}
if (!resolvedPlacements.isEmpty()) {
placementGroups.add(new NativePlacementGroup(
@@ -592,8 +588,9 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"vegetation cleanup", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
NativeStructureSurfaceFitter.VacuumFoundationPlan vacuumFoundationPlan;
try {
NativeStructureSurfaceFitter.prepareSurfaceStructures(
vacuumFoundationPlan = NativeStructureSurfaceFitter.prepareSurfaceStructures(
world, area, terrainTargets,
(x, z) -> engine.getHeight(x, z, true) + engine.getMinHeight());
} catch (Throwable error) {
@@ -621,6 +618,14 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
"placement", group.structureId(), chunkPos.x(), chunkPos.z(), error);
}
}
try {
NativeStructureSurfaceFitter.repairVacuumFoundations(
world, area, vacuumFoundationPlan);
} catch (Throwable error) {
throw NativeStructureGenerationException.failure(
"foundation repair", nativeStructureBatchContext(placementGroups),
chunkPos.x(), chunkPos.z(), error);
}
}
private static String nativeStructureBatchContext(List<NativePlacementGroup> placementGroups) {
@@ -45,6 +45,7 @@ public class IrisChunkGeneratorFailureContractTest {
assertTrue(placement.contains("\"resolution\""));
assertTrue(placement.contains("\"terrain integration\""));
assertTrue(placement.contains("\"terrain preparation\""));
assertTrue(placement.contains("\"foundation repair\""));
assertFalse(placement.contains("\"terrain carving\""));
assertTrue(placement.contains("\"vegetation cleanup\""));
assertTrue(placement.contains("\"placement\""));
@@ -55,6 +56,8 @@ public class IrisChunkGeneratorFailureContractTest {
< placement.indexOf("prepareSurfaceStructures"));
assertTrue(placement.indexOf("prepareSurfaceStructures")
< placement.indexOf("for (NativePlacementGroup group"));
assertTrue(placement.indexOf("repairVacuumFoundations")
> placement.indexOf("for (NativePlacementGroup group"));
assertFalse(placement.contains("IrisLogging.reportError"));
}
@@ -40,6 +40,7 @@ import net.minecraft.world.level.levelgen.structure.StructurePiece;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.pools.JigsawJunction;
import net.minecraft.world.level.levelgen.structure.pools.LegacySinglePoolElement;
import net.minecraft.world.level.levelgen.structure.pools.ListPoolElement;
import net.minecraft.world.level.levelgen.structure.pools.SinglePoolElement;
@@ -54,6 +55,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProc
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureProcessor;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplate;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
import net.minecraft.world.level.levelgen.placement.PlacedFeature;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -131,7 +133,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
}
@Test
public void explicitVacuumForcesThinSurfaceFittingWithoutAuthoredAdaptation() {
public void explicitVacuumUsesIndependentTerrainFittingWithoutAuthoredAdaptation() {
StructureStart none = desertStart(TerrainAdjustment.NONE);
StructureStart box = desertStart(TerrainAdjustment.BEARD_BOX);
NativeStructureTerrainIntegrator.TerrainTarget sourceNone =
@@ -150,11 +152,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertFalse(NativeStructureSurfaceFitter.requiresSurfaceTerrain(sourceNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumNone));
assertTrue(NativeStructureSurfaceFitter.requiresSurfaceTerrain(vacuumBox));
assertEquals(TerrainAdjustment.BEARD_THIN,
NativeStructureSurfaceFitter.effectiveSurfaceAdjustment(vacuumNone));
assertEquals(TerrainAdjustment.BEARD_THIN,
NativeStructureSurfaceFitter.effectiveSurfaceAdjustment(vacuumBox));
assertTrue(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
assertFalse(NativeStructureTerrainIntegrator.clearsLegacyTemplateAir(
none, vacuumNone.terrain()));
}
@@ -263,6 +261,476 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 11, 70, 1));
}
@Test
public void vacuumFitsOnlyProcessedSolidTemplateColumns() throws Exception {
StructureTemplate sparseTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(15, 0, 0, Blocks.STONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement(
sparseTemplate, List.of(new ReplaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 64, 0, 15, 70, 3), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 56, 0, 15, 72, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 64, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 63, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 15, 60, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 15, 61, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 15, 64, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 15, 60, 3));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 15, 64, 3));
}
@Test
public void vacuumSupportsGroundDeltaZeroFoundations() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template),
new BoundingBox(0, 66, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 3, 72, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, 64, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 66, 0));
}
@Test
public void vacuumFindsActualTerrainBelowNominalOpenAirSurface() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template),
new BoundingBox(0, 75, 0, 0, 81, 0), 2, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 3, 84, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 80);
for (int y = 61; y <= 74; y++) {
assertFalse(state(blocks, 0, y, 0).isAir());
}
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 74, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 75, 0));
}
@Test
public void vacuumSupportsFoundationBelowHigherNaturalSurface() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineLegacyPoolElement(template),
new BoundingBox(0, 75, 0, 0, 81, 0), 2, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 84, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
put(blocks, 0, 78, 0, Blocks.DIRT.defaultBlockState());
put(blocks, 0, 79, 0, Blocks.GRASS_BLOCK.defaultBlockState());
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 79);
for (int y = 61; y <= 74; y++) {
assertFalse(state(blocks, 0, y, 0).isAir());
}
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 75, 0));
assertEquals(Blocks.DIRT.defaultBlockState(), state(blocks, 0, 78, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 79, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 15, 60, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 15, 61, 0));
}
@Test
public void vacuumRepairUsesOnlyProcessedSolidFoundationColumns() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.STONE.defaultBlockState()),
block(2, 0, 0, Blocks.AIR.defaultBlockState()),
block(2, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
InlineSinglePoolElement element = new InlineSinglePoolElement(
template, List.of(new ReplaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
element, new BoundingBox(0, 65, 0, 2, 72, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 3, 74, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.VacuumFoundationPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 64; y++) {
put(blocks, 0, y, 0, Blocks.CAVE_AIR.defaultBlockState());
}
put(blocks, 0, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
put(blocks, 1, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
put(blocks, 2, 67, 0, Blocks.COBBLESTONE.defaultBlockState());
BlockState processorAirSupport = state(blocks, 1, 64, 0);
BlockState authoredAirSupport = state(blocks, 2, 66, 0);
NativeStructureSurfaceFitter.repairVacuumFoundations(
world(blocks), area, plan);
for (int y = 61; y <= 64; y++) {
assertFalse(state(blocks, 0, y, 0).isAir());
}
assertEquals(processorAirSupport, state(blocks, 1, 64, 0));
assertEquals(authoredAirSupport, state(blocks, 2, 66, 0));
}
@Test
public void vacuumRepairPreservesOverlappingAuthoredInterior() throws Exception {
PoolElementStructurePiece lower = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(0, 1, 0, Blocks.AIR.defaultBlockState())))),
new BoundingBox(0, 61, 0, 0, 62, 0), 0, Rotation.NONE);
PoolElementStructurePiece upper = rigidTemplatePiece(
new InlineLegacyPoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 65, 0, 0, 70, 0), 0, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(lower, upper), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 3, 72, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.VacuumFoundationPlan plan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
put(blocks, 0, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
NativeStructureSurfaceFitter.repairVacuumFoundations(
world(blocks), area, plan);
for (int y = 61; y <= 64; y++) {
assertTrue(state(blocks, 0, y, 0).isAir());
}
}
@Test
public void vacuumRepairIsStableAcrossSplitChunkAreas() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineLegacyPoolElement(template),
new BoundingBox(15, 65, 0, 16, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 72, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 72, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 72, 15);
Map<BlockPos, BlockState> wideBlocks = flatTerrain(wideArea, 60);
Map<BlockPos, BlockState> splitBlocks = flatTerrain(wideArea, 60);
NativeStructureSurfaceFitter.VacuumFoundationPlan widePlan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(wideBlocks), wideArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.VacuumFoundationPlan westPlan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(splitBlocks), westArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.VacuumFoundationPlan eastPlan =
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(splitBlocks), eastArea, List.of(target), (x, z) -> 60);
for (int x = 15; x <= 16; x++) {
for (int y = 61; y <= 64; y++) {
put(wideBlocks, x, y, 0, Blocks.CAVE_AIR.defaultBlockState());
put(splitBlocks, x, y, 0, Blocks.CAVE_AIR.defaultBlockState());
}
put(wideBlocks, x, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
put(splitBlocks, x, 65, 0, Blocks.COBBLESTONE.defaultBlockState());
}
NativeStructureSurfaceFitter.repairVacuumFoundations(
world(wideBlocks), wideArea, widePlan);
NativeStructureSurfaceFitter.repairVacuumFoundations(
world(splitBlocks), eastArea, eastPlan);
NativeStructureSurfaceFitter.repairVacuumFoundations(
world(splitBlocks), westArea, westPlan);
assertEquals(wideBlocks, splitBlocks);
}
@Test
public void vacuumNeverLowersExistingTerrainAroundSparsePieces() throws Exception {
StructureTemplate sparseTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(sparseTemplate),
new BoundingBox(0, 56, 0, 7, 62, 7), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 48, 0, 7, 70, 7);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumDoesNotRaiseTerrainToRoofsAboveLegacyAir() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineLegacyPoolElement(template),
new BoundingBox(0, 66, 0, 7, 72, 7), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 74, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumCapsNeighborTaperAtAuthoredAirGroundPlanes() throws Exception {
PoolElementStructurePiece roof = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.AIR.defaultBlockState()),
block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(1, 66, 0, 1, 72, 0), 1, Rotation.NONE);
PoolElementStructurePiece highChild = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 90, 0, 0, 94, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(roof, highChild), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 96, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 1, 66, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 1, 67, 0));
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 89, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 90, 0));
}
@Test
public void vacuumDoesNotAnchorRoofsAboveProcessorCreatedAir() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.STONE.defaultBlockState()),
block(0, 2, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template, List.of(new ReplaceBlockProcessor(
Blocks.STONE.defaultBlockState(), Blocks.AIR.defaultBlockState()))),
new BoundingBox(0, 66, 0, 7, 72, 7), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 74, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumDoesNotTreatSparseRoofCellsAsFoundations() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.SANDSTONE.defaultBlockState()),
block(1, 20, 0, Blocks.SANDSTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template),
new BoundingBox(0, 66, 0, 20, 100, 20), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 20, 102, 20);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 0, 65, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 0, 66, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 1, 67, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 1, 86, 0));
}
@Test
public void vacuumDoesNotFlattenTerrainMatchingPoolPieces() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(
template, List.of(),
StructureTemplatePool.Projection.TERRAIN_MATCHING),
new BoundingBox(0, 68, 0, 15, 72, 15), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 74, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumRetainsJunctionSmoothingForRigidFeaturePieces() {
StructurePoolElement feature = StructurePoolElement.feature(
Holder.<PlacedFeature>direct(null))
.apply(StructureTemplatePool.Projection.RIGID);
PoolElementStructurePiece piece = rigidTemplatePiece(
feature, new BoundingBox(4, 66, 4, 4, 70, 4), 1, Rotation.NONE);
piece.addJunction(new JigsawJunction(
4, 66, 4, 0, StructureTemplatePool.Projection.RIGID));
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 72, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 4, 61, 4));
}
@Test
public void vacuumDoesNotInventNonPoolBoundingBoxFootprints() {
StructureStart start = desertStart(TerrainAdjustment.NONE);
BoundingBox bounds = start.getBoundingBox();
BoundingBox area = new BoundingBox(
bounds.minX() - 12, 40, bounds.minZ() - 12,
bounds.maxX() + 12, bounds.maxY() + 12, bounds.maxZ() + 12);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 50);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 50);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumBudgetExhaustionSkipsFittingWithoutFailing() throws Exception {
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 66, 0, 0, 70, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 72, 15);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
Map<BlockPos, BlockState> originalBlocks = new HashMap<>(blocks);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60, 1);
assertEquals(originalBlocks, blocks);
}
@Test
public void vacuumContinuouslySupportsProcessedChildBases() throws Exception {
PoolElementStructurePiece center = rigidTemplatePiece(
new InlineSinglePoolElement(template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())))),
new BoundingBox(0, 60, 0, 0, 64, 0), 1, Rotation.NONE);
StructureTemplate childTemplate = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece child = rigidTemplatePiece(
new InlineSinglePoolElement(childTemplate),
new BoundingBox(10, 66, 0, 10, 72, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(
List.of(center, child), TerrainAdjustment.NONE);
BoundingBox area = new BoundingBox(0, 54, 0, 15, 74, 3);
Map<BlockPos, BlockState> blocks = flatTerrain(area, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(blocks), area,
List.of(surfaceTarget(start, IrisStructureTerrainMode.VACUUM)),
(x, z) -> 60);
for (int y = 61; y <= 65; y++) {
assertFalse(state(blocks, 10, y, 0).isAir());
}
assertEquals(Blocks.GRASS_BLOCK.defaultBlockState(), state(blocks, 10, 65, 0));
assertEquals(Blocks.AIR.defaultBlockState(), state(blocks, 10, 66, 0));
}
@Test
public void vacuumSurfaceFittingIsStableAcrossSplitChunkAreas() throws Exception {
StructureTemplate template = template(List.of(
block(0, 0, 0, Blocks.COBBLESTONE.defaultBlockState()),
block(1, 0, 0, Blocks.COBBLESTONE.defaultBlockState())));
PoolElementStructurePiece piece = rigidTemplatePiece(
new InlineSinglePoolElement(template),
new BoundingBox(15, 66, 0, 16, 72, 0), 1, Rotation.NONE);
StructureStart start = rigidSurfaceStart(List.of(piece), TerrainAdjustment.NONE);
NativeStructureTerrainIntegrator.TerrainTarget target =
surfaceTarget(start, IrisStructureTerrainMode.VACUUM);
BoundingBox wideArea = new BoundingBox(0, 54, 0, 31, 74, 15);
BoundingBox westArea = new BoundingBox(0, 54, 0, 15, 74, 15);
BoundingBox eastArea = new BoundingBox(16, 54, 0, 31, 74, 15);
Map<BlockPos, BlockState> wideBlocks = flatTerrain(wideArea, 60);
Map<BlockPos, BlockState> splitBlocks = flatTerrain(wideArea, 60);
Map<BlockPos, BlockState> reverseSplitBlocks = flatTerrain(wideArea, 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(wideBlocks), wideArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(splitBlocks), westArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(splitBlocks), eastArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(reverseSplitBlocks), eastArea, List.of(target), (x, z) -> 60);
NativeStructureSurfaceFitter.prepareSurfaceStructures(
world(reverseSplitBlocks), westArea, List.of(target), (x, z) -> 60);
assertEquals(wideBlocks, splitBlocks);
assertEquals(wideBlocks, reverseSplitBlocks);
}
@Test
public void surfaceAnchorIsFlushInsideAndUnchangedAtRadius() {
NativeStructureSurfaceFitter.SurfaceAnchor anchor = anchor(72, 2);
@@ -863,8 +1331,7 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
BoundingBox area = new BoundingBox(0, 63, 0, 0, 68, 0);
NativeStructureVegetationClearer.clearIntersectingVegetation(
world, chunk, area, List.of(
new NativeStructureVegetationClearer.VegetationTarget(desertStart(), false)));
world, chunk, area, List.of(desertStart()));
NativeStructureSurfaceFitter.applySurfaceColumn(
world, new BlockPos.MutableBlockPos(),
0, 0, 64, 68, -64, 319);
@@ -1642,9 +2109,15 @@ public class NativeStructurePostProcessorSurfaceTerrainTest {
private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors) {
this(template, processors, StructureTemplatePool.Projection.RIGID);
}
private InlineSinglePoolElement(
StructureTemplate template, List<StructureProcessor> processors,
StructureTemplatePool.Projection projection) {
super(Either.right(template),
Holder.direct(new StructureProcessorList(processors)),
StructureTemplatePool.Projection.RIGID,
projection,
Optional.<LiquidSettings>empty());
}
}
@@ -1,27 +1,55 @@
package art.arcane.iris.nativegen;
import com.mojang.serialization.Codec;
import net.minecraft.SharedConstants;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderSet;
import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.server.Bootstrap;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.LevelHeightAccessor;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.chunk.PalettedContainer;
import net.minecraft.world.level.chunk.PalettedContainerFactory;
import net.minecraft.world.level.chunk.ProtoChunk;
import net.minecraft.world.level.chunk.Strategy;
import net.minecraft.world.level.chunk.UpgradeData;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.structure.TerrainAdjustment;
import net.minecraft.world.level.levelgen.structure.pieces.PiecesContainer;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidPiece;
import net.minecraft.world.level.levelgen.structure.structures.DesertPyramidStructure;
import org.junit.BeforeClass;
import org.junit.Test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class NativeStructurePostProcessorVegetationTest {
private static final long TEST_SEED = 1234L;
private static final int MIN_Y = -64;
private static final int MAX_Y = 319;
private static final int GROUND_Y = 64;
private static final BoundingBox AREA = new BoundingBox(0, MIN_Y, 0, 15, MAX_Y, 15);
@BeforeClass
public static void bootstrapMinecraftRegistries() {
SharedConstants.tryDetectVersion();
@@ -29,52 +57,108 @@ public class NativeStructurePostProcessorVegetationTest {
}
@Test
public void surfaceStructuresClearTreeColumnsAutomatically() {
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(100, 100, false));
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(116, 100, false));
public void canopyAboveTheStructureRoofSurvivesVegetationClearing() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
for (int y = bounds.maxY() - 1; y <= GROUND_Y + 40; y++) {
fillLayer(chunk, y, leaves());
}
write(chunk, 4, bounds.minY() - 1, 4, log());
clearVegetation(chunk, start);
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
assertEquals(leaves(), chunk.getBlockState(new BlockPos(x, GROUND_Y + 40, z)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(x, bounds.maxY() + 1, z)));
assertEquals(air(), chunk.getBlockState(new BlockPos(x, bounds.maxY(), z)));
assertEquals(air(), chunk.getBlockState(new BlockPos(x, bounds.maxY() - 1, z)));
}
}
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 1, 4)));
}
@Test
public void buriedStructuresPreserveUnrelatedSurfaceForest() {
assertFalse(NativeStructureVegetationClearer.shouldClearVegetationColumn(99, 100, false));
assertFalse(NativeStructureVegetationClearer.shouldClearVegetationColumn(20, 100, false));
public void buriedStructuresPreserveTheForestAboveThem() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, -20);
write(chunk, 8, GROUND_Y + 1, 8, log());
write(chunk, 8, GROUND_Y + 2, 8, log());
write(chunk, 8, GROUND_Y + 3, 8, log());
write(chunk, 8, GROUND_Y + 4, 8, leaves());
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 1, 8)));
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 2, 8)));
assertEquals(log(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 3, 8)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(8, GROUND_Y + 4, 8)));
}
@Test
public void explicitVegetationOptionForcesUnusualPlacementCleanup() {
assertTrue(NativeStructureVegetationClearer.shouldClearVegetationColumn(20, 100, true));
public void columnsOutsideThePieceFootprintKeepEveryTreeBlock() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(8, 8, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
int inside = bounds.minY() + 4;
write(chunk, 0, inside, 0, log());
write(chunk, 0, GROUND_Y + 40, 0, leaves());
write(chunk, 12, inside, 12, log());
write(chunk, 12, GROUND_Y + 40, 12, leaves());
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(0, inside, 0)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(0, GROUND_Y + 40, 0)));
assertEquals(air(), chunk.getBlockState(new BlockPos(12, inside, 12)));
assertEquals(leaves(), chunk.getBlockState(new BlockPos(12, GROUND_Y + 40, 12)));
}
@Test
public void nonForcedValidTargetsReachIntersectionProcessing() {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(HolderSet.empty()));
StructureStart start = new StructureStart(
structure, new ChunkPos(0, 0), 0,
new PiecesContainer(List.of(
new DesertPyramidPiece(RandomSource.create(1L), 0, 0))));
public void clearingStopsAtThePieceEnvelopeBoundaries() {
ProtoChunk chunk = chunk();
StructureStart start = desertStart(0, 0, GROUND_Y + 15);
BoundingBox bounds = start.getBoundingBox();
write(chunk, 4, bounds.maxY() + 1, 4, log());
write(chunk, 4, bounds.maxY(), 4, log());
write(chunk, 4, bounds.minY() - 1, 4, log());
write(chunk, 4, bounds.minY() - 2, 4, log());
assertTrue(NativeStructureVegetationClearer.shouldProcessTarget(
new NativeStructureVegetationClearer.VegetationTarget(start, false)));
assertFalse(NativeStructureVegetationClearer.shouldProcessTarget(
new NativeStructureVegetationClearer.VegetationTarget(
StructureStart.INVALID_START, true)));
clearVegetation(chunk, start);
assertEquals(log(), chunk.getBlockState(new BlockPos(4, bounds.maxY() + 1, 4)));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.maxY(), 4)));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 1, 4)));
assertEquals(log(), chunk.getBlockState(new BlockPos(4, bounds.minY() - 2, 4)));
}
@Test
public void surfaceStructuresPreserveVegetationUnlessConfigured() {
assertFalse(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.SURFACE_STRUCTURES, false));
assertTrue(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.SURFACE_STRUCTURES, true));
public void overlappingPieceEnvelopesMergeIntoOneClearedRange() {
ProtoChunk chunk = chunk();
StructureStart low = desertStart(0, 0, GROUND_Y);
StructureStart high = desertStart(0, 0, GROUND_Y + 40);
int between = GROUND_Y + 20;
write(chunk, 4, between, 4, log());
write(chunk, 4, high.getBoundingBox().maxY() + 1, 4, log());
write(chunk, 4, low.getBoundingBox().minY() - 2, 4, log());
NativeStructureVegetationClearer.clearIntersectingVegetation(
world(chunk), chunk, AREA, List.of(low, high));
assertEquals(air(), chunk.getBlockState(new BlockPos(4, between, 4)));
assertEquals(log(), chunk.getBlockState(
new BlockPos(4, high.getBoundingBox().maxY() + 1, 4)));
assertEquals(log(), chunk.getBlockState(
new BlockPos(4, low.getBoundingBox().minY() - 2, 4)));
}
@Test
public void undergroundStructuresPreserveSurfaceVegetationUnlessConfigured() {
assertFalse(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.UNDERGROUND_STRUCTURES, false));
assertTrue(NativeStructureVegetationClearer.shouldClearEntireVegetationFootprint(
GenerationStep.Decoration.UNDERGROUND_STRUCTURES, true));
public void validStartsReachIntersectionProcessing() {
assertTrue(NativeStructureVegetationClearer.shouldProcessStart(
desertStart(0, 0, GROUND_Y + 15)));
assertFalse(NativeStructureVegetationClearer.shouldProcessStart(
StructureStart.INVALID_START));
}
@Test
@@ -103,4 +187,105 @@ public class NativeStructurePostProcessorVegetationTest {
assertEquals(-2, NativeStructureVerticalPlacer.resolveBuriedOffset(
bounds, 0, 58, 320, (x, z) -> x == 1 ? 76 : 100));
}
private static void clearVegetation(ProtoChunk chunk, StructureStart start) {
NativeStructureVegetationClearer.clearIntersectingVegetation(
world(chunk), chunk, AREA, List.of(start));
}
private static StructureStart desertStart(int originX, int originZ, int topY) {
Structure structure = new DesertPyramidStructure(
new Structure.StructureSettings(
HolderSet.empty(), Map.of(),
GenerationStep.Decoration.SURFACE_STRUCTURES, TerrainAdjustment.NONE));
DesertPyramidPiece piece = new DesertPyramidPiece(RandomSource.create(7L), originX, originZ);
piece.move(0, topY - piece.getBoundingBox().maxY(), 0);
return new StructureStart(
structure, new ChunkPos(0, 0), 0, new PiecesContainer(List.of(piece)));
}
private static ProtoChunk chunk() {
return new ProtoChunk(
new ChunkPos(0, 0), UpgradeData.EMPTY,
LevelHeightAccessor.create(MIN_Y, MAX_Y - MIN_Y + 1), containerFactory(), null);
}
private static void fillLayer(ProtoChunk chunk, int y, BlockState state) {
for (int z = 0; z < 16; z++) {
for (int x = 0; x < 16; x++) {
write(chunk, x, y, z, state);
}
}
}
private static void write(ProtoChunk chunk, int x, int y, int z, BlockState state) {
LevelChunkSection section = chunk.getSection(chunk.getSectionIndex(y));
section.setBlockState(x, y & 15, z, state, false);
}
private static BlockState log() {
return Blocks.OAK_LOG.defaultBlockState();
}
private static BlockState leaves() {
return Blocks.OAK_LEAVES.defaultBlockState();
}
private static BlockState air() {
return Blocks.AIR.defaultBlockState();
}
private static WorldGenLevel world(ChunkAccess chunk) {
InvocationHandler handler = (proxy, method, arguments) -> {
String methodName = method.getName();
if (methodName.equals("getBlockState")) {
return chunk.getBlockState((BlockPos) arguments[0]);
}
if (methodName.equals("setBlock")) {
return chunk.setBlockState(
((BlockPos) arguments[0]).immutable(),
(BlockState) arguments[1], (int) arguments[2]) != null;
}
if (methodName.equals("getSeed")) {
return TEST_SEED;
}
if (methodName.equals("getLevel")) {
return null;
}
if (methodName.equals("holderLookup")) {
return BuiltInRegistries.BLOCK;
}
if (methodName.equals("hashCode")) {
return System.identityHashCode(proxy);
}
if (methodName.equals("equals")) {
return proxy == arguments[0];
}
if (methodName.equals("toString")) {
return "vegetation-test-world";
}
throw new UnsupportedOperationException(method.toString());
};
return (WorldGenLevel) Proxy.newProxyInstance(
WorldGenLevel.class.getClassLoader(), new Class<?>[]{WorldGenLevel.class}, handler);
}
private static PalettedContainerFactory containerFactory() {
Strategy<BlockState> blockStates = Strategy.createForBlockStates(Block.BLOCK_STATE_REGISTRY);
BlockState air = Blocks.AIR.defaultBlockState();
IdMapper<Holder<Biome>> biomeIds = new IdMapper<>();
Holder<Biome> defaultBiome = Holder.direct((Biome) null);
biomeIds.add(defaultBiome);
Strategy<Holder<Biome>> biomes = Strategy.createForBiomes(biomeIds);
Codec<Holder<Biome>> biomeCodec = Codec.STRING.xmap(
name -> defaultBiome, holder -> "iris-test-biome");
return new PalettedContainerFactory(
blockStates,
air,
PalettedContainer.codecRW(BlockState.CODEC, blockStates, air),
biomes,
defaultBiome,
PalettedContainer.codecRO(biomeCodec, biomes, defaultBiome),
PalettedContainer.codecRW(biomeCodec, biomes, defaultBiome));
}
}
@@ -258,6 +258,6 @@ public class IrisStructureLocateCommandContractTest {
private static IrisNativeStructureDecision decision(NativeStructureGenerationStatus status) {
return new IrisNativeStructureDecision(
status, 0, null, false, false, null, null);
status, 0, null, false, null, null);
}
}