From abf56f81b67b2e76d8771aa16777c7767110d58e Mon Sep 17 00:00:00 2001 From: Mario Date: Thu, 3 Sep 2026 10:39:23 +0200 Subject: [PATCH] Weltkarte: tile-basiertes Rendering, Hires-Chunks, 5 Zoom-Stufen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Durchschnittliche Kachel-Farben (terrainColorMap, 2048px) als Basis für Low-Res-Ansicht - Bilineares Splatmap-Blending (blendTileAvgColors) statt Nearest-Neighbor - Chunk-basierte Hires-Karte (8192px, 256 Chunks à 512px) auf Disk gecacht - Zoom-Stufen 0–2: Overview-Bild; Stufen 3–4: Hires-Chunks per LRU-Cache - 5 geometrisch verteilte Zoom-Stufen (Fit-All bis 0,25m/Pixel) - Klippen-Erkennung per Steigungswinkel (1,5m Sampling-Distanz, harter 30°-Schwellwert) - Leichter Box-Blur (Radius 2) über Terrain-Layer - Wasserfarbe in Hires-Chunks vereinheitlicht (WATER_COLOR) - Kein Hillshading Co-Authored-By: Claude Sonnet 4.6 --- .../resources/Textures/hud/minimap_world.png | Bin 74630 -> 45603 bytes .../common/map/WorldMapRenderModel.java | 3 +- .../blight/common/map/WorldMapRenderer.java | 377 ++++++++++---- .../de/blight/editor/ui/WorldMapView.java | 478 +++++++----------- .../de/blight/game/state/MinimapState.java | 32 +- 5 files changed, 477 insertions(+), 413 deletions(-) diff --git a/blight-assets/src/main/resources/Textures/hud/minimap_world.png b/blight-assets/src/main/resources/Textures/hud/minimap_world.png index 0ba193283792ebe34d8d3e81b240e6cf3bb588ab..1ce75ad566d0814a8259905be2fb20fa70676f42 100644 GIT binary patch literal 45603 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�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 diff --git a/blight-common/src/main/java/de/blight/common/map/WorldMapRenderModel.java b/blight-common/src/main/java/de/blight/common/map/WorldMapRenderModel.java index 2e86df4..48aa29b 100644 --- a/blight-common/src/main/java/de/blight/common/map/WorldMapRenderModel.java +++ b/blight-common/src/main/java/de/blight/common/map/WorldMapRenderModel.java @@ -26,5 +26,6 @@ public record WorldMapRenderModel( float[] terrainSamples, // Höhenwerte bei SEA_MASK_SIZE (für Wellen-Prüfung) List coastPaths, // geglättete Marching-Squares Küstenpfade (Weltkoord.) List> treeClusters, - int[][] mapTilePixels // 6 Kacheln (TILE_WATER..TILE_ROCK), je 16×16 ARGB; null = Fallback + int[][] mapTilePixels, // 6 Kacheln (TILE_WATER..TILE_ROCK), je 16×16 ARGB; null = Fallback + int[] terrainColorMap // COLOR_MAP_SIZE×COLOR_MAP_SIZE packed RGB; null → render() berechnet intern ) {} diff --git a/blight-common/src/main/java/de/blight/common/map/WorldMapRenderer.java b/blight-common/src/main/java/de/blight/common/map/WorldMapRenderer.java index ac54bf1..7b28cc2 100644 --- a/blight-common/src/main/java/de/blight/common/map/WorldMapRenderer.java +++ b/blight-common/src/main/java/de/blight/common/map/WorldMapRenderer.java @@ -29,6 +29,18 @@ public final class WorldMapRenderer { public static final float WORLD_SIZE = 2048f; /** Auflösung des voxelSurface-Bake-Arrays (1 Zelle pro Welteinheit). */ public static final int VOXEL_SURFACE_RES = 2048; + /** + * Auflösung der vorberechneten Übersichts-Farbkarte (terrainColorMap, für Zoom 0-2). + * 2048 → 1 Farbwert pro Meter. + */ + public static final int COLOR_MAP_SIZE = 2048; + /** + * Auflösung der hochauflösenden Chunk-Karte (für Zoom 3-4). + * 8192 → 1 Farbwert pro 0,25m. Auf 4096 für 0,5m/px ändern. + */ + public static final int HIRES_MAP_SIZE = 8192; + /** Pixelgröße eines Chunks (quadratisch). HIRES_MAP_SIZE muss ganzzahlig teilbar sein. */ + public static final int CHUNK_SIZE = 512; public record RenderInput( MapData mapData, @@ -146,49 +158,37 @@ public final class WorldMapRenderer { } } - // 3. Tile-Rendering: tileIdx + Rohfarbe pro Pixel speichern - float cellW = WORLD_SIZE / (targetSize - 1); - int SS = MapData.SPLAT_SIZE; - int[] slotCols = model.slotColorsRGB(); - int[][] tiles = model.mapTilePixels(); - int[] rawRGB = new int[targetSize * targetSize]; + // 3. Terrain-Farben aus vorberechneter Farbkarte (oder intern berechnen als Fallback) + float cellW = WORLD_SIZE / (targetSize - 1); + int[] slotCols = model.slotColorsRGB(); + int[] colorMap = model.terrainColorMap() != null + ? model.terrainColorMap() + : buildTerrainColorMap(m, slotCols, model.mapTilePixels(), model.voxelSurface(), model.waters()); + int[] rawRGB = new int[targetSize * targetSize]; int waterR = (WATER_COLOR >> 16) & 0xFF; int waterG = (WATER_COLOR >> 8) & 0xFF; int waterB = WATER_COLOR & 0xFF; for (int py = 0; py < targetSize; py++) { - float wz = -WORLD_HALF + py * cellW; for (int px = 0; px < targetSize; px++) { - float wx = -WORLD_HALF + px * cellW; int idx = py * targetSize + px; - - int tileIdx; - int tr, tg, tb; if (isWater[idx]) { - tileIdx = TILE_WATER; - tr = waterR; tg = waterG; tb = waterB; + rawRGB[idx] = (waterR << 16) | (waterG << 8) | waterB; } else { - int dPx = Math.max(1, (int)(0.25f / cellW)); - float hR = heights[py * targetSize + Math.min(targetSize-1, px+dPx)]; - float hL = heights[py * targetSize + Math.max(0, px-dPx)]; - float hD = heights[Math.min(targetSize-1, py+dPx) * targetSize + px]; - float hU = heights[Math.max(0, py-dPx) * targetSize + px]; - float dist = dPx * cellW; - double slope = Math.toDegrees(Math.atan(Math.sqrt( - Math.pow((hR-hL) / (2*dist), 2) + Math.pow((hD-hU) / (2*dist), 2)))); - tileIdx = (slope > 30.0) ? TILE_CLIFF : getTerrainTile(m, SS, wx, wz, slotCols); - if (tiles != null && tileIdx < tiles.length && tiles[tileIdx] != null) { - int tu = Math.floorMod((int)Math.floor((wx + WORLD_HALF) / TILE_WU), 16); - int tv = Math.floorMod((int)Math.floor((wz + WORLD_HALF) / TILE_WU), 16); - int argb = tiles[tileIdx][tv * 16 + tu]; - tr = (argb >> 16) & 0xFF; tg = (argb >> 8) & 0xFF; tb = argb & 0xFF; - } else { - tr = TILE_FALLBACK_RGB[tileIdx][0]; - tg = TILE_FALLBACK_RGB[tileIdx][1]; - tb = TILE_FALLBACK_RGB[tileIdx][2]; - } + // Bilinear sample aus der Farbkarte (COLOR_MAP_SIZE → targetSize) + float fx = (float) px / (targetSize - 1) * (COLOR_MAP_SIZE - 1); + float fz = (float) py / (targetSize - 1) * (COLOR_MAP_SIZE - 1); + int cx0 = (int) fx, cx1 = Math.min(cx0 + 1, COLOR_MAP_SIZE - 1); + int cz0 = (int) fz, cz1 = Math.min(cz0 + 1, COLOR_MAP_SIZE - 1); + float tx = fx - cx0, tz = fz - cz0; + int c00 = colorMap[cz0 * COLOR_MAP_SIZE + cx0]; + int c10 = colorMap[cz0 * COLOR_MAP_SIZE + cx1]; + int c01 = colorMap[cz1 * COLOR_MAP_SIZE + cx0]; + int c11 = colorMap[cz1 * COLOR_MAP_SIZE + cx1]; + rawRGB[idx] = (clamp((int)(((c00>>16)&0xFF)*(1-tx)*(1-tz)+((c10>>16)&0xFF)*tx*(1-tz)+((c01>>16)&0xFF)*(1-tx)*tz+((c11>>16)&0xFF)*tx*tz)) << 16) + | (clamp((int)(((c00>>8) &0xFF)*(1-tx)*(1-tz)+((c10>>8) &0xFF)*tx*(1-tz)+((c01>>8) &0xFF)*(1-tx)*tz+((c11>>8) &0xFF)*tx*tz)) << 8) + | clamp((int)(( c00 &0xFF)*(1-tx)*(1-tz)+( c10 &0xFF)*tx*(1-tz)+( c01 &0xFF)*(1-tx)*tz+( c11 &0xFF)*tx*tz)); } - rawRGB[idx] = (tr << 16) | (tg << 8) | tb; } } @@ -223,31 +223,10 @@ public final class WorldMapRenderer { } } float wFrac = wCount * subInv; - // Land-Farbe: fuer Wasser-Pixel on-the-fly berechnen, sonst aus rawRGB - int landRGB; - if (isS) { - int dPx = Math.max(1, (int)(0.25f / cellW)); - float hR2 = heights[py * targetSize + Math.min(targetSize-1, px+dPx)]; - float hL2 = heights[py * targetSize + Math.max(0, px-dPx)]; - float hD2 = heights[Math.min(targetSize-1, py+dPx) * targetSize + px]; - float hU2 = heights[Math.max(0, py-dPx) * targetSize + px]; - float d2 = dPx * cellW; - double sl2 = Math.toDegrees(Math.atan(Math.sqrt( - Math.pow((hR2-hL2)/(2*d2), 2) + Math.pow((hD2-hU2)/(2*d2), 2)))); - int ti2 = (sl2 > 30.0) ? TILE_CLIFF : getTerrainTile(m, SS, wx, wz, slotCols); - int tr2, tg2, tb2; - if (tiles != null && ti2 < tiles.length && tiles[ti2] != null) { - int tu=Math.floorMod((int)Math.floor((wx+WORLD_HALF)/TILE_WU),16); - int tv=Math.floorMod((int)Math.floor((wz+WORLD_HALF)/TILE_WU),16); - int argb = tiles[ti2][tv * 16 + tu]; - tr2=(argb>>16)&0xFF; tg2=(argb>>8)&0xFF; tb2=argb&0xFF; - } else { - tr2=TILE_FALLBACK_RGB[ti2][0]; tg2=TILE_FALLBACK_RGB[ti2][1]; tb2=TILE_FALLBACK_RGB[ti2][2]; - } - landRGB = (tr2<<16)|(tg2<<8)|tb2; - } else { - landRGB = rawRGB[idx]; - } + // Land-Farbe aus Farbkarte (Wasser-Pixel bekommen ihren Nachbar-Landwert) + int mapX = Math.min((int)((float)px / (targetSize-1) * (COLOR_MAP_SIZE-1)), COLOR_MAP_SIZE-1); + int mapZ = Math.min((int)((float)py / (targetSize-1) * (COLOR_MAP_SIZE-1)), COLOR_MAP_SIZE-1); + int landRGB = colorMap[mapZ * COLOR_MAP_SIZE + mapX]; int lr=(landRGB>>16)&0xFF, lg=(landRGB>>8)&0xFF, lb=landRGB&0xFF; rawRGB[idx] = (clamp((int)(waterR*wFrac + lr*(1-wFrac))) << 16) | (clamp((int)(waterG*wFrac + lg*(1-wFrac))) << 8) @@ -267,9 +246,7 @@ public final class WorldMapRenderer { int x1 = Math.min(targetSize-1, bb2.x + bb2.width + 1); int y1 = Math.min(targetSize-1, bb2.y + bb2.height + 1); for (int py = y0; py <= y1; py++) { - float wz = -WORLD_HALF + py * cellW; for (int px = x0; px <= x1; px++) { - float wx = -WORLD_HALF + px * cellW; int idx = py * targetSize + px; boolean inPoly = wp.contains(px, py) && heights[idx] < wh2; boolean boundary2 = false; @@ -294,31 +271,9 @@ public final class WorldMapRenderer { } } float wFrac2 = wCount2 * subInv; - int landRGB2; - if (inPoly) { - // Wasser-Pixel: Land-Farbe on-the-fly - int dPx = Math.max(1, (int)(0.25f / cellW)); - float hR2 = heights[py * targetSize + Math.min(targetSize-1, px+dPx)]; - float hL2 = heights[py * targetSize + Math.max(0, px-dPx)]; - float hD2 = heights[Math.min(targetSize-1, py+dPx) * targetSize + px]; - float hU2 = heights[Math.max(0, py-dPx) * targetSize + px]; - float d2 = dPx * cellW; - double sl2 = Math.toDegrees(Math.atan(Math.sqrt( - Math.pow((hR2-hL2)/(2*d2),2) + Math.pow((hD2-hU2)/(2*d2),2)))); - int ti2 = (sl2 > 30.0) ? TILE_CLIFF : getTerrainTile(m, SS, wx, wz, slotCols); - int tr2, tg2, tb2; - if (tiles != null && ti2 < tiles.length && tiles[ti2] != null) { - int tu2=Math.floorMod((int)Math.floor((wx+WORLD_HALF)/TILE_WU),16); - int tv2=Math.floorMod((int)Math.floor((wz+WORLD_HALF)/TILE_WU),16); - int argb = tiles[ti2][tv2 * 16 + tu2]; - tr2=(argb>>16)&0xFF; tg2=(argb>>8)&0xFF; tb2=argb&0xFF; - } else { - tr2=TILE_FALLBACK_RGB[ti2][0]; tg2=TILE_FALLBACK_RGB[ti2][1]; tb2=TILE_FALLBACK_RGB[ti2][2]; - } - landRGB2 = (tr2<<16)|(tg2<<8)|tb2; - } else { - landRGB2 = rawRGB[idx]; - } + int mapX2 = Math.min((int)((float)px / (targetSize-1) * (COLOR_MAP_SIZE-1)), COLOR_MAP_SIZE-1); + int mapZ2 = Math.min((int)((float)py / (targetSize-1) * (COLOR_MAP_SIZE-1)), COLOR_MAP_SIZE-1); + int landRGB2 = colorMap[mapZ2 * COLOR_MAP_SIZE + mapX2]; int lr2=(landRGB2>>16)&0xFF, lg2=(landRGB2>>8)&0xFF, lb2=landRGB2&0xFF; rawRGB[idx] = (clamp((int)(waterR*wFrac2 + lr2*(1-wFrac2))) << 16) | (clamp((int)(waterG*wFrac2 + lg2*(1-wFrac2))) << 8) @@ -531,7 +486,7 @@ public final class WorldMapRenderer { return render(new WorldMapRenderModel( input.mapData(), input.areas(), input.zones(), input.locations(), input.waters(), input.models(), null, - input.slotColorsRGB(), null, null, null, null, null, null + input.slotColorsRGB(), null, null, null, null, null, null, null ), targetSize, opts); } @@ -1243,6 +1198,256 @@ public final class WorldMapRenderer { return m.modelPath().replace('\\', '/').toLowerCase().contains("/trees/"); } + /** Bilineare Tile-Textur-Abfrage mit UV-Gewichten (w00/w10/w01/w11 = bilinear weights). */ + private static int tileSampleBil(int[][] tiles, int tileIdx, + int u0, int u1, int v0, int v1, float w00, float w10, float w01, float w11) { + if (tiles == null || tileIdx < 0 || tileIdx >= tiles.length || tiles[tileIdx] == null) { + int[] fb = TILE_FALLBACK_RGB[Math.max(0, Math.min(TILE_FALLBACK_RGB.length-1, tileIdx))]; + return (fb[0]<<16)|(fb[1]<<8)|fb[2]; + } + int[] t = tiles[tileIdx]; + int c00=t[v0*16+u0], c10=t[v0*16+u1], c01=t[v1*16+u0], c11=t[v1*16+u1]; + int r=clamp((int)(((c00>>16)&0xFF)*w00+((c10>>16)&0xFF)*w10+((c01>>16)&0xFF)*w01+((c11>>16)&0xFF)*w11)); + int g=clamp((int)(((c00>>8)&0xFF)*w00+((c10>>8)&0xFF)*w10+((c01>>8)&0xFF)*w01+((c11>>8)&0xFF)*w11)); + int b=clamp((int)((c00&0xFF)*w00+(c10&0xFF)*w10+(c01&0xFF)*w01+(c11&0xFF)*w11)); + return (r<<16)|(g<<8)|b; + } + + /** + * Baut die Terrain-Farbkarte vor: für jeden Pixel den dominanten Splatmap-Slot + * ermitteln, Durchschnittsfarbe der Kachel lesen, Hillshading anwenden. + * Das Ergebnis wird im WorldMapRenderModel gespeichert und von render() direkt + * verwendet — kein Splatmap-Blend zur Renderzeit mehr. + */ + public static int[] buildTerrainColorMap(MapData m, int[] slotColors, int[][] tilePixels, + float[] voxelSurface, List waters) { + 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[] result = new int[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]; + int rgb; + if (h < 0f || (waterH[idx] > 0f && waterH[idx] > h)) { + int[] wc = TILE_FALLBACK_RGB[TILE_WATER]; + rgb = (wc[0]<<16)|(wc[1]<<8)|wc[2]; + } else { + int dPx = Math.max(1, (int)(1.5f / cellW)); + float hR = heights[py * SIZE + Math.min(SIZE-1, px+dPx)]; + float hL = heights[py * SIZE + Math.max(0, px-dPx)]; + float hD = heights[Math.min(SIZE-1, py+dPx) * SIZE + px]; + float hU = heights[Math.max(0, py-dPx) * SIZE + px]; + float dist = dPx * cellW; + double slope = Math.toDegrees(Math.atan(Math.sqrt( + Math.pow((hR-hL)/(2*dist),2) + Math.pow((hD-hU)/(2*dist),2)))); + if (slope > 30.0) { + rgb = tileAvg[Math.min(TILE_CLIFF, tileAvg.length-1)]; + } else { + rgb = blendTileAvgColors(m, SS, wx, wz, tileAvg, slotTypes); + } + } + result[idx] = rgb; + } + } + return boxBlur(result, SIZE, 2); + } + + /** + * Generiert die hochauflösende Terrain-Farbkarte (HIRES_MAP_SIZE×HIRES_MAP_SIZE) in + * CHUNK_SIZE×CHUNK_SIZE-Kacheln und speichert sie als PNG unter {@code cacheDir}. + * Bereits vorhandene Chunks werden übersprungen, wenn {@code forceRegen} false ist. + * + * @return Anzahl neu geschriebener Chunks (0 = alle aus Cache geladen) + */ + public static int buildAndSaveHiresChunks(MapData m, int[] slotColors, int[][] tilePixels, + float[] voxelSurface, List waters, + java.nio.file.Path cacheDir, boolean forceRegen) { + final int SIZE = HIRES_MAP_SIZE; + final int CS = CHUNK_SIZE; + final int COLS = SIZE / CS; // Chunks pro Zeile/Spalte + final int SS = MapData.SPLAT_SIZE; + + try { Files.createDirectories(cacheDir); } catch (Exception e) { log.error("Chunk-Dir nicht erstellbar", e); return 0; } + + // Prüfen ob alle Chunks vorhanden sind (dann Früh-Exit) + if (!forceRegen) { + boolean allExist = true; + for (int cy = 0; cy < COLS && allExist; cy++) { + for (int cx = 0; cx < COLS && allExist; cx++) { + if (!Files.exists(chunkPath(cacheDir, cx, cy))) allExist = false; + } + } + 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); + int written = 0; + + for (int cy = 0; cy < COLS; cy++) { + for (int cx = 0; cx < COLS; cx++) { + java.nio.file.Path p = chunkPath(cacheDir, cx, cy); + if (!forceRegen && Files.exists(p)) continue; + // Chunk-Pixel berechnen + int[] chunk = new int[CS * CS]; + for (int ly = 0; ly < CS; ly++) { + int gy = cy * CS + ly; // globaler Y-Index in der HIRES-Karte + float wz = -WORLD_HALF + gy * cellW; + for (int lx = 0; lx < CS; lx++) { + int gx = cx * CS + lx; + float wx = -WORLD_HALF + gx * cellW; + int idx = gy * SIZE + gx; + float h = heights[idx]; + int tr, tg, tb; + if (h < 0f || (waterH[idx] > 0f && waterH[idx] > h)) { + tr = (WATER_COLOR >> 16) & 0xFF; + tg = (WATER_COLOR >> 8) & 0xFF; + tb = WATER_COLOR & 0xFF; + } else { + int dPx = Math.max(1, (int)(1.5f / cellW)); + float hR = heights[gy * SIZE + Math.min(SIZE-1, gx+dPx)]; + float hL = heights[gy * SIZE + Math.max(0, gx-dPx)]; + float hD = heights[Math.min(SIZE-1, gy+dPx) * SIZE + gx]; + float hU = heights[Math.max(0, gy-dPx) * SIZE + gx]; + float dist = dPx * cellW; + double slope = Math.toDegrees(Math.atan(Math.sqrt( + Math.pow((hR-hL)/(2*dist),2) + Math.pow((hD-hU)/(2*dist),2)))); + int rgb = (slope > 30.0) + ? tileAvg[Math.min(TILE_CLIFF, tileAvg.length-1)] + : blendTileAvgColors(m, SS, wx, wz, tileAvg, slotTypes); + tr = (rgb>>16)&0xFF; tg = (rgb>>8)&0xFF; tb = rgb&0xFF; + } + chunk[ly * CS + lx] = (0xFF << 24) | (tr << 16) | (tg << 8) | tb; + } + } + // Als PNG speichern + BufferedImage img = new BufferedImage(CS, CS, BufferedImage.TYPE_INT_ARGB); + img.setRGB(0, 0, CS, CS, chunk, 0, CS); + try { javax.imageio.ImageIO.write(img, "PNG", p.toFile()); written++; } + catch (Exception e) { log.error("Chunk {}/{} konnte nicht gespeichert werden", cx, cy, e); } + } + } + log.debug("[HiresChunks] {} neue Chunks geschrieben nach {}", written, cacheDir); + return written; + } + + /** Lädt einen Chunk aus dem Cache. Gibt null zurück wenn nicht vorhanden. */ + public static int[] loadChunk(int chunkX, int chunkY, java.nio.file.Path cacheDir) { + java.nio.file.Path p = chunkPath(cacheDir, chunkX, chunkY); + if (!Files.exists(p)) return null; + try { + BufferedImage img = javax.imageio.ImageIO.read(p.toFile()); + if (img == null) return null; + return img.getRGB(0, 0, CHUNK_SIZE, CHUNK_SIZE, null, 0, CHUNK_SIZE); + } catch (Exception e) { + log.error("Chunk {}/{} konnte nicht geladen werden", chunkX, chunkY, e); + return null; + } + } + + private static java.nio.file.Path chunkPath(java.nio.file.Path dir, int cx, int cy) { + return dir.resolve(String.format("chunk_%02x_%02x.png", cx, cy)); + } + + /** Separabler Box-Blur (horizontal + vertikal), Radius in Pixeln. */ + private static int[] boxBlur(int[] src, int size, int radius) { + int[] tmp = new int[size * size]; + int[] dst = new int[size * size]; + int diam = radius * 2 + 1; + // Horizontaler Pass + for (int py = 0; py < size; py++) { + for (int px = 0; px < size; px++) { + long r = 0, g = 0, b = 0; + for (int dx = -radius; dx <= radius; dx++) { + int c = src[py * size + Math.max(0, Math.min(size - 1, px + dx))]; + r += (c >> 16) & 0xFF; g += (c >> 8) & 0xFF; b += c & 0xFF; + } + tmp[py * size + px] = ((int)(r/diam) << 16) | ((int)(g/diam) << 8) | (int)(b/diam); + } + } + // Vertikaler Pass + for (int py = 0; py < size; py++) { + for (int px = 0; px < size; px++) { + long r = 0, g = 0, b = 0; + for (int dz = -radius; dz <= radius; dz++) { + int c = tmp[Math.max(0, Math.min(size - 1, py + dz)) * size + px]; + r += (c >> 16) & 0xFF; g += (c >> 8) & 0xFF; b += c & 0xFF; + } + dst[py * size + px] = ((int)(r/diam) << 16) | ((int)(g/diam) << 8) | (int)(b/diam); + } + } + return dst; + } + + /** + * Bilineare Splatmap-Blendung der Tile-Durchschnittsfarben an Weltposition (wx, wz). + * Identische Logik wie der alte progressiver Splatmap-Blend in render(), aber mit + * vorberechneten Durchschnittsfarben statt Tile-UV-Sampling. + */ + private static int blendTileAvgColors(MapData m, int SS, float wx, float wz, + int[] tileAvg, int[] slotTypes) { + float sfx = Math.max(0f, Math.min(SS-1, (wx+WORLD_HALF)/WORLD_SIZE*(SS-1))); + float sfz = Math.max(0f, Math.min(SS-1, (WORLD_HALF-wz)/WORLD_SIZE*(SS-1))); + int sx0=(int)sfx, sx1=Math.min(sx0+1,SS-1); + int sz0=(int)sfz, sz1=Math.min(sz0+1,SS-1); + float stx=sfx-sx0, stz=sfz-sz0; + int bi00=sz0*SS+sx0, bi10=sz0*SS+sx1, bi01=sz1*SS+sx0, bi11=sz1*SS+sx1; + float wg = bilerp(m.splatG, bi00,bi10,bi01,bi11,stx,stz); + float wb = bilerp(m.splatB, bi00,bi10,bi01,bi11,stx,stz); + float wa = bilerp(m.splatA, bi00,bi10,bi01,bi11,stx,stz); + float up1 = bilerp(m.upperSplatR, bi00,bi10,bi01,bi11,stx,stz); + float up2 = bilerp(m.upperSplatG, bi00,bi10,bi01,bi11,stx,stz); + float up3 = bilerp(m.upperSplatB, bi00,bi10,bi01,bi11,stx,stz); + float up4 = bilerp(m.upperSplatA, bi00,bi10,bi01,bi11,stx,stz); + float t31 = bilerp(m.thirdSplatR, bi00,bi10,bi01,bi11,stx,stz); + float t32 = bilerp(m.thirdSplatG, bi00,bi10,bi01,bi11,stx,stz); + float t33 = bilerp(m.thirdSplatB, bi00,bi10,bi01,bi11,stx,stz); + float t34 = bilerp(m.thirdSplatA, bi00,bi10,bi01,bi11,stx,stz); + int c0 = tileAvg[slotTypes[0]]; + float fr=(c0>>16)&0xFF, fg=(c0>>8)&0xFF, fb=c0&0xFF; + int col; + col=tileAvg[slotTypes[1]]; fr=fr*(1-wg) +((col>>16)&0xFF)*wg; fg=fg*(1-wg) +((col>>8)&0xFF)*wg; fb=fb*(1-wg) +(col&0xFF)*wg; + col=tileAvg[slotTypes[2]]; fr=fr*(1-wb) +((col>>16)&0xFF)*wb; fg=fg*(1-wb) +((col>>8)&0xFF)*wb; fb=fb*(1-wb) +(col&0xFF)*wb; + col=tileAvg[slotTypes[3]]; fr=fr*(1-wa) +((col>>16)&0xFF)*wa; fg=fg*(1-wa) +((col>>8)&0xFF)*wa; fb=fb*(1-wa) +(col&0xFF)*wa; + col=tileAvg[slotTypes[4]]; fr=fr*(1-up1)+((col>>16)&0xFF)*up1; fg=fg*(1-up1)+((col>>8)&0xFF)*up1; fb=fb*(1-up1)+(col&0xFF)*up1; + col=tileAvg[slotTypes[5]]; fr=fr*(1-up2)+((col>>16)&0xFF)*up2; fg=fg*(1-up2)+((col>>8)&0xFF)*up2; fb=fb*(1-up2)+(col&0xFF)*up2; + col=tileAvg[slotTypes[6]]; fr=fr*(1-up3)+((col>>16)&0xFF)*up3; fg=fg*(1-up3)+((col>>8)&0xFF)*up3; fb=fb*(1-up3)+(col&0xFF)*up3; + col=tileAvg[slotTypes[7]]; fr=fr*(1-up4)+((col>>16)&0xFF)*up4; fg=fg*(1-up4)+((col>>8)&0xFF)*up4; fb=fb*(1-up4)+(col&0xFF)*up4; + col=tileAvg[slotTypes[8]]; fr=fr*(1-t31)+((col>>16)&0xFF)*t31; fg=fg*(1-t31)+((col>>8)&0xFF)*t31; fb=fb*(1-t31)+(col&0xFF)*t31; + col=tileAvg[slotTypes[9]]; fr=fr*(1-t32)+((col>>16)&0xFF)*t32; fg=fg*(1-t32)+((col>>8)&0xFF)*t32; fb=fb*(1-t32)+(col&0xFF)*t32; + col=tileAvg[slotTypes[10]]; fr=fr*(1-t33)+((col>>16)&0xFF)*t33; fg=fg*(1-t33)+((col>>8)&0xFF)*t33; fb=fb*(1-t33)+(col&0xFF)*t33; + col=tileAvg[slotTypes[11]]; fr=fr*(1-t34)+((col>>16)&0xFF)*t34; fg=fg*(1-t34)+((col>>8)&0xFF)*t34; fb=fb*(1-t34)+(col&0xFF)*t34; + return (clamp((int)fr)<<16)|(clamp((int)fg)<<8)|clamp((int)fb); + } + + private static int[] computeTileAvgColors(int[][] tiles) { + int n = TILE_FALLBACK_RGB.length; + int[] avg = new int[n]; + for (int i = 0; i < n; i++) { + if (tiles != null && i < tiles.length && tiles[i] != null) { + long r = 0, g = 0, b = 0; + for (int argb : tiles[i]) { r += (argb>>16)&0xFF; g += (argb>>8)&0xFF; b += argb&0xFF; } + int len = tiles[i].length; + avg[i] = ((int)(r/len) << 16) | ((int)(g/len) << 8) | (int)(b/len); + } else { + int[] fb = TILE_FALLBACK_RGB[i]; + avg[i] = (fb[0] << 16) | (fb[1] << 8) | fb[2]; + } + } + return avg; + } + /** * Kuwahara-Filter: Für jeden Pixel das Quadranten-Fenster mit der kleinsten * Varianz wählen und dessen Mittelwert setzen → Ölgemälde-/gemalt-Effekt. diff --git a/blight-editor/src/main/java/de/blight/editor/ui/WorldMapView.java b/blight-editor/src/main/java/de/blight/editor/ui/WorldMapView.java index bf6bc1e..0348496 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/WorldMapView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/WorldMapView.java @@ -108,27 +108,28 @@ public class WorldMapView extends VBox { private byte[] categoryMap = null; private float[] heightMap = null; // terrain+voxel, CAT_SIZE×CAT_SIZE (für Klippen) private float[] heightMapTerrain = null; // terrain-only, CAT_SIZE×CAT_SIZE (für Wasser-Füllung) - private WritableImage tileOverlayImg = null; - private int[] tileOverlayPixels = null; - private int tileOverlayW = 0; - private int tileOverlayH = 0; - private WritableImage forestOverlayImg = null; - private int[] forestOverlayPixels = null; - private int forestOverlayW = 0; - private int forestOverlayH = 0; - // Gecachte Vorab-Berechnungen — nur bei Modell-Wechsel neu berechnen - private WorldMapRenderModel tileOverlayModel = null; - private float[] tileOverlayForestD = null; - private int[] tileOverlaySlots = null; - // Dirty-Flag: Overlay-Pixel neu rendern wenn Pan/Zoom oder Modell geändert - private double tileOverlayCachedScale = Double.NaN; - private double tileOverlayCachedPanX = Double.NaN; - private double tileOverlayCachedPanY = Double.NaN; - // Wasser-Fill-Overlay (Layer über Terrain-Textur, unter Küstenlinie) - private WritableImage waterFillImg = null; - private int[] waterFillPixels = null; - private int waterFillW = 0; - private int waterFillH = 0; + private static final int ZOOM_LEVELS = 5; + private static final double ZOOM_MAX_SCALE = 4.0; // 1 Canvas-Pixel = 0,25 WU = 0,25m + private static final int ZOOM_HIRES_CUTOFF = 3; // ab diesem Index Chunk-Rendering + private static final int CHUNK_CACHE_MAX = 24; // max. Chunks gleichzeitig im RAM + private static final java.nio.file.Path CHUNK_CACHE_DIR = + java.nio.file.Paths.get(System.getProperty("user.home"), ".blight", "map_chunks"); + + private double[] zoomLevels = null; + private int zoomLevelIdx = 0; + + /** LRU-Cache: chunk-Key (cy*COLS+cx) → WritableImage. */ + private final java.util.LinkedHashMap chunkCache = + new java.util.LinkedHashMap<>(CHUNK_CACHE_MAX * 2, 0.75f, true) { + @Override protected boolean removeEldestEntry(java.util.Map.Entry e) { + return size() > CHUNK_CACHE_MAX; + } + }; + private final java.util.Set chunksLoading = + java.util.Collections.synchronizedSet(new java.util.HashSet<>()); + + private WritableImage bakedForestOverlay = null; + // Wasser-Fill-Overlay entfernt: ocean + PlacedWater sind bereits in mapFxImage korrekt gerendert // PlacedWater-Wasserhoehe pro Zelle (0 = kein PlacedWater); fuer glatte Uebergaenge private float[] waterHeightMap = null; @@ -282,10 +283,13 @@ public class WorldMapView extends VBox { }); canvas.setOnScroll(e -> { - if (e.getDeltaY() == 0) return; - double factor = e.getDeltaY() > 0 ? 1.15 : 1.0 / 1.15; + if (zoomLevels == null || e.getDeltaY() == 0) return; + int newIdx = zoomLevelIdx + (e.getDeltaY() > 0 ? 1 : -1); + newIdx = Math.max(0, Math.min(ZOOM_LEVELS - 1, newIdx)); + if (newIdx == zoomLevelIdx) return; double oldScale = scale; - scale = Math.max(0.1, Math.min(30.0, scale * factor)); + zoomLevelIdx = newIdx; + scale = zoomLevels[zoomLevelIdx]; panX = e.getX() - (e.getX() - panX) * scale / oldScale; panY = e.getY() - (e.getY() - panY) * scale / oldScale; redraw(); @@ -365,6 +369,17 @@ public class WorldMapView extends VBox { log.debug("[WorldMap] Hintergrund-PNG + Kategorie-Karte gerendert ({}×{} px, {} ms)", RENDER_SIZE, RENDER_SIZE, System.currentTimeMillis() - t1); + float[] fDensity = computeForestDensity(model); + int[] bakedFstPx = computeForestPixels(model, fDensity, 2.0f); + + // Hires-Chunks im Hintergrund generieren (nach dem UI-Update, nicht-blockierend) + final WorldMapRenderModel chunkModel = model; + Platform.runLater(() -> statusLbl.setText("Generiere Hires-Chunks…")); + WorldMapRenderer.buildAndSaveHiresChunks( + chunkModel.mapData(), chunkModel.slotColorsRGB(), chunkModel.mapTilePixels(), + chunkModel.voxelSurface(), chunkModel.waters(), CHUNK_CACHE_DIR, true); + chunkCache.clear(); + Platform.runLater(() -> { currentModel = model; cachedAreas = new ArrayList<>(model.areas()); @@ -375,6 +390,7 @@ public class WorldMapView extends VBox { heightMapTerrain = hMapT; categoryMap = catMap; waterHeightMap = whMap; + bakedForestOverlay = toWritableImage(bakedFstPx, RENDER_SIZE); fitToView(); redraw(); progress.setVisible(false); @@ -409,22 +425,13 @@ public class WorldMapView extends VBox { Thread t = new Thread(() -> { log.debug("[WorldMap] PNG-Rerender aus gecachtem Modell gestartet ({}×{})", RENDER_SIZE, RENDER_SIZE); long t0 = System.currentTimeMillis(); + // Nur das PNG neu rendern — heightMap/catMap/waterHeightMap kommen aus Stage 1 + // und sind bereits in den Instanz-Feldern vorhanden BufferedImage bi = WorldMapRenderer.render(model, RENDER_SIZE, buildBackgroundOptions()); - float[] hMap = WorldMapRenderer.buildTerrainSamples( - model.mapData(), CAT_SIZE, model.voxelSurface()); - float[] hMapT = WorldMapRenderer.buildTerrainSamples( - model.mapData(), CAT_SIZE, null); - byte[] catMap = WorldMapRenderer.buildCategoryMap( - model.mapData(), CAT_SIZE, model.voxelSurface(), model.waters(), model.slotColorsRGB()); - float[] whMap = WorldMapRenderer.buildWaterHeightMap(model.waters(), CAT_SIZE); log.debug("[WorldMap] PNG-Rerender fertig ({} ms)", System.currentTimeMillis() - t0); Platform.runLater(() -> { mapBuffered = bi; mapFxImage = SwingFXUtils.toFXImage(bi, null); - heightMap = hMap; - heightMapTerrain = hMapT; - categoryMap = catMap; - waterHeightMap = whMap; redraw(); progress.setVisible(false); statusLbl.setText("Bereit"); @@ -454,20 +461,71 @@ public class WorldMapView extends VBox { return; } - gc.setImageSmoothing(false); - gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale); gc.setImageSmoothing(true); - // Layer 1: Terrain-Textur (überall, opak) - drawTileOverlay(gc); - // Layer 2: Wasser darüber (glatte Küste via seaCoastPaths) + if (zoomLevelIdx >= ZOOM_HIRES_CUTOFF) { + drawHiresChunks(gc); + } else { + gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale); + } + // Layer 1: Wasser (glatte Küste via seaCoastPaths) if (cbWater.isSelected()) drawWaterOverlay(gc); - // Layer 3: Wald-Overlay (nach Wasser, damit Bäume nicht im Wasser erscheinen) + // Layer 2: Wald-Overlay (nach Wasser, damit Bäume nicht im Wasser erscheinen) drawForestOverlay(gc); if (cbAreas.isSelected()) drawAreasOverlay(gc); if (cbLocations.isSelected()) drawLocationsOverlay(gc); if (!isFullscreen) drawCameraIndicator(gc); } + private void drawHiresChunks(GraphicsContext gc) { + final int CS = WorldMapRenderer.CHUNK_SIZE; + final int COLS = WorldMapRenderer.HIRES_MAP_SIZE / CS; + // Pixelgröße eines Hires-Chunks auf dem Canvas + final double chunkCanvasW = (double) RENDER_SIZE * scale / COLS; + final double chunkCanvasH = chunkCanvasW; + int cW = (int) canvas.getWidth(), cH = (int) canvas.getHeight(); + + for (int cy = 0; cy < COLS; cy++) { + double canvasTop = panY + cy * chunkCanvasH; + if (canvasTop > cH) break; + if (canvasTop + chunkCanvasH < 0) continue; + for (int cx = 0; cx < COLS; cx++) { + double canvasLeft = panX + cx * chunkCanvasW; + if (canvasLeft > cW) break; + if (canvasLeft + chunkCanvasW < 0) continue; + + int key = cy * COLS + cx; + WritableImage img = chunkCache.get(key); + if (img != null) { + gc.drawImage(img, canvasLeft, canvasTop, chunkCanvasW, chunkCanvasH); + } else { + // Platzhalter (dunkelgrau) + asynchrones Laden + gc.setFill(javafx.scene.paint.Color.rgb(40, 40, 40)); + gc.fillRect(canvasLeft, canvasTop, chunkCanvasW, chunkCanvasH); + if (chunksLoading.add(key)) { + final int fcx = cx, fcy = cy, fkey = key; + Thread lt = new Thread(() -> { + int[] pixels = WorldMapRenderer.loadChunk(fcx, fcy, CHUNK_CACHE_DIR); + if (pixels != null) { + WritableImage wi = new WritableImage(CS, CS); + wi.getPixelWriter().setPixels(0, 0, CS, CS, + javafx.scene.image.PixelFormat.getIntArgbInstance(), pixels, 0, CS); + Platform.runLater(() -> { + chunkCache.put(fkey, wi); + chunksLoading.remove(fkey); + redraw(); + }); + } else { + chunksLoading.remove(fkey); + } + }, "ChunkLoader-" + fcx + "-" + fcy); + lt.setDaemon(true); + lt.start(); + } + } + } + } + } + // ── Canvas Tile-Overlay (zoom-unabhängige Kacheln) ──────────────────────── private static int tileSample(int[][] tilePx, int cat, @@ -492,206 +550,67 @@ public class WorldMapView extends VBox { return (v00*(1-tx)+v10*tx)*(1-tz)+(v01*(1-tx)+v11*tx)*tz; } - private void drawTileOverlay(GraphicsContext gc) { - if (heightMap == null || currentModel == null) return; - de.blight.common.MapData md = currentModel.mapData(); - if (md == null) return; - int[][] tilePx = currentModel.mapTilePixels(); - if (tilePx == null) return; + private void drawForestOverlay(GraphicsContext gc) { + if (bakedForestOverlay == null) return; + int cW = (int) canvas.getWidth(), cH = (int) canvas.getHeight(); + gc.drawImage(bakedForestOverlay, -panX / scale, -panY / scale, cW / scale, cH / scale, 0, 0, cW, cH); + } - int cW = (int) canvas.getWidth(); - int cH = (int) canvas.getHeight(); - if (cW <= 0 || cH <= 0) return; + // ── Wald-Baking (läuft im Background-Thread) ───────────────────────────── - final float WH = WorldMapRenderer.WORLD_HALF; - final float WS = WorldMapRenderer.WORLD_SIZE; - final float TW = WorldMapRenderer.TILE_WU; - final float scaleF = (float) scale; - final float panXf = (float) panX; - final float panYf = (float) panY; - final float imgW = (float)(RENDER_SIZE - 1); - final int CS = CAT_SIZE; - final float[] hm = heightMap; - final float CS1 = CS - 1f; - final float CLIFF_LO = 0.4663f, CLIFF_RNG = 0.8391f - 0.4663f; - - // ── Modell-abhängige Vorab-Berechnungen cachen ──────────────────── - if (tileOverlayModel != currentModel) { - tileOverlayModel = currentModel; - tileOverlaySlots = WorldMapRenderer.computeSlotTileTypes(md, currentModel.slotColorsRGB()); - final int FD0 = 512; - final float forestRadius0 = 14f; - final int forestRCells0 = Math.max(1, (int)(forestRadius0 / (WS / FD0))); - final float[] fd0 = new float[FD0 * FD0]; - for (de.blight.common.PlacedModel pm : currentModel.models()) { - if (!WorldMapRenderer.isTree(pm)) continue; - int fx = Math.max(0, Math.min(FD0-1, (int)((pm.x() + WH) / WS * FD0))); - int fz = Math.max(0, Math.min(FD0-1, (int)((WH - pm.z()) / WS * FD0))); - for (int dz = -forestRCells0; dz <= forestRCells0; dz++) { - for (int dx = -forestRCells0; dx <= forestRCells0; dx++) { - if (dx*dx + dz*dz <= forestRCells0*forestRCells0) { - int nx = fx+dx, nz = fz+dz; - if (nx >= 0 && nx < FD0 && nz >= 0 && nz < FD0) fd0[nz*FD0+nx] += 1f; - } + private static float[] computeForestDensity(WorldMapRenderModel model) { + final int FD = 512; + final float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE; + final float radius = 14f; + final int rc = Math.max(1, (int)(radius / (WS / FD))); + final float[] fd = new float[FD * FD]; + for (de.blight.common.PlacedModel pm : model.models()) { + if (!WorldMapRenderer.isTree(pm)) continue; + int fx = Math.max(0, Math.min(FD-1, (int)((pm.x()+WH)/WS*FD))); + int fz = Math.max(0, Math.min(FD-1, (int)((WH-pm.z())/WS*FD))); + for (int dz=-rc; dz<=rc; dz++) { + for (int dx=-rc; dx<=rc; dx++) { + if (dx*dx+dz*dz <= rc*rc) { + int nx=fx+dx, nz=fz+dz; + if (nx>=0&&nx=0&&nz= WH || wz < -WH || wz >= WH) { - tileOverlayPixels[cy * cW + cx] = 0x00000000; - continue; - } - - // ── Steigung aus heightMap → Cliff-Blend - float fmx = (wx + WH) / WS * CS1; - float fmz = (wz + WH) / WS * CS1; - int cmx = Math.max(0, Math.min(CS-1, (int)fmx)); - int cmz = Math.max(0, Math.min(CS-1, (int)fmz)); - int cmxR = Math.min(CS-1, cmx+2), cmxL = Math.max(0, cmx-2); - int cmzD = Math.min(CS-1, cmz+2), cmzU = Math.max(0, cmz-2); - float hC = hm[cmz*CS+cmx]; - float hR = hm[cmz*CS+cmxR]; if (hR < 0f) hR = hC; - float hL = hm[cmz*CS+cmxL]; if (hL < 0f) hL = hC; - float hD = hm[cmzD*CS+cmx]; if (hD < 0f) hD = hC; - float hU = hm[cmzU*CS+cmx]; if (hU < 0f) hU = hC; - float dx = (hR - hL) / (cmxR - cmxL), dz = (hD - hU) / (cmzD - cmzU); - float gradMag = (float)Math.sqrt((double)(dx*dx + dz*dz)); - float t = Math.max(0f, Math.min(1f, (gradMag - CLIFF_LO) / CLIFF_RNG)); - t = t * t * (3f - 2f * t); - - // ── Tile-UV bilinear - float rawU = (wx + WH) / TW, rawV = (wz + WH) / TW; - float uf16 = (rawU - (float)Math.floor(rawU)) * 16f; - float vf16 = (rawV - (float)Math.floor(rawV)) * 16f; - int u0 = Math.floorMod((int)uf16,16), u1 = Math.floorMod(u0+1,16); - int v0 = Math.floorMod((int)vf16,16), v1 = Math.floorMod(v0+1,16); - float ux = uf16-(float)Math.floor(uf16), vxf = vf16-(float)Math.floor(vf16); - float w00=(1-ux)*(1-vxf), w10=ux*(1-vxf), w01=(1-ux)*vxf, w11=ux*vxf; - - // ── Splatmap bilinear (Y-flip) - float sfx = Math.max(0f, Math.min(SS-1, (wx+WH)/WS*(SS-1))); - float sfz = Math.max(0f, Math.min(SS-1, (WH-wz)/WS*(SS-1))); - int sx0=(int)sfx, sx1=Math.min(sx0+1,SS-1); - int sz0=(int)sfz, sz1=Math.min(sz0+1,SS-1); - float stx=sfx-sx0, stz=sfz-sz0; - int bi00=sz0*SS+sx0, bi10=sz0*SS+sx1, bi01=sz1*SS+sx0, bi11=sz1*SS+sx1; - float wg = spBilerp(spG,bi00,bi10,bi01,bi11,stx,stz); - float wb = spBilerp(spB,bi00,bi10,bi01,bi11,stx,stz); - float wa = spBilerp(spA,bi00,bi10,bi01,bi11,stx,stz); - float up1 = spBilerp(upR,bi00,bi10,bi01,bi11,stx,stz); - float up2 = spBilerp(upG,bi00,bi10,bi01,bi11,stx,stz); - float up3 = spBilerp(upB,bi00,bi10,bi01,bi11,stx,stz); - float up4 = spBilerp(upA,bi00,bi10,bi01,bi11,stx,stz); - float t31 = spBilerp(t3R,bi00,bi10,bi01,bi11,stx,stz); - float t32 = spBilerp(t3G,bi00,bi10,bi01,bi11,stx,stz); - float t33 = spBilerp(t3B,bi00,bi10,bi01,bi11,stx,stz); - float t34 = spBilerp(t3A,bi00,bi10,bi01,bi11,stx,stz); - - // ── Alle Tile-Farben sampeln - for (int ti = 0; ti < 8; ti++) tileArgbs[ti] = tileSample(tilePx,ti,u0,u1,v0,v1,w00,w10,w01,w11); - - // ── Splatmap-Blend - int col = tileArgbs[slotTiles[0]]; - int cr=(col>>16)&0xFF, cgv=(col>>8)&0xFF, cbv=col&0xFF; - col=tileArgbs[slotTiles[1]]; cr=(int)(cr*(1-wg) +((col>>16)&0xFF)*wg ); cgv=(int)(cgv*(1-wg) +((col>>8)&0xFF)*wg ); cbv=(int)(cbv*(1-wg) +(col&0xFF)*wg ); - col=tileArgbs[slotTiles[2]]; cr=(int)(cr*(1-wb) +((col>>16)&0xFF)*wb ); cgv=(int)(cgv*(1-wb) +((col>>8)&0xFF)*wb ); cbv=(int)(cbv*(1-wb) +(col&0xFF)*wb ); - col=tileArgbs[slotTiles[3]]; cr=(int)(cr*(1-wa) +((col>>16)&0xFF)*wa ); cgv=(int)(cgv*(1-wa) +((col>>8)&0xFF)*wa ); cbv=(int)(cbv*(1-wa) +(col&0xFF)*wa ); - col=tileArgbs[slotTiles[4]]; cr=(int)(cr*(1-up1)+((col>>16)&0xFF)*up1); cgv=(int)(cgv*(1-up1)+((col>>8)&0xFF)*up1); cbv=(int)(cbv*(1-up1)+(col&0xFF)*up1); - col=tileArgbs[slotTiles[5]]; cr=(int)(cr*(1-up2)+((col>>16)&0xFF)*up2); cgv=(int)(cgv*(1-up2)+((col>>8)&0xFF)*up2); cbv=(int)(cbv*(1-up2)+(col&0xFF)*up2); - col=tileArgbs[slotTiles[6]]; cr=(int)(cr*(1-up3)+((col>>16)&0xFF)*up3); cgv=(int)(cgv*(1-up3)+((col>>8)&0xFF)*up3); cbv=(int)(cbv*(1-up3)+(col&0xFF)*up3); - col=tileArgbs[slotTiles[7]]; cr=(int)(cr*(1-up4)+((col>>16)&0xFF)*up4); cgv=(int)(cgv*(1-up4)+((col>>8)&0xFF)*up4); cbv=(int)(cbv*(1-up4)+(col&0xFF)*up4); - col=tileArgbs[slotTiles[8]]; cr=(int)(cr*(1-t31)+((col>>16)&0xFF)*t31); cgv=(int)(cgv*(1-t31)+((col>>8)&0xFF)*t31); cbv=(int)(cbv*(1-t31)+(col&0xFF)*t31); - col=tileArgbs[slotTiles[9]]; cr=(int)(cr*(1-t32)+((col>>16)&0xFF)*t32); cgv=(int)(cgv*(1-t32)+((col>>8)&0xFF)*t32); cbv=(int)(cbv*(1-t32)+(col&0xFF)*t32); - col=tileArgbs[slotTiles[10]];cr=(int)(cr*(1-t33)+((col>>16)&0xFF)*t33); cgv=(int)(cgv*(1-t33)+((col>>8)&0xFF)*t33); cbv=(int)(cbv*(1-t33)+(col&0xFF)*t33); - col=tileArgbs[slotTiles[11]];cr=(int)(cr*(1-t34)+((col>>16)&0xFF)*t34); cgv=(int)(cgv*(1-t34)+((col>>8)&0xFF)*t34); cbv=(int)(cbv*(1-t34)+(col&0xFF)*t34); - - // ── Wald-Dichte (bilinear) → separater Buffer, nach Wasser gezeichnet - float fsfx = Math.max(0f, Math.min(FD-1.001f, (wx + WH) / WS * FD)); - float fsfz = Math.max(0f, Math.min(FD-1.001f, (WH - wz) / WS * FD)); + private static int[] computeForestPixels(WorldMapRenderModel model, float[] density, float threshold) { + final int SIZE = RENDER_SIZE, FD = 512; + final float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE; + final int[][] tilePx = model.mapTilePixels(); + final int forestArgb = (tilePx != null && WorldMapRenderer.TILE_FOREST < tilePx.length + && tilePx[WorldMapRenderer.TILE_FOREST] != null) + ? tileSample(tilePx, WorldMapRenderer.TILE_FOREST, 0, 0, 0, 0, 1f, 0f, 0f, 0f) + : 0x3D7A28; + final int forestColor = 0x80000000 | (forestArgb & 0x00FFFFFF); + final int[] px = new int[SIZE * SIZE]; + for (int py = 0; py < SIZE; py++) { + float wz = (float) py / (SIZE - 1) * WS - WH; + for (int bx = 0; bx < SIZE; bx++) { + float wx = (float) bx / (SIZE - 1) * WS - WH; + float fsfx=Math.max(0f,Math.min(FD-1.001f,(wx+WH)/WS*FD)); + float fsfz=Math.max(0f,Math.min(FD-1.001f,(WH-wz)/WS*FD)); int fsx0=(int)fsfx, fsx1=Math.min(fsx0+1,FD-1); int fsz0=(int)fsfz, fsz1=Math.min(fsz0+1,FD-1); float fstx=fsfx-fsx0, fstz=fsfz-fsz0; - float fd = (forestDensity[fsz0*FD+fsx0]*(1-fstx)+forestDensity[fsz0*FD+fsx1]*fstx)*(1-fstz) - + (forestDensity[fsz1*FD+fsx0]*(1-fstx)+forestDensity[fsz1*FD+fsx1]*fstx)*fstz; - // mind. 3 Bäume im Überlappungsbereich (fd > 2.0 bedeutet >2 Bäume) - forestOverlayPixels[cy * cW + cx] = (fd > 2.0f) - ? (0x80000000 | (tileArgbs[WorldMapRenderer.TILE_FOREST] & 0x00FFFFFF)) - : 0x00000000; - - int argb = (cr<<16)|(cgv<<8)|cbv; - - // ── Cliff-Overlay - if (t > 0f) { - int cArgb = tileArgbs[WorldMapRenderer.TILE_CLIFF]; - int ccr=(cArgb>>16)&0xFF, ccg=(cArgb>>8)&0xFF, ccb=cArgb&0xFF; - argb = ((int)(cr+(ccr-cr)*t)<<16)|((int)(cgv+(ccg-cgv)*t)<<8)|(int)(cbv+(ccb-cbv)*t); - } - - tileOverlayPixels[cy * cW + cx] = 0xFF000000 | (argb & 0x00FFFFFF); + float fd=(density[fsz0*FD+fsx0]*(1-fstx)+density[fsz0*FD+fsx1]*fstx)*(1-fstz) + +(density[fsz1*FD+fsx0]*(1-fstx)+density[fsz1*FD+fsx1]*fstx)*fstz; + px[py*SIZE+bx] = (fd > threshold) ? forestColor : 0x00000000; } } - - tileOverlayImg.getPixelWriter().setPixels( - 0, 0, cW, cH, PixelFormat.getIntArgbInstance(), tileOverlayPixels, 0, cW); - forestOverlayImg.getPixelWriter().setPixels( - 0, 0, cW, cH, PixelFormat.getIntArgbInstance(), forestOverlayPixels, 0, cW); - gc.drawImage(tileOverlayImg, 0, 0); + return px; } - private void drawForestOverlay(GraphicsContext gc) { - if (forestOverlayImg == null) return; - gc.drawImage(forestOverlayImg, 0, 0); + private static WritableImage toWritableImage(int[] pixels, int size) { + WritableImage img = new WritableImage(size, size); + img.getPixelWriter().setPixels(0, 0, size, size, PixelFormat.getIntArgbInstance(), pixels, 0, size); + return img; } // ── Canvas-Vektor-Overlays ──────────────────────────────────────────────── @@ -768,82 +687,11 @@ public class WorldMapView extends VBox { } private void drawWaterOverlay(GraphicsContext gc) { - if (currentModel == null || heightMap == null || waterHeightMap == null) return; + if (currentModel == null) return; + // Wasser-Füllung kommt aus mapFxImage (bereits korrekt gerendert). + // Hier nur noch Küstenlinie und Wellen-Vektoren. - int cW = (int) canvas.getWidth(), cH = (int) canvas.getHeight(); - if (cW <= 0 || cH <= 0) return; - - // ── Wasser-Füllung: blauer Pixel-Layer über dem Terrain-Overlay ────────── - if (waterFillPixels == null || waterFillPixels.length != cW * cH) { - waterFillPixels = new int[cW * cH]; - } - if (waterFillImg == null || waterFillW != cW || waterFillH != cH) { - waterFillImg = new WritableImage(cW, cH); - waterFillW = cW; - waterFillH = cH; - } - - final float WH = WorldMapRenderer.WORLD_HALF; - final float WS = WorldMapRenderer.WORLD_SIZE; - final float scaleF = (float) scale; - final float panXf = (float) panX; - final float panYf = (float) panY; - final float imgW = (float)(RENDER_SIZE - 1); - final int CS = CAT_SIZE; - final float[] hm = heightMap; // terrain+voxel - final float[] whm = waterHeightMap; // PlacedWater-Höhe pro Zelle (0 = kein PlacedWater) - final float CS1 = CS - 1f; - final int WR = 0xAD, WG = 0xD8, WB = 0xE6; // WATER_COLOR = #ADD8E6 - - for (int cy = 0; cy < cH; cy++) { - float wz = (cy - panYf) / scaleF / imgW * WS - WH; - for (int cx = 0; cx < cW; cx++) { - float wx = (cx - panXf) / scaleF / imgW * WS - WH; - - if (wx < -WH || wx >= WH || wz < -WH || wz >= WH) { - waterFillPixels[cy * cW + cx] = 0; - continue; - } - - float fmx = (wx + WH) / WS * CS1; - float fmz = (wz + WH) / WS * CS1; - int cmx = Math.max(0, Math.min(CS-1, (int) fmx)); - int cmz = Math.max(0, Math.min(CS-1, (int) fmz)); - int cmx1 = Math.min(CS-1, cmx+1), cmz1 = Math.min(CS-1, cmz+1); - float tx = fmx - cmx, tz = fmz - cmz; - - // Bilinear heightMap (terrain+voxel) - float hPx = hm[cmz *CS+cmx ] * (1-tx)*(1-tz) - + hm[cmz *CS+cmx1] * tx *(1-tz) - + hm[cmz1*CS+cmx ] * (1-tx)*tz - + hm[cmz1*CS+cmx1] * tx *tz; - - // Bilinear waterHeightMap → PlacedWater-Schwelle (0 = nur Ozean) - float wH = whm[cmz *CS+cmx ] * (1-tx)*(1-tz) - + whm[cmz *CS+cmx1] * tx *(1-tz) - + whm[cmz1*CS+cmx ] * (1-tx)*tz - + whm[cmz1*CS+cmx1] * tx *tz; - - // delta < 0 → unter Wasseroberfläche; Übergangsband ±0.5 WU - float delta = hPx - wH; - int alpha; - if (delta <= -0.5f) { - alpha = 255; - } else if (delta < 0.5f) { - alpha = Math.max(0, Math.min(255, (int)((0.5f - delta) * 255f))); - } else { - alpha = 0; - } - - waterFillPixels[cy * cW + cx] = (alpha << 24) | (WR << 16) | (WG << 8) | WB; - } - } - - waterFillImg.getPixelWriter().setPixels( - 0, 0, cW, cH, PixelFormat.getIntArgbInstance(), waterFillPixels, 0, cW); - gc.drawImage(waterFillImg, 0, 0); - - // ── Küstenlinie – geglättete Pfade über der Wasser-Füllung ───────────── + // ── Küstenlinie ─────────────────────────────────────────────────────────── List seaCoastPaths = currentModel.coastPaths(); if (cbCoastline.isSelected() && seaCoastPaths != null && !seaCoastPaths.isEmpty()) { double canvasW = canvas.getWidth(), canvasH = canvas.getHeight(); @@ -1068,9 +916,10 @@ public class WorldMapView extends VBox { List treeList = models.stream() .filter(WorldMapRenderer::isTree).collect(Collectors.toList()); List> tClusters = clusterTreeModels(treeList, 40f); + int[] colorMap = WorldMapRenderer.buildTerrainColorMap(mapData, slotColors, mapTiles, voxelSurface, waters); return new WorldMapRenderModel( mapData, areas, zones, locs, waters, models, waterfalls, slotColors, - mask, voxelSurface, tSamp, paths, tClusters, mapTiles); + mask, voxelSurface, tSamp, paths, tClusters, mapTiles, colorMap); } // Erzeugt interpolierte Marching-Squares-Segmente für Meer + Wasserflächen. @@ -1274,9 +1123,21 @@ public class WorldMapView extends VBox { private void fitToView() { if (canvas.getWidth() <= 0 || mapFxImage == null) return; - scale = Math.min(canvas.getWidth() / mapFxImage.getWidth(), canvas.getHeight() / mapFxImage.getHeight()); - panX = (canvas.getWidth() - mapFxImage.getWidth() * scale) / 2; - panY = (canvas.getHeight() - mapFxImage.getHeight() * scale) / 2; + double fitScale = Math.min(canvas.getWidth() / RENDER_SIZE, canvas.getHeight() / RENDER_SIZE); + computeZoomLevels(fitScale); + zoomLevelIdx = 0; + scale = zoomLevels[0]; + panX = (canvas.getWidth() - RENDER_SIZE * scale) / 2; + panY = (canvas.getHeight() - RENDER_SIZE * scale) / 2; + } + + private void computeZoomLevels(double fitScale) { + zoomLevels = new double[ZOOM_LEVELS]; + zoomLevels[0] = fitScale; + double ratio = Math.pow(ZOOM_MAX_SCALE / fitScale, 1.0 / (ZOOM_LEVELS - 1)); + for (int i = 1; i < ZOOM_LEVELS; i++) { + zoomLevels[i] = zoomLevels[i-1] * ratio; + } } private static boolean polyContains(float[] xs, float[] zs, float px, float pz) { @@ -1400,7 +1261,10 @@ public class WorldMapView extends VBox { double cw = canvasPane.getWidth(); double ch = canvasPane.getHeight(); if (cw <= 0 || ch <= 0 || mapFxImage == null) return; - scale = Math.min(cw / RENDER_SIZE, ch / RENDER_SIZE); + double fitScale = Math.min(cw / RENDER_SIZE, ch / RENDER_SIZE); + computeZoomLevels(fitScale); + zoomLevelIdx = 0; + scale = zoomLevels[0]; panX = (cw - RENDER_SIZE * scale) / 2.0; panY = (ch - RENDER_SIZE * scale) / 2.0; redraw(); diff --git a/blight-game/src/main/java/de/blight/game/state/MinimapState.java b/blight-game/src/main/java/de/blight/game/state/MinimapState.java index 866605b..46a0b57 100644 --- a/blight-game/src/main/java/de/blight/game/state/MinimapState.java +++ b/blight-game/src/main/java/de/blight/game/state/MinimapState.java @@ -40,7 +40,9 @@ import de.blight.common.PlacedLocationZone; import de.blight.common.PlacedModel; import de.blight.common.PlacedModelIO; import de.blight.common.PlacedWater; +import de.blight.common.PlacedWaterfall; import de.blight.common.WaterBodyIO; +import de.blight.common.WaterfallIO; import de.blight.common.map.WorldMapRenderer; import de.blight.common.map.WorldMapRenderer.RenderInput; import de.blight.common.map.WorldMapRenderer.RenderOptions; @@ -546,30 +548,22 @@ public class MinimapState extends BaseAppState { List locs = LocationIO.load(); List waters = WaterBodyIO.load(); List models = PlacedModelIO.load(); + List waterfalls = WaterfallIO.load(); int[] slotColors = computeSlotColors(mapData, root); + int[][] tilePixels = WorldMapRenderer.loadMapTiles(root); renderInput = new RenderInput(mapData, areas, zones, locs, waters, models, slotColors); + de.blight.common.map.WorldMapRenderModel renderModel = new de.blight.common.map.WorldMapRenderModel( + mapData, areas, zones, locs, waters, models, waterfalls, + slotColors, null, null, null, null, null, tilePixels, null); log.debug("[Minimap] Render-Modell bereit – {} Areas, {} Orte, {} Wasser ({} ms)", areas.size(), locs.size(), waters.size(), System.currentTimeMillis() - t0); - boolean needsRender = !Files.exists(png); - if (!needsRender) { - // Cache ungültig wenn Karte neuer als das PNG - try { - long pngMod = Files.getLastModifiedTime(png).toMillis(); - long mapMod = Files.getLastModifiedTime(MapIO.getMapPath()).toMillis(); - if (mapMod > pngMod) { needsRender = true; } - } catch (Exception ignored) { needsRender = true; } - } - if (needsRender) { - log.info("[Minimap] Rendere Weltkarte {}×{}…", TEXTURE_SIZE, TEXTURE_SIZE); - long t1 = System.currentTimeMillis(); - BufferedImage bi = WorldMapRenderer.render(renderInput, TEXTURE_SIZE, RenderOptions.all()); - ImageIO.write(bi, "PNG", png.toFile()); - log.debug("[Minimap] Weltkarte gerendert und gespeichert ({} ms): {}", - System.currentTimeMillis() - t1, png); - } else { - log.info("[Minimap] Gecachte Weltkarte: {}", png); - } + log.info("[Minimap] Rendere Weltkarte {}×{}…", TEXTURE_SIZE, TEXTURE_SIZE); + long t1 = System.currentTimeMillis(); + BufferedImage bi = WorldMapRenderer.render(renderModel, TEXTURE_SIZE, RenderOptions.all()); + ImageIO.write(bi, "PNG", png.toFile()); + log.debug("[Minimap] Weltkarte gerendert und gespeichert ({} ms): {}", + System.currentTimeMillis() - t1, png); app.enqueue(() -> { onTextureReady(); return null; }); } catch (Exception e) {