Einmal den Fortschritt mit dem Wasser sichern
This commit is contained in:
@@ -324,14 +324,14 @@ public class WorldScene extends BaseAppState {
|
||||
|
||||
/**
|
||||
* Erstellt ein Terrain aus der gespeicherten {@link MapData}.
|
||||
* Die 4097×4097 Editor-Daten werden auf 513×513 heruntergesampelt
|
||||
* (jeder 8. Vertex), mit Scale (8, 1, 8) auf die gleiche Weltgröße
|
||||
* Die 16385×16385 Editor-Daten werden auf 513×513 heruntergesampelt
|
||||
* (jeder 32. Vertex), mit Scale (8, 1, 8) auf die gleiche Weltgröße
|
||||
* 4096 × 4096 WE gebracht.
|
||||
*/
|
||||
private TerrainQuad buildTerrainFromMap(MapData map) {
|
||||
final int GAME_VERTS = 513; // 512 Zellen à 8 WE = 4096 WE
|
||||
final int STEP = 8; // 4096 / 512 = 8 Vertices überspringen
|
||||
final int SRC_VERTS = MapData.TERRAIN_VERTS; // 4097
|
||||
final int STEP = (MapData.TERRAIN_VERTS - 1) / (GAME_VERTS - 1); // 32
|
||||
final int SRC_VERTS = MapData.TERRAIN_VERTS; // 16385
|
||||
|
||||
float[] heights = new float[GAME_VERTS * GAME_VERTS];
|
||||
for (int gz = 0; gz < GAME_VERTS; gz++) {
|
||||
|
||||
@@ -40,7 +40,8 @@ import java.util.Random;
|
||||
public class RiverState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RiverState.class);
|
||||
private static final float UV_SCALE = 6.0f;
|
||||
private static final float UV_SCALE = 6.0f;
|
||||
private static final float WATER_SINK = 0.5f; // Wasser liegt 0,5m unter den Kontrollpunkten
|
||||
|
||||
private Node riverNode;
|
||||
private AssetManager assets;
|
||||
@@ -118,7 +119,7 @@ public class RiverState extends BaseAppState {
|
||||
List<RiverPoint> run = RiverSpline.subdivide(pts.subList(i, j + 1));
|
||||
if (run.size() >= 2) {
|
||||
buildRibbonSection(run, wf);
|
||||
if (wf) buildWaterfallParticles(run.get(run.size() - 1));
|
||||
if (wf) buildWaterfallParticles(run.get(run.size() - 1), computeFlowDir(run));
|
||||
}
|
||||
i = j;
|
||||
}
|
||||
@@ -175,7 +176,7 @@ public class RiverState extends BaseAppState {
|
||||
if (right.lengthSquared() < 1e-6f) right.set(1f, 0f, 0f);
|
||||
|
||||
float halfW = pt.width() * 0.5f;
|
||||
float px = pt.x(), py = pt.y(), pz = pt.z();
|
||||
float px = pt.x(), py = pt.y() - WATER_SINK, pz = pt.z();
|
||||
float vCoord = arcLen[i] / UV_SCALE;
|
||||
|
||||
// Linker Rand (U=0), rechter Rand (U=1)
|
||||
@@ -208,28 +209,44 @@ public class RiverState extends BaseAppState {
|
||||
|
||||
private Material buildMaterial(boolean isWaterfall) {
|
||||
ColorRGBA tint = isWaterfall
|
||||
? new ColorRGBA(0.65f, 0.82f, 0.95f, 0.80f)
|
||||
: new ColorRGBA(0.10f, 0.30f, 0.62f, 0.85f);
|
||||
? new ColorRGBA(0.65f, 0.82f, 0.95f, 0.75f)
|
||||
: new ColorRGBA(0.10f, 0.30f, 0.62f, 0.68f);
|
||||
|
||||
Material mat;
|
||||
try {
|
||||
mat = new Material(assets, "MatDefs/FlowingWater.j3md");
|
||||
try {
|
||||
Texture nm = assets.loadTexture(
|
||||
"Common/MatDefs/Water/Textures/water_normalmap.png");
|
||||
|
||||
// Normal-Map: erst eigene Texturen versuchen, dann JME-Fallback
|
||||
Texture nm = loadTextureOr(
|
||||
isWaterfall ? "Textures/water/waterfall_normal.png"
|
||||
: "Textures/water/river_normal.jpg",
|
||||
"Common/MatDefs/Water/Textures/water_normalmap.png");
|
||||
if (nm != null) {
|
||||
nm.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("NormalMap", nm);
|
||||
} catch (Exception e) {
|
||||
log.warn("Normal-Map nicht ladbar, wird ohne Wellenstruktur gerendert");
|
||||
} else {
|
||||
log.warn("Keine Normal-Map geladen – Mesh ohne Wellenstruktur");
|
||||
}
|
||||
|
||||
if (foamTexture != null) {
|
||||
mat.setTexture("FoamMap", foamTexture);
|
||||
}
|
||||
mat.setColor("Tint", tint);
|
||||
mat.setFloat("UVScale", UV_SCALE);
|
||||
mat.setFloat("FlowSpeed", isWaterfall ? RiverPoint.WATERFALL_SPEED
|
||||
: RiverPoint.RIVER_SPEED);
|
||||
mat.setFloat("FoamAmount", isWaterfall ? 1.0f : 0.0f);
|
||||
|
||||
// Diffuse-Map: river.jpg für Fluss (Farbmodulation), waterfall_diffuse für Gischt
|
||||
String diffPath = isWaterfall ? "Textures/water/waterfall_diffuse.png"
|
||||
: "Textures/water/river.jpg";
|
||||
Texture diff = loadTextureOr(diffPath, null);
|
||||
if (diff != null) {
|
||||
diff.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("DiffuseMap", diff);
|
||||
}
|
||||
|
||||
mat.setColor("Tint", tint);
|
||||
mat.setFloat("UVScale", UV_SCALE);
|
||||
mat.setFloat("NormalUVScale", 0.5f); // 2x größeres Wellenmuster
|
||||
mat.setFloat("FlowSpeed", isWaterfall ? RiverPoint.WATERFALL_SPEED
|
||||
: RiverPoint.RIVER_SPEED);
|
||||
mat.setFloat("FoamAmount", isWaterfall ? 1.0f : 0.0f);
|
||||
} catch (Exception e) {
|
||||
log.warn("FlowingWater-Material nicht ladbar, Fallback auf Unshaded", e);
|
||||
mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
@@ -242,6 +259,20 @@ public class RiverState extends BaseAppState {
|
||||
return mat;
|
||||
}
|
||||
|
||||
/** Versucht primaryPath zu laden; schlägt das fehl, wird fallbackPath versucht (null = kein Fallback). */
|
||||
private Texture loadTextureOr(String primaryPath, String fallbackPath) {
|
||||
try {
|
||||
return assets.loadTexture(primaryPath);
|
||||
} catch (Exception ignored) {}
|
||||
if (fallbackPath == null) return null;
|
||||
try {
|
||||
return assets.loadTexture(fallbackPath);
|
||||
} catch (Exception e) {
|
||||
log.warn("Textur nicht ladbar: {} und {}", primaryPath, fallbackPath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Worley-Noise Schaum-Textur ────────────────────────────────────────────
|
||||
|
||||
private Texture2D generateFoamTexture() {
|
||||
@@ -286,29 +317,58 @@ public class RiverState extends BaseAppState {
|
||||
|
||||
// ── Partikel-Emitter ──────────────────────────────────────────────────────
|
||||
|
||||
private void buildWaterfallParticles(RiverPoint base) {
|
||||
/**
|
||||
* Gischt-Partikel am Fuß des Wasserfalls.
|
||||
* Die Spray-Richtung ist das Spiegelbild der horizontalen Fließrichtung
|
||||
* (Wasser prallt auf und spritzt zurück + hoch).
|
||||
*/
|
||||
private void buildWaterfallParticles(RiverPoint base, Vector3f flowDir) {
|
||||
ParticleEmitter emitter = new ParticleEmitter(
|
||||
"waterfall_particles", ParticleMesh.Type.Triangle, 30);
|
||||
"waterfall_particles", ParticleMesh.Type.Triangle, 60);
|
||||
|
||||
Material pMat = new Material(assets, "Common/MatDefs/Misc/Particle.j3md");
|
||||
try {
|
||||
pMat.setTexture("Texture", assets.loadTexture("Effects/Smoke/Smoke.png"));
|
||||
} catch (Exception e) {
|
||||
log.warn("Partikel-Textur nicht ladbar", e);
|
||||
}
|
||||
Texture pTex = loadTextureOr("Textures/Water/spray.png", "Effects/Smoke/Smoke.png");
|
||||
if (pTex != null) pMat.setTexture("Texture", pTex);
|
||||
pMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.AlphaAdditive);
|
||||
|
||||
emitter.setMaterial(pMat);
|
||||
emitter.setImagesX(1);
|
||||
emitter.setImagesY(1);
|
||||
emitter.setStartColor(new ColorRGBA(1f, 1f, 1f, 0.5f));
|
||||
emitter.setEndColor(new ColorRGBA(1f, 1f, 1f, 0f));
|
||||
emitter.setStartSize(1.2f);
|
||||
emitter.setEndSize(2.5f);
|
||||
emitter.setGravity(0f, -0.5f, 0f);
|
||||
emitter.setLowLife(0.8f);
|
||||
emitter.setHighLife(1.2f);
|
||||
emitter.setInitialVelocity(new Vector3f(0f, 3f, 0f));
|
||||
emitter.setVelocityVariation(0.6f);
|
||||
emitter.setParticlesPerSec(15);
|
||||
|
||||
// Hellweiße, leicht bläuliche Gischt – startet fast opak, verblasst komplett
|
||||
emitter.setStartColor(new ColorRGBA(0.90f, 0.95f, 1.00f, 0.55f));
|
||||
emitter.setEndColor (new ColorRGBA(1.00f, 1.00f, 1.00f, 0.00f));
|
||||
|
||||
// Partikel starten klein, werden als Nebelwolke größer
|
||||
emitter.setStartSize(0.4f);
|
||||
emitter.setEndSize (2.8f);
|
||||
|
||||
// Leichte Schwerkraft damit Gischt wieder fällt
|
||||
emitter.setGravity(0f, -1.2f, 0f);
|
||||
emitter.setLowLife (0.7f);
|
||||
emitter.setHighLife(1.6f);
|
||||
|
||||
// Spray-Richtung: nach oben + horizontal entgegen der Fließrichtung (Aufprall-Bounce)
|
||||
// flowDir zeigt bergab/in Fließrichtung; horizontale Umkehrung = Rückspritzer
|
||||
Vector3f sprayVel = new Vector3f(
|
||||
-flowDir.x * 1.8f,
|
||||
4.5f,
|
||||
-flowDir.z * 1.8f);
|
||||
emitter.setInitialVelocity(sprayVel);
|
||||
emitter.setVelocityVariation(0.75f); // hohe Variation → aufgefächerte Gischt-Wolke
|
||||
|
||||
emitter.setParticlesPerSec(35);
|
||||
emitter.setLocalTranslation(base.x(), base.y(), base.z());
|
||||
riverNode.attachChild(emitter);
|
||||
}
|
||||
|
||||
/** Fließrichtung am Ende eines Abschnitts aus den letzten Stützpunkten. */
|
||||
private Vector3f computeFlowDir(List<RiverPoint> pts) {
|
||||
int n = pts.size();
|
||||
RiverPoint a = pts.get(Math.max(0, n - 3));
|
||||
RiverPoint b = pts.get(n - 1);
|
||||
Vector3f dir = new Vector3f(b.x() - a.x(), b.y() - a.y(), b.z() - a.z());
|
||||
if (dir.lengthSquared() < 1e-6f) return new Vector3f(0f, -1f, 0f);
|
||||
return dir.normalizeLocal();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user