Weltkarte: Anpassungen vor schematischem Umbau

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 16:22:13 +02:00
parent 0bf7725051
commit e8896c7a4d
3 changed files with 123 additions and 46 deletions

View File

@@ -163,7 +163,7 @@ public final class WorldMapRenderer {
int[] slotCols = model.slotColorsRGB();
int[] colorMap = model.terrainColorMap() != null
? model.terrainColorMap()
: buildTerrainColorMap(m, slotCols, model.mapTilePixels(), model.voxelSurface(), model.waters());
: buildTerrainColorMap(m, slotCols, model.mapTilePixels(), model.voxelSurface(), model.waters(), model.waterfalls());
int[] rawRGB = new int[targetSize * targetSize];
int waterR = (WATER_COLOR >> 16) & 0xFF;
int waterG = (WATER_COLOR >> 8) & 0xFF;
@@ -962,6 +962,41 @@ public final class WorldMapRenderer {
return map;
}
/** Erzeugt eine Maske: true für jeden Pixel, der innerhalb eines Wasserfall-Quads liegt.
* Nutzt Graphics2D-Rasterisierung inkl. Stroke, damit auch schmale Quads erfasst werden. */
private static boolean[] buildWaterfallMask(List<PlacedWaterfall> waterfalls, int size) {
boolean[] mask = new boolean[size * size];
if (waterfalls == null || waterfalls.isEmpty()) return mask;
// BYTE_BINARY: 1 bit/px → 512 KB bei 2048px, 8 MB bei 8192px
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_BYTE_BINARY);
Graphics2D g = img.createGraphics();
g.setColor(java.awt.Color.WHITE);
float strokeW = Math.max(4f, size / 512f); // mind. 4px Strichbreite für dünne Quads
g.setStroke(new BasicStroke(strokeW, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
for (PlacedWaterfall wf : waterfalls) {
int[] xs = {
worldToPixel(wf.ax(), size), worldToPixel(wf.bx(), size),
worldToPixel(wf.cx(), size), worldToPixel(wf.dx(), size)
};
int[] zs = {
worldToPixel(wf.az(), size), worldToPixel(wf.bz(), size),
worldToPixel(wf.cz(), size), worldToPixel(wf.dz(), size)
};
g.fillPolygon(xs, zs, 4); // Füllung
g.drawPolygon(xs, zs, 4); // Stroke-Backup für schmale Quads
}
g.dispose();
java.awt.image.Raster raster = img.getData();
int[] px = new int[1];
for (int py = 0; py < size; py++) {
for (int x = 0; x < size; x++) {
raster.getPixel(x, py, px);
if (px[0] != 0) mask[py * size + x] = true;
}
}
return mask;
}
/** Lädt die 6 Karten-Kacheln aus Textures/map/ im Asset-Verzeichnis. */
public static int[][] loadMapTiles(Path assetRoot) {
int[][] result = new int[TILE_FILE_NAMES.length][];
@@ -1220,23 +1255,25 @@ public final class WorldMapRenderer {
* verwendet — kein Splatmap-Blend zur Renderzeit mehr.
*/
public static int[] buildTerrainColorMap(MapData m, int[] slotColors, int[][] tilePixels,
float[] voxelSurface, List<PlacedWater> waters) {
float[] voxelSurface, List<PlacedWater> waters,
List<PlacedWaterfall> waterfalls) {
final int SIZE = COLOR_MAP_SIZE;
final int SS = MapData.SPLAT_SIZE;
float[] heights = buildTerrainSamples(m, SIZE, voxelSurface);
float[] waterH = buildWaterHeightMap(waters, SIZE);
int[] tileAvg = computeTileAvgColors(tilePixels);
int[] slotTypes = computeSlotTileTypes(m, slotColors);
float cellW = WORLD_SIZE / (SIZE - 1);
int[] terrainRGB = new int[SIZE * SIZE];
float[] cliffMask = new float[SIZE * SIZE];
float[] heights = buildTerrainSamples(m, SIZE, voxelSurface);
float[] waterH = buildWaterHeightMap(waters, SIZE);
boolean[] wfMask = buildWaterfallMask(waterfalls, SIZE);
int[] tileAvg = computeTileAvgColors(tilePixels);
int[] slotTypes = computeSlotTileTypes(m, slotColors);
float cellW = WORLD_SIZE / (SIZE - 1);
int[] terrainRGB = new int[SIZE * SIZE];
float[] cliffMask = new float[SIZE * SIZE];
for (int py = 0; py < SIZE; py++) {
float wz = -WORLD_HALF + py * cellW;
for (int px = 0; px < SIZE; px++) {
float wx = -WORLD_HALF + px * cellW;
int idx = py * SIZE + px;
float h = heights[idx];
if (h < 0f || (waterH[idx] > 0f && waterH[idx] > h)) {
if (h < 0f || (waterH[idx] > 0f && waterH[idx] > h) || wfMask[idx]) {
int[] wc = TILE_FALLBACK_RGB[TILE_WATER];
terrainRGB[idx] = (wc[0]<<16)|(wc[1]<<8)|wc[2];
} else {
@@ -1278,6 +1315,7 @@ public final class WorldMapRenderer {
*/
public static int buildAndSaveHiresChunks(MapData m, int[] slotColors, int[][] tilePixels,
float[] voxelSurface, List<PlacedWater> waters,
List<PlacedWaterfall> waterfalls,
java.nio.file.Path cacheDir, boolean forceRegen) {
final int SIZE = HIRES_MAP_SIZE;
final int CS = CHUNK_SIZE;
@@ -1297,11 +1335,12 @@ public final class WorldMapRenderer {
if (allExist) { log.debug("[HiresChunks] Alle {} Chunks im Cache – übersprungen.", COLS*COLS); return 0; }
}
float[] heights = buildTerrainSamples(m, SIZE, voxelSurface);
float[] waterH = buildWaterHeightMap(waters, SIZE);
int[] tileAvg = computeTileAvgColors(tilePixels);
int[] slotTypes = computeSlotTileTypes(m, slotColors);
float cellW = WORLD_SIZE / (SIZE - 1);
float[] heights = buildTerrainSamples(m, SIZE, voxelSurface);
float[] waterH = buildWaterHeightMap(waters, SIZE);
boolean[] wfMask = buildWaterfallMask(waterfalls, SIZE);
int[] tileAvg = computeTileAvgColors(tilePixels);
int[] slotTypes = computeSlotTileTypes(m, slotColors);
float cellW = WORLD_SIZE / (SIZE - 1);
int written = 0;
final int CLIFF_BLUR_R = 4; // Blur-Radius für weiche Klippen-Kanten
@@ -1329,7 +1368,7 @@ public final class WorldMapRenderer {
int eidx = ely * EXTW + elx;
int hidx = gyC * SIZE + gxC;
float h = heights[hidx];
if (h < 0f || (waterH[hidx] > 0f && waterH[hidx] > h)) {
if (h < 0f || (waterH[hidx] > 0f && waterH[hidx] > h) || wfMask[hidx]) {
isWaterExt[eidx] = true;
} else {
float hR = heights[gyC * SIZE + Math.min(SIZE-1, gxC+dPxHires)];