Sichtweite und Nebel hinzugefügt
This commit is contained in:
@@ -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) -> {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -72,9 +72,19 @@ public class TerrainChunkState extends BaseAppState {
|
||||
private final RigidBodyControl[] physics = new RigidBodyControl[TOTAL];
|
||||
private final List<ChunkListener> 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];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Material> windMaterials = new ArrayList<>();
|
||||
private final List<Material> 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<String, RigidBodyControl> 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);
|
||||
|
||||
Reference in New Issue
Block a user