diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index 17091d6..2b9424f 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -3471,12 +3471,47 @@ public class EditorApp extends Application { Label name = new Label(param.getName()); name.setStyle("-fx-text-fill: #111111;"); - Slider slider = new Slider(param.getMin(), param.getMax(), param.getValue()); - slider.setShowTickMarks(true); - slider.setShowTickLabels(true); - slider.setMajorTickUnit((param.getMax() - param.getMin()) / 2); - slider.valueProperty().addListener((obs, oldV, newV) -> param.setValue(newV.doubleValue())); - panel.getChildren().add(new VBox(3, name, withField(slider, "%.3f"))); + if (tool instanceof de.blight.editor.tool.VoxelTool vtR + && param == vtR.brushRadius) { + // Ganzzahliger Slider: snapToTicks mit majorTickUnit=1 → nur ganze Zahlen + int initR = Math.max(1, (int) Math.round(param.getValue())); + Slider slider = new Slider(1, 30, initR); + slider.setShowTickMarks(true); + slider.setShowTickLabels(true); + slider.setMajorTickUnit(5); + slider.setMinorTickCount(4); + slider.setSnapToTicks(true); + slider.setBlockIncrement(1); + slider.valueProperty().addListener((obs, oldV, newV) -> + param.setValue(Math.round(newV.doubleValue()))); + panel.getChildren().add(new VBox(3, name, withField(slider, "%.0f"))); + + } else if (tool instanceof de.blight.editor.tool.VoxelTool vtP + && param == vtP.plateauTarget) { + // x.5-gestufter Slider: min=0.5, Schritt=1 → 0.5, 1.5, 2.5, … + double initSnap = Math.round(param.getValue() - 0.5) + 0.5; + initSnap = Math.max(0.5, Math.min(49.5, initSnap)); + Slider slider = new Slider(0.5, 49.5, initSnap); + slider.setShowTickMarks(true); + slider.setShowTickLabels(true); + slider.setMajorTickUnit(5); + slider.setMinorTickCount(4); + slider.setSnapToTicks(true); + slider.setBlockIncrement(1.0); + slider.valueProperty().addListener((obs, oldV, newV) -> { + double sv = Math.round(newV.doubleValue() - 0.5) + 0.5; + param.setValue(sv); + }); + panel.getChildren().add(new VBox(3, name, withField(slider, "%.1f"))); + + } else { + Slider slider = new Slider(param.getMin(), param.getMax(), param.getValue()); + slider.setShowTickMarks(true); + slider.setShowTickLabels(true); + slider.setMajorTickUnit((param.getMax() - param.getMin()) / 2); + slider.valueProperty().addListener((obs, oldV, newV) -> param.setValue(newV.doubleValue())); + panel.getChildren().add(new VBox(3, name, withField(slider, "%.3f"))); + } } // Textur-Slot-Konfigurator (nur beim TextureTool) @@ -3635,8 +3670,36 @@ public class EditorApp extends Application { selPoller.setCycleCount(javafx.animation.Animation.INDEFINITE); selPoller.play(); + // ── Unterirdische Voxel bereinigen ────────────────────────────────── + Label cleanupStatus = new Label(""); + cleanupStatus.setWrapText(true); + cleanupStatus.setStyle("-fx-text-fill: #555; -fx-font-size: 11;"); + + Button cleanupBtn = new Button("Unterirdische Voxel bereinigen"); + cleanupBtn.setMaxWidth(Double.MAX_VALUE); + cleanupBtn.setOnAction(e -> { + input.cleanupUndergroundStatus = null; + input.cleanupUndergroundRequested = true; + cleanupBtn.setDisable(true); + cleanupStatus.setText("Läuft..."); + }); + + javafx.animation.Timeline cleanupPoller = new javafx.animation.Timeline( + new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> { + String msg = input.cleanupUndergroundStatus; + if (msg != null) { + input.cleanupUndergroundStatus = null; + cleanupStatus.setText(msg); + cleanupBtn.setDisable(false); + } + }) + ); + cleanupPoller.setCycleCount(javafx.animation.Animation.INDEFINITE); + cleanupPoller.play(); + panel.getChildren().addAll(new Separator(), bakeBtn, bakeBar, bakeBarLabel, bakeStatus, + new Separator(), cleanupBtn, cleanupStatus, new Separator(), selInfoLabel, selHintLabel, deleteBtn, revertBtn); } } diff --git a/blight-editor/src/main/java/de/blight/editor/SharedInput.java b/blight-editor/src/main/java/de/blight/editor/SharedInput.java index 51c0de5..c687031 100644 --- a/blight-editor/src/main/java/de/blight/editor/SharedInput.java +++ b/blight-editor/src/main/java/de/blight/editor/SharedInput.java @@ -801,6 +801,10 @@ public class SharedInput { public volatile boolean deleteMarkedBakedRequested = false; /** JFX → JME: selektierte Chunks aus Pre-Bake-Backup wiederherstellen. */ public volatile boolean revertMarkedBakedRequested = false; + /** JFX → JME: unterirdische Voxel-Spalten (Oberfläche < Basis-Terrain) löschen. */ + public volatile boolean cleanupUndergroundRequested = false; + /** JME → JFX: Ergebnis der Unterirdisch-Bereinigung (null = läuft noch / nicht gestartet). */ + public volatile String cleanupUndergroundStatus = null; /** Terrain-Slot (0-7) für flache Voxel-Flächen, -1 = kein Slot. */ public volatile int voxelFlatSlot = -1; 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 e3393dc..df767a9 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 @@ -755,9 +755,12 @@ public class VoxelEditorState extends BaseAppState { // gebakte Meshes nach Distanz priorisiert – immer der korrekte Oberflächenpunkt. if (!isHorizontal && isColumn && modeIdx == de.blight.editor.tool.VoxelTool.MODE_PLATEAU && lower) { if (Float.isFinite(wy)) { - input.voxelTool.plateauTarget.setValue(wy); + // Auf x.5-Raster einrasten: MC-Oberfläche liegt ~0.5m über dem Zelldach, + // daher ist 1.5m, 2.5m, 3.5m usw. der korrekte Zielwert. + double snapped = Math.round(wy - 0.5) + 0.5; + input.voxelTool.plateauTarget.setValue(snapped); input.voxelTool.plateauTargetChanged = true; - input.heightTool.plateauHeight.setValue(wy); + input.heightTool.plateauHeight.setValue(snapped); input.heightTool.plateauHeightChanged = true; } return; @@ -1330,12 +1333,27 @@ public class VoxelEditorState extends BaseAppState { } // Terrain-Höhe als Fallback wenn noch keine Voxel in dieser Spalte float th = currentTopLY < 0 ? terrainH(wx, wz) : Float.NaN; - float currentTopWY = currentTopLY >= 0 - ? VoxelChunk.toWorldY(cy, currentTopLY) - : th; - float diff = targetH - currentTopWY; - if (Math.abs(diff) < 0.5f) continue; + // MC-Oberfläche liegt ~0.5m über der obersten soliden Zelle. + // Ohne Offset pendelt das Tool: raise→3m, diff=-0.5→lower→2m, diff=+0.5→raise→... + // Für leere Spalten (Terrain-Fallback) reicht terrainH direkt; dort + // genügt 0.1m Schwelle, damit z.B. Terrain 2m → Ziel 2.5m zuverlässig greift. + float effectiveCurrentH; + float threshold; + if (currentTopLY >= 0) { + effectiveCurrentH = VoxelChunk.toWorldY(cy, currentTopLY) + 0.5f; + threshold = 0.5f; + } else { + effectiveCurrentH = th; + threshold = 0.1f; + } + float diff = targetH - effectiveCurrentH; + if (Math.abs(diff) < threshold) continue; + + // Ziel-Zelle: floor(targetH) = oberste SOLIDE Zelle bei Ziel-Höhe. + // Raise/Lower nie über diese Zelle hinaus → verhindert Überschuss-Artefakte. + int targetLY = Math.max(0, Math.min(VoxelChunk.SIZE - 1, + (int) Math.floor(targetH - cy * (float) VoxelChunk.CELLS))); if (diff > 0) { // Erhöhen @@ -1353,13 +1371,13 @@ public class VoxelEditorState extends BaseAppState { continue; } } - int newTop = Math.min(VoxelChunk.SIZE - 1, startLY + step); + int newTop = Math.min(startLY + step, targetLY); // nie über Ziel-Zelle for (int ly = startLY; ly <= newTop; ly++) { chunk.setDensity(lx, ly, lz, (byte) 127); } } else { if (currentTopLY < 0) continue; - int newTop = Math.max(0, currentTopLY - step); + int newTop = Math.max(currentTopLY - step, targetLY); // nie unter Ziel-Zelle for (int ly = newTop + 1; ly <= currentTopLY; ly++) { chunk.setDensity(lx, ly, lz, Byte.MIN_VALUE); } @@ -2093,10 +2111,81 @@ 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. - int[][] DIRS8 = {{1,0},{-1,0},{0,1},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}}; + // 4:1-Abwärtsrampe bei Stufenabbrüchen (inter-terrace + Basisterrain-Übergang). + // + // 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. + // + // 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. + { + final float RAMP_RATIO = 4.0f; + final int MAX_RAMP_R = 20; - // Perimeter-Extension deaktiviert – saubere Kante an der Voxel-Grenze. - // (Berechnung auskommentiert, Code bleibt für spätere Nutzung erhalten.) + // 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 + 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; + + outer2b: + 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); + 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 (neighborH < h0raw) { + sumLowH += neighborH; + lowCount++; + } + } + } + if (lowCount > 0) { + lowDist = r; + break outer2b; + } + } + + if (lowDist < 0) continue; // kein niedrigerer Nachbar im Suchradius + + float avgLowH = sumLowH / lowCount; + float rampH = avgLowH + lowDist / RAMP_RATIO; + if (rampH < h0sm) { + smoothMap.put(hk0, rampH); + } + } + } // ── Schritt 3: Dichte anpassen ──────────────────────────────────── // ALLE Spalten aus hfMap bekommen den Dichte-Gradienten geschrieben, auch diff --git a/blight-editor/src/main/java/de/blight/editor/tool/HeightTool.java b/blight-editor/src/main/java/de/blight/editor/tool/HeightTool.java index 54c0f44..b283aeb 100644 --- a/blight-editor/src/main/java/de/blight/editor/tool/HeightTool.java +++ b/blight-editor/src/main/java/de/blight/editor/tool/HeightTool.java @@ -25,7 +25,7 @@ public class HeightTool extends EditorTool { } ); - public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 50.0, 1.0, 500.0); + public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 5.0, 1.0, 500.0); public final ToolParameter brushStrength = new ToolParameter("Pinselstärke", 2.0, 0.1, 50.0); public final ToolParameter plateauHeight = new ToolParameter("Plateau-Höhe", 0.0, -500.0, 500.0); public volatile boolean plateauHeightChanged = false; diff --git a/blight-editor/src/main/java/de/blight/editor/tool/VoxelTool.java b/blight-editor/src/main/java/de/blight/editor/tool/VoxelTool.java index 96759a7..55471d5 100644 --- a/blight-editor/src/main/java/de/blight/editor/tool/VoxelTool.java +++ b/blight-editor/src/main/java/de/blight/editor/tool/VoxelTool.java @@ -47,9 +47,9 @@ public class VoxelTool extends EditorTool { public volatile boolean modeChanged = false; public volatile boolean horizontal = false; - public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 5.0, 0.5, 30.0); + public final ToolParameter brushRadius = new ToolParameter("Pinselradius", 5.0, 1.0, 30.0); public final ToolParameter brushStrength = new ToolParameter("Stärke", 20.0, 1.0, 80.0); - public final ToolParameter plateauTarget = new ToolParameter("Plateau-Ziel", 0.0, -200.0, 500.0); + public final ToolParameter plateauTarget = new ToolParameter("Plateau-Ziel", 1.5, -99.5, 499.5); public volatile boolean plateauTargetChanged = false; @Override public String getName() { return "Voxel"; } diff --git a/blight-map/src/main/map/chunks/voxel_14_0_07.blvc b/blight-map/src/main/map/chunks/voxel_14_0_07.blvc index a2bd9f4..28f1d4f 100644 Binary files a/blight-map/src/main/map/chunks/voxel_14_0_07.blvc and b/blight-map/src/main/map/chunks/voxel_14_0_07.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 index cb7306a..1dbe784 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake and b/blight-map/src/main/map/chunks/voxel_15_0_06.blvc.prebake 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 index 99163b9..2c987f9 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod0.j3o 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 index 54cf5a0..94e0c43 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod1.j3o 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 index 909f19e..5458549 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o and b/blight-map/src/main/map/chunks/voxel_15_0_06_baked_lod2.j3o differ diff --git a/blight-map/src/main/map/chunks/voxel_15_0_07.blvc b/blight-map/src/main/map/chunks/voxel_15_0_07.blvc index ae5a090..4e50edd 100644 Binary files a/blight-map/src/main/map/chunks/voxel_15_0_07.blvc and b/blight-map/src/main/map/chunks/voxel_15_0_07.blvc differ