Fehler beim Backen von Voxels behoben
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Long, Float> terrainCache2b = new HashMap<>();
|
||||
|
||||
for (Map.Entry<Long, Float> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user