Weltkarte: Render-Modell-Trennung, UI-Umbau und Kamera-Indikator
- WorldMapRenderModel: neues Record als Zwischenschicht zwischen I/O und Rendering - WorldMapRenderer: render() akzeptiert Modell direkt; rückwärtskompatible Wrapper-Signatur für MinimapState - WorldMapView: Layer-Auswahl als MenuButton mit Checkboxen; Vollbild-Modus (centerStack-Swap); Kamera-Indikator mit FOV-Kegel (120ms-Timer, unabhängig vom Karten-Rendering); Linksklick teleportiert JME3-Kamera unter Beibehaltung der Terrain-Höhendifferenz - EditorApp: CameraInfoSupplier, TeleportCallback und FullscreenCallbacks verdrahtet - MinimapState, Logback: Debug-Logging für Modell-Build und Render-Zyklen - Map-Daten: Zwischenstand Weltdaten Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 73 KiB |
@@ -0,0 +1,27 @@
|
||||
package de.blight.common.map;
|
||||
|
||||
import de.blight.common.*;
|
||||
import de.blight.common.model.Location;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Fertig vorberechnetes Karten-Modell: enthält alle Rohdaten + die im Editor
|
||||
* vorcompilierten Overlay-Informationen (SeaMask, Küstenpfade, Baum-Cluster).
|
||||
* WorldMapRenderer.render() arbeitet nur noch gegen dieses Modell.
|
||||
*/
|
||||
public record WorldMapRenderModel(
|
||||
// Terrain-Daten + Polygon-Objekte (werden vom Renderer für das PNG genutzt)
|
||||
MapData mapData,
|
||||
List<PlacedArea> areas,
|
||||
List<PlacedLocationZone> zones,
|
||||
List<Location> locations,
|
||||
List<PlacedWater> waters,
|
||||
List<PlacedModel> models,
|
||||
int[] slotColorsRGB,
|
||||
// Vorberechnete Overlay-Daten (werden vom Canvas genutzt, nicht vom PNG-Renderer)
|
||||
boolean[] seaMask, // Wasser-Pixel-Maske bei SEA_MASK_SIZE-Auflösung
|
||||
float[] terrainSamples, // Höhenwerte bei SEA_MASK_SIZE (für Wellen-Prüfung)
|
||||
List<float[][]> coastPaths, // geglättete Marching-Squares Küstenpfade (Weltkoord.)
|
||||
List<List<PlacedModel>> treeClusters
|
||||
) {}
|
||||
@@ -62,17 +62,25 @@ public final class WorldMapRenderer {
|
||||
|
||||
public static boolean[] buildSeaMask(MapData m, int size) {
|
||||
int TV = MapData.TERRAIN_VERTS;
|
||||
int UV = MapData.UPPER_VERTS;
|
||||
boolean[] mask = new boolean[size * size];
|
||||
for (int py = 0; py < size; py++) {
|
||||
for (int px = 0; px < size; px++) {
|
||||
int hx = Math.min((int)((float) px / (size - 1) * (TV - 1)), TV - 1);
|
||||
int hz = Math.min((int)((float) py / (size - 1) * (TV - 1)), TV - 1);
|
||||
mask[py * size + px] = m.terrainHeight[hz * TV + hx] < 0f;
|
||||
int ux = Math.min((int)((float) px / (size - 1) * (UV - 1)), UV - 1);
|
||||
int uz = Math.min((int)((float) py / (size - 1) * (UV - 1)), UV - 1);
|
||||
float h = m.terrainHeight[hz * TV + hx];
|
||||
float upper = m.upperTop[uz * UV + ux];
|
||||
if (upper > 0f && upper > h) { h = upper; }
|
||||
mask[py * size + px] = h < 0f;
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
|
||||
private static final int WATER_COLOR = 0xFF_ADD8E6;
|
||||
|
||||
// Default-Slot-Farben für Slots 1-8 (Base-Layer 1-4 + Upper-Layer 5-8)
|
||||
private static final int[] DEF_SLOT_R = { 71, 115, 140, 204, 90, 130, 110, 180 };
|
||||
private static final int[] DEF_SLOT_G = { 148, 82, 115, 184, 80, 90, 60, 100 };
|
||||
@@ -80,15 +88,17 @@ public final class WorldMapRenderer {
|
||||
|
||||
private WorldMapRenderer() {}
|
||||
|
||||
public static BufferedImage render(RenderInput input, int targetSize, RenderOptions opts) {
|
||||
MapData m = input.mapData();
|
||||
/** Hauptmethode: rendert das Hintergrund-PNG aus einem vorberechneten Modell. */
|
||||
public static BufferedImage render(WorldMapRenderModel model, int targetSize, RenderOptions opts) {
|
||||
MapData m = model.mapData();
|
||||
int TV = MapData.TERRAIN_VERTS;
|
||||
int SS = MapData.SPLAT_SIZE;
|
||||
int[] slotR = slotChannel(input, 0);
|
||||
int[] slotG = slotChannel(input, 1);
|
||||
int[] slotB = slotChannel(input, 2);
|
||||
int[] slotR = slotChannel(model.slotColorsRGB(), 0);
|
||||
int[] slotG = slotChannel(model.slotColorsRGB(), 1);
|
||||
int[] slotB = slotChannel(model.slotColorsRGB(), 2);
|
||||
|
||||
// ── 1. Heightmap auf Zielauflösung samplen ────────────────────────────
|
||||
int UV = MapData.UPPER_VERTS;
|
||||
float[] heights = new float[targetSize * targetSize];
|
||||
float minH = Float.MAX_VALUE, maxH = -Float.MAX_VALUE;
|
||||
|
||||
@@ -96,7 +106,11 @@ public final class WorldMapRenderer {
|
||||
for (int px = 0; px < targetSize; px++) {
|
||||
int hx = Math.min((int)((float) px / (targetSize - 1) * (TV - 1)), TV - 1);
|
||||
int hz = Math.min((int)((float) py / (targetSize - 1) * (TV - 1)), TV - 1);
|
||||
int ux = Math.min((int)((float) px / (targetSize - 1) * (UV - 1)), UV - 1);
|
||||
int uz = Math.min((int)((float) py / (targetSize - 1) * (UV - 1)), UV - 1);
|
||||
float h = m.terrainHeight[hz * TV + hx];
|
||||
float upper = m.upperTop[uz * UV + ux];
|
||||
if (upper > 0f && upper > h) { h = upper; }
|
||||
heights[py * targetSize + px] = h;
|
||||
if (h < minH) minH = h;
|
||||
if (h > maxH) maxH = h;
|
||||
@@ -161,6 +175,9 @@ public final class WorldMapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Kuwahara-Filter: lässt das Terrain wie gemalt wirken
|
||||
if (opts.showSplatColors()) { applyKuwahara(img, 3); }
|
||||
|
||||
// ── 3. Vektor-Overlays ────────────────────────────────────────────────
|
||||
Graphics2D gfx = img.createGraphics();
|
||||
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
@@ -171,10 +188,10 @@ public final class WorldMapRenderer {
|
||||
// ── Weißfüllung ───────────────────────────────────────────────────────
|
||||
for (int py = 0; py < targetSize; py++) {
|
||||
for (int px = 0; px < targetSize; px++) {
|
||||
if (heights[py * targetSize + px] < 0f) img.setRGB(px, py, 0xFFFFFFFF);
|
||||
if (heights[py * targetSize + px] < 0f) img.setRGB(px, py, WATER_COLOR);
|
||||
}
|
||||
}
|
||||
for (PlacedWater w : input.waters()) {
|
||||
for (PlacedWater w : model.waters()) {
|
||||
int[] xs = worldToPixels(w.pointsX(), targetSize);
|
||||
int[] ys = worldToPixels(w.pointsZ(), targetSize);
|
||||
Polygon poly = new Polygon(xs, ys, xs.length);
|
||||
@@ -186,7 +203,7 @@ public final class WorldMapRenderer {
|
||||
for (int py = y0; py <= y1; py++) {
|
||||
for (int px = x0; px <= x1; px++) {
|
||||
if (poly.contains(px, py) && heights[py * targetSize + px] < wh)
|
||||
img.setRGB(px, py, 0xFFFFFFFF);
|
||||
img.setRGB(px, py, WATER_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,7 +236,7 @@ public final class WorldMapRenderer {
|
||||
}
|
||||
}
|
||||
// Wasserflächen: nur wenn groß genug
|
||||
for (PlacedWater w : input.waters()) {
|
||||
for (PlacedWater w : model.waters()) {
|
||||
int[] xs = worldToPixels(w.pointsX(), targetSize);
|
||||
int[] ys = worldToPixels(w.pointsZ(), targetSize);
|
||||
Polygon poly = new Polygon(xs, ys, xs.length);
|
||||
@@ -250,7 +267,7 @@ public final class WorldMapRenderer {
|
||||
gfx.setColor(Color.BLACK);
|
||||
gfx.setStroke(new BasicStroke(3.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
|
||||
// Wasserflächen: Polygon-Umriss
|
||||
for (PlacedWater w : input.waters()) {
|
||||
for (PlacedWater w : model.waters()) {
|
||||
int[] xs = worldToPixels(w.pointsX(), targetSize);
|
||||
int[] ys = worldToPixels(w.pointsZ(), targetSize);
|
||||
gfx.drawPolygon(xs, ys, xs.length);
|
||||
@@ -293,7 +310,7 @@ public final class WorldMapRenderer {
|
||||
10f, new float[]{dash, gap}, 0f);
|
||||
int aFontSize = Math.max(8, targetSize / 160);
|
||||
gfx.setFont(new Font("SansSerif", Font.BOLD, aFontSize));
|
||||
for (PlacedArea a : input.areas()) {
|
||||
for (PlacedArea a : model.areas()) {
|
||||
int[] xs = worldToPixels(a.pointsX(), targetSize);
|
||||
int[] ys = worldToPixels(a.pointsZ(), targetSize);
|
||||
gfx.setStroke(dashed);
|
||||
@@ -314,7 +331,7 @@ public final class WorldMapRenderer {
|
||||
// Location-Zonen
|
||||
if (opts.showZones()) {
|
||||
gfx.setStroke(new BasicStroke(lineW));
|
||||
for (PlacedLocationZone z : input.zones()) {
|
||||
for (PlacedLocationZone z : model.zones()) {
|
||||
int[] xs = worldToPixels(z.pointsX(), targetSize);
|
||||
int[] ys = worldToPixels(z.pointsZ(), targetSize);
|
||||
gfx.setColor(new Color(240, 190, 40, 70));
|
||||
@@ -324,13 +341,13 @@ public final class WorldMapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Modell-Punkte
|
||||
if (opts.showModels()) {
|
||||
int dotR = Math.max(1, targetSize / 600);
|
||||
gfx.setColor(new Color(160, 80, 20, 200));
|
||||
for (PlacedModel model : input.models()) {
|
||||
int mx = worldToPixel(model.x(), targetSize);
|
||||
int mz = worldToPixel(model.z(), targetSize);
|
||||
for (PlacedModel pm : model.models()) {
|
||||
if (isTree(pm)) { continue; }
|
||||
int mx = worldToPixel(pm.x(), targetSize);
|
||||
int mz = worldToPixel(pm.z(), targetSize);
|
||||
gfx.fillRect(mx - dotR, mz - dotR, dotR * 2 + 1, dotR * 2 + 1);
|
||||
}
|
||||
}
|
||||
@@ -339,7 +356,7 @@ public final class WorldMapRenderer {
|
||||
if (opts.showLocations()) {
|
||||
int fontSize = Math.max(8, targetSize / 140);
|
||||
gfx.setFont(new Font("SansSerif", Font.BOLD, fontSize));
|
||||
for (Location loc : input.locations()) {
|
||||
for (Location loc : model.locations()) {
|
||||
if (!loc.isShowOnMap()) continue;
|
||||
if (loc.getId() == null || loc.getId().isEmpty()) continue;
|
||||
float wx = Float.isNaN(loc.getLabelX()) ? loc.getCenterX() : loc.getLabelX();
|
||||
@@ -354,6 +371,15 @@ public final class WorldMapRenderer {
|
||||
return img;
|
||||
}
|
||||
|
||||
/** Rückwärts-kompatibel: baut ein minimales Modell (ohne Overlay-Daten) und delegiert. */
|
||||
public static BufferedImage render(RenderInput input, int targetSize, RenderOptions opts) {
|
||||
return render(new WorldMapRenderModel(
|
||||
input.mapData(), input.areas(), input.zones(), input.locations(),
|
||||
input.waters(), input.models(), input.slotColorsRGB(),
|
||||
null, null, null, null
|
||||
), targetSize, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert einen rechteckigen Weltausschnitt als {@link BufferedImage}.
|
||||
* Koordinatenursprung und Skalierung passen sich dem Ausschnitt an,
|
||||
@@ -383,16 +409,21 @@ public final class WorldMapRenderer {
|
||||
float minH = 0f, maxH = 1f;
|
||||
|
||||
if (opts.showTerrain() || opts.showWater()) {
|
||||
int UV2 = MapData.UPPER_VERTS;
|
||||
heights = new float[targetSize * targetSize];
|
||||
minH = Float.MAX_VALUE;
|
||||
maxH = -Float.MAX_VALUE;
|
||||
for (int py = 0; py < targetSize; py++) {
|
||||
float wz = wz0 + (float) py / (targetSize - 1) * rSize;
|
||||
int hz = iclamp((int) ((wz + WORLD_HALF) / WORLD_SIZE * (TV - 1)), 0, TV - 1);
|
||||
int hz = iclamp((int) ((wz + WORLD_HALF) / WORLD_SIZE * (TV - 1)), 0, TV - 1);
|
||||
int uz = iclamp((int) ((wz + WORLD_HALF) / WORLD_SIZE * (UV2 - 1)), 0, UV2 - 1);
|
||||
for (int px = 0; px < targetSize; px++) {
|
||||
float wx = wx0 + (float) px / (targetSize - 1) * rSize;
|
||||
int hx = iclamp((int) ((wx + WORLD_HALF) / WORLD_SIZE * (TV - 1)), 0, TV - 1);
|
||||
int hx = iclamp((int) ((wx + WORLD_HALF) / WORLD_SIZE * (TV - 1)), 0, TV - 1);
|
||||
int ux = iclamp((int) ((wx + WORLD_HALF) / WORLD_SIZE * (UV2 - 1)), 0, UV2 - 1);
|
||||
float h = m.terrainHeight[hz * TV + hx];
|
||||
float upper = m.upperTop[uz * UV2 + ux];
|
||||
if (upper > 0f && upper > h) { h = upper; }
|
||||
heights[py * targetSize + px] = h;
|
||||
if (h < minH) { minH = h; }
|
||||
if (h > maxH) { maxH = h; }
|
||||
@@ -403,9 +434,9 @@ public final class WorldMapRenderer {
|
||||
// ── Terrain (optional) ────────────────────────────────────────────────
|
||||
if (opts.showTerrain()) {
|
||||
float heightRange = Math.max(0.01f, maxH - minH);
|
||||
int[] sR = slotChannel(input, 0);
|
||||
int[] sG = slotChannel(input, 1);
|
||||
int[] sB = slotChannel(input, 2);
|
||||
int[] sR = slotChannel(input.slotColorsRGB(), 0);
|
||||
int[] sG = slotChannel(input.slotColorsRGB(), 1);
|
||||
int[] sB = slotChannel(input.slotColorsRGB(), 2);
|
||||
|
||||
for (int py = 0; py < targetSize; py++) {
|
||||
for (int px = 0; px < targetSize; px++) {
|
||||
@@ -465,6 +496,8 @@ public final class WorldMapRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.showSplatColors()) { applyKuwahara(img, 3); }
|
||||
|
||||
// ── Vektor-Overlays ───────────────────────────────────────────────────
|
||||
Graphics2D gfx = img.createGraphics();
|
||||
gfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
@@ -474,7 +507,7 @@ public final class WorldMapRenderer {
|
||||
// ── Weißfüllung ───────────────────────────────────────────────────────
|
||||
for (int py = 0; py < targetSize; py++) {
|
||||
for (int px = 0; px < targetSize; px++) {
|
||||
if (heights[py * targetSize + px] < 0f) img.setRGB(px, py, 0xFFFFFFFF);
|
||||
if (heights[py * targetSize + px] < 0f) img.setRGB(px, py, WATER_COLOR);
|
||||
}
|
||||
}
|
||||
for (PlacedWater w : input.waters()) {
|
||||
@@ -489,7 +522,7 @@ public final class WorldMapRenderer {
|
||||
for (int py = y0; py <= y1; py++) {
|
||||
for (int px = x0; px <= x1; px++) {
|
||||
if (poly.contains(px, py) && heights[py * targetSize + px] < wh)
|
||||
img.setRGB(px, py, 0xFFFFFFFF);
|
||||
img.setRGB(px, py, WATER_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,6 +662,7 @@ public final class WorldMapRenderer {
|
||||
int dotR = Math.max(1, targetSize / 600);
|
||||
gfx.setColor(new Color(160, 80, 20, 200));
|
||||
for (PlacedModel model : input.models()) {
|
||||
if (isTree(model)) { continue; }
|
||||
int mx = wrp1(model.x(), wx0, rSize, targetSize);
|
||||
int mz = wrp1(model.z(), wz0, rSize, targetSize);
|
||||
gfx.fillRect(mx - dotR, mz - dotR, dotR * 2 + 1, dotR * 2 + 1);
|
||||
@@ -680,13 +714,12 @@ public final class WorldMapRenderer {
|
||||
|
||||
// Gibt den R-, G- oder B-Kanal (channel=0/1/2) aller 8 Splatmap-Slots zurück.
|
||||
// slotColorsRGB: 24 Werte (8 Slots × 3), 12 Werte (4 Slots, Upper-Layer = Defaults) oder null.
|
||||
private static int[] slotChannel(RenderInput input, int channel) {
|
||||
int[] rgb = input.slotColorsRGB();
|
||||
private static int[] slotChannel(int[] slotColorsRGB, int channel) {
|
||||
int[] def = channel == 0 ? DEF_SLOT_R : (channel == 1 ? DEF_SLOT_G : DEF_SLOT_B);
|
||||
if (rgb == null || rgb.length < 12) { return def; }
|
||||
if (slotColorsRGB == null || slotColorsRGB.length < 12) { return def; }
|
||||
int[] out = new int[8];
|
||||
for (int s = 0; s < 8; s++) {
|
||||
out[s] = (rgb.length >= (s + 1) * 3) ? rgb[s * 3 + channel] : def[s];
|
||||
out[s] = (slotColorsRGB.length >= (s + 1) * 3) ? slotColorsRGB[s * 3 + channel] : def[s];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -802,4 +835,53 @@ public final class WorldMapRenderer {
|
||||
float v01 = (arr[i01] & 0xFF) / 255f, v11 = (arr[i11] & 0xFF) / 255f;
|
||||
return (v00*(1-tx) + v10*tx)*(1-tz) + (v01*(1-tx) + v11*tx)*tz;
|
||||
}
|
||||
|
||||
public static boolean isTree(PlacedModel m) {
|
||||
return m.modelPath().replace('\\', '/').toLowerCase().contains("/trees/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Kuwahara-Filter: Für jeden Pixel das Quadranten-Fenster mit der kleinsten
|
||||
* Varianz wählen und dessen Mittelwert setzen → Ölgemälde-/gemalt-Effekt.
|
||||
* Radius r=3 → Fenster 7×7, 4 Quadranten je 4×4.
|
||||
*/
|
||||
private static void applyKuwahara(BufferedImage img, int r) {
|
||||
int w = img.getWidth(), h = img.getHeight();
|
||||
int[] src = img.getRGB(0, 0, w, h, null, 0, w);
|
||||
int[] dst = new int[src.length];
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
float bestVar = Float.MAX_VALUE;
|
||||
int bestPacked = src[y * w + x];
|
||||
|
||||
// 4 Quadranten: [xOff0..xOff1] × [yOff0..yOff1]
|
||||
int[][] quads = {{-r,-r,0,0},{0,-r,r,0},{-r,0,0,r},{0,0,r,r}};
|
||||
for (int[] q : quads) {
|
||||
int x0 = Math.max(0, x+q[0]), y0 = Math.max(0, y+q[1]);
|
||||
int x1 = Math.min(w-1, x+q[2]), y1 = Math.min(h-1, y+q[3]);
|
||||
float sumR=0,sumG=0,sumB=0, sum2R=0,sum2G=0,sum2B=0;
|
||||
int cnt = 0;
|
||||
for (int qy = y0; qy <= y1; qy++) {
|
||||
for (int qx = x0; qx <= x1; qx++) {
|
||||
int p = src[qy * w + qx];
|
||||
float pr = (p>>16)&0xFF, pg = (p>>8)&0xFF, pb = p&0xFF;
|
||||
sumR+=pr; sumG+=pg; sumB+=pb;
|
||||
sum2R+=pr*pr; sum2G+=pg*pg; sum2B+=pb*pb;
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
float inv = 1f / cnt;
|
||||
float mR=sumR*inv, mG=sumG*inv, mB=sumB*inv;
|
||||
float var = (sum2R*inv - mR*mR) + (sum2G*inv - mG*mG) + (sum2B*inv - mB*mB);
|
||||
if (var < bestVar) {
|
||||
bestVar = var;
|
||||
bestPacked = (clamp((int)mR)<<16) | (clamp((int)mG)<<8) | clamp((int)mB);
|
||||
}
|
||||
}
|
||||
dst[y * w + x] = bestPacked | 0xFF000000;
|
||||
}
|
||||
}
|
||||
img.setRGB(0, 0, w, h, dst, 0, w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5639,11 +5639,36 @@ public class EditorApp extends Application {
|
||||
});
|
||||
|
||||
worldMapView = new de.blight.editor.ui.WorldMapView(() -> primaryStage);
|
||||
Tab weltkartTab = new Tab("Weltkarte", worldMapView);
|
||||
worldMapView.setVoxelChunkSupplier(() -> {
|
||||
de.blight.editor.state.VoxelEditorState ves = jmeApp == null ? null
|
||||
: jmeApp.getStateManager().getState(de.blight.editor.state.VoxelEditorState.class);
|
||||
return ves != null ? ves.getChunksSnapshot() : de.blight.common.VoxelChunkIO.loadAll();
|
||||
});
|
||||
worldMapView.setCameraInfoSupplier(() ->
|
||||
new de.blight.editor.ui.WorldMapView.CameraInfo(
|
||||
input.camX, input.camY, input.camZ, input.camYaw));
|
||||
worldMapView.setTeleportCallback(pos -> {
|
||||
input.pendingGotoX = pos[0];
|
||||
input.pendingGotoY = pos[1];
|
||||
input.pendingGotoZ = pos[2];
|
||||
});
|
||||
|
||||
final Tab weltkartTab = new Tab("Weltkarte", worldMapView);
|
||||
weltkartTab.setClosable(false);
|
||||
weltkartTab.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
|
||||
if (isSelected && !worldMapView.isLoaded()) worldMapView.loadAndRender();
|
||||
});
|
||||
worldMapView.setFullscreenCallbacks(
|
||||
() -> {
|
||||
// Erst aus Tab lösen, dann in centerStack einsetzen
|
||||
weltkartTab.setContent(new javafx.scene.control.Label(""));
|
||||
setCenterView(worldMapView);
|
||||
},
|
||||
() -> {
|
||||
// Erst aus centerStack lösen (worldViewport zurück), dann in Tab setzen
|
||||
setCenterView(worldViewport);
|
||||
weltkartTab.setContent(worldMapView);
|
||||
});
|
||||
|
||||
TabPane tabPane = new TabPane(assetsTab, karteTab, weltkartTab);
|
||||
tabPane.setStyle("-fx-background-color: #e8e8e8;");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,9 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- Karten-Rendering: Modell-Build und PNG-Render auf DEBUG -->
|
||||
<logger name="de.blight.editor.ui.WorldMapView" level="DEBUG"/>
|
||||
|
||||
<!-- JME-interne JUL-Logs auf WARN reduzieren -->
|
||||
<logger name="com.jme3" level="WARN"/>
|
||||
<!-- GltfLoader meldet bei jeder Animation "only supports linear interpolation" – bekanntes JME-Verhalten, kein Fehler -->
|
||||
|
||||
@@ -533,6 +533,8 @@ public class MinimapState extends BaseAppState {
|
||||
Files.createDirectories(dir);
|
||||
Path png = dir.resolve("minimap_world.png");
|
||||
|
||||
log.debug("[Minimap] Lade Weltdaten für Render-Modell…");
|
||||
long t0 = System.currentTimeMillis();
|
||||
MapData mapData = MapIO.load();
|
||||
List<PlacedArea> areas = AreaIO.load();
|
||||
List<PlacedLocationZone> zones = LocationZoneIO.load();
|
||||
@@ -541,6 +543,8 @@ public class MinimapState extends BaseAppState {
|
||||
List<PlacedModel> models = PlacedModelIO.load();
|
||||
int[] slotColors = computeSlotColors(mapData, root);
|
||||
renderInput = new RenderInput(mapData, areas, zones, locs, waters, models, slotColors);
|
||||
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) {
|
||||
@@ -553,9 +557,11 @@ public class MinimapState extends BaseAppState {
|
||||
}
|
||||
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.info("[Minimap] Weltkarte gespeichert: {}", png);
|
||||
log.debug("[Minimap] Weltkarte gerendert und gespeichert ({} ms): {}",
|
||||
System.currentTimeMillis() - t1, png);
|
||||
} else {
|
||||
log.info("[Minimap] Gecachte Weltkarte: {}", png);
|
||||
}
|
||||
@@ -645,8 +651,12 @@ public class MinimapState extends BaseAppState {
|
||||
final float capHU = vr / WORLD_SIZE;
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
log.debug("[Minimap] Vektor-Layer neu rendern – Zentrum ({}/{}), Radius {}",
|
||||
Math.round(wx), Math.round(wz), Math.round(vr));
|
||||
long t0 = System.currentTimeMillis();
|
||||
RenderOptions opts = new RenderOptions(false, false, true, true, true, true, true);
|
||||
BufferedImage bi = WorldMapRenderer.renderRegion(renderInput, VEC_TEX_SIZE, opts, wx, wz, vr);
|
||||
log.debug("[Minimap] Vektor-Layer fertig ({} ms)", System.currentTimeMillis() - t0);
|
||||
app.enqueue(() -> {
|
||||
updateVectorTexture(bi);
|
||||
vecRenderU = capU;
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
<!-- LOD-Slot-Wechsel auf DEBUG aktivieren -->
|
||||
<logger name="de.blight.game.state.ModelLodControl" level="DEBUG"/>
|
||||
<!-- Minimap: Modell-Build und Render-Zyklen auf DEBUG -->
|
||||
<logger name="de.blight.game.state.MinimapState" level="DEBUG"/>
|
||||
|
||||
<!-- JME-interne JUL-Logs auf WARN reduzieren -->
|
||||
<logger name="com.jme3" level="WARN"/>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "b26f42df-7897-4840-9e7f-5f35329aa0a6",
|
||||
"benchType": "Simple",
|
||||
"sitzX": 0.30349,
|
||||
"sitzY": 2.5,
|
||||
"sitzZ": -5.00573,
|
||||
"sitzRotY": 1.5707964,
|
||||
"sitzSet": true
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -11,3 +11,5 @@ Models/trees/palm/palm_20260816_213341.j3o 270.73062 3.06689 -913.67090 -1.45828
|
||||
Models/imported/bank1.j3o 236.63928 -6.31074 -888.17450 -3.22597 1.00000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000 BENCH 9ac9943d-0e12-4d5c-8323-0e2b92eebdec
|
||||
Models/trees/willow/willow_20260823_101850.j3o 152.40488 11.49070 -888.86847 0.00000 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
|
||||
Models/trees/willow/willow_20260823_101856.j3o 170.68971 11.48961 -873.92236 0.00000 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
|
||||
Models/imported/wolf.j3o -1.25617 2.00000 -1.86424 0.00000 1.00000 0.00000 0.00000 false true true 30.00000 80.00000 120.00000
|
||||
Models/imported/bank1.j3o 0.30349 2.00000 -5.00573 0.00000 1.00000 0.00000 0.00000 true true true 30.00000 80.00000 120.00000 BENCH b26f42df-7897-4840-9e7f-5f35329aa0a6
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user