diff --git a/blight-editor/src/main/java/de/blight/editor/ui/CaveMapView.java b/blight-editor/src/main/java/de/blight/editor/ui/CaveMapView.java index 39dcbe4..74fd78f 100644 --- a/blight-editor/src/main/java/de/blight/editor/ui/CaveMapView.java +++ b/blight-editor/src/main/java/de/blight/editor/ui/CaveMapView.java @@ -381,15 +381,30 @@ public class CaveMapView extends VBox { } GraphicsContext gc = canvas.getGraphicsContext2D(); double w = canvas.getWidth(), h = canvas.getHeight(); - gc.setFill(Color.rgb(14, 14, 14)); + gc.setFill(Color.rgb(28, 28, 28)); // passt zu buildCaveMap-Hintergrund 0x1c1c1c gc.fillRect(0, 0, w, h); if (mapFxImage == null) { gc.setFill(Color.GRAY); gc.fillText("Höhlenkarte wird geladen…", 20, 40); return; } - gc.setImageSmoothing(true); - gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale); + // Vektor-Füllung: scharf bei jedem Zoom-Level + if (!tunnelContourPaths.isEmpty()) { + gc.save(); + gc.setFill(Color.rgb(182, 138, 80)); // Dirt-Farbe 0xb68a50 + for (float[][] path : tunnelContourPaths) { + gc.beginPath(); + gc.moveTo(toCanvasX(path[0][0]), toCanvasZ(path[0][1])); + for (int i = 1; i < path.length; i++) gc.lineTo(toCanvasX(path[i][0]), toCanvasZ(path[i][1])); + gc.closePath(); + gc.fill(); + } + gc.restore(); + } else { + // Pixel-Fallback, falls Konturpfade noch nicht berechnet + gc.setImageSmoothing(true); + gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale); + } if (cbEntrances.isSelected()) { drawTunnelContourOverlay(gc); drawTunnelEntrancesOverlay(gc); 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 9e5011d..67a58a2 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 @@ -127,8 +127,6 @@ public class WorldMapView extends VBox { private List buildingFootprints = new ArrayList<>(); private List cachedPathChains = new ArrayList<>(); - private List tunnelPaths = new ArrayList<>(); - private WritableImage bakedTunnelOverlay = null; private static final java.util.concurrent.ConcurrentHashMap MODEL_BOUNDS_CACHE = new java.util.concurrent.ConcurrentHashMap<>(); private static final float[] DEFAULT_EXTENTS = {5f, 5f}; @@ -411,9 +409,6 @@ public class WorldMapView extends VBox { List newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false); List surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false); - List newTunnelPaths = buildTunnelContourPaths(caveMask); - int[] tunnelFillPx = buildTunnelFillPixels(caveMask); - // Hires-Chunks im Hintergrund generieren (nach dem UI-Update, nicht-blockierend) final WorldMapRenderModel chunkModel = model; Platform.runLater(() -> statusLbl.setText("Generiere Hires-Chunks…")); @@ -435,8 +430,6 @@ public class WorldMapView extends VBox { forestPaths = newForestPaths; buildingFootprints = newBuildingFootprints; cachedPathChains = newPathChains; - tunnelPaths = newTunnelPaths; - bakedTunnelOverlay = toWritableImage(tunnelFillPx, RENDER_SIZE); fitToView(); redraw(); progress.setVisible(false); @@ -1046,25 +1039,7 @@ public class WorldMapView extends VBox { result.add(new float[]{wx, wz, dirX, dirZ, radius}); } - private void drawTunnelOverlay(GraphicsContext gc) { - double cW = canvas.getWidth(), cH = canvas.getHeight(); - if (bakedTunnelOverlay != null) { - gc.save(); - gc.setImageSmoothing(false); - gc.drawImage(bakedTunnelOverlay, -panX / scale, -panY / scale, cW / scale, cH / scale, 0, 0, cW, cH); - gc.restore(); - } - if (!tunnelPaths.isEmpty()) { - gc.save(); - gc.setStroke(Color.rgb(40, 22, 5, 0.90)); - gc.setLineWidth(overlayLineWidth() + 0.5); - gc.setLineDashes(); - gc.setLineCap(StrokeLineCap.ROUND); - gc.setLineJoin(StrokeLineJoin.ROUND); - for (float[][] path : tunnelPaths) drawCoastPath(gc, path, cW, cH, 8); - gc.restore(); - } - } + private void drawTunnelOverlay(GraphicsContext gc) { /* Kein Tunnel-Overlay auf Weltkarte */ } static List buildTunnelContourPaths(boolean[] caveMask) { final int SIZE = CAVE_MASK_SIZE; @@ -1077,22 +1052,7 @@ public class WorldMapView extends VBox { return paths.stream().filter(p -> pathPerimeterSq(p) >= MIN_PERI_SQ).collect(Collectors.toList()); } - private static int[] buildTunnelFillPixels(boolean[] caveMask) { - final int SIZE = CAVE_MASK_SIZE; - final int OUT = RENDER_SIZE; - final int FILL = 0x60_3D2208; // 37% alpha, very dark earthy brown - int[] px = new int[OUT * OUT]; - for (int py = 0; py < OUT; py++) { - int mz = py * SIZE / OUT; - for (int bx = 0; bx < OUT; bx++) { - int mx = bx * SIZE / OUT; - if (caveMask[mz * SIZE + mx]) px[py * OUT + bx] = FILL; - } - } - return px; - } - - private static List computeBuildingFootprints(WorldMapRenderModel model, Path assetRoot) { +private static List computeBuildingFootprints(WorldMapRenderModel model, Path assetRoot) { List result = new ArrayList<>(); for (PlacedModel pm : model.models()) { if (!isBuilding(pm)) continue; @@ -1152,16 +1112,16 @@ public class WorldMapView extends VBox { /** * Prüft ob ein Knoten unter der Erde liegt. - * Bedingung: Position liegt in einem Tunnel-Chunk-Bereich (Cave-Maske) - * UND das höchste Solid in dieser Spalte ist ≥ PATH_UNDERGROUND_CLEARANCE über der Knoten-Y. + * Bedingung: Das höchste Solid-Voxel in dieser X,Z-Spalte liegt ≥ PATH_UNDERGROUND_CLEARANCE + * über der Knoten-Y — d.h. es gibt Material direkt über dem Weg (Tunneldecke). + * Die Cave-Maske wird bewusst NICHT genutzt: ihr MIN_FOOTPRINT_Filter bei niedrige Auflösung + * filtert schmale Tunnel heraus und würde echte Tunnel-Segmente blockieren. */ - private static boolean isNodeUnderground(float[] node, float[] voxelSurface, boolean[] caveMask) { + private static boolean isNodeUnderground(float[] node, float[] voxelSurface, + @SuppressWarnings("unused") boolean[] caveMask) { if (node.length < 3) return false; float wy = node[2]; if (Float.isNaN(wy)) return false; - // Cave-Maske (Chunk-Level): nur Bereiche nahe Tunneln prüfen - if (caveMask != null && !isCavePoint(caveMask, node[0], node[1])) return false; - // Y-Prüfung: Knoten muss unter festem Voxel-Material liegen int VS = WorldMapRenderer.VOXEL_SURFACE_RES; float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE; int px = Math.round((node[0] + WH) / WS * (VS - 1));