Karte: 3 Korrekturen nach User-Feedback
- Weltkarte: Cave-Overlay (brown fill + Kontur) vollständig entfernt, war nie gewünscht; drawTunnelOverlay tut nun nichts - Pfad-Detection: Cave-Maske als Filter entfernt; Grund: MIN_FOOTPRINT_ Filter in buildCaveMasks (≥40 Pixel bei 1024px) schmeißt schmale Tunnel raus und würde Tunnel-Segmente blocken. Stattdessen reiner Y-Check: voxelSurface > pathY + 3m (Tunneldecke direkt über dem Weg) - Höhlenkarte: Pixel-Bild ersetzt durch Vektor-Füllung (gc.fill auf tunnelContourPaths) → bleibt bei jedem Zoom-Level scharf Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -381,15 +381,30 @@ public class CaveMapView extends VBox {
|
|||||||
}
|
}
|
||||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||||
double w = canvas.getWidth(), h = canvas.getHeight();
|
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);
|
gc.fillRect(0, 0, w, h);
|
||||||
if (mapFxImage == null) {
|
if (mapFxImage == null) {
|
||||||
gc.setFill(Color.GRAY);
|
gc.setFill(Color.GRAY);
|
||||||
gc.fillText("Höhlenkarte wird geladen…", 20, 40);
|
gc.fillText("Höhlenkarte wird geladen…", 20, 40);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 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.setImageSmoothing(true);
|
||||||
gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale);
|
gc.drawImage(mapFxImage, panX, panY, mapFxImage.getWidth() * scale, mapFxImage.getHeight() * scale);
|
||||||
|
}
|
||||||
if (cbEntrances.isSelected()) {
|
if (cbEntrances.isSelected()) {
|
||||||
drawTunnelContourOverlay(gc);
|
drawTunnelContourOverlay(gc);
|
||||||
drawTunnelEntrancesOverlay(gc);
|
drawTunnelEntrancesOverlay(gc);
|
||||||
|
|||||||
@@ -127,8 +127,6 @@ public class WorldMapView extends VBox {
|
|||||||
|
|
||||||
private List<float[]> buildingFootprints = new ArrayList<>();
|
private List<float[]> buildingFootprints = new ArrayList<>();
|
||||||
private List<float[][]> cachedPathChains = new ArrayList<>();
|
private List<float[][]> cachedPathChains = new ArrayList<>();
|
||||||
private List<float[][]> tunnelPaths = new ArrayList<>();
|
|
||||||
private WritableImage bakedTunnelOverlay = null;
|
|
||||||
private static final java.util.concurrent.ConcurrentHashMap<String, float[]> MODEL_BOUNDS_CACHE
|
private static final java.util.concurrent.ConcurrentHashMap<String, float[]> MODEL_BOUNDS_CACHE
|
||||||
= new java.util.concurrent.ConcurrentHashMap<>();
|
= new java.util.concurrent.ConcurrentHashMap<>();
|
||||||
private static final float[] DEFAULT_EXTENTS = {5f, 5f};
|
private static final float[] DEFAULT_EXTENTS = {5f, 5f};
|
||||||
@@ -411,9 +409,6 @@ public class WorldMapView extends VBox {
|
|||||||
List<float[]> newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false);
|
List<float[]> newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false);
|
||||||
List<PlacedArea> surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false);
|
List<PlacedArea> surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false);
|
||||||
|
|
||||||
List<float[][]> newTunnelPaths = buildTunnelContourPaths(caveMask);
|
|
||||||
int[] tunnelFillPx = buildTunnelFillPixels(caveMask);
|
|
||||||
|
|
||||||
// Hires-Chunks im Hintergrund generieren (nach dem UI-Update, nicht-blockierend)
|
// Hires-Chunks im Hintergrund generieren (nach dem UI-Update, nicht-blockierend)
|
||||||
final WorldMapRenderModel chunkModel = model;
|
final WorldMapRenderModel chunkModel = model;
|
||||||
Platform.runLater(() -> statusLbl.setText("Generiere Hires-Chunks…"));
|
Platform.runLater(() -> statusLbl.setText("Generiere Hires-Chunks…"));
|
||||||
@@ -435,8 +430,6 @@ public class WorldMapView extends VBox {
|
|||||||
forestPaths = newForestPaths;
|
forestPaths = newForestPaths;
|
||||||
buildingFootprints = newBuildingFootprints;
|
buildingFootprints = newBuildingFootprints;
|
||||||
cachedPathChains = newPathChains;
|
cachedPathChains = newPathChains;
|
||||||
tunnelPaths = newTunnelPaths;
|
|
||||||
bakedTunnelOverlay = toWritableImage(tunnelFillPx, RENDER_SIZE);
|
|
||||||
fitToView();
|
fitToView();
|
||||||
redraw();
|
redraw();
|
||||||
progress.setVisible(false);
|
progress.setVisible(false);
|
||||||
@@ -1046,25 +1039,7 @@ public class WorldMapView extends VBox {
|
|||||||
result.add(new float[]{wx, wz, dirX, dirZ, radius});
|
result.add(new float[]{wx, wz, dirX, dirZ, radius});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawTunnelOverlay(GraphicsContext gc) {
|
private void drawTunnelOverlay(GraphicsContext gc) { /* Kein Tunnel-Overlay auf Weltkarte */ }
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<float[][]> buildTunnelContourPaths(boolean[] caveMask) {
|
static List<float[][]> buildTunnelContourPaths(boolean[] caveMask) {
|
||||||
final int SIZE = CAVE_MASK_SIZE;
|
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());
|
return paths.stream().filter(p -> pathPerimeterSq(p) >= MIN_PERI_SQ).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int[] buildTunnelFillPixels(boolean[] caveMask) {
|
private static List<float[]> computeBuildingFootprints(WorldMapRenderModel model, Path assetRoot) {
|
||||||
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<float[]> computeBuildingFootprints(WorldMapRenderModel model, Path assetRoot) {
|
|
||||||
List<float[]> result = new ArrayList<>();
|
List<float[]> result = new ArrayList<>();
|
||||||
for (PlacedModel pm : model.models()) {
|
for (PlacedModel pm : model.models()) {
|
||||||
if (!isBuilding(pm)) continue;
|
if (!isBuilding(pm)) continue;
|
||||||
@@ -1152,16 +1112,16 @@ public class WorldMapView extends VBox {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Prüft ob ein Knoten unter der Erde liegt.
|
* Prüft ob ein Knoten unter der Erde liegt.
|
||||||
* Bedingung: Position liegt in einem Tunnel-Chunk-Bereich (Cave-Maske)
|
* Bedingung: Das höchste Solid-Voxel in dieser X,Z-Spalte liegt ≥ PATH_UNDERGROUND_CLEARANCE
|
||||||
* UND das höchste Solid in dieser Spalte ist ≥ PATH_UNDERGROUND_CLEARANCE über der Knoten-Y.
|
* ü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;
|
if (node.length < 3) return false;
|
||||||
float wy = node[2];
|
float wy = node[2];
|
||||||
if (Float.isNaN(wy)) return false;
|
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;
|
int VS = WorldMapRenderer.VOXEL_SURFACE_RES;
|
||||||
float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE;
|
float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE;
|
||||||
int px = Math.round((node[0] + WH) / WS * (VS - 1));
|
int px = Math.round((node[0] + WH) / WS * (VS - 1));
|
||||||
|
|||||||
Reference in New Issue
Block a user