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 4a7b142..4c4de6b 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 @@ -2133,6 +2133,11 @@ public class VoxelEditorState extends BaseAppState { Map terrainCache2b = new HashMap<>(); + // ── Loop 1: Pass A – Basisterrain (LOWER + RAISE) ─────────────────── + // Muss als eigenständiger Loop VOR Pass B laufen: Pass B liest smoothMap + // der Nachbarn. Wären beide Passes im selben Loop, sähe Pass B manchmal + // noch den vergrabenen h0raw einer Nachbarzelle, die von PassA-RAISE noch + // nicht angehoben wurde (HashMap-Reihenfolge unbestimmt) → Graben. for (Map.Entry entry : hfMap.entrySet()) { long hk0 = entry.getKey(); float h0raw = entry.getValue(); @@ -2140,16 +2145,10 @@ public class VoxelEditorState extends BaseAppState { int colX2b = (int)(hk0 / 60001L) - 30000; int colZ2b = (int)(hk0 % 60001L) - 30000; - 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; + float minRampH = h0sm; + float minRaiseH = Float.POSITIVE_INFINITY; + float passA_maxThAbove = Float.NEGATIVE_INFINITY; - // ── 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; @@ -2188,7 +2187,6 @@ public class VoxelEditorState extends BaseAppState { 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) { @@ -2196,7 +2194,6 @@ public class VoxelEditorState extends BaseAppState { } } } - // Diagnose if (log.isTraceEnabled() && passA_maxThAbove > Float.NEGATIVE_INFINITY && minRampH >= h0sm) { if (minRaiseH < Float.POSITIVE_INFINITY) { @@ -2205,61 +2202,17 @@ public class VoxelEditorState extends BaseAppState { 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", + "PassA-UNTEN (%d,%d) h0sm=%.3f h0raw=%.3f terrainR1=%.3f", colX2b, colZ2b, h0sm, h0raw, passA_maxThAbove)); } } - - // ── 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; - } - } - } - if (minRampH < h0sm) { if (log.isTraceEnabled()) { - log.trace(String.format("Schritt2b (%d,%d) h=%.2f → %.2f", + log.trace(String.format("Schritt2b-A (%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)); @@ -2267,6 +2220,61 @@ public class VoxelEditorState extends BaseAppState { smoothMap.put(hk0, minRaiseH); } } + + // ── Loop 2: Pass B – Voxel-zu-Voxel ───────────────────────────────── + // Liest smoothMap aus Loop 1 → RAISE-Zellen haben ihre angehobene Höhe. + // Effektive Nachbarhöhe: max(raw, smooth). + // RAISE-Nachbar (smooth > raw): smooth gewinnt → kein Kandidat unter Terrain. + // LOWER-Nachbar (smooth < raw): raw gewinnt → Kaskaden-Schutz bleibt. + for (Map.Entry entry : hfMap.entrySet()) { + long hk0 = entry.getKey(); + float h0raw = entry.getValue(); + if (h0raw > 4.25f) continue; + float h0sm = smoothMap.getOrDefault(hk0, h0raw); + int colX2b = (int)(hk0 / 60001L) - 30000; + int colZ2b = (int)(hk0 % 60001L) - 30000; + + float minRampH = h0sm; + 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 hNraw = hfMap.get(hkN); + if (hNraw == null) { + if (r <= 3) { hadNonVoxelR1R3 = true; } + continue; + } + float hN = Math.max(hNraw, smoothMap.getOrDefault(hkN, hNraw)); + 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; + } + } + } + if (r == 3 && !hadAnyLower && !hadNonVoxelR1R3) { + break voxelPassB; + } + if (hadAnyLower) { + break voxelPassB; + } + } + if (minRampH < h0sm) { + if (log.isTraceEnabled()) { + log.trace(String.format("Schritt2b-B (%d,%d) h=%.2f → %.2f", + colX2b, colZ2b, h0sm, minRampH)); + } + smoothMap.put(hk0, minRampH); + } + } } // ── Schritt 3: Dichte anpassen ──────────────────────────────────── diff --git a/blight-map/src/main/map/blight_map.blm.tmp b/blight-map/src/main/map/blight_map.blm.tmp new file mode 100644 index 0000000..3143159 Binary files /dev/null and b/blight-map/src/main/map/blight_map.blm.tmp 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.prebake similarity index 100% rename from blight-map/src/main/map/chunks/voxel_16_0_07.blvc rename to blight-map/src/main/map/chunks/voxel_16_0_07.blvc.prebake diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o new file mode 100644 index 0000000..c9a380e Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod0.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o new file mode 100644 index 0000000..014a901 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod1.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod2.j3o b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod2.j3o new file mode 100644 index 0000000..3a35b53 Binary files /dev/null and b/blight-map/src/main/map/chunks/voxel_16_0_07_baked_lod2.j3o differ