Weiter an der Karte gearbeitet - hier Höhlen und Tunnel
This commit is contained in:
@@ -708,6 +708,56 @@ public final class WorldMapRenderer {
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest alle VoxelChunks von Disk und baut daraus das voxelSurface-Array
|
||||
* (oberste solide Voxel-Y pro VOXEL_SURFACE_RES-Zelle; NaN = kein Voxel).
|
||||
* Aktualisiert dabei mapData.upperTop auf dem UPPER_VERTS-Raster als Nebeneffekt.
|
||||
*/
|
||||
public static float[] bakeVoxelSurface(MapData mapData) {
|
||||
int VS = VOXEL_SURFACE_RES;
|
||||
float[] voxelSurface = new float[VS * VS];
|
||||
java.util.Arrays.fill(voxelSurface, Float.NaN);
|
||||
List<VoxelChunk> chunks;
|
||||
try {
|
||||
chunks = VoxelChunkIO.loadAll();
|
||||
} catch (Exception e) {
|
||||
log.warn("[WorldMap] VoxelChunks nicht ladbar: {}", e.getMessage());
|
||||
return voxelSurface;
|
||||
}
|
||||
if (chunks.isEmpty()) { return voxelSurface; }
|
||||
int UV = MapData.UPPER_VERTS;
|
||||
for (VoxelChunk chunk : chunks) {
|
||||
if (chunk.isEmpty()) { continue; }
|
||||
for (int lz = 0; lz < VoxelChunk.SIZE; lz++) {
|
||||
float worldZ = VoxelChunk.toWorldZ(chunk.cz, lz);
|
||||
int vsPz = Math.round((worldZ + WORLD_HALF) / WORLD_SIZE * (VS - 1));
|
||||
if (vsPz < 0 || vsPz >= VS) { continue; }
|
||||
int uz = Math.round((worldZ + WORLD_HALF) / WORLD_SIZE * (UV - 1));
|
||||
for (int lx = 0; lx < VoxelChunk.SIZE; lx++) {
|
||||
float worldX = VoxelChunk.toWorldX(chunk.cx, lx);
|
||||
int vsPx = Math.round((worldX + WORLD_HALF) / WORLD_SIZE * (VS - 1));
|
||||
if (vsPx < 0 || vsPx >= VS) { continue; }
|
||||
int ux = Math.round((worldX + WORLD_HALF) / WORLD_SIZE * (UV - 1));
|
||||
for (int ly = VoxelChunk.SIZE - 1; ly >= 0; ly--) {
|
||||
if (chunk.getDensity(lx, ly, lz) > 0) {
|
||||
float topY = VoxelChunk.toWorldY(chunk.cy, ly);
|
||||
int vsIdx = vsPz * VS + vsPx;
|
||||
if (Float.isNaN(voxelSurface[vsIdx]) || topY > voxelSurface[vsIdx]) {
|
||||
voxelSurface[vsIdx] = topY;
|
||||
}
|
||||
if (mapData != null && uz >= 0 && uz < UV && ux >= 0 && ux < UV) {
|
||||
int uvIdx = uz * UV + ux;
|
||||
if (topY > mapData.upperTop[uvIdx]) { mapData.upperTop[uvIdx] = topY; }
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return voxelSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet eine Hillshading-Karte (float pro Pixel, 0.55–1.35) aus reinen Terrain-Höhen.
|
||||
* Voxels werden NICHT eingerechnet, um Artefakte an Voxel-Kanten zu vermeiden.
|
||||
@@ -1181,6 +1231,184 @@ public final class WorldMapRenderer {
|
||||
*
|
||||
* @return Anzahl neu geschriebener Chunks (0 = alle aus Cache geladen)
|
||||
*/
|
||||
/**
|
||||
* Berechnet die visuelle Footprint-Maske für die Höhlenkarte.
|
||||
*
|
||||
* Zeigt alle Pixel wo Voxel-Material deutlich über dem Terrain liegt
|
||||
* (Kliff-Wände + Tunnel-Decke) ODER wo ein offener Tunnel-Eingang ist
|
||||
* (Boden solid, kein Dach, Void startet nahe am Boden).
|
||||
*
|
||||
* Kriterium A: voxelSurface > Terrain + 1.5m → Kliff-Wände + Decken
|
||||
* Kriterium B∧C: hasSolid ∧ Void∧ voidStartWY ≤ 8 → offene Eingänge
|
||||
* Ergebnis: A ∨ (B∧C)
|
||||
*/
|
||||
public static boolean[] buildCaveFootprintMask(MapData mapData, int size) {
|
||||
return buildCaveMasks(mapData, size, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Berechnet die Zugänglichkeits-Maske für Tunnel-Eingangs-Erkennung.
|
||||
*
|
||||
* Markiert nur zugängliche Bereiche (Hohlräume), NICHT solide Kliff-Wände.
|
||||
* So findet Two-Farthest-Points die echten Tunnel-Eingänge, nicht den Kliff-Rand.
|
||||
*
|
||||
* Kriterium B∧(C∨A): hasSolid ∧ Void ∧ (voidStartWY≤8 ∨ voxelSurface>Terrain+1.5)
|
||||
* → Eingangs-Korridore (niedrige voidStart) + Tunnel-Inneres (hohe voxelSurface),
|
||||
* aber NICHT solide Wände (kein Void) und NICHT Terrain-Peaks (voxelSurface≈Terrain).
|
||||
*/
|
||||
public static boolean[] buildCaveTunnelMask(MapData mapData, int size) {
|
||||
return buildCaveMasks(mapData, size, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sandwich-Erkennung als Seed: Boden + Hohlraum (≥ MIN_VOID_VOXELS) + Decke.
|
||||
* Von diesen Seeds wächst die Maske per BFS durch alle Spalten mit irgendwelchem
|
||||
* Voxel-Solid (Kliffwände, Tunnelboden). Abschließend 1px Dilation.
|
||||
*/
|
||||
private static boolean[] buildCaveMasks(MapData mapData, int size, boolean tunnelOnly) {
|
||||
final int MIN_VOID_VOXELS = 3; // Mindest-Hohlraumhöhe für echten Sandwich
|
||||
|
||||
boolean[] seeds = new boolean[size * size]; // Sandwich-Treffer
|
||||
boolean[] solidPresent = new boolean[size * size]; // irgendwo d>0 in der Spalte
|
||||
|
||||
List<VoxelChunk> allChunks;
|
||||
try {
|
||||
allChunks = VoxelChunkIO.loadAll();
|
||||
} catch (Exception e) {
|
||||
log.warn("[CaveMap] VoxelChunks nicht ladbar: {}", e.getMessage());
|
||||
return seeds;
|
||||
}
|
||||
if (allChunks.isEmpty()) return seeds;
|
||||
|
||||
for (VoxelChunk chunk : allChunks) {
|
||||
if (chunk.cy != 0) continue;
|
||||
for (int lz = 0; lz < VoxelChunk.SIZE; lz++) {
|
||||
float worldZ = VoxelChunk.toWorldZ(chunk.cz, lz);
|
||||
int pz = Math.round((worldZ + WORLD_HALF) / WORLD_SIZE * (size - 1));
|
||||
if (pz < 0 || pz >= size) continue;
|
||||
for (int lx = 0; lx < VoxelChunk.SIZE; lx++) {
|
||||
float worldX = VoxelChunk.toWorldX(chunk.cx, lx);
|
||||
int px = Math.round((worldX + WORLD_HALF) / WORLD_SIZE * (size - 1));
|
||||
if (px < 0 || px >= size) continue;
|
||||
int idx = pz * size + px;
|
||||
|
||||
// Sandwich-Scan + solid-present in einem Durchlauf
|
||||
int phase = 0, voidCount = 0;
|
||||
for (int ly = 0; ly < VoxelChunk.SIZE; ly++) {
|
||||
byte d = chunk.getDensity(lx, ly, lz);
|
||||
if (d > 0) solidPresent[idx] = true;
|
||||
if (seeds[idx]) continue; // bereits gefunden, trotzdem solidPresent weiter scannen
|
||||
if (phase == 0) {
|
||||
if (d > 0) phase = 1;
|
||||
} else if (phase == 1) {
|
||||
if (d < 0) { phase = 2; voidCount = 1; }
|
||||
} else {
|
||||
if (d < 0) {
|
||||
voidCount++;
|
||||
} else if (d > 0) {
|
||||
if (voidCount >= MIN_VOID_VOXELS) { seeds[idx] = true; }
|
||||
phase = 1; voidCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bounding-Box der Seeds + Margin → BFS bleibt lokal, springt nicht zu fremden Voxelgebieten
|
||||
int bbMinX = size, bbMaxX = -1, bbMinZ = size, bbMaxZ = -1;
|
||||
for (int i = 0; i < size * size; i++) {
|
||||
if (!seeds[i]) continue;
|
||||
int spx = i % size, spz = i / size;
|
||||
if (spx < bbMinX) bbMinX = spx; if (spx > bbMaxX) bbMaxX = spx;
|
||||
if (spz < bbMinZ) bbMinZ = spz; if (spz > bbMaxZ) bbMaxZ = spz;
|
||||
}
|
||||
if (bbMaxX < 0) return seeds; // keine Seeds → leere Maske
|
||||
final int MARGIN = 20; // 20px * 8m/px = 160m Puffer rund um den Tunnel
|
||||
bbMinX = Math.max(0, bbMinX - MARGIN); bbMaxX = Math.min(size - 1, bbMaxX + MARGIN);
|
||||
bbMinZ = Math.max(0, bbMinZ - MARGIN); bbMaxZ = Math.min(size - 1, bbMaxZ + MARGIN);
|
||||
|
||||
// BFS von Sandwich-Seeds durch Spalten mit irgendwelchem Voxel-Solid (begrenzt auf Bounding-Box)
|
||||
boolean[] mask = seeds.clone();
|
||||
boolean[] visited = seeds.clone();
|
||||
java.util.ArrayDeque<Integer> queue = new java.util.ArrayDeque<>();
|
||||
for (int i = 0; i < size * size; i++) {
|
||||
if (seeds[i]) queue.add(i);
|
||||
}
|
||||
final int[] dx4 = {1, -1, 0, 0};
|
||||
final int[] dz4 = {0, 0, 1, -1};
|
||||
while (!queue.isEmpty()) {
|
||||
int cur = queue.poll();
|
||||
int cpx = cur % size, cpz = cur / size;
|
||||
for (int d = 0; d < 4; d++) {
|
||||
int nx = cpx + dx4[d], nz = cpz + dz4[d];
|
||||
if (nx < bbMinX || nx > bbMaxX || nz < bbMinZ || nz > bbMaxZ) continue;
|
||||
int nIdx = nz * size + nx;
|
||||
if (visited[nIdx] || !solidPresent[nIdx]) continue;
|
||||
visited[nIdx] = true;
|
||||
mask[nIdx] = true;
|
||||
queue.add(nIdx);
|
||||
}
|
||||
}
|
||||
|
||||
// 1px Dilation: Randbereich direkt an den Kliffwänden mit einschließen
|
||||
boolean[] dilated = new boolean[size * size];
|
||||
for (int pz = 0; pz < size; pz++) {
|
||||
for (int px = 0; px < size; px++) {
|
||||
if (!mask[pz * size + px]) continue;
|
||||
for (int d = 0; d < 4; d++) {
|
||||
int nx = px + dx4[d], nz = pz + dz4[d];
|
||||
if (nx < 0 || nx >= size || nz < 0 || nz >= size) continue;
|
||||
dilated[nz * size + nx] = true;
|
||||
}
|
||||
dilated[pz * size + px] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Kleine isolierte Reste entfernen (≥40px behalten)
|
||||
final int MIN_FOOTPRINT_PX = 40;
|
||||
boolean[] vis2 = new boolean[size * size];
|
||||
boolean[] cleaned = new boolean[size * size];
|
||||
for (int start = 0; start < size * size; start++) {
|
||||
if (!dilated[start] || vis2[start]) continue;
|
||||
java.util.ArrayDeque<Integer> q2 = new java.util.ArrayDeque<>();
|
||||
q2.add(start); vis2[start] = true;
|
||||
java.util.List<Integer> comp = new java.util.ArrayList<>();
|
||||
while (!q2.isEmpty()) {
|
||||
int cur = q2.poll(); comp.add(cur);
|
||||
int cpx = cur % size, cpz = cur / size;
|
||||
for (int d = 0; d < 4; d++) {
|
||||
int nx = cpx + dx4[d], nz = cpz + dz4[d];
|
||||
if (nx < 0 || nx >= size || nz < 0 || nz >= size) continue;
|
||||
int nIdx = nz * size + nx;
|
||||
if (dilated[nIdx] && !vis2[nIdx]) { vis2[nIdx] = true; q2.add(nIdx); }
|
||||
}
|
||||
}
|
||||
if (comp.size() >= MIN_FOOTPRINT_PX) {
|
||||
for (int idx : comp) cleaned[idx] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert eine schematische Höhlen/Tunnel-Karte aus den VoxelChunks.
|
||||
* Tunnelbereiche werden als Erdfarbe dargestellt, alles andere fast schwarz.
|
||||
*/
|
||||
public static BufferedImage buildCaveMap(MapData mapData, int targetSize) {
|
||||
boolean[] mask = buildCaveFootprintMask(mapData, targetSize);
|
||||
int bgPacked = 0x1c1c1c; // fast schwarz (Gestein/Außen)
|
||||
int floorPacked = 0xb68a50; // Dirt-Farbe für Tunnelboden
|
||||
BufferedImage img = new BufferedImage(targetSize, targetSize, BufferedImage.TYPE_INT_RGB);
|
||||
int[] pixels = ((java.awt.image.DataBufferInt) img.getRaster().getDataBuffer()).getData();
|
||||
java.util.Arrays.fill(pixels, bgPacked);
|
||||
for (int i = 0; i < mask.length; i++) {
|
||||
if (mask[i]) pixels[i] = floorPacked;
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
public static int buildAndSaveHiresChunks(MapData m, int[] slotColors, int[][] tilePixels,
|
||||
float[] voxelSurface, List<PlacedWater> waters,
|
||||
List<PlacedWaterfall> waterfalls,
|
||||
|
||||
Reference in New Issue
Block a user