Weiter an den Grafik Effekten gearbeitet
|
After Width: | Height: | Size: 272 KiB |
|
After Width: | Height: | Size: 324 KiB |
|
After Width: | Height: | Size: 306 KiB |
|
After Width: | Height: | Size: 303 KiB |
|
After Width: | Height: | Size: 209 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
After Width: | Height: | Size: 188 KiB |
|
After Width: | Height: | Size: 336 KiB |
@@ -31,7 +31,8 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
private static final int ROW_VIEWDIST = 4;
|
private static final int ROW_VIEWDIST = 4;
|
||||||
private static final int ROW_SHADOW = 5;
|
private static final int ROW_SHADOW = 5;
|
||||||
private static final int ROW_POSTFX = 6;
|
private static final int ROW_POSTFX = 6;
|
||||||
private static final int ROW_COUNT = 7;
|
private static final int ROW_PARTICLES = 7;
|
||||||
|
private static final int ROW_COUNT = 8;
|
||||||
|
|
||||||
private final GraphicsSettings live;
|
private final GraphicsSettings live;
|
||||||
private GraphicsSettings edit;
|
private GraphicsSettings edit;
|
||||||
@@ -62,9 +63,10 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
edit.fullscreen = live.fullscreen;
|
edit.fullscreen = live.fullscreen;
|
||||||
edit.vsync = live.vsync;
|
edit.vsync = live.vsync;
|
||||||
edit.samples = live.samples;
|
edit.samples = live.samples;
|
||||||
edit.viewDistance = live.viewDistance;
|
edit.viewDistance = live.viewDistance;
|
||||||
edit.shadowQuality = live.shadowQuality;
|
edit.shadowQuality = live.shadowQuality;
|
||||||
edit.postFxQuality = live.postFxQuality;
|
edit.postFxQuality = live.postFxQuality;
|
||||||
|
edit.particleQuality = live.particleQuality;
|
||||||
|
|
||||||
resIdx = 0;
|
resIdx = 0;
|
||||||
for (int i = 0; i < resolutions.length; i++) {
|
for (int i = 0; i < resolutions.length; i++) {
|
||||||
@@ -89,7 +91,8 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
"menu.graphics.row.aa",
|
"menu.graphics.row.aa",
|
||||||
"menu.graphics.row.viewdist",
|
"menu.graphics.row.viewdist",
|
||||||
"menu.graphics.row.shadow",
|
"menu.graphics.row.shadow",
|
||||||
"menu.graphics.row.postfx"
|
"menu.graphics.row.postfx",
|
||||||
|
"menu.graphics.row.particles"
|
||||||
};
|
};
|
||||||
|
|
||||||
float lblX = CONT_X + 30f;
|
float lblX = CONT_X + 30f;
|
||||||
@@ -153,6 +156,11 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
case MEDIUM -> t("menu.graphics.val.shadow.medium");
|
case MEDIUM -> t("menu.graphics.val.shadow.medium");
|
||||||
case HIGH -> t("menu.graphics.val.shadow.high");
|
case HIGH -> t("menu.graphics.val.shadow.high");
|
||||||
};
|
};
|
||||||
|
case ROW_PARTICLES -> switch (edit.particleQuality) {
|
||||||
|
case OFF -> t("menu.graphics.val.off");
|
||||||
|
case MEDIUM -> t("menu.graphics.val.shadow.medium");
|
||||||
|
case HIGH -> t("menu.graphics.val.shadow.high");
|
||||||
|
};
|
||||||
default -> "";
|
default -> "";
|
||||||
};
|
};
|
||||||
BitmapText vt = valTexts[row];
|
BitmapText vt = valTexts[row];
|
||||||
@@ -190,29 +198,36 @@ public class GraphicsScreen extends MenuScreen {
|
|||||||
GraphicsSettings.PostFxQuality[] pfqs = GraphicsSettings.PostFxQuality.values();
|
GraphicsSettings.PostFxQuality[] pfqs = GraphicsSettings.PostFxQuality.values();
|
||||||
edit.postFxQuality = pfqs[(edit.postFxQuality.ordinal() + dir + pfqs.length) % pfqs.length];
|
edit.postFxQuality = pfqs[(edit.postFxQuality.ordinal() + dir + pfqs.length) % pfqs.length];
|
||||||
break;
|
break;
|
||||||
|
case ROW_PARTICLES:
|
||||||
|
GraphicsSettings.ParticleQuality[] pqs = GraphicsSettings.ParticleQuality.values();
|
||||||
|
edit.particleQuality = pqs[(edit.particleQuality.ordinal() + dir + pqs.length) % pqs.length];
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
refreshText(row);
|
refreshText(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyAndSave() {
|
private void applyAndSave() {
|
||||||
boolean shadowChanged = live.shadowQuality != edit.shadowQuality;
|
boolean shadowChanged = live.shadowQuality != edit.shadowQuality;
|
||||||
boolean postFxChanged = live.postFxQuality != edit.postFxQuality;
|
boolean postFxChanged = live.postFxQuality != edit.postFxQuality;
|
||||||
|
boolean particleChanged = live.particleQuality != edit.particleQuality;
|
||||||
boolean needsRestart = live.width != edit.width
|
boolean needsRestart = live.width != edit.width
|
||||||
|| live.height != edit.height
|
|| live.height != edit.height
|
||||||
|| live.fullscreen != edit.fullscreen
|
|| live.fullscreen != edit.fullscreen
|
||||||
|| live.vsync != edit.vsync
|
|| live.vsync != edit.vsync
|
||||||
|| live.samples != edit.samples
|
|| live.samples != edit.samples
|
||||||
|| shadowChanged
|
|| shadowChanged
|
||||||
|| postFxChanged;
|
|| postFxChanged
|
||||||
|
|| particleChanged;
|
||||||
|
|
||||||
live.width = edit.width;
|
live.width = edit.width;
|
||||||
live.height = edit.height;
|
live.height = edit.height;
|
||||||
live.fullscreen = edit.fullscreen;
|
live.fullscreen = edit.fullscreen;
|
||||||
live.vsync = edit.vsync;
|
live.vsync = edit.vsync;
|
||||||
live.samples = edit.samples;
|
live.samples = edit.samples;
|
||||||
live.viewDistance = edit.viewDistance;
|
live.viewDistance = edit.viewDistance;
|
||||||
live.shadowQuality = edit.shadowQuality;
|
live.shadowQuality = edit.shadowQuality;
|
||||||
live.postFxQuality = edit.postFxQuality;
|
live.postFxQuality = edit.postFxQuality;
|
||||||
|
live.particleQuality = edit.particleQuality;
|
||||||
GraphicsStore.save(live);
|
GraphicsStore.save(live);
|
||||||
|
|
||||||
WeatherState ws = getApplication().getStateManager().getState(WeatherState.class);
|
WeatherState ws = getApplication().getStateManager().getState(WeatherState.class);
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
package de.blight.game.config;
|
package de.blight.game.config;
|
||||||
|
|
||||||
public class GraphicsSettings {
|
public class GraphicsSettings {
|
||||||
public int width = 1280;
|
public int width = 1280;
|
||||||
public int height = 720;
|
public int height = 720;
|
||||||
public boolean fullscreen = false;
|
public boolean fullscreen = false;
|
||||||
public boolean vsync = false;
|
public boolean vsync = false;
|
||||||
public int samples = 4;
|
public int samples = 4;
|
||||||
public ViewDistance viewDistance = ViewDistance.NORMAL;
|
public ViewDistance viewDistance = ViewDistance.NORMAL;
|
||||||
public ShadowQuality shadowQuality = ShadowQuality.MEDIUM;
|
public ShadowQuality shadowQuality = ShadowQuality.MEDIUM;
|
||||||
/** Verstecktes Debug-Flag: erzwingt LOD2 (Impostor) für alle Objekte. Nur per Konsole setzbar. */
|
/** Verstecktes Debug-Flag: erzwingt LOD2 (Impostor) für alle Objekte. Nur per Konsole setzbar. */
|
||||||
public boolean debugLod2 = false;
|
public boolean debugLod2 = false;
|
||||||
public PostFxQuality postFxQuality = PostFxQuality.HIGH;
|
public PostFxQuality postFxQuality = PostFxQuality.HIGH;
|
||||||
|
public ParticleQuality particleQuality = ParticleQuality.HIGH;
|
||||||
|
|
||||||
public enum PostFxQuality {
|
public enum PostFxQuality {
|
||||||
// fxaa toneMap bloomI bloomExp godRays
|
// fxaa toneMap bloomI bloomExp godRays
|
||||||
@@ -68,6 +69,10 @@ public class GraphicsSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum ParticleQuality {
|
||||||
|
OFF, MEDIUM, HIGH
|
||||||
|
}
|
||||||
|
|
||||||
public enum ViewDistance {
|
public enum ViewDistance {
|
||||||
SHORT (1.00f, 1, 3),
|
SHORT (1.00f, 1, 3),
|
||||||
NORMAL(1.60f, 2, 5),
|
NORMAL(1.60f, 2, 5),
|
||||||
|
|||||||
@@ -813,6 +813,10 @@ public class WorldScene extends BaseAppState {
|
|||||||
weather.setDayTime(dayNight.getDayTime());
|
weather.setDayTime(dayNight.getDayTime());
|
||||||
app.getStateManager().attach(weather);
|
app.getStateManager().attach(weather);
|
||||||
app.getStateManager().attach(new de.blight.game.state.RainState());
|
app.getStateManager().attach(new de.blight.game.state.RainState());
|
||||||
|
if (graphicsSettings.particleQuality != GraphicsSettings.ParticleQuality.OFF) {
|
||||||
|
app.getStateManager().attach(
|
||||||
|
new de.blight.game.state.AmbientParticlesState(graphicsSettings));
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("[WorldScene] Post-Processing nicht verfügbar: {}", e.getMessage());
|
log.warn("[WorldScene] Post-Processing nicht verfügbar: {}", e.getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,386 @@
|
|||||||
|
package de.blight.game.state;
|
||||||
|
|
||||||
|
import com.jme3.app.Application;
|
||||||
|
import com.jme3.app.SimpleApplication;
|
||||||
|
import com.jme3.app.state.BaseAppState;
|
||||||
|
import com.jme3.asset.AssetManager;
|
||||||
|
import com.jme3.effect.ParticleEmitter;
|
||||||
|
import com.jme3.effect.ParticleMesh;
|
||||||
|
import com.jme3.effect.shapes.EmitterBoxShape;
|
||||||
|
import com.jme3.math.ColorRGBA;
|
||||||
|
import com.jme3.math.FastMath;
|
||||||
|
import com.jme3.math.Vector3f;
|
||||||
|
import com.jme3.scene.Node;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Texture2D;
|
||||||
|
import com.jme3.texture.image.ColorSpace;
|
||||||
|
import de.blight.common.PlacedModel;
|
||||||
|
import de.blight.common.PlacedModelIO;
|
||||||
|
import de.blight.game.config.GraphicsSettings;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ambient-Partikeleffekte:
|
||||||
|
* - Blätter (Kiefer/Eiche/Esche) bei Wind in Waldnähe
|
||||||
|
* - Staubwirbel bei starkem Wind im offenen Gelände
|
||||||
|
* - Regen-Bodensplitzer (ab ParticleQuality.MEDIUM)
|
||||||
|
*
|
||||||
|
* Aktiviert sich nur bei ParticleQuality != OFF.
|
||||||
|
*/
|
||||||
|
public class AmbientParticlesState extends BaseAppState {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(AmbientParticlesState.class);
|
||||||
|
|
||||||
|
// ── Konstanten ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private static final float SCAN_INTERVAL = 6f;
|
||||||
|
private static final float TREE_RADIUS = 40f;
|
||||||
|
private static final float TREE_DENSITY_MAX = 8f;
|
||||||
|
private static final float EMITTER_RADIUS = 35f;
|
||||||
|
private static final float SPLASH_RADIUS = 40f;
|
||||||
|
private static final float CHAR_HEIGHT = 1.7f; // Kamera-Auge über Boden
|
||||||
|
|
||||||
|
/** Ab dieser Windstärke beginnen Blätter zu fliegen. */
|
||||||
|
private static final float LEAF_MIN_WIND = 6f;
|
||||||
|
/** Ab dieser Windstärke beginnt Staubaufwirbelung. */
|
||||||
|
private static final float DUST_MIN_WIND = 20f;
|
||||||
|
|
||||||
|
// ── State ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private final GraphicsSettings settings;
|
||||||
|
|
||||||
|
private final List<Vector3f> treePositions = new ArrayList<>();
|
||||||
|
private float treeDensityFactor = 0f;
|
||||||
|
private float scanTimer = 0f;
|
||||||
|
|
||||||
|
private static final String[] LEAF_TEX_PATHS = {
|
||||||
|
"Textures/internal/leavesfilter/leaf1.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf2.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf3.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf4.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf5.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf6.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf7.png",
|
||||||
|
"Textures/internal/leavesfilter/leaf8.png",
|
||||||
|
};
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") private ParticleEmitter[] leafEmitters;
|
||||||
|
@SuppressWarnings("deprecation") private ParticleEmitter debrisEmitter;
|
||||||
|
@SuppressWarnings("deprecation") private ParticleEmitter splashEmitter;
|
||||||
|
|
||||||
|
private Node leafNode;
|
||||||
|
private Node debrisNode;
|
||||||
|
private Node splashNode;
|
||||||
|
|
||||||
|
private float leafIntensity = 0f;
|
||||||
|
private float debrisIntensity = 0f;
|
||||||
|
private float splashIntensity = 0f;
|
||||||
|
|
||||||
|
// ── Konstruktor ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
public AmbientParticlesState(GraphicsSettings settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
protected void initialize(Application app) {
|
||||||
|
// Baumpositionen einmalig laden; Palmen ausschließen
|
||||||
|
try {
|
||||||
|
for (PlacedModel m : PlacedModelIO.load()) {
|
||||||
|
String path = (m.meshFile() != null && !m.meshFile().isBlank())
|
||||||
|
? m.meshFile() : (m.modelPath() != null ? m.modelPath() : "");
|
||||||
|
if (path.contains("Models/trees/") && !path.contains("/palm/")) {
|
||||||
|
treePositions.add(new Vector3f(m.x(), m.y(), m.z()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.info("[AmbientParticles] {} Baumpositionen (ohne Palmen).", treePositions.size());
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("[AmbientParticles] Baumpositionen nicht ladbar: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean high = settings.particleQuality == GraphicsSettings.ParticleQuality.HIGH;
|
||||||
|
SimpleApplication sa = (SimpleApplication) app;
|
||||||
|
Node root = sa.getRootNode();
|
||||||
|
AssetManager assets = app.getAssetManager();
|
||||||
|
|
||||||
|
// ── Blatt-Emitter (1 pro Textur → zufällige Blattformen im Umlauf) ────
|
||||||
|
Texture2D[] leafTextures = loadLeafTextures(assets);
|
||||||
|
int leafTexCount = leafTextures.length;
|
||||||
|
int poolPerEmitter = Math.max(1, (high ? 300 : 150) / leafTexCount);
|
||||||
|
leafEmitters = new ParticleEmitter[leafTexCount];
|
||||||
|
leafNode = new Node("leafNode");
|
||||||
|
|
||||||
|
for (int li = 0; li < leafTexCount; li++) {
|
||||||
|
ParticleEmitter le = new ParticleEmitter("leaves_" + li,
|
||||||
|
ParticleMesh.Type.Triangle, poolPerEmitter);
|
||||||
|
le.setShape(new EmitterBoxShape(
|
||||||
|
new Vector3f(-EMITTER_RADIUS, -1f, -EMITTER_RADIUS),
|
||||||
|
new Vector3f( EMITTER_RADIUS, 4f, EMITTER_RADIUS)));
|
||||||
|
le.setGravity(0f, -0.3f, 0f);
|
||||||
|
le.setParticlesPerSec(0f);
|
||||||
|
le.setLowLife(4f);
|
||||||
|
le.setHighLife(9f);
|
||||||
|
le.setStartSize(0.16f);
|
||||||
|
le.setEndSize(0.15f);
|
||||||
|
le.setStartColor(new ColorRGBA(0.38f, 0.58f, 0.22f, 0.88f));
|
||||||
|
le.setEndColor( new ColorRGBA(0.50f, 0.35f, 0.10f, 0.00f));
|
||||||
|
le.setInitialVelocity(new Vector3f(0f, -0.3f, 0f));
|
||||||
|
le.setVelocityVariation(0.9f);
|
||||||
|
le.setRandomAngle(true);
|
||||||
|
le.setRotateSpeed(FastMath.PI * 1.5f);
|
||||||
|
com.jme3.material.Material lm =
|
||||||
|
new com.jme3.material.Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
lm.setTexture("Texture", leafTextures[li]);
|
||||||
|
lm.getAdditionalRenderState().setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
|
||||||
|
lm.getAdditionalRenderState().setDepthWrite(false);
|
||||||
|
le.setMaterial(lm);
|
||||||
|
leafEmitters[li] = le;
|
||||||
|
leafNode.attachChild(le);
|
||||||
|
}
|
||||||
|
root.attachChild(leafNode);
|
||||||
|
|
||||||
|
// ── Staub-Emitter (offenes Gelände) ───────────────────────────────────
|
||||||
|
debrisEmitter = new ParticleEmitter("debris", ParticleMesh.Type.Triangle, high ? 200 : 100);
|
||||||
|
debrisEmitter.setShape(new EmitterBoxShape(
|
||||||
|
new Vector3f(-EMITTER_RADIUS, -1f, -EMITTER_RADIUS),
|
||||||
|
new Vector3f( EMITTER_RADIUS, 4f, EMITTER_RADIUS)));
|
||||||
|
debrisEmitter.setGravity(0f, 0f, 0f);
|
||||||
|
debrisEmitter.setParticlesPerSec(0f);
|
||||||
|
debrisEmitter.setLowLife(3f);
|
||||||
|
debrisEmitter.setHighLife(6f);
|
||||||
|
debrisEmitter.setStartSize(0.30f);
|
||||||
|
debrisEmitter.setEndSize(0.80f);
|
||||||
|
debrisEmitter.setStartColor(new ColorRGBA(0.68f, 0.60f, 0.50f, 0.30f));
|
||||||
|
debrisEmitter.setEndColor( new ColorRGBA(0.68f, 0.60f, 0.50f, 0.00f));
|
||||||
|
debrisEmitter.setInitialVelocity(new Vector3f(0f, 0f, 0f));
|
||||||
|
debrisEmitter.setVelocityVariation(0.4f);
|
||||||
|
com.jme3.material.Material dm =
|
||||||
|
new com.jme3.material.Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
dm.setTexture("Texture", createSoftCircleTexture(32, 173, 153, 128, 140));
|
||||||
|
dm.getAdditionalRenderState().setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
|
||||||
|
dm.getAdditionalRenderState().setDepthWrite(false);
|
||||||
|
debrisEmitter.setMaterial(dm);
|
||||||
|
|
||||||
|
debrisNode = new Node("debrisNode");
|
||||||
|
debrisNode.attachChild(debrisEmitter);
|
||||||
|
root.attachChild(debrisNode);
|
||||||
|
|
||||||
|
// ── Regen-Bodensplitzer (ab MEDIUM) ──────────────────────────────────
|
||||||
|
splashEmitter = new ParticleEmitter("splash", ParticleMesh.Type.Triangle, high ? 2000 : 1000);
|
||||||
|
splashEmitter.setShape(new EmitterBoxShape(
|
||||||
|
new Vector3f(-SPLASH_RADIUS, -0.02f, -SPLASH_RADIUS),
|
||||||
|
new Vector3f( SPLASH_RADIUS, 0.02f, SPLASH_RADIUS)));
|
||||||
|
// Starke Schwerkraft: Spritzer schießen kurz hoch und fallen sofort zurück
|
||||||
|
splashEmitter.setGravity(0f, -18f, 0f);
|
||||||
|
splashEmitter.setParticlesPerSec(0f);
|
||||||
|
splashEmitter.setLowLife(0.05f);
|
||||||
|
splashEmitter.setHighLife(0.14f);
|
||||||
|
splashEmitter.setStartSize(0.01f);
|
||||||
|
splashEmitter.setEndSize(0.03f);
|
||||||
|
splashEmitter.setStartColor(new ColorRGBA(0.82f, 0.91f, 1.00f, 0.60f));
|
||||||
|
splashEmitter.setEndColor( new ColorRGBA(0.82f, 0.91f, 1.00f, 0.00f));
|
||||||
|
// Velocity in ALLE Richtungen → Variation wirkt auf x, y UND z → echter Spritz-Effekt
|
||||||
|
splashEmitter.setInitialVelocity(new Vector3f(0.5f, 0.8f, 0.5f));
|
||||||
|
splashEmitter.setVelocityVariation(1.0f);
|
||||||
|
com.jme3.material.Material sm =
|
||||||
|
new com.jme3.material.Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
sm.setTexture("Texture", createSoftCircleTexture(16, 210, 230, 255, 200));
|
||||||
|
sm.getAdditionalRenderState().setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
|
||||||
|
sm.getAdditionalRenderState().setDepthWrite(false);
|
||||||
|
splashEmitter.setMaterial(sm);
|
||||||
|
|
||||||
|
splashNode = new Node("splashNode");
|
||||||
|
splashNode.attachChild(splashEmitter);
|
||||||
|
root.attachChild(splashNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public void update(float tpf) {
|
||||||
|
WeatherState ws = getApplication().getStateManager().getState(WeatherState.class);
|
||||||
|
if (ws == null) { return; }
|
||||||
|
|
||||||
|
Vector3f cam = getApplication().getCamera().getLocation();
|
||||||
|
boolean high = settings.particleQuality == GraphicsSettings.ParticleQuality.HIGH;
|
||||||
|
|
||||||
|
// ── Periodischer Baum-Dichte-Scan ─────────────────────────────────────
|
||||||
|
scanTimer -= tpf;
|
||||||
|
if (scanTimer <= 0f) {
|
||||||
|
scanTimer = SCAN_INTERVAL;
|
||||||
|
float r2 = TREE_RADIUS * TREE_RADIUS;
|
||||||
|
int count = 0;
|
||||||
|
for (Vector3f tp : treePositions) {
|
||||||
|
float dx = tp.x - cam.x, dz = tp.z - cam.z;
|
||||||
|
if (dx * dx + dz * dz < r2) { count++; }
|
||||||
|
}
|
||||||
|
treeDensityFactor = FastMath.clamp(count / TREE_DENSITY_MAX, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
float wind = ws.getWindSpeed();
|
||||||
|
Vector3f wDir = ws.getWindDirection();
|
||||||
|
|
||||||
|
// ── Blätter ───────────────────────────────────────────────────────────
|
||||||
|
float leafTarget = 0f;
|
||||||
|
if (wind > LEAF_MIN_WIND && treeDensityFactor > 0.20f) {
|
||||||
|
leafTarget = FastMath.clamp(
|
||||||
|
(wind - LEAF_MIN_WIND) / 55f * treeDensityFactor, 0f, 1f);
|
||||||
|
}
|
||||||
|
leafIntensity += (leafTarget - leafIntensity) * FastMath.clamp(tpf * 0.5f, 0f, 1f);
|
||||||
|
|
||||||
|
leafNode.setLocalTranslation(cam.x, cam.y, cam.z);
|
||||||
|
if (leafIntensity > 0.01f) {
|
||||||
|
float ws2 = wind * 0.15f;
|
||||||
|
Vector3f leafVel = new Vector3f(wDir.x * ws2, -0.05f, wDir.z * ws2);
|
||||||
|
float ratePerEmitter = leafIntensity * (high ? 40f : 20f) / leafEmitters.length;
|
||||||
|
for (ParticleEmitter le : leafEmitters) {
|
||||||
|
le.getParticleInfluencer().setInitialVelocity(leafVel);
|
||||||
|
le.setParticlesPerSec(ratePerEmitter);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (ParticleEmitter le : leafEmitters) { le.setParticlesPerSec(0f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Staub / Schutt ────────────────────────────────────────────────────
|
||||||
|
float debrisTarget = 0f;
|
||||||
|
if (wind > DUST_MIN_WIND && treeDensityFactor < 0.5f) {
|
||||||
|
debrisTarget = FastMath.clamp(
|
||||||
|
(wind - DUST_MIN_WIND) / 50f * (1f - treeDensityFactor), 0f, 1f);
|
||||||
|
}
|
||||||
|
debrisIntensity += (debrisTarget - debrisIntensity) * FastMath.clamp(tpf * 0.5f, 0f, 1f);
|
||||||
|
|
||||||
|
debrisNode.setLocalTranslation(cam.x, cam.y, cam.z);
|
||||||
|
if (debrisIntensity > 0.01f) {
|
||||||
|
float ws2 = wind * 0.05f;
|
||||||
|
debrisEmitter.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(wDir.x * ws2, 0.05f, wDir.z * ws2));
|
||||||
|
debrisEmitter.setParticlesPerSec(debrisIntensity * (high ? 30f : 15f));
|
||||||
|
} else {
|
||||||
|
debrisEmitter.setParticlesPerSec(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Regen-Bodensplitzer ───────────────────────────────────────────────
|
||||||
|
float rainIntensity = ws.getRainIntensity();
|
||||||
|
float splashTarget = rainIntensity;
|
||||||
|
splashIntensity += (splashTarget - splashIntensity) * FastMath.clamp(tpf * 2f, 0f, 1f);
|
||||||
|
|
||||||
|
// Splashknoten am Boden unter der Kamera positionieren
|
||||||
|
splashNode.setLocalTranslation(cam.x, cam.y - CHAR_HEIGHT, cam.z);
|
||||||
|
if (splashIntensity > 0.01f) {
|
||||||
|
// Wind bläst Spritzer leicht schräg — aber y muss immer >> 0 bleiben
|
||||||
|
// damit Variation auf alle Achsen greift (VelocityVariation * component)
|
||||||
|
float wn = wind * 0.01f;
|
||||||
|
splashEmitter.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(
|
||||||
|
0.5f + wDir.x * wn,
|
||||||
|
0.8f,
|
||||||
|
0.5f + wDir.z * wn));
|
||||||
|
splashEmitter.setParticlesPerSec(splashIntensity * (high ? 700f : 350f));
|
||||||
|
} else {
|
||||||
|
splashEmitter.setParticlesPerSec(0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDisable() {
|
||||||
|
if (leafEmitters != null) {
|
||||||
|
for (ParticleEmitter le : leafEmitters) { le.setParticlesPerSec(0f); }
|
||||||
|
}
|
||||||
|
if (debrisEmitter != null) { debrisEmitter.setParticlesPerSec(0f); }
|
||||||
|
if (splashEmitter != null) { splashEmitter.setParticlesPerSec(0f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override protected void onEnable() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cleanup(Application app) {
|
||||||
|
Node root = ((SimpleApplication) app).getRootNode();
|
||||||
|
if (leafNode != null) { root.detachChild(leafNode); }
|
||||||
|
if (debrisNode != null) { root.detachChild(debrisNode); }
|
||||||
|
if (splashNode != null) { root.detachChild(splashNode); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Texturen ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt alle Blatt-Texturen aus LEAF_TEX_PATHS.
|
||||||
|
* Fehlende Einträge werden übersprungen; ist das Array leer, wird die prozedurale Fallback-Textur verwendet.
|
||||||
|
*/
|
||||||
|
private static Texture2D[] loadLeafTextures(AssetManager assets) {
|
||||||
|
List<Texture2D> loaded = new ArrayList<>();
|
||||||
|
for (String path : LEAF_TEX_PATHS) {
|
||||||
|
try {
|
||||||
|
com.jme3.texture.Texture t = assets.loadTexture(path);
|
||||||
|
if (t instanceof Texture2D) {
|
||||||
|
loaded.add((Texture2D) t);
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
if (loaded.isEmpty()) {
|
||||||
|
log.warn("[AmbientParticles] Keine Blatt-Texturen gefunden — prozeduraler Fallback.");
|
||||||
|
loaded.add(createLeafTexture());
|
||||||
|
} else {
|
||||||
|
log.info("[AmbientParticles] {} Blatt-Texturen geladen.", loaded.size());
|
||||||
|
}
|
||||||
|
return loaded.toArray(new Texture2D[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prozedurale weiße Blattsilhouette (lang-oval mit Mittelrippe).
|
||||||
|
* Weiß → Partikel-StartColor/EndColor bestimmt die Farbe unverfälscht.
|
||||||
|
*/
|
||||||
|
private static Texture2D createLeafTexture() {
|
||||||
|
int w = 32, h = 64;
|
||||||
|
byte[] pixels = new byte[w * h * 4];
|
||||||
|
float cx = (w - 1) * 0.5f, cy = (h - 1) * 0.5f;
|
||||||
|
float rx = w * 0.40f, ry = h * 0.46f;
|
||||||
|
for (int y = 0; y < h; y++) {
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
float nx = (x - cx) / rx;
|
||||||
|
float ny = (y - cy) / ry;
|
||||||
|
// Leichte Asymmetrie für natürlichere Blattspitze
|
||||||
|
float nyd = ny + 0.18f * nx * nx;
|
||||||
|
float d = nx * nx + nyd * nyd;
|
||||||
|
float alpha = FastMath.clamp((1f - d) * 3.5f, 0f, 1f);
|
||||||
|
// Sanfte Abdunkelung zur Blattmitte hin (Mittelrippen-Effekt)
|
||||||
|
float vein = 1f - 0.30f * (float) Math.exp(-nx * nx * 30f);
|
||||||
|
int brightness = (int)(vein * 255f);
|
||||||
|
int i = (y * w + x) * 4;
|
||||||
|
pixels[i ] = (byte) brightness;
|
||||||
|
pixels[i+1] = (byte) brightness;
|
||||||
|
pixels[i+2] = (byte) brightness;
|
||||||
|
pixels[i+3] = (byte) (int)(alpha * 240f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(pixels.length);
|
||||||
|
buf.put(pixels).flip();
|
||||||
|
return new Texture2D(new Image(Image.Format.RGBA8, w, h, buf, ColorSpace.Linear));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Weiches rundes Sprite — für Staub und Regen-Splitzer. */
|
||||||
|
private static Texture2D createSoftCircleTexture(int size, int r, int g, int b, int maxAlpha) {
|
||||||
|
byte[] pixels = new byte[size * size * 4];
|
||||||
|
float c = (size - 1) * 0.5f;
|
||||||
|
for (int y = 0; y < size; y++) {
|
||||||
|
for (int x = 0; x < size; x++) {
|
||||||
|
float dx = (x - c) / c, dy = (y - c) / c;
|
||||||
|
float alpha = FastMath.clamp(1f - (dx * dx + dy * dy) * 2f, 0f, 1f);
|
||||||
|
int i = (y * size + x) * 4;
|
||||||
|
pixels[i ] = (byte) r;
|
||||||
|
pixels[i+1] = (byte) g;
|
||||||
|
pixels[i+2] = (byte) b;
|
||||||
|
pixels[i+3] = (byte) (int)(alpha * maxAlpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(pixels.length);
|
||||||
|
buf.put(pixels).flip();
|
||||||
|
return new Texture2D(new Image(Image.Format.RGBA8, size, size, buf, ColorSpace.Linear));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,6 +75,8 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
private float lastSunOcc = 0f;
|
private float lastSunOcc = 0f;
|
||||||
/** 1.0 = klar, sinkt mit zunehmender Wolkendecke (min 0.20). */
|
/** 1.0 = klar, sinkt mit zunehmender Wolkendecke (min 0.20). */
|
||||||
private float cloudDimFactor = 1f;
|
private float cloudDimFactor = 1f;
|
||||||
|
/** Aktuelle Sonnenhöhe (-1=Nadir, 0=Horizont, +1=Zenit) — gecacht aus onTimeChanged(). */
|
||||||
|
private float sunElevation = 0f;
|
||||||
private ColorRGBA customAmbient = new ColorRGBA();
|
private ColorRGBA customAmbient = new ColorRGBA();
|
||||||
/** true = Spiel-Beleuchtungswerte, false = Editor-Werte. Standardmäßig = withShadows. */
|
/** true = Spiel-Beleuchtungswerte, false = Editor-Werte. Standardmäßig = withShadows. */
|
||||||
private boolean gameMode;
|
private boolean gameMode;
|
||||||
@@ -204,6 +206,11 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
/** Ambient-Farbe für eigene Shader-Uniforms (AmbientColor). Kleiner als AmbientLight. */
|
/** Ambient-Farbe für eigene Shader-Uniforms (AmbientColor). Kleiner als AmbientLight. */
|
||||||
public ColorRGBA getCustomAmbient() { return customAmbient; }
|
public ColorRGBA getCustomAmbient() { return customAmbient; }
|
||||||
|
|
||||||
|
/** Letzter sunOcclusionByCloud()-Wert (0=frei, 1=verdeckt) — für WeatherState. */
|
||||||
|
public float getLastSunOcc() { return lastSunOcc; }
|
||||||
|
/** Cached Sonnenhöhe aus letztem onTimeChanged()-Aufruf (-1..+1). */
|
||||||
|
public float getSunElevation() { return sunElevation; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void cleanup(Application app) {
|
protected void cleanup(Application app) {
|
||||||
dayTime.removeListener(this);
|
dayTime.removeListener(this);
|
||||||
@@ -389,6 +396,7 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
skyControl.getSunAndStars().setHour(t * 24f);
|
skyControl.getSunAndStars().setHour(t * 24f);
|
||||||
Vector3f toSun = skyControl.getSunAndStars().sunDirection(null);
|
Vector3f toSun = skyControl.getSunAndStars().sunDirection(null);
|
||||||
float elevation = toSun.y; // +1 = Zenit, 0 = Horizont, -1 = Nadir
|
float elevation = toSun.y; // +1 = Zenit, 0 = Horizont, -1 = Nadir
|
||||||
|
this.sunElevation = elevation;
|
||||||
float elevC = FastMath.clamp(elevation, 0f, 1f);
|
float elevC = FastMath.clamp(elevation, 0f, 1f);
|
||||||
|
|
||||||
// Spiel vs. Editor: unterschiedliche Helligkeitswerte aus LightingConfig
|
// Spiel vs. Editor: unterschiedliche Helligkeitswerte aus LightingConfig
|
||||||
@@ -417,8 +425,20 @@ public class DayNightState extends BaseAppState implements TimeListener {
|
|||||||
ColorRGBA custDayTinted = custDay.mult(sunTint);
|
ColorRGBA custDayTinted = custDay.mult(sunTint);
|
||||||
float ambFactor = FastMath.clamp(
|
float ambFactor = FastMath.clamp(
|
||||||
(elevation - LightingConfig.AMB_ELEV_LOW) / LightingConfig.AMB_ELEV_RANGE, 0f, 1f);
|
(elevation - LightingConfig.AMB_ELEV_LOW) / LightingConfig.AMB_ELEV_RANGE, 0f, 1f);
|
||||||
ambient.setColor(ambNight.clone().interpolateLocal(ambDayTinted, ambFactor));
|
// Mondlicht: Vollmond hellt die Nacht bläulich-weiß auf (0=Neumond, 1=Vollmond)
|
||||||
customAmbient.set(custNight.clone().interpolateLocal(custDayTinted, ambFactor));
|
float moonIllum = skyControl.getMoonIllumination();
|
||||||
|
ColorRGBA ambNightMoon = ambNight.clone();
|
||||||
|
ColorRGBA custNightMoon = custNight.clone();
|
||||||
|
if (moonIllum > 0f) {
|
||||||
|
ambNightMoon.r = FastMath.clamp(ambNightMoon.r + 0.030f * moonIllum, 0f, 1f);
|
||||||
|
ambNightMoon.g = FastMath.clamp(ambNightMoon.g + 0.040f * moonIllum, 0f, 1f);
|
||||||
|
ambNightMoon.b = FastMath.clamp(ambNightMoon.b + 0.070f * moonIllum, 0f, 1f);
|
||||||
|
custNightMoon.r = FastMath.clamp(custNightMoon.r + 0.015f * moonIllum, 0f, 1f);
|
||||||
|
custNightMoon.g = FastMath.clamp(custNightMoon.g + 0.020f * moonIllum, 0f, 1f);
|
||||||
|
custNightMoon.b = FastMath.clamp(custNightMoon.b + 0.040f * moonIllum, 0f, 1f);
|
||||||
|
}
|
||||||
|
ambient.setColor(ambNightMoon.interpolateLocal(ambDayTinted, ambFactor));
|
||||||
|
customAmbient.set(custNightMoon.interpolateLocal(custDayTinted, ambFactor));
|
||||||
|
|
||||||
// Basis-Schattenintensität: nur von Sonnenhöhe abhängig.
|
// Basis-Schattenintensität: nur von Sonnenhöhe abhängig.
|
||||||
// Wolken-Okklusion wird jedes Frame separat in update() berechnet.
|
// Wolken-Okklusion wird jedes Frame separat in update() berechnet.
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ public class RainState extends BaseAppState {
|
|||||||
private ParticleEmitter rainEmitter;
|
private ParticleEmitter rainEmitter;
|
||||||
private Node rainNode;
|
private Node rainNode;
|
||||||
|
|
||||||
|
// ── Calima-Staub ──────────────────────────────────────────────────────────
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
private ParticleEmitter dustEmitter;
|
||||||
|
private Node dustNode;
|
||||||
|
private float dustIntensity = 0f;
|
||||||
|
|
||||||
// ── Audio ─────────────────────────────────────────────────────────────────
|
// ── Audio ─────────────────────────────────────────────────────────────────
|
||||||
private AudioNode lightRainAudio;
|
private AudioNode lightRainAudio;
|
||||||
private AudioNode heavyRainAudio;
|
private AudioNode heavyRainAudio;
|
||||||
@@ -109,6 +115,33 @@ public class RainState extends BaseAppState {
|
|||||||
rainNode.attachChild(rainEmitter);
|
rainNode.attachChild(rainEmitter);
|
||||||
root.attachChild(rainNode);
|
root.attachChild(rainNode);
|
||||||
|
|
||||||
|
// Calima-Staubpartikel — große, langsame, horizontale Schwebepartikel
|
||||||
|
dustEmitter = new ParticleEmitter("dust", ParticleMesh.Type.Triangle, 1500);
|
||||||
|
dustEmitter.setShape(new EmitterBoxShape(
|
||||||
|
new Vector3f(-EMITTER_RADIUS, -6f, -EMITTER_RADIUS),
|
||||||
|
new Vector3f( EMITTER_RADIUS, 4f, EMITTER_RADIUS)));
|
||||||
|
dustEmitter.setGravity(0, 0, 0);
|
||||||
|
dustEmitter.setParticlesPerSec(0);
|
||||||
|
dustEmitter.setLowLife(5f);
|
||||||
|
dustEmitter.setHighLife(9f);
|
||||||
|
dustEmitter.setStartSize(0.50f);
|
||||||
|
dustEmitter.setEndSize(0.90f);
|
||||||
|
dustEmitter.setStartColor(new ColorRGBA(0.88f, 0.72f, 0.38f, 0.22f));
|
||||||
|
dustEmitter.setEndColor( new ColorRGBA(0.88f, 0.72f, 0.38f, 0.00f));
|
||||||
|
dustEmitter.setInitialVelocity(new Vector3f(0f, 0f, 0f));
|
||||||
|
dustEmitter.setVelocityVariation(0.4f);
|
||||||
|
com.jme3.material.Material dm =
|
||||||
|
new com.jme3.material.Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||||
|
dm.setTexture("Texture", createDustTexture());
|
||||||
|
dm.getAdditionalRenderState()
|
||||||
|
.setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
|
||||||
|
dm.getAdditionalRenderState().setDepthWrite(false);
|
||||||
|
dustEmitter.setMaterial(dm);
|
||||||
|
|
||||||
|
dustNode = new Node("dustNode");
|
||||||
|
dustNode.attachChild(dustEmitter);
|
||||||
|
root.attachChild(dustNode);
|
||||||
|
|
||||||
// Regen-Audio (gestreamt, da lange Schleifen)
|
// Regen-Audio (gestreamt, da lange Schleifen)
|
||||||
lightRainAudio = streamAudio(assets, "audio/ambient/weather/leichter_regen.ogg");
|
lightRainAudio = streamAudio(assets, "audio/ambient/weather/leichter_regen.ogg");
|
||||||
heavyRainAudio = streamAudio(assets, "audio/ambient/weather/starker_regen.ogg");
|
heavyRainAudio = streamAudio(assets, "audio/ambient/weather/starker_regen.ogg");
|
||||||
@@ -159,6 +192,19 @@ public class RainState extends BaseAppState {
|
|||||||
|
|
||||||
rainEmitter.setParticlesPerSec(intensity * 2500f);
|
rainEmitter.setParticlesPerSec(intensity * 2500f);
|
||||||
|
|
||||||
|
// ── Calima-Staub ──────────────────────────────────────────────────────
|
||||||
|
boolean isCalima = weather == WeatherState.Weather.CALIMA;
|
||||||
|
dustIntensity += ((isCalima ? 1f : 0f) - dustIntensity) * FastMath.clamp(tpf * 0.3f, 0f, 1f);
|
||||||
|
if (dustIntensity > 0.01f) {
|
||||||
|
float dustWs = ws.getWindSpeed() * 0.06f;
|
||||||
|
dustEmitter.getParticleInfluencer()
|
||||||
|
.setInitialVelocity(new Vector3f(wind.x * dustWs, 0.10f, wind.z * dustWs));
|
||||||
|
dustEmitter.setParticlesPerSec(dustIntensity * 70f);
|
||||||
|
dustNode.setLocalTranslation(cam.x, cam.y, cam.z);
|
||||||
|
} else {
|
||||||
|
dustEmitter.setParticlesPerSec(0f);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Audio ─────────────────────────────────────────────────────────────
|
// ── Audio ─────────────────────────────────────────────────────────────
|
||||||
float tgtLight = intensity > 0.01f ? FastMath.clamp(1f - intensity * 1.2f, 0f, 0.225f) : 0f;
|
float tgtLight = intensity > 0.01f ? FastMath.clamp(1f - intensity * 1.2f, 0f, 0.225f) : 0f;
|
||||||
float tgtHeavy = FastMath.clamp((intensity - 0.20f) * 1.35f, 0f, 0.25f);
|
float tgtHeavy = FastMath.clamp((intensity - 0.20f) * 1.35f, 0f, 0.25f);
|
||||||
@@ -222,6 +268,7 @@ public class RainState extends BaseAppState {
|
|||||||
@Override
|
@Override
|
||||||
protected void onDisable() {
|
protected void onDisable() {
|
||||||
if (rainEmitter != null) rainEmitter.setParticlesPerSec(0);
|
if (rainEmitter != null) rainEmitter.setParticlesPerSec(0);
|
||||||
|
if (dustEmitter != null) dustEmitter.setParticlesPerSec(0);
|
||||||
if (lightRainAudio != null) lightRainAudio.setVolume(0);
|
if (lightRainAudio != null) lightRainAudio.setVolume(0);
|
||||||
if (heavyRainAudio != null) heavyRainAudio.setVolume(0);
|
if (heavyRainAudio != null) heavyRainAudio.setVolume(0);
|
||||||
}
|
}
|
||||||
@@ -234,6 +281,7 @@ public class RainState extends BaseAppState {
|
|||||||
Node root = ((SimpleApplication) app).getRootNode();
|
Node root = ((SimpleApplication) app).getRootNode();
|
||||||
if (lightningLight != null) root.removeLight(lightningLight);
|
if (lightningLight != null) root.removeLight(lightningLight);
|
||||||
if (rainNode != null) root.detachChild(rainNode);
|
if (rainNode != null) root.detachChild(rainNode);
|
||||||
|
if (dustNode != null) root.detachChild(dustNode);
|
||||||
if (lightRainAudio != null) { lightRainAudio.stop(); root.detachChild(lightRainAudio); }
|
if (lightRainAudio != null) { lightRainAudio.stop(); root.detachChild(lightRainAudio); }
|
||||||
if (heavyRainAudio != null) { heavyRainAudio.stop(); root.detachChild(heavyRainAudio); }
|
if (heavyRainAudio != null) { heavyRainAudio.stop(); root.detachChild(heavyRainAudio); }
|
||||||
for (AudioNode tn : thunderAudio) { if (tn != null) root.detachChild(tn); }
|
for (AudioNode tn : thunderAudio) { if (tn != null) root.detachChild(tn); }
|
||||||
@@ -292,6 +340,27 @@ public class RainState extends BaseAppState {
|
|||||||
return new Texture2D(new Image(Image.Format.RGBA8, w, h, buf, ColorSpace.Linear));
|
return new Texture2D(new Image(Image.Format.RGBA8, w, h, buf, ColorSpace.Linear));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Texture2D createDustTexture() {
|
||||||
|
int w = 32, h = 32;
|
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(w * h * 4);
|
||||||
|
float cx = (w - 1) * 0.5f, cy = (h - 1) * 0.5f;
|
||||||
|
float maxR = w * 0.5f;
|
||||||
|
for (int y = 0; y < h; y++) {
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
float dx = (x - cx) / maxR;
|
||||||
|
float dy = (y - cy) / maxR;
|
||||||
|
float r = (float) Math.sqrt(dx * dx + dy * dy);
|
||||||
|
float alpha = FastMath.clamp(1f - r * r * 2.5f, 0f, 1f);
|
||||||
|
buf.put((byte) 224); // R — gelblich
|
||||||
|
buf.put((byte) 184); // G
|
||||||
|
buf.put((byte) 96); // B — warm-bräunlich
|
||||||
|
buf.put((byte) (int) (alpha * 160));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
return new Texture2D(new Image(Image.Format.RGBA8, w, h, buf, ColorSpace.Linear));
|
||||||
|
}
|
||||||
|
|
||||||
private static AudioNode streamAudio(AssetManager assets, String path) {
|
private static AudioNode streamAudio(AssetManager assets, String path) {
|
||||||
AudioNode n = new AudioNode(assets, path, AudioData.DataType.Stream);
|
AudioNode n = new AudioNode(assets, path, AudioData.DataType.Stream);
|
||||||
n.setPositional(false);
|
n.setPositional(false);
|
||||||
|
|||||||
@@ -327,9 +327,20 @@ public class WeatherState extends BaseAppState {
|
|||||||
deepWaterColor.interpolateLocal(DEEP_WATER_COLOR[i], tpf * 0.020f);
|
deepWaterColor.interpolateLocal(DEEP_WATER_COLOR[i], tpf * 0.020f);
|
||||||
|
|
||||||
if (fogFilter != null) {
|
if (fogFilter != null) {
|
||||||
|
// Abendrot/Morgenröte: orange-rötlicher Dunst wenn Sonne nahe am Horizont
|
||||||
|
ColorRGBA tintedFog = fogColor.clone();
|
||||||
|
DayNightState dns = getApplication().getStateManager().getState(DayNightState.class);
|
||||||
|
if (dns != null) {
|
||||||
|
float elev = dns.getSunElevation();
|
||||||
|
float dawnT = FastMath.clamp(1f - Math.abs(elev - 0.08f) / 0.18f, 0f, 1f);
|
||||||
|
dawnT = dawnT * dawnT; // weichere Kurve
|
||||||
|
tintedFog.r = FastMath.clamp(tintedFog.r + 0.28f * dawnT, 0f, 1f);
|
||||||
|
tintedFog.g = FastMath.clamp(tintedFog.g - 0.06f * dawnT, 0f, 1f);
|
||||||
|
tintedFog.b = FastMath.clamp(tintedFog.b - 0.14f * dawnT, 0f, 1f);
|
||||||
|
}
|
||||||
fogFilter.setFogDensity(fogDensity);
|
fogFilter.setFogDensity(fogDensity);
|
||||||
fogFilter.setFogDistance(fogDistance);
|
fogFilter.setFogDistance(fogDistance);
|
||||||
fogFilter.setFogColor(fogColor.clone());
|
fogFilter.setFogColor(tintedFog);
|
||||||
}
|
}
|
||||||
if (waterFilter != null) {
|
if (waterFilter != null) {
|
||||||
waterFilter.setSpeed(waveSpeed);
|
waterFilter.setSpeed(waveSpeed);
|
||||||
@@ -345,6 +356,10 @@ public class WeatherState extends BaseAppState {
|
|||||||
if (lightScatterFilter != null) {
|
if (lightScatterFilter != null) {
|
||||||
// Max 0.08 um Gras-Streifen zu vermeiden; Nebel/Wolken reduzieren weiter
|
// Max 0.08 um Gras-Streifen zu vermeiden; Nebel/Wolken reduzieren weiter
|
||||||
float ld = Math.max(0f, 0.08f - fogDensity * 0.3f);
|
float ld = Math.max(0f, 0.08f - fogDensity * 0.3f);
|
||||||
|
// God Rays verschwinden wenn Sonne hinter Wolken steckt
|
||||||
|
DayNightState dns = getApplication().getStateManager().getState(DayNightState.class);
|
||||||
|
float sunOcc = dns != null ? dns.getLastSunOcc() : 0f;
|
||||||
|
ld *= FastMath.clamp(1f - sunOcc * 0.95f, 0.05f, 1f);
|
||||||
lightScatterFilter.setLightDensity(ld);
|
lightScatterFilter.setLightDensity(ld);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ menu.graphics.val.shadow.low=Niedrig
|
|||||||
menu.graphics.val.shadow.medium=Mittel
|
menu.graphics.val.shadow.medium=Mittel
|
||||||
menu.graphics.val.shadow.high=Hoch
|
menu.graphics.val.shadow.high=Hoch
|
||||||
menu.graphics.row.postfx=Post-Effekte
|
menu.graphics.row.postfx=Post-Effekte
|
||||||
|
menu.graphics.row.particles=Partikeleffekte
|
||||||
menu.graphics.val.resolution.native=Nativ
|
menu.graphics.val.resolution.native=Nativ
|
||||||
menu.graphics.btn.apply=Übernehmen
|
menu.graphics.btn.apply=Übernehmen
|
||||||
menu.graphics.btn.cancel=Abbrechen
|
menu.graphics.btn.cancel=Abbrechen
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ menu.graphics.val.shadow.low=Low
|
|||||||
menu.graphics.val.shadow.medium=Medium
|
menu.graphics.val.shadow.medium=Medium
|
||||||
menu.graphics.val.shadow.high=High
|
menu.graphics.val.shadow.high=High
|
||||||
menu.graphics.row.postfx=Post Effects
|
menu.graphics.row.postfx=Post Effects
|
||||||
|
menu.graphics.row.particles=Particle Effects
|
||||||
menu.graphics.val.resolution.native=Native
|
menu.graphics.val.resolution.native=Native
|
||||||
menu.graphics.btn.apply=Apply
|
menu.graphics.btn.apply=Apply
|
||||||
menu.graphics.btn.cancel=Cancel
|
menu.graphics.btn.cancel=Cancel
|
||||||
|
|||||||