Am Lightning System gearbeitet

This commit is contained in:
2026-07-17 11:23:44 +02:00
parent cdaaba3841
commit 5ed7b4017c
23 changed files with 928 additions and 164 deletions

View File

@@ -1137,7 +1137,18 @@ public class EditorApp extends Application {
viewMenu.getItems().addAll(resetCam, new SeparatorMenuItem(), viewTexture, viewWireframe,
new SeparatorMenuItem(), viewTopology);
menuBar.getMenus().addAll(fileMenu, toolsMenu, viewMenu);
Menu zeitMenu = new Menu("Zeit");
ToggleGroup zeitGroup = new ToggleGroup();
int[] hours = {0, 3, 6, 9, 12, 15, 18, 21};
for (int h : hours) {
RadioMenuItem item = new RadioMenuItem(String.format("%02d:00", h));
item.setToggleGroup(zeitGroup);
if (h == 12) { item.setSelected(true); }
final float t = h / 24f;
item.setOnAction(e -> input.pendingTimeOfDay = t);
zeitMenu.getItems().add(item);
}
menuBar.getMenus().addAll(fileMenu, toolsMenu, viewMenu, zeitMenu);
ToolBar toolBar = new ToolBar();
baseBtn = new ToggleButton("▲▼ Basis-Terrain");

View File

@@ -309,6 +309,17 @@ public class JmeEditorApp extends SimpleApplication {
input.camPitch = (float) Math.toDegrees(
Math.asin(Math.max(-1f, Math.min(1f, dir.y))));
// Zeit-Menü: Tageszeit setzen
float pendingTime = input.pendingTimeOfDay;
if (!Float.isNaN(pendingTime)) {
input.pendingTimeOfDay = Float.NaN;
DayNightState dns = stateManager.getState(DayNightState.class);
if (dns != null) {
dns.getDayTime().setTimeOfDay(pendingTime);
dns.getDayTime().setPaused(true);
}
}
if (jmeConsole == null) return;
// Toggle-Signal von JavaFX

View File

@@ -359,6 +359,9 @@ public class SharedInput {
/** JME setzt diesen Wert; JavaFX liest ihn (z. B. für Eingabesperre). */
public volatile boolean consoleIsOpen = false;
/** JavaFX → JME3: Tageszeit setzen (0.0 = Mitternacht, 0.5 = Mittag, 1.0 = Mitternacht). NaN = kein Auftrag. */
public volatile float pendingTimeOfDay = Float.NaN;
/** @deprecated Nur noch für Rückwärtskompatibilität nicht mehr verwenden. */
@Deprecated public volatile String pendingCommand = null;
/** Status-/Fehlermeldungen von JME an JavaFX-Statusleiste. */

View File

@@ -113,6 +113,11 @@ public class SceneObjectState extends BaseAppState {
/** Kleiner Cache: modelPath → ModelMeta, damit nicht pro Frame geladen werden muss. */
private final java.util.Map<String, de.blight.common.ModelMeta> metaCache = new java.util.HashMap<>();
// ── Beleuchtungs-Materialien (Tree/TreeLeaf/Fern) ────────────────────────
private final List<Material> sceneLitMaterials = new ArrayList<>();
private final List<Material> windMaterials = new ArrayList<>();
private final List<Material> pbrMaterials = new ArrayList<>();
// ── Bett-Liegefläche ─────────────────────────────────────────────────────
/** Visualisierungspfeil für die Liegefläche (1,8 m); wird nur gezeigt wenn Bett-Objekt gewählt. */
private Node bedArrowNode = null;
@@ -183,6 +188,9 @@ public class SceneObjectState extends BaseAppState {
objects.clear();
objNodes.clear();
animClips.clear();
sceneLitMaterials.clear();
windMaterials.clear();
pbrMaterials.clear();
selectedIndices.clear();
activeGizmo = -1;
if (gizmoNode != null)
@@ -222,6 +230,7 @@ public class SceneObjectState extends BaseAppState {
objNodes.add(node);
objectRoot.attachChild(node);
collectLitMaterials(node);
}
}
@@ -310,6 +319,32 @@ public class SceneObjectState extends BaseAppState {
updateGizmoVisibility();
updateGizmoOrientation();
// Baum- und Farn-Shader mit Sonnen- und Windwerten versorgen; PBR-Emissive setzen
if (!sceneLitMaterials.isEmpty() || !windMaterials.isEmpty() || !pbrMaterials.isEmpty()) {
de.blight.game.state.DayNightState dns =
getApplication().getStateManager().getState(de.blight.game.state.DayNightState.class);
if (dns != null && dns.getSunLight() != null) {
Vector3f lightDir = dns.getSunDirection().negate();
com.jme3.math.ColorRGBA sc = dns.getSunLight().getColor();
com.jme3.math.ColorRGBA ac = dns.getCustomAmbient();
Vector3f sunVec = new Vector3f(sc.r, sc.g, sc.b);
Vector3f ambVec = new Vector3f(ac.r, ac.g, ac.b);
for (Material mat : sceneLitMaterials) {
mat.setVector3("LightDir", lightDir);
mat.setVector3("SunColor", sunVec);
mat.setVector3("AmbientColor", ambVec);
}
for (Material mat : windMaterials) {
mat.setVector3("LightDir", lightDir);
mat.setVector3("SunColor", sunVec);
mat.setVector3("AmbientColor", ambVec);
}
for (Material mat : pbrMaterials) {
mat.setColor("Emissive", new com.jme3.math.ColorRGBA(ac.r, ac.g, ac.b, 1f));
}
}
}
// Baum-Ordner-Platzierungsmodus
String treeFolder = input.treeFolderPath;
if (treeFolder != null && !treeFolder.equals(activeFolderPath)) {
@@ -687,6 +722,7 @@ public class SceneObjectState extends BaseAppState {
}
objNodes.add(node);
objectRoot.attachChild(node);
collectLitMaterials(node);
selectObject(objects.size() - 1, false);
input.objectJustPlaced = true;
@@ -741,13 +777,73 @@ public class SceneObjectState extends BaseAppState {
}
}
private void convertUnshadedToPbr(Spatial spatial) {
if (spatial instanceof Geometry geo) {
Material mat = geo.getMaterial();
if (mat == null) return;
if (!"Unshaded".equals(mat.getMaterialDef().getName())) return;
com.jme3.material.MatParam colorMap = mat.getParam("ColorMap");
com.jme3.material.MatParam color = mat.getParam("Color");
com.jme3.material.RenderState origRS = mat.getAdditionalRenderState();
Material pbrMat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
pbrMat.setFloat("Metallic", 0.0f);
pbrMat.setFloat("Roughness", 0.85f);
if (colorMap != null && colorMap.getValue() instanceof Texture t) {
pbrMat.setTexture("BaseColorMap", t);
} else if (color != null && color.getValue() instanceof ColorRGBA c) {
pbrMat.setColor("BaseColor", new ColorRGBA(c.r, c.g, c.b, 1f));
}
com.jme3.material.RenderState rs = pbrMat.getAdditionalRenderState();
rs.setBlendMode(origRS.getBlendMode());
rs.setFaceCullMode(origRS.getFaceCullMode());
geo.setMaterial(pbrMat);
} else if (spatial instanceof Node node) {
for (Spatial child : new java.util.ArrayList<>(node.getChildren())) {
convertUnshadedToPbr(child);
}
}
}
private void collectLitMaterials(Spatial spatial) {
if (spatial instanceof Geometry geo) {
Material mat = geo.getMaterial();
if (mat == null) return;
String name = mat.getMaterialDef().getName();
if ("Tree".equals(name) || "TreeLeaf".equals(name)) {
mat.setVector3("SunColor", new com.jme3.math.Vector3f(0.68f, 0.65f, 0.60f));
mat.setVector3("AmbientColor", new com.jme3.math.Vector3f(0.08f, 0.10f, 0.16f));
mat.setVector3("LightDir", new com.jme3.math.Vector3f(0f, -1f, 0f));
sceneLitMaterials.add(mat);
} else if ("Fern".equals(name)) {
windMaterials.add(mat);
} else if ("PBRLighting".equals(name)) {
mat.setFloat("EmissivePower", 1f);
mat.setFloat("EmissiveIntensity", 1f);
mat.setColor("Emissive", new com.jme3.math.ColorRGBA(0.08f, 0.10f, 0.16f, 1f));
pbrMaterials.add(mat);
}
} else if (spatial instanceof Node node) {
for (Spatial child : node.getChildren()) {
collectLitMaterials(child);
}
}
}
private Node loadModelNode(String modelPath, float wx, float wy, float wz) {
Node node = new Node("obj_" + objects.size());
try {
Spatial model = modelPath.startsWith("@")
? createPrimitiveSpatial(modelPath.substring(1))
: assets.loadModel(modelPath);
if (!modelPath.startsWith("@")) stripControlsRecursive(model);
if (!modelPath.startsWith("@")) {
stripControlsRecursive(model);
convertUnshadedToPbr(model);
}
if (modelPath.startsWith("@")) {
Material mat = buildPrimitiveMaterial();
if (model instanceof Geometry g) g.setMaterial(mat);
@@ -785,12 +881,13 @@ public class SceneObjectState extends BaseAppState {
boolean hasNmap = nmap != null && !nmap.isEmpty();
if (hasTex || hasNmap) {
try {
Material mat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
mat.setBoolean("UseMaterialColors", false);
Material mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.85f);
if (hasTex) {
Texture t = assets.loadTexture(tex);
t.setWrap(Texture.WrapMode.Repeat);
mat.setTexture("DiffuseMap", t);
mat.setTexture("BaseColorMap", t);
}
if (hasNmap) {
Texture n = assets.loadTexture(nmap);
@@ -800,8 +897,10 @@ public class SceneObjectState extends BaseAppState {
return mat;
} catch (Exception ignored) {}
}
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new com.jme3.math.ColorRGBA(0.75f, 0.75f, 0.75f, 1f));
Material mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.85f);
mat.setColor("BaseColor", new com.jme3.math.ColorRGBA(0.75f, 0.75f, 0.75f, 1f));
return mat;
}
@@ -1461,7 +1560,9 @@ public class SceneObjectState extends BaseAppState {
return;
}
} else if (hasTex) {
mat = new Material(assets, "Common/MatDefs/Light/Lighting.j3md");
mat = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md");
mat.setFloat("Metallic", 0.0f);
mat.setFloat("Roughness", 0.85f);
} else {
mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setColor("Color", new ColorRGBA(0.75f, 0.75f, 0.75f, 1f));

View File

@@ -96,6 +96,7 @@ public class TerrainEditorState extends BaseAppState {
private final SharedInput input;
private TerrainQuad terrain;
private com.jme3.material.Material terrainMat;
private float[] cachedHeightMap; // Einmal geladen, danach manuell synchron gehalten
private Geometry brushIndicator;
private PlacedObjectState placedObjectState;
@@ -157,6 +158,9 @@ public class TerrainEditorState extends BaseAppState {
// ── Lifecycle ─────────────────────────────────────────────────────────────
private de.blight.game.state.DayNightState dayNightState;
private com.jme3.light.LightProbe probe;
private com.jme3.environment.EnvironmentCamera envCam;
private int envCamFrames = 0;
@Override
protected void initialize(Application app) {
@@ -182,6 +186,16 @@ public class TerrainEditorState extends BaseAppState {
dayNightState.getDayTime().setTimeOfDay(0.5f);
dayNightState.setPaused(true);
app.getStateManager().attach(dayNightState);
// LightProbe für PBR-IBL ohne Probe ist envLightContribution immer 0 in JME3 3.9.0.
// Probe backen wenn SkyControl den Himmel gerendert hat (ab Frame 30).
probe = new com.jme3.light.LightProbe();
probe.getArea().setRadius(8000f);
rootNode.addLight(probe);
envCam = new com.jme3.environment.EnvironmentCamera(256, new com.jme3.math.Vector3f(0f, 5f, 0f));
app.getStateManager().attach(envCam);
envCamFrames = 0;
}
private void loadCameraPrefs() {
@@ -391,7 +405,7 @@ public class TerrainEditorState extends BaseAppState {
tq.addControl(lod);
initSplatmap();
tq.setMaterial(buildTerrainMaterial());
tq.setMaterial(terrainMat = buildTerrainMaterial());
return tq;
}
@@ -594,7 +608,7 @@ public class TerrainEditorState extends BaseAppState {
if (dayNightState != null && dayNightState.getSunLight() != null) {
mat.setVector3("LightDir", dayNightState.getSunDirection().negate());
com.jme3.math.ColorRGBA sc = dayNightState.getSunLight().getColor();
com.jme3.math.ColorRGBA ac = dayNightState.getAmbientLight().getColor();
com.jme3.math.ColorRGBA ac = dayNightState.getCustomAmbient();
mat.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b));
mat.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b));
}
@@ -925,6 +939,30 @@ public class TerrainEditorState extends BaseAppState {
@Override
public void update(float tpf) {
// LightProbe backen: nach 30 Frames hat SkyControl den Himmel vollständig gerendert.
if (envCam != null) {
envCamFrames++;
if (envCamFrames == 30) {
com.jme3.environment.LightProbeFactory.updateProbe(probe, envCam, rootNode,
new com.jme3.environment.generation.JobProgressAdapter<com.jme3.light.LightProbe>() {
@Override public void done(com.jme3.light.LightProbe result) {
app.getStateManager().detach(envCam);
envCam = null;
log.info("[TerrainEditor] LightProbe fertig.");
}
});
}
}
// Terrain-Shader mit DayNightState synchronisieren (reagiert auf Zeit-Menü-Änderungen)
if (terrainMat != null && dayNightState != null && dayNightState.getSunLight() != null) {
terrainMat.setVector3("LightDir", dayNightState.getSunDirection().negate());
com.jme3.math.ColorRGBA sc = dayNightState.getSunLight().getColor();
com.jme3.math.ColorRGBA ac = dayNightState.getCustomAmbient();
terrainMat.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b));
terrainMat.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b));
}
float pgx = input.pendingGotoX;
if (!Float.isNaN(pgx)) {
input.pendingGotoX = Float.NaN;
@@ -987,7 +1025,7 @@ public class TerrainEditorState extends BaseAppState {
input.thirdNormalMapsChanged = false;
input.normalMapOrderChanged = false;
input.diffuseScalesChanged = false;
terrain.setMaterial(buildTerrainMaterial());
terrain.setMaterial(terrainMat = buildTerrainMaterial());
}
if (input.reloadPlacedOther) {

View File

@@ -219,6 +219,19 @@ public class VoxelEditorState extends BaseAppState {
@Override
public void update(float tpf) {
// Voxel-Shader mit DayNightState synchronisieren
if (voxelMaterial != null) {
de.blight.game.state.DayNightState dns =
getApplication().getStateManager().getState(de.blight.game.state.DayNightState.class);
if (dns != null && dns.getSunLight() != null) {
voxelMaterial.setVector3("LightDir", dns.getSunDirection().negate());
com.jme3.math.ColorRGBA sc = dns.getSunLight().getColor();
com.jme3.math.ColorRGBA ac = dns.getCustomAmbient();
voxelMaterial.setVector3("SunColor", new com.jme3.math.Vector3f(sc.r, sc.g, sc.b));
voxelMaterial.setVector3("AmbientColor", new com.jme3.math.Vector3f(ac.r, ac.g, ac.b));
}
}
// LOD-Ergebnisse aus dem Hintergrund-Thread übernehmen
Runnable r;
while ((r = lodResultQueue.poll()) != null) r.run();