Weiter daran gearbeitet, die welt aus dem editor ins game zu bringen
This commit is contained in:
@@ -44,12 +44,17 @@ public class EditorApp extends Application {
|
||||
static final int VP_WIDTH = 1024;
|
||||
static final int VP_HEIGHT = 640;
|
||||
|
||||
private static final Path ASSET_ROOT = Paths.get("editor-assets");
|
||||
private static final Path ASSET_ROOT = ProjectRoot.resolve("editor-assets");
|
||||
|
||||
private final SharedInput input = new SharedInput();
|
||||
private WritableImage jfxImage;
|
||||
private ImageView viewport;
|
||||
private Label statusLabel;
|
||||
private Label camCoordsLabel;
|
||||
private HBox consoleBar;
|
||||
private TextField consoleField;
|
||||
private boolean consoleOpen = false;
|
||||
private boolean launchGameAfterSave = false;
|
||||
private VBox toolPanel;
|
||||
private BorderPane root;
|
||||
private VBox assetPanel;
|
||||
@@ -129,7 +134,7 @@ public class EditorApp extends Application {
|
||||
root.setLeft(assetPanel);
|
||||
root.setCenter(worldViewport);
|
||||
root.setRight(toolPanel);
|
||||
root.setBottom(buildStatusBar());
|
||||
root.setBottom(buildBottomBox());
|
||||
|
||||
Scene scene = new Scene(root, 1280, 760);
|
||||
scene.setOnKeyPressed(e -> handleKeyPress(e.getCode(), true));
|
||||
@@ -140,14 +145,21 @@ public class EditorApp extends Application {
|
||||
stage.setScene(scene);
|
||||
stage.setMinWidth(900);
|
||||
stage.setMinHeight(600);
|
||||
stage.setOnCloseRequest(e -> Platform.exit());
|
||||
stage.setOnCloseRequest(e -> { saveCameraPrefs(); Platform.exit(); });
|
||||
stage.setMaximized(true);
|
||||
stage.show();
|
||||
|
||||
javafx.animation.Timeline statusPoller = new javafx.animation.Timeline(
|
||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
||||
String saveMsg = input.saveStatusMsg;
|
||||
if (saveMsg != null) { input.saveStatusMsg = null; setStatus(saveMsg); }
|
||||
if (saveMsg != null) {
|
||||
input.saveStatusMsg = null;
|
||||
setStatus(saveMsg);
|
||||
if (launchGameAfterSave) {
|
||||
launchGameAfterSave = false;
|
||||
startGameProcess();
|
||||
}
|
||||
}
|
||||
|
||||
String treeMsg = input.treeGenStatusMsg;
|
||||
if (treeMsg != null) { input.treeGenStatusMsg = null; setStatus(treeMsg); }
|
||||
@@ -178,10 +190,31 @@ public class EditorApp extends Application {
|
||||
input.objectSelectionChanged = false;
|
||||
updateObjectPanel(input.selectedObjectInfo);
|
||||
}
|
||||
|
||||
// Kamera-Koordinaten aktualisieren
|
||||
camCoordsLabel.setText(String.format(
|
||||
"X:%.1f Y:%.1f Z:%.1f Yaw:%.0f° Pitch:%.0f°",
|
||||
input.camX, input.camY, input.camZ,
|
||||
input.camYaw, input.camPitch));
|
||||
|
||||
// Konsolen-Antwort anzeigen
|
||||
String consoleMsg = input.consoleOutput;
|
||||
if (consoleMsg != null) { input.consoleOutput = null; setStatus(consoleMsg); }
|
||||
})
|
||||
);
|
||||
statusPoller.setCycleCount(javafx.animation.Timeline.INDEFINITE);
|
||||
statusPoller.play();
|
||||
|
||||
javafx.animation.Timeline autoSave = new javafx.animation.Timeline(
|
||||
new javafx.animation.KeyFrame(javafx.util.Duration.seconds(60), ev -> {
|
||||
if (!input.saveRequested) {
|
||||
input.saveRequested = true;
|
||||
setStatus("Auto-Speicherung…");
|
||||
}
|
||||
})
|
||||
);
|
||||
autoSave.setCycleCount(javafx.animation.Timeline.INDEFINITE);
|
||||
autoSave.play();
|
||||
}
|
||||
|
||||
// ── Modus-Wechsel ────────────────────────────────────────────────────────
|
||||
@@ -246,7 +279,7 @@ public class EditorApp extends Application {
|
||||
|
||||
ToolBar toolBar = new ToolBar();
|
||||
ToggleButton baseBtn = new ToggleButton("▲▼ Basis-Terrain");
|
||||
ToggleButton upperBtn = new ToggleButton("⛰ Obere Schicht");
|
||||
ToggleButton upperBtn = new ToggleButton("⛰ Gebirge");
|
||||
ToggleButton holesBtn = new ToggleButton("⬤ Höhlen/Löcher");
|
||||
ToggleButton grassBtn = new ToggleButton("🌿 Gras");
|
||||
ToggleButton textureBtn = new ToggleButton("🎨 Textur");
|
||||
@@ -305,17 +338,28 @@ public class EditorApp extends Application {
|
||||
root.setRight(buildObjectEditPanel());
|
||||
});
|
||||
|
||||
CheckBox visibleCB = new CheckBox("Obere Schicht sichtbar");
|
||||
CheckBox visibleCB = new CheckBox("Gebirge sichtbar");
|
||||
visibleCB.setSelected(true);
|
||||
visibleCB.setOnAction(e -> input.upperLayerVisible = visibleCB.isSelected());
|
||||
|
||||
Label hint = new Label("WASD/QE: Kamera | Mitte-Drag / L+R-Drag: Drehen | L-Klick: hoch | R-Klick: tief");
|
||||
hint.setStyle("-fx-text-fill: #555;");
|
||||
|
||||
Region toolbarSpacer = new Region();
|
||||
HBox.setHgrow(toolbarSpacer, Priority.ALWAYS);
|
||||
|
||||
Button playBtn = new Button("▶ Spielen");
|
||||
playBtn.setStyle(
|
||||
"-fx-background-color: #2d8a3e; -fx-text-fill: white; " +
|
||||
"-fx-font-weight: bold; -fx-padding: 4 12 4 12;");
|
||||
playBtn.setOnAction(e -> launchGame());
|
||||
|
||||
toolBar.getItems().addAll(baseBtn, upperBtn, holesBtn, grassBtn, textureBtn,
|
||||
new Separator(Orientation.VERTICAL), objPlaceBtn, objEditBtn,
|
||||
new Separator(Orientation.VERTICAL), visibleCB,
|
||||
new Separator(Orientation.VERTICAL), hint);
|
||||
new Separator(Orientation.VERTICAL), hint,
|
||||
toolbarSpacer,
|
||||
new Separator(Orientation.VERTICAL), playBtn);
|
||||
|
||||
worldToolBar = toolBar;
|
||||
return new VBox(menuBar, toolBar);
|
||||
@@ -1819,13 +1863,118 @@ public class EditorApp extends Application {
|
||||
|
||||
// ── Statusleiste ─────────────────────────────────────────────────────────
|
||||
|
||||
private HBox buildStatusBar() {
|
||||
private VBox buildBottomBox() {
|
||||
// Status-Leiste
|
||||
statusLabel = new Label("Bereit | Werkzeug: Höhe | WASD/QE: Bewegen | Mitte-Drag / L+R-Drag: Drehen");
|
||||
statusLabel.setPadding(new Insets(3, 8, 3, 8));
|
||||
statusLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #333;");
|
||||
HBox bar = new HBox(statusLabel);
|
||||
bar.setStyle("-fx-background-color: #e8e8e8; -fx-border-color: #bbb; -fx-border-width: 1 0 0 0;");
|
||||
return bar;
|
||||
|
||||
camCoordsLabel = new Label("X:0.0 Y:0.0 Z:0.0 Yaw:0° Pitch:0°");
|
||||
camCoordsLabel.setPadding(new Insets(3, 8, 3, 8));
|
||||
camCoordsLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #555; -fx-font-family: monospace;");
|
||||
|
||||
Region spacer = new Region();
|
||||
HBox.setHgrow(spacer, Priority.ALWAYS);
|
||||
|
||||
HBox statusBar = new HBox(statusLabel, spacer, camCoordsLabel);
|
||||
statusBar.setStyle("-fx-background-color: #e8e8e8; -fx-border-color: #bbb; -fx-border-width: 1 0 0 0;");
|
||||
|
||||
// Konsolen-Panel (anfangs ausgeblendet)
|
||||
Label prompt = new Label(">");
|
||||
prompt.setStyle("-fx-text-fill: #7ec8e3; -fx-font-family: monospace; -fx-font-size: 12; -fx-padding: 0 4 0 0;");
|
||||
|
||||
consoleField = new TextField();
|
||||
consoleField.setStyle(
|
||||
"-fx-background-color: transparent; -fx-text-fill: #f0f0f0; " +
|
||||
"-fx-font-family: monospace; -fx-font-size: 12; -fx-border-width: 0;");
|
||||
consoleField.setPromptText("Befehl eingeben… (Enter = ausführen, Esc = schließen)");
|
||||
HBox.setHgrow(consoleField, Priority.ALWAYS);
|
||||
|
||||
consoleField.setOnAction(e -> {
|
||||
String cmd = consoleField.getText().trim();
|
||||
if (!cmd.isEmpty()) input.pendingCommand = cmd;
|
||||
toggleConsole();
|
||||
});
|
||||
consoleField.setOnKeyPressed(e -> {
|
||||
if (e.getCode() == KeyCode.ESCAPE
|
||||
|| e.getCode() == KeyCode.DEAD_CIRCUMFLEX
|
||||
|| e.getCode() == KeyCode.CIRCUMFLEX) {
|
||||
toggleConsole();
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
consoleBar = new HBox(4, prompt, consoleField);
|
||||
consoleBar.setStyle(
|
||||
"-fx-background-color: #1e1e1e; -fx-padding: 3 8 3 8; " +
|
||||
"-fx-border-color: #555; -fx-border-width: 1 0 0 0;");
|
||||
consoleBar.setVisible(false);
|
||||
consoleBar.setManaged(false);
|
||||
|
||||
return new VBox(statusBar, consoleBar);
|
||||
}
|
||||
|
||||
private void toggleConsole() {
|
||||
boolean show = !consoleOpen;
|
||||
consoleOpen = show;
|
||||
consoleBar.setVisible(show);
|
||||
consoleBar.setManaged(show);
|
||||
if (show) {
|
||||
// Bewegungstasten loslassen, damit keine Dauerbewegung entsteht
|
||||
input.forward = input.backward = input.left = input.right = input.up = input.down = false;
|
||||
consoleField.clear();
|
||||
consoleField.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
private void launchGame() {
|
||||
if (launchGameAfterSave) return; // bereits ausstehend
|
||||
launchGameAfterSave = true;
|
||||
input.saveRequested = true;
|
||||
setStatus("Karte wird gespeichert, Spiel startet…");
|
||||
}
|
||||
|
||||
private void startGameProcess() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
String javaExe = Paths.get(System.getProperty("java.home"), "bin", "java").toString();
|
||||
String classpath = System.getProperty("java.class.path");
|
||||
String libPath = System.getProperty("java.library.path", "");
|
||||
String projRoot = ProjectRoot.PATH.toString();
|
||||
|
||||
new ProcessBuilder(
|
||||
javaExe,
|
||||
"--add-opens", "java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens", "java.desktop/sun.awt=ALL-UNNAMED",
|
||||
"-Djava.library.path=" + libPath,
|
||||
"-Dblight.project.root=" + projRoot,
|
||||
"-cp", classpath,
|
||||
"de.blight.game.BlightApp")
|
||||
.directory(ProjectRoot.PATH.toFile())
|
||||
.inheritIO()
|
||||
.start();
|
||||
Platform.runLater(() -> setStatus("Spiel gestartet"));
|
||||
} catch (IOException ex) {
|
||||
Platform.runLater(() -> setStatus("Spielstart fehlgeschlagen: " + ex.getMessage()));
|
||||
}
|
||||
}, "game-launcher").start();
|
||||
}
|
||||
|
||||
private void saveCameraPrefs() {
|
||||
java.util.Properties p = new java.util.Properties();
|
||||
p.setProperty("cam.x", String.valueOf(input.camX));
|
||||
p.setProperty("cam.y", String.valueOf(input.camY));
|
||||
p.setProperty("cam.z", String.valueOf(input.camZ));
|
||||
p.setProperty("cam.yaw", String.valueOf(input.camYaw));
|
||||
p.setProperty("cam.pitch", String.valueOf(input.camPitch));
|
||||
try {
|
||||
Files.createDirectories(ProjectRoot.resolve("config"));
|
||||
try (java.io.Writer w = Files.newBufferedWriter(ProjectRoot.resolve("config", "editor.prefs"))) {
|
||||
p.store(w, "Blight Editor – Kamera-Einstellungen");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("[Editor] Kamera-Prefs konnten nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void setStatus(String msg) {
|
||||
@@ -1835,6 +1984,13 @@ public class EditorApp extends Application {
|
||||
// ── Tastatur-Handling ────────────────────────────────────────────────────
|
||||
|
||||
private void handleKeyPress(KeyCode code, boolean pressed) {
|
||||
// ^ öffnet/schließt die Konsole (DEAD_CIRCUMFLEX = deutsche Tastatur)
|
||||
if (code == KeyCode.DEAD_CIRCUMFLEX || code == KeyCode.CIRCUMFLEX) {
|
||||
if (pressed) toggleConsole();
|
||||
return;
|
||||
}
|
||||
// Während die Konsole offen ist, keine Editor-Tasten weiterleiten
|
||||
if (consoleOpen) return;
|
||||
switch (code) {
|
||||
case W -> input.forward = pressed;
|
||||
case S -> input.backward = pressed;
|
||||
|
||||
@@ -6,6 +6,10 @@ package de.blight.editor;
|
||||
*/
|
||||
public class EditorLauncher {
|
||||
public static void main(String[] args) {
|
||||
// ProjectRoot muss als erstes initialisiert werden, damit alle
|
||||
// relativen Pfade korrekt aufgelöst werden (auch bei IDE-Start mit
|
||||
// workingDir = blight-editor/ statt Projekt-Root).
|
||||
ProjectRoot.PATH.toString(); // Trigger static init
|
||||
EditorApp.main(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class JmeEditorApp extends SimpleApplication {
|
||||
// aus diesem Verzeichnis geladen werden können (relativ zum Arbeitsverzeichnis).
|
||||
try {
|
||||
assetManager.registerLocator(
|
||||
java.nio.file.Paths.get("editor-assets").toAbsolutePath().toString(),
|
||||
ProjectRoot.resolve("editor-assets").toAbsolutePath().toString(),
|
||||
FileLocator.class);
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
@@ -79,5 +79,64 @@ public class JmeEditorApp extends SimpleApplication {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {}
|
||||
public void simpleUpdate(float tpf) {
|
||||
com.jme3.math.Vector3f loc = cam.getLocation();
|
||||
com.jme3.math.Vector3f dir = cam.getDirection();
|
||||
input.camX = loc.x;
|
||||
input.camY = loc.y;
|
||||
input.camZ = loc.z;
|
||||
input.camYaw = (float) Math.toDegrees(Math.atan2(-dir.x, -dir.z));
|
||||
input.camPitch = (float) Math.toDegrees(
|
||||
Math.asin(Math.max(-1f, Math.min(1f, dir.y))));
|
||||
|
||||
String cmd = input.pendingCommand;
|
||||
if (cmd != null) {
|
||||
input.pendingCommand = null;
|
||||
processCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
private void processCommand(String raw) {
|
||||
String[] parts = raw.trim().split("\\s+");
|
||||
switch (parts[0].toLowerCase()) {
|
||||
case "goto" -> {
|
||||
try {
|
||||
if (parts.length >= 4) {
|
||||
// goto x y z — direkte Koordinaten
|
||||
float x = Float.parseFloat(parts[1]);
|
||||
float y = Float.parseFloat(parts[2]);
|
||||
float z = Float.parseFloat(parts[3]);
|
||||
cam.setLocation(new com.jme3.math.Vector3f(x, y, z));
|
||||
input.consoleOutput = "Goto → " + x + " / " + y + " / " + z;
|
||||
} else if (parts.length >= 3) {
|
||||
// goto x z — Bodenabstand beibehalten
|
||||
float x = Float.parseFloat(parts[1]);
|
||||
float z = Float.parseFloat(parts[2]);
|
||||
TerrainEditorState tes =
|
||||
stateManager.getState(TerrainEditorState.class);
|
||||
float srcGround = tes != null
|
||||
? tes.getTerrainHeightAt(cam.getLocation().x,
|
||||
cam.getLocation().z)
|
||||
: 0f;
|
||||
float heightAboveGround = cam.getLocation().y - srcGround;
|
||||
float dstGround = tes != null
|
||||
? tes.getTerrainHeightAt(x, z)
|
||||
: 0f;
|
||||
float y = dstGround + heightAboveGround;
|
||||
cam.setLocation(new com.jme3.math.Vector3f(x, y, z));
|
||||
input.consoleOutput = "Goto → X=" + x + " Z=" + z
|
||||
+ " (Y=" + String.format("%.1f", y) + ")";
|
||||
} else {
|
||||
input.consoleOutput = "Syntax: goto <x> <z> oder goto <x> <y> <z>";
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
input.consoleOutput = "Fehler: Koordinaten müssen Zahlen sein";
|
||||
}
|
||||
}
|
||||
case "help" -> input.consoleOutput =
|
||||
"Befehle: goto <x> <z> | goto <x> <y> <z> | help";
|
||||
default ->
|
||||
input.consoleOutput = "Unbekannter Befehl: " + parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package de.blight.editor;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Ermittelt einmalig den Projekt-Root (das Verzeichnis, das gradlew enthält)
|
||||
* und stellt ihn als kanonischen Basispfad bereit.
|
||||
*
|
||||
* Muss so früh wie möglich aufgerufen werden (EditorLauncher.main),
|
||||
* damit alle relativen Pfade korrekt aufgelöst werden — auch wenn der Editor
|
||||
* aus einer IDE mit workingDir=blight-editor/ gestartet wird.
|
||||
*/
|
||||
public final class ProjectRoot {
|
||||
|
||||
public static final Path PATH = findRoot();
|
||||
|
||||
private ProjectRoot() {}
|
||||
|
||||
public static Path resolve(String first, String... more) {
|
||||
return PATH.resolve(Paths.get(first, more));
|
||||
}
|
||||
|
||||
private static Path findRoot() {
|
||||
// blight-editor/ UND blight-game/ als Kinder → eindeutig der echte Root
|
||||
File dir = Paths.get(".").toAbsolutePath().normalize().toFile();
|
||||
while (dir != null) {
|
||||
if (new File(dir, "blight-editor").isDirectory()
|
||||
&& new File(dir, "blight-game").isDirectory()) {
|
||||
Path root = dir.toPath();
|
||||
System.setProperty("blight.project.root", root.toString());
|
||||
return root;
|
||||
}
|
||||
dir = dir.getParentFile();
|
||||
}
|
||||
Path fallback = Paths.get(".").toAbsolutePath().normalize();
|
||||
System.setProperty("blight.project.root", fallback.toString());
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class SharedInput {
|
||||
public final TextureTool textureTool = new TextureTool();
|
||||
public volatile EditorTool activeTool = heightTool;
|
||||
|
||||
// ── Aktive Ebene: 0=Basis-Terrain, 1=Obere Schicht, 2=Höhlen, 3=Gras, 4=Textur ──
|
||||
// ── Aktive Ebene: 0=Basis-Terrain, 1=Gebirge, 2=Höhlen, 3=Gras, 4=Textur ──
|
||||
public volatile int activeLayer = 0;
|
||||
public volatile boolean upperLayerVisible = true;
|
||||
|
||||
@@ -152,6 +152,19 @@ public class SharedInput {
|
||||
public final ConcurrentLinkedQueue<MeshCreateRequest> meshCreateQueue =
|
||||
new ConcurrentLinkedQueue<>();
|
||||
|
||||
// ── Kamera-Info (JME3-Thread schreibt, JavaFX-Thread liest) ─────────────
|
||||
public volatile float camX = 0f, camY = 0f, camZ = 0f;
|
||||
/** Yaw in Grad: 0° = Süden (−Z), 90° = Westen (−X), ±180° = Norden (+Z). */
|
||||
public volatile float camYaw = 0f;
|
||||
/** Pitch in Grad: positiv = Blick nach oben, negativ = nach unten. */
|
||||
public volatile float camPitch = 0f;
|
||||
|
||||
// ── Konsole (JavaFX → JME3 und zurück) ──────────────────────────────────
|
||||
/** Befehl, der beim nächsten JME3-Update ausgeführt werden soll. */
|
||||
public volatile String pendingCommand = null;
|
||||
/** Antworttext, den JME3 nach der Befehlsausführung setzt. */
|
||||
public volatile String consoleOutput = null;
|
||||
|
||||
// ── Modell-Konvertierung ──────────────────────────────────────────────────
|
||||
/**
|
||||
* Konvertiert ein natives Modell (OBJ/GLTF/…) zu .j3o.
|
||||
|
||||
@@ -38,6 +38,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
/**
|
||||
* JME3-AppState für den EZ-Tree-Generator.
|
||||
@@ -50,7 +52,7 @@ import java.nio.file.Paths;
|
||||
public class EzTreeState extends BaseAppState {
|
||||
|
||||
private static final int IMPOSTOR_SIZE = 512;
|
||||
private static final Path ASSET_ROOT = Paths.get("editor-assets");
|
||||
private static final Path ASSET_ROOT = de.blight.editor.ProjectRoot.resolve("editor-assets");
|
||||
|
||||
private final SharedInput input;
|
||||
private SimpleApplication app;
|
||||
@@ -155,8 +157,10 @@ public class EzTreeState extends BaseAppState {
|
||||
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
||||
cleanupCapture();
|
||||
|
||||
saveImpostor(pixels, "ez_impostor_" + pendingRequest.exportName());
|
||||
exportTree(pendingTreeNode, pendingRequest.exportName());
|
||||
String exportName = pendingRequest.exportName() + "_"
|
||||
+ DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now());
|
||||
saveImpostor(pixels, "ez_impostor_" + exportName);
|
||||
exportTree(pendingTreeNode, exportName);
|
||||
|
||||
pendingRequest = null;
|
||||
pendingTreeNode = null;
|
||||
|
||||
@@ -24,10 +24,12 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class PalmGeneratorState extends BaseAppState {
|
||||
|
||||
private static final Path ASSET_ROOT = Paths.get("editor-assets");
|
||||
private static final Path ASSET_ROOT = de.blight.editor.ProjectRoot.resolve("editor-assets");
|
||||
|
||||
private final SharedInput input;
|
||||
private SimpleApplication app;
|
||||
@@ -188,7 +190,9 @@ public class PalmGeneratorState extends BaseAppState {
|
||||
try {
|
||||
Path modelDir = ASSET_ROOT.resolve("models");
|
||||
Files.createDirectories(modelDir);
|
||||
File out = modelDir.resolve("Palm_" + name + ".j3o").toFile();
|
||||
String stampedName = name + "_"
|
||||
+ DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now());
|
||||
File out = modelDir.resolve("Palm_" + stampedName + ".j3o").toFile();
|
||||
BinaryExporter.getInstance().save(palmNode, out);
|
||||
input.treeGenStatusMsg = "Palme exportiert: " + out.getName();
|
||||
input.refreshAssets = true;
|
||||
|
||||
@@ -39,7 +39,7 @@ import java.util.List;
|
||||
*/
|
||||
public class SceneObjectState extends BaseAppState {
|
||||
|
||||
private static final Path ASSET_ROOT = Paths.get("editor-assets");
|
||||
private static final Path ASSET_ROOT = de.blight.editor.ProjectRoot.resolve("editor-assets");
|
||||
|
||||
// ── Gizmo-Farben ─────────────────────────────────────────────────────────
|
||||
private static final ColorRGBA COL_X = new ColorRGBA(0.9f, 0.1f, 0.1f, 1f);
|
||||
|
||||
@@ -31,12 +31,18 @@ import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.tool.HeightTool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
|
||||
public class TerrainEditorState extends BaseAppState {
|
||||
|
||||
@@ -75,9 +81,13 @@ public class TerrainEditorState extends BaseAppState {
|
||||
private Texture2D splatTex;
|
||||
|
||||
// ── Kameraposition ────────────────────────────────────────────────────────
|
||||
private static final Path EDITOR_PREFS = de.blight.editor.ProjectRoot.resolve("config", "editor.prefs");
|
||||
private static final float DEFAULT_CAM_Y = 50f;
|
||||
private static final float DEFAULT_PITCH = (float) (-Math.PI / 4); // -45°
|
||||
|
||||
private float camYaw = 0f;
|
||||
private float camPitch = -1.0f;
|
||||
private final Vector3f camPos = new Vector3f(0f, 800f, (float)(800.0 / Math.tan(1.0)));
|
||||
private float camPitch = DEFAULT_PITCH;
|
||||
private final Vector3f camPos = new Vector3f(0f, DEFAULT_CAM_Y, 0f);
|
||||
|
||||
public TerrainEditorState(SharedInput input) {
|
||||
this.input = input;
|
||||
@@ -101,6 +111,28 @@ public class TerrainEditorState extends BaseAppState {
|
||||
System.err.println("[TerrainEditor] Karte nicht ladbar: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
loadCameraPrefs();
|
||||
}
|
||||
|
||||
private void loadCameraPrefs() {
|
||||
if (!Files.exists(EDITOR_PREFS)) return;
|
||||
Properties p = new Properties();
|
||||
try (Reader r = Files.newBufferedReader(EDITOR_PREFS)) {
|
||||
p.load(r);
|
||||
camPos.set(
|
||||
parsePref(p, "cam.x", 0f),
|
||||
parsePref(p, "cam.y", DEFAULT_CAM_Y),
|
||||
parsePref(p, "cam.z", 0f));
|
||||
camYaw = (float) Math.toRadians(parsePref(p, "cam.yaw", 0f));
|
||||
camPitch = (float) Math.toRadians(parsePref(p, "cam.pitch", (float) Math.toDegrees(DEFAULT_PITCH)));
|
||||
} catch (IOException e) {
|
||||
System.err.println("[TerrainEditor] Kamera-Prefs nicht ladbar: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static float parsePref(Properties p, String key, float def) {
|
||||
try { return Float.parseFloat(p.getProperty(key, String.valueOf(def))); }
|
||||
catch (NumberFormatException e) { return def; }
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -354,6 +386,13 @@ public class TerrainEditorState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gibt die Terrain-Höhe (Welt-Y) an der angegebenen Welt-XZ-Position zurück. */
|
||||
public float getTerrainHeightAt(float worldX, float worldZ) {
|
||||
if (terrain == null) return 0f;
|
||||
Float h = terrain.getHeight(new Vector2f(worldX, worldZ));
|
||||
return h != null ? h : 0f;
|
||||
}
|
||||
|
||||
// ── Speichern ─────────────────────────────────────────────────────────────
|
||||
|
||||
private void performSave() {
|
||||
|
||||
@@ -7,6 +7,8 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
@@ -64,7 +66,7 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
|
||||
private static final int IMPOSTOR_SIZE = 512;
|
||||
private static final int PREVIEW_SIZE = 1024;
|
||||
private static final Path ASSET_ROOT = Paths.get("editor-assets");
|
||||
private static final Path ASSET_ROOT = de.blight.editor.ProjectRoot.resolve("editor-assets");
|
||||
|
||||
private final SharedInput input;
|
||||
|
||||
@@ -286,7 +288,12 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
||||
cleanupCapture();
|
||||
|
||||
Texture2D impostorTex = saveImpostor(pixels, "impostor_" + pendingRequest.exportName());
|
||||
String baseName = pendingRequest.exportName();
|
||||
String exportName = pendingRequest.exportAfter()
|
||||
? baseName + "_" + DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now())
|
||||
: baseName;
|
||||
|
||||
Texture2D impostorTex = saveImpostor(pixels, "impostor_" + exportName);
|
||||
|
||||
// HD-Mesh im Dialog-Preview anzeigen (keine LOD-Umschaltung, kein Welt-Platzierung)
|
||||
Node previewTree = makeTreeNode(pendingHdResult,
|
||||
@@ -301,9 +308,9 @@ public class TreeGeneratorState extends BaseAppState {
|
||||
|
||||
if (pendingRequest.exportAfter()) {
|
||||
Node treeNode = assembleLodNode(impostorTex);
|
||||
exportTree(treeNode, pendingRequest.exportName());
|
||||
exportTree(treeNode, exportName);
|
||||
} else {
|
||||
input.treeGenStatusMsg = "Vorschau: '" + pendingRequest.exportName() + "'";
|
||||
input.treeGenStatusMsg = "Vorschau: '" + baseName + "'";
|
||||
}
|
||||
|
||||
pendingRequest = null;
|
||||
|
||||
@@ -23,8 +23,9 @@ public class UpperLayerData {
|
||||
/** Whether a cell is an open hole (no geometry emitted) [CELLS*CELLS]. */
|
||||
public final boolean[] hole;
|
||||
|
||||
/** Initiale Höhe der Gras-Oberfläche (muss mit TerrainEditorState übereinstimmen). */
|
||||
public static final float INITIAL_TERRAIN_Y = 1f;
|
||||
/** Initiale Höhe der Gebirgsschicht-Oberseite. Tiefer als das flache Terrain (Y=1),
|
||||
* damit das Gebirge standardmäßig verdeckt ist und gezielt hochgezogen werden muss. */
|
||||
public static final float INITIAL_TERRAIN_Y = -10f;
|
||||
/** Dicke der Gesteinsschicht in Welteinheiten. */
|
||||
public static final float LAYER_THICKNESS = 30f;
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ import de.blight.editor.SharedInput;
|
||||
import de.blight.editor.tool.HoleTool;
|
||||
import de.blight.editor.tool.UpperHeightTool;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* AppState that owns the upper (mountain) layer: 1024 chunk geometries
|
||||
@@ -256,20 +257,27 @@ public class UpperLayerState extends BaseAppState {
|
||||
|
||||
/**
|
||||
* Verschiebt top- und bottomHeight um dieselben Deltas wie das Basis-Terrain.
|
||||
* Jeder obere Vertex wird dabei nur einmal angepasst, auch wenn mehrere
|
||||
* Terrain-Vertices auf denselben oberen Vertex fallen.
|
||||
* Da das Terrain-Grid (4097×4097) feiner ist als das Gebirge-Grid (513×513),
|
||||
* fallen mehrere Terrain-Vertices auf denselben Gebirge-Vertex. Wir nehmen
|
||||
* jeweils den Delta mit dem größten Absolutbetrag, damit der Gebirge-Vertex
|
||||
* der stärksten Terrain-Verschiebung folgt und das Terrain nicht durchsticht.
|
||||
*/
|
||||
public void adjustHeightsWithTerrain(List<Vector2f> worldXZ, List<Float> deltas) {
|
||||
HashSet<Integer> seen = new HashSet<>();
|
||||
HashMap<Integer, Float> maxDelta = new HashMap<>();
|
||||
for (int i = 0; i < worldXZ.size(); i++) {
|
||||
Vector2f p = worldXZ.get(i);
|
||||
int uvx = UpperLayerData.worldToVertexX(p.x);
|
||||
int uvz = UpperLayerData.worldToVertexZ(p.y);
|
||||
int uvx = UpperLayerData.worldToVertexX(worldXZ.get(i).x);
|
||||
int uvz = UpperLayerData.worldToVertexZ(worldXZ.get(i).y);
|
||||
int key = uvx + uvz * UpperLayerData.VERTS;
|
||||
if (!seen.add(key)) continue;
|
||||
float d = deltas.get(i);
|
||||
maxDelta.merge(key, d, (a, b) -> Math.abs(b) > Math.abs(a) ? b : a);
|
||||
}
|
||||
for (Map.Entry<Integer, Float> e : maxDelta.entrySet()) {
|
||||
int key = e.getKey();
|
||||
float d = e.getValue();
|
||||
data.topHeight[key] += d;
|
||||
data.bottomHeight[key] += d;
|
||||
int uvx = key % UpperLayerData.VERTS;
|
||||
int uvz = key / UpperLayerData.VERTS;
|
||||
markVertexDirty(uvx, uvz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class UpperHeightTool extends EditorTool {
|
||||
public final ToolParameter brushStrength = new ToolParameter("Pinselstärke", 2.0, 0.1, 50.0);
|
||||
|
||||
@Override
|
||||
public String getName() { return "Obere Schicht – Höhe"; }
|
||||
public String getName() { return "Gebirge – Höhe"; }
|
||||
|
||||
@Override
|
||||
public List<ChoiceToolParameter> getChoiceParameters() {
|
||||
|
||||
Reference in New Issue
Block a user