diff --git a/blight-assets/src/main/resources/Shaders/Voxel.frag b/blight-assets/src/main/resources/Shaders/Voxel.frag index 233be17..9ccf71a 100644 --- a/blight-assets/src/main/resources/Shaders/Voxel.frag +++ b/blight-assets/src/main/resources/Shaders/Voxel.frag @@ -58,11 +58,10 @@ void main() { vec2 uvY = vWorldPos.xz / m_TexScale; vec2 uvZ = vWorldPos.xy / m_TexScale; - // Flach bis ~22,5° Gefälle (deckt sich mit der Bake-Schwelle in VoxelEditorState, - // die Rampen bis 22,5° glättet – vorher lag diese Grenze bei ~15° und sorgte dafür, - // dass geometrisch glatt gebackene, aber 15-22,5° steile Rampen trotzdem komplett - // mit der Klippen-Textur eingefärbt wurden statt mit der Flach-Textur. - float flatBlend = smoothstep(0.90, 0.95, vNormal.y); + // Flach bis ~45° Gefälle. LOD0-Smoothing (3 Passes, 0.3 Stärke) kann die Normalen + // an Rampenfüßen (14°-Rampe, ideal normal.y≈0.97) auf 0.70-0.85 herunterziehen. + // Untere Grenze 0.70 (≈45°) – echte Klippen (normal.y<0.70) zeigen weiter Gebirge. + float flatBlend = smoothstep(0.70, 0.90, vNormal.y); float steepBlend = 1.0 - flatBlend; // Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar. diff --git a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java index df767a9..4a7b142 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/VoxelEditorState.java @@ -2111,78 +2111,160 @@ public class VoxelEditorState extends BaseAppState { // Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend // von der (ring-gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe. Folgt // nicht mehr exakt dem Rampen-Winkel, ist aber garantiert monoton/glatt. - // 4:1-Abwärtsrampe bei Stufenabbrüchen (inter-terrace + Basisterrain-Übergang). + // 4:1-Abwärtsrampe bei Stufenabbrüchen. // - // Schritt 2 lässt Spalten mit hUp=null flach (smoothH=h0), auch wenn direkt - // daneben eine niedrigere Terasse oder das Basisterrain-Tal liegt. Das erzeugt - // vertikale Klippen an allen Plateau-Kanten. + // Zwei getrennte Passes wegen unterschiedlicher Schutz-Logik: // - // Fix: Für jede Spalte den nächsten Nachbarn mit Höhe < h0 suchen - // (Voxel-Spalte aus hfMap ODER Basisterrain außerhalb). Wenn gefunden bei - // Chebyshev-Distanz d mit mittlerer Höhe avgLow: - // rampH = avgLow + d / 4.0 - // Spalte wird auf min(smoothH, rampH) abgesenkt – nie angehoben. - // Entspricht 4 Schritte vor / 1 Schritt runter, deckt beide Bedingungen ab: - // „Tal darunter" (avgLow << h0) und „<1m über Basisterrain" (kleines dH). - // Ringsuche je Spalte unabhängig von anderen Schritt-2b-Ergebnissen → - // keine Kettenfortpflanzung, keine Divergenz. + // Pass A – Basisterrain-Übergang: + // Sucht nur Nicht-hfMap-Zellen (echtes Basisterrain). Voxel-Spalten in + // hfMap blocken die Sicht natürlich (Kliffwände sind in hfMap → werden + // übersprungen). MAX_TERRAIN_DIFF verhindert tiefe Klippen. + // Kein Ring-Vorfilter nötig – der Schutz kommt durch hfMap-Blocking. + // + // Pass B – Voxel-zu-Voxel-Übergang: + // Sucht nur hfMap-Zellen. Ring-1-Vorfilter: alle Voxel auf gleicher Höhe + // → Plateau-Innenzelle → überspringen. { - final float RAMP_RATIO = 4.0f; - final int MAX_RAMP_R = 20; + final float RAMP_RATIO = 4.0f; + final int MAX_RAMP_R = 20; + final int MAX_RAMP_R_TERRAIN = 4; + final float MAX_TERRAIN_DIFF = 1.0f; + final float MAX_VOXEL_DIFF = 1.05f; - // Terrain-Cache: vermeidet wiederholte terrainH()-Aufrufe bei Überlappungen Map terrainCache2b = new HashMap<>(); for (Map.Entry entry : hfMap.entrySet()) { - long hk0 = entry.getKey(); - float h0raw = entry.getValue(); // Original-Voxelhöhe - float h0sm = smoothMap.getOrDefault(hk0, h0raw); // Schritt-2-Ausgabe + long hk0 = entry.getKey(); + float h0raw = entry.getValue(); + float h0sm = smoothMap.getOrDefault(hk0, h0raw); int colX2b = (int)(hk0 / 60001L) - 30000; int colZ2b = (int)(hk0 % 60001L) - 30000; - // Ringsuche nach nächstem niedrigeren Nachbarn (Voxel oder Terrain) - float sumLowH = 0f; - int lowCount = 0; - int lowDist = -1; + float minRampH = h0sm; + // Terrain ÜBER dem Voxel: Voxel nach oben auf Terrain-Niveau heben. + // Kein +0.1f-Offset an Ring-1 → Ring-1-Oberfläche liegt exakt auf Terrain + // (nahtloser Übergang). Innere Ringe steigen per 4:1-Rampe weiter an. + float minRaiseH = Float.POSITIVE_INFINITY; - outer2b: - for (int r = 1; r <= MAX_RAMP_R; r++) { + // ── Pass A: Basisterrain ────────────────────────────────────────── + // Pro Ring: höchstes qualifizierendes th (maxThAtR) → Abwärts-Kandidat. + // Gegenfall: Terrain ÜBER h0sm (maxThAboveAtR) → Aufwärts-Kandidat. + float passA_maxThAbove = Float.NEGATIVE_INFINITY; // Terrain ÜBER h0sm (r=1, Diagnose) + for (int r = 1; r <= MAX_RAMP_R_TERRAIN; r++) { + float maxThAtR = Float.NEGATIVE_INFINITY; + float maxThAboveAtR = Float.NEGATIVE_INFINITY; for (int dz2b = -r; dz2b <= r; dz2b++) { for (int dx2b = -r; dx2b <= r; dx2b++) { if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue; long hkN = (long)(colX2b + dx2b + 30000) * 60001L + (colZ2b + dz2b + 30000); - Float hN = hfMap.get(hkN); - float neighborH; - if (hN != null) { - neighborH = hN; // Voxel-Spalte: Originalhöhe - } else { - Float cached = terrainCache2b.get(hkN); - if (cached == null) { - cached = terrainH((float)(colX2b + dx2b), - (float)(colZ2b + dz2b)); - terrainCache2b.put(hkN, cached); - } - neighborH = cached; // Basisterrain + if (hfMap.containsKey(hkN)) continue; + Float cached = terrainCache2b.get(hkN); + if (cached == null) { + cached = terrainH((float)(colX2b + dx2b), + (float)(colZ2b + dz2b)); + terrainCache2b.put(hkN, cached); } - if (neighborH < h0raw) { - sumLowH += neighborH; - lowCount++; + float th = cached; + if (th >= h0sm) { + if (r == 1 && th > passA_maxThAbove) { + passA_maxThAbove = th; + } + if ((th - h0sm) <= MAX_TERRAIN_DIFF && th > maxThAboveAtR) { + maxThAboveAtR = th; + } + continue; + } + float diff = h0sm - th; + if (diff > MAX_TERRAIN_DIFF) continue; + if (th > maxThAtR) { + maxThAtR = th; } } } - if (lowCount > 0) { - lowDist = r; - break outer2b; + if (maxThAtR > Float.NEGATIVE_INFINITY) { + float candidate = maxThAtR + 0.1f + (r - 1) / RAMP_RATIO; + if (candidate < minRampH) { + minRampH = candidate; + } + } + // Aufwärts-Kandidat: gleiche Formel wie Abwärts (symmetrisch) + if (maxThAboveAtR > Float.NEGATIVE_INFINITY) { + float raiseCandidate = maxThAboveAtR + (r - 1) / RAMP_RATIO; + if (raiseCandidate < minRaiseH) { + minRaiseH = raiseCandidate; + } + } + } + // Diagnose + if (log.isTraceEnabled() && passA_maxThAbove > Float.NEGATIVE_INFINITY + && minRampH >= h0sm) { + if (minRaiseH < Float.POSITIVE_INFINITY) { + log.trace(String.format( + "PassA-RAISE (%d,%d) h=%.2f → %.2f (terrainR1=%.2f)", + colX2b, colZ2b, h0sm, minRaiseH, passA_maxThAbove)); + } else { + log.trace(String.format( + "PassA-UNTEN (%d,%d) h0sm=%.3f h0raw=%.3f terrainR1=%.3f – Terrain zu weit oben (>MAX_TERRAIN_DIFF), kein Kandidat", + colX2b, colZ2b, h0sm, h0raw, passA_maxThAbove)); } } - if (lowDist < 0) continue; // kein niedrigerer Nachbar im Suchradius + // ── Pass B: Voxel-zu-Voxel ──────────────────────────────────────── + // Nur Terrassen-Zellen (h0raw ≤ 4.25m) – Bergterrain wird nicht angefasst. + // Pass B als if-Block (kein continue): smoothMap.put() am Ende bleibt für + // Pass-A-Ergebnisse aller Zellen erreichbar. + // Cascade-Schutz: alle Vergleiche ausschließlich mit RAW-Höhen (hN, h0raw), + // nie mit smoothMap – gerampte Kliffränder behalten raw ihre Originalhöhe + // und werden nicht fälschlicherweise als niedrigere Nachbarn erkannt. + if (h0raw <= 4.25f) { + boolean hadAnyLower = false; + boolean hadNonVoxelR1R3 = false; + voxelPassB: + for (int r = 1; r <= MAX_RAMP_R; r++) { + for (int dz2b = -r; dz2b <= r; dz2b++) { + for (int dx2b = -r; dx2b <= r; dx2b++) { + if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue; + long hkN = (long)(colX2b + dx2b + 30000) * 60001L + + (colZ2b + dz2b + 30000); + Float hN = hfMap.get(hkN); + if (hN == null) { + if (r <= 3) { hadNonVoxelR1R3 = true; } + continue; + } + if (hN >= h0raw) continue; + if ((h0raw - hN) >= MAX_VOXEL_DIFF) continue; + hadAnyLower = true; + if ((h0raw - hN) > (float) r) continue; + float candidate = hN + r / RAMP_RATIO; + if (candidate < minRampH) { + minRampH = candidate; + } + } + } + // Nach Ring-3: kein gültiger niedrigerer Voxel + kein Terrain → Plateau-Innenzelle + if (r == 3 && !hadAnyLower && !hadNonVoxelR1R3) { + break voxelPassB; + } + if (hadAnyLower) { + break voxelPassB; + } + } + } - float avgLowH = sumLowH / lowCount; - float rampH = avgLowH + lowDist / RAMP_RATIO; - if (rampH < h0sm) { - smoothMap.put(hk0, rampH); + if (minRampH < h0sm) { + if (log.isTraceEnabled()) { + log.trace(String.format("Schritt2b (%d,%d) h=%.2f → %.2f", + colX2b, colZ2b, h0sm, minRampH)); + } + smoothMap.put(hk0, minRampH); + } else if (minRaiseH > h0sm && minRaiseH < Float.POSITIVE_INFINITY) { + // Voxel lag unter dem Terrain → auf Terrain-Niveau anheben + if (log.isTraceEnabled()) { + log.trace(String.format("Schritt2b-RAISE (%d,%d) h=%.2f → %.2f", + colX2b, colZ2b, h0sm, minRaiseH)); + } + smoothMap.put(hk0, minRaiseH); } } } diff --git a/blight-editor/src/main/resources/logback.xml b/blight-editor/src/main/resources/logback.xml index eb72a9b..bae16b6 100644 --- a/blight-editor/src/main/resources/logback.xml +++ b/blight-editor/src/main/resources/logback.xml @@ -26,6 +26,7 @@ + diff --git a/blight-map/src/main/map/blight_map.blm b/blight-map/src/main/map/blight_map.blm index 1add408..09ca7bf 100644 Binary files a/blight-map/src/main/map/blight_map.blm and b/blight-map/src/main/map/blight_map.blm differ diff --git a/blight-map/src/main/map/chunks/chunk_16_07.blc b/blight-map/src/main/map/chunks/chunk_16_07.blc index e7aa0ef..cccef79 100644 Binary files a/blight-map/src/main/map/chunks/chunk_16_07.blc and b/blight-map/src/main/map/chunks/chunk_16_07.blc differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07.blvc.prebake b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc similarity index 73% rename from blight-map/src/main/map/chunks/voxel_16_0_07.blvc.prebake rename to blight-map/src/main/map/chunks/voxel_15_0_06.blvc index ed99e4d..56deeae 100644 Binary files a/blight-map/src/main/map/chunks/voxel_16_0_07.blvc.prebake and b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake deleted file mode 100644 index 1dbe784..0000000 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake and /dev/null differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o deleted file mode 100644 index 2c987f9..0000000 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o and /dev/null differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o deleted file mode 100644 index 94e0c43..0000000 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o and /dev/null differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o deleted file mode 100644 index 5458549..0000000 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o and /dev/null differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07.blvc b/blight-map/src/main/map/chunks/voxel_16_0_07.blvc index f0b8603..ab6be05 100644 Binary files a/blight-map/src/main/map/chunks/voxel_16_0_07.blvc and b/blight-map/src/main/map/chunks/voxel_16_0_07.blvc differ