Wasserfall System eingebaut

This commit is contained in:
2026-08-22 11:37:51 +02:00
parent 528b2ea576
commit 6ee404fc2c
58 changed files with 2548 additions and 534 deletions

View File

@@ -1,6 +1,86 @@
package de.blight.common;
import java.util.Arrays;
/**
* Platzierte Wasserfläche, definiert durch ein Benutzer-Polygon, eine Höhe und eine Fließrichtung.
* Platzierte Wasserfläche: Polygon, Höhe, Fließrichtung und Rendering-Parameter.
* pointsY enthält pro Ecke eine individuelle Y-Höhe; sind alle gleich waterHeight
* ist die Fläche flach (WaterPolygonFilter), sonst abschüssig (FlowingWater-Shader).
*/
public record PlacedWater(float[] pointsX, float[] pointsZ, float waterHeight, float flowDegrees) {}
public record PlacedWater(
float[] pointsX,
float[] pointsY, // pro Ecke; Länge == pointsX.length
float[] pointsZ,
float waterHeight,
float flowDegrees,
// Rendering-Parameter
float speed,
float waveScale,
float waveAmplitude,
float transparency,
float waterColorR, float waterColorG, float waterColorB,
float deepColorR, float deepColorG, float deepColorB
) {
public static final float DEF_SPEED = 0.5f;
public static final float DEF_WAVE_SCALE = 0.008f;
public static final float DEF_WAVE_AMPLITUDE = 0.1f;
public static final float DEF_TRANSPARENCY = 0.15f;
public static final float DEF_WATER_R = 0.05f, DEF_WATER_G = 0.25f, DEF_WATER_B = 0.55f;
public static final float DEF_DEEP_R = 0.02f, DEF_DEEP_G = 0.12f, DEF_DEEP_B = 0.30f;
/** True wenn die Y-Spanne aller Eckpunkte unter 0.5 m liegt (flaches Wasser). */
public boolean isFlat() {
float min = pointsY[0], max = pointsY[0];
for (float y : pointsY) {
if (y < min) min = y;
if (y > max) max = y;
}
return (max - min) < 0.5f;
}
/** Erstellt eine PlacedWater mit Standard-Rendering-Parametern und flacher Höhe. */
public static PlacedWater of(float[] xs, float[] zs, float h, float flow) {
float[] ys = new float[xs.length];
Arrays.fill(ys, h);
return new PlacedWater(xs, ys, zs, h, flow,
DEF_SPEED, DEF_WAVE_SCALE, DEF_WAVE_AMPLITUDE, DEF_TRANSPARENCY,
DEF_WATER_R, DEF_WATER_G, DEF_WATER_B,
DEF_DEEP_R, DEF_DEEP_G, DEF_DEEP_B);
}
/** Gibt eine Kopie mit neuen Rendering-Parametern zurück. */
public PlacedWater withParams(float speed, float waveScale, float waveAmplitude,
float transparency,
float wr, float wg, float wb,
float dr, float dg, float db) {
return new PlacedWater(pointsX, pointsY, pointsZ, waterHeight, flowDegrees,
speed, waveScale, waveAmplitude, transparency,
wr, wg, wb, dr, dg, db);
}
/** Gibt eine Kopie mit neuer globaler Höhe zurück; setzt alle pointsY auf newHeight (flacht ab). */
public PlacedWater withHeight(float newHeight) {
float[] ys = new float[pointsX.length];
Arrays.fill(ys, newHeight);
return new PlacedWater(pointsX, ys, pointsZ, newHeight, flowDegrees,
speed, waveScale, waveAmplitude, transparency,
waterColorR, waterColorG, waterColorB,
deepColorR, deepColorG, deepColorB);
}
/** Gibt eine Kopie mit neuer Fließrichtung zurück. */
public PlacedWater withFlow(float newDegrees) {
return new PlacedWater(pointsX, pointsY, pointsZ, waterHeight, newDegrees,
speed, waveScale, waveAmplitude, transparency,
waterColorR, waterColorG, waterColorB,
deepColorR, deepColorG, deepColorB);
}
/** Gibt eine Kopie mit neuen Eckpunkten (inkl. per-Vertex-Y) zurück. */
public PlacedWater withPoints(float[] xs, float[] ys, float[] zs) {
return new PlacedWater(xs, ys, zs, waterHeight, flowDegrees,
speed, waveScale, waveAmplitude, transparency,
waterColorR, waterColorG, waterColorB,
deepColorR, deepColorG, deepColorB);
}
}

View File

@@ -0,0 +1,20 @@
package de.blight.common;
/**
* Wasserfall-Quad: definiert durch 4 explizite 3D-Eckpunkte.
* Reihenfolge: A=oben-links, B=oben-rechts, C=unten-rechts, D=unten-links.
* UV scrollt von V=0 (oben) nach V=1 (unten), Wasser fließt bergab.
*/
public record PlacedWaterfall(
float ax, float ay, float az,
float bx, float by, float bz,
float cx, float cy, float cz,
float dx, float dy, float dz,
float speed,
float transparency,
float colorR, float colorG, float colorB
) {
public static final float DEF_SPEED = 1.5f;
public static final float DEF_TRANSPARENCY = 0.75f;
public static final float DEF_R = 0.35f, DEF_G = 0.55f, DEF_B = 0.75f;
}

View File

@@ -5,9 +5,14 @@ import java.nio.file.*;
import java.util.*;
/**
* Liest und schreibt platzierte Wasserflächen ({@code blight_water.blw}) neben der Kartendatei.
* Liest und schreibt platzierte Wasserflächen ({@code blight_water.blw}).
*
* Format (je Zeile): x1,z1;x2,z2;x3,z3;... TAB waterHeight [TAB flowDegrees]
* Format (tab-getrennt):
* polygon_points waterHeight flowDegrees speed waveScale waveAmplitude transparency
* waterR waterG waterB deepR deepG deepB
*
* polygon_points: Semikolon-getrennte Tripel "x,y,z" (neu) oder Paare "x,z" (alt, Y = waterHeight).
* Alle Felder ab flowDegrees sind optional (rückwärtskompatibel).
*/
public final class WaterBodyIO {
@@ -21,18 +26,28 @@ public final class WaterBodyIO {
Path p = getPath();
Files.createDirectories(p.getParent());
try (BufferedWriter w = Files.newBufferedWriter(p)) {
w.write("# polygon_points\twaterHeight\tflowDegrees");
w.write("# polygon_points\twaterHeight\tflowDegrees\tspeed\twaveScale\twaveAmplitude" +
"\ttransparency\twaterR\twaterG\twaterB\tdeepR\tdeepG\tdeepB");
w.newLine();
for (PlacedWater b : bodies) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < b.pointsX().length; i++) {
if (i > 0) sb.append(';');
sb.append(String.format(Locale.ROOT, "%.5f,%.5f", b.pointsX()[i], b.pointsZ()[i]));
sb.append(String.format(Locale.ROOT, "%.5f,%.5f,%.5f",
b.pointsX()[i], b.pointsY()[i], b.pointsZ()[i]));
}
sb.append('\t');
sb.append(String.format(Locale.ROOT, "%.5f", b.waterHeight()));
sb.append('\t');
sb.append(String.format(Locale.ROOT, "%.1f", b.flowDegrees()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.waterHeight()));
sb.append('\t').append(String.format(Locale.ROOT, "%.1f", b.flowDegrees()));
sb.append('\t').append(String.format(Locale.ROOT, "%.4f", b.speed()));
sb.append('\t').append(String.format(Locale.ROOT, "%.6f", b.waveScale()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.waveAmplitude()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.transparency()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.waterColorR()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.waterColorG()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.waterColorB()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.deepColorR()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.deepColorG()));
sb.append('\t').append(String.format(Locale.ROOT, "%.5f", b.deepColorB()));
w.write(sb.toString());
w.newLine();
}
@@ -46,20 +61,42 @@ public final class WaterBodyIO {
for (String line : Files.readAllLines(p)) {
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
String[] parts = line.split("\t", -1);
if (parts.length < 2) continue;
String[] cols = line.split("\t", -1);
if (cols.length < 2) continue;
try {
float wh = Float.parseFloat(parts[1].strip());
float fd = parts.length >= 3 ? Float.parseFloat(parts[2].strip()) : 0f;
String[] pts = parts[0].split(";", -1);
float wh = Float.parseFloat(cols[1].strip());
float flow = cols.length >= 3 ? Float.parseFloat(cols[2].strip()) : 0f;
float spd = cols.length >= 4 ? Float.parseFloat(cols[3].strip()) : PlacedWater.DEF_SPEED;
float ws = cols.length >= 5 ? Float.parseFloat(cols[4].strip()) : PlacedWater.DEF_WAVE_SCALE;
float wa = cols.length >= 6 ? Float.parseFloat(cols[5].strip()) : PlacedWater.DEF_WAVE_AMPLITUDE;
float tr = cols.length >= 7 ? Float.parseFloat(cols[6].strip()) : PlacedWater.DEF_TRANSPARENCY;
float wr = cols.length >= 8 ? Float.parseFloat(cols[7].strip()) : PlacedWater.DEF_WATER_R;
float wg = cols.length >= 9 ? Float.parseFloat(cols[8].strip()) : PlacedWater.DEF_WATER_G;
float wb2 = cols.length >= 10? Float.parseFloat(cols[9].strip()) : PlacedWater.DEF_WATER_B;
float dr = cols.length >= 11? Float.parseFloat(cols[10].strip()): PlacedWater.DEF_DEEP_R;
float dg = cols.length >= 12? Float.parseFloat(cols[11].strip()): PlacedWater.DEF_DEEP_G;
float db = cols.length >= 13? Float.parseFloat(cols[12].strip()): PlacedWater.DEF_DEEP_B;
String[] pts = cols[0].split(";", -1);
float[] xs = new float[pts.length];
float[] ys = new float[pts.length];
float[] zs = new float[pts.length];
for (int i = 0; i < pts.length; i++) {
String[] c = pts[i].split(",", -1);
xs[i] = Float.parseFloat(c[0].strip());
zs[i] = Float.parseFloat(c[1].strip());
if (c.length >= 3) {
// neues Format: x,y,z
ys[i] = Float.parseFloat(c[1].strip());
zs[i] = Float.parseFloat(c[2].strip());
} else {
// altes Format: x,z → Y = waterHeight
ys[i] = wh;
zs[i] = Float.parseFloat(c[1].strip());
}
}
if (xs.length >= 3) {
list.add(new PlacedWater(xs, ys, zs, wh, flow, spd, ws, wa, tr, wr, wg, wb2, dr, dg, db));
}
if (xs.length >= 3) list.add(new PlacedWater(xs, zs, wh, fd));
} catch (NumberFormatException | ArrayIndexOutOfBoundsException ignored) {}
}
return list;

View File

@@ -0,0 +1,74 @@
package de.blight.common;
import java.io.*;
import java.nio.file.*;
import java.util.*;
/**
* Liest und schreibt Wasserfall-Quads ({@code blight_waterfall.blwf}).
*
* Format (tab-getrennt):
* ax,ay,az bx,by,bz cx,cy,cz dx,dy,dz speed transparency r g b
*/
public final class WaterfallIO {
private WaterfallIO() {}
public static Path getPath() {
return MapIO.getMapPath().resolveSibling("blight_waterfall.blwf");
}
public static void save(List<PlacedWaterfall> list) throws IOException {
Path p = getPath();
Files.createDirectories(p.getParent());
try (BufferedWriter w = Files.newBufferedWriter(p)) {
w.write("# ax,ay,az\tbx,by,bz\tcx,cy,cz\tdx,dy,dz\tspeed\ttransparency\tr\tg\tb");
w.newLine();
for (PlacedWaterfall wf : list) {
w.write(String.format(Locale.ROOT,
"%.5f,%.5f,%.5f\t%.5f,%.5f,%.5f\t%.5f,%.5f,%.5f\t%.5f,%.5f,%.5f\t%.4f\t%.5f\t%.5f\t%.5f\t%.5f",
wf.ax(), wf.ay(), wf.az(),
wf.bx(), wf.by(), wf.bz(),
wf.cx(), wf.cy(), wf.cz(),
wf.dx(), wf.dy(), wf.dz(),
wf.speed(), wf.transparency(),
wf.colorR(), wf.colorG(), wf.colorB()));
w.newLine();
}
}
}
public static List<PlacedWaterfall> load() throws IOException {
Path p = getPath();
if (!Files.exists(p)) return List.of();
List<PlacedWaterfall> result = new ArrayList<>();
for (String line : Files.readAllLines(p)) {
line = line.strip();
if (line.isEmpty() || line.startsWith("#")) continue;
try {
String[] cols = line.split("\t", -1);
float[] a = parseVec(cols[0]);
float[] b = parseVec(cols[1]);
float[] c = parseVec(cols[2]);
float[] d = parseVec(cols[3]);
float speed = cols.length > 4 ? Float.parseFloat(cols[4].strip()) : PlacedWaterfall.DEF_SPEED;
float transp = cols.length > 5 ? Float.parseFloat(cols[5].strip()) : PlacedWaterfall.DEF_TRANSPARENCY;
float r = cols.length > 6 ? Float.parseFloat(cols[6].strip()) : PlacedWaterfall.DEF_R;
float g = cols.length > 7 ? Float.parseFloat(cols[7].strip()) : PlacedWaterfall.DEF_G;
float bv = cols.length > 8 ? Float.parseFloat(cols[8].strip()) : PlacedWaterfall.DEF_B;
result.add(new PlacedWaterfall(
a[0], a[1], a[2], b[0], b[1], b[2],
c[0], c[1], c[2], d[0], d[1], d[2],
speed, transp, r, g, bv));
} catch (Exception ignored) {}
}
return result;
}
private static float[] parseVec(String s) {
String[] p = s.strip().split(",", -1);
return new float[]{ Float.parseFloat(p[0].strip()),
Float.parseFloat(p[1].strip()),
Float.parseFloat(p[2].strip()) };
}
}