Karte: Pfade/Gebäude/Areas nach Oberfläche vs. Untergrund trennen
Pfad-Ketten werden exakt an Tunnel-Grenzen per Bisektion aufgesplittet (12 Iterationen ≈ 0.05m Genauigkeit). Gebäude und Areas werden anhand des Footprint-/Schwerpunkt-Punkts der Cave-Maske zugeordnet. - WorldMapView: zeigt nur Oberflächen-Segmente (splitChains[0]) - CaveMapView: zeigt nur Tunnel-Segmente (splitChains[1]) - Cave-Maske: buildCaveTunnelMask bei 1024px, einmal pro Lade-Zyklus Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -198,21 +198,27 @@ public class CaveMapView extends VBox {
|
||||
|
||||
Thread t = new Thread(() -> {
|
||||
try {
|
||||
MapData mapData = MapIO.load();
|
||||
List<PlacedArea> areas = AreaIO.load();
|
||||
MapData mapData = MapIO.load();
|
||||
List<PlacedArea> areas = AreaIO.load();
|
||||
List<PlacedModel> models = PlacedModelIO.load();
|
||||
|
||||
BufferedImage bi = WorldMapRenderer.buildCaveMap(mapData, RENDER_SIZE);
|
||||
List<float[][]> paths = WorldMapView.loadPathChains();
|
||||
List<float[]> entrances = WorldMapView.computeTunnelEntrances(mapData);
|
||||
List<float[]> footprints = WorldMapView.computeBuildingFootprintsSimple(models);
|
||||
BufferedImage bi = WorldMapRenderer.buildCaveMap(mapData, RENDER_SIZE);
|
||||
List<float[][]> allPaths = WorldMapView.loadPathChains();
|
||||
List<float[]> entrances = WorldMapView.computeTunnelEntrances(mapData);
|
||||
List<float[]> allFootprints = WorldMapView.computeBuildingFootprintsSimple(models);
|
||||
|
||||
boolean[] caveMask = WorldMapView.loadCaveMask(mapData);
|
||||
List<float[][]>[] splitChains = WorldMapView.splitPathChains(allPaths, caveMask);
|
||||
List<float[][]> cavePaths = splitChains[1]; // nur Tunnel-Segmente
|
||||
List<float[]> caveFootprints = WorldMapView.filterBuildingsByCave(allFootprints, caveMask, true);
|
||||
List<PlacedArea> caveAreas = WorldMapView.filterAreasByCave(new ArrayList<>(areas), caveMask, true);
|
||||
|
||||
Platform.runLater(() -> {
|
||||
mapFxImage = SwingFXUtils.toFXImage(bi, null);
|
||||
cachedPathChains = paths;
|
||||
cachedPathChains = cavePaths;
|
||||
tunnelEntrances = entrances;
|
||||
cachedAreas = new ArrayList<>(areas);
|
||||
buildingFootprints = footprints;
|
||||
cachedAreas = caveAreas;
|
||||
buildingFootprints = caveFootprints;
|
||||
fitToView();
|
||||
redraw();
|
||||
progress.setVisible(false);
|
||||
|
||||
@@ -399,9 +399,16 @@ public class WorldMapView extends VBox {
|
||||
List<float[][]> newCliffPaths = buildCliffPaths(model.mapData(), model.voxelSurface());
|
||||
List<float[][]> newForestPaths = buildForestOutlinePaths(fDensity, 512, 3.0f);
|
||||
|
||||
List<float[]> newBuildingFootprints = computeBuildingFootprints(model, findAssetRoot());
|
||||
List<float[][]> newPathChains = loadPathChains();
|
||||
List<float[]> newTunnelEntrances = computeTunnelEntrances(model.mapData());
|
||||
List<float[]> allBuildingFootprints = computeBuildingFootprints(model, findAssetRoot());
|
||||
List<float[][]> allPathChains = loadPathChains();
|
||||
List<float[]> newTunnelEntrances = computeTunnelEntrances(model.mapData());
|
||||
|
||||
Platform.runLater(() -> statusLbl.setText("Berechne Cave-Maske…"));
|
||||
boolean[] caveMask = loadCaveMask(model.mapData());
|
||||
List<float[][]>[] splitChains = splitPathChains(allPathChains, caveMask);
|
||||
List<float[][]> newPathChains = splitChains[0]; // nur Oberfläche
|
||||
List<float[]> newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false);
|
||||
List<PlacedArea> surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false);
|
||||
|
||||
// Hires-Chunks im Hintergrund generieren (nach dem UI-Update, nicht-blockierend)
|
||||
final WorldMapRenderModel chunkModel = model;
|
||||
@@ -414,7 +421,7 @@ public class WorldMapView extends VBox {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
currentModel = model;
|
||||
cachedAreas = new ArrayList<>(model.areas());
|
||||
cachedAreas = surfaceAreas;
|
||||
cachedLocs = new ArrayList<>(model.locations());
|
||||
mapBuffered = bi;
|
||||
mapFxImage = SwingFXUtils.toFXImage(bi, null);
|
||||
@@ -1083,6 +1090,107 @@ public class WorldMapView extends VBox {
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Cave-Mask: Trennung Oberfläche / Untergrund ───────────────────────────
|
||||
|
||||
static final int CAVE_MASK_SIZE = 1024;
|
||||
|
||||
/** Lädt die Cave-Tunnel-Maske (true = Untergrund/Tunnel). */
|
||||
static boolean[] loadCaveMask(MapData mapData) {
|
||||
return WorldMapRenderer.buildCaveTunnelMask(mapData, CAVE_MASK_SIZE);
|
||||
}
|
||||
|
||||
/** Prüft ob eine Weltposition unterhalb von Tunnel-Voxeln liegt. */
|
||||
static boolean isCavePoint(boolean[] mask, float wx, float wz) {
|
||||
int px = Math.round((wx + WorldMapRenderer.WORLD_HALF) / WorldMapRenderer.WORLD_SIZE * (CAVE_MASK_SIZE - 1));
|
||||
int pz = Math.round((wz + WorldMapRenderer.WORLD_HALF) / WorldMapRenderer.WORLD_SIZE * (CAVE_MASK_SIZE - 1));
|
||||
px = Math.max(0, Math.min(CAVE_MASK_SIZE - 1, px));
|
||||
pz = Math.max(0, Math.min(CAVE_MASK_SIZE - 1, pz));
|
||||
return mask[pz * CAVE_MASK_SIZE + px];
|
||||
}
|
||||
|
||||
/**
|
||||
* Teilt Pfad-Ketten an Tunnel-Grenzen auf.
|
||||
* Rückgabe: Index 0 = Oberflächensegmente, Index 1 = Tunnel-Segmente.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static List<float[][]>[] splitPathChains(List<float[][]> chains, boolean[] caveMask) {
|
||||
List<float[][]> surface = new ArrayList<>();
|
||||
List<float[][]> cave = new ArrayList<>();
|
||||
for (float[][] chain : chains) {
|
||||
splitChainByCaveMask(chain, caveMask, surface, cave);
|
||||
}
|
||||
return new List[]{surface, cave};
|
||||
}
|
||||
|
||||
private static void splitChainByCaveMask(float[][] chain, boolean[] caveMask,
|
||||
List<float[][]> surface, List<float[][]> cave) {
|
||||
if (chain.length < 2) return;
|
||||
List<float[]> current = new ArrayList<>();
|
||||
boolean currentCave = isCavePoint(caveMask, chain[0][0], chain[0][1]);
|
||||
current.add(chain[0]);
|
||||
|
||||
for (int i = 0; i < chain.length - 1; i++) {
|
||||
float[] a = chain[i];
|
||||
float[] b = chain[i + 1];
|
||||
boolean bCave = isCavePoint(caveMask, b[0], b[1]);
|
||||
|
||||
if (currentCave == bCave) {
|
||||
current.add(b);
|
||||
} else {
|
||||
// Genauen Grenzpunkt per Bisektion ermitteln
|
||||
float[] crossing = findCaveCrossing(a, b, caveMask, currentCave);
|
||||
current.add(crossing);
|
||||
if (current.size() >= 2) {
|
||||
(currentCave ? cave : surface).add(current.toArray(new float[0][]));
|
||||
}
|
||||
current = new ArrayList<>();
|
||||
current.add(crossing);
|
||||
current.add(b);
|
||||
currentCave = bCave;
|
||||
}
|
||||
}
|
||||
if (current.size() >= 2) {
|
||||
(currentCave ? cave : surface).add(current.toArray(new float[0][]));
|
||||
}
|
||||
}
|
||||
|
||||
/** Bisektion: findet den Weltpunkt, an dem die Cave-Maske umspringt (12 Iterationen ≈ 0.05m Genauigkeit). */
|
||||
private static float[] findCaveCrossing(float[] a, float[] b, boolean[] mask, boolean aIsCave) {
|
||||
float ax = a[0], az = a[1], bx = b[0], bz = b[1];
|
||||
for (int i = 0; i < 12; i++) {
|
||||
float mx = (ax + bx) * 0.5f, mz = (az + bz) * 0.5f;
|
||||
if (isCavePoint(mask, mx, mz) == aIsCave) { ax = mx; az = mz; }
|
||||
else { bx = mx; bz = mz; }
|
||||
}
|
||||
return new float[]{(ax + bx) * 0.5f, (az + bz) * 0.5f};
|
||||
}
|
||||
|
||||
/** Filtert Gebäude-Footprints: wantCave=true → nur Untergrund, false → nur Oberfläche. */
|
||||
static List<float[]> filterBuildingsByCave(List<float[]> footprints, boolean[] caveMask, boolean wantCave) {
|
||||
List<float[]> result = new ArrayList<>();
|
||||
for (float[] fp : footprints) {
|
||||
if (isCavePoint(caveMask, fp[0], fp[1]) == wantCave) result.add(fp);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Filtert Areas anhand des Polygon-Schwerpunkts. */
|
||||
static List<PlacedArea> filterAreasByCave(List<PlacedArea> areas, boolean[] caveMask, boolean wantCave) {
|
||||
List<PlacedArea> result = new ArrayList<>();
|
||||
for (PlacedArea area : areas) {
|
||||
float[] xs = area.pointsX(), zs = area.pointsZ();
|
||||
if (xs == null || xs.length == 0) {
|
||||
if (!wantCave) result.add(area);
|
||||
continue;
|
||||
}
|
||||
float cx = 0, cz = 0;
|
||||
for (int i = 0; i < xs.length; i++) { cx += xs[i]; cz += zs[i]; }
|
||||
cx /= xs.length; cz /= zs.length;
|
||||
if (isCavePoint(caveMask, cx, cz) == wantCave) result.add(area);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static WritableImage toWritableImage(int[] pixels, int size) {
|
||||
WritableImage img = new WritableImage(size, size);
|
||||
img.getPixelWriter().setPixels(0, 0, size, size, PixelFormat.getIntArgbInstance(), pixels, 0, size);
|
||||
|
||||
Reference in New Issue
Block a user