Weltkarte Wald: Outline-Fill-Mismatch durch Chaikin-Schrumpfen behoben

Chaikin corner-cutting bewegt Punkte nach innen → Outline lag kleiner als
die Fill-Fläche. Die Wald-Density-Boundary ist bereits glatt (Summe
kreisförmiger Kernels), braucht kein Smoothing.

- assembleClosedPaths(segs, chaikinIter) extrahiert aus buildSeaCoastPaths:
  chaikinIter=0 → Pfade ohne Glättung, liegen exakt auf der Fill-Grenze
- buildForestOutlinePaths nutzt assembleClosedPaths(..., 0)
- buildSeaCoastPaths delegiert weiterhin mit chaikinIter=3 (Küste braucht
  Glättung wegen der stepped Terrain-Height-Boundary)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-03 18:31:30 +02:00
parent b79ccaa1e0
commit f0a3c462b6

View File

@@ -1006,6 +1006,14 @@ public class WorldMapView extends VBox {
// Verbindet Rohsegmente zu geschlossenen Küstenpfaden.
private static List<float[][]> buildSeaCoastPaths(float[][] segs) {
return assembleClosedPaths(segs, 3);
}
/**
* Verbindet Rohsegmente zu geschlossenen Pfaden.
* chaikinIter=0 → kein Smoothing (für bereits glatte Boundaries wie Wald-Density).
*/
private static List<float[][]> assembleClosedPaths(float[][] segs, int chaikinIter) {
if (segs == null || segs.length == 0) { return Collections.emptyList(); }
Map<Long, List<Integer>> adj = new HashMap<>(segs.length * 4);
@@ -1031,7 +1039,8 @@ public class WorldMapView extends VBox {
float[][] raw = new float[bwd.size() + fwd.size()][];
for (int i = 0; i < bwd.size(); i++) { raw[i] = bwd.get(bwd.size() - 1 - i); }
for (int i = 0; i < fwd.size(); i++) { raw[bwd.size() + i] = fwd.get(i); }
result.add(chaikin(simplifyCoastPath(raw), 3));
float[][] path = simplifyCoastPath(raw);
result.add(chaikinIter > 0 ? chaikin(path, chaikinIter) : path);
}
return result;
}
@@ -1138,9 +1147,11 @@ public class WorldMapView extends VBox {
}
List<float[]> segs = new ArrayList<>();
marchingSquares(mask, SIZE, 0.5f, null, null, segs);
List<float[][]> paths = buildSeaCoastPaths(segs.toArray(new float[0][]));
// chaikinIter=0: Density-Boundary ist bereits glatt (kreisförmige Kernels),
// kein Chaikin-Schrumpfen → Outline liegt exakt auf der Fill-Grenze.
List<float[][]> paths = assembleClosedPaths(segs.toArray(new float[0][]), 0);
// Kleinstpolygone filtern: weniger als ~100m Umfang = Artefakt (z.B. 2 isolierte Bäume)
// Kleinstpolygone filtern: weniger als ~60m Umfang = Artefakt (z.B. isolierte Bäume)
final float MIN_PERIMETER_SQ = 60f * 60f;
return paths.stream().filter(p -> pathPerimeterSq(p) >= MIN_PERIMETER_SQ)
.collect(java.util.stream.Collectors.toList());