diff --git a/blight-game/src/main/java/de/blight/game/BlightGame.java b/blight-game/src/main/java/de/blight/game/BlightGame.java index 27a21ab..c3b1c80 100644 --- a/blight-game/src/main/java/de/blight/game/BlightGame.java +++ b/blight-game/src/main/java/de/blight/game/BlightGame.java @@ -197,6 +197,7 @@ public class BlightGame extends SimpleApplication { keyBindings = KeyBindingStore.load(); graphicsSettings = GraphicsStore.load(); audioSettings = AudioSettingsStore.load(); + de.blight.game.state.ModelLodControl.debugLod2 = graphicsSettings.debugLod2; startTimeMs = System.currentTimeMillis(); initialFixDone = false; @@ -478,6 +479,8 @@ public class BlightGame extends SimpleApplication { rebuildState(HudState.class); rebuildState(HotbarState.class); rebuildState(LoadingScreenState.class); + JmeConsole console = stateManager.getState(JmeConsole.class); + if (console != null) console.rebuild(); } private void rebuildState(Class cls) { @@ -550,6 +553,73 @@ public class BlightGame extends SimpleApplication { return "Unbekanntes Wetter. Erlaubt: sunny, cloudy, overcast, storm"; } }); + + console.registerCommand("set", args -> { + if (args.length < 3) + return "Syntax: set " + + " | debugLod2, width, height, fullscreen, vsync, samples, viewDistance, shadowQuality"; + String key = args[1].toLowerCase(); + String val = args[2]; + return switch (key) { + case "debuglod2" -> { + boolean v = Boolean.parseBoolean(val); + graphicsSettings.debugLod2 = v; + de.blight.game.state.ModelLodControl.debugLod2 = v; + GraphicsStore.save(graphicsSettings); + yield "debugLod2 = " + v; + } + case "width" -> { + try { graphicsSettings.width = Integer.parseInt(val); } + catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; } + GraphicsStore.save(graphicsSettings); + yield "width = " + graphicsSettings.width + " (Neustart erforderlich)"; + } + case "height" -> { + try { graphicsSettings.height = Integer.parseInt(val); } + catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; } + GraphicsStore.save(graphicsSettings); + yield "height = " + graphicsSettings.height + " (Neustart erforderlich)"; + } + case "fullscreen" -> { + graphicsSettings.fullscreen = Boolean.parseBoolean(val); + GraphicsStore.save(graphicsSettings); + yield "fullscreen = " + graphicsSettings.fullscreen + " (Neustart erforderlich)"; + } + case "vsync" -> { + graphicsSettings.vsync = Boolean.parseBoolean(val); + GraphicsStore.save(graphicsSettings); + yield "vsync = " + graphicsSettings.vsync + " (Neustart erforderlich)"; + } + case "samples" -> { + try { graphicsSettings.samples = Integer.parseInt(val); } + catch (NumberFormatException e) { yield "Fehler: ganze Zahl erwartet"; } + GraphicsStore.save(graphicsSettings); + yield "samples = " + graphicsSettings.samples + " (Neustart erforderlich)"; + } + case "viewdistance" -> { + try { + graphicsSettings.viewDistance = + GraphicsSettings.ViewDistance.valueOf(val.toUpperCase()); + } catch (IllegalArgumentException e) { + yield "Unbekannt. Erlaubt: SHORT, NORMAL, FAR"; + } + GraphicsStore.save(graphicsSettings); + yield "viewDistance = " + graphicsSettings.viewDistance + " (Neustart erforderlich)"; + } + case "shadowquality" -> { + try { + graphicsSettings.shadowQuality = + GraphicsSettings.ShadowQuality.valueOf(val.toUpperCase()); + } catch (IllegalArgumentException e) { + yield "Unbekannt. Erlaubt: LOW, MEDIUM, HIGH"; + } + GraphicsStore.save(graphicsSettings); + yield "shadowQuality = " + graphicsSettings.shadowQuality + " (Neustart erforderlich)"; + } + default -> "Unbekannter Schlüssel: " + args[1] + + " | Erlaubt: debugLod2, width, height, fullscreen, vsync, samples, viewDistance, shadowQuality"; + }; + }); } // Vollbild sicherstellen: GLFW ist hier bereits durch JME3 initialisiert. diff --git a/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java b/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java index e376e93..e96ea62 100644 --- a/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java +++ b/blight-game/src/main/java/de/blight/game/config/GraphicsSettings.java @@ -8,6 +8,8 @@ public class GraphicsSettings { public int samples = 4; public ViewDistance viewDistance = ViewDistance.NORMAL; public ShadowQuality shadowQuality = ShadowQuality.MEDIUM; + /** Verstecktes Debug-Flag: erzwingt LOD2 (Impostor) für alle Objekte. Nur per Konsole setzbar. */ + public boolean debugLod2 = false; /** * Schattenqualitäts-Stufen. diff --git a/blight-game/src/main/java/de/blight/game/state/ModelLodControl.java b/blight-game/src/main/java/de/blight/game/state/ModelLodControl.java index b9e4bb6..f1df497 100644 --- a/blight-game/src/main/java/de/blight/game/state/ModelLodControl.java +++ b/blight-game/src/main/java/de/blight/game/state/ModelLodControl.java @@ -31,6 +31,9 @@ public class ModelLodControl extends AbstractControl { private static final Logger log = LoggerFactory.getLogger(ModelLodControl.class); + /** Erzwingt LOD2 für alle Instanzen. Wird per Konsolen-Befehl "set debugLod2 true" gesetzt. */ + public static volatile boolean debugLod2 = false; + private final Camera cam; private final float lod1DistSq; private final float lod2DistSq; @@ -119,7 +122,10 @@ public class ModelLodControl extends AbstractControl { // Fehlt LOD1 aber LOD2 vorhanden: bei lod1Distance direkt zu LOD2 wechseln (nicht ausblenden). // Fehlt beides (z.B. Kaktusfeige): ab lod1Distance ausblenden. int targetSlot; - if (distSq >= lod2DistSq) { + if (debugLod2) { + // Debug: erzwingt die niedrigste verfügbare LOD-Stufe + targetSlot = hasLod2 ? 2 : (hasLod1 ? 1 : 0); + } else if (distSq >= lod2DistSq) { targetSlot = hasLod2 ? 2 : -1; } else if (distSq >= lod1DistSq) { if (hasLod1) targetSlot = 1; diff --git a/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java b/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java index 5b49a8d..e023275 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldObjectsState.java @@ -41,10 +41,16 @@ public class WorldObjectsState extends BaseAppState { private final List sceneLitMaterials = new ArrayList<>(); private final List windMaterials = new ArrayList<>(); private final List pbrMaterials = new ArrayList<>(); - /** Impostor-Unshaded-Materialien: erhalten pro Frame einen Ambient-Tint (Color-Uniform). */ + /** Impostor-Unshaded-Materialien: erhalten pro Frame einen Tint (Color-Uniform). */ private final List impostorMaterials = new ArrayList<>(); - /** Capture-Ambient, mit dem der Impostor-Atlas gerendert wurde. Normalisiert den Tint. */ - private static final float IMPOSTOR_CAP_AMB = 0.30f; + // Licht-Werte beim Impostor-Atlas-Capture (aus ImpostorUtil): Haupt-Sonne + Ambient. + // Tint = (aktSonne + aktAmbient) / (capSonne + capAmbient) → folgt dem Tageszyklus. + private static final float IMPOSTOR_CAP_SUN_R = 0.70f; + private static final float IMPOSTOR_CAP_SUN_G = 0.65f; + private static final float IMPOSTOR_CAP_SUN_B = 0.52f; + private static final float IMPOSTOR_CAP_AMB_R = 0.30f; + private static final float IMPOSTOR_CAP_AMB_G = 0.30f; + private static final float IMPOSTOR_CAP_AMB_B = 0.32f; private float debugTimer = 0f; private float lodFactor = 1.0f; @@ -174,12 +180,12 @@ public class WorldObjectsState extends BaseAppState { for (Material mat : pbrMaterials) { mat.setColor("Emissive", new ColorRGBA(ac.r, ac.g, ac.b, 1f)); } - // Impostor-Tint: Unshaded-Atlas folgt dem Tag/Nacht-Zyklus über den Color-Multiplikator. - // Normiert auf die Capture-Ambient (0.30): Bei gleichem Ambient = 1.0, Nacht ≈ 0.05. + // Impostor-Tint: (aktSonne + aktAmbient) / (captureSonne + captureAmbient) + // → Impostor folgt Sonne und Ambient; ≈1.0 bei gleichem Licht wie beim Capture. if (!impostorMaterials.isEmpty()) { - float tR = Math.min(1.2f, ac.r / IMPOSTOR_CAP_AMB); - float tG = Math.min(1.2f, ac.g / IMPOSTOR_CAP_AMB); - float tB = Math.min(1.2f, ac.b / IMPOSTOR_CAP_AMB); + float tR = Math.min(1.5f, (sc.r + ac.r) / (IMPOSTOR_CAP_SUN_R + IMPOSTOR_CAP_AMB_R)); + float tG = Math.min(1.5f, (sc.g + ac.g) / (IMPOSTOR_CAP_SUN_G + IMPOSTOR_CAP_AMB_G)); + float tB = Math.min(1.5f, (sc.b + ac.b) / (IMPOSTOR_CAP_SUN_B + IMPOSTOR_CAP_AMB_B)); ColorRGBA tint = new ColorRGBA(tR, tG, tB, 1f); for (Material mat : impostorMaterials) { mat.setColor("Color", tint);