Mehrere Probleme behoben
This commit is contained in:
Binary file not shown.
@@ -69,7 +69,7 @@ sourceSets {
|
||||
|
||||
run {
|
||||
dependsOn extractNatives
|
||||
workingDir = rootDir // gemeinsames Arbeitsverzeichnis = Projekt-Root
|
||||
workingDir = rootDir
|
||||
}
|
||||
|
||||
jar {
|
||||
|
||||
@@ -291,8 +291,10 @@ public class EditorApp extends Application {
|
||||
private Label tempSpawnCoordsLabel;
|
||||
private Label permSpawnCoordsLabel;
|
||||
private Button gameNewBtn;
|
||||
private Button gameFullBtn;
|
||||
private boolean launchNewGameAfterSave = false;
|
||||
private boolean pendingNewGame = false;
|
||||
private boolean pendingAutostart = true;
|
||||
|
||||
// Baum-Ordner-Modus
|
||||
private Label randomTreeStatusLabel;
|
||||
@@ -5448,7 +5450,11 @@ public class EditorApp extends Application {
|
||||
}
|
||||
target.getChildren().add(draggedItem);
|
||||
target.setExpanded(true);
|
||||
setStatus("Verschoben: " + src.getFileName());
|
||||
int updated = srcIsFolder
|
||||
? renameAssetPathsForFolder(src, dest)
|
||||
: renameAssetPaths(src, dest);
|
||||
String suffix = updated > 0 ? " (" + updated + " Referenz" + (updated == 1 ? "" : "en") + " aktualisiert)" : "";
|
||||
setStatus("Verschoben: " + src.getFileName() + suffix);
|
||||
ok = true;
|
||||
} catch (IOException ex) {
|
||||
setStatus("Verschieben fehlgeschlagen: " + ex.getMessage());
|
||||
@@ -5838,7 +5844,11 @@ public class EditorApp extends Application {
|
||||
updateDescendantPaths(item, oldPath, newPath);
|
||||
}
|
||||
item.setValue(newName);
|
||||
setStatus("Umbenannt: " + oldFileName + " → " + newName);
|
||||
int updated = Files.isDirectory(newPath)
|
||||
? renameAssetPathsForFolder(oldPath, newPath)
|
||||
: renameAssetPaths(oldPath, newPath);
|
||||
String suffix = updated > 0 ? " (" + updated + " Referenz" + (updated == 1 ? "" : "en") + " aktualisiert)" : "";
|
||||
setStatus("Umbenannt: " + oldFileName + " → " + newName + suffix);
|
||||
} catch (IOException ex) {
|
||||
setStatus("Umbenennen fehlgeschlagen: " + ex.getMessage());
|
||||
}
|
||||
@@ -5854,6 +5864,94 @@ public class EditorApp extends Application {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Asset-Pfad-Umbenennung bei Move/Rename ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Aktualisiert alle in SharedInput gespeicherten Asset-Pfade wenn eine einzelne Datei
|
||||
* von oldAbs nach newAbs verschoben/umbenannt wurde.
|
||||
* Gibt die Anzahl aktualisierter Referenzen zurück.
|
||||
*/
|
||||
private int renameAssetPaths(Path oldAbs, Path newAbs) {
|
||||
if (!oldAbs.startsWith(ASSET_ROOT) || !newAbs.startsWith(ASSET_ROOT)) return 0;
|
||||
String oldRel = ASSET_ROOT.relativize(oldAbs).toString().replace('\\', '/');
|
||||
String newRel = ASSET_ROOT.relativize(newAbs).toString().replace('\\', '/');
|
||||
return applyPathRenameToSharedInput(oldRel, newRel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert alle Pfade wenn ein Ordner verschoben/umbenannt wurde:
|
||||
* Walk durch den neuen Ordner, für jede Datei alte/neue rel. Pfade berechnen.
|
||||
*/
|
||||
private int renameAssetPathsForFolder(Path oldFolderAbs, Path newFolderAbs) {
|
||||
int total = 0;
|
||||
try (java.util.stream.Stream<Path> stream = Files.walk(newFolderAbs)) {
|
||||
for (Path newFile : (Iterable<Path>) stream.filter(Files::isRegularFile)::iterator) {
|
||||
Path relToNew = newFolderAbs.relativize(newFile);
|
||||
Path oldFile = oldFolderAbs.resolve(relToNew);
|
||||
total += renameAssetPaths(oldFile, newFile);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
log.warn("[Asset] Ordner-Pfad-Update fehlgeschlagen: {}", ex.getMessage());
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
/** Wendet ein (oldRel→newRel)-Rename auf alle SharedInput-Pfad-Arrays an. */
|
||||
private int applyPathRenameToSharedInput(String oldRel, String newRel) {
|
||||
int count = 0;
|
||||
|
||||
count += replaceInPaths(input.terrainTexturePaths, oldRel, newRel,
|
||||
() -> input.terrainTexturesChanged = true);
|
||||
count += replaceInPaths(input.terrainNormalMapPaths, oldRel, newRel,
|
||||
() -> input.terrainNormalMapsChanged = true);
|
||||
count += replaceInPaths(input.terrainDisplacementMapPaths, oldRel, newRel, null);
|
||||
|
||||
count += replaceInPaths(input.upperTexturePaths, oldRel, newRel,
|
||||
() -> input.upperTexturesChanged = true);
|
||||
count += replaceInPaths(input.upperNormalMapPaths, oldRel, newRel,
|
||||
() -> input.upperNormalMapsChanged = true);
|
||||
count += replaceInPaths(input.upperDisplacementMapPaths, oldRel, newRel, null);
|
||||
|
||||
count += replaceInPaths(input.thirdTexturePaths, oldRel, newRel,
|
||||
() -> input.thirdTexturesChanged = true);
|
||||
count += replaceInPaths(input.thirdNormalMapPaths, oldRel, newRel,
|
||||
() -> input.thirdNormalMapsChanged = true);
|
||||
count += replaceInPaths(input.thirdDisplacementMapPaths, oldRel, newRel, null);
|
||||
|
||||
count += replaceInPaths(input.voxelSplatTexturePaths, oldRel, newRel,
|
||||
() -> input.voxelSplatTexturesChanged = true);
|
||||
|
||||
if (oldRel.equals(input.grassTexturePath)) {
|
||||
input.grassTexturePath = newRel;
|
||||
input.grassSlotsChanged = true;
|
||||
count++;
|
||||
}
|
||||
count += replaceInPaths(input.grassTextureSlots, oldRel, newRel,
|
||||
() -> input.grassSlotsChanged = true);
|
||||
count += replaceInPaths(input.grassNormalMapPaths, oldRel, newRel,
|
||||
() -> input.grassSlotsChanged = true);
|
||||
|
||||
// JME3-States (SceneObjects, Emitter) über Queue informieren
|
||||
SharedInput.PathRename rename = new SharedInput.PathRename(oldRel, newRel);
|
||||
input.sceneObjectPathRenames.offer(rename);
|
||||
input.emitterPathRenames.offer(rename);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Ersetzt oldRel durch newRel in einem String-Array; ruft onChange einmalig wenn geändert. */
|
||||
private static int replaceInPaths(String[] arr, String oldRel, String newRel, Runnable onChange) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
if (oldRel.equals(arr[i])) {
|
||||
arr[i] = newRel;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count > 0 && onChange != null) onChange.run();
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Scannt alle JME-JARs im Classpath und baut den Textur-Teilbaum unter root auf. */
|
||||
private void loadJmeTexturesInto(TreeItem<String> root) {
|
||||
String[] texExts = {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".dds"};
|
||||
@@ -8615,9 +8713,11 @@ public class EditorApp extends Application {
|
||||
private void launchGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = false;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = true;
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setDisable(true); gamePlayBtn.setText("⏳ Startet…"); }
|
||||
if (gameNewBtn != null) gameNewBtn.setDisable(true);
|
||||
if (gameFullBtn != null) gameFullBtn.setDisable(true);
|
||||
input.saveRequested = true;
|
||||
setStatus("Karte wird gespeichert, Spiel startet…");
|
||||
}
|
||||
@@ -8625,16 +8725,32 @@ public class EditorApp extends Application {
|
||||
private void launchNewGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = true;
|
||||
pendingNewGame = true;
|
||||
pendingAutostart = true;
|
||||
if (gameNewBtn != null) { gameNewBtn.setDisable(true); gameNewBtn.setText("⏳ Startet…"); }
|
||||
if (gamePlayBtn != null) gamePlayBtn.setDisable(true);
|
||||
if (gameFullBtn != null) gameFullBtn.setDisable(true);
|
||||
input.saveRequested = true;
|
||||
setStatus("Karte wird gespeichert, Neues Spiel startet…");
|
||||
}
|
||||
|
||||
private void launchFullGame() {
|
||||
if (launchGameAfterSave) return;
|
||||
launchGameAfterSave = true;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = false;
|
||||
if (gameFullBtn != null) { gameFullBtn.setDisable(true); gameFullBtn.setText("⏳ Startet…"); }
|
||||
if (gamePlayBtn != null) gamePlayBtn.setDisable(true);
|
||||
if (gameNewBtn != null) gameNewBtn.setDisable(true);
|
||||
input.saveRequested = true;
|
||||
setStatus("Karte wird gespeichert, vollständiger Spielstart…");
|
||||
}
|
||||
|
||||
private void startGameProcess() {
|
||||
final boolean isNewGame = pendingNewGame;
|
||||
pendingNewGame = false;
|
||||
final boolean isNewGame = pendingNewGame;
|
||||
final boolean isAutostart = pendingAutostart;
|
||||
pendingNewGame = false;
|
||||
pendingAutostart = true;
|
||||
// Editor minimieren, damit das GLFW-Vollbild-Fenster keinen Focus-Konkurrenten hat.
|
||||
Platform.runLater(() -> { if (primaryStage != null) primaryStage.setIconified(true); });
|
||||
new Thread(() -> {
|
||||
@@ -8676,8 +8792,8 @@ public class EditorApp extends Application {
|
||||
"-Djava.library.path=" + libPath,
|
||||
"-Dblight.project.root=" + projRoot,
|
||||
"-D" + de.blight.common.MapIO.PROP_SESSION_MAP + "=" + sessionMapPath,
|
||||
// Vom Editor gestartet: Hauptmenü überspringen, letzten Stand fortsetzen
|
||||
"-Dblight.autostart=true"));
|
||||
// Vom Editor gestartet: ggf. Hauptmenü überspringen, letzten Stand fortsetzen
|
||||
"-Dblight.autostart=" + isAutostart));
|
||||
|
||||
if (isNewGame) {
|
||||
cmd.add("-Dblight.new.game=true");
|
||||
@@ -8698,6 +8814,7 @@ public class EditorApp extends Application {
|
||||
setStatus(isNewGame ? "Neues Spiel gestartet" : "Spiel gestartet");
|
||||
if (gamePlayBtn != null) gamePlayBtn.setText("🎮 Läuft…");
|
||||
if (gameNewBtn != null) gameNewBtn.setText("🎮 Läuft…");
|
||||
if (gameFullBtn != null) gameFullBtn.setText("🎮 Läuft…");
|
||||
if (debugConsoleCB != null && debugConsoleCB.isSelected()) openGameConsole();
|
||||
});
|
||||
|
||||
@@ -8714,15 +8831,17 @@ public class EditorApp extends Application {
|
||||
consoleBuffer.offer("--- Spiel beendet ---");
|
||||
Platform.runLater(() -> {
|
||||
if (primaryStage != null) primaryStage.setIconified(false);
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("🆕 Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("🆕 Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gameFullBtn != null) { gameFullBtn.setText("▶ Vollst. Start"); gameFullBtn.setDisable(false); }
|
||||
});
|
||||
|
||||
} catch (IOException ex) {
|
||||
Platform.runLater(() -> {
|
||||
setStatus("Spielstart fehlgeschlagen: " + ex.getMessage());
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("🆕 Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gamePlayBtn != null) { gamePlayBtn.setText("▶ Spielen"); gamePlayBtn.setDisable(false); }
|
||||
if (gameNewBtn != null) { gameNewBtn.setText("🆕 Neues Spiel"); gameNewBtn.setDisable(false); }
|
||||
if (gameFullBtn != null) { gameFullBtn.setText("▶ Vollst. Start"); gameFullBtn.setDisable(false); }
|
||||
});
|
||||
}
|
||||
}, "game-launcher").start();
|
||||
@@ -9769,11 +9888,21 @@ public class EditorApp extends Application {
|
||||
"Startet ein neues Spiel am perm. Spawnpunkt mit Intro-Sequenz"));
|
||||
newGameBtn.setOnAction(e -> launchNewGame());
|
||||
|
||||
Button fullStartBtn = new Button("▶ Vollst. Start");
|
||||
gameFullBtn = fullStartBtn;
|
||||
fullStartBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
fullStartBtn.setStyle(
|
||||
"-fx-background-color: #1a6080; -fx-text-fill: white; " +
|
||||
"-fx-font-weight: bold; -fx-padding: 6 12 6 12;");
|
||||
fullStartBtn.setTooltip(new javafx.scene.control.Tooltip(
|
||||
"Startet das Spiel wie von außen: mit OAA-Logo, JME-Logo und Hauptmenü"));
|
||||
fullStartBtn.setOnAction(e -> launchFullGame());
|
||||
|
||||
debugConsoleCB = new CheckBox("Debug-Konsole anzeigen");
|
||||
debugConsoleCB.setTooltip(new javafx.scene.control.Tooltip(
|
||||
"Öffnet bei Spielstart die Ausgabekonsole mit der Spielausgabe"));
|
||||
|
||||
inner.getChildren().addAll(playBtn, newGameBtn, debugConsoleCB);
|
||||
inner.getChildren().addAll(playBtn, newGameBtn, fullStartBtn, debugConsoleCB);
|
||||
|
||||
ScrollPane scroll = new ScrollPane(inner);
|
||||
scroll.setFitToWidth(true);
|
||||
|
||||
@@ -1143,4 +1143,12 @@ public class SharedInput {
|
||||
public volatile String[] voxelSplatTexturePaths = new String[]{"", "", "", ""};
|
||||
/** JFX setzt true wenn Voxel-Splat-Texturen geändert wurden; JME liest + resettet. */
|
||||
public volatile boolean voxelSplatTexturesChanged = false;
|
||||
|
||||
// ── Asset-Pfad-Umbenennung (JFX → JME3-States) ───────────────────────────
|
||||
/** Unveränderliches Paar (oldRel, newRel) relativ zu blight-assets/src/main/resources/. */
|
||||
public record PathRename(String oldRel, String newRel) {}
|
||||
/** SceneObjectState pollt diese Queue und aktualisiert alle SceneObject-Pfadfelder. */
|
||||
public final ConcurrentLinkedQueue<PathRename> sceneObjectPathRenames = new ConcurrentLinkedQueue<>();
|
||||
/** EmitterState pollt diese Queue und ersetzt betroffene PlacedEmitter-Records. */
|
||||
public final ConcurrentLinkedQueue<PathRename> emitterPathRenames = new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
@@ -82,6 +82,26 @@ public class EmitterState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
SharedInput.PathRename rename;
|
||||
while ((rename = input.emitterPathRenames.poll()) != null) {
|
||||
for (int i = 0; i < emitters.size(); i++) {
|
||||
PlacedEmitter e = emitters.get(i);
|
||||
if (rename.oldRel().equals(e.texturePath())) {
|
||||
emitters.set(i, new PlacedEmitter(
|
||||
e.x(), e.y(), e.z(), e.activationRadius(),
|
||||
rename.newRel(),
|
||||
e.imagesX(), e.imagesY(),
|
||||
e.startR(), e.startG(), e.startB(), e.startA(),
|
||||
e.endR(), e.endG(), e.endB(), e.endA(),
|
||||
e.startSize(), e.endSize(),
|
||||
e.velX(), e.velY(), e.velZ(), e.velocityVariation(),
|
||||
e.gravX(), e.gravY(), e.gravZ(),
|
||||
e.lowLife(), e.highLife(),
|
||||
e.maxParticles(), e.emitRate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharedInput.ObjectClick auswahlClick;
|
||||
while ((auswahlClick = input.auswahlEmitterClickQueue.poll()) != null) {
|
||||
handleAuswahlClick(auswahlClick);
|
||||
|
||||
@@ -326,6 +326,12 @@ public class SceneObjectState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
// Asset-Pfad-Umbenennung (Move/Rename im Asset-Browser)
|
||||
SharedInput.PathRename rename;
|
||||
while ((rename = input.sceneObjectPathRenames.poll()) != null) {
|
||||
applyPathRename(rename.oldRel(), rename.newRel());
|
||||
}
|
||||
|
||||
// Modell-Konvertierung und Mesh-Erstellung (unabhängig vom aktiven Layer)
|
||||
SharedInput.ModelConvertRequest conv;
|
||||
while ((conv = input.modelConvertQueue.poll()) != null) {
|
||||
@@ -2686,4 +2692,17 @@ public class SceneObjectState extends BaseAppState {
|
||||
benchArrowNode.attachChild(g);
|
||||
benchArrowNode.setCullHint(Spatial.CullHint.Inherit);
|
||||
}
|
||||
|
||||
// ── Asset-Pfad-Umbenennung ─────────────────────────────────────────────────
|
||||
|
||||
private void applyPathRename(String oldRel, String newRel) {
|
||||
for (SceneObject so : objects) {
|
||||
if (oldRel.equals(so.modelPath)) so.modelPath = newRel;
|
||||
if (oldRel.equals(so.texturePath)) so.texturePath = newRel;
|
||||
if (oldRel.equals(so.normalMapPath)) so.normalMapPath = newRel;
|
||||
if (oldRel.equals(so.materialPath)) so.materialPath = newRel;
|
||||
if (oldRel.equals(so.lod1Path)) so.lod1Path = newRel;
|
||||
if (oldRel.equals(so.lod2Path)) so.lod2Path = newRel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,14 +69,14 @@ public class TerrainEditorState extends BaseAppState {
|
||||
private static final Logger log = LoggerFactory.getLogger(TerrainEditorState.class);
|
||||
|
||||
// ── Terrain-Konstanten ────────────────────────────────────────────────────
|
||||
private static final int TERRAIN_SIZE = 4096;
|
||||
private static final int TERRAIN_SIZE = 2048;
|
||||
private static final int TOTAL_SIZE = TERRAIN_SIZE + 1; // 4097
|
||||
private static final float VERTEX_SPACING = (float) TERRAIN_SIZE / (TOTAL_SIZE - 1); // 1.0f
|
||||
private static final int PATCH_SIZE = 65;
|
||||
|
||||
// ── Splatmap-Konstanten ────────────────────────────────────────────────────
|
||||
private static final int SPLAT_SIZE = MapData.SPLAT_SIZE; // 2049
|
||||
private static final float WORLD_HALF = 2048f;
|
||||
private static final int SPLAT_SIZE = MapData.SPLAT_SIZE; // 1025
|
||||
private static final float WORLD_HALF = TERRAIN_SIZE * 0.5f;
|
||||
private static final float SPLAT_WE_PER_PX = (float) TERRAIN_SIZE / (SPLAT_SIZE - 1); // 2 WE/px
|
||||
|
||||
// ── Kamera ────────────────────────────────────────────────────────────────
|
||||
@@ -96,6 +96,7 @@ public class TerrainEditorState extends BaseAppState {
|
||||
private com.jme3.material.Material terrainMat;
|
||||
private float[] cachedHeightMap; // Einmal geladen, danach manuell synchron gehalten
|
||||
private Geometry brushIndicator;
|
||||
private Geometry waterGeo;
|
||||
private PlacedObjectState placedObjectState;
|
||||
private GrassVertexState grassVertexState;
|
||||
private StoneEditorState stoneEditorState;
|
||||
@@ -377,7 +378,8 @@ public class TerrainEditorState extends BaseAppState {
|
||||
PathNetworkEditorState pathNetState = app.getStateManager().getState(PathNetworkEditorState.class);
|
||||
if (pathNetState != null) pathNetState.setTerrain(terrain);
|
||||
|
||||
rootNode.attachChild(buildWater());
|
||||
waterGeo = buildWater();
|
||||
rootNode.attachChild(waterGeo);
|
||||
|
||||
brushIndicator = buildBrushIndicator();
|
||||
rootNode.attachChild(brushIndicator);
|
||||
@@ -956,6 +958,14 @@ public class TerrainEditorState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
// Wasserebene im Voxel-Layer ausblenden (stört Sicht unter Wasser)
|
||||
if (waterGeo != null) {
|
||||
boolean voxelActive = input.activeLayer == SharedInput.LAYER_VOXEL;
|
||||
waterGeo.setCullHint(voxelActive
|
||||
? com.jme3.scene.Spatial.CullHint.Always
|
||||
: com.jme3.scene.Spatial.CullHint.Inherit);
|
||||
}
|
||||
|
||||
// LightProbe backen: nach 30 Frames hat SkyControl den Himmel vollständig gerendert.
|
||||
if (envCam != null) {
|
||||
envCamFrames++;
|
||||
@@ -1881,9 +1891,13 @@ public class TerrainEditorState extends BaseAppState {
|
||||
}
|
||||
camPos.y = FastMath.clamp(camPos.y, -200f, MAX_CAM_Y);
|
||||
|
||||
// Kamera nicht unter das Terrain fallen lassen
|
||||
float terrainFloor = getTerrainHeightFast(camPos.x, camPos.z) + 2f;
|
||||
if (camPos.y < terrainFloor) camPos.y = terrainFloor;
|
||||
// Kamera nicht unter das Terrain oder gebackenes Voxel-Terrain fallen lassen
|
||||
float baseFloor = getTerrainHeightFast(camPos.x, camPos.z);
|
||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
||||
float floor = (ves != null)
|
||||
? Math.max(baseFloor, ves.columnTopWorldY(camPos.x, camPos.z))
|
||||
: baseFloor;
|
||||
if (camPos.y < floor + 2f) camPos.y = floor + 2f;
|
||||
|
||||
cam.setLocation(camPos);
|
||||
}
|
||||
@@ -1917,15 +1931,23 @@ public class TerrainEditorState extends BaseAppState {
|
||||
|
||||
// ── Hilfsobjekte ─────────────────────────────────────────────────────────
|
||||
|
||||
private static final float WATER_SIZE = 6_000f; // folgt der Kamera → 3 km Radius reicht
|
||||
private static final float WATER_HALF = WATER_SIZE * 0.5f;
|
||||
private static final float WATER_Y = 0f;
|
||||
|
||||
private Geometry buildWater() {
|
||||
float half = TERRAIN_SIZE * 0.5f;
|
||||
Geometry water = new Geometry("water", new Quad(TERRAIN_SIZE, TERRAIN_SIZE));
|
||||
Geometry water = new Geometry("water", new Quad(WATER_SIZE, WATER_SIZE)) {
|
||||
@Override
|
||||
public int collideWith(com.jme3.collision.Collidable other,
|
||||
com.jme3.collision.CollisionResults results) { return 0; }
|
||||
};
|
||||
water.rotate(-FastMath.HALF_PI, 0, 0);
|
||||
water.setLocalTranslation(-half, 0.01f, half);
|
||||
water.setLocalTranslation(-WATER_HALF, WATER_Y, WATER_HALF); // zentriert auf Welt-Ursprung, statisch
|
||||
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(0.05f, 0.25f, 0.70f, 0.55f));
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setPolyOffset(1f, 1f); // Terrain gewinnt Z-Test an der Küstenlinie
|
||||
water.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
water.setMaterial(mat);
|
||||
return water;
|
||||
|
||||
@@ -5,14 +5,14 @@ import java.util.Arrays;
|
||||
/**
|
||||
* Data arrays for the upper (mountain) layer.
|
||||
*
|
||||
* Grid: 512×512 cells → 513×513 vertices, covering 4096×4096 world units
|
||||
* (8 world units per cell). World origin is at grid centre: vertex (256,256)
|
||||
* Grid: 256×256 cells → 257×257 vertices, covering 2048×2048 world units
|
||||
* (8 world units per cell). World origin is at grid centre: vertex (128,128)
|
||||
* maps to world (0,0).
|
||||
*/
|
||||
public class UpperLayerData {
|
||||
|
||||
public static final int CELLS = 512; // cells per axis
|
||||
public static final int VERTS = 513; // vertices per axis (CELLS + 1)
|
||||
public static final int CELLS = 256; // cells per axis
|
||||
public static final int VERTS = 257; // vertices per axis (CELLS + 1)
|
||||
|
||||
/** Y of the top surface at each vertex [VERTS*VERTS]. */
|
||||
public final float[] topHeight;
|
||||
@@ -29,6 +29,8 @@ public class UpperLayerData {
|
||||
/** Dicke der Gesteinsschicht in Welteinheiten. */
|
||||
public static final float LAYER_THICKNESS = 30f;
|
||||
|
||||
private static final float WORLD_HALF = 1024f; // 2048 / 2
|
||||
|
||||
public UpperLayerData() {
|
||||
topHeight = new float[VERTS * VERTS];
|
||||
bottomHeight = new float[VERTS * VERTS];
|
||||
@@ -59,17 +61,17 @@ public class UpperLayerData {
|
||||
|
||||
/** World X/Z → nearest vertex index (clamped). */
|
||||
public static int worldToVertexX(float wx) {
|
||||
return Math.max(0, Math.min(VERTS - 1, Math.round((wx + 2048f) / 8f)));
|
||||
return Math.max(0, Math.min(VERTS - 1, Math.round((wx + WORLD_HALF) / 8f)));
|
||||
}
|
||||
public static int worldToVertexZ(float wz) {
|
||||
return Math.max(0, Math.min(VERTS - 1, Math.round((wz + 2048f) / 8f)));
|
||||
return Math.max(0, Math.min(VERTS - 1, Math.round((wz + WORLD_HALF) / 8f)));
|
||||
}
|
||||
|
||||
/** World X/Z → cell index (floored, clamped). */
|
||||
public static int worldToCellX(float wx) {
|
||||
return Math.max(0, Math.min(CELLS - 1, (int) ((wx + 2048f) / 8f)));
|
||||
return Math.max(0, Math.min(CELLS - 1, (int) ((wx + WORLD_HALF) / 8f)));
|
||||
}
|
||||
public static int worldToCellZ(float wz) {
|
||||
return Math.max(0, Math.min(CELLS - 1, (int) ((wz + 2048f) / 8f)));
|
||||
return Math.max(0, Math.min(CELLS - 1, (int) ((wz + WORLD_HALF) / 8f)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
package de.blight.editor.tools;
|
||||
|
||||
import de.blight.common.MapData;
|
||||
import de.blight.common.MapIO;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Erzeugt eine neue, leere 2048×2048m Karte:
|
||||
* - Überall Mindesthöhe 2m
|
||||
* - Letzten 25m am Rand: linearer Abfall von 2m auf -10m
|
||||
* - Rand-Bereich (Splatmap): Slot 8 (= upperSplatA) auf 255
|
||||
*/
|
||||
public class NewMapGenerator {
|
||||
|
||||
private static final float WORLD_HALF = 1024f; // 2048 / 2
|
||||
private static final float HEIGHT_FLAT = 2f;
|
||||
private static final float HEIGHT_EDGE = -10f;
|
||||
private static final float RAMP_DIST = 25f;
|
||||
private static final float VERTEX_STEP = 0.25f; // 1 / 4 m pro Vertex
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("[NewMapGenerator] Starte Karten-Generierung...");
|
||||
MapData data = new MapData();
|
||||
|
||||
// Textur-Konfiguration aus bestehender Map übernehmen (falls vorhanden)
|
||||
if (MapIO.exists()) {
|
||||
try {
|
||||
MapData existing = MapIO.load();
|
||||
TexturePathRestorer.copyTexturePaths(existing, data);
|
||||
System.out.println("[NewMapGenerator] Textur-Pfade aus bestehender Map übernommen.");
|
||||
} catch (Exception e) {
|
||||
System.out.println("[NewMapGenerator] Konnte Textur-Pfade nicht laden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Terrain-Höhen ─────────────────────────────────────────────────────
|
||||
int verts = MapData.TERRAIN_VERTS; // 8193
|
||||
for (int vz = 0; vz < verts; vz++) {
|
||||
float wz = vz * VERTEX_STEP - WORLD_HALF;
|
||||
for (int vx = 0; vx < verts; vx++) {
|
||||
float wx = vx * VERTEX_STEP - WORLD_HALF;
|
||||
data.terrainHeight[vz * verts + vx] = heightAt(wx, wz);
|
||||
}
|
||||
}
|
||||
System.out.printf("[NewMapGenerator] Terrain (%d×%d Vertices) fertig.%n", verts, verts);
|
||||
|
||||
// ── Basis-Splatmap: Slot 1 (splatR) immer 255 ─────────────────────────
|
||||
Arrays.fill(data.splatR, (byte) 255);
|
||||
|
||||
// ── Rand-Splatmap: Slot 8 (upperSplatA) im Randbereich ────────────────
|
||||
int splat = MapData.SPLAT_SIZE; // 1025
|
||||
float splatStep = (WORLD_HALF * 2f) / (splat - 1); // 2 m/Pixel
|
||||
for (int pz = 0; pz < splat; pz++) {
|
||||
// Z ist im Splatmap invertiert: Zeile 0 = max. Welt-Z
|
||||
float wz = (splat - 1 - pz) * splatStep - WORLD_HALF;
|
||||
for (int px = 0; px < splat; px++) {
|
||||
float wx = px * splatStep - WORLD_HALF;
|
||||
float dist = edgeDist(wx, wz);
|
||||
if (dist < RAMP_DIST) {
|
||||
int idx = pz * splat + px;
|
||||
data.upperSplatA[idx] = (byte) 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
System.out.printf("[NewMapGenerator] Splatmap (%d×%d Pixel) fertig.%n", splat, splat);
|
||||
|
||||
// ── Spawnpunkt in der Mitte ────────────────────────────────────────────
|
||||
data.spawnX = 0f;
|
||||
data.spawnZ = 0f;
|
||||
|
||||
MapIO.save(data);
|
||||
System.out.println("[NewMapGenerator] blight_map.blm gespeichert.");
|
||||
}
|
||||
|
||||
private static float heightAt(float wx, float wz) {
|
||||
float dist = edgeDist(wx, wz);
|
||||
if (dist >= RAMP_DIST) return HEIGHT_FLAT;
|
||||
if (dist <= 0f) return HEIGHT_EDGE;
|
||||
float t = dist / RAMP_DIST;
|
||||
return HEIGHT_EDGE + (HEIGHT_FLAT - HEIGHT_EDGE) * t;
|
||||
}
|
||||
|
||||
private static float edgeDist(float wx, float wz) {
|
||||
float dxMin = wx + WORLD_HALF;
|
||||
float dxMax = WORLD_HALF - wx;
|
||||
float dzMin = wz + WORLD_HALF;
|
||||
float dzMax = WORLD_HALF - wz;
|
||||
return Math.min(Math.min(dxMin, dxMax), Math.min(dzMin, dzMax));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package de.blight.editor.tools;
|
||||
|
||||
import de.blight.common.MapData;
|
||||
import de.blight.common.MapIO;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Kopiert Textur-Konfiguration aus einer alten Map-Datei in die aktuelle Map.
|
||||
*
|
||||
* Verwendung: java -cp ... TexturePathRestorer <pfad-zur-alten-map.blm>
|
||||
*/
|
||||
public class TexturePathRestorer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args.length < 1) {
|
||||
System.err.println("Verwendung: TexturePathRestorer <pfad-zur-alten-map.blm>");
|
||||
System.exit(1);
|
||||
}
|
||||
Path oldPath = Paths.get(args[0]).toAbsolutePath();
|
||||
System.out.println("[Restorer] Lade alte Map: " + oldPath);
|
||||
MapData old = MapIO.loadFrom(oldPath);
|
||||
|
||||
System.out.println("[Restorer] Lade aktuelle Map...");
|
||||
MapData cur = MapIO.load();
|
||||
|
||||
copyTexturePaths(old, cur);
|
||||
|
||||
System.out.println("[Restorer] Speichere...");
|
||||
MapIO.save(cur);
|
||||
System.out.println("[Restorer] Fertig.");
|
||||
}
|
||||
|
||||
static void copyTexturePaths(MapData src, MapData dst) {
|
||||
System.arraycopy(src.terrainTextures, 0, dst.terrainTextures, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.terrainNormalMaps, 0, dst.terrainNormalMaps, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.terrainDisplacementMaps,0,dst.terrainDisplacementMaps,0,MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.upperTextures, 0, dst.upperTextures, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.upperNormalMaps, 0, dst.upperNormalMaps, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.upperDisplacementMaps, 0, dst.upperDisplacementMaps, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.thirdTextures, 0, dst.thirdTextures, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.thirdNormalMaps, 0, dst.thirdNormalMaps, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.thirdDisplacementMaps, 0, dst.thirdDisplacementMaps, 0, MapData.TEXTURE_SLOTS);
|
||||
System.arraycopy(src.diffuseScales, 0, dst.diffuseScales, 0, src.diffuseScales.length);
|
||||
System.arraycopy(src.normalMapOrder, 0, dst.normalMapOrder, 0, src.normalMapOrder.length);
|
||||
dst.voxelFlatSlot = src.voxelFlatSlot;
|
||||
dst.voxelSteepSlot = src.voxelSteepSlot;
|
||||
dst.voxelCeilSlot = src.voxelCeilSlot;
|
||||
dst.grassTexturePath = src.grassTexturePath;
|
||||
dst.grassDefaultHeight = src.grassDefaultHeight;
|
||||
dst.grassTextureSlots = src.grassTextureSlots != null
|
||||
? Arrays.copyOf(src.grassTextureSlots, src.grassTextureSlots.length)
|
||||
: new String[0];
|
||||
|
||||
System.out.println("[Restorer] Terrain-Texturen: " + Arrays.toString(dst.terrainTextures));
|
||||
System.out.println("[Restorer] Upper-Texturen: " + Arrays.toString(dst.upperTextures));
|
||||
System.out.println("[Restorer] Third-Texturen: " + Arrays.toString(dst.thirdTextures));
|
||||
}
|
||||
}
|
||||
@@ -25,11 +25,6 @@
|
||||
<logger name="com.jme3.util.TangentBinormalGenerator" level="ERROR"/>
|
||||
<!-- Material warnt bei linear-color-space Texturen ohne passenden Parameter – bekannt, kein Fehler -->
|
||||
<logger name="com.jme3.material.Material" level="ERROR"/>
|
||||
<logger name="de.blight.game.animation.FootIKControl" level="DEBUG"/>
|
||||
<logger name="de.blight.editor.state.VoxelEditorState" level="TRACE"/>
|
||||
<logger name="de.blight.editor.EditorPrefs" level="DEBUG"/>
|
||||
<logger name="de.blight.editor.state.TerrainEditorState" level="DEBUG"/>
|
||||
<logger name="de.blight.editor.EditorApp" level="DEBUG"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
|
||||
Reference in New Issue
Block a user