mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 22:40:13 +00:00
Improved riverbed generation
- Pond levels follow intersecting lower channels - Pond sampling covers requested flood margins - Water steps align with sea level - River crests grade into surrounding beds - Riverbeds include varied cross sections and finer noise - Submerged soils use stable block types - Hydrology probes inspect wet bed geometry
This commit is contained in:
1 parent
494cdb1a5c
commit
75837d757b
25 files changed
+1566
-144
No files matched your search
@@ -0,0 +1,92 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class HydrologyBlockMaps {
|
||||
private HydrologyBlockMaps() {
|
||||
}
|
||||
|
||||
static List<String> write(HydrologyBlockProbe.Configuration configuration,
|
||||
Map<Long, HydrologyBlockProbe.PlannedColumn> columns, HydrologyVoxels.Volume volume,
|
||||
int worldHeight) throws IOException {
|
||||
int minimumX = configuration.minimumChunkX() << 4;
|
||||
int minimumZ = configuration.minimumChunkZ() << 4;
|
||||
int width = (configuration.maximumChunkX() - configuration.minimumChunkX() + 1) << 4;
|
||||
int depth = (configuration.maximumChunkZ() - configuration.minimumChunkZ() + 1) << 4;
|
||||
BufferedImage raw = new BufferedImage(width, depth, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage effective = new BufferedImage(width, depth, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage loss = new BufferedImage(width, depth, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage fill = new BufferedImage(width, depth, BufferedImage.TYPE_INT_RGB);
|
||||
File csv = new File(configuration.output(), "columns.csv");
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(csv.toPath(), StandardCharsets.UTF_8)) {
|
||||
writer.write("x,z,role,natural_height,raster_terrain,effective_terrain,raster_head,effective_head,water_lost,terrain_delta,wet_bed_raise,generated_fill_solids,lowest_neighbor_head,known_neighbors\n");
|
||||
for (int z = 0; z < depth; z++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int blockX = minimumX + x;
|
||||
int blockZ = minimumZ + z;
|
||||
HydrologyBlockProbe.PlannedColumn column = columns.get(HydrologyVoxels.columnKey(blockX, blockZ));
|
||||
boolean lost = column.rawHead() != HydrologyVoxels.NO_WATER && column.head() == HydrologyVoxels.NO_WATER;
|
||||
int raised = column.wetBedRaise();
|
||||
HydrologyBlockProbe.BedNeighbours neighbours = HydrologyBlockProbe.bedNeighbours(columns, blockX, blockZ);
|
||||
raw.setRGB(x, z, relief(columns, column, blockX, blockZ, false, worldHeight));
|
||||
effective.setRGB(x, z, relief(columns, column, blockX, blockZ, true, worldHeight));
|
||||
loss.setRGB(x, z, lost ? 0xD33B31 : column.head() != HydrologyVoxels.NO_WATER ? 0x247AC4 : 0xC4C4C4);
|
||||
fill.setRGB(x, z, raised > 0 ? (0xFF0000 | (Math.max(0, 224 - 28 * raised) << 8) | 0x20)
|
||||
: column.head() != HydrologyVoxels.NO_WATER ? 0x247AC4 : 0xC4C4C4);
|
||||
writer.write(blockX + "," + blockZ + "," + column.role() + "," + column.natural() + "," + column.terrain() + ","
|
||||
+ column.effectiveTerrain() + "," + waterHead(column.rawHead()) + "," + waterHead(column.head()) + "," + lost + ","
|
||||
+ (column.effectiveTerrain() - column.terrain()) + "," + raised + ","
|
||||
+ HydrologyBlockProbe.raisedBedSolids(column, volume, blockX, blockZ) + ","
|
||||
+ waterHead(neighbours.lowestHead()) + "," + neighbours.known() + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> artifacts = new ArrayList<>();
|
||||
artifacts.add(csv.getAbsolutePath());
|
||||
artifacts.add(writeImage(configuration.output(), "raster-bed.png", raw));
|
||||
artifacts.add(writeImage(configuration.output(), "effective-bed.png", effective));
|
||||
artifacts.add(writeImage(configuration.output(), "water-loss.png", loss));
|
||||
artifacts.add(writeImage(configuration.output(), "bed-fill.png", fill));
|
||||
return List.copyOf(artifacts);
|
||||
}
|
||||
|
||||
private static String waterHead(int head) {
|
||||
return head == HydrologyVoxels.NO_WATER ? "" : Integer.toString(head);
|
||||
}
|
||||
|
||||
private static String writeImage(File output, String name, BufferedImage image) throws IOException {
|
||||
File file = new File(output, name);
|
||||
ImageIO.write(image, "png", file);
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
private static int relief(Map<Long, HydrologyBlockProbe.PlannedColumn> columns, HydrologyBlockProbe.PlannedColumn column,
|
||||
int x, int z, boolean effective, int worldHeight) {
|
||||
int height = effective ? column.effectiveTerrain() : column.terrain();
|
||||
int head = effective ? column.head() : column.rawHead();
|
||||
double dx = height(columns, x + 1, z, effective, height) - height(columns, x - 1, z, effective, height);
|
||||
double dz = height(columns, x, z + 1, effective, height) - height(columns, x, z - 1, effective, height);
|
||||
double light = Math.clamp((dx + dz + 4D) / Math.sqrt(6D * (dx * dx + dz * dz + 4D)), 0D, 1D);
|
||||
double shade = (0.5D + 0.5D * light) * (Math.floorMod(height, 4) == 0 ? 0.82D : 1D);
|
||||
double altitude = Math.clamp(height / (double) Math.max(1, worldHeight - 1), 0D, 1D);
|
||||
boolean wet = head != HydrologyVoxels.NO_WATER;
|
||||
int red = (int) Math.round(shade * (wet ? 50D + 60D * altitude : 160D + 50D * altitude));
|
||||
int green = (int) Math.round(shade * (wet ? 130D + 65D * altitude : 145D + 60D * altitude));
|
||||
int blue = (int) Math.round(shade * (wet ? 190D + 50D * altitude : 110D + 80D * altitude));
|
||||
return (red << 16) | (green << 8) | blue;
|
||||
}
|
||||
|
||||
private static int height(Map<Long, HydrologyBlockProbe.PlannedColumn> columns, int x, int z, boolean effective, int fallback) {
|
||||
HydrologyBlockProbe.PlannedColumn column = columns.get(HydrologyVoxels.columnKey(x, z));
|
||||
return column == null ? fallback : effective ? column.effectiveTerrain() : column.terrain();
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,14 @@ public final class HydrologyBlockProbe {
|
||||
|
||||
static final class Evidence {
|
||||
long plannedWetColumns;
|
||||
long effectiveWetColumns;
|
||||
long lostWetColumns;
|
||||
long raisedWetBedColumns;
|
||||
long raisedWetBedBlocks;
|
||||
int maximumWetBedRaise;
|
||||
long solidRaisedBedBlocks;
|
||||
long dropAdjacentRaisedWetBedColumns;
|
||||
long censoredRaisedWetBedColumns;
|
||||
long channelColumns;
|
||||
long pondColumns;
|
||||
long bankColumns;
|
||||
@@ -88,7 +96,16 @@ public final class HydrologyBlockProbe {
|
||||
List<String> artifacts) {
|
||||
}
|
||||
|
||||
record PlannedColumn(WaterRole role, int terrain, int natural, int head) {
|
||||
record PlannedColumn(WaterRole role, int terrain, int natural, int rawHead, int effectiveTerrain, int head) {
|
||||
int wetBedRaise() {
|
||||
return rawHead == HydrologyVoxels.NO_WATER || head == HydrologyVoxels.NO_WATER ? 0 : Math.max(0, effectiveTerrain - terrain);
|
||||
}
|
||||
}
|
||||
|
||||
record BedNeighbours(int lowestHead, int known) {
|
||||
boolean dropBelow(int head) {
|
||||
return lowestHead != HydrologyVoxels.NO_WATER && lowestHead <= head - 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -96,8 +113,10 @@ public final class HydrologyBlockProbe {
|
||||
try {
|
||||
Result result = run(Configuration.parse(args));
|
||||
Evidence evidence = result.evidence();
|
||||
System.out.println("IRIS_HYDROLOGY_BLOCK_RESULT version=2 status=" + result.status() + " seed=" + result.configuration().seed()
|
||||
System.out.println("IRIS_HYDROLOGY_BLOCK_RESULT version=4 status=" + result.status() + " seed=" + result.configuration().seed()
|
||||
+ " plannedWetColumns=" + evidence.plannedWetColumns + " missingSurfaceColumns=" + evidence.scan.missingSurfaces
|
||||
+ " effectiveWetColumns=" + evidence.effectiveWetColumns + " lostWetColumns=" + evidence.lostWetColumns
|
||||
+ " raisedWetBedColumns=" + evidence.raisedWetBedColumns + " raisedWetBedBlocks=" + evidence.raisedWetBedBlocks
|
||||
+ " containmentLeaks=" + evidence.scan.containmentLeaks + " tickConversionCells=" + evidence.scan.tickConversionCells
|
||||
+ " cane=" + evidence.validCaneRoots + " failures=" + result.failures().size()
|
||||
+ " output=" + result.configuration().output().getAbsolutePath());
|
||||
@@ -135,7 +154,8 @@ public final class HydrologyBlockProbe {
|
||||
failures.add("The engine reported " + reports.size() + " generation errors.");
|
||||
}
|
||||
Files.createDirectories(configuration.output().toPath());
|
||||
List<String> artifacts = writeImages(configuration, volume, engine);
|
||||
List<String> artifacts = new ArrayList<>(writeImages(configuration, volume, engine));
|
||||
artifacts.addAll(HydrologyBlockMaps.write(configuration, planned, volume, engine.getHeight()));
|
||||
Result result = new Result(failures.isEmpty() ? "PASS" : "FAIL", configuration, seaLevel, evidence, List.copyOf(failures), artifacts);
|
||||
Files.writeString(new File(configuration.output(), "hydrology-blocks.json").toPath(), json(result), StandardCharsets.UTF_8);
|
||||
return result;
|
||||
@@ -155,7 +175,9 @@ public final class HydrologyBlockProbe {
|
||||
int head = heads[(lz << 4) | lx];
|
||||
int natural = complex.naturalTrueHeight(x, z);
|
||||
int terrain = own.empty() ? natural : own.terrainHeight(lx, lz);
|
||||
planned.put(HydrologyVoxels.columnKey(x, z), new PlannedColumn(own.role(lx, lz), terrain, natural, head));
|
||||
int effectiveTerrain = (int) Math.round(complex.getRawHeightStream().getDouble(x, z));
|
||||
planned.put(HydrologyVoxels.columnKey(x, z), new PlannedColumn(own.role(lx, lz), terrain, natural,
|
||||
own.waterHead(lx, lz), effectiveTerrain, head));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,8 +225,15 @@ public final class HydrologyBlockProbe {
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
if (column.head() != HydrologyVoxels.NO_WATER) {
|
||||
evidence.plannedWetColumns++;
|
||||
inspectWater(column, x, z, evidence);
|
||||
if (column.wetBedRaise() > 0) {
|
||||
evidence.solidRaisedBedBlocks += raisedBedSolids(column, volume, x, z);
|
||||
BedNeighbours neighbours = bedNeighbours(planned, x, z);
|
||||
if (neighbours.dropBelow(column.head())) {
|
||||
evidence.dropAdjacentRaisedWetBedColumns++;
|
||||
} else if (neighbours.known() < CARDINALS.length) {
|
||||
evidence.censoredRaisedWetBedColumns++;
|
||||
}
|
||||
}
|
||||
if (HydrologyBlockMetrics.scanned(columns, x, z)) {
|
||||
evidence.scan.column(volume, x, z, HydrologyBlockMetrics.scanFloor(columns, seaLevel, x, z), height - 1);
|
||||
@@ -214,6 +243,58 @@ public final class HydrologyBlockProbe {
|
||||
return evidence;
|
||||
}
|
||||
|
||||
static void inspectWater(PlannedColumn column, int x, int z, Evidence evidence) {
|
||||
if (column.head() != HydrologyVoxels.NO_WATER) {
|
||||
evidence.effectiveWetColumns++;
|
||||
}
|
||||
if (column.rawHead() == HydrologyVoxels.NO_WATER) {
|
||||
return;
|
||||
}
|
||||
evidence.plannedWetColumns++;
|
||||
if (column.head() == HydrologyVoxels.NO_WATER) {
|
||||
evidence.lostWetColumns++;
|
||||
evidence.example("lost planned water at " + x + "," + column.rawHead() + "," + z + " role=" + column.role()
|
||||
+ " bed=" + column.terrain() + " effectiveGround=" + column.effectiveTerrain());
|
||||
} else if (column.wetBedRaise() > 0) {
|
||||
int raised = column.wetBedRaise();
|
||||
evidence.raisedWetBedColumns++;
|
||||
evidence.raisedWetBedBlocks += raised;
|
||||
evidence.maximumWetBedRaise = Math.max(evidence.maximumWetBedRaise, raised);
|
||||
evidence.example("raised wet bed at " + x + "," + z + " role=" + column.role() + " rasterGround=" + column.terrain()
|
||||
+ " effectiveGround=" + column.effectiveTerrain() + " fill=" + raised + " remainingDepth="
|
||||
+ (column.head() - column.effectiveTerrain()));
|
||||
}
|
||||
}
|
||||
|
||||
static int raisedBedSolids(PlannedColumn column, HydrologyVoxels.Volume volume, int x, int z) {
|
||||
if (column.wetBedRaise() == 0) {
|
||||
return 0;
|
||||
}
|
||||
int solids = 0;
|
||||
for (int y = column.terrain() + 1; y <= column.effectiveTerrain(); y++) {
|
||||
if (volume.at(x, y, z) == HydrologyVoxels.SOLID) {
|
||||
solids++;
|
||||
}
|
||||
}
|
||||
return solids;
|
||||
}
|
||||
|
||||
static BedNeighbours bedNeighbours(Map<Long, PlannedColumn> columns, int x, int z) {
|
||||
int lowest = Integer.MAX_VALUE;
|
||||
int known = 0;
|
||||
for (int[] side : CARDINALS) {
|
||||
PlannedColumn neighbour = columns.get(HydrologyVoxels.columnKey(x + side[0], z + side[1]));
|
||||
if (neighbour == null) {
|
||||
continue;
|
||||
}
|
||||
known++;
|
||||
if (neighbour.head() != HydrologyVoxels.NO_WATER) {
|
||||
lowest = Math.min(lowest, neighbour.head());
|
||||
}
|
||||
}
|
||||
return new BedNeighbours(lowest == Integer.MAX_VALUE ? HydrologyVoxels.NO_WATER : lowest, known);
|
||||
}
|
||||
|
||||
private static void inspectCane(HydrologyVoxels.Volume volume, int x, int z, int height, Evidence evidence) {
|
||||
for (int y = 1; y < height; y++) {
|
||||
if (volume.at(x, y, z) != HydrologyVoxels.CANE || volume.at(x, y - 1, z) == HydrologyVoxels.CANE) {
|
||||
@@ -249,6 +330,13 @@ public final class HydrologyBlockProbe {
|
||||
if (evidence.invalidCaneSubstrates > 0 || evidence.dryCaneRoots > 0) {
|
||||
failures.add("Generated sugar cane has invalid substrate or no adjacent water.");
|
||||
}
|
||||
if (evidence.lostWetColumns > 0) {
|
||||
failures.add("Water settling removes planned water from " + evidence.lostWetColumns + " columns in fresh generation.");
|
||||
}
|
||||
if (evidence.raisedWetBedColumns > 0) {
|
||||
failures.add("Water settling fills " + evidence.raisedWetBedBlocks + " blocks above the raster bed in "
|
||||
+ evidence.raisedWetBedColumns + " wet columns in fresh generation.");
|
||||
}
|
||||
if (evidence.scan.missingSurfaces > 0) {
|
||||
failures.add("Generated blocks miss the planned water surface in " + evidence.scan.missingSurfaces + " columns.");
|
||||
}
|
||||
@@ -288,6 +376,14 @@ public final class HydrologyBlockProbe {
|
||||
Evidence evidence = result.evidence();
|
||||
Map<String, Object> counts = new LinkedHashMap<>();
|
||||
counts.put("plannedWetColumns", evidence.plannedWetColumns);
|
||||
counts.put("effectiveWetColumns", evidence.effectiveWetColumns);
|
||||
counts.put("lostWetColumns", evidence.lostWetColumns);
|
||||
counts.put("raisedWetBedColumns", evidence.raisedWetBedColumns);
|
||||
counts.put("raisedWetBedBlocks", evidence.raisedWetBedBlocks);
|
||||
counts.put("maximumWetBedRaise", evidence.maximumWetBedRaise);
|
||||
counts.put("solidRaisedBedBlocks", evidence.solidRaisedBedBlocks);
|
||||
counts.put("dropAdjacentRaisedWetBedColumns", evidence.dropAdjacentRaisedWetBedColumns);
|
||||
counts.put("censoredRaisedWetBedColumns", evidence.censoredRaisedWetBedColumns);
|
||||
counts.put("missingSurfaceColumns", evidence.scan.missingSurfaces);
|
||||
counts.put("channelColumns", evidence.channelColumns);
|
||||
counts.put("pondColumns", evidence.pondColumns);
|
||||
@@ -313,6 +409,9 @@ public final class HydrologyBlockProbe {
|
||||
report.put("artifacts", result.artifacts());
|
||||
report.put("coordinateConvention", "Block X/Z are world coordinates; Y is relative to the world minimum height.");
|
||||
report.put("imageLegend", "open=light blue,water=blue,lava=orange,cane=lime,solid=gray");
|
||||
report.put("bedMapLegend", "raster-bed and effective-bed show elevation, slope shading and four-block contours on the same world-height scale; water-loss: red=planned wet made dry, blue=retained water, gray=dry");
|
||||
report.put("bedFillLegend", "bed-fill: yellow to red=one to eight or more blocks of wet bed fill, blue=wet bed retained, gray=dry; dry bank and dam fill is excluded");
|
||||
report.put("columnData", "columns.csv is ordered by Z then X; blank water heads mean dry; heights are relative to the world minimum height");
|
||||
return new GsonBuilder().setPrettyPrinting().create().toJson(report) + "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,28 @@
|
||||
package art.arcane.iris.probe;
|
||||
|
||||
import art.arcane.iris.generation.hydrology.raster.WaterRole;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.nio.file.Files;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HydrologyBlockProbeTest {
|
||||
@Rule
|
||||
public TemporaryFolder folder = new TemporaryFolder();
|
||||
|
||||
@Test
|
||||
public void failuresNameMissingSurfaceLeaksTicksAndCoverage() {
|
||||
HydrologyBlockProbe.Evidence evidence = new HydrologyBlockProbe.Evidence();
|
||||
@@ -21,4 +36,110 @@ public class HydrologyBlockProbeTest {
|
||||
"Fluid ticks would move generated water in 4 cells.",
|
||||
"Required generated coverage is absent: pond"), HydrologyBlockProbe.failures(evidence, Set.of("channel", "pond")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dryRidgesFailEvenWhenTheRemainingWaterIsSealed() {
|
||||
HydrologyBlockProbe.Evidence evidence = new HydrologyBlockProbe.Evidence();
|
||||
HydrologyBlockProbe.inspectWater(new HydrologyBlockProbe.PlannedColumn(WaterRole.CHANNEL, 77, 82, 79, 80,
|
||||
HydrologyVoxels.NO_WATER), -103, -1391, evidence);
|
||||
HydrologyBlockProbe.inspectWater(new HydrologyBlockProbe.PlannedColumn(WaterRole.WATERFALL, 70, 82, 79, 70, 79),
|
||||
-102, -1391, evidence);
|
||||
HydrologyBlockProbe.inspectWater(new HydrologyBlockProbe.PlannedColumn(WaterRole.BANK, 82, 82, HydrologyVoxels.NO_WATER,
|
||||
82, HydrologyVoxels.NO_WATER), -104, -1391, evidence);
|
||||
|
||||
assertEquals(2L, evidence.plannedWetColumns);
|
||||
assertEquals(1L, evidence.effectiveWetColumns);
|
||||
assertEquals(1L, evidence.lostWetColumns);
|
||||
assertEquals(List.of("Water settling removes planned water from 1 columns in fresh generation."),
|
||||
HydrologyBlockProbe.failures(evidence, Set.of()));
|
||||
assertEquals(List.of("lost planned water at -103,79,-1391 role=CHANNEL bed=77 effectiveGround=80"), evidence.examples);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submergedChannelAndPondFillFailsWithoutAnyLostSurfaceWater() {
|
||||
HydrologyBlockProbe.PlannedColumn channel = new HydrologyBlockProbe.PlannedColumn(WaterRole.CHANNEL, 318, 330, 326, 324, 326);
|
||||
HydrologyBlockProbe.PlannedColumn pond = new HydrologyBlockProbe.PlannedColumn(WaterRole.POND, 316, 330, 326, 324, 326);
|
||||
HydrologyBlockProbe.Evidence evidence = new HydrologyBlockProbe.Evidence();
|
||||
HydrologyBlockProbe.inspectWater(channel, -372, -441, evidence);
|
||||
HydrologyBlockProbe.inspectWater(pond, -371, -441, evidence);
|
||||
HydrologyBlockProbe.inspectWater(new HydrologyBlockProbe.PlannedColumn(WaterRole.DAM, 318, 318, HydrologyVoxels.NO_WATER,
|
||||
330, HydrologyVoxels.NO_WATER), -373, -441, evidence);
|
||||
|
||||
assertEquals(0L, evidence.lostWetColumns);
|
||||
assertEquals(2L, evidence.effectiveWetColumns);
|
||||
assertEquals(2L, evidence.raisedWetBedColumns);
|
||||
assertEquals(14L, evidence.raisedWetBedBlocks);
|
||||
assertEquals(8, evidence.maximumWetBedRaise);
|
||||
assertEquals(List.of("Water settling fills 14 blocks above the raster bed in 2 wet columns in fresh generation."),
|
||||
HydrologyBlockProbe.failures(evidence, Set.of()));
|
||||
assertEquals("raised wet bed at -372,-441 role=CHANNEL rasterGround=318 effectiveGround=324 fill=6 remainingDepth=2",
|
||||
evidence.examples.getFirst());
|
||||
|
||||
HydrologyVoxels.ChunkBlocks chunk = HydrologyVoxels.ChunkBlocks.empty(-24, -28, 384);
|
||||
for (int y = 319; y <= 324; y++) {
|
||||
chunk.set(12, y, 7, HydrologyVoxels.SOLID);
|
||||
}
|
||||
chunk.set(12, 325, 7, HydrologyVoxels.WATER);
|
||||
HydrologyVoxels.Volume volume = new HydrologyVoxels.Volume();
|
||||
volume.put(chunk);
|
||||
assertEquals(6, HydrologyBlockProbe.raisedBedSolids(channel, volume, -372, -441));
|
||||
assertEquals(0, HydrologyBlockProbe.raisedBedSolids(pond, volume, -371, -441));
|
||||
|
||||
HydrologyBlockProbe.BedNeighbours neighbours = HydrologyBlockProbe.bedNeighbours(Map.of(
|
||||
HydrologyVoxels.columnKey(-373, -441), new HydrologyBlockProbe.PlannedColumn(WaterRole.CHANNEL, 316, 330, 324, 316, 324)),
|
||||
-372, -441);
|
||||
assertTrue(neighbours.dropBelow(channel.head()));
|
||||
assertEquals(1, neighbours.known());
|
||||
assertEquals(324, neighbours.lowestHead());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void columnDataAndBedImagesExposeHeightChangesBeneathOneWaterSurface() throws Exception {
|
||||
File output = folder.newFolder("columns");
|
||||
HydrologyBlockProbe.Configuration configuration = new HydrologyBlockProbe.Configuration(new File("/synthetic-pack"), "overworld",
|
||||
1337L, -1, -1, -1, -1, Set.of(), output, false);
|
||||
Map<Long, HydrologyBlockProbe.PlannedColumn> columns = new HashMap<>();
|
||||
for (int z = 0; z < 16; z++) {
|
||||
for (int x = 0; x < 16; x++) {
|
||||
boolean lost = x == 4 && z == 5;
|
||||
boolean dry = x == 0 && z == 0;
|
||||
boolean raised = x == 6 && z == 5;
|
||||
columns.put(HydrologyVoxels.columnKey(x - 16, z - 16), new HydrologyBlockProbe.PlannedColumn(
|
||||
dry ? WaterRole.BANK : WaterRole.CHANNEL, 60 + x, 120, dry ? HydrologyVoxels.NO_WATER : 100,
|
||||
lost ? 101 : raised ? 72 : 60 + x, lost || dry ? HydrologyVoxels.NO_WATER : 100));
|
||||
}
|
||||
}
|
||||
HydrologyVoxels.ChunkBlocks chunk = HydrologyVoxels.ChunkBlocks.empty(-1, -1, 128);
|
||||
for (int y = 67; y <= 72; y++) {
|
||||
chunk.set(6, y, 5, HydrologyVoxels.SOLID);
|
||||
}
|
||||
HydrologyVoxels.Volume volume = new HydrologyVoxels.Volume();
|
||||
volume.put(chunk);
|
||||
|
||||
List<String> artifacts = HydrologyBlockMaps.write(configuration, columns, volume, 320);
|
||||
|
||||
assertEquals(5, artifacts.size());
|
||||
List<String> csv = Files.readAllLines(new File(output, "columns.csv").toPath());
|
||||
assertEquals(257, csv.size());
|
||||
assertEquals("x,z,role,natural_height,raster_terrain,effective_terrain,raster_head,effective_head,water_lost,terrain_delta,wet_bed_raise,generated_fill_solids,lowest_neighbor_head,known_neighbors", csv.getFirst());
|
||||
assertEquals("-16,-16,BANK,120,60,60,,,false,0,0,0,100,2", csv.get(1));
|
||||
assertTrue(csv.contains("-12,-11,CHANNEL,120,64,101,100,,true,37,0,0,100,4"));
|
||||
assertTrue(csv.contains("-10,-11,CHANNEL,120,66,72,100,100,false,6,6,6,100,4"));
|
||||
assertFalse(String.join("\n", csv).contains(Integer.toString(HydrologyVoxels.NO_WATER)));
|
||||
BufferedImage raw = ImageIO.read(new File(output, "raster-bed.png"));
|
||||
BufferedImage effective = ImageIO.read(new File(output, "effective-bed.png"));
|
||||
BufferedImage loss = ImageIO.read(new File(output, "water-loss.png"));
|
||||
BufferedImage fill = ImageIO.read(new File(output, "bed-fill.png"));
|
||||
assertEquals(16, raw.getWidth());
|
||||
assertEquals(16, raw.getHeight());
|
||||
assertNotEquals(raw.getRGB(1, 10), raw.getRGB(13, 10));
|
||||
assertNotEquals(raw.getRGB(4, 5), effective.getRGB(4, 5));
|
||||
assertEquals(raw.getRGB(10, 10), effective.getRGB(10, 10));
|
||||
assertEquals(0xD33B31, loss.getRGB(4, 5) & 0xFFFFFF);
|
||||
assertEquals(0x247AC4, loss.getRGB(10, 10) & 0xFFFFFF);
|
||||
assertEquals(0xC4C4C4, loss.getRGB(0, 0) & 0xFFFFFF);
|
||||
assertEquals(0xFF3820, fill.getRGB(6, 5) & 0xFFFFFF);
|
||||
assertEquals(0x247AC4, fill.getRGB(10, 10) & 0xFFFFFF);
|
||||
assertEquals(0xC4C4C4, fill.getRGB(0, 0) & 0xFFFFFF);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user