Bewegung im Wasser bearbeitet
This commit is contained in:
@@ -15,13 +15,18 @@ import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.PlacedModel;
|
||||
import de.blight.common.PlacedModelIO;
|
||||
import de.blight.common.PlacedWater;
|
||||
import de.blight.common.WaterBodyIO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
@@ -47,6 +52,9 @@ public class WaterBodyState extends BaseAppState {
|
||||
private final List<WaterPolygonFilter> filters = new ArrayList<>();
|
||||
private final List<Geometry> inclinedGeos = new ArrayList<>();
|
||||
private final List<Material> animatedMaterials = new ArrayList<>();
|
||||
private final List<Geometry> objectFoams = new ArrayList<>();
|
||||
private final List<Material> objectFoamMats = new ArrayList<>();
|
||||
private Texture2D foamRingTex = null;
|
||||
private float time = 0f;
|
||||
|
||||
private Node rootNode;
|
||||
@@ -85,15 +93,20 @@ public class WaterBodyState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
log.info("{} flache + {} abschüssige Wasserfläche(n) geladen.", flat, inclined);
|
||||
// buildObjectFoam(assets, bodies); // TODO: Objekt-Schaum deaktiviert
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (!animatedMaterials.isEmpty()) {
|
||||
if (!animatedMaterials.isEmpty() || !objectFoamMats.isEmpty()) {
|
||||
time += tpf;
|
||||
for (Material m : animatedMaterials) {
|
||||
m.setFloat("Time", time);
|
||||
}
|
||||
for (int i = 0; i < objectFoamMats.size(); i++) {
|
||||
float a = 0.22f + 0.12f * (float) Math.sin(time * 2.2f + i * 1.3f);
|
||||
objectFoamMats.get(i).setColor("Color", new ColorRGBA(1f, 1f, 1f, a));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,6 +117,9 @@ public class WaterBodyState extends BaseAppState {
|
||||
for (Geometry g : inclinedGeos) rootNode.detachChild(g);
|
||||
inclinedGeos.clear();
|
||||
animatedMaterials.clear();
|
||||
for (Geometry g : objectFoams) rootNode.detachChild(g);
|
||||
objectFoams.clear();
|
||||
objectFoamMats.clear();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@@ -144,6 +160,109 @@ public class WaterBodyState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Objekt-Schaum ─────────────────────────────────────────────────────────
|
||||
|
||||
/** Meereshöhe für den globalen DynamicWaterFilter (kein PlacedWater-Body). */
|
||||
private static final float SEA_LEVEL = 0f;
|
||||
|
||||
private void buildObjectFoam(AssetManager assets, List<PlacedWater> bodies) {
|
||||
List<PlacedWater> flatBodies = bodies.stream().filter(PlacedWater::isFlat).toList();
|
||||
|
||||
List<PlacedModel> models;
|
||||
try {
|
||||
models = PlacedModelIO.load();
|
||||
} catch (Exception e) {
|
||||
log.warn("[WaterBodyState] Objekte für Objekt-Schaum nicht ladbar: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (PlacedModel m : models) {
|
||||
boolean added = false;
|
||||
|
||||
// 1. Definierte flache Wasserkörper (Seen, Teiche …)
|
||||
for (PlacedWater body : flatBodies) {
|
||||
if (!pointInPolygon(m.x(), m.z(), body.pointsX(), body.pointsZ())) continue;
|
||||
if (m.y() > body.waterHeight() + 1.0f) continue;
|
||||
if (m.y() < body.waterHeight() - 6.0f) continue;
|
||||
|
||||
attachObjectFoam(new Vector3f(m.x(), body.waterHeight() + 0.04f, m.z()),
|
||||
m.scale(), assets);
|
||||
count++;
|
||||
added = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// 2. Meereshöhe (globaler DynamicWaterFilter, kein Polygon nötig)
|
||||
if (!added && m.y() <= SEA_LEVEL + 1.0f && m.y() >= SEA_LEVEL - 6.0f) {
|
||||
attachObjectFoam(new Vector3f(m.x(), SEA_LEVEL + 0.04f, m.z()),
|
||||
m.scale(), assets);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
log.info("[WaterBodyState] {} Objekt-Schaum-Scheibe(n) platziert.", count);
|
||||
}
|
||||
|
||||
private void attachObjectFoam(Vector3f center, float scale, AssetManager assets) {
|
||||
float r = com.jme3.math.FastMath.clamp(scale * 0.5f, 0.4f, 2.0f);
|
||||
// Horizontales Quad in der XZ-Ebene, texturiert mit Ring-Textur
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(new float[]{ -r,0,-r, r,0,-r, r,0,r, -r,0,r });
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(new float[]{ 0,0, 1,0, 1,1, 0,1 });
|
||||
FloatBuffer norm = BufferUtils.createFloatBuffer(new float[]{ 0,1,0, 0,1,0, 0,1,0, 0,1,0 });
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(new int[]{ 0,1,2, 0,2,3 });
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Normal, 3, norm);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound(); mesh.updateCounts();
|
||||
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", getFoamRingTex());
|
||||
mat.setColor("Color", new ColorRGBA(1f, 1f, 1f, 0.28f));
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
|
||||
Geometry geo = new Geometry("obj_foam", mesh);
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(center);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
rootNode.attachChild(geo);
|
||||
objectFoams.add(geo);
|
||||
objectFoamMats.add(mat);
|
||||
}
|
||||
|
||||
/** Ring-Textur: transparent innen + außen, opak in der Mitte — sin(π*d)-Profil. */
|
||||
private Texture2D getFoamRingTex() {
|
||||
if (foamRingTex != null) return foamRingTex;
|
||||
int size = 64;
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(size * size * 4);
|
||||
float c = (size - 1) / 2f;
|
||||
for (int y = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float dx = (x - c) / c, dy = (y - c) / c;
|
||||
float d = (float) Math.sqrt(dx * dx + dy * dy); // 0=Mitte, 1=Rand
|
||||
float a = (d < 1f) ? (float) Math.sin(Math.PI * d) : 0f;
|
||||
buf.put((byte) 255).put((byte) 255).put((byte) 255).put((byte)(a * 255));
|
||||
}
|
||||
}
|
||||
buf.flip();
|
||||
foamRingTex = new Texture2D(new Image(Image.Format.RGBA8, size, size, buf));
|
||||
return foamRingTex;
|
||||
}
|
||||
|
||||
private static boolean pointInPolygon(float px, float pz, float[] xs, float[] zs) {
|
||||
boolean inside = false;
|
||||
int n = xs.length;
|
||||
for (int i = 0, j = n - 1; i < n; j = i++) {
|
||||
if (((zs[i] > pz) != (zs[j] > pz)) &&
|
||||
(px < (xs[j] - xs[i]) * (pz - zs[i]) / (zs[j] - zs[i]) + xs[i])) {
|
||||
inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut das Wasserfall-Mesh aus den gespeicherten Eckpunkt-Koordinaten.
|
||||
* Y wird zwischen Nachbar-Ecken linear interpoliert – kein Terrain-Raycast,
|
||||
|
||||
@@ -1,11 +1,22 @@
|
||||
package de.blight.game.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.FastMath;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
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.VertexBuffer;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import com.jme3.texture.Image;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.Texture2D;
|
||||
@@ -15,6 +26,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
@@ -37,9 +50,9 @@ public class WaterInteractionState extends BaseAppState {
|
||||
private static final int N = 256;
|
||||
private static final float HALF_EXTENT = 64f; // half of 128m world coverage
|
||||
private static final float INV_EXTENT = 1.0f / (HALF_EXTENT * 2f);
|
||||
private static final float WAVE_K = 0.25f; // wave eq. coefficient (stability: ≤ 0.5)
|
||||
private static final float DAMPING = 0.995f;
|
||||
private static final float WAVE_STRENGTH = 6.0f; // normal perturbation scale in shader
|
||||
private static final float WAVE_K = 0.004f; // wave eq. coefficient — speed = sqrt(K)*0.5m*fps ≈ sqrt(0.004)*30 ≈ 1.9 m/s
|
||||
private static final float DAMPING = 0.985f; // stärkere Dämpfung da langsame Wellen länger verweilen
|
||||
private static final float WAVE_STRENGTH = 1.5f; // normal perturbation scale in shader
|
||||
private static final float BLOB_RADIUS_TEX = 3.0f; // blob radius in texels
|
||||
private static final float BLOB_COOLDOWN = 0.07f; // seconds between blob injections
|
||||
private static final float RECENTER_THRESH = HALF_EXTENT * 0.45f; // ~29m
|
||||
@@ -66,6 +79,12 @@ public class WaterInteractionState extends BaseAppState {
|
||||
private float waterHeight = 0f;
|
||||
private final Vector3f prevPos = new Vector3f(Float.NaN, 0f, Float.NaN);
|
||||
|
||||
// ── Player foam ──────────────────────────────────────────────────────────
|
||||
|
||||
private Geometry foamGeo;
|
||||
private Material foamMat;
|
||||
private float foamTime = 0f;
|
||||
|
||||
public WaterInteractionState(DynamicWaterFilter filter) {
|
||||
this.dynFilter = filter;
|
||||
}
|
||||
@@ -100,6 +119,8 @@ public class WaterInteractionState extends BaseAppState {
|
||||
waveTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
|
||||
waveTex.setWrap(Texture.WrapMode.EdgeClamp);
|
||||
|
||||
// buildPlayerFoam(app); // TODO: Spieler-Schaum deaktiviert
|
||||
|
||||
log.debug("[WaterInteraction] Simulation initialized ({}×{}, {}m coverage)", N, N, HALF_EXTENT * 2f);
|
||||
}
|
||||
|
||||
@@ -109,6 +130,7 @@ public class WaterInteractionState extends BaseAppState {
|
||||
waveBuf = null;
|
||||
waveImg = null;
|
||||
waveTex = null;
|
||||
if (foamGeo != null) foamGeo.removeFromParent();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@@ -125,6 +147,7 @@ public class WaterInteractionState extends BaseAppState {
|
||||
paramsApplied = true;
|
||||
}
|
||||
|
||||
boolean playerInWater = false;
|
||||
if (playerControl != null) {
|
||||
Vector3f pos = playerControl.getPhysicsLocation();
|
||||
|
||||
@@ -132,11 +155,12 @@ public class WaterInteractionState extends BaseAppState {
|
||||
|
||||
float feetY = pos.y - 0.9f; // CharacterControl position = capsule center, ~0.9m above feet
|
||||
if (feetY < waterHeight + 0.4f) { // player in or just above water
|
||||
playerInWater = true;
|
||||
float speed = Float.isNaN(prevPos.x) ? 0f : pos.distance(prevPos) / tpf;
|
||||
if (speed > 0.3f) { // ignore sub-threshold shuffling
|
||||
blobTimer -= tpf;
|
||||
if (blobTimer <= 0f) {
|
||||
float strength = FastMath.clamp(speed / 8.0f, 0.1f, 1.0f);
|
||||
float strength = FastMath.clamp(speed / 8.0f, 0.05f, 0.3f);
|
||||
addBlob(pos.x, pos.z, strength);
|
||||
blobTimer = BLOB_COOLDOWN;
|
||||
}
|
||||
@@ -145,6 +169,8 @@ public class WaterInteractionState extends BaseAppState {
|
||||
prevPos.set(pos);
|
||||
}
|
||||
|
||||
// updatePlayerFoam(tpf, playerInWater); // TODO: Spieler-Schaum deaktiviert
|
||||
|
||||
runSimulation();
|
||||
uploadTexture();
|
||||
}
|
||||
@@ -210,4 +236,63 @@ public class WaterInteractionState extends BaseAppState {
|
||||
waveBuf.rewind();
|
||||
waveImg.setUpdateNeeded();
|
||||
}
|
||||
|
||||
// ── Player foam ──────────────────────────────────────────────────────────
|
||||
|
||||
private void buildPlayerFoam(Application app) {
|
||||
Node root = ((SimpleApplication) app).getRootNode();
|
||||
float r = 0.38f;
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(new float[]{ -r,0,-r, r,0,-r, r,0,r, -r,0,r });
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(new float[]{ 0,0, 1,0, 1,1, 0,1 });
|
||||
FloatBuffer norm = BufferUtils.createFloatBuffer(new float[]{ 0,1,0, 0,1,0, 0,1,0, 0,1,0 });
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(new int[]{ 0,1,2, 0,2,3 });
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Normal, 3, norm);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound(); mesh.updateCounts();
|
||||
|
||||
foamMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
foamMat.setTexture("ColorMap", buildFoamRingTex());
|
||||
foamMat.setColor("Color", new ColorRGBA(1f, 1f, 1f, 0.30f));
|
||||
foamMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
foamMat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
|
||||
foamGeo = new Geometry("player_foam", mesh);
|
||||
foamGeo.setMaterial(foamMat);
|
||||
foamGeo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
foamGeo.setCullHint(Spatial.CullHint.Always);
|
||||
root.attachChild(foamGeo);
|
||||
}
|
||||
|
||||
private void updatePlayerFoam(float tpf, boolean inWater) {
|
||||
if (foamGeo == null || playerControl == null) return;
|
||||
if (inWater) {
|
||||
Vector3f pos = playerControl.getPhysicsLocation();
|
||||
foamGeo.setLocalTranslation(pos.x, waterHeight + 0.04f, pos.z);
|
||||
foamGeo.setCullHint(Spatial.CullHint.Inherit);
|
||||
foamTime += tpf;
|
||||
float a = 0.20f + 0.12f * FastMath.sin(foamTime * 2.5f);
|
||||
foamMat.setColor("Color", new ColorRGBA(1f, 1f, 1f, a));
|
||||
} else {
|
||||
foamGeo.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
}
|
||||
|
||||
private static Texture2D buildFoamRingTex() {
|
||||
int size = 64;
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(size * size * 4);
|
||||
float c = (size - 1) / 2f;
|
||||
for (int y = 0; y < size; y++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float dx = (x - c) / c, dy = (y - c) / c;
|
||||
float d = (float) Math.sqrt(dx * dx + dy * dy);
|
||||
float a = (d < 1f) ? (float) Math.sin(Math.PI * d) : 0f;
|
||||
buf.put((byte) 255).put((byte) 255).put((byte) 255).put((byte)(a * 255));
|
||||
}
|
||||
}
|
||||
buf.flip();
|
||||
return new Texture2D(new Image(Image.Format.RGBA8, size, size, buf));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user