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());
|
Label name = new Label(param.getName());
|
||||||
name.setStyle("-fx-text-fill: #111111;");
|
name.setStyle("-fx-text-fill: #111111;");
|
||||||
|
|
||||||
Slider slider = new Slider(param.getMin(), param.getMax(), param.getValue());
|
if (tool instanceof de.blight.editor.tool.VoxelTool vtR
|
||||||
slider.setShowTickMarks(true);
|
&& param == vtR.brushRadius) {
|
||||||
slider.setShowTickLabels(true);
|
// Ganzzahliger Slider: snapToTicks mit majorTickUnit=1 → nur ganze Zahlen
|
||||||
slider.setMajorTickUnit((param.getMax() - param.getMin()) / 2);
|
int initR = Math.max(1, (int) Math.round(param.getValue()));
|
||||||
slider.valueProperty().addListener((obs, oldV, newV) -> param.setValue(newV.doubleValue()));
|
Slider slider = new Slider(1, 30, initR);
|
||||||
panel.getChildren().add(new VBox(3, name, withField(slider, "%.3f")));
|
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)
|
// Textur-Slot-Konfigurator (nur beim TextureTool)
|
||||||
@@ -3635,8 +3670,36 @@ public class EditorApp extends Application {
|
|||||||
selPoller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
selPoller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
||||||
selPoller.play();
|
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,
|
panel.getChildren().addAll(new Separator(), bakeBtn,
|
||||||
bakeBar, bakeBarLabel, bakeStatus,
|
bakeBar, bakeBarLabel, bakeStatus,
|
||||||
|
new Separator(), cleanupBtn, cleanupStatus,
|
||||||
new Separator(), selInfoLabel, selHintLabel, deleteBtn, revertBtn);
|
new Separator(), selInfoLabel, selHintLabel, deleteBtn, revertBtn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -801,6 +801,10 @@ public class SharedInput {
|
|||||||
public volatile boolean deleteMarkedBakedRequested = false;
|
public volatile boolean deleteMarkedBakedRequested = false;
|
||||||
/** JFX → JME: selektierte Chunks aus Pre-Bake-Backup wiederherstellen. */
|
/** JFX → JME: selektierte Chunks aus Pre-Bake-Backup wiederherstellen. */
|
||||||
public volatile boolean revertMarkedBakedRequested = false;
|
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. */
|
/** Terrain-Slot (0-7) für flache Voxel-Flächen, -1 = kein Slot. */
|
||||||
public volatile int voxelFlatSlot = -1;
|
public volatile int voxelFlatSlot = -1;
|
||||||
|
|||||||
@@ -755,9 +755,12 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// gebakte Meshes nach Distanz priorisiert – immer der korrekte Oberflächenpunkt.
|
// gebakte Meshes nach Distanz priorisiert – immer der korrekte Oberflächenpunkt.
|
||||||
if (!isHorizontal && isColumn && modeIdx == de.blight.editor.tool.VoxelTool.MODE_PLATEAU && lower) {
|
if (!isHorizontal && isColumn && modeIdx == de.blight.editor.tool.VoxelTool.MODE_PLATEAU && lower) {
|
||||||
if (Float.isFinite(wy)) {
|
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.voxelTool.plateauTargetChanged = true;
|
||||||
input.heightTool.plateauHeight.setValue(wy);
|
input.heightTool.plateauHeight.setValue(snapped);
|
||||||
input.heightTool.plateauHeightChanged = true;
|
input.heightTool.plateauHeightChanged = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1330,12 +1333,27 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
// Terrain-Höhe als Fallback wenn noch keine Voxel in dieser Spalte
|
// Terrain-Höhe als Fallback wenn noch keine Voxel in dieser Spalte
|
||||||
float th = currentTopLY < 0 ? terrainH(wx, wz) : Float.NaN;
|
float th = currentTopLY < 0 ? terrainH(wx, wz) : Float.NaN;
|
||||||
float currentTopWY = currentTopLY >= 0
|
|
||||||
? VoxelChunk.toWorldY(cy, currentTopLY)
|
|
||||||
: th;
|
|
||||||
|
|
||||||
float diff = targetH - currentTopWY;
|
// MC-Oberfläche liegt ~0.5m über der obersten soliden Zelle.
|
||||||
if (Math.abs(diff) < 0.5f) continue;
|
// 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) {
|
if (diff > 0) {
|
||||||
// Erhöhen
|
// Erhöhen
|
||||||
@@ -1353,13 +1371,13 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
continue;
|
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++) {
|
for (int ly = startLY; ly <= newTop; ly++) {
|
||||||
chunk.setDensity(lx, ly, lz, (byte) 127);
|
chunk.setDensity(lx, ly, lz, (byte) 127);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (currentTopLY < 0) continue;
|
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++) {
|
for (int ly = newTop + 1; ly <= currentTopLY; ly++) {
|
||||||
chunk.setDensity(lx, ly, lz, Byte.MIN_VALUE);
|
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
|
// Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend
|
||||||
// von der (ring-gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe. Folgt
|
// 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.
|
// 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.
|
// Terrain-Cache: vermeidet wiederholte terrainH()-Aufrufe bei Überlappungen
|
||||||
// (Berechnung auskommentiert, Code bleibt für spätere Nutzung erhalten.)
|
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 ────────────────────────────────────
|
// ── Schritt 3: Dichte anpassen ────────────────────────────────────
|
||||||
// ALLE Spalten aus hfMap bekommen den Dichte-Gradienten geschrieben, auch
|
// 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 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 final ToolParameter plateauHeight = new ToolParameter("Plateau-Höhe", 0.0, -500.0, 500.0);
|
||||||
public volatile boolean plateauHeightChanged = false;
|
public volatile boolean plateauHeightChanged = false;
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ public class VoxelTool extends EditorTool {
|
|||||||
public volatile boolean modeChanged = false;
|
public volatile boolean modeChanged = false;
|
||||||
public volatile boolean horizontal = 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 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;
|
public volatile boolean plateauTargetChanged = false;
|
||||||
|
|
||||||
@Override public String getName() { return "Voxel"; }
|
@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