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

@@ -563,8 +563,15 @@ public class EditorApp extends Application {
javafx.animation.Timeline[] pollerRef = new javafx.animation.Timeline[1];
pollerRef[0] = new javafx.animation.Timeline(
new javafx.animation.KeyFrame(javafx.util.Duration.millis(100), ev -> {
splashStatus.setText(input.loadingStatus);
if (input.jmeReady) {
boolean jmeOk = input.jmeReady;
boolean mapOk = worldMapView != null && worldMapView.isMapReady();
if (!jmeOk) {
splashStatus.setText(input.loadingStatus);
} else {
splashStatus.setText("Karte: " + (worldMapView != null
? worldMapView.getLoadingStatus() : "…"));
}
if (jmeOk && mapOk) {
pollerRef[0].stop();
stage.show();
restoreEditorPrefs();
@@ -5646,30 +5653,34 @@ public class EditorApp extends Application {
if (isSelected) mapObjectsView.refresh();
});
worldMapView = new de.blight.editor.ui.WorldMapView(() -> primaryStage);
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];
});
if (worldMapView == null) {
worldMapView = new de.blight.editor.ui.WorldMapView(() -> primaryStage);
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];
});
worldMapView.setRefreshAction(() -> {
input.saveRequested = true;
pendingAfterSave = worldMapView::loadAndRender;
setStatus("Speichere vor Weltkarten-Refresh…");
});
worldMapView.loadAndRender(); // Vorladen beim Start, nicht erst beim Tab-Öffnen
}
weltkartTab = new Tab("Weltkarte", worldMapView);
weltkartTab.setClosable(false);
weltkartTab.selectedProperty().addListener((obs, wasSelected, isSelected) -> {
if (isSelected && !worldMapView.isLoaded()) worldMapView.loadAndRender();
});
worldMapView.setRefreshAction(() -> {
input.saveRequested = true;
pendingAfterSave = worldMapView::loadAndRender;
setStatus("Speichere vor Weltkarten-Refresh…");
else if (isSelected) worldMapView.ensureFitToView();
});
worldMapView.setFullscreenCallbacks(
() -> {

View File

@@ -127,6 +127,7 @@ public class WorldMapView extends VBox {
};
private final java.util.Set<Integer> chunksLoading =
java.util.Collections.synchronizedSet(new java.util.HashSet<>());
private volatile boolean chunkRedrawPending = false;
private WritableImage bakedForestOverlay = null;
// Wasser-Fill-Overlay entfernt: ocean + PlacedWater sind bereits in mapFxImage korrekt gerendert
@@ -326,7 +327,16 @@ public class WorldMapView extends VBox {
// ── Öffentliche API ───────────────────────────────────────────────────────
public boolean isLoaded() { return mapFxImage != null || loading.get(); }
public boolean isLoaded() { return mapFxImage != null || loading.get(); }
public boolean isMapReady() { return mapFxImage != null; }
public String getLoadingStatus() { return statusLbl.getText(); }
/** Stellt sicher, dass die Karte beim ersten Anzeigen korrekt skaliert/positioniert ist. */
public void ensureFitToView() {
if (mapFxImage != null && zoomLevels == null) {
fitCanvas();
}
}
/** Lädt alle Weltdaten neu und baut das Render-Modell (teuer: I/O + Berechnungen). */
public void loadAndRender() {
@@ -377,7 +387,8 @@ public class WorldMapView extends VBox {
Platform.runLater(() -> statusLbl.setText("Generiere Hires-Chunks…"));
WorldMapRenderer.buildAndSaveHiresChunks(
chunkModel.mapData(), chunkModel.slotColorsRGB(), chunkModel.mapTilePixels(),
chunkModel.voxelSurface(), chunkModel.waters(), CHUNK_CACHE_DIR, true);
chunkModel.voxelSurface(), chunkModel.waters(), chunkModel.waterfalls(),
CHUNK_CACHE_DIR, true);
chunkCache.clear();
Platform.runLater(() -> {
@@ -498,9 +509,14 @@ public class WorldMapView extends VBox {
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);
// Fallback: lowres-Ausschnitt zeigen bis Hires-Chunk geladen ist
if (mapFxImage != null) {
double srcX = (double) cx * RENDER_SIZE / COLS;
double srcY = (double) cy * RENDER_SIZE / COLS;
double srcW = (double) RENDER_SIZE / COLS;
gc.drawImage(mapFxImage, srcX, srcY, srcW, srcW,
canvasLeft, canvasTop, chunkCanvasW, chunkCanvasH);
}
if (chunksLoading.add(key)) {
final int fcx = cx, fcy = cy, fkey = key;
Thread lt = new Thread(() -> {
@@ -512,7 +528,7 @@ public class WorldMapView extends VBox {
Platform.runLater(() -> {
chunkCache.put(fkey, wi);
chunksLoading.remove(fkey);
redraw();
scheduleChunkRedraw();
});
} else {
chunksLoading.remove(fkey);
@@ -526,6 +542,17 @@ public class WorldMapView extends VBox {
}
}
/** Bündelt Redraws nach Chunk-Ladevorgängen: maximal ein Redraw pro FX-Puls. */
private void scheduleChunkRedraw() {
if (!chunkRedrawPending) {
chunkRedrawPending = true;
Platform.runLater(() -> {
chunkRedrawPending = false;
redraw();
});
}
}
// ── Canvas Tile-Overlay (zoom-unabhängige Kacheln) ────────────────────────
private static int tileSample(int[][] tilePx, int cat,
@@ -916,7 +943,7 @@ public class WorldMapView extends VBox {
List<PlacedModel> treeList = models.stream()
.filter(WorldMapRenderer::isTree).collect(Collectors.toList());
List<List<PlacedModel>> tClusters = clusterTreeModels(treeList, 40f);
int[] colorMap = WorldMapRenderer.buildTerrainColorMap(mapData, slotColors, mapTiles, voxelSurface, waters);
int[] colorMap = WorldMapRenderer.buildTerrainColorMap(mapData, slotColors, mapTiles, voxelSurface, waters, waterfalls);
return new WorldMapRenderModel(
mapData, areas, zones, locs, waters, models, waterfalls, slotColors,
mask, voxelSurface, tSamp, paths, tClusters, mapTiles, colorMap);
@@ -924,8 +951,8 @@ public class WorldMapView extends VBox {
// Erzeugt interpolierte Marching-Squares-Segmente für Meer + Wasserflächen.
// Endpunkte liegen am echten Höhen-Nulldurchgang → keine Treppenstufen auf Diagonalen.
private static float[][] buildCoastSegsMS(MapData mapData, List<PlacedWater> waters, int size,
float[] voxelSurface) {
private static float[][] buildCoastSegsMS(MapData mapData, List<PlacedWater> waters,
int size, float[] voxelSurface) {
int TV = MapData.TERRAIN_VERTS;
int VS = WorldMapRenderer.VOXEL_SURFACE_RES;
float[] h = new float[size * size];