diff --git a/blight-assets/src/main/resources/MatDefs/BlightFog.j3md b/blight-assets/src/main/resources/MatDefs/BlightFog.j3md new file mode 100644 index 0000000..54266fe --- /dev/null +++ b/blight-assets/src/main/resources/MatDefs/BlightFog.j3md @@ -0,0 +1,22 @@ +MaterialDef BlightFog { + + MaterialParameters { + Texture2D Texture + Texture2D DepthTexture + Int NumSamples + Int NumSamplesDepth + + Color FogColor : 0.75 0.80 0.88 1.0 + Float FogDensity : 0.40 + Float FogDistance : 600.0 + // Wird jeden Frame aus der echten Kamera gesetzt (near, far) + Vector2 FrustumNearFar : 1.0 1000.0 + } + + Technique { + VertexShader GLSL150: Common/MatDefs/Post/Post15.vert + FragmentShader GLSL150: Shaders/BlightFog.frag + + WorldParameters {} + } +} diff --git a/blight-assets/src/main/resources/Shaders/BlightFog.frag b/blight-assets/src/main/resources/Shaders/BlightFog.frag new file mode 100644 index 0000000..372ce8c --- /dev/null +++ b/blight-assets/src/main/resources/Shaders/BlightFog.frag @@ -0,0 +1,37 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +uniform sampler2D m_Texture; +uniform sampler2D m_DepthTexture; +uniform vec4 m_FogColor; +uniform float m_FogDensity; +uniform float m_FogDistance; +// Kamera-Frustum (near, far) — wird jeden Frame aus der echten Kamera gesetzt +uniform vec2 m_FrustumNearFar; + +in vec2 texCoord; + +void main() { + vec4 sceneColor = texture2D(m_Texture, texCoord); + float rawDepth = texture2D(m_DepthTexture, texCoord).r; + + // Sky-Pixel (Depth-Buffer = 1.0) nicht nebeln + if (rawDepth >= 1.0) { + gl_FragColor = sceneColor; + return; + } + + float near = m_FrustumNearFar.x; + float far = m_FrustumNearFar.y; + + // Depth-Buffer [0,1] → echte Weltdistanz (Meter) + // Herleitung: z_view = near*far / (far - rawDepth*(far-near)) + float linearDist = (near * far) / (far - rawDepth * (far - near)); + + // Normiert auf FogDistance; d=1 entspricht genau der eingestellten Nebel-Distanz + float d = linearDist / m_FogDistance; + + // Exponential-squared fog (gleiche Formel wie JME FogFilter, korrekte Tiefe) + float fogFactor = clamp(exp2(-m_FogDensity * m_FogDensity * d * d * 1.4426950408), 0.0, 1.0); + + gl_FragColor = mix(m_FogColor, sceneColor, fogFactor); +} diff --git a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java index 60f2e8e..596833f 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java @@ -1616,7 +1616,7 @@ public class TerrainEditorState extends BaseAppState { // Geschwindigkeit skaliert linear mit Abstand zum Terrain (näher = langsamer) float terrainDist = terrainDistBelow(); - float speed = FastMath.clamp(terrainDist, 5f, CAM_SPEED) * tpf; + float speed = FastMath.clamp(terrainDist, 5f, CAM_SPEED) * tpf * (input.shiftHeld ? 5f : 1f); if (input.camMode == SharedInput.CAM_FREEFLY) { Vector3f fwd = cam.getDirection().clone(); 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 48cf77e..5e564af 100644 --- a/blight-game/src/main/java/de/blight/game/BlightGame.java +++ b/blight-game/src/main/java/de/blight/game/BlightGame.java @@ -203,7 +203,7 @@ public class BlightGame extends SimpleApplication { // WorldScene erst vorbereiten, aber nicht aktivieren (onEnable lädt die Welt) status("Initialisiere Welt..."); - worldScene = new WorldScene(keyBindings); + worldScene = new WorldScene(keyBindings, graphicsSettings); worldScene.setEnabled(false); stateManager.attach(worldScene); @@ -268,6 +268,13 @@ public class BlightGame extends SimpleApplication { }); stateManager.attach(console); + // ── Debug: Nebel-Toggle (F7) ───────────────────────────────────────────── + inputManager.addMapping("DebugFog", new KeyTrigger(KeyInput.KEY_F7)); + inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { + if (!isPressed) return; + worldScene.toggleDebugFog(); + }, "DebugFog"); + // ── Debug: Lighting-Toggle (F8) ────────────────────────────────────────── inputManager.addMapping("DebugNoLight", new KeyTrigger(KeyInput.KEY_F8)); inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { diff --git a/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java b/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java index d6bd0cf..d9b446b 100644 --- a/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java +++ b/blight-game/src/main/java/de/blight/game/config/GraphicsScreen.java @@ -4,6 +4,8 @@ import com.jme3.font.BitmapText; import com.jme3.math.ColorRGBA; import com.jme3.scene.Node; import com.jme3.system.AppSettings; +import de.blight.game.state.TerrainChunkState; +import de.blight.game.state.WeatherState; import de.blight.lang.TextResolver; public class GraphicsScreen extends MenuScreen { @@ -13,11 +15,12 @@ public class GraphicsScreen extends MenuScreen { }; private static final int[] SAMPLES = {0, 2, 4, 8}; - private static final int ROW_RES = 0; - private static final int ROW_FULL = 1; - private static final int ROW_VSYNC = 2; - private static final int ROW_AA = 3; - private static final int ROW_COUNT = 4; + private static final int ROW_RES = 0; + private static final int ROW_FULL = 1; + private static final int ROW_VSYNC = 2; + private static final int ROW_AA = 3; + private static final int ROW_VIEWDIST = 4; + private static final int ROW_COUNT = 5; private final GraphicsSettings live; private GraphicsSettings edit; @@ -42,9 +45,10 @@ public class GraphicsScreen extends MenuScreen { protected void onEnableExtras() { edit = new GraphicsSettings(); edit.width = live.width; edit.height = live.height; - edit.fullscreen = live.fullscreen; - edit.vsync = live.vsync; - edit.samples = live.samples; + edit.fullscreen = live.fullscreen; + edit.vsync = live.vsync; + edit.samples = live.samples; + edit.viewDistance = live.viewDistance; resIdx = 0; for (int i = 0; i < RESOLUTIONS.length; i++) { @@ -66,7 +70,8 @@ public class GraphicsScreen extends MenuScreen { "menu.graphics.row.resolution", "menu.graphics.row.fullscreen", "menu.graphics.row.vsync", - "menu.graphics.row.aa" + "menu.graphics.row.aa", + "menu.graphics.row.viewdist" }; float lblX = CONT_X + 30f; @@ -110,11 +115,16 @@ public class GraphicsScreen extends MenuScreen { private void refreshText(int row) { String val = switch (row) { - case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1]; - case ROW_FULL -> edit.fullscreen ? t("menu.graphics.val.on") : t("menu.graphics.val.off"); - case ROW_VSYNC -> edit.vsync ? t("menu.graphics.val.on") : t("menu.graphics.val.off"); - case ROW_AA -> SAMPLES[samplesIdx] == 0 - ? t("menu.graphics.val.off") : SAMPLES[samplesIdx] + "x MSAA"; + case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1]; + case ROW_FULL -> edit.fullscreen ? t("menu.graphics.val.on") : t("menu.graphics.val.off"); + case ROW_VSYNC -> edit.vsync ? t("menu.graphics.val.on") : t("menu.graphics.val.off"); + case ROW_AA -> SAMPLES[samplesIdx] == 0 + ? t("menu.graphics.val.off") : SAMPLES[samplesIdx] + "x MSAA"; + case ROW_VIEWDIST -> switch (edit.viewDistance) { + case SHORT -> t("menu.graphics.val.viewdist.short"); + case NORMAL -> t("menu.graphics.val.viewdist.normal"); + case FAR -> t("menu.graphics.val.viewdist.far"); + }; default -> ""; }; BitmapText vt = valTexts[row]; @@ -140,27 +150,47 @@ public class GraphicsScreen extends MenuScreen { samplesIdx = (samplesIdx + dir + SAMPLES.length) % SAMPLES.length; edit.samples = SAMPLES[samplesIdx]; break; + case ROW_VIEWDIST: + GraphicsSettings.ViewDistance[] vds = GraphicsSettings.ViewDistance.values(); + edit.viewDistance = vds[(edit.viewDistance.ordinal() + dir + vds.length) % vds.length]; + break; } refreshText(row); } private void applyAndSave() { - live.width = edit.width; live.height = edit.height; - live.fullscreen = edit.fullscreen; - live.vsync = edit.vsync; - live.samples = edit.samples; + boolean needsRestart = live.width != edit.width + || live.height != edit.height + || live.fullscreen != edit.fullscreen + || live.vsync != edit.vsync + || live.samples != edit.samples; + + live.width = edit.width; + live.height = edit.height; + live.fullscreen = edit.fullscreen; + live.vsync = edit.vsync; + live.samples = edit.samples; + live.viewDistance = edit.viewDistance; GraphicsStore.save(live); - AppSettings s = app.getContext().getSettings(); - s.setResolution(live.width, live.height); - s.setFullscreen(live.fullscreen); - s.setBitsPerPixel(32); - s.setVSync(live.vsync); - s.setSamples(live.samples); - app.setSettings(s); + WeatherState ws = getApplication().getStateManager().getState(WeatherState.class); + if (ws != null) ws.setViewDistanceFactor(live.viewDistance.fogFactor); + + TerrainChunkState tcs = getApplication().getStateManager().getState(TerrainChunkState.class); + if (tcs != null) tcs.setLodRanges(live.viewDistance.lod0Range, live.viewDistance.lod1Range); close(); - app.restart(); + + if (needsRestart) { + AppSettings s = app.getContext().getSettings(); + s.setResolution(live.width, live.height); + s.setFullscreen(live.fullscreen); + s.setBitsPerPixel(32); + s.setVSync(live.vsync); + s.setSamples(live.samples); + app.setSettings(s); + app.restart(); + } } private void close() { 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 fbc9ea9..90206da 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 @@ -1,9 +1,26 @@ package de.blight.game.config; public class GraphicsSettings { - public int width = 1280; - public int height = 720; - public boolean fullscreen = false; - public boolean vsync = false; - public int samples = 4; + public int width = 1280; + public int height = 720; + public boolean fullscreen = false; + public boolean vsync = false; + public int samples = 4; + public ViewDistance viewDistance = ViewDistance.NORMAL; + + public enum ViewDistance { + SHORT (0.55f, 0, 2), + NORMAL(1.00f, 1, 3), + FAR (1.60f, 2, 5); + + public final float fogFactor; + public final int lod0Range; + public final int lod1Range; + + ViewDistance(float fogFactor, int lod0Range, int lod1Range) { + this.fogFactor = fogFactor; + this.lod0Range = lod0Range; + this.lod1Range = lod1Range; + } + } } diff --git a/blight-game/src/main/java/de/blight/game/post/BlightFogFilter.java b/blight-game/src/main/java/de/blight/game/post/BlightFogFilter.java new file mode 100644 index 0000000..4264aac --- /dev/null +++ b/blight-game/src/main/java/de/blight/game/post/BlightFogFilter.java @@ -0,0 +1,96 @@ +package de.blight.game.post; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector2f; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Nebelfilter mit korrekter Tiefen-Linearisierung. + * + * JME3s eingebauter FogFilter setzt FrustumNearFar=[1, fogDistance], was bei + * camera.far=4000m dazu führt, dass alle Objekte jenseits von fogDistance + * praktisch den gleichen Nebelwert bekommen (NDC-z clustert um 1.0). + * Dieser Filter liest jeden Frame near/far aus der echten Kamera und rechnet + * korrekt zurück auf die tatsächliche Weltdistanz. + */ +public class BlightFogFilter extends Filter { + + private float fogDensity = 0.40f; + private float fogDistance = 600f; + private ColorRGBA fogColor = new ColorRGBA(0.75f, 0.80f, 0.88f, 1f); + private Camera camera; + + public BlightFogFilter() { + super("BlightFog"); + } + + // ── Lifecycle ───────────────────────────────────────────────────────────── + + @Override + protected boolean isRequiresDepthTexture() { + return true; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager rm, + ViewPort vp, int w, int h) { + camera = vp.getCamera(); + material = new Material(manager, "MatDefs/BlightFog.j3md"); + material.setColor("FogColor", fogColor.clone()); + material.setFloat("FogDensity", fogDensity); + material.setFloat("FogDistance", fogDistance); + syncFrustum(); + } + + @Override + protected void preFrame(float tpf) { + syncFrustum(); + } + + @Override + protected Material getMaterial() { + return material; + } + + // ── Setter (gleiche API wie FogFilter) ─────────────────────────────────── + + public void setFogDensity(float density) { + this.fogDensity = density; + if (material != null) { + material.setFloat("FogDensity", density); + } + } + + public void setFogDistance(float distance) { + this.fogDistance = distance; + if (material != null) { + material.setFloat("FogDistance", distance); + } + } + + public void setFogColor(ColorRGBA color) { + this.fogColor = color; + if (material != null) { + material.setColor("FogColor", color); + } + } + + public float getFogDensity() { return fogDensity; } + public float getFogDistance() { return fogDistance; } + public ColorRGBA getFogColor() { return fogColor; } + + // ── Intern ──────────────────────────────────────────────────────────────── + + private void syncFrustum() { + if (camera == null || material == null) { + return; + } + material.setVector2("FrustumNearFar", + new Vector2f(camera.getFrustumNear(), camera.getFrustumFar())); + } +} diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 2517f2a..31a41b9 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -33,11 +33,12 @@ import de.blight.common.model.GameCharacter; import de.blight.common.model.MainCharacter; import de.blight.game.BlightGame; import de.blight.game.animation.AnimationLibrary; +import de.blight.game.config.GraphicsSettings; import de.blight.game.config.KeyBindings; import de.blight.game.control.PlayerInputControl; import de.blight.game.control.ThirdPersonCamera; import com.jme3.post.FilterPostProcessor; -import com.jme3.post.filters.FogFilter; +import de.blight.game.post.BlightFogFilter; import com.jme3.water.WaterFilter; import de.blight.game.state.DynamicWaterFilter; import de.blight.game.state.WaterInteractionState; @@ -78,10 +79,12 @@ public class WorldScene extends BaseAppState { private BulletAppState bulletAppState; private MapData loadedMapData; private FilterPostProcessor sharedFPP; + private BlightFogFilter fogFilter; private TerrainChunkState terrainChunkState; private Material terrainMaterial; - private final KeyBindings keyBindings; + private final KeyBindings keyBindings; + private final GraphicsSettings graphicsSettings; private ThirdPersonCamera thirdPersonCam; private PlayerInputControl playerInput; private InventoryState inventoryState; @@ -101,8 +104,9 @@ public class WorldScene extends BaseAppState { private de.blight.game.state.DrownState drownState; private WaterInteractionState waterInteractionState; - public WorldScene(KeyBindings keyBindings) { - this.keyBindings = keyBindings; + public WorldScene(KeyBindings keyBindings, GraphicsSettings graphicsSettings) { + this.keyBindings = keyBindings; + this.graphicsSettings = graphicsSettings; } public InventoryState getInventoryState() { return inventoryState; } @@ -246,7 +250,9 @@ public class WorldScene extends BaseAppState { BlightGame.status("Lade Welt-Objekte..."); app.getStateManager().attach(new RiverState()); - app.getStateManager().attach(new WorldObjectsState()); + WorldObjectsState worldObjects = new WorldObjectsState(); + worldObjects.setLodFactor(graphicsSettings.viewDistance.fogFactor); + app.getStateManager().attach(worldObjects); app.getStateManager().attach(new WorldLightState(sharedFPP)); app.getStateManager().attach(new StoneWorldState()); @@ -466,6 +472,13 @@ public class WorldScene extends BaseAppState { } } + public void toggleDebugFog() { + if (fogFilter == null) return; + boolean nowEnabled = !fogFilter.isEnabled(); + fogFilter.setEnabled(nowEnabled); + log.info("[Debug] Nebel {}", nowEnabled ? "AN" : "AUS"); + } + @Override protected void cleanup(Application app) { } @Override protected void onDisable() {} @@ -712,13 +725,14 @@ public class WorldScene extends BaseAppState { WeatherState weather = new WeatherState(); weather.setWaterFilter(waterFilter); - FogFilter fogFilter = new FogFilter(); + fogFilter = new BlightFogFilter(); fogFilter.setFogColor(new ColorRGBA(0.75f, 0.80f, 0.88f, 1f)); - fogFilter.setFogDensity(0.0f); + fogFilter.setFogDensity(0.40f); fogFilter.setFogDistance(600f); fpp.addFilter(fogFilter); weather.setFogFilter(fogFilter); + weather.setViewDistanceFactor(graphicsSettings.viewDistance.fogFactor); weather.setSkyControl(dayNight.getSkyControl()); app.getStateManager().attach(weather); } catch (Exception e) { @@ -775,6 +789,9 @@ public class WorldScene extends BaseAppState { terrainMaterial = buildTerrainMaterial(loadedMapData); terrainChunkState = new TerrainChunkState(bulletAppState, terrainMaterial, loadedMapData); + terrainChunkState.setLodRanges( + graphicsSettings.viewDistance.lod0Range, + graphicsSettings.viewDistance.lod1Range); // Höhen vorab laden, damit getHeightAt() bereits hier (vor initialize()) korrekte Werte liefert terrainChunkState.loadChunkHeights(); app.getStateManager().attach(terrainChunkState); diff --git a/blight-game/src/main/java/de/blight/game/state/CompassHudState.java b/blight-game/src/main/java/de/blight/game/state/CompassHudState.java index f68c2c9..81a861e 100644 --- a/blight-game/src/main/java/de/blight/game/state/CompassHudState.java +++ b/blight-game/src/main/java/de/blight/game/state/CompassHudState.java @@ -10,48 +10,59 @@ import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.VertexBuffer; +import com.jme3.util.BufferUtils; /** - * Kompass unten links. Zeigt N/O/S/W-Labels, die um das Kompasszentrum - * rotieren wenn sich die Kamera dreht. Eine feste Markierung oben - * zeigt immer die aktuelle Blickrichtung an. + * Kompass unten links – runde rotierende Rose, identisch mit dem Editor-Kompass. + * + * Die Rose dreht sich mit dem Kamera-Yaw; die Buchstaben konter-rotieren, + * damit sie immer aufrecht bleiben. Ein festes goldenes Dreieck oben zeigt + * die aktuelle Blickrichtung an. */ public class CompassHudState extends BaseAppState { // ── Layout ──────────────────────────────────────────────────────────────── - private static final float SIZE = 76f; // Kompass-Durchmesser (Pixel) - private static final float RADIUS = 26f; // Label-Radius vom Mittelpunkt - private static final float MARGIN = 16f; // Abstand zum Bildschirmrand - private static final float LABEL_Z = 4f; - private static final float DOT_SZ = 6f; // Vorwärts-Marker + private static final float SIZE = 100f; // Canvas-Größe (px) + private static final float RADIUS = 47f; // Kreis-Außenradius + private static final float LABEL_R = 30f; // Beschriftungsradius vom Mittelpunkt + private static final float MARGIN = 16f; // Abstand zum Bildschirmrand - private static final ColorRGBA COL_BG = new ColorRGBA(0.06f, 0.06f, 0.10f, 0.88f); - private static final ColorRGBA COL_BORDER = new ColorRGBA(0.30f, 0.30f, 0.44f, 1.00f); - private static final ColorRGBA COL_N = new ColorRGBA(0.90f, 0.20f, 0.20f, 1.00f); - private static final ColorRGBA COL_CARD = new ColorRGBA(0.75f, 0.75f, 0.78f, 1.00f); - private static final ColorRGBA COL_MARKER = new ColorRGBA(1.00f, 0.90f, 0.20f, 1.00f); + // ── Farben (matching EditorApp.drawCompass) ─────────────────────────────── + private static final ColorRGBA COL_BG = new ColorRGBA(0.047f, 0.047f, 0.094f, 0.82f); + private static final ColorRGBA COL_BORDER = new ColorRGBA(0.392f, 0.392f, 0.627f, 0.85f); + private static final ColorRGBA COL_TICK_MJ = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.65f); + private static final ColorRGBA COL_TICK_MN = new ColorRGBA(0.627f, 0.627f, 0.784f, 0.45f); + private static final ColorRGBA COL_N = new ColorRGBA(1.00f, 0.314f, 0.314f, 1.00f); + private static final ColorRGBA COL_CARD = new ColorRGBA(0.824f, 0.824f, 0.902f, 1.00f); + private static final ColorRGBA COL_MARKER = new ColorRGBA(1.00f, 0.863f, 0.235f, 0.95f); + private static final ColorRGBA COL_CENTER = new ColorRGBA(0.784f, 0.784f, 0.941f, 0.85f); - // ── Himmelsrichtungen: Name, Winkel-Offset von Nord (Rad) ───────────────── - private static final String[] LABELS = {"N", "O", "S", "W"}; - private static final float[] OFFSETS = {0f, FastMath.HALF_PI, FastMath.PI, -FastMath.HALF_PI}; + // Kompasswinkel (Grad von Nord CW): N=0, O=90, S=180, W=270 + private static final String[] LABELS = {"N", "O", "S", "W"}; + private static final float[] LABEL_DEG = {0f, 90f, 180f, 270f}; - private SimpleApplication app; - private Camera cam; - private AssetManager assets; - private BitmapFont font; - private Node compassNode; - private float cx, cy; // Mittelpunkt (Bildschirmkoordinaten) + private SimpleApplication app; + private Camera cam; + private AssetManager assets; + private BitmapFont font; - private final BitmapText[] dirLabels = new BitmapText[4]; + private Node compassNode; + private Node roseNode; + + private final Node[] labelNodes = new Node[4]; + private final Quaternion roseRot = new Quaternion(); + private final Quaternion labelRot = new Quaternion(); // ── Lifecycle ───────────────────────────────────────────────────────────── + @Override protected void initialize(Application application) { app = (SimpleApplication) application; @@ -70,6 +81,7 @@ public class CompassHudState extends BaseAppState { if (compassNode != null) { app.getGuiNode().detachChild(compassNode); compassNode = null; + roseNode = null; } } @@ -77,80 +89,173 @@ public class CompassHudState extends BaseAppState { protected void cleanup(Application application) {} // ── Update ──────────────────────────────────────────────────────────────── + @Override public void update(float tpf) { if (compassNode == null) { return; } Vector3f dir = cam.getDirection(); - // Yaw vom Weltursprung: 0 = Blick nach +Z (Nord), π/2 = Osten (+X) + // yaw=0 = Blick nach +Z (Nord); positiv = Osten float yaw = FastMath.atan2(dir.x, dir.z); - for (int i = 0; i < 4; i++) { - // Winkel des Labels im Kompass-Raum (von Oben CW = von +Y Achse) - float a = -yaw + OFFSETS[i]; - float lx = cx + RADIUS * FastMath.sin(a); - float ly = cy + RADIUS * FastMath.cos(a); - BitmapText lbl = dirLabels[i]; - lbl.setLocalTranslation(lx - lbl.getLineWidth() * 0.5f, ly + lbl.getLineHeight() * 0.4f, LABEL_Z); + // Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt + roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z); + roseNode.setLocalRotation(roseRot); + + // Buchstaben konter-rotieren → bleiben immer aufrecht + labelRot.fromAngleAxis(yaw, Vector3f.UNIT_Z); + for (Node ln : labelNodes) { + ln.setLocalRotation(labelRot); } } - // ── Aufbau ─────────────────────────────────────────────────────────────── - private void buildCompass() { - float sw = cam.getWidth(); - float sh = cam.getHeight(); + // ── Aufbau ──────────────────────────────────────────────────────────────── - // Kompass unten links - float ox = MARGIN; - // Direkt über der Hotbar: Hotbar.MARGIN_BOT + SLOT_SIZE + Gap + private void buildCompass() { float oy = HotbarState.MARGIN_BOT + HotbarState.SLOT_SIZE + 8f; - cx = ox + SIZE / 2f; - cy = oy + SIZE / 2f; + float cx = MARGIN + SIZE / 2f; + float cy = oy + SIZE / 2f; compassNode = new Node("compass"); + compassNode.setLocalTranslation(cx, cy, 0f); - // Rahmen - compassNode.attachChild(makeQuad(ox, oy, SIZE, SIZE, COL_BORDER, 1f)); - // Hintergrund - compassNode.attachChild(makeQuad(ox + 1, oy + 1, SIZE - 2, SIZE - 2, COL_BG, 2f)); + // Rahmen (etwas größerer Kreis) + Hintergrund + compassNode.attachChild(makeCircle(RADIUS + 1.5f, 64, COL_BORDER, 1f)); + compassNode.attachChild(makeCircle(RADIUS, 64, COL_BG, 2f)); - // Vorwärts-Marker: kleine goldene Raute oben in der Mitte - compassNode.attachChild(makeQuad(cx - DOT_SZ * 0.5f, cy + RADIUS - DOT_SZ * 0.5f, - DOT_SZ, DOT_SZ, COL_MARKER, 3f)); + // Rotierende Rose + roseNode = new Node("rose"); + compassNode.attachChild(roseNode); - // Himmelsrichtungs-Labels (Positionen werden in update() gesetzt) - for (int i = 0; i < 4; i++) { - ColorRGBA col = i == 0 ? COL_N : COL_CARD; - BitmapText lbl = txt(LABELS[i], 13, col); - lbl.setLocalTranslation(cx, cy, LABEL_Z); - dirLabels[i] = lbl; - compassNode.attachChild(lbl); + // 8 Tick-Striche (major = Hauptrichtungen, minor = Zwischenrichtungen) + for (int i = 0; i < 8; i++) { + float deg = i * 45f; + boolean maj = (i % 2 == 0); + float innerR = RADIUS - (maj ? 9f : 5f); + roseNode.attachChild(makeTickQuad(deg, innerR, maj ? COL_TICK_MJ : COL_TICK_MN, 3f)); } + // Himmelsrichtungs-Beschriftungen + for (int i = 0; i < 4; i++) { + float ca = LABEL_DEG[i] * FastMath.DEG_TO_RAD; + float lx = FastMath.sin(ca) * LABEL_R; + float ly = FastMath.cos(ca) * LABEL_R; + + BitmapText lbl = makeBitmapText(LABELS[i], i == 0 ? 14 : 12, i == 0 ? COL_N : COL_CARD); + // Buchstabe am Label-Ursprung zentrieren + lbl.setLocalTranslation(-lbl.getLineWidth() * 0.5f, lbl.getLineHeight() * 0.4f, 0f); + + Node ln = new Node("lbl_" + LABELS[i]); + ln.setLocalTranslation(lx, ly, 4f); + ln.attachChild(lbl); + labelNodes[i] = ln; + roseNode.attachChild(ln); + } + + // Fixer Richtungs-Zeiger: gelbes Dreieck oben (nicht in roseNode) + compassNode.attachChild(makeTriangle(COL_MARKER, 5f)); + + // Mittelpunkt-Punkt + compassNode.attachChild(makeCircle(2.5f, 16, COL_CENTER, 6f)); + app.getGuiNode().attachChild(compassNode); } - // ── Hilfsmethoden ──────────────────────────────────────────────────────── - private Geometry makeQuad(float x, float y, float w, float h, ColorRGBA col, float z) { - Geometry g = new Geometry("q", new Quad(w, h)); + // ── Geometry-Helfer ─────────────────────────────────────────────────────── + + /** Gefüllter Kreis (Triangle-Fan) zentriert bei (0,0). */ + private Geometry makeCircle(float r, int segs, ColorRGBA col, float z) { + float[] verts = new float[(segs + 2) * 3]; + // Mittelpunkt + verts[0] = 0f; verts[1] = 0f; verts[2] = 0f; + for (int i = 0; i <= segs; i++) { + float a = FastMath.TWO_PI * i / segs; + int base = (i + 1) * 3; + verts[base] = FastMath.cos(a) * r; + verts[base + 1] = FastMath.sin(a) * r; + verts[base + 2] = 0f; + } + int[] idx = new int[segs * 3]; + for (int i = 0; i < segs; i++) { + idx[i * 3] = 0; + idx[i * 3 + 1] = i + 1; + idx[i * 3 + 2] = i + 2; + } + Geometry g = new Geometry("circle", buildMesh(verts, idx)); + g.setMaterial(makeMat(col)); + g.setLocalTranslation(0f, 0f, z); + g.setQueueBucket(col.a < 1f ? RenderQueue.Bucket.Transparent : RenderQueue.Bucket.Gui); + return g; + } + + /** + * Tick-Strich als dünnes Quad entlang der radialen Richtung. + * ca_deg: Kompasswinkel (0=Nord, 90=Ost, CW). + */ + private Geometry makeTickQuad(float ca_deg, float innerR, ColorRGBA col, float z) { + float ca = ca_deg * FastMath.DEG_TO_RAD; + float sx = FastMath.sin(ca); // Richtung entlang des Radius + float cy_ = FastMath.cos(ca); + float px = -cy_ * 0.75f; // Halbe Breite (senkrecht zur Radiale) + float py = sx * 0.75f; + + float[] v = { + sx * innerR + px, cy_ * innerR + py, z, // v0 links innen + sx * innerR - px, cy_ * innerR - py, z, // v1 rechts innen + sx * RADIUS + px, cy_ * RADIUS + py, z, // v2 links außen + sx * RADIUS - px, cy_ * RADIUS - py, z // v3 rechts außen + }; + int[] idx = {0, 1, 2, 1, 3, 2}; + + Geometry g = new Geometry("tick", buildMesh(v, idx)); + g.setMaterial(makeMat(col)); + g.setQueueBucket(RenderQueue.Bucket.Transparent); + return g; + } + + /** + * Festes gelbes Dreieck – zeigt immer nach oben (Blickrichtung). + * Spitze bei (0, RADIUS−5), Basis bei (±5, RADIUS−16). + */ + private Geometry makeTriangle(ColorRGBA col, float z) { + float tipY = RADIUS - 5f; + float baseY = RADIUS - 16f; + float[] v = { + 0f, tipY, 0f, // Spitze + -5f, baseY, 0f, // Basis links + 5f, baseY, 0f // Basis rechts + }; + // CCW von +Z-Seite: (0,high)→(-5,low)→(5,low) ✓ + int[] idx = {0, 1, 2}; + Geometry g = new Geometry("triangle", buildMesh(v, idx)); + g.setMaterial(makeMat(col)); + g.setLocalTranslation(0f, 0f, z); + g.setQueueBucket(RenderQueue.Bucket.Transparent); + return g; + } + + private static Mesh buildMesh(float[] verts, int[] idx) { + Mesh m = new Mesh(); + m.setBuffer(VertexBuffer.Type.Position, 3, BufferUtils.createFloatBuffer(verts)); + m.setBuffer(VertexBuffer.Type.Index, 3, BufferUtils.createIntBuffer(idx)); + m.updateBound(); + return m; + } + + private Material makeMat(ColorRGBA col) { Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md"); m.setColor("Color", col.clone()); if (col.a < 1f) { m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); - g.setQueueBucket(RenderQueue.Bucket.Transparent); - } else { - g.setQueueBucket(RenderQueue.Bucket.Gui); } - g.setMaterial(m); - g.setLocalTranslation(x, y, z); - return g; + return m; } - private BitmapText txt(String s, int size, ColorRGBA col) { + private BitmapText makeBitmapText(String text, int size, ColorRGBA col) { BitmapText t = new BitmapText(font); t.setSize(size); - t.setColor(col); - t.setText(s); + t.setColor(col.clone()); + t.setText(text); t.setQueueBucket(RenderQueue.Bucket.Gui); return t; } 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 26da3d3..c14e772 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 @@ -1,12 +1,15 @@ package de.blight.game.state; import com.jme3.asset.AssetManager; +import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Wechselt zwischen LOD-Stufen und blendet Objekte ab einer Sichtweite aus. @@ -26,6 +29,8 @@ import com.jme3.scene.control.AbstractControl; */ public class ModelLodControl extends AbstractControl { + private static final Logger log = LoggerFactory.getLogger(ModelLodControl.class); + private final Camera cam; private final float lod1DistSq; private final float lod2DistSq; @@ -93,9 +98,19 @@ public class ModelLodControl extends AbstractControl { @Override protected void controlUpdate(float tpf) { - float dx = cam.getLocation().x - spatial.getWorldTranslation().x; - float dy = cam.getLocation().y - spatial.getWorldTranslation().y; - float dz = cam.getLocation().z - spatial.getWorldTranslation().z; + // getWorldTranslation() liefert auf dem ersten Frame (0,0,0) solange updateGeometricState() + // noch nicht gelaufen ist. Deshalb: wenn Translation noch (0,0,0) ist und der spatial + // eine LocalTranslation hat, die verschieden ist, überspringen wir diesen Frame. + Vector3f wt = spatial.getWorldTranslation(); + Vector3f lt = spatial.getLocalTranslation(); + if (wt.x == 0f && wt.y == 0f && wt.z == 0f + && (lt.x != 0f || lt.y != 0f || lt.z != 0f)) { + return; + } + + float dx = cam.getLocation().x - wt.x; + float dy = cam.getLocation().y - wt.y; + float dz = cam.getLocation().z - wt.z; float distSq = dx*dx + dy*dy + dz*dz; boolean embedded = embLod0 != null; @@ -114,6 +129,9 @@ public class ModelLodControl extends AbstractControl { } if (targetSlot == currentSlot) return; + log.debug("[LOD] {} Slot {} → {} (dist={}m)", + spatial.getName(), currentSlot, targetSlot, + String.format("%.1f", Math.sqrt(distSq))); currentSlot = targetSlot; if (targetSlot == -1) { diff --git a/blight-game/src/main/java/de/blight/game/state/TerrainChunkState.java b/blight-game/src/main/java/de/blight/game/state/TerrainChunkState.java index d8828cf..6ae5992 100644 --- a/blight-game/src/main/java/de/blight/game/state/TerrainChunkState.java +++ b/blight-game/src/main/java/de/blight/game/state/TerrainChunkState.java @@ -72,9 +72,19 @@ public class TerrainChunkState extends BaseAppState { private final RigidBodyControl[] physics = new RigidBodyControl[TOTAL]; private final List listeners = new ArrayList<>(); + private int lod0Range = ChunkTerrainIO.LOD0_RANGE; + private int lod1Range = ChunkTerrainIO.LOD1_RANGE; + private int lastPlayerCx = Integer.MIN_VALUE; private int lastPlayerCz = Integer.MIN_VALUE; + public void setLodRanges(int lod0, int lod1) { + this.lod0Range = lod0; + this.lod1Range = lod1; + lastPlayerCx = Integer.MIN_VALUE; + lastPlayerCz = Integer.MIN_VALUE; + } + /** Beim nächsten update() diese Position statt Kamera-Position verwenden (einmalig). */ private float spawnHintX = Float.NaN; private float spawnHintZ = Float.NaN; @@ -184,7 +194,7 @@ public class TerrainChunkState extends BaseAppState { for (int cx = 0; cx < N; cx++) { int ci = ChunkTerrainIO.chunkIndex(cx, cz); int dist = ChunkTerrainIO.chebyshev(cx, cz, pcx, pcz); - targetLod[ci] = ChunkTerrainIO.lodForDistance(dist); + targetLod[ci] = dist <= lod0Range ? 0 : dist <= lod1Range ? 1 : 2; dirty[ci] = targetLod[ci] != chunkLod[ci]; } } diff --git a/blight-game/src/main/java/de/blight/game/state/WeatherState.java b/blight-game/src/main/java/de/blight/game/state/WeatherState.java index b1812ef..8256b44 100644 --- a/blight-game/src/main/java/de/blight/game/state/WeatherState.java +++ b/blight-game/src/main/java/de/blight/game/state/WeatherState.java @@ -6,7 +6,7 @@ import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; -import com.jme3.post.filters.FogFilter; +import de.blight.game.post.BlightFogFilter; import com.jme3.water.WaterFilter; import jme3utilities.sky.SkyControl; import org.slf4j.Logger; @@ -16,37 +16,40 @@ public class WeatherState extends BaseAppState { private static final Logger log = LoggerFactory.getLogger(WeatherState.class); - public enum Weather { SUNNY, CLOUDY, OVERCAST, STORM } + public enum Weather { SUNNY, CLOUDY, OVERCAST, STORM, FOG } - // ── Per-weather targets (Reihenfolge: SUNNY, CLOUDY, OVERCAST, STORM) ───── + // ── Per-weather targets (Reihenfolge: SUNNY, CLOUDY, OVERCAST, STORM, FOG) ─ - private static final float[] FOG_DENSITY = { 0.00f, 0.30f, 0.65f, 0.90f }; - private static final float[] FOG_DISTANCE = { 600f, 350f, 140f, 50f }; - private static final float[] WIND_SPEED = { 4f, 14f, 26f, 55f }; - private static final float[] WAVE_SPEED = { 0.5f, 1.0f, 1.5f, 3.2f }; - private static final float[] WAVE_AMP = { 0.3f, 0.5f, 0.8f, 1.8f }; - private static final float[] WAVE_SCALE = { 0.008f, 0.007f, 0.006f, 0.005f}; - private static final float[] WATER_TRANS = { 0.15f, 0.10f, 0.07f, 0.02f }; - private static final float[] FOAM_INTENSITY= { 0.0f, 0.20f, 0.45f, 0.90f }; - private static final float[] CLOUD_OPACITY = { 0.0f, 0.40f, 0.80f, 1.00f }; + private static final float[] FOG_DENSITY = { 0.40f, 0.55f, 0.75f, 0.90f, 0.88f }; + private static final float[] FOG_DISTANCE = { 600f, 350f, 140f, 50f, 80f }; + private static final float[] WIND_SPEED = { 4f, 14f, 26f, 55f, 3f }; + private static final float[] WAVE_SPEED = { 0.5f, 1.0f, 1.5f, 3.2f, 0.4f }; + private static final float[] WAVE_AMP = { 0.3f, 0.5f, 0.8f, 1.8f, 0.2f }; + private static final float[] WAVE_SCALE = { 0.008f, 0.007f, 0.006f, 0.005f, 0.009f}; + private static final float[] WATER_TRANS = { 0.15f, 0.10f, 0.07f, 0.02f, 0.12f }; + private static final float[] FOAM_INTENSITY= { 0.0f, 0.20f, 0.45f, 0.90f, 0.0f }; + private static final float[] CLOUD_OPACITY = { 0.0f, 0.40f, 0.80f, 1.00f, 0.95f }; private static final ColorRGBA[] FOG_COLOR = { new ColorRGBA(0.75f, 0.80f, 0.88f, 1f), new ColorRGBA(0.62f, 0.65f, 0.70f, 1f), new ColorRGBA(0.42f, 0.43f, 0.46f, 1f), new ColorRGBA(0.18f, 0.19f, 0.21f, 1f), + new ColorRGBA(0.70f, 0.72f, 0.75f, 1f), }; private static final ColorRGBA[] WATER_COLOR = { new ColorRGBA(0.05f, 0.25f, 0.55f, 1f), new ColorRGBA(0.04f, 0.18f, 0.42f, 1f), new ColorRGBA(0.03f, 0.12f, 0.28f, 1f), new ColorRGBA(0.02f, 0.06f, 0.14f, 1f), + new ColorRGBA(0.04f, 0.20f, 0.45f, 1f), }; private static final ColorRGBA[] DEEP_WATER_COLOR = { new ColorRGBA(0.02f, 0.12f, 0.30f, 1f), new ColorRGBA(0.01f, 0.08f, 0.20f, 1f), new ColorRGBA(0.01f, 0.04f, 0.12f, 1f), new ColorRGBA(0.00f, 0.02f, 0.06f, 1f), + new ColorRGBA(0.01f, 0.09f, 0.23f, 1f), }; // ── State ──────────────────────────────────────────────────────────────── @@ -54,7 +57,7 @@ public class WeatherState extends BaseAppState { private Weather active = Weather.SUNNY; private float changeTimer = 120f; - private float fogDensity = 0f; + private float fogDensity = 0.40f; // Startwert = SUNNY-Ziel, kein langsames Fade-in private float fogDistance = 600f; private float windSpeed = 4f; private float waveSpeed = 0.5f; @@ -69,13 +72,19 @@ public class WeatherState extends BaseAppState { private final ColorRGBA waterColor = new ColorRGBA(0.05f, 0.25f, 0.55f, 1f); private final ColorRGBA deepWaterColor= new ColorRGBA(0.02f, 0.12f, 0.30f, 1f); + // ── Sichtweiten-Faktor (aus Grafikeinstellungen) ────────────────────────── + + private float viewDistanceFactor = 1.0f; + + public void setViewDistanceFactor(float f) { this.viewDistanceFactor = f; } + // ── Externe Referenzen ──────────────────────────────────────────────────── - private FogFilter fogFilter; - private WaterFilter waterFilter; - private SkyControl skyControl; + private BlightFogFilter fogFilter; + private WaterFilter waterFilter; + private SkyControl skyControl; - public void setFogFilter(FogFilter f) { this.fogFilter = f; } + public void setFogFilter(BlightFogFilter f) { this.fogFilter = f; } public void setWaterFilter(WaterFilter f) { this.waterFilter = f; } public void setSkyControl(SkyControl sc) { this.skyControl = sc; } @@ -114,8 +123,8 @@ public class WeatherState extends BaseAppState { int i = active.ordinal(); - fogDensity = approach(fogDensity, FOG_DENSITY[i], tpf * 0.025f); - fogDistance = approach(fogDistance, FOG_DISTANCE[i], tpf * 0.025f); + fogDensity = approach(fogDensity, FOG_DENSITY[i], tpf * 0.025f); + fogDistance = approach(fogDistance, FOG_DISTANCE[i] * viewDistanceFactor, tpf * 0.025f); windSpeed = approach(windSpeed, WIND_SPEED[i], tpf * 0.040f); waveSpeed = approach(waveSpeed, WAVE_SPEED[i], tpf * 0.030f); waveAmp = approach(waveAmp, WAVE_AMP[i], tpf * 0.025f); @@ -156,10 +165,11 @@ public class WeatherState extends BaseAppState { private void pickNextWeather() { float r = FastMath.nextRandomFloat(); - Weather next = r < 0.40f ? Weather.SUNNY - : r < 0.70f ? Weather.CLOUDY - : r < 0.88f ? Weather.OVERCAST - : Weather.STORM; + Weather next = r < 0.38f ? Weather.SUNNY + : r < 0.65f ? Weather.CLOUDY + : r < 0.82f ? Weather.OVERCAST + : r < 0.92f ? Weather.STORM + : Weather.FOG; windAngleTgt = FastMath.nextRandomFloat() * FastMath.TWO_PI; if (next != active) { active = next; 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 29ce903..dff348d 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 @@ -14,6 +14,7 @@ import com.jme3.material.RenderState; import com.jme3.math.*; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.*; +import com.jme3.scene.control.BillboardControl; import com.jme3.scene.shape.*; import com.jme3.texture.Texture; import de.blight.common.PlacedModel; @@ -38,6 +39,9 @@ public class WorldObjectsState extends BaseAppState { private final List windMaterials = new ArrayList<>(); private final List pbrMaterials = new ArrayList<>(); private float debugTimer = 0f; + private float lodFactor = 1.0f; + + public void setLodFactor(float f) { this.lodFactor = f; } /** RigidBodyControl pro Interactable-ID, damit Kollision während Animationen deaktiviert werden kann. */ private final Map interactableRbcs = new HashMap<>(); @@ -192,22 +196,41 @@ public class WorldObjectsState extends BaseAppState { // LOD / Distance-Culling if (m.cullDistance() > 0f) { - // Embedded-LOD: j3o enthält bereits lod0/lod1/lod2 Child-Nodes (TreeGenerator). - // TreeLodControl im j3o ist editor-only und wird im Spiel nicht deserialisiert — - // wir übernehmen die LOD-Steuerung selbst, direkt am j3o-Root-Node. if (spatial instanceof Node treeNode) { Spatial lod0 = treeNode.getChild("lod0"); Spatial lod1 = treeNode.getChild("lod1"); Spatial lod2 = treeNode.getChild("lod2"); + + // Fall A: j3o hat explizite lod0/lod1/lod2 Child-Nodes. if (lod0 != null) { lod0.setCullHint(Spatial.CullHint.Inherit); if (lod1 != null) lod1.setCullHint(Spatial.CullHint.Always); if (lod2 != null) lod2.setCullHint(Spatial.CullHint.Always); treeNode.addControl(new ModelLodControl( app.getCamera(), lod0, lod1, lod2, - m.lod1Distance(), m.lod2Distance(), m.cullDistance())); + m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor, m.cullDistance() * lodFactor)); return treeNode; } + + // Fall B: j3o hat nur "lod2" als Kind — Root ist lod0, lod2 ist das vereinfachte Mesh. + // lod2 aus dem Root ausgliedern, damit beide unabhängig ein/ausgeblendet werden können. + if (lod2 != null) { + treeNode.detachChild(lod2); + Node lodRoot = new Node("lodRoot_" + path); + lodRoot.attachChild(treeNode); // index 0 = lod0 (volle Qualität) + lodRoot.attachChild(lod2); // index 1 = lod2 (Impostor-Karte) + treeNode.setCullHint(Spatial.CullHint.Inherit); + lod2.setCullHint(Spatial.CullHint.Always); + // Billboard: Impostor-Karte dreht sich immer zur Kamera, + // unabhängig von der Baum-Rotation im BLO. + BillboardControl bc = new BillboardControl(); + bc.setAlignment(BillboardControl.Alignment.Camera); + lod2.addControl(bc); + lodRoot.addControl(new ModelLodControl( + app.getCamera(), treeNode, null, lod2, + m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor, m.cullDistance() * lodFactor)); + return lodRoot; + } } // Fallback: externe LOD-Dateien (lod1Path / lod2Path) @@ -216,7 +239,7 @@ public class WorldObjectsState extends BaseAppState { ModelLodControl ctrl = new ModelLodControl( assets, app.getCamera(), m.lod1Path(), m.lod2Path(), - m.lod1Distance(), m.lod2Distance(), m.cullDistance()); + m.lod1Distance() * lodFactor, m.lod2Distance() * lodFactor, m.cullDistance() * lodFactor); ctrl.setLodLoadCallback(lod -> { convertUnshadedToPbr(lod); collectSceneLitMaterials(lod); diff --git a/blight-lang/src/main/resources/lang/messages_de.properties b/blight-lang/src/main/resources/lang/messages_de.properties index 93d4ebe..b7923f1 100644 --- a/blight-lang/src/main/resources/lang/messages_de.properties +++ b/blight-lang/src/main/resources/lang/messages_de.properties @@ -48,8 +48,12 @@ menu.graphics.row.resolution=Auflösung menu.graphics.row.fullscreen=Vollbild menu.graphics.row.vsync=VSync menu.graphics.row.aa=Kantenglättung +menu.graphics.row.viewdist=Sichtweite menu.graphics.val.on=An menu.graphics.val.off=Aus +menu.graphics.val.viewdist.short=Kurz +menu.graphics.val.viewdist.normal=Normal +menu.graphics.val.viewdist.far=Weit menu.graphics.btn.apply=Übernehmen menu.graphics.btn.cancel=Abbrechen diff --git a/blight-lang/src/main/resources/lang/messages_en.properties b/blight-lang/src/main/resources/lang/messages_en.properties index 6b3d61e..7fd4839 100644 --- a/blight-lang/src/main/resources/lang/messages_en.properties +++ b/blight-lang/src/main/resources/lang/messages_en.properties @@ -51,8 +51,12 @@ menu.graphics.row.resolution=Resolution menu.graphics.row.fullscreen=Fullscreen menu.graphics.row.vsync=VSync menu.graphics.row.aa=Anti-aliasing +menu.graphics.row.viewdist=View Distance menu.graphics.val.on=On menu.graphics.val.off=Off +menu.graphics.val.viewdist.short=Short +menu.graphics.val.viewdist.normal=Normal +menu.graphics.val.viewdist.far=Far menu.graphics.btn.apply=Apply menu.graphics.btn.cancel=Cancel diff --git a/blight-map/src/main/map/blight_grass_vertex.blgv b/blight-map/src/main/map/blight_grass_vertex.blgv index ec9d247..470af62 100644 Binary files a/blight-map/src/main/map/blight_grass_vertex.blgv and b/blight-map/src/main/map/blight_grass_vertex.blgv differ