Wasserfall System eingebaut
This commit is contained in:
@@ -44,7 +44,7 @@ public class GraphicsSettings {
|
||||
// mapSize splits lambda zExtend zFade backface ssaoR ssaoI
|
||||
LOW ( 1024, 2, 0.70f, 40f, 5f, false, 0f, 0f ),
|
||||
MEDIUM ( 2048, 3, 0.75f, 60f, 8f, true, 1.0f, 1.5f),
|
||||
HIGH ( 4096, 4, 0.80f, 80f, 10f, true, 1.5f, 2.5f);
|
||||
HIGH ( 4096, 4, 0.70f, 50f, 8f, true, 1.5f, 2.5f);
|
||||
|
||||
public final int shadowMapSize;
|
||||
public final int splits;
|
||||
|
||||
@@ -258,6 +258,7 @@ public class WorldScene extends BaseAppState {
|
||||
|
||||
BlightGame.status("Lade Welt-Objekte...");
|
||||
app.getStateManager().attach(new RiverState());
|
||||
app.getStateManager().attach(new de.blight.game.state.WaterfallState());
|
||||
WorldObjectsState worldObjects = new WorldObjectsState();
|
||||
worldObjects.setLodFactor((float) graphicsSettings.viewDistance.lod0Range);
|
||||
app.getStateManager().attach(worldObjects);
|
||||
|
||||
@@ -3,30 +3,53 @@ 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.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
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.VertexBuffer;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.PlacedWater;
|
||||
import de.blight.common.WaterBodyIO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Rendert Polygon-Wasserflächen per WaterPolygonFilter (ein Filter pro Fläche).
|
||||
* Identisches Aussehen wie WaterFilter, aber auf das eingezeichnete Polygon beschränkt.
|
||||
* Rendert Polygon-Wasserflächen.
|
||||
*
|
||||
* Flache Flächen (isFlat) → WaterPolygonFilter.
|
||||
* Abschüssige Flächen → FlowingWater-Shader auf Polygon-Mesh.
|
||||
* Die Kanten werden tesselliert, Y wird linear zwischen den Editor-Eckpunkten interpoliert.
|
||||
*/
|
||||
public class WaterBodyState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WaterBodyState.class);
|
||||
|
||||
private static final Vector3f SUN_DIR = new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal();
|
||||
private static final Vector3f SUN_DIR = new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal();
|
||||
private static final float UV_SCALE = 0.12f;
|
||||
private static final float TESS_STEP = 0.8f; // Tessellierungs-Abstand in Welteinheiten
|
||||
private static final float LIFT = 0.03f; // Abstand über gespeicherter Oberfläche
|
||||
|
||||
private final FilterPostProcessor fpp;
|
||||
private final List<WaterPolygonFilter> filters = new ArrayList<>();
|
||||
private final List<WaterPolygonFilter> filters = new ArrayList<>();
|
||||
private final List<Geometry> inclinedGeos = new ArrayList<>();
|
||||
private final List<Material> animatedMaterials = new ArrayList<>();
|
||||
private float time = 0f;
|
||||
|
||||
private Node rootNode;
|
||||
|
||||
public WaterBodyState(FilterPostProcessor fpp) {
|
||||
this.fpp = fpp;
|
||||
@@ -34,7 +57,8 @@ public class WaterBodyState extends BaseAppState {
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
SimpleApplication sa = (SimpleApplication) app;
|
||||
rootNode = ((SimpleApplication) app).getRootNode();
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
List<PlacedWater> bodies;
|
||||
try {
|
||||
@@ -45,37 +69,221 @@ public class WaterBodyState extends BaseAppState {
|
||||
}
|
||||
if (bodies.isEmpty()) return;
|
||||
|
||||
int flat = 0, inclined = 0;
|
||||
for (PlacedWater body : bodies) {
|
||||
float[] xs = body.pointsX();
|
||||
float[] zs = body.pointsZ();
|
||||
if (xs.length < 3) continue;
|
||||
if (body.pointsX().length < 3) continue;
|
||||
try {
|
||||
WaterPolygonFilter f = new WaterPolygonFilter(
|
||||
sa.getRootNode(), SUN_DIR, body.waterHeight(), xs, zs);
|
||||
f.setWaterColor(new ColorRGBA(0.05f, 0.25f, 0.55f, 1f));
|
||||
f.setDeepWaterColor(new ColorRGBA(0.02f, 0.12f, 0.30f, 1f));
|
||||
f.setWaterTransparency(0.15f);
|
||||
f.setMaxAmplitude(0.1f); // reduced to keep visual height close to waterHeight
|
||||
f.setWaveScale(0.008f);
|
||||
f.setSpeed(0.5f);
|
||||
float rad = (float) Math.toRadians(body.flowDegrees());
|
||||
f.setWindDirection(new Vector2f((float) Math.sin(rad), (float) Math.cos(rad)));
|
||||
fpp.addFilter(f);
|
||||
filters.add(f);
|
||||
log.info("Wasserfläche geladen: {} Punkte, h={}", xs.length, body.waterHeight());
|
||||
if (body.isFlat()) {
|
||||
buildFlatWater(body, assets);
|
||||
flat++;
|
||||
} else {
|
||||
buildInclinedWater(body, assets);
|
||||
inclined++;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("Fehler beim Laden einer Wasserfläche", e);
|
||||
}
|
||||
}
|
||||
log.info("{}/{} Wasserfläche(n) geladen.", filters.size(), bodies.size());
|
||||
log.info("{} flache + {} abschüssige Wasserfläche(n) geladen.", flat, inclined);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (!animatedMaterials.isEmpty()) {
|
||||
time += tpf;
|
||||
for (Material m : animatedMaterials) {
|
||||
m.setFloat("Time", time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
for (WaterPolygonFilter f : filters) fpp.removeFilter(f);
|
||||
filters.clear();
|
||||
for (Geometry g : inclinedGeos) rootNode.detachChild(g);
|
||||
inclinedGeos.clear();
|
||||
animatedMaterials.clear();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
// ── Flaches Wasser (WaterPolygonFilter) ───────────────────────────────────
|
||||
|
||||
private void buildFlatWater(PlacedWater body, AssetManager assets) {
|
||||
float[] xs = body.pointsX(), zs = body.pointsZ();
|
||||
WaterPolygonFilter f = new WaterPolygonFilter(
|
||||
rootNode, SUN_DIR, body.waterHeight(), xs, zs);
|
||||
f.setWaterColor(new ColorRGBA(body.waterColorR(), body.waterColorG(), body.waterColorB(), 1f));
|
||||
f.setDeepWaterColor(new ColorRGBA(body.deepColorR(), body.deepColorG(), body.deepColorB(), 1f));
|
||||
f.setWaterTransparency(body.transparency());
|
||||
f.setMaxAmplitude(body.waveAmplitude());
|
||||
f.setWaveScale(body.waveScale());
|
||||
f.setSpeed(body.speed());
|
||||
float rad = (float) Math.toRadians(body.flowDegrees());
|
||||
f.setWindDirection(new Vector2f(-(float) Math.sin(rad), -(float) Math.cos(rad)));
|
||||
fpp.addFilter(f);
|
||||
filters.add(f);
|
||||
}
|
||||
|
||||
// ── Abschüssiges Wasser (FlowingWater-Shader + Polygon-Mesh) ─────────────
|
||||
|
||||
private void buildInclinedWater(PlacedWater body, AssetManager assets) {
|
||||
Mesh mesh = buildPointBasedMesh(body);
|
||||
if (mesh == null) return;
|
||||
|
||||
Material mat = buildInclinedMaterial(body, assets);
|
||||
Geometry geo = new Geometry("inclined_water", mesh);
|
||||
geo.setMaterial(mat);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
rootNode.attachChild(geo);
|
||||
inclinedGeos.add(geo);
|
||||
if (mat.getParam("Time") != null) {
|
||||
animatedMaterials.add(mat);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut das Wasserfall-Mesh aus den gespeicherten Eckpunkt-Koordinaten.
|
||||
* Y wird zwischen Nachbar-Ecken linear interpoliert – kein Terrain-Raycast,
|
||||
* da vertikale Klippen-Faces von senkrechten Strahlen nicht zuverlässig
|
||||
* getroffen werden. Der Editor platziert die Punkte bereits korrekt auf der
|
||||
* Terrain-Oberfläche.
|
||||
*/
|
||||
private Mesh buildPointBasedMesh(PlacedWater body) {
|
||||
float[] xs = body.pointsX(), ys = body.pointsY(), zs = body.pointsZ();
|
||||
int n = xs.length;
|
||||
if (n < 3) return null;
|
||||
|
||||
// ── Rand tessellieren — Y linear zwischen Editor-Eckpunkten ──────────
|
||||
List<float[]> rim = new ArrayList<>();
|
||||
for (int i = 0; i < n; i++) {
|
||||
int j = (i + 1) % n;
|
||||
float ax = xs[i], ay = ys[i] + LIFT, az = zs[i];
|
||||
float bx = xs[j], by = ys[j] + LIFT, bz = zs[j];
|
||||
float dx = bx - ax, dy = by - ay, dz = bz - az;
|
||||
float horizLen = (float) Math.sqrt(dx * dx + dz * dz);
|
||||
int K = Math.max(1, (int) Math.ceil(horizLen / TESS_STEP));
|
||||
for (int k = 0; k < K; k++) {
|
||||
float t = (float) k / K;
|
||||
rim.add(new float[]{ ax + t * dx, ay + t * dy, az + t * dz });
|
||||
}
|
||||
}
|
||||
|
||||
int P = rim.size();
|
||||
if (P < 3) return null;
|
||||
|
||||
// ── Zentrum: Durchschnitt der Eckpunkte (nicht der tessellierten Rand-Punkte) ──
|
||||
float cx = 0, cy = 0, cz = 0;
|
||||
for (int i = 0; i < n; i++) { cx += xs[i]; cy += ys[i] + LIFT; cz += zs[i]; }
|
||||
cx /= n; cy /= n; cz /= n;
|
||||
|
||||
// ── Y-Range für UV (V=0 oben, V=1 unten → Shader scrollt bergab) ────
|
||||
float minY = cy, maxY = cy;
|
||||
for (float[] r : rim) {
|
||||
if (r[1] < minY) minY = r[1];
|
||||
if (r[1] > maxY) maxY = r[1];
|
||||
}
|
||||
float yRange = maxY - minY;
|
||||
if (yRange < 0.01f) yRange = 1f;
|
||||
|
||||
// ── Durchschnittliche Flächennormale ──────────────────────────────────
|
||||
Vector3f avgNorm = new Vector3f();
|
||||
for (int k = 0; k < P; k++) {
|
||||
float[] a = rim.get(k), b = rim.get((k + 1) % P);
|
||||
Vector3f e1 = new Vector3f(a[0] - cx, a[1] - cy, a[2] - cz);
|
||||
Vector3f e2 = new Vector3f(b[0] - cx, b[1] - cy, b[2] - cz);
|
||||
avgNorm.addLocal(e1.cross(e2));
|
||||
}
|
||||
if (avgNorm.lengthSquared() < 1e-6f) avgNorm.set(0f, 1f, 0f);
|
||||
else avgNorm.normalizeLocal();
|
||||
if (avgNorm.y < 0) avgNorm.negateLocal();
|
||||
|
||||
// ── Buffer befüllen ───────────────────────────────────────────────────
|
||||
int vertCount = P + 1;
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(vertCount * 3);
|
||||
FloatBuffer norm = BufferUtils.createFloatBuffer(vertCount * 3);
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(vertCount * 2);
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(P * 3);
|
||||
|
||||
pos.put(cx).put(cy).put(cz);
|
||||
norm.put(avgNorm.x).put(avgNorm.y).put(avgNorm.z);
|
||||
uv.put(cx * UV_SCALE).put((maxY - cy) / yRange);
|
||||
|
||||
for (float[] r : rim) {
|
||||
pos.put(r[0]).put(r[1]).put(r[2]);
|
||||
norm.put(avgNorm.x).put(avgNorm.y).put(avgNorm.z);
|
||||
uv.put(r[0] * UV_SCALE).put((maxY - r[1]) / yRange);
|
||||
}
|
||||
|
||||
for (int k = 0; k < P; k++) {
|
||||
idx.put(0).put(k + 1).put((k + 1) % P + 1);
|
||||
}
|
||||
|
||||
pos.rewind(); norm.rewind(); uv.rewind(); idx.rewind();
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.Normal, 3, norm);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound();
|
||||
mesh.updateCounts();
|
||||
log.debug("Wasserfall-Mesh: {} Rand-Punkte, Y {}-{}", P,
|
||||
String.format("%.1f", minY), String.format("%.1f", maxY));
|
||||
return mesh;
|
||||
}
|
||||
|
||||
// ── Material ──────────────────────────────────────────────────────────────
|
||||
|
||||
private Material buildInclinedMaterial(PlacedWater body, AssetManager assets) {
|
||||
Material mat;
|
||||
try {
|
||||
mat = new Material(assets, "MatDefs/FlowingWater.j3md");
|
||||
|
||||
Texture nm = loadTextureOr(assets,
|
||||
"Textures/internal/water/waterfall_normal.png",
|
||||
"Common/MatDefs/Water/Textures/water_normalmap.png");
|
||||
if (nm != null) {
|
||||
nm.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("NormalMap", nm);
|
||||
}
|
||||
|
||||
Texture diff = loadTextureOr(assets,
|
||||
"Textures/internal/water/waterfall_diffuse.png", null);
|
||||
if (diff != null) {
|
||||
diff.setWrap(Texture.WrapMode.Repeat);
|
||||
mat.setTexture("DiffuseMap", diff);
|
||||
}
|
||||
|
||||
ColorRGBA tint = new ColorRGBA(
|
||||
body.waterColorR(), body.waterColorG(), body.waterColorB(), body.transparency());
|
||||
mat.setColor("Tint", tint);
|
||||
mat.setFloat("UVScale", 1.0f);
|
||||
mat.setFloat("NormalUVScale", 0.5f);
|
||||
mat.setFloat("FlowSpeed", body.speed() * 1.5f);
|
||||
mat.setFloat("FoamAmount", 0.25f);
|
||||
mat.setFloat("Time", 0f);
|
||||
} catch (Exception e) {
|
||||
log.warn("FlowingWater-Material nicht ladbar, Fallback", e);
|
||||
mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(
|
||||
body.waterColorR(), body.waterColorG(), body.waterColorB(), body.transparency()));
|
||||
}
|
||||
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mat.getAdditionalRenderState().setDepthWrite(false);
|
||||
return mat;
|
||||
}
|
||||
|
||||
private static Texture loadTextureOr(AssetManager assets, String primary, String fallback) {
|
||||
try { return assets.loadTexture(primary); } catch (Exception ignored) {}
|
||||
if (fallback == null) return null;
|
||||
try { return assets.loadTexture(fallback); } catch (Exception e) {
|
||||
log.warn("Textur nicht ladbar: {} und {}", primary, fallback);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
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.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
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.VertexBuffer;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.PlacedWaterfall;
|
||||
import de.blight.common.WaterfallIO;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Rendert Wasserfall-Quads (4-Eckpunkt-Definitionen aus blight_waterfall.blwf).
|
||||
* UV V=0 oben, V=1 unten — FlowingWater-Shader scrollt bergab.
|
||||
*/
|
||||
public class WaterfallState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(WaterfallState.class);
|
||||
|
||||
private static final int ROWS = 24; // Tessellierungs-Zeilen vertikal
|
||||
private static final int COLS = 6; // Tessellierungs-Spalten horizontal
|
||||
private static final float BULGE_MAX = 1.2f; // Maximale horizontale Vorwölbung am oberen Rand (m)
|
||||
private static final float V_RANGE = 0.30f; // Anteil der Wasserfallhöhe für den Bogen (0..1)
|
||||
|
||||
private Node rootNode;
|
||||
private final List<Geometry> geos = new ArrayList<>();
|
||||
private final List<Material> materials = new ArrayList<>();
|
||||
private float time = 0f;
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
rootNode = ((SimpleApplication) app).getRootNode();
|
||||
AssetManager assets = app.getAssetManager();
|
||||
|
||||
List<PlacedWaterfall> list;
|
||||
try {
|
||||
list = WaterfallIO.load();
|
||||
} catch (Exception e) {
|
||||
log.error("Wasserfälle nicht ladbar", e);
|
||||
return;
|
||||
}
|
||||
if (list.isEmpty()) return;
|
||||
|
||||
for (PlacedWaterfall wf : list) {
|
||||
try {
|
||||
buildWaterfall(wf, assets);
|
||||
} catch (Exception e) {
|
||||
log.error("Fehler beim Aufbauen eines Wasserfalls", e);
|
||||
}
|
||||
}
|
||||
log.info("{} Wasserfall-Quad(s) geladen.", list.size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (!materials.isEmpty()) {
|
||||
time += tpf;
|
||||
for (Material m : materials) {
|
||||
m.setFloat("Time", time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application app) {
|
||||
for (Geometry g : geos) g.removeFromParent();
|
||||
geos.clear();
|
||||
materials.clear();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
private void buildWaterfall(PlacedWaterfall wf, AssetManager assets) {
|
||||
Mesh mesh = buildQuadMesh(wf);
|
||||
Material mat = buildMaterial(wf, assets);
|
||||
|
||||
Geometry geo = new Geometry("waterfall_quad", mesh);
|
||||
geo.setMaterial(mat);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
rootNode.attachChild(geo);
|
||||
geos.add(geo);
|
||||
materials.add(mat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tesselliertes Quad-Mesh aus 4 Eckpunkten.
|
||||
* Bilineare Interpolation zwischen A,B,C,D.
|
||||
* A=oben-links, B=oben-rechts, C=unten-rechts, D=unten-links.
|
||||
* Die oberen V_RANGE Anteile erhalten eine Viertel-Sinus-Vorwölbung (sofortige Wölbung an der
|
||||
* Oberkante, glatter Übergang bei V_RANGE), danach fällt das Mesh senkrecht mit BULGE_MAX-Offset.
|
||||
*/
|
||||
private static Mesh buildQuadMesh(PlacedWaterfall wf) {
|
||||
Vector3f a = new Vector3f(wf.ax(), wf.ay(), wf.az());
|
||||
Vector3f b = new Vector3f(wf.bx(), wf.by(), wf.bz());
|
||||
Vector3f c = new Vector3f(wf.cx(), wf.cy(), wf.cz());
|
||||
Vector3f d = new Vector3f(wf.dx(), wf.dy(), wf.dz());
|
||||
|
||||
int vRows = ROWS + 1, vCols = COLS + 1;
|
||||
int vertCount = vRows * vCols;
|
||||
int triCount = ROWS * COLS * 2;
|
||||
|
||||
// Referenz-Normale (nach außen zeigend) aus Winding A top-left, B top-right, D bottom-left
|
||||
Vector3f n1 = d.subtract(a).cross(b.subtract(a)).normalizeLocal();
|
||||
Vector3f n2 = b.subtract(c).cross(d.subtract(c)).normalizeLocal();
|
||||
Vector3f avgNorm = n1.add(n2).normalizeLocal();
|
||||
if (avgNorm.lengthSquared() < 1e-4f) avgNorm.set(0f, 0f, 1f);
|
||||
|
||||
// Horizontale Outward-Richtung für die Wölbung (Y=0, normiert)
|
||||
Vector3f outward = new Vector3f(avgNorm.x, 0f, avgNorm.z);
|
||||
if (outward.lengthSquared() < 1e-4f) outward.set(avgNorm); // Fallback für horizontale Fläche
|
||||
else outward.normalizeLocal();
|
||||
|
||||
// Pass 1: alle Vertex-Positionen mit Bulge berechnen und zwischenspeichern
|
||||
Vector3f[][] positions = new Vector3f[vRows][vCols];
|
||||
for (int row = 0; row < vRows; row++) {
|
||||
float v = (float) row / ROWS;
|
||||
// Viertel-Sinus: sofortige Wölbung an der Oberkante (Steigung max bei v=0),
|
||||
// Steigung=0 bei v=V_RANGE → glatter Übergang zum konstanten senkrechten Bereich.
|
||||
float bulgeVal = (v < V_RANGE)
|
||||
? BULGE_MAX * (float) Math.sin(Math.PI / 2f * v / V_RANGE)
|
||||
: BULGE_MAX;
|
||||
for (int col = 0; col < vCols; col++) {
|
||||
float u = (float) col / COLS;
|
||||
Vector3f top = a.mult(1f - u).add(b.mult(u));
|
||||
Vector3f bot = d.mult(1f - u).add(c.mult(u));
|
||||
Vector3f p = top.mult(1f - v).add(bot.mult(v));
|
||||
p.addLocal(outward.mult(bulgeVal));
|
||||
positions[row][col] = p;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: Normalen per Finite-Differenzen, Vorzeichen gegen avgNorm gesichert
|
||||
FloatBuffer pos = BufferUtils.createFloatBuffer(vertCount * 3);
|
||||
FloatBuffer norm = BufferUtils.createFloatBuffer(vertCount * 3);
|
||||
FloatBuffer uv = BufferUtils.createFloatBuffer(vertCount * 2);
|
||||
|
||||
for (int row = 0; row < vRows; row++) {
|
||||
for (int col = 0; col < vCols; col++) {
|
||||
Vector3f p = positions[row][col];
|
||||
pos.put(p.x).put(p.y).put(p.z);
|
||||
|
||||
Vector3f left = positions[row][Math.max(col - 1, 0)];
|
||||
Vector3f right = positions[row][Math.min(col + 1, vCols - 1)];
|
||||
Vector3f up = positions[Math.max(row - 1, 0)][col];
|
||||
Vector3f down = positions[Math.min(row + 1, vRows - 1)][col];
|
||||
|
||||
Vector3f dU = right.subtract(left);
|
||||
Vector3f dV = down.subtract(up);
|
||||
Vector3f n = dU.cross(dV).normalizeLocal();
|
||||
if (n.lengthSquared() < 1e-6f) n.set(avgNorm);
|
||||
if (n.dot(avgNorm) < 0f) n.negateLocal();
|
||||
|
||||
norm.put(n.x).put(n.y).put(n.z);
|
||||
uv.put((float) col / COLS).put((float) row / ROWS);
|
||||
}
|
||||
}
|
||||
|
||||
IntBuffer idx = BufferUtils.createIntBuffer(triCount * 3);
|
||||
for (int row = 0; row < ROWS; row++) {
|
||||
for (int col = 0; col < COLS; col++) {
|
||||
int i0 = row * vCols + col;
|
||||
int i1 = i0 + 1;
|
||||
int i2 = i0 + vCols;
|
||||
int i3 = i2 + 1;
|
||||
idx.put(i0).put(i2).put(i1);
|
||||
idx.put(i1).put(i2).put(i3);
|
||||
}
|
||||
}
|
||||
|
||||
pos.rewind(); norm.rewind(); uv.rewind(); idx.rewind();
|
||||
|
||||
Mesh mesh = new Mesh();
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3, pos);
|
||||
mesh.setBuffer(VertexBuffer.Type.Normal, 3, norm);
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, uv);
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3, idx);
|
||||
mesh.updateBound();
|
||||
mesh.updateCounts();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
private static Material buildMaterial(PlacedWaterfall wf, AssetManager assets) {
|
||||
Material mat;
|
||||
try {
|
||||
mat = new Material(assets, "MatDefs/FlowingWater.j3md");
|
||||
|
||||
Texture nm = loadTex(assets, "Textures/internal/water/waterfall_normal.png",
|
||||
"Common/MatDefs/Water/Textures/water_normalmap.png");
|
||||
if (nm != null) { nm.setWrap(Texture.WrapMode.Repeat); mat.setTexture("NormalMap", nm); }
|
||||
|
||||
Texture diff = loadTex(assets, "Textures/internal/water/waterfall_diffuse.png", null);
|
||||
if (diff != null) { diff.setWrap(Texture.WrapMode.Repeat); mat.setTexture("DiffuseMap", diff); }
|
||||
|
||||
mat.setColor("Tint", new ColorRGBA(wf.colorR(), wf.colorG(), wf.colorB(), wf.transparency()));
|
||||
mat.setFloat("UVScale", 2.0f);
|
||||
mat.setFloat("FlowSpeed", wf.speed());
|
||||
mat.setFloat("Time", 0f);
|
||||
} catch (Exception e) {
|
||||
log.warn("FlowingWater.j3md nicht ladbar, Fallback Unshaded", e);
|
||||
mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(wf.colorR(), wf.colorG(), wf.colorB(), wf.transparency()));
|
||||
}
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
|
||||
mat.getAdditionalRenderState().setDepthWrite(false);
|
||||
return mat;
|
||||
}
|
||||
|
||||
private static Texture loadTex(AssetManager assets, String primary, String fallback) {
|
||||
try { return assets.loadTexture(primary); } catch (Exception ignored) {}
|
||||
if (fallback == null) return null;
|
||||
try { return assets.loadTexture(fallback); } catch (Exception ignored) { return null; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user