Wasserfall System eingebaut
This commit is contained in:
@@ -814,6 +814,22 @@ public class EditorApp extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
if (input.auswahlWaterSelected) {
|
||||
input.auswahlWaterSelected = false;
|
||||
if (wasserMergedBtn != null) wasserMergedBtn.setSelected(true);
|
||||
input.activeLayer = SharedInput.LAYER_WATER;
|
||||
root.setRight(buildWasserPanel());
|
||||
updateWaterPanel(input.selectedWaterInfo);
|
||||
}
|
||||
|
||||
if (input.auswahlWaterfallSelected) {
|
||||
input.auswahlWaterfallSelected = false;
|
||||
if (wasserMergedBtn != null) wasserMergedBtn.setSelected(true);
|
||||
input.activeLayer = SharedInput.LAYER_WATERFALL;
|
||||
root.setRight(buildWasserPanel());
|
||||
updateWaterfallPanel(input.selectedWaterfallInfo);
|
||||
}
|
||||
|
||||
if (input.itemSelectionChanged) {
|
||||
input.itemSelectionChanged = false;
|
||||
updateAuswahlItemPanel(input.selectedItemInfo);
|
||||
@@ -2877,12 +2893,13 @@ public class EditorApp extends Application {
|
||||
|
||||
inner.getChildren().addAll(
|
||||
new Separator(),
|
||||
styledHint("L-Klick → Polygon-Punkt setzen"),
|
||||
styledHint("R-Klick → letzten Punkt entfernen"),
|
||||
styledHint("L-Klick (leer) → erste Ecke setzen"),
|
||||
styledHint("L-Klick (2.) → Quad abschliessen"),
|
||||
styledHint("Handle ziehen → Ecke / Kante verschieben"),
|
||||
styledHint("R-Klick → Abbrechen / Auswahl aufheben"),
|
||||
styledHint("Leertaste → Höhe vom Terrain übernehmen"),
|
||||
styledHint("Erster Punkt bestimmt die Höhe"),
|
||||
styledHint("ESC → Polygon abbrechen"),
|
||||
styledHint("Entf → Fläche löschen"));
|
||||
styledHint("ESC → Abbrechen / Auswahl aufheben"),
|
||||
styledHint("Entf → Quad löschen"));
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
@@ -2900,27 +2917,20 @@ public class EditorApp extends Application {
|
||||
VBox inner = new VBox(10);
|
||||
inner.setPadding(new Insets(10));
|
||||
|
||||
Label widthTitle = sectionTitle("Breite (nächster Punkt)");
|
||||
Slider widthSlider = new Slider(8.0, 40.0, input.waterfallNewWidth);
|
||||
widthSlider.setBlockIncrement(0.5);
|
||||
widthSlider.setShowTickLabels(true);
|
||||
widthSlider.setMajorTickUnit(5);
|
||||
widthSlider.valueProperty().addListener((o, ov, nv) -> input.waterfallNewWidth = nv.floatValue());
|
||||
|
||||
Button undoBtn = new Button("Letzten Punkt entfernen");
|
||||
undoBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
undoBtn.setOnAction(e -> input.undoWaterfallPointRequested = true);
|
||||
|
||||
inner.getChildren().addAll(
|
||||
sectionTitle("Wasserfall"),
|
||||
new Separator(),
|
||||
widthTitle, withField(widthSlider, "%.1f"),
|
||||
sectionTitle("Wasserfall (4-Klick Quad)"),
|
||||
new Separator(),
|
||||
undoBtn,
|
||||
new Separator(),
|
||||
styledHint("L-Klick → Punkt setzen"),
|
||||
styledHint("R-Klick → Wasserfall abschließen"),
|
||||
styledHint("Backspace → letzten Punkt löschen"));
|
||||
styledHint("L-Klick → Punkt setzen (1→2→3→4)"),
|
||||
styledHint("Punkte auf Klippen, Voxel & Seenkanten möglich"),
|
||||
styledHint("R-Klick / Backspace → letzten Punkt rückgängig"),
|
||||
styledHint("ESC → Platzierung abbrechen"),
|
||||
styledHint("Wasserfall anklicken → Ecken verschiebbar"));
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
@@ -2959,7 +2969,19 @@ public class EditorApp extends Application {
|
||||
Button deleteBtn = new Button("Wasserfall löschen");
|
||||
deleteBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
deleteBtn.setStyle("-fx-background-color: #c0392b; -fx-text-fill: white;");
|
||||
deleteBtn.setOnAction(e -> input.deleteWaterfallRequested = true);
|
||||
deleteBtn.setOnAction(e -> {
|
||||
javafx.scene.control.Alert confirm = new javafx.scene.control.Alert(
|
||||
javafx.scene.control.Alert.AlertType.CONFIRMATION,
|
||||
"Wasserfall unwiderruflich löschen?",
|
||||
javafx.scene.control.ButtonType.YES,
|
||||
javafx.scene.control.ButtonType.NO);
|
||||
confirm.setTitle("Wasserfall löschen");
|
||||
confirm.setHeaderText(null);
|
||||
confirm.initOwner(primaryStage);
|
||||
confirm.showAndWait().ifPresent(btn -> {
|
||||
if (btn == javafx.scene.control.ButtonType.YES) input.deleteWaterfallRequested = true;
|
||||
});
|
||||
});
|
||||
|
||||
inner.getChildren().addAll(
|
||||
sectionTitle("Wasserfall selektiert"),
|
||||
@@ -2999,54 +3021,132 @@ public class EditorApp extends Application {
|
||||
return;
|
||||
}
|
||||
|
||||
// Format: "idx|waterHeight|pointCount|flowDegrees"
|
||||
// Format: idx|h|n|flow|speed|waveScale|waveAmp|transp|wr|wg|wb|dr|dg|db
|
||||
String[] p = info.split("\\|", -1);
|
||||
if (p.length < 3) return;
|
||||
try {
|
||||
float waterHeight = Float.parseFloat(p[1]);
|
||||
int pointCount = Integer.parseInt(p[2]);
|
||||
float flowDeg = p.length >= 4 ? Float.parseFloat(p[3]) : 0f;
|
||||
float flowDeg = p.length > 3 ? Float.parseFloat(p[3]) : 0f;
|
||||
float speed = p.length > 4 ? Float.parseFloat(p[4]) : 0.5f;
|
||||
float waveScale = p.length > 5 ? Float.parseFloat(p[5]) : 0.008f;
|
||||
float waveAmp = p.length > 6 ? Float.parseFloat(p[6]) : 0.1f;
|
||||
float transp = p.length > 7 ? Float.parseFloat(p[7]) : 0.15f;
|
||||
float wr = p.length > 8 ? Float.parseFloat(p[8]) : 0.05f;
|
||||
float wg = p.length > 9 ? Float.parseFloat(p[9]) : 0.25f;
|
||||
float wb = p.length > 10 ? Float.parseFloat(p[10]) : 0.55f;
|
||||
float dr = p.length > 11 ? Float.parseFloat(p[11]) : 0.02f;
|
||||
float dg = p.length > 12 ? Float.parseFloat(p[12]) : 0.12f;
|
||||
float db = p.length > 13 ? Float.parseFloat(p[13]) : 0.30f;
|
||||
|
||||
Label infoLabel = new Label(String.format("Punkte: %d | Höhe: %.2f m", pointCount, waterHeight));
|
||||
// Live-Array für alle Rendering-Parameter; wird bei jeder Änderung neu gesetzt
|
||||
float[] params = {speed, waveScale, waveAmp, transp, wr, wg, wb, dr, dg, db};
|
||||
|
||||
// ── Info ──────────────────────────────────────────────────────────
|
||||
Label infoLabel = new Label(String.format("Ecken: %d | Höhe: %.2f m", pointCount, waterHeight));
|
||||
infoLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #555;");
|
||||
|
||||
Label heightLabel = new Label("Höhe (m)");
|
||||
heightLabel.setStyle("-fx-font-size: 11;");
|
||||
|
||||
// ── Höhe ──────────────────────────────────────────────────────────
|
||||
Slider heightSlider = new Slider(-50, 500, waterHeight);
|
||||
heightSlider.setBlockIncrement(0.1);
|
||||
heightSlider.setShowTickLabels(false);
|
||||
heightSlider.valueProperty().addListener((o, ov, nv) -> input.pendingWaterHeight.set(nv.floatValue()));
|
||||
|
||||
Label flowLabel = new Label("Fließrichtung");
|
||||
flowLabel.setStyle("-fx-font-size: 11; -fx-font-weight: bold;");
|
||||
Label flowDegLabel = new Label(String.format("%.0f°", flowDeg));
|
||||
flowDegLabel.setStyle("-fx-font-size: 11;");
|
||||
heightSlider.valueProperty().addListener((o, ov, nv) ->
|
||||
input.pendingWaterHeight.set(nv.floatValue()));
|
||||
|
||||
// ── Fließrichtung ─────────────────────────────────────────────────
|
||||
Spinner<Integer> flowSpinner = new Spinner<>(0, 359, Math.round(flowDeg));
|
||||
flowSpinner.setEditable(true);
|
||||
flowSpinner.setMaxWidth(Double.MAX_VALUE);
|
||||
flowSpinner.valueProperty().addListener((o, ov, nv) -> {
|
||||
flowDegLabel.setText(nv + "°");
|
||||
input.pendingWaterFlowDegrees.set(nv.floatValue());
|
||||
flowSpinner.valueProperty().addListener((o, ov, nv) ->
|
||||
input.pendingWaterFlowDegrees.set(nv.floatValue()));
|
||||
|
||||
// ── Geschwindigkeit ───────────────────────────────────────────────
|
||||
Slider speedSlider = new Slider(0.05, 5.0, speed);
|
||||
speedSlider.setBlockIncrement(0.05);
|
||||
speedSlider.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[0] = nv.floatValue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
Button delBtn = new Button("Löschen");
|
||||
// ── Wellengröße ───────────────────────────────────────────────────
|
||||
Slider waveScaleSlider = new Slider(0.001, 0.05, waveScale);
|
||||
waveScaleSlider.setBlockIncrement(0.001);
|
||||
waveScaleSlider.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[1] = nv.floatValue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
// ── Wellenamplitude ───────────────────────────────────────────────
|
||||
Slider waveAmpSlider = new Slider(0.0, 2.0, waveAmp);
|
||||
waveAmpSlider.setBlockIncrement(0.05);
|
||||
waveAmpSlider.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[2] = nv.floatValue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
// ── Transparenz ───────────────────────────────────────────────────
|
||||
Slider transpSlider = new Slider(0.0, 1.0, transp);
|
||||
transpSlider.setBlockIncrement(0.01);
|
||||
transpSlider.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[3] = nv.floatValue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
// ── Wasserfarbe ───────────────────────────────────────────────────
|
||||
javafx.scene.control.ColorPicker waterColorPicker =
|
||||
new javafx.scene.control.ColorPicker(javafx.scene.paint.Color.color(
|
||||
Math.min(wr, 1.0), Math.min(wg, 1.0), Math.min(wb, 1.0)));
|
||||
waterColorPicker.setMaxWidth(Double.MAX_VALUE);
|
||||
waterColorPicker.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[4] = (float) nv.getRed();
|
||||
params[5] = (float) nv.getGreen();
|
||||
params[6] = (float) nv.getBlue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
// ── Tiefwasserfarbe ───────────────────────────────────────────────
|
||||
javafx.scene.control.ColorPicker deepColorPicker =
|
||||
new javafx.scene.control.ColorPicker(javafx.scene.paint.Color.color(
|
||||
Math.min(dr, 1.0), Math.min(dg, 1.0), Math.min(db, 1.0)));
|
||||
deepColorPicker.setMaxWidth(Double.MAX_VALUE);
|
||||
deepColorPicker.valueProperty().addListener((o, ov, nv) -> {
|
||||
params[7] = (float) nv.getRed();
|
||||
params[8] = (float) nv.getGreen();
|
||||
params[9] = (float) nv.getBlue();
|
||||
input.pendingWaterParams.set(params.clone());
|
||||
});
|
||||
|
||||
// ── Löschen ───────────────────────────────────────────────────────
|
||||
Button delBtn = new Button("Quad löschen");
|
||||
delBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
delBtn.setStyle("-fx-text-fill: #c0392b;");
|
||||
delBtn.setOnAction(e -> input.deleteWaterRequested = true);
|
||||
|
||||
waterDynamicContent.getChildren().addAll(
|
||||
infoLabel, new Separator(),
|
||||
heightLabel, withField(heightSlider, "%.2f"),
|
||||
infoLabel,
|
||||
new Separator(),
|
||||
flowLabel, flowSpinner,
|
||||
lbl("Höhe (m)"), withField(heightSlider, "%.2f"),
|
||||
new Separator(),
|
||||
lbl("Fließrichtung"), flowSpinner,
|
||||
lbl("Geschwindigkeit"),withField(speedSlider, "%.2f"),
|
||||
lbl("Wellengröße"), withField(waveScaleSlider, "%.4f"),
|
||||
lbl("Wellenamplitude"),withField(waveAmpSlider, "%.2f"),
|
||||
lbl("Transparenz"), withField(transpSlider, "%.2f"),
|
||||
new Separator(),
|
||||
lbl("Wasserfarbe"), waterColorPicker,
|
||||
lbl("Tiefwasserfarbe"),deepColorPicker,
|
||||
new Separator(),
|
||||
delBtn);
|
||||
|
||||
} catch (NumberFormatException ignored) {}
|
||||
}
|
||||
|
||||
private static Label lbl(String text) {
|
||||
Label l = new Label(text);
|
||||
l.setStyle("-fx-font-size: 11;");
|
||||
return l;
|
||||
}
|
||||
|
||||
// ── Emitter-Panel ─────────────────────────────────────────────────────────
|
||||
|
||||
private VBox buildEmitterPanel() {
|
||||
@@ -5354,6 +5454,12 @@ public class EditorApp extends Application {
|
||||
input.reloadPlacedOther = true;
|
||||
}
|
||||
case "waterfall" -> {
|
||||
java.util.List<de.blight.common.PlacedWaterfall> list = new java.util.ArrayList<>(de.blight.common.WaterfallIO.load());
|
||||
if (index >= 0 && index < list.size()) list.remove(index);
|
||||
de.blight.common.WaterfallIO.save(list);
|
||||
input.reloadPlacedOther = true;
|
||||
}
|
||||
case "river" -> {
|
||||
java.util.List<java.util.List<de.blight.common.RiverPoint>> list = new java.util.ArrayList<>(de.blight.common.RiverIO.load());
|
||||
if (index >= 0 && index < list.size()) list.remove(index);
|
||||
de.blight.common.RiverIO.save(list);
|
||||
@@ -8748,7 +8854,8 @@ public class EditorApp extends Application {
|
||||
|| input.activeLayer == SharedInput.LAYER_ZONEN
|
||||
|| input.activeLayer == SharedInput.LAYER_SOUND_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES;
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES
|
||||
|| input.activeLayer == SharedInput.LAYER_WATER;
|
||||
if (input.activeLayer == SharedInput.LAYER_PLAY_TOOL) {
|
||||
// Einzel-Klick ohne Edit-Timer (verhindert Dauer-Spam)
|
||||
input.playToolClickQueue.offer(
|
||||
@@ -8764,7 +8871,8 @@ public class EditorApp extends Application {
|
||||
boolean singleClickLayer = input.activeLayer == SharedInput.LAYER_ZONEN
|
||||
|| input.activeLayer == SharedInput.LAYER_SOUND_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_AREAS
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES;
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES
|
||||
|| input.activeLayer == SharedInput.LAYER_WATER;
|
||||
editPressX = e.getX(); editPressY = e.getY(); editPressAction = -1;
|
||||
submitEdit(editPressX, editPressY, editPressAction);
|
||||
if (!singleClickLayer) startEditTimer();
|
||||
@@ -8789,6 +8897,18 @@ public class EditorApp extends Application {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_WATER && e.isPrimaryButtonDown()) {
|
||||
input.waterDragQueue.offer(new SharedInput.WaterDrag((float) e.getX(), (float) e.getY()));
|
||||
input.mouseScreenX = (float) e.getX();
|
||||
input.mouseScreenY = (float) e.getY();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_WATERFALL && e.isPrimaryButtonDown()) {
|
||||
input.waterfallDragQueue.offer(new SharedInput.WaterfallDrag((float) e.getX(), (float) e.getY()));
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.activeLayer == SharedInput.LAYER_ROUTINE_EDITOR && e.isSecondaryButtonDown()) {
|
||||
input.routineYawDragQueue.offer(
|
||||
new SharedInput.RoutineYawDrag((float) e.getX(), (float) e.getY(), false));
|
||||
@@ -8815,6 +8935,12 @@ public class EditorApp extends Application {
|
||||
viewport.setOnMouseReleased(e -> {
|
||||
objDragging = false;
|
||||
if (!isObjectMode()) stopEditTimer();
|
||||
if (input.activeLayer == SharedInput.LAYER_WATER) {
|
||||
input.waterDragEnd = true;
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_WATERFALL) {
|
||||
input.waterfallDragEnd = true;
|
||||
}
|
||||
if (input.activeLayer == SharedInput.LAYER_ROUTINE_EDITOR) {
|
||||
input.routineYawDragQueue.offer(
|
||||
new SharedInput.RoutineYawDrag((float) e.getX(), (float) e.getY(), true));
|
||||
@@ -9404,6 +9530,8 @@ public class EditorApp extends Application {
|
||||
case CONTROL -> input.ctrlHeld = pressed;
|
||||
case ALT -> input.altHeld = pressed;
|
||||
case ENTER -> {
|
||||
if (pressed && input.activeLayer == SharedInput.LAYER_WATERFALL)
|
||||
input.finalizeWaterfallRequested = true;
|
||||
if (pressed && input.activeLayer == SharedInput.LAYER_VOXEL_CLIFF) {
|
||||
input.generateCliffVoxelsRequested = true;
|
||||
if (voxelCliffGenerateBtn != null) voxelCliffGenerateBtn.setDisable(true);
|
||||
@@ -9416,6 +9544,7 @@ public class EditorApp extends Application {
|
||||
|| input.activeLayer == SharedInput.LAYER_LOCATION_ZONES
|
||||
|| input.activeLayer == SharedInput.LAYER_ZONEN
|
||||
|| input.activeLayer == SharedInput.LAYER_WATER
|
||||
|| input.activeLayer == SharedInput.LAYER_WATERFALL
|
||||
|| input.activeLayer == SharedInput.LAYER_VOXEL_CLIFF))
|
||||
input.cancelZoneDrawing = true;
|
||||
}
|
||||
@@ -9439,6 +9568,23 @@ public class EditorApp extends Application {
|
||||
input.deleteVoxelCliffZoneRequested = true;
|
||||
else if (input.activeLayer == SharedInput.LAYER_WATER)
|
||||
input.deleteWaterRequested = true;
|
||||
else if (input.activeLayer == SharedInput.LAYER_WATERFALL
|
||||
&& input.selectedWaterfallInfo != null) {
|
||||
Platform.runLater(() -> {
|
||||
javafx.scene.control.Alert confirm = new javafx.scene.control.Alert(
|
||||
javafx.scene.control.Alert.AlertType.CONFIRMATION,
|
||||
"Wasserfall unwiderruflich löschen?",
|
||||
javafx.scene.control.ButtonType.YES,
|
||||
javafx.scene.control.ButtonType.NO);
|
||||
confirm.setTitle("Wasserfall löschen");
|
||||
confirm.setHeaderText(null);
|
||||
confirm.initOwner(primaryStage);
|
||||
confirm.showAndWait().ifPresent(btn -> {
|
||||
if (btn == javafx.scene.control.ButtonType.YES)
|
||||
input.deleteWaterfallRequested = true;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
case BACK_SPACE -> {
|
||||
|
||||
@@ -24,6 +24,7 @@ import de.blight.editor.state.ModelEditorState;
|
||||
import de.blight.editor.state.EmitterState;
|
||||
import de.blight.editor.state.LocationZoneState;
|
||||
import de.blight.editor.state.RiverEditorState;
|
||||
import de.blight.editor.state.WaterfallEditorState;
|
||||
import de.blight.editor.state.PlayToolState;
|
||||
import de.blight.editor.state.SoundAreaState;
|
||||
import de.blight.editor.state.WaterBodyState;
|
||||
@@ -206,6 +207,7 @@ public class JmeEditorApp extends SimpleApplication {
|
||||
stateManager.attach(new AreaState(input));
|
||||
stateManager.attach(new LocationZoneState(input));
|
||||
stateManager.attach(new RiverEditorState(input));
|
||||
stateManager.attach(new WaterfallEditorState(input));
|
||||
stateManager.attach(new PlayToolState(input));
|
||||
stateManager.attach(new RoutineMapState(input));
|
||||
stateManager.attach(new PathNetworkEditorState(input));
|
||||
|
||||
@@ -472,14 +472,20 @@ public class SharedInput {
|
||||
/** activeLayer==12 → Temporären Spawnpunkt setzen und Spiel starten */
|
||||
public static final int LAYER_PLAY_TOOL = 12;
|
||||
|
||||
/** activeLayer==13 → Wasserfälle platzieren */
|
||||
/** activeLayer==13 → Wasserfälle platzieren (4-Klick-Quad) */
|
||||
public static final int LAYER_WATERFALL = 13;
|
||||
/** activeLayer==40 → Flüsse/Bäche (RiverEditorState) */
|
||||
public static final int LAYER_RIVER = 40;
|
||||
|
||||
// ── Wasserfall-Werkzeug ────────────────────────────────────────────────────
|
||||
public record WaterfallClick(float screenX, float screenY, boolean rightButton) {}
|
||||
public record WaterfallDrag(float screenX, float screenY) {}
|
||||
public final ConcurrentLinkedQueue<WaterfallClick> waterfallClickQueue = new ConcurrentLinkedQueue<>();
|
||||
public volatile float waterfallNewWidth = 8.0f;
|
||||
public volatile boolean undoWaterfallPointRequested = false;
|
||||
public final ConcurrentLinkedQueue<WaterfallDrag> waterfallDragQueue = new ConcurrentLinkedQueue<>();
|
||||
public volatile boolean waterfallDragEnd = false;
|
||||
public volatile boolean undoWaterfallPointRequested = false;
|
||||
public volatile boolean finalizeWaterfallRequested = false;
|
||||
public volatile float waterfallNewWidth = 8.0f;
|
||||
|
||||
/** JME → JavaFX: Info des selektierten Wasserfalls. Format: "idx|numPoints|totalLengthM" oder null. */
|
||||
public volatile String selectedWaterfallInfo = null;
|
||||
@@ -487,11 +493,18 @@ public class SharedInput {
|
||||
/** JavaFX → JME: Selektierten Wasserfall löschen. */
|
||||
public volatile boolean deleteWaterfallRequested = false;
|
||||
|
||||
// ── Wasser-Werkzeug (Polygon) ─────────────────────────────────────────────
|
||||
/** Klick im Viewport im Wasser-Modus: Punkt setzen oder selektieren. */
|
||||
// ── Wasser-Werkzeug (Quad) ────────────────────────────────────────────────
|
||||
/** Klick im Viewport im Wasser-Modus: Ecke setzen, Quad abschließen oder selektieren. */
|
||||
public record WaterClick(float screenX, float screenY, boolean rightButton) {}
|
||||
public final ConcurrentLinkedQueue<WaterClick> waterClickQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/** Maus-Drag während Handle-Bearbeitung: absolute Bildschirmposition. */
|
||||
public record WaterDrag(float screenX, float screenY) {}
|
||||
public final ConcurrentLinkedQueue<WaterDrag> waterDragQueue = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/** Maus-Release im Wasser-Modus: beendet einen laufenden Handle-Drag. */
|
||||
public volatile boolean waterDragEnd = false;
|
||||
|
||||
/**
|
||||
* JME → JavaFX: Info der selektierten Wasserfläche.
|
||||
* Format: "idx|waterHeight|pointCount" oder null.
|
||||
@@ -512,6 +525,13 @@ public class SharedInput {
|
||||
/** JavaFX → JME: neue Fließrichtung (Grad 0–359) für selektierte Wasserfläche. null = kein Auftrag. */
|
||||
public final AtomicReference<Float> pendingWaterFlowDegrees = new AtomicReference<>();
|
||||
|
||||
/**
|
||||
* JavaFX → JME: neue Rendering-Parameter für selektierte Wasserfläche.
|
||||
* float[10]: [speed, waveScale, waveAmplitude, transparency,
|
||||
* waterR, waterG, waterB, deepR, deepG, deepB]
|
||||
*/
|
||||
public final AtomicReference<float[]> pendingWaterParams = new AtomicReference<>();
|
||||
|
||||
/** JavaFX → JME: Selektierte Wasserfläche löschen. */
|
||||
public volatile boolean deleteWaterRequested = false;
|
||||
|
||||
@@ -1146,15 +1166,23 @@ public class SharedInput {
|
||||
/** JavaFX → JME3: Selektierten Item-Spawn löschen. */
|
||||
public volatile boolean deleteSelectedItemRequested = false;
|
||||
/** JME3-intern: Auswahl-Klick für ItemPlacementState (kein Objekt/keine Zone getroffen). */
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlClickQueue = new ConcurrentLinkedQueue<>();
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlClickQueue = new ConcurrentLinkedQueue<>();
|
||||
/** JME3-intern: Auswahl-Klick für LightState (kein Objekt/keine Zone getroffen). */
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlLightClickQueue = new ConcurrentLinkedQueue<>();
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlLightClickQueue = new ConcurrentLinkedQueue<>();
|
||||
/** JME3-intern: Auswahl-Klick für EmitterState (kein Objekt/keine Zone getroffen). */
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlEmitterClickQueue = new ConcurrentLinkedQueue<>();
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlEmitterClickQueue = new ConcurrentLinkedQueue<>();
|
||||
/** JME3-intern: Auswahl-Klick für WaterBodyState. */
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlWaterClickQueue = new ConcurrentLinkedQueue<>();
|
||||
/** JME3-intern: Auswahl-Klick für WaterfallEditorState. */
|
||||
public final ConcurrentLinkedQueue<ObjectClick> auswahlWaterfallClickQueue = new ConcurrentLinkedQueue<>();
|
||||
/** JME3 → JavaFX: Licht im Auswahl-Modus selektiert → Atmosphäre/Licht-Werkzeug aktivieren. */
|
||||
public volatile boolean auswahlLightSelected = false;
|
||||
public volatile boolean auswahlLightSelected = false;
|
||||
/** JME3 → JavaFX: Emitter im Auswahl-Modus selektiert → Atmosphäre/Emitter-Werkzeug aktivieren. */
|
||||
public volatile boolean auswahlEmitterSelected = false;
|
||||
public volatile boolean auswahlEmitterSelected = false;
|
||||
/** JME3 → JavaFX: Wasserfläche im Auswahl-Modus selektiert → Wasser-Werkzeug aktivieren. */
|
||||
public volatile boolean auswahlWaterSelected = false;
|
||||
/** JME3 → JavaFX: Wasserfall im Auswahl-Modus selektiert → Wasserfall-Werkzeug aktivieren. */
|
||||
public volatile boolean auswahlWaterfallSelected = false;
|
||||
|
||||
// ── Voxel-Textur-Malen ────────────────────────────────────────────────────
|
||||
/** Parallel-Queue zu textureEditQueue – wird von submitEdit(layer=4) mitbefüllt,
|
||||
|
||||
@@ -89,13 +89,27 @@ public class RiverEditorState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (input.activeLayer != SharedInput.LAYER_WATERFALL) return;
|
||||
if (input.activeLayer != SharedInput.LAYER_RIVER) return;
|
||||
|
||||
if (input.undoWaterfallPointRequested) {
|
||||
input.undoWaterfallPointRequested = false;
|
||||
undoLastPoint();
|
||||
}
|
||||
|
||||
if (input.finalizeWaterfallRequested) {
|
||||
input.finalizeWaterfallRequested = false;
|
||||
if (activeRiver >= 0) finalizeActiveRiver();
|
||||
}
|
||||
|
||||
if (input.cancelZoneDrawing) {
|
||||
input.cancelZoneDrawing = false;
|
||||
if (activeRiver >= 0) {
|
||||
cancelActiveRiver();
|
||||
} else if (selectedRiver >= 0) {
|
||||
selectRiver(-1);
|
||||
}
|
||||
}
|
||||
|
||||
if (input.deleteWaterfallRequested) {
|
||||
input.deleteWaterfallRequested = false;
|
||||
if (selectedRiver >= 0) {
|
||||
@@ -114,7 +128,7 @@ public class RiverEditorState extends BaseAppState {
|
||||
|
||||
private void handleClick(SharedInput.WaterfallClick click) {
|
||||
if (click.rightButton()) {
|
||||
finalizeActiveRiver();
|
||||
undoLastPoint();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -137,6 +151,7 @@ public class RiverEditorState extends BaseAppState {
|
||||
int nearby = findNearestRiver(hit, 8f);
|
||||
if (nearby >= 0) {
|
||||
selectRiver(nearby);
|
||||
activeRiver = nearby; // bestehenden Wasserfall bearbeiten (Punkte anhängen)
|
||||
return;
|
||||
}
|
||||
selectRiver(-1);
|
||||
@@ -163,6 +178,13 @@ public class RiverEditorState extends BaseAppState {
|
||||
rebuildActiveRibbon();
|
||||
}
|
||||
|
||||
private void cancelActiveRiver() {
|
||||
if (activeRiver < 0 || activeRiver >= rivers.size()) return;
|
||||
removeRiver(activeRiver);
|
||||
activeRiver = -1;
|
||||
selectRiver(-1);
|
||||
}
|
||||
|
||||
private void finalizeActiveRiver() {
|
||||
if (activeRiver >= 0 && activeRiver < rivers.size()) {
|
||||
if (rivers.get(activeRiver).size() < 2) {
|
||||
|
||||
@@ -729,6 +729,8 @@ public class SceneObjectState extends BaseAppState {
|
||||
input.auswahlClickQueue.offer(click);
|
||||
input.auswahlLightClickQueue.offer(click);
|
||||
input.auswahlEmitterClickQueue.offer(click);
|
||||
input.auswahlWaterClickQueue.offer(click);
|
||||
input.auswahlWaterfallClickQueue.offer(click);
|
||||
return;
|
||||
}
|
||||
if (input.activeLayer != SharedInput.LAYER_OBJECTS) { deselectAll(); return; }
|
||||
|
||||
@@ -390,6 +390,11 @@ public class TerrainEditorState extends BaseAppState {
|
||||
riverEditorState.setTerrain(terrain);
|
||||
}
|
||||
|
||||
WaterfallEditorState waterfallEditorState = app.getStateManager().getState(WaterfallEditorState.class);
|
||||
if (waterfallEditorState != null) {
|
||||
waterfallEditorState.setTerrain(terrain);
|
||||
}
|
||||
|
||||
input.loadingStatus = "Baue Szene...";
|
||||
VoxelEditorState ves = app.getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null) ves.setTerrainNode(terrain);
|
||||
@@ -1811,10 +1816,8 @@ public class TerrainEditorState extends BaseAppState {
|
||||
|
||||
int mode = input.heightTool.mode.getSelectedIndex();
|
||||
|
||||
CollisionResults hits = new CollisionResults();
|
||||
terrain.collideWith(ray, hits);
|
||||
if (hits.size() == 0) continue;
|
||||
Vector3f contact = hits.getClosestCollision().getContactPoint();
|
||||
Vector3f contact = raycastSurface(ray);
|
||||
if (contact == null) continue;
|
||||
|
||||
// Plateau-RMB: Höhe von der sichtbaren Oberfläche samplen (Basis + Voxel)
|
||||
if (mode == HeightTool.MODE_PLATEAU && edit.action() < 0) {
|
||||
@@ -1871,6 +1874,15 @@ public class TerrainEditorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4. Gebackene Voxel-Meshes (SculptedMeshEditorState) direkt raycasten
|
||||
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||
if (smes != null) {
|
||||
Vector3f sp = smes.raycastGeometry(ray);
|
||||
if (sp != null) {
|
||||
float vh = sp.y;
|
||||
if (Float.isFinite(vh)) h = Float.isFinite(h) ? Math.max(h, vh) : vh;
|
||||
}
|
||||
}
|
||||
if (Float.isFinite(h)) {
|
||||
input.heightTool.plateauHeight.setValue(h);
|
||||
input.heightTool.plateauHeightChanged = true;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,739 @@
|
||||
package de.blight.editor.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.collision.CollisionResults;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.shape.Sphere;
|
||||
import com.jme3.terrain.geomipmap.TerrainQuad;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.PlacedWater;
|
||||
import de.blight.common.PlacedWaterfall;
|
||||
import de.blight.common.WaterfallIO;
|
||||
import de.blight.editor.SharedInput;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Editor-State für das 4-Klick-Wasserfall-Werkzeug.
|
||||
*
|
||||
* Workflow:
|
||||
* 1–4 Linksklicks setzen Ecken (A=oben-links, B=oben-rechts, C=unten-rechts, D=unten-links).
|
||||
* Raycast trifft Basis-Terrain, Voxel-Geometrie, SculptedMesh und Seenkanten.
|
||||
* Selektierter Wasserfall: Ecken per Drag verschiebbar.
|
||||
* Rechtsklick / Backspace: letzten Punkt rückgängig oder Selektion aufheben.
|
||||
* Escape: Platzierung abbrechen oder Selektion aufheben.
|
||||
* Delete: selektierten Wasserfall löschen.
|
||||
*/
|
||||
public class WaterfallEditorState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WaterfallEditorState.class);
|
||||
|
||||
private enum Mode { IDLE, PLACING, EDITING }
|
||||
|
||||
private static final ColorRGBA COL_PLACING = new ColorRGBA(0.2f, 0.6f, 1.0f, 1f);
|
||||
private static final ColorRGBA COL_SELECTED = new ColorRGBA(1.0f, 0.8f, 0.1f, 1f);
|
||||
private static final ColorRGBA COL_NORMAL = new ColorRGBA(0.2f, 0.4f, 0.8f, 1f);
|
||||
private static final float HANDLE_RADIUS = 0.25f;
|
||||
private static final float HANDLE_PICK_PX = 20f;
|
||||
private static final float WATER_SNAP_PX = 30f; // screen-Pixel Snap-Radius für Seenkanten
|
||||
|
||||
private final SharedInput input;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Camera cam;
|
||||
private Node rootNode;
|
||||
private TerrainQuad terrain;
|
||||
|
||||
private Mode mode = Mode.IDLE;
|
||||
|
||||
private final List<PlacedWaterfall> waterfalls = new ArrayList<>();
|
||||
private final List<Node> wfNodes = new ArrayList<>();
|
||||
|
||||
// Platzierungs-State
|
||||
private final List<Vector3f> placingCorners = new ArrayList<>();
|
||||
private Node placingNode;
|
||||
|
||||
// Editier-State
|
||||
private int selectedIdx = -1;
|
||||
private Vector3f[] liveCorners = null; // 4 Ecken A,B,C,D live während Drag
|
||||
private List<Geometry> handleGeos = new ArrayList<>();
|
||||
private Geometry selOutline = null; // live Umriss des selektierten Wf
|
||||
private boolean isDragging = false;
|
||||
private int dragHandle = -1;
|
||||
|
||||
public WaterfallEditorState(SharedInput input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
public void setTerrain(TerrainQuad t) { this.terrain = t; }
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.cam = app.getCamera();
|
||||
this.rootNode = this.app.getRootNode();
|
||||
try {
|
||||
for (PlacedWaterfall wf : WaterfallIO.load()) {
|
||||
waterfalls.add(wf);
|
||||
wfNodes.add(buildWfNode(wf, false));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Wasserfälle nicht ladbar", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
clearPlacing();
|
||||
clearEditHandles();
|
||||
for (Node n : wfNodes) n.removeFromParent();
|
||||
wfNodes.clear();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() { setAllCull(Spatial.CullHint.Inherit); }
|
||||
@Override protected void onDisable() { setAllCull(Spatial.CullHint.Always); }
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (input.activeLayer == SharedInput.LAYER_AUSWAHL) {
|
||||
SharedInput.ObjectClick ac;
|
||||
while ((ac = input.auswahlWaterfallClickQueue.poll()) != null) handleAuswahlClick(ac);
|
||||
return;
|
||||
}
|
||||
if (input.activeLayer != SharedInput.LAYER_WATERFALL) {
|
||||
if (mode == Mode.PLACING) clearPlacing();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.cancelZoneDrawing) {
|
||||
input.cancelZoneDrawing = false;
|
||||
if (mode == Mode.PLACING) clearPlacing();
|
||||
else deselect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.undoWaterfallPointRequested) {
|
||||
input.undoWaterfallPointRequested = false;
|
||||
if (mode == Mode.PLACING && !placingCorners.isEmpty()) {
|
||||
placingCorners.remove(placingCorners.size() - 1);
|
||||
if (placingCorners.isEmpty()) clearPlacing();
|
||||
else updatePlacingVisual();
|
||||
}
|
||||
}
|
||||
|
||||
if (input.deleteWaterfallRequested) {
|
||||
input.deleteWaterfallRequested = false;
|
||||
if (selectedIdx >= 0) { removeWf(selectedIdx); deselect(); }
|
||||
}
|
||||
|
||||
SharedInput.WaterfallClick click;
|
||||
while ((click = input.waterfallClickQueue.poll()) != null) handleClick(click);
|
||||
|
||||
SharedInput.WaterfallDrag drag;
|
||||
while ((drag = input.waterfallDragQueue.poll()) != null) {
|
||||
if (isDragging) processDragMove(drag.screenX(), drag.screenY());
|
||||
}
|
||||
|
||||
if (input.waterfallDragEnd) {
|
||||
input.waterfallDragEnd = false;
|
||||
if (isDragging) finalizeDrag();
|
||||
}
|
||||
}
|
||||
|
||||
// ── Klick ─────────────────────────────────────────────────────────────────
|
||||
|
||||
private void handleClick(SharedInput.WaterfallClick click) {
|
||||
float jmeX = (float) (click.screenX() * input.viewportScaleX);
|
||||
float jmeY = cam.getHeight() - (float) (click.screenY() * input.viewportScaleY);
|
||||
|
||||
if (click.rightButton()) {
|
||||
if (mode == Mode.PLACING && !placingCorners.isEmpty()) {
|
||||
placingCorners.remove(placingCorners.size() - 1);
|
||||
if (placingCorners.isEmpty()) clearPlacing();
|
||||
else updatePlacingVisual();
|
||||
} else {
|
||||
deselect();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Selektierter Wasserfall: Handle-Treffer → Drag starten
|
||||
if (mode == Mode.EDITING && selectedIdx >= 0) {
|
||||
int h = findHandleAt(jmeX, jmeY);
|
||||
if (h >= 0) { startDrag(h); return; }
|
||||
}
|
||||
|
||||
// Raycast + See-Snapping
|
||||
Ray ray = buildRay(jmeX, jmeY);
|
||||
Vector3f pt = raycastAll(ray);
|
||||
Vector3f snapped = snapToWaterVertex(jmeX, jmeY);
|
||||
if (snapped != null) pt = snapped;
|
||||
if (pt == null) return;
|
||||
|
||||
if (mode == Mode.PLACING) {
|
||||
placingCorners.add(pt.clone());
|
||||
updatePlacingVisual();
|
||||
if (placingCorners.size() == 4) finalizeWaterfall();
|
||||
return;
|
||||
}
|
||||
|
||||
// Vorhandenen Wasserfall selektieren
|
||||
int nearby = findNearestWf(pt, 3f);
|
||||
if (nearby >= 0) { selectWf(nearby); return; }
|
||||
|
||||
// Leere Fläche: Platzierung starten
|
||||
deselect();
|
||||
mode = Mode.PLACING;
|
||||
placingCorners.add(pt.clone());
|
||||
updatePlacingVisual();
|
||||
}
|
||||
|
||||
// ── Finalisieren / Entfernen ──────────────────────────────────────────────
|
||||
|
||||
private void finalizeWaterfall() {
|
||||
Vector3f a = placingCorners.get(0), b = placingCorners.get(1);
|
||||
Vector3f c = placingCorners.get(2), d = placingCorners.get(3);
|
||||
PlacedWaterfall wf = new PlacedWaterfall(
|
||||
a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z, d.x, d.y, d.z,
|
||||
PlacedWaterfall.DEF_SPEED, PlacedWaterfall.DEF_TRANSPARENCY,
|
||||
PlacedWaterfall.DEF_R, PlacedWaterfall.DEF_G, PlacedWaterfall.DEF_B);
|
||||
clearPlacing();
|
||||
waterfalls.add(wf);
|
||||
wfNodes.add(buildWfNode(wf, false));
|
||||
save();
|
||||
selectWf(waterfalls.size() - 1);
|
||||
}
|
||||
|
||||
private void removeWf(int idx) {
|
||||
if (idx < 0 || idx >= waterfalls.size()) return;
|
||||
wfNodes.remove(idx).removeFromParent();
|
||||
waterfalls.remove(idx);
|
||||
save();
|
||||
}
|
||||
|
||||
// ── Selektion ─────────────────────────────────────────────────────────────
|
||||
|
||||
private void selectWf(int idx) {
|
||||
// Vorherige Selektion aufheben
|
||||
if (selectedIdx >= 0 && selectedIdx < wfNodes.size()) {
|
||||
wfNodes.get(selectedIdx).setCullHint(Spatial.CullHint.Inherit);
|
||||
}
|
||||
clearEditHandles();
|
||||
|
||||
selectedIdx = idx;
|
||||
mode = (idx >= 0) ? Mode.EDITING : Mode.IDLE;
|
||||
|
||||
if (idx >= 0 && idx < waterfalls.size()) {
|
||||
// wfNode ausblenden — Handles + selOutline übernehmen die Darstellung
|
||||
wfNodes.get(idx).setCullHint(Spatial.CullHint.Always);
|
||||
PlacedWaterfall wf = waterfalls.get(idx);
|
||||
liveCorners = new Vector3f[]{
|
||||
new Vector3f(wf.ax(), wf.ay(), wf.az()),
|
||||
new Vector3f(wf.bx(), wf.by(), wf.bz()),
|
||||
new Vector3f(wf.cx(), wf.cy(), wf.cz()),
|
||||
new Vector3f(wf.dx(), wf.dy(), wf.dz())
|
||||
};
|
||||
buildHandles();
|
||||
buildSelOutline();
|
||||
}
|
||||
publishSelection(idx);
|
||||
}
|
||||
|
||||
private void deselect() {
|
||||
if (selectedIdx >= 0 && selectedIdx < wfNodes.size()) {
|
||||
wfNodes.get(selectedIdx).setCullHint(Spatial.CullHint.Inherit);
|
||||
}
|
||||
selectedIdx = -1;
|
||||
mode = Mode.IDLE;
|
||||
liveCorners = null;
|
||||
isDragging = false;
|
||||
dragHandle = -1;
|
||||
clearEditHandles();
|
||||
publishSelection(-1);
|
||||
}
|
||||
|
||||
private void publishSelection(int idx) {
|
||||
input.selectedWaterfallInfo = (idx >= 0 && idx < waterfalls.size())
|
||||
? (idx + "|4|" + String.format(java.util.Locale.ROOT, "%.1f", quadWidth(waterfalls.get(idx))))
|
||||
: null;
|
||||
input.waterfallSelectionChanged = true;
|
||||
}
|
||||
|
||||
// ── Drag ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private void startDrag(int h) {
|
||||
isDragging = true;
|
||||
dragHandle = h;
|
||||
}
|
||||
|
||||
private void processDragMove(float screenX, float screenY) {
|
||||
if (!isDragging || selectedIdx < 0 || liveCorners == null) return;
|
||||
float jmeX = screenX * (float) input.viewportScaleX;
|
||||
float jmeY = cam.getHeight() - screenY * (float) input.viewportScaleY;
|
||||
Vector3f pt = raycastAll(buildRay(jmeX, jmeY));
|
||||
Vector3f snapped = snapToWaterVertex(jmeX, jmeY);
|
||||
if (snapped != null) pt = snapped;
|
||||
if (pt == null) return;
|
||||
liveCorners[dragHandle].set(pt);
|
||||
rebuildHandlePositions();
|
||||
rebuildSelOutline();
|
||||
}
|
||||
|
||||
private void finalizeDrag() {
|
||||
isDragging = false;
|
||||
dragHandle = -1;
|
||||
if (selectedIdx < 0 || liveCorners == null) return;
|
||||
Vector3f a = liveCorners[0], b = liveCorners[1], c = liveCorners[2], d = liveCorners[3];
|
||||
PlacedWaterfall old = waterfalls.get(selectedIdx);
|
||||
PlacedWaterfall updated = new PlacedWaterfall(
|
||||
a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z, d.x, d.y, d.z,
|
||||
old.speed(), old.transparency(), old.colorR(), old.colorG(), old.colorB());
|
||||
waterfalls.set(selectedIdx, updated);
|
||||
// wfNode (verborgen) auf neue Positionen updaten, damit er beim Abwählen stimmt
|
||||
Node old2 = wfNodes.get(selectedIdx);
|
||||
old2.removeFromParent();
|
||||
Node fresh = buildWfNode(updated, false);
|
||||
fresh.setCullHint(Spatial.CullHint.Always); // bleibt verborgen, solange selektiert
|
||||
wfNodes.set(selectedIdx, fresh);
|
||||
save();
|
||||
publishSelection(selectedIdx);
|
||||
}
|
||||
|
||||
// ── Handles ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void buildHandles() {
|
||||
if (liveCorners == null) return;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Geometry g = buildSphere("wf_h" + i, liveCorners[i], COL_SELECTED);
|
||||
handleGeos.add(g);
|
||||
rootNode.attachChild(g);
|
||||
}
|
||||
}
|
||||
|
||||
private void rebuildHandlePositions() {
|
||||
for (int i = 0; i < 4 && i < handleGeos.size(); i++) {
|
||||
handleGeos.get(i).setLocalTranslation(liveCorners[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private void clearEditHandles() {
|
||||
for (Geometry g : handleGeos) g.removeFromParent();
|
||||
handleGeos.clear();
|
||||
if (selOutline != null) { selOutline.removeFromParent(); selOutline = null; }
|
||||
liveCorners = null;
|
||||
}
|
||||
|
||||
private int findHandleAt(float jmeX, float jmeY) {
|
||||
if (liveCorners == null) return -1;
|
||||
float bestSq = HANDLE_PICK_PX * HANDLE_PICK_PX;
|
||||
int best = -1;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
Vector3f sc = cam.getScreenCoordinates(liveCorners[i]);
|
||||
if (sc.z <= 0 || sc.z >= 1) continue;
|
||||
float dx = sc.x - jmeX, dy = sc.y - jmeY;
|
||||
float d = dx * dx + dy * dy;
|
||||
if (d < bestSq) { bestSq = d; best = i; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private void buildSelOutline() {
|
||||
if (selOutline != null) { selOutline.removeFromParent(); selOutline = null; }
|
||||
if (liveCorners == null) return;
|
||||
selOutline = buildLineLoop(toFlatArray(liveCorners), COL_SELECTED);
|
||||
rootNode.attachChild(selOutline);
|
||||
}
|
||||
|
||||
private void rebuildSelOutline() {
|
||||
if (selOutline == null || liveCorners == null) return;
|
||||
float[] pts = toFlatArray(liveCorners);
|
||||
selOutline.getMesh().setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pts));
|
||||
selOutline.getMesh().updateBound();
|
||||
}
|
||||
|
||||
// ── See-Kanten-Snapping ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Prüft ob ein Seekanten-Vertex näher als WATER_SNAP_PX Pixel am Cursor liegt.
|
||||
* Gibt den 3D-Vertex zurück oder null.
|
||||
*/
|
||||
private Vector3f snapToWaterVertex(float jmeX, float jmeY) {
|
||||
WaterBodyState wbs = getStateManager().getState(WaterBodyState.class);
|
||||
if (wbs == null) return null;
|
||||
List<PlacedWater> bodies = wbs.getPlacedBodies();
|
||||
if (bodies.isEmpty()) return null;
|
||||
|
||||
float bestSq = WATER_SNAP_PX * WATER_SNAP_PX;
|
||||
Vector3f best = null;
|
||||
for (PlacedWater body : bodies) {
|
||||
float[] xs = body.pointsX(), ys = body.pointsY(), zs = body.pointsZ();
|
||||
for (int i = 0; i < xs.length; i++) {
|
||||
Vector3f sc = cam.getScreenCoordinates(new Vector3f(xs[i], ys[i], zs[i]));
|
||||
if (sc.z <= 0 || sc.z >= 1) continue;
|
||||
float dx = sc.x - jmeX, dy = sc.y - jmeY;
|
||||
float d = dx * dx + dy * dy;
|
||||
if (d < bestSq) { bestSq = d; best = new Vector3f(xs[i], ys[i], zs[i]); }
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
// ── Raycast ───────────────────────────────────────────────────────────────
|
||||
|
||||
private Ray buildRay(float jmeX, float jmeY) {
|
||||
Vector3f near = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 0f);
|
||||
Vector3f far = cam.getWorldCoordinates(new Vector2f(jmeX, jmeY), 1f);
|
||||
return new Ray(near, far.subtract(near).normalizeLocal());
|
||||
}
|
||||
|
||||
private Vector3f raycastAll(Ray ray) {
|
||||
Vector3f best = null;
|
||||
float bestDistSq = Float.MAX_VALUE;
|
||||
|
||||
if (terrain != null) {
|
||||
CollisionResults res = new CollisionResults();
|
||||
terrain.collideWith(ray, res);
|
||||
if (res.size() > 0) {
|
||||
Vector3f p = res.getClosestCollision().getContactPoint();
|
||||
float d = ray.origin.distanceSquared(p);
|
||||
if (d < bestDistSq) { best = p; bestDistSq = d; }
|
||||
}
|
||||
}
|
||||
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
if (ves != null) {
|
||||
Vector3f vp = ves.raycastVoxelGeometry(ray);
|
||||
if (vp != null) {
|
||||
float d = ray.origin.distanceSquared(vp);
|
||||
if (d < bestDistSq) { best = vp; bestDistSq = d; }
|
||||
}
|
||||
}
|
||||
|
||||
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||
if (smes != null) {
|
||||
Vector3f sp = smes.raycastGeometry(ray);
|
||||
if (sp != null) {
|
||||
float d = ray.origin.distanceSquared(sp);
|
||||
if (d < bestDistSq) { best = sp; }
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
// ── Visuals ───────────────────────────────────────────────────────────────
|
||||
|
||||
private int findNearestWf(Vector3f pos, float maxDist) {
|
||||
int best = -1;
|
||||
float bestD = maxDist * maxDist;
|
||||
for (int i = 0; i < waterfalls.size(); i++) {
|
||||
PlacedWaterfall wf = waterfalls.get(i);
|
||||
float[] pts = { wf.ax(), wf.ay(), wf.az(), wf.bx(), wf.by(), wf.bz(),
|
||||
wf.cx(), wf.cy(), wf.cz(), wf.dx(), wf.dy(), wf.dz() };
|
||||
for (int k = 0; k < 4; k++) {
|
||||
float dx = pos.x - pts[k*3], dy = pos.y - pts[k*3+1], dz = pos.z - pts[k*3+2];
|
||||
float d2 = dx*dx + dy*dy + dz*dz;
|
||||
if (d2 < bestD) { bestD = d2; best = i; }
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
private Node buildWfNode(PlacedWaterfall wf, boolean selected) {
|
||||
Node n = new Node("wf_node");
|
||||
ColorRGBA col = selected ? COL_SELECTED : COL_NORMAL;
|
||||
float[] pts = { wf.ax(), wf.ay(), wf.az(), wf.bx(), wf.by(), wf.bz(),
|
||||
wf.cx(), wf.cy(), wf.cz(), wf.dx(), wf.dy(), wf.dz() };
|
||||
n.attachChild(buildFillQuad(wf));
|
||||
n.attachChild(buildLineLoop(pts, col));
|
||||
rootNode.attachChild(n);
|
||||
return n;
|
||||
}
|
||||
|
||||
// Muss mit WaterfallState (game) synchron gehalten werden
|
||||
private static final int PREVIEW_ROWS = 24;
|
||||
private static final int PREVIEW_COLS = 6;
|
||||
private static final float PREVIEW_BULGE_MAX = 1.2f;
|
||||
private static final float PREVIEW_V_RANGE = 0.30f;
|
||||
|
||||
private Geometry buildFillQuad(PlacedWaterfall wf) {
|
||||
Mesh mesh = buildBulgeMesh(wf);
|
||||
Geometry g = new Geometry("wf_fill", mesh);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(1f, 1f, 1f, 0.25f));
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mat.getAdditionalRenderState().setDepthWrite(false);
|
||||
g.setMaterial(mat);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
return g;
|
||||
}
|
||||
|
||||
private static Mesh buildBulgeMesh(PlacedWaterfall wf) {
|
||||
Vector3f a = new Vector3f(wf.ax(), wf.ay(), wf.az());
|
||||
Vector3f b = new Vector3f(wf.bx(), wf.by(), wf.bz());
|
||||
Vector3f c = new Vector3f(wf.cx(), wf.cy(), wf.cz());
|
||||
Vector3f d = new Vector3f(wf.dx(), wf.dy(), wf.dz());
|
||||
|
||||
Vector3f n1 = d.subtract(a).cross(b.subtract(a)).normalizeLocal();
|
||||
Vector3f n2 = b.subtract(c).cross(d.subtract(c)).normalizeLocal();
|
||||
Vector3f avgNorm = n1.add(n2).normalizeLocal();
|
||||
if (avgNorm.lengthSquared() < 1e-4f) avgNorm.set(0f, 0f, 1f);
|
||||
Vector3f outward = new Vector3f(avgNorm.x, 0f, avgNorm.z);
|
||||
if (outward.lengthSquared() < 1e-4f) outward.set(avgNorm);
|
||||
else outward.normalizeLocal();
|
||||
|
||||
int vRows = PREVIEW_ROWS + 1, vCols = PREVIEW_COLS + 1;
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(vRows * vCols * 3);
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(PREVIEW_ROWS * PREVIEW_COLS * 6);
|
||||
|
||||
for (int row = 0; row < vRows; row++) {
|
||||
float v = (float) row / PREVIEW_ROWS;
|
||||
float bulge = (v < PREVIEW_V_RANGE)
|
||||
? PREVIEW_BULGE_MAX * (float) Math.sin(Math.PI / 2f * v / PREVIEW_V_RANGE)
|
||||
: PREVIEW_BULGE_MAX;
|
||||
for (int col = 0; col < vCols; col++) {
|
||||
float u = (float) col / PREVIEW_COLS;
|
||||
Vector3f p = a.mult(1f - u).add(b.mult(u))
|
||||
.mult(1f - v)
|
||||
.add(d.mult(1f - u).add(c.mult(u)).mult(v));
|
||||
p.addLocal(outward.mult(bulge));
|
||||
pos.put(p.x).put(p.y).put(p.z);
|
||||
}
|
||||
}
|
||||
|
||||
for (int row = 0; row < PREVIEW_ROWS; row++) {
|
||||
for (int col = 0; col < PREVIEW_COLS; col++) {
|
||||
int i0 = row * vCols + col, i1 = i0 + 1, i2 = i0 + vCols, i3 = i2 + 1;
|
||||
idx.put(i0).put(i2).put(i1);
|
||||
idx.put(i1).put(i2).put(i3);
|
||||
}
|
||||
}
|
||||
|
||||
pos.rewind(); idx.rewind();
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound();
|
||||
mesh.updateCounts();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private void updatePlacingVisual() {
|
||||
if (placingNode == null) {
|
||||
placingNode = new Node("wf_placing");
|
||||
rootNode.attachChild(placingNode);
|
||||
}
|
||||
placingNode.detachAllChildren();
|
||||
for (Vector3f p : placingCorners) {
|
||||
placingNode.attachChild(buildSphere("wf_ph", p, COL_PLACING));
|
||||
}
|
||||
if (placingCorners.size() >= 2) {
|
||||
float[] pts = new float[placingCorners.size() * 3];
|
||||
for (int i = 0; i < placingCorners.size(); i++) {
|
||||
pts[i*3] = placingCorners.get(i).x;
|
||||
pts[i*3+1] = placingCorners.get(i).y;
|
||||
pts[i*3+2] = placingCorners.get(i).z;
|
||||
}
|
||||
placingNode.attachChild(buildLineStrip(pts, COL_PLACING));
|
||||
}
|
||||
}
|
||||
|
||||
private void clearPlacing() {
|
||||
placingCorners.clear();
|
||||
if (placingNode != null) { placingNode.removeFromParent(); placingNode = null; }
|
||||
if (mode == Mode.PLACING) mode = Mode.IDLE;
|
||||
}
|
||||
|
||||
private void setAllCull(Spatial.CullHint hint) {
|
||||
for (int i = 0; i < wfNodes.size(); i++) {
|
||||
// Selektierter bleibt Always wenn hint=Always, sonst Inherit
|
||||
if (hint == Spatial.CullHint.Always || i != selectedIdx) {
|
||||
wfNodes.get(i).setCullHint(hint);
|
||||
}
|
||||
}
|
||||
if (placingNode != null) placingNode.setCullHint(hint);
|
||||
for (Geometry g : handleGeos) g.setCullHint(hint);
|
||||
if (selOutline != null) selOutline.setCullHint(hint);
|
||||
}
|
||||
|
||||
// ── Geometry-Helfer ───────────────────────────────────────────────────────
|
||||
|
||||
private Geometry buildSphere(String name, Vector3f pos, ColorRGBA col) {
|
||||
Sphere s = new Sphere(8, 8, HANDLE_RADIUS);
|
||||
Geometry g = new Geometry(name, s);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", col);
|
||||
g.setMaterial(mat);
|
||||
g.setLocalTranslation(pos);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
return g;
|
||||
}
|
||||
|
||||
private Geometry buildLineLoop(float[] pts, ColorRGBA col) {
|
||||
int n = pts.length / 3;
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(n * 2);
|
||||
for (int i = 0; i < n; i++) { idx.put(i).put((i + 1) % n); }
|
||||
idx.rewind();
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setMode(Mesh.Mode.Lines);
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pts));
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 2, idx);
|
||||
mesh.updateBound(); mesh.updateCounts();
|
||||
Geometry g = new Geometry("wf_loop", mesh);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", col);
|
||||
mat.getAdditionalRenderState().setLineWidth(2f);
|
||||
g.setMaterial(mat);
|
||||
return g;
|
||||
}
|
||||
|
||||
private Geometry buildLineStrip(float[] pts, ColorRGBA col) {
|
||||
int n = pts.length / 3;
|
||||
IntBuffer idx = BufferUtils.createIntBuffer((n - 1) * 2);
|
||||
for (int i = 0; i < n - 1; i++) { idx.put(i).put(i + 1); }
|
||||
idx.rewind();
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setMode(Mesh.Mode.Lines);
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(pts));
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 2, idx);
|
||||
mesh.updateBound(); mesh.updateCounts();
|
||||
Geometry g = new Geometry("wf_strip", mesh);
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", col);
|
||||
mat.getAdditionalRenderState().setLineWidth(2f);
|
||||
g.setMaterial(mat);
|
||||
return g;
|
||||
}
|
||||
|
||||
private static float[] toFlatArray(Vector3f[] corners) {
|
||||
float[] pts = new float[corners.length * 3];
|
||||
for (int i = 0; i < corners.length; i++) {
|
||||
pts[i*3] = corners[i].x; pts[i*3+1] = corners[i].y; pts[i*3+2] = corners[i].z;
|
||||
}
|
||||
return pts;
|
||||
}
|
||||
|
||||
// ── Auswahl-Modus ─────────────────────────────────────────────────────────
|
||||
|
||||
private void handleAuswahlClick(SharedInput.ObjectClick click) {
|
||||
if (click.rightButton()) return;
|
||||
float jmeX = click.screenX() * (float) input.viewportScaleX;
|
||||
float jmeY = cam.getHeight() - click.screenY() * (float) input.viewportScaleY;
|
||||
Ray ray = buildRay(jmeX, jmeY);
|
||||
for (int i = 0; i < waterfalls.size(); i++) {
|
||||
if (rayHitsWaterfall(ray, waterfalls.get(i))) {
|
||||
selectWf(i);
|
||||
input.auswahlWaterfallSelected = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean rayHitsWaterfall(Ray ray, PlacedWaterfall wf) {
|
||||
Vector3f a = new Vector3f(wf.ax(), wf.ay(), wf.az());
|
||||
Vector3f b = new Vector3f(wf.bx(), wf.by(), wf.bz());
|
||||
Vector3f c = new Vector3f(wf.cx(), wf.cy(), wf.cz());
|
||||
Vector3f d = new Vector3f(wf.dx(), wf.dy(), wf.dz());
|
||||
return rayHitsTriangle(ray, a, d, b) || rayHitsTriangle(ray, b, d, c);
|
||||
}
|
||||
|
||||
/** Möller–Trumbore ray–triangle intersection, beide Seiten (kein Backface-Culling). */
|
||||
private static boolean rayHitsTriangle(Ray ray, Vector3f v0, Vector3f v1, Vector3f v2) {
|
||||
Vector3f e1 = v1.subtract(v0);
|
||||
Vector3f e2 = v2.subtract(v0);
|
||||
Vector3f h = ray.direction.cross(e2);
|
||||
float det = e1.dot(h);
|
||||
if (Math.abs(det) < 1e-6f) return false;
|
||||
float inv = 1f / det;
|
||||
Vector3f s = ray.origin.subtract(v0);
|
||||
float u = inv * s.dot(h);
|
||||
if (u < 0f || u > 1f) return false;
|
||||
Vector3f q = s.cross(e1);
|
||||
float v = inv * ray.direction.dot(q);
|
||||
if (v < 0f || u + v > 1f) return false;
|
||||
float t = inv * e2.dot(q);
|
||||
return t > 1e-4f;
|
||||
}
|
||||
|
||||
// ── Externaler Sync (von WaterBodyState) ──────────────────────────────────
|
||||
|
||||
/**
|
||||
* Verschiebt alle Wasserfall-Ecken die sich näher als {@code threshold} Meter
|
||||
* an (ox,oy,oz) befinden auf die neue Position (nx,ny,nz).
|
||||
* Gibt true zurück wenn mindestens eine Ecke geändert wurde (zum Batching des save()-Aufrufs).
|
||||
* Wird von WaterBodyState.finalizeDrag() aufgerufen.
|
||||
*/
|
||||
public boolean syncCornersFromVertex(float ox, float oy, float oz,
|
||||
float nx, float ny, float nz, float threshold) {
|
||||
float t2 = threshold * threshold;
|
||||
boolean anyChanged = false;
|
||||
for (int i = 0; i < waterfalls.size(); i++) {
|
||||
PlacedWaterfall wf = waterfalls.get(i);
|
||||
float[] cx = { wf.ax(), wf.bx(), wf.cx(), wf.dx() };
|
||||
float[] cy = { wf.ay(), wf.by(), wf.cy(), wf.dy() };
|
||||
float[] cz = { wf.az(), wf.bz(), wf.cz(), wf.dz() };
|
||||
boolean wfChanged = false;
|
||||
for (int c = 0; c < 4; c++) {
|
||||
float ddx = cx[c] - ox, ddy = cy[c] - oy, ddz = cz[c] - oz;
|
||||
if (ddx*ddx + ddy*ddy + ddz*ddz < t2) {
|
||||
cx[c] = nx; cy[c] = ny; cz[c] = nz;
|
||||
wfChanged = true;
|
||||
}
|
||||
}
|
||||
if (wfChanged) {
|
||||
PlacedWaterfall upd = new PlacedWaterfall(
|
||||
cx[0], cy[0], cz[0], cx[1], cy[1], cz[1],
|
||||
cx[2], cy[2], cz[2], cx[3], cy[3], cz[3],
|
||||
wf.speed(), wf.transparency(), wf.colorR(), wf.colorG(), wf.colorB());
|
||||
waterfalls.set(i, upd);
|
||||
boolean isSelected = (i == selectedIdx);
|
||||
wfNodes.remove(i).removeFromParent();
|
||||
Node fresh = buildWfNode(upd, false);
|
||||
wfNodes.add(i, fresh);
|
||||
if (isSelected) {
|
||||
fresh.setCullHint(Spatial.CullHint.Always);
|
||||
if (liveCorners != null) {
|
||||
liveCorners[0].set(upd.ax(), upd.ay(), upd.az());
|
||||
liveCorners[1].set(upd.bx(), upd.by(), upd.bz());
|
||||
liveCorners[2].set(upd.cx(), upd.cy(), upd.cz());
|
||||
liveCorners[3].set(upd.dx(), upd.dy(), upd.dz());
|
||||
rebuildHandlePositions();
|
||||
rebuildSelOutline();
|
||||
}
|
||||
}
|
||||
anyChanged = true;
|
||||
}
|
||||
}
|
||||
return anyChanged;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
WaterfallIO.save(waterfalls);
|
||||
} catch (Exception e) {
|
||||
log.error("Wasserfall-Speichern fehlgeschlagen", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static float quadWidth(PlacedWaterfall wf) {
|
||||
float dx = wf.bx() - wf.ax(), dy = wf.by() - wf.ay(), dz = wf.bz() - wf.az();
|
||||
return (float) Math.sqrt(dx*dx + dy*dy + dz*dz);
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,7 @@ public class MapObjectsView extends VBox {
|
||||
loadLights();
|
||||
loadEmitters();
|
||||
loadWaterBodies();
|
||||
loadWaterfalls();
|
||||
loadRivers();
|
||||
loadSoundAreas();
|
||||
loadAreas();
|
||||
@@ -203,17 +204,35 @@ public class MapObjectsView extends VBox {
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private void loadWaterfalls() {
|
||||
try {
|
||||
List<PlacedWaterfall> list = WaterfallIO.load();
|
||||
TreeItem<String> group = group("Wasserfälle", list.size());
|
||||
for (int idx = 0; idx < list.size(); idx++) {
|
||||
PlacedWaterfall wf = list.get(idx);
|
||||
float cx = (wf.ax() + wf.bx() + wf.cx() + wf.dx()) / 4f;
|
||||
float cy = (wf.ay() + wf.by() + wf.cy() + wf.dy()) / 4f;
|
||||
float cz = (wf.az() + wf.bz() + wf.cz() + wf.dz()) / 4f;
|
||||
TreeItem<String> item = leaf("Wasserfall #" + (idx + 1) + " " + pos(cx, cy, cz));
|
||||
entryMap.put(item, new Entry(cx, cy + 5f, cz, "waterfall", wf, idx));
|
||||
group.getChildren().add(item);
|
||||
}
|
||||
treeRoot.getChildren().add(group);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
private void loadRivers() {
|
||||
try {
|
||||
List<List<RiverPoint>> list = RiverIO.load();
|
||||
TreeItem<String> group = group("Wasserfälle", list.size());
|
||||
if (list.isEmpty()) return;
|
||||
TreeItem<String> group = group("Flüsse (veraltet)", list.size());
|
||||
for (int idx = 0; idx < list.size(); idx++) {
|
||||
List<RiverPoint> river = list.get(idx);
|
||||
float cx = 0f, cy = 0f, cz = 0f;
|
||||
for (RiverPoint pt : river) { cx += pt.x(); cy += pt.y(); cz += pt.z(); }
|
||||
if (!river.isEmpty()) { cx /= river.size(); cy /= river.size(); cz /= river.size(); }
|
||||
TreeItem<String> item = leaf("Wasserfall #" + (idx + 1) + " " + pos(cx, cy, cz));
|
||||
entryMap.put(item, new Entry(cx, cy + 5f, cz, "waterfall", river, idx));
|
||||
TreeItem<String> item = leaf("Fluss #" + (idx + 1) + " " + pos(cx, cy, cz));
|
||||
entryMap.put(item, new Entry(cx, cy + 5f, cz, "river", river, idx));
|
||||
group.getChildren().add(item);
|
||||
}
|
||||
treeRoot.getChildren().add(group);
|
||||
|
||||
Reference in New Issue
Block a user