Weltkarte Klippen: Schraffierung nur im Kliff-Band via Winding-Korrektur
- fixWindingForClip(): äußere Pfade CCW, innere Pfade (Löcher/Berg-Inneres) CW → gc.clip() füllt mit Non-Zero-Rule nur das echte Kliff-Band, nicht den gesamten eingeschlossenen Bereich inkl. Berggipfel - Schraffierungs-Liniendicke = overlayLineWidth() (identisch zu Konturlinie) - Schraffierungs-Abstand = 4× Liniendicke → konstante ~25% Flächendeckung über alle Zoom-Stufen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -602,9 +602,9 @@ public class WorldMapView extends VBox {
|
|||||||
}
|
}
|
||||||
gc.clip();
|
gc.clip();
|
||||||
gc.setStroke(Color.rgb(120, 115, 115, 0.45));
|
gc.setStroke(Color.rgb(120, 115, 115, 0.45));
|
||||||
gc.setLineWidth(0.8);
|
gc.setLineWidth(overlayLineWidth());
|
||||||
gc.setLineDashes();
|
gc.setLineDashes();
|
||||||
double hatchSpacing = 5.5;
|
double hatchSpacing = overlayLineWidth() * 4.0;
|
||||||
for (double d = -cH; d < cW + cH; d += hatchSpacing) {
|
for (double d = -cH; d < cW + cH; d += hatchSpacing) {
|
||||||
gc.strokeLine(d, 0, d + cH, cH);
|
gc.strokeLine(d, 0, d + cH, cH);
|
||||||
}
|
}
|
||||||
@@ -1036,6 +1036,57 @@ public class WorldMapView extends VBox {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Winding-Korrektur für Clip-Masken ────────────────────────────────────
|
||||||
|
// gc.clip() nutzt Non-Zero-Winding: äußere Pfade müssen CCW sein, innere (Löcher) CW.
|
||||||
|
// Ohne Korrektur füllt clip() den gesamten Innenbereich inkl. eingeschlossener Nicht-Kliff-Gebiete.
|
||||||
|
|
||||||
|
private static float signedArea(float[][] path) {
|
||||||
|
float a = 0f;
|
||||||
|
for (int i = 0, n = path.length; i < n; i++) {
|
||||||
|
float[] p0 = path[i], p1 = path[(i + 1) % n];
|
||||||
|
a += p0[0] * p1[1] - p1[0] * p0[1];
|
||||||
|
}
|
||||||
|
return a * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean pointInPolygon(float px, float py, float[][] poly) {
|
||||||
|
boolean inside = false;
|
||||||
|
for (int i = 0, j = poly.length - 1; i < poly.length; j = i++) {
|
||||||
|
float xi = poly[i][0], yi = poly[i][1], xj = poly[j][0], yj = poly[j][1];
|
||||||
|
if (((yi > py) != (yj > py)) && (px < (xj - xi) * (py - yi) / (yj - yi) + xi)) {
|
||||||
|
inside = !inside;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return inside;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[][] reversePath(float[][] path) {
|
||||||
|
float[][] r = new float[path.length][];
|
||||||
|
for (int i = 0; i < path.length; i++) { r[i] = path[path.length - 1 - i]; }
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stellt sicher dass äußere Pfade CCW und innere Pfade (Löcher) CW gewunden sind.
|
||||||
|
* Nötig damit gc.clip() mit Non-Zero-Rule nur die echte Kliff-Band-Fläche maskiert.
|
||||||
|
*/
|
||||||
|
private static List<float[][]> fixWindingForClip(List<float[][]> paths) {
|
||||||
|
if (paths.size() <= 1) { return paths; }
|
||||||
|
List<float[][]> result = new ArrayList<>(paths.size());
|
||||||
|
for (float[][] path : paths) {
|
||||||
|
float[] probe = path[path.length / 2];
|
||||||
|
int containCount = 0;
|
||||||
|
for (float[][] other : paths) {
|
||||||
|
if (other != path && pointInPolygon(probe[0], probe[1], other)) { containCount++; }
|
||||||
|
}
|
||||||
|
boolean isHole = (containCount % 2) == 1;
|
||||||
|
boolean isCCW = signedArea(path) > 0;
|
||||||
|
boolean wantCCW = !isHole;
|
||||||
|
result.add(isCCW == wantCCW ? path : reversePath(path));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/** Extrahiert Klippen-Konturen aus Terrain-Höhen als geglättete Weltkoordinaten-Pfade. */
|
/** Extrahiert Klippen-Konturen aus Terrain-Höhen als geglättete Weltkoordinaten-Pfade. */
|
||||||
private static List<float[][]> buildCliffPaths(MapData mapData, float[] voxelSurface) {
|
private static List<float[][]> buildCliffPaths(MapData mapData, float[] voxelSurface) {
|
||||||
final int SIZE = SEA_MASK_SIZE;
|
final int SIZE = SEA_MASK_SIZE;
|
||||||
@@ -1061,7 +1112,7 @@ public class WorldMapView extends VBox {
|
|||||||
|
|
||||||
List<float[]> segs = new ArrayList<>();
|
List<float[]> segs = new ArrayList<>();
|
||||||
marchingSquares(mask, SIZE, 0.5f, null, null, segs);
|
marchingSquares(mask, SIZE, 0.5f, null, null, segs);
|
||||||
return buildSeaCoastPaths(segs.toArray(new float[0][]));
|
return fixWindingForClip(buildSeaCoastPaths(segs.toArray(new float[0][])));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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. */
|
||||||
|
|||||||
Reference in New Issue
Block a user