Weiter daran gearbeitet, die welt aus dem editor ins game zu bringen
This commit is contained in:
@@ -8,9 +8,12 @@ plugins {
|
|||||||
group = 'de.blight'
|
group = 'de.blight'
|
||||||
version = '0.1.0'
|
version = '0.1.0'
|
||||||
|
|
||||||
|
// Explizite Toolchain für standalone-Import (Eclipse ohne Root-Build).
|
||||||
|
// Im Full-Build wird dies durch die Root-Konfiguration überschrieben.
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_21
|
toolchain {
|
||||||
targetCompatibility = JavaVersion.VERSION_21
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import java.util.zip.*;
|
|||||||
/**
|
/**
|
||||||
* Liest und schreibt {@link MapData} als komprimierte Binärdatei.
|
* Liest und schreibt {@link MapData} als komprimierte Binärdatei.
|
||||||
*
|
*
|
||||||
* Speicherort: {@code world/blight_map.blm} relativ zum Arbeitsverzeichnis.
|
* Speicherort: {@code blight-map/src/main/map/blight_map.blm} relativ zum Arbeitsverzeichnis.
|
||||||
* Beide Projekte setzen {@code workingDir = rootDir} im Gradle-Run-Task,
|
* Beide Projekte setzen {@code workingDir = rootDir} im Gradle-Run-Task,
|
||||||
* zeigen also auf dasselbe Verzeichnis.
|
* zeigen also auf dasselbe Verzeichnis.
|
||||||
*
|
*
|
||||||
@@ -19,7 +19,32 @@ import java.util.zip.*;
|
|||||||
*/
|
*/
|
||||||
public final class MapIO {
|
public final class MapIO {
|
||||||
|
|
||||||
private static final Path MAP_PATH = Paths.get("world", "blight_map.blm");
|
private static final Path PROJECT_ROOT = findProjectRoot();
|
||||||
|
private static final Path MAP_PATH = PROJECT_ROOT.resolve(
|
||||||
|
Paths.get("blight-map", "src", "main", "map", "blight_map.blm"));
|
||||||
|
private static final Path MAP_PATH_OLD = PROJECT_ROOT.resolve(
|
||||||
|
Paths.get("world", "blight_map.blm"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ermittelt den Projekt-Root einmalig beim Classload:
|
||||||
|
* 1. System-Property {@code blight.project.root} (vom Editor-Subprocess gesetzt)
|
||||||
|
* 2. Aufwärtssuche im Verzeichnisbaum nach {@code gradlew}
|
||||||
|
* 3. Fallback: aktuelles Arbeitsverzeichnis
|
||||||
|
*/
|
||||||
|
private static Path findProjectRoot() {
|
||||||
|
String prop = System.getProperty("blight.project.root");
|
||||||
|
if (prop != null) return Paths.get(prop);
|
||||||
|
|
||||||
|
// 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())
|
||||||
|
return dir.toPath();
|
||||||
|
dir = dir.getParentFile();
|
||||||
|
}
|
||||||
|
return Paths.get(".").toAbsolutePath().normalize();
|
||||||
|
}
|
||||||
|
|
||||||
private static final int MAGIC = 0x424C4947; // "BLIG"
|
private static final int MAGIC = 0x424C4947; // "BLIG"
|
||||||
private static final int VERSION = 3;
|
private static final int VERSION = 3;
|
||||||
@@ -29,7 +54,20 @@ public final class MapIO {
|
|||||||
// ── Public API ────────────────────────────────────────────────────────────
|
// ── Public API ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
public static boolean exists() {
|
public static boolean exists() {
|
||||||
return Files.exists(MAP_PATH);
|
System.out.println("[MapIO] Suche Karte: " + MAP_PATH.toAbsolutePath());
|
||||||
|
if (Files.exists(MAP_PATH)) return true;
|
||||||
|
// Einmalige Migration vom alten Speicherort (world/blight_map.blm)
|
||||||
|
if (Files.exists(MAP_PATH_OLD)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectories(MAP_PATH.getParent());
|
||||||
|
Files.move(MAP_PATH_OLD, MAP_PATH);
|
||||||
|
System.out.println("[MapIO] Karte migriert: " + MAP_PATH_OLD + " → " + MAP_PATH);
|
||||||
|
return true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("[MapIO] Migration fehlgeschlagen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Path getMapPath() {
|
public static Path getMapPath() {
|
||||||
|
|||||||
BIN
blight-editor/blight-map/src/main/map/blight_map.blm
Normal file
BIN
blight-editor/blight-map/src/main/map/blight_map.blm
Normal file
Binary file not shown.
@@ -25,7 +25,11 @@ ext {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':blight-common')
|
implementation project(':blight-common')
|
||||||
implementation project(':blight-assets')
|
implementation project(':blight-assets')
|
||||||
|
implementation project(':blight-map')
|
||||||
implementation project(':ez-tree-jme')
|
implementation project(':ez-tree-jme')
|
||||||
|
// Spiel-Klassen + deren Abhängigkeiten (jme3-jbullet, gson) auf dem Runtime-
|
||||||
|
// Classpath, damit der Editor BlightApp als Subprocess starten kann.
|
||||||
|
implementation project(':blight-game')
|
||||||
|
|
||||||
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
||||||
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
||||||
|
|||||||
BIN
blight-editor/editor-assets/models/Palm_Palme1.j3o
Normal file
BIN
blight-editor/editor-assets/models/Palm_Palme1.j3o
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
23
blight-editor/hs_err_pid83128.log
Normal file
23
blight-editor/hs_err_pid83128.log
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#
|
||||||
|
# A fatal error has been detected by the Java Runtime Environment:
|
||||||
|
#
|
||||||
|
# SIGSEGV (0xb) at pc=0x00007c37e02a43a0, pid=83128, tid=83197
|
||||||
|
#
|
||||||
|
# JRE version: Java(TM) SE Runtime Environment (26.0.1+8) (build 26.0.1+8-34)
|
||||||
|
# Java VM: Java HotSpot(TM) 64-Bit Server VM (26.0.1+8-34, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
|
||||||
|
# Problematic frame:
|
||||||
|
# C 0x00007c37e02a43a0
|
||||||
|
#
|
||||||
|
# Core dump will be written. Default location: Determined by the following: "/usr/lib/systemd/systemd-coredump %P %u %g %s %t 9223372036854775808 %h %d" (alternatively, falling back to /home/mario/Workspaces/blight/blight-editor/core.83128)
|
||||||
|
#
|
||||||
|
# If you would like to submit a bug report, please visit:
|
||||||
|
# https://bugreport.java.com/bugreport/crash.jsp
|
||||||
|
# The crash happened outside the Java Virtual Machine in native code.
|
||||||
|
# See problematic frame for where to report the bug.
|
||||||
|
#
|
||||||
|
|
||||||
|
--------------- S U M M A R Y ------------
|
||||||
|
|
||||||
|
Command Line: -Dfile.encoding=UTF-8 -Dstdout.encoding=UTF-8 -Dstderr.encoding=UTF-8 -XX:+ShowCodeDetailsInExceptionMessages de.blight.editor.EditorLauncher
|
||||||
|
|
||||||
|
Host: Intel(R) Core(TM) i7-14700KF, 28 cores, 31G,
|
||||||
2991
blight-editor/hs_err_pid83303.log
Normal file
2991
blight-editor/hs_err_pid83303.log
Normal file
File diff suppressed because one or more lines are too long
@@ -44,12 +44,17 @@ public class EditorApp extends Application {
|
|||||||
static final int VP_WIDTH = 1024;
|
static final int VP_WIDTH = 1024;
|
||||||
static final int VP_HEIGHT = 640;
|
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 final SharedInput input = new SharedInput();
|
||||||
private WritableImage jfxImage;
|
private WritableImage jfxImage;
|
||||||
private ImageView viewport;
|
private ImageView viewport;
|
||||||
private Label statusLabel;
|
private Label statusLabel;
|
||||||
|
private Label camCoordsLabel;
|
||||||
|
private HBox consoleBar;
|
||||||
|
private TextField consoleField;
|
||||||
|
private boolean consoleOpen = false;
|
||||||
|
private boolean launchGameAfterSave = false;
|
||||||
private VBox toolPanel;
|
private VBox toolPanel;
|
||||||
private BorderPane root;
|
private BorderPane root;
|
||||||
private VBox assetPanel;
|
private VBox assetPanel;
|
||||||
@@ -129,7 +134,7 @@ public class EditorApp extends Application {
|
|||||||
root.setLeft(assetPanel);
|
root.setLeft(assetPanel);
|
||||||
root.setCenter(worldViewport);
|
root.setCenter(worldViewport);
|
||||||
root.setRight(toolPanel);
|
root.setRight(toolPanel);
|
||||||
root.setBottom(buildStatusBar());
|
root.setBottom(buildBottomBox());
|
||||||
|
|
||||||
Scene scene = new Scene(root, 1280, 760);
|
Scene scene = new Scene(root, 1280, 760);
|
||||||
scene.setOnKeyPressed(e -> handleKeyPress(e.getCode(), true));
|
scene.setOnKeyPressed(e -> handleKeyPress(e.getCode(), true));
|
||||||
@@ -140,14 +145,21 @@ public class EditorApp extends Application {
|
|||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setMinWidth(900);
|
stage.setMinWidth(900);
|
||||||
stage.setMinHeight(600);
|
stage.setMinHeight(600);
|
||||||
stage.setOnCloseRequest(e -> Platform.exit());
|
stage.setOnCloseRequest(e -> { saveCameraPrefs(); Platform.exit(); });
|
||||||
stage.setMaximized(true);
|
stage.setMaximized(true);
|
||||||
stage.show();
|
stage.show();
|
||||||
|
|
||||||
javafx.animation.Timeline statusPoller = new javafx.animation.Timeline(
|
javafx.animation.Timeline statusPoller = new javafx.animation.Timeline(
|
||||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
||||||
String saveMsg = input.saveStatusMsg;
|
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;
|
String treeMsg = input.treeGenStatusMsg;
|
||||||
if (treeMsg != null) { input.treeGenStatusMsg = null; setStatus(treeMsg); }
|
if (treeMsg != null) { input.treeGenStatusMsg = null; setStatus(treeMsg); }
|
||||||
@@ -178,10 +190,31 @@ public class EditorApp extends Application {
|
|||||||
input.objectSelectionChanged = false;
|
input.objectSelectionChanged = false;
|
||||||
updateObjectPanel(input.selectedObjectInfo);
|
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.setCycleCount(javafx.animation.Timeline.INDEFINITE);
|
||||||
statusPoller.play();
|
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 ────────────────────────────────────────────────────────
|
// ── Modus-Wechsel ────────────────────────────────────────────────────────
|
||||||
@@ -246,7 +279,7 @@ public class EditorApp extends Application {
|
|||||||
|
|
||||||
ToolBar toolBar = new ToolBar();
|
ToolBar toolBar = new ToolBar();
|
||||||
ToggleButton baseBtn = new ToggleButton("▲▼ Basis-Terrain");
|
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 holesBtn = new ToggleButton("⬤ Höhlen/Löcher");
|
||||||
ToggleButton grassBtn = new ToggleButton("🌿 Gras");
|
ToggleButton grassBtn = new ToggleButton("🌿 Gras");
|
||||||
ToggleButton textureBtn = new ToggleButton("🎨 Textur");
|
ToggleButton textureBtn = new ToggleButton("🎨 Textur");
|
||||||
@@ -305,17 +338,28 @@ public class EditorApp extends Application {
|
|||||||
root.setRight(buildObjectEditPanel());
|
root.setRight(buildObjectEditPanel());
|
||||||
});
|
});
|
||||||
|
|
||||||
CheckBox visibleCB = new CheckBox("Obere Schicht sichtbar");
|
CheckBox visibleCB = new CheckBox("Gebirge sichtbar");
|
||||||
visibleCB.setSelected(true);
|
visibleCB.setSelected(true);
|
||||||
visibleCB.setOnAction(e -> input.upperLayerVisible = visibleCB.isSelected());
|
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");
|
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;");
|
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,
|
toolBar.getItems().addAll(baseBtn, upperBtn, holesBtn, grassBtn, textureBtn,
|
||||||
new Separator(Orientation.VERTICAL), objPlaceBtn, objEditBtn,
|
new Separator(Orientation.VERTICAL), objPlaceBtn, objEditBtn,
|
||||||
new Separator(Orientation.VERTICAL), visibleCB,
|
new Separator(Orientation.VERTICAL), visibleCB,
|
||||||
new Separator(Orientation.VERTICAL), hint);
|
new Separator(Orientation.VERTICAL), hint,
|
||||||
|
toolbarSpacer,
|
||||||
|
new Separator(Orientation.VERTICAL), playBtn);
|
||||||
|
|
||||||
worldToolBar = toolBar;
|
worldToolBar = toolBar;
|
||||||
return new VBox(menuBar, toolBar);
|
return new VBox(menuBar, toolBar);
|
||||||
@@ -1819,13 +1863,118 @@ public class EditorApp extends Application {
|
|||||||
|
|
||||||
// ── Statusleiste ─────────────────────────────────────────────────────────
|
// ── 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 = new Label("Bereit | Werkzeug: Höhe | WASD/QE: Bewegen | Mitte-Drag / L+R-Drag: Drehen");
|
||||||
statusLabel.setPadding(new Insets(3, 8, 3, 8));
|
statusLabel.setPadding(new Insets(3, 8, 3, 8));
|
||||||
statusLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #333;");
|
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;");
|
camCoordsLabel = new Label("X:0.0 Y:0.0 Z:0.0 Yaw:0° Pitch:0°");
|
||||||
return bar;
|
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) {
|
private void setStatus(String msg) {
|
||||||
@@ -1835,6 +1984,13 @@ public class EditorApp extends Application {
|
|||||||
// ── Tastatur-Handling ────────────────────────────────────────────────────
|
// ── Tastatur-Handling ────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void handleKeyPress(KeyCode code, boolean pressed) {
|
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) {
|
switch (code) {
|
||||||
case W -> input.forward = pressed;
|
case W -> input.forward = pressed;
|
||||||
case S -> input.backward = pressed;
|
case S -> input.backward = pressed;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ package de.blight.editor;
|
|||||||
*/
|
*/
|
||||||
public class EditorLauncher {
|
public class EditorLauncher {
|
||||||
public static void main(String[] args) {
|
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);
|
EditorApp.main(args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ public class JmeEditorApp extends SimpleApplication {
|
|||||||
// aus diesem Verzeichnis geladen werden können (relativ zum Arbeitsverzeichnis).
|
// aus diesem Verzeichnis geladen werden können (relativ zum Arbeitsverzeichnis).
|
||||||
try {
|
try {
|
||||||
assetManager.registerLocator(
|
assetManager.registerLocator(
|
||||||
java.nio.file.Paths.get("editor-assets").toAbsolutePath().toString(),
|
ProjectRoot.resolve("editor-assets").toAbsolutePath().toString(),
|
||||||
FileLocator.class);
|
FileLocator.class);
|
||||||
} catch (Exception ignored) {}
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
@@ -79,5 +79,64 @@ public class JmeEditorApp extends SimpleApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 final TextureTool textureTool = new TextureTool();
|
||||||
public volatile EditorTool activeTool = heightTool;
|
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 int activeLayer = 0;
|
||||||
public volatile boolean upperLayerVisible = true;
|
public volatile boolean upperLayerVisible = true;
|
||||||
|
|
||||||
@@ -152,6 +152,19 @@ public class SharedInput {
|
|||||||
public final ConcurrentLinkedQueue<MeshCreateRequest> meshCreateQueue =
|
public final ConcurrentLinkedQueue<MeshCreateRequest> meshCreateQueue =
|
||||||
new ConcurrentLinkedQueue<>();
|
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 ──────────────────────────────────────────────────
|
// ── Modell-Konvertierung ──────────────────────────────────────────────────
|
||||||
/**
|
/**
|
||||||
* Konvertiert ein natives Modell (OBJ/GLTF/…) zu .j3o.
|
* 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.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JME3-AppState für den EZ-Tree-Generator.
|
* JME3-AppState für den EZ-Tree-Generator.
|
||||||
@@ -50,7 +52,7 @@ import java.nio.file.Paths;
|
|||||||
public class EzTreeState extends BaseAppState {
|
public class EzTreeState extends BaseAppState {
|
||||||
|
|
||||||
private static final int IMPOSTOR_SIZE = 512;
|
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 final SharedInput input;
|
||||||
private SimpleApplication app;
|
private SimpleApplication app;
|
||||||
@@ -155,8 +157,10 @@ public class EzTreeState extends BaseAppState {
|
|||||||
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
||||||
cleanupCapture();
|
cleanupCapture();
|
||||||
|
|
||||||
saveImpostor(pixels, "ez_impostor_" + pendingRequest.exportName());
|
String exportName = pendingRequest.exportName() + "_"
|
||||||
exportTree(pendingTreeNode, pendingRequest.exportName());
|
+ DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss").format(LocalDateTime.now());
|
||||||
|
saveImpostor(pixels, "ez_impostor_" + exportName);
|
||||||
|
exportTree(pendingTreeNode, exportName);
|
||||||
|
|
||||||
pendingRequest = null;
|
pendingRequest = null;
|
||||||
pendingTreeNode = null;
|
pendingTreeNode = null;
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import java.io.IOException;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
public class PalmGeneratorState extends BaseAppState {
|
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 final SharedInput input;
|
||||||
private SimpleApplication app;
|
private SimpleApplication app;
|
||||||
@@ -188,7 +190,9 @@ public class PalmGeneratorState extends BaseAppState {
|
|||||||
try {
|
try {
|
||||||
Path modelDir = ASSET_ROOT.resolve("models");
|
Path modelDir = ASSET_ROOT.resolve("models");
|
||||||
Files.createDirectories(modelDir);
|
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);
|
BinaryExporter.getInstance().save(palmNode, out);
|
||||||
input.treeGenStatusMsg = "Palme exportiert: " + out.getName();
|
input.treeGenStatusMsg = "Palme exportiert: " + out.getName();
|
||||||
input.refreshAssets = true;
|
input.refreshAssets = true;
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class SceneObjectState extends BaseAppState {
|
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 ─────────────────────────────────────────────────────────
|
// ── Gizmo-Farben ─────────────────────────────────────────────────────────
|
||||||
private static final ColorRGBA COL_X = new ColorRGBA(0.9f, 0.1f, 0.1f, 1f);
|
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 de.blight.editor.tool.HeightTool;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.Writer;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
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.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
public class TerrainEditorState extends BaseAppState {
|
public class TerrainEditorState extends BaseAppState {
|
||||||
|
|
||||||
@@ -75,9 +81,13 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
private Texture2D splatTex;
|
private Texture2D splatTex;
|
||||||
|
|
||||||
// ── Kameraposition ────────────────────────────────────────────────────────
|
// ── 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 camYaw = 0f;
|
||||||
private float camPitch = -1.0f;
|
private float camPitch = DEFAULT_PITCH;
|
||||||
private final Vector3f camPos = new Vector3f(0f, 800f, (float)(800.0 / Math.tan(1.0)));
|
private final Vector3f camPos = new Vector3f(0f, DEFAULT_CAM_Y, 0f);
|
||||||
|
|
||||||
public TerrainEditorState(SharedInput input) {
|
public TerrainEditorState(SharedInput input) {
|
||||||
this.input = input;
|
this.input = input;
|
||||||
@@ -101,6 +111,28 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
System.err.println("[TerrainEditor] Karte nicht ladbar: " + e.getMessage());
|
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
|
@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 ─────────────────────────────────────────────────────────────
|
// ── Speichern ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void performSave() {
|
private void performSave() {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import java.nio.ByteBuffer;
|
|||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
@@ -64,7 +66,7 @@ public class TreeGeneratorState extends BaseAppState {
|
|||||||
|
|
||||||
private static final int IMPOSTOR_SIZE = 512;
|
private static final int IMPOSTOR_SIZE = 512;
|
||||||
private static final int PREVIEW_SIZE = 1024;
|
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;
|
private final SharedInput input;
|
||||||
|
|
||||||
@@ -286,7 +288,12 @@ public class TreeGeneratorState extends BaseAppState {
|
|||||||
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
app.getRenderer().readFrameBuffer(captureFB, pixels);
|
||||||
cleanupCapture();
|
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)
|
// HD-Mesh im Dialog-Preview anzeigen (keine LOD-Umschaltung, kein Welt-Platzierung)
|
||||||
Node previewTree = makeTreeNode(pendingHdResult,
|
Node previewTree = makeTreeNode(pendingHdResult,
|
||||||
@@ -301,9 +308,9 @@ public class TreeGeneratorState extends BaseAppState {
|
|||||||
|
|
||||||
if (pendingRequest.exportAfter()) {
|
if (pendingRequest.exportAfter()) {
|
||||||
Node treeNode = assembleLodNode(impostorTex);
|
Node treeNode = assembleLodNode(impostorTex);
|
||||||
exportTree(treeNode, pendingRequest.exportName());
|
exportTree(treeNode, exportName);
|
||||||
} else {
|
} else {
|
||||||
input.treeGenStatusMsg = "Vorschau: '" + pendingRequest.exportName() + "'";
|
input.treeGenStatusMsg = "Vorschau: '" + baseName + "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
pendingRequest = null;
|
pendingRequest = null;
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ public class UpperLayerData {
|
|||||||
/** Whether a cell is an open hole (no geometry emitted) [CELLS*CELLS]. */
|
/** Whether a cell is an open hole (no geometry emitted) [CELLS*CELLS]. */
|
||||||
public final boolean[] hole;
|
public final boolean[] hole;
|
||||||
|
|
||||||
/** Initiale Höhe der Gras-Oberfläche (muss mit TerrainEditorState übereinstimmen). */
|
/** Initiale Höhe der Gebirgsschicht-Oberseite. Tiefer als das flache Terrain (Y=1),
|
||||||
public static final float INITIAL_TERRAIN_Y = 1f;
|
* 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. */
|
/** Dicke der Gesteinsschicht in Welteinheiten. */
|
||||||
public static final float LAYER_THICKNESS = 30f;
|
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.HoleTool;
|
||||||
import de.blight.editor.tool.UpperHeightTool;
|
import de.blight.editor.tool.UpperHeightTool;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AppState that owns the upper (mountain) layer: 1024 chunk geometries
|
* 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.
|
* Verschiebt top- und bottomHeight um dieselben Deltas wie das Basis-Terrain.
|
||||||
* Jeder obere Vertex wird dabei nur einmal angepasst, auch wenn mehrere
|
* Da das Terrain-Grid (4097×4097) feiner ist als das Gebirge-Grid (513×513),
|
||||||
* Terrain-Vertices auf denselben oberen Vertex fallen.
|
* 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) {
|
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++) {
|
for (int i = 0; i < worldXZ.size(); i++) {
|
||||||
Vector2f p = worldXZ.get(i);
|
int uvx = UpperLayerData.worldToVertexX(worldXZ.get(i).x);
|
||||||
int uvx = UpperLayerData.worldToVertexX(p.x);
|
int uvz = UpperLayerData.worldToVertexZ(worldXZ.get(i).y);
|
||||||
int uvz = UpperLayerData.worldToVertexZ(p.y);
|
|
||||||
int key = uvx + uvz * UpperLayerData.VERTS;
|
int key = uvx + uvz * UpperLayerData.VERTS;
|
||||||
if (!seen.add(key)) continue;
|
|
||||||
float d = deltas.get(i);
|
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.topHeight[key] += d;
|
||||||
data.bottomHeight[key] += d;
|
data.bottomHeight[key] += d;
|
||||||
|
int uvx = key % UpperLayerData.VERTS;
|
||||||
|
int uvz = key / UpperLayerData.VERTS;
|
||||||
markVertexDirty(uvx, uvz);
|
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);
|
public final ToolParameter brushStrength = new ToolParameter("Pinselstärke", 2.0, 0.1, 50.0);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() { return "Obere Schicht – Höhe"; }
|
public String getName() { return "Gebirge – Höhe"; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ChoiceToolParameter> getChoiceParameters() {
|
public List<ChoiceToolParameter> getChoiceParameters() {
|
||||||
|
|||||||
Binary file not shown.
@@ -1,16 +1,11 @@
|
|||||||
// group / version / java / repositories kommen vom Root-Build.
|
// group / version / java / repositories kommen vom Root-Build.
|
||||||
|
// Kein 'application'-Plugin: dessen DistributionPlugin nutzt afterEvaluate,
|
||||||
|
// was mit runtimeOnly project(':blight-game') in blight-editor kollidiert.
|
||||||
plugins {
|
plugins {
|
||||||
id 'application'
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
application {
|
ext { mainClassName = 'de.blight.game.BlightApp' }
|
||||||
mainClass = 'de.blight.game.BlightApp'
|
|
||||||
applicationDefaultJvmArgs = [
|
|
||||||
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
|
||||||
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
|
|
||||||
"-Djava.library.path=${buildDir}/natives"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
jmeVersion = '3.9.0-stable'
|
jmeVersion = '3.9.0-stable'
|
||||||
@@ -19,6 +14,7 @@ ext {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':blight-common')
|
implementation project(':blight-common')
|
||||||
implementation project(':blight-assets')
|
implementation project(':blight-assets')
|
||||||
|
implementation project(':blight-map')
|
||||||
|
|
||||||
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
implementation "org.jmonkeyengine:jme3-core:${jmeVersion}"
|
||||||
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
implementation "org.jmonkeyengine:jme3-desktop:${jmeVersion}"
|
||||||
@@ -41,13 +37,22 @@ tasks.register('extractNatives', Copy) {
|
|||||||
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||||
}
|
}
|
||||||
|
|
||||||
run {
|
tasks.register('run', JavaExec) {
|
||||||
|
group = 'application'
|
||||||
|
description = 'Startet das Spiel'
|
||||||
|
mainClass = mainClassName
|
||||||
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
|
jvmArgs = [
|
||||||
|
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
||||||
|
'--add-opens', 'java.desktop/sun.awt=ALL-UNNAMED',
|
||||||
|
"-Djava.library.path=${buildDir}/natives",
|
||||||
|
]
|
||||||
dependsOn extractNatives
|
dependsOn extractNatives
|
||||||
workingDir = rootDir // gemeinsames Arbeitsverzeichnis = Projekt-Root
|
workingDir = rootDir
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
manifest {
|
manifest {
|
||||||
attributes 'Main-Class': application.mainClass
|
attributes 'Main-Class': mainClassName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,19 +6,24 @@ import com.jme3.app.state.BaseAppState;
|
|||||||
import com.jme3.asset.AssetManager;
|
import com.jme3.asset.AssetManager;
|
||||||
import com.jme3.bullet.BulletAppState;
|
import com.jme3.bullet.BulletAppState;
|
||||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||||
|
import com.jme3.bullet.collision.shapes.HeightfieldCollisionShape;
|
||||||
import com.jme3.bullet.control.CharacterControl;
|
import com.jme3.bullet.control.CharacterControl;
|
||||||
import com.jme3.bullet.control.RigidBodyControl;
|
import com.jme3.bullet.control.RigidBodyControl;
|
||||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||||
import com.jme3.light.*;
|
import com.jme3.light.*;
|
||||||
import com.jme3.material.Material;
|
import com.jme3.material.Material;
|
||||||
|
import com.jme3.material.RenderState;
|
||||||
import com.jme3.math.*;
|
import com.jme3.math.*;
|
||||||
import com.jme3.renderer.queue.RenderQueue;
|
import com.jme3.renderer.queue.RenderQueue;
|
||||||
import com.jme3.scene.*;
|
import com.jme3.scene.*;
|
||||||
|
import com.jme3.scene.VertexBuffer;
|
||||||
import com.jme3.scene.shape.*;
|
import com.jme3.scene.shape.*;
|
||||||
import com.jme3.shadow.*;
|
import com.jme3.shadow.*;
|
||||||
import com.jme3.terrain.geomipmap.*;
|
import com.jme3.terrain.geomipmap.*;
|
||||||
import com.jme3.texture.*;
|
import com.jme3.texture.*;
|
||||||
|
import com.jme3.util.BufferUtils;
|
||||||
import com.jme3.util.SkyFactory;
|
import com.jme3.util.SkyFactory;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import de.blight.common.MapData;
|
import de.blight.common.MapData;
|
||||||
import de.blight.common.MapIO;
|
import de.blight.common.MapIO;
|
||||||
import de.blight.game.config.KeyBindings;
|
import de.blight.game.config.KeyBindings;
|
||||||
@@ -27,6 +32,8 @@ import de.blight.game.control.ThirdPersonCamera;
|
|||||||
import de.blight.game.state.GrassState;
|
import de.blight.game.state.GrassState;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class WorldScene extends BaseAppState {
|
public class WorldScene extends BaseAppState {
|
||||||
|
|
||||||
@@ -73,9 +80,9 @@ public class WorldScene extends BaseAppState {
|
|||||||
protected void onEnable() {
|
protected void onEnable() {
|
||||||
buildLighting();
|
buildLighting();
|
||||||
TerrainQuad terrain = buildTerrain();
|
TerrainQuad terrain = buildTerrain();
|
||||||
buildDecorations(terrain);
|
|
||||||
|
|
||||||
if (loadedMapData != null) {
|
if (loadedMapData != null) {
|
||||||
|
rootNode.attachChild(buildGebirge(loadedMapData));
|
||||||
app.getStateManager().attach(new GrassState(loadedMapData, terrain));
|
app.getStateManager().attach(new GrassState(loadedMapData, terrain));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,23 +188,31 @@ public class WorldScene extends BaseAppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Höhe in der Weltmitte als Spawn-Grundlage
|
// Spawn über dem höchsten Punkt – Basis-Terrain UND Gebirge-Oberkante
|
||||||
float centerHeight = heights[(GAME_VERTS / 2) * GAME_VERTS + (GAME_VERTS / 2)];
|
float minH = Float.MAX_VALUE, maxH = -Float.MAX_VALUE;
|
||||||
spawnY = centerHeight + 3f;
|
for (float h : heights) { if (h < minH) minH = h; if (h > maxH) maxH = h; }
|
||||||
|
float midH = (minH + maxH) * 0.5f;
|
||||||
|
float maxUpperTop = maxH;
|
||||||
|
for (float h : map.upperTop) { if (h > maxUpperTop) maxUpperTop = h; }
|
||||||
|
spawnY = maxUpperTop + 20f;
|
||||||
|
|
||||||
TerrainQuad terrain = new TerrainQuad("terrain", 65, GAME_VERTS, heights);
|
TerrainQuad terrain = new TerrainQuad("terrain", 65, GAME_VERTS, heights);
|
||||||
terrain.setLocalScale(8f, 1f, 8f); // 512 Zellen * 8 WE = 4096 WE pro Achse
|
terrain.setLocalScale(8f, 1f, 8f);
|
||||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||||
|
applyTerrainMaterial(terrain, map);
|
||||||
|
rootNode.attachChild(terrain);
|
||||||
|
|
||||||
applyTerrainMaterial(terrain, 32f);
|
// jBullet subtrahiert midH intern in getVertex() → Physics-Body bei midH
|
||||||
|
// damit Kollisionsfläche und sichtbares Terrain übereinstimmen.
|
||||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
HeightfieldCollisionShape hcs = new HeightfieldCollisionShape(
|
||||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
heights, terrain.getLocalScale());
|
||||||
|
RigidBodyControl terrainPhysics = new RigidBodyControl(hcs, 0f);
|
||||||
terrain.addControl(terrainPhysics);
|
terrain.addControl(terrainPhysics);
|
||||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||||
|
terrainPhysics.setPhysicsLocation(new Vector3f(0f, midH, 0f));
|
||||||
|
|
||||||
rootNode.attachChild(terrain);
|
System.out.println("[WorldScene] Karte geladen, Spawn Y=" + spawnY
|
||||||
System.out.println("[WorldScene] Karte geladen, Spawn Y=" + spawnY);
|
+ " maxGebirgeH=" + maxUpperTop);
|
||||||
return terrain;
|
return terrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,32 +238,234 @@ public class WorldScene extends BaseAppState {
|
|||||||
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
|
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
|
||||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||||
|
|
||||||
applyTerrainMaterial(terrain, 64f);
|
applyTerrainMaterial(terrain, null);
|
||||||
|
|
||||||
|
rootNode.attachChild(terrain);
|
||||||
|
|
||||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
||||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
||||||
terrain.addControl(terrainPhysics);
|
terrain.addControl(terrainPhysics);
|
||||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||||
|
|
||||||
rootNode.attachChild(terrain);
|
|
||||||
return terrain;
|
return terrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTerrainMaterial(TerrainQuad terrain, float texScale) {
|
// -----------------------------------------------------------------------
|
||||||
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
// Gebirge (obere Gesteinsschicht)
|
||||||
|
// -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
private Node buildGebirge(MapData map) {
|
||||||
|
final int VERTS = 513;
|
||||||
|
final int CELLS = 512;
|
||||||
|
final float CELL = 8f;
|
||||||
|
final float ORIGIN = -2048f;
|
||||||
|
final int CHUNK = 16;
|
||||||
|
final int NCHUNK = CELLS / CHUNK;
|
||||||
|
|
||||||
|
Node node = new Node("gebirge");
|
||||||
|
|
||||||
|
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||||
try {
|
try {
|
||||||
|
Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg");
|
||||||
|
rock.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
mat.setTexture("DiffuseMap", rock);
|
||||||
|
mat.setColor("Diffuse", new ColorRGBA(0.45f, 0.32f, 0.25f, 1f));
|
||||||
|
} catch (Exception e) {
|
||||||
|
mat.setBoolean("UseMaterialColors", true);
|
||||||
|
mat.setColor("Diffuse", new ColorRGBA(0.18f, 0.12f, 0.08f, 1f));
|
||||||
|
}
|
||||||
|
mat.setColor("Ambient", new ColorRGBA(0.10f, 0.07f, 0.05f, 1f));
|
||||||
|
mat.setColor("Specular", new ColorRGBA(0.06f, 0.05f, 0.04f, 1f));
|
||||||
|
mat.setFloat("Shininess", 6f);
|
||||||
|
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||||
|
mat.getAdditionalRenderState().setPolyOffset(2f, 2f);
|
||||||
|
|
||||||
|
for (int chunkZ = 0; chunkZ < NCHUNK; chunkZ++) {
|
||||||
|
for (int chunkX = 0; chunkX < NCHUNK; chunkX++) {
|
||||||
|
List<Float> pos = new ArrayList<>();
|
||||||
|
List<Float> norm = new ArrayList<>();
|
||||||
|
List<Float> uv = new ArrayList<>();
|
||||||
|
List<Integer> idx = new ArrayList<>();
|
||||||
|
int vtx = 0;
|
||||||
|
|
||||||
|
for (int lcz = 0; lcz < CHUNK; lcz++) {
|
||||||
|
int cz = chunkZ * CHUNK + lcz;
|
||||||
|
for (int lcx = 0; lcx < CHUNK; lcx++) {
|
||||||
|
int cx = chunkX * CHUNK + lcx;
|
||||||
|
if (map.upperHole[cz * CELLS + cx] != 0) continue;
|
||||||
|
|
||||||
|
float wx0 = ORIGIN + cx * CELL, wx1 = wx0 + CELL;
|
||||||
|
float wz0 = ORIGIN + cz * CELL, wz1 = wz0 + CELL;
|
||||||
|
float t00 = map.upperTop[cz*VERTS+cx], t10 = map.upperTop[cz*VERTS+cx+1];
|
||||||
|
float t01 = map.upperTop[(cz+1)*VERTS+cx], t11 = map.upperTop[(cz+1)*VERTS+cx+1];
|
||||||
|
float b00 = map.upperBottom[cz*VERTS+cx], b10 = map.upperBottom[cz*VERTS+cx+1];
|
||||||
|
float b01 = map.upperBottom[(cz+1)*VERTS+cx], b11 = map.upperBottom[(cz+1)*VERTS+cx+1];
|
||||||
|
|
||||||
|
// UV: eine Textur-Kachel pro Zelle (8 WE)
|
||||||
|
float u0 = cx, u1 = cx+1f, v0 = cz, v1 = cz+1f;
|
||||||
|
|
||||||
|
// Oben (XZ-Ebene)
|
||||||
|
vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx0,t00,wz0, wx1,t10,wz0, wx1,t11,wz1, wx0,t01,wz1, 0,1,0,
|
||||||
|
u0,v0, u1,v0, u1,v1, u0,v1);
|
||||||
|
// Unten
|
||||||
|
vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx0,b00,wz0, wx0,b01,wz1, wx1,b11,wz1, wx1,b10,wz0, 0,-1,0,
|
||||||
|
u0,v0, u0,v1, u1,v1, u1,v0);
|
||||||
|
// Seiten – UV: horizontal = Zellposition, vertikal = Höhe normiert
|
||||||
|
if (gebirgeHole(map,cx,cz-1,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx1,t10,wz0, wx0,t00,wz0, wx0,b00,wz0, wx1,b10,wz0, 0,0,-1,
|
||||||
|
u1,t10, u0,t00, u0,b00, u1,b10);
|
||||||
|
if (gebirgeHole(map,cx,cz+1,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx0,t01,wz1, wx1,t11,wz1, wx1,b11,wz1, wx0,b01,wz1, 0,0,1,
|
||||||
|
u0,t01, u1,t11, u1,b11, u0,b01);
|
||||||
|
if (gebirgeHole(map,cx-1,cz,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx0,t00,wz0, wx0,t01,wz1, wx0,b01,wz1, wx0,b00,wz0, -1,0,0,
|
||||||
|
v0,t00, v1,t01, v1,b01, v0,b00);
|
||||||
|
if (gebirgeHole(map,cx+1,cz,CELLS)) vtx = quad(pos,norm,uv,idx,vtx,
|
||||||
|
wx1,t11,wz1, wx1,t10,wz0, wx1,b10,wz0, wx1,b11,wz1, 1,0,0,
|
||||||
|
v1,t11, v0,t10, v0,b10, v1,b11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (idx.isEmpty()) continue;
|
||||||
|
|
||||||
|
Mesh mesh = new Mesh();
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(toFA(pos)));
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Normal, 3, BufferUtils.createFloatBuffer(toFA(norm)));
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(toFA(uv)));
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(toIA(idx)));
|
||||||
|
mesh.updateBound();
|
||||||
|
mesh.updateCounts();
|
||||||
|
|
||||||
|
Geometry geom = new Geometry("gebirge_" + chunkX + "_" + chunkZ, mesh);
|
||||||
|
geom.setMaterial(mat);
|
||||||
|
geom.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||||
|
|
||||||
|
RigidBodyControl rbc = new RigidBodyControl(
|
||||||
|
CollisionShapeFactory.createMeshShape(geom), 0f);
|
||||||
|
geom.addControl(rbc);
|
||||||
|
bulletAppState.getPhysicsSpace().add(rbc);
|
||||||
|
node.attachChild(geom);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean gebirgeHole(MapData map, int cx, int cz, int CELLS) {
|
||||||
|
if (cx < 0 || cx >= CELLS || cz < 0 || cz >= CELLS) return true;
|
||||||
|
return map.upperHole[cz * CELLS + cx] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Fügt ein Quad (2 Dreiecke) mit Position, Normal, UV und Index hinzu. */
|
||||||
|
private int quad(List<Float> pos, List<Float> norm, List<Float> uv, List<Integer> idx, int v,
|
||||||
|
float x0, float y0, float z0, float x1, float y1, float z1,
|
||||||
|
float x2, float y2, float z2, float x3, float y3, float z3,
|
||||||
|
float nx, float ny, float nz,
|
||||||
|
float u0, float v0, float u1, float v1,
|
||||||
|
float u2, float v2, float u3, float v3) {
|
||||||
|
pos.add(x0); pos.add(y0); pos.add(z0);
|
||||||
|
pos.add(x1); pos.add(y1); pos.add(z1);
|
||||||
|
pos.add(x2); pos.add(y2); pos.add(z2);
|
||||||
|
pos.add(x3); pos.add(y3); pos.add(z3);
|
||||||
|
for (int i = 0; i < 4; i++) { norm.add(nx); norm.add(ny); norm.add(nz); }
|
||||||
|
uv.add(u0); uv.add(v0);
|
||||||
|
uv.add(u1); uv.add(v1);
|
||||||
|
uv.add(u2); uv.add(v2);
|
||||||
|
uv.add(u3); uv.add(v3);
|
||||||
|
idx.add(v); idx.add(v+1); idx.add(v+2);
|
||||||
|
idx.add(v); idx.add(v+2); idx.add(v+3);
|
||||||
|
return v + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] toFA(List<Float> l) {
|
||||||
|
float[] a = new float[l.size()];
|
||||||
|
for (int i = 0; i < l.size(); i++) a[i] = l.get(i);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int[] toIA(List<Integer> l) {
|
||||||
|
int[] a = new int[l.size()];
|
||||||
|
for (int i = 0; i < l.size(); i++) a[i] = l.get(i);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTerrainMaterial(TerrainQuad terrain, MapData map) {
|
||||||
|
if (map != null) {
|
||||||
|
try {
|
||||||
|
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||||
|
|
||||||
|
Texture tex1 = loadTexOrFallback("Textures/Terrain/splat/grass.jpg",
|
||||||
|
new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
|
||||||
|
Texture tex2 = loadTexOrFallback("Textures/Terrain/splat/road.jpg",
|
||||||
|
new ColorRGBA(0.55f, 0.50f, 0.40f, 1f));
|
||||||
|
Texture tex3 = loadTexOrFallback("Textures/Terrain/splat/Gravel.jpg",
|
||||||
|
new ColorRGBA(0.45f, 0.35f, 0.25f, 1f));
|
||||||
|
tex1.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
tex2.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
tex3.setWrap(Texture.WrapMode.Repeat);
|
||||||
|
mat.setTexture("Tex1", tex1); mat.setFloat("Tex1Scale", 512f);
|
||||||
|
mat.setTexture("Tex2", tex2); mat.setFloat("Tex2Scale", 512f);
|
||||||
|
mat.setTexture("Tex3", tex3); mat.setFloat("Tex3Scale", 512f);
|
||||||
|
|
||||||
|
// Ältere Maps haben splatR=0 → Gras (Tex1) wäre unsichtbar; auf 255 setzen.
|
||||||
|
byte[] splatR = map.splatR;
|
||||||
|
boolean rAllZero = true;
|
||||||
|
for (byte b : splatR) { if (b != 0) { rAllZero = false; break; } }
|
||||||
|
if (rAllZero) {
|
||||||
|
splatR = new byte[splatR.length];
|
||||||
|
java.util.Arrays.fill(splatR, (byte) 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sz = MapData.SPLAT_SIZE;
|
||||||
|
ByteBuffer splatBuf = BufferUtils.createByteBuffer(sz * sz * 4);
|
||||||
|
for (int i = 0; i < sz * sz; i++) {
|
||||||
|
splatBuf.put(splatR[i]);
|
||||||
|
splatBuf.put(map.splatG[i]);
|
||||||
|
splatBuf.put(map.splatB[i]);
|
||||||
|
splatBuf.put((byte) 0);
|
||||||
|
}
|
||||||
|
splatBuf.flip();
|
||||||
|
Texture2D splatTex = new Texture2D(new Image(Image.Format.RGBA8, sz, sz, splatBuf));
|
||||||
|
splatTex.setWrap(Texture.WrapMode.EdgeClamp);
|
||||||
|
splatTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
|
||||||
|
splatTex.setMagFilter(Texture.MagFilter.Bilinear);
|
||||||
|
mat.setTexture("Alpha", splatTex);
|
||||||
|
|
||||||
|
terrain.setMaterial(mat);
|
||||||
|
return;
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("[WorldScene] Splat-Material fehlgeschlagen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: einfaches Gras-Material
|
||||||
|
try {
|
||||||
|
Material mat = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md");
|
||||||
Texture grass = assetManager.loadTexture("Textures/gras.png");
|
Texture grass = assetManager.loadTexture("Textures/gras.png");
|
||||||
grass.setWrap(Texture.WrapMode.Repeat);
|
grass.setWrap(Texture.WrapMode.Repeat);
|
||||||
mat.setTexture("Tex1", grass);
|
mat.setTexture("DiffuseMap", grass);
|
||||||
mat.setFloat("Tex1Scale", texScale);
|
mat.setFloat("DiffuseMap_0_scale", 32f);
|
||||||
|
mat.setBoolean("useTriPlanarMapping", false);
|
||||||
|
terrain.setMaterial(mat);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// Fallback: einfarbiges Material
|
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||||
mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
|
||||||
mat.setBoolean("UseMaterialColors", true);
|
mat.setBoolean("UseMaterialColors", true);
|
||||||
mat.setColor("Diffuse", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
|
mat.setColor("Diffuse", new ColorRGBA(0.28f, 0.58f, 0.18f, 1f));
|
||||||
mat.setColor("Ambient", new ColorRGBA(0.15f, 0.30f, 0.09f, 1f));
|
mat.setColor("Ambient", new ColorRGBA(0.15f, 0.30f, 0.09f, 1f));
|
||||||
|
terrain.setMaterial(mat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Texture loadTexOrFallback(String path, ColorRGBA color) {
|
||||||
|
try {
|
||||||
|
return assetManager.loadTexture(path);
|
||||||
|
} catch (Exception e) {
|
||||||
|
ByteBuffer buf = BufferUtils.createByteBuffer(4);
|
||||||
|
buf.put((byte)(color.r * 255)).put((byte)(color.g * 255))
|
||||||
|
.put((byte)(color.b * 255)).put((byte)(color.a * 255));
|
||||||
|
buf.flip();
|
||||||
|
return new Texture2D(new Image(Image.Format.RGBA8, 1, 1, buf));
|
||||||
}
|
}
|
||||||
terrain.setMaterial(mat);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------
|
// -----------------------------------------------------------------------
|
||||||
|
|||||||
14
blight-map/build.gradle
Normal file
14
blight-map/build.gradle
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// Enthält die Karten-Daten des Spiels (src/main/map/blight_map.blm).
|
||||||
|
// Wird von blight-editor und blight-game importiert, damit beide
|
||||||
|
// denselben kanonischen Map-Pfad verwenden.
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main {
|
||||||
|
resources {
|
||||||
|
srcDirs = ['src/main/map', 'src/main/resources']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
blight-map/src/main/java/de/blight/map/MapPaths.java
Normal file
13
blight-map/src/main/java/de/blight/map/MapPaths.java
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package de.blight.map;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
/** Kanonische Pfade zur Karten-Datei, relativ zum Projekt-Root (workingDir). */
|
||||||
|
public final class MapPaths {
|
||||||
|
|
||||||
|
public static final Path MAP_DIR = Paths.get("blight-map", "src", "main", "map");
|
||||||
|
public static final Path MAP_FILE = MAP_DIR.resolve("blight_map.blm");
|
||||||
|
|
||||||
|
private MapPaths() {}
|
||||||
|
}
|
||||||
0
blight-map/src/main/map/.gitkeep
Normal file
0
blight-map/src/main/map/.gitkeep
Normal file
BIN
blight-map/src/main/map/blight_map.blm
Normal file
BIN
blight-map/src/main/map/blight_map.blm
Normal file
Binary file not shown.
@@ -9,8 +9,11 @@ subprojects {
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
|
||||||
java {
|
java {
|
||||||
sourceCompatibility = JavaVersion.VERSION_26
|
// Toolchain: Source-Kompilierung mit Java 26, Gradle-Daemon läuft auf Java 21
|
||||||
targetCompatibility = JavaVersion.VERSION_26
|
// (siehe gradle.properties → org.gradle.java.home).
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(26)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
|
|||||||
3
gradle.properties
Normal file
3
gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Gradle-Daemon läuft auf Java 21 (Kompatibilität mit Groovy 3.x ASM).
|
||||||
|
# Source-Kompilierung erfolgt über Toolchain mit Java 26 (→ build.gradle).
|
||||||
|
org.gradle.java.home=/usr/lib/jvm/java-21-openjdk-amd64
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
validateDistributionUrl=true
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ rootProject.name = 'blight'
|
|||||||
|
|
||||||
include 'blight-common'
|
include 'blight-common'
|
||||||
include 'blight-assets'
|
include 'blight-assets'
|
||||||
|
include 'blight-map'
|
||||||
include 'blight-editor'
|
include 'blight-editor'
|
||||||
include 'blight-game'
|
include 'blight-game'
|
||||||
include 'simarboreal'
|
include 'simarboreal'
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user