Weltkarte Wald: Outline-Versatz durch Koordinaten-Mismatch behoben

Root cause: Fill nutzt fsfz = (WH-wz)/WS * FD (Skala: /FD=512),
MS-Grid nutzt msW → / (fdSize-1)=511. Ergibt ~2m Versatz in Kartenmitte.

Fix: h-Feld bei SEA_MASK_SIZE mit exakt der Fill-Formel aufbauen
(* FD statt * (FD-1)), dann MS mit thr=0 auf diesem h-Feld.
Die Isokontur liegt damit pixelgenau auf der Fill-Grenze.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 18:46:23 +02:00
parent cf11a50a21
commit 3feff60e24

View File

@@ -1126,19 +1126,29 @@ public class WorldMapView extends VBox {
/** Extrahiert Wald-Konturen aus der Tree-Density-Map als geglättete Weltkoordinaten-Pfade. */ /** Extrahiert Wald-Konturen aus der Tree-Density-Map als geglättete Weltkoordinaten-Pfade. */
private static List<float[][]> buildForestOutlinePaths(float[] density, int fdSize, float threshold) { private static List<float[][]> buildForestOutlinePaths(float[] density, int fdSize, float threshold) {
// Z-Flip: Density mz=0=Nord, MS mz=0=Süd. // h-Feld mit EXAKT derselben Koordinatenformel wie computeForestPixels aufbauen:
// h = threshold - density → forest (density > threshold) ergibt h < 0 = "inside" für MS. // fsfz = (WH - wz) / WS * FD (Fill nutzt * FD=512, nicht * (FD-1)=511!)
// MS läuft direkt auf echten Density-Werten mit thr=0 → exakt dieselbe Isokontur wie // Damit liegt die MS-Isokontur (h=0) pixelgenau auf der Fill-Grenze.
// die Füllung in computeForestPixels (keine binäre Maske, kein Boundary-Versatz). final int SIZE = SEA_MASK_SIZE;
float[] h = new float[fdSize * fdSize]; final float WH = WorldMapRenderer.WORLD_HALF, WS = WorldMapRenderer.WORLD_SIZE;
for (int mz = 0; mz < fdSize; mz++) { float[] h = new float[SIZE * SIZE];
int dz = fdSize - 1 - mz; for (int py = 0; py < SIZE; py++) {
for (int mx = 0; mx < fdSize; mx++) { float wz = (float) py / (SIZE - 1) * WS - WH;
h[mz * fdSize + mx] = threshold - density[dz * fdSize + mx]; float fsfz = Math.max(0f, Math.min(fdSize - 1.001f, (WH - wz) / WS * fdSize));
int fsz0 = (int) fsfz, fsz1 = Math.min(fsz0 + 1, fdSize - 1);
float fstz = fsfz - fsz0;
for (int px = 0; px < SIZE; px++) {
float wx = (float) px / (SIZE - 1) * WS - WH;
float fsfx = Math.max(0f, Math.min(fdSize - 1.001f, (wx + WH) / WS * fdSize));
int fsx0 = (int) fsfx, fsx1 = Math.min(fsx0 + 1, fdSize - 1);
float fstx = fsfx - fsx0;
float d = (density[fsz0 * fdSize + fsx0] * (1 - fstx) + density[fsz0 * fdSize + fsx1] * fstx) * (1 - fstz)
+ (density[fsz1 * fdSize + fsx0] * (1 - fstx) + density[fsz1 * fdSize + fsx1] * fstx) * fstz;
h[py * SIZE + px] = threshold - d; // <0 = forest (inside), >0 = non-forest
} }
} }
List<float[]> segs = new ArrayList<>(); List<float[]> segs = new ArrayList<>();
marchingSquares(h, fdSize, 0f, null, null, segs); marchingSquares(h, SIZE, 0f, null, null, segs);
List<float[][]> paths = assembleClosedPaths(segs.toArray(new float[0][]), 0); List<float[][]> paths = assembleClosedPaths(segs.toArray(new float[0][]), 0);
final float MIN_PERIMETER_SQ = 60f * 60f; final float MIN_PERIMETER_SQ = 60f * 60f;