Mehrere Probleme behoben

This commit is contained in:
2026-08-16 21:48:19 +02:00
parent 225d9cfa82
commit 13a1c45ca6
1132 changed files with 754 additions and 133 deletions

View File

@@ -69,7 +69,7 @@ sourceSets {
run {
dependsOn extractNatives
workingDir = rootDir // gemeinsames Arbeitsverzeichnis = Projekt-Root
workingDir = rootDir
}
jar {

View File

@@ -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);

View File

@@ -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<>();
}

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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)));
}
}

View File

@@ -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));
}
}

View File

@@ -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));
}
}

View File

@@ -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"/>