Einmal den Fortschritt mit dem Wasser sichern

This commit is contained in:
2026-06-06 09:05:25 +02:00
parent d56f2ea41f
commit 7faed35287
21 changed files with 853 additions and 152 deletions

View File

@@ -3,10 +3,10 @@ package de.blight.common;
/**
* Serialisierbarer Zustand einer Blight-Weltkarte.
*
* Basis-Terrain : 4097 × 4097 Vertices (= 4096 × 4096 Zellen),
* 8 Welteinheiten pro Zelle → Welt 2048 .. +2048.
* Basis-Terrain : 16385 × 16385 Vertices (= 16384 × 16384 Zellen, 0,25 m/Vertex),
* Welt 2048 .. +2048 m.
* Obere Schicht : 513 × 513 Vertices (= 512 × 512 Zellen), gleiche Weltausdehnung.
* Splatmap : 513 × 513 Pixel (1:1 zu beiden Terrain-Grids).
* Splatmap : 2049 × 2049 Pixel (≈ 2 m/Pixel).
* Kanäle R/G/B/A = Gewicht für Tex1-Helligkeit / Tex2 / Tex3 / Tex4.
*/
public final class MapData {
@@ -14,7 +14,7 @@ public final class MapData {
// ── Terrain-Konstanten ────────────────────────────────────────────────────
/** Vertices pro Achse des Basis-Terrains (muss 2^n + 1 sein). */
public static final int TERRAIN_VERTS = 4097;
public static final int TERRAIN_VERTS = 16385;
// ── Upper-Layer-Konstanten ────────────────────────────────────────────────
@@ -26,8 +26,8 @@ public final class MapData {
// ── Splatmap-Konstanten ────────────────────────────────────────────────────
/** Pixel pro Achse der Splatmap (entspricht UPPER_VERTS = Spiel-Terrain-Auflösung). */
public static final int SPLAT_SIZE = 513;
/** Pixel pro Achse der Splatmap (≈ 2 m/Pixel bei 4096 m Weltkante). */
public static final int SPLAT_SIZE = 2049;
/** Anzahl konfigurierbarer Textur-Slots pro Terrain-Layer. */
public static final int TEXTURE_SLOTS = 4;

View File

@@ -22,6 +22,8 @@ import java.util.zip.*;
* 7 wie 6 + Gras-Texturpfad (UTF-String) + Gras-Standardhöhe (float)
* 8 wie 7 + Gras-Höhen-Map (SPLAT_SIZE² Bytes, 0=ungesetzt 1-255=0.1-10m)
* 9 wie 8 + Gras-Textur-Map (SPLAT_SIZE² Bytes) + Slots (N×UTF)
* 10 wie 9, aber TERRAIN_VERTS=16385 (0,25 m/Vertex) + SPLAT_SIZE=2049 (2 m/Pixel)
* Altes Format (v≤9) wird beim Laden bilinear hochskaliert.
*/
public final class MapIO {
@@ -53,7 +55,11 @@ public final class MapIO {
}
private static final int MAGIC = 0x424C4947; // "BLIG"
private static final int VERSION = 9;
private static final int VERSION = 10;
// Größen älterer Saves (v≤9) für Migrations-Upsampling
private static final int OLD_TERRAIN_VERTS = 4097;
private static final int OLD_SPLAT_SIZE = 513;
private MapIO() {}
@@ -134,36 +140,76 @@ public final class MapIO {
if (version < 1 || version > VERSION)
throw new IOException("Unbekannte Map-Version: " + version);
readFloats(in, data.terrainHeight);
readFloats(in, data.upperTop);
readFloats(in, data.upperBottom);
if (version >= 10) {
readFloats(in, data.terrainHeight);
readFloats(in, data.upperTop);
readFloats(in, data.upperBottom);
} else {
// v≤9: alte Größen lesen und bilinear hochskalieren
float[] oldH = new float[OLD_TERRAIN_VERTS * OLD_TERRAIN_VERTS];
float[] oldUT = new float[MapData.UPPER_VERTS * MapData.UPPER_VERTS];
float[] oldUB = new float[MapData.UPPER_VERTS * MapData.UPPER_VERTS];
readFloats(in, oldH);
upsampleFloats(oldH, OLD_TERRAIN_VERTS, data.terrainHeight, MapData.TERRAIN_VERTS);
readFloats(in, oldUT); System.arraycopy(oldUT, 0, data.upperTop, 0, oldUT.length);
readFloats(in, oldUB); System.arraycopy(oldUB, 0, data.upperBottom, 0, oldUB.length);
}
if (version <= 5) {
// v5 had upperHole[UPPER_CELLS²]; read and discard
in.skip((long) MapData.UPPER_CELLS * MapData.UPPER_CELLS);
}
if (version >= 2) {
in.readFully(data.splatR);
in.readFully(data.splatG);
in.readFully(data.splatB);
if (version >= 10) {
in.readFully(data.splatR); in.readFully(data.splatG); in.readFully(data.splatB);
} else {
byte[] oR = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oG = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oB = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
in.readFully(oR); in.readFully(oG); in.readFully(oB);
upsampleBytes(oR, OLD_SPLAT_SIZE, data.splatR, MapData.SPLAT_SIZE);
upsampleBytes(oG, OLD_SPLAT_SIZE, data.splatG, MapData.SPLAT_SIZE);
upsampleBytes(oB, OLD_SPLAT_SIZE, data.splatB, MapData.SPLAT_SIZE);
}
}
if (version >= 3) {
in.readFully(data.grassDensity);
if (version >= 10) {
in.readFully(data.grassDensity);
} else {
byte[] old = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
in.readFully(old);
upsampleBytes(old, OLD_SPLAT_SIZE, data.grassDensity, MapData.SPLAT_SIZE);
}
}
if (version >= 4) {
data.spawnX = in.readFloat();
data.spawnZ = in.readFloat();
}
if (version >= 5) {
in.readFully(data.splatA);
readStrings(in, data.terrainTextures);
in.readFully(data.upperSplatR);
in.readFully(data.upperSplatG);
in.readFully(data.upperSplatB);
in.readFully(data.upperSplatA);
readStrings(in, data.upperTextures);
if (version >= 10) {
in.readFully(data.splatA);
readStrings(in, data.terrainTextures);
in.readFully(data.upperSplatR); in.readFully(data.upperSplatG);
in.readFully(data.upperSplatB); in.readFully(data.upperSplatA);
readStrings(in, data.upperTextures);
} else {
byte[] oA = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oUR = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oUG = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oUB = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
byte[] oUA = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
in.readFully(oA);
readStrings(in, data.terrainTextures);
in.readFully(oUR); in.readFully(oUG); in.readFully(oUB); in.readFully(oUA);
readStrings(in, data.upperTextures);
upsampleBytes(oA, OLD_SPLAT_SIZE, data.splatA, MapData.SPLAT_SIZE);
upsampleBytes(oUR, OLD_SPLAT_SIZE, data.upperSplatR, MapData.SPLAT_SIZE);
upsampleBytes(oUG, OLD_SPLAT_SIZE, data.upperSplatG, MapData.SPLAT_SIZE);
upsampleBytes(oUB, OLD_SPLAT_SIZE, data.upperSplatB, MapData.SPLAT_SIZE);
upsampleBytes(oUA, OLD_SPLAT_SIZE, data.upperSplatA, MapData.SPLAT_SIZE);
}
} else {
// Ältere Maps: upperSplatR auf 255 initialisieren (Tex1 immer sichtbar)
java.util.Arrays.fill(data.upperSplatR, (byte) 255);
}
if (version >= 7) {
@@ -171,13 +217,25 @@ public final class MapIO {
data.grassDefaultHeight = in.readFloat();
}
if (version >= 8) {
in.readFully(data.grassHeightMap);
if (version >= 10) {
in.readFully(data.grassHeightMap);
} else {
byte[] old = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
in.readFully(old);
upsampleBytes(old, OLD_SPLAT_SIZE, data.grassHeightMap, MapData.SPLAT_SIZE);
}
}
if (version >= 9) {
int n = in.readInt();
data.grassTextureSlots = new String[n];
for (int i = 0; i < n; i++) data.grassTextureSlots[i] = in.readUTF();
in.readFully(data.grassTextureMap);
if (version >= 10) {
in.readFully(data.grassTextureMap);
} else {
byte[] old = new byte[OLD_SPLAT_SIZE*OLD_SPLAT_SIZE];
in.readFully(old);
upsampleBytes(old, OLD_SPLAT_SIZE, data.grassTextureMap, MapData.SPLAT_SIZE);
}
}
}
return data;
@@ -207,4 +265,39 @@ public final class MapIO {
int len = in.readInt();
for (int i = 0; i < len && i < arr.length; i++) arr[i] = in.readUTF();
}
private static void upsampleFloats(float[] src, int srcSize, float[] dst, int dstSize) {
float scale = (float)(srcSize - 1) / (dstSize - 1);
for (int dz = 0; dz < dstSize; dz++) {
float sz = dz * scale;
int sz0 = Math.min((int)sz, srcSize - 2), sz1 = sz0 + 1;
float fz = sz - sz0;
for (int dx = 0; dx < dstSize; dx++) {
float sx = dx * scale;
int sx0 = Math.min((int)sx, srcSize - 2), sx1 = sx0 + 1;
float fx = sx - sx0;
dst[dz*dstSize+dx] =
(src[sz0*srcSize+sx0]*(1-fx) + src[sz0*srcSize+sx1]*fx)*(1-fz)
+ (src[sz1*srcSize+sx0]*(1-fx) + src[sz1*srcSize+sx1]*fx)*fz;
}
}
}
private static void upsampleBytes(byte[] src, int srcSize, byte[] dst, int dstSize) {
float scale = (float)(srcSize - 1) / (dstSize - 1);
for (int dz = 0; dz < dstSize; dz++) {
float sz = dz * scale;
int sz0 = Math.min((int)sz, srcSize - 2), sz1 = sz0 + 1;
float fz = sz - sz0;
for (int dx = 0; dx < dstSize; dx++) {
float sx = dx * scale;
int sx0 = Math.min((int)sx, srcSize - 2), sx1 = sx0 + 1;
float fx = sx - sx0;
float v =
((src[sz0*srcSize+sx0]&0xFF)*(1-fx) + (src[sz0*srcSize+sx1]&0xFF)*fx)*(1-fz)
+ ((src[sz1*srcSize+sx0]&0xFF)*(1-fx) + (src[sz1*srcSize+sx1]&0xFF)*fx)*fz;
dst[dz*dstSize+dx] = (byte)Math.round(v);
}
}
}
}