Karte: Zoom-Fix, Layout-Sync, Sandwich-basierter Pfad-Split
- Zoom-Bug behoben: CaveMapView auto-lud mit Canvas 0×0 (scale=1.0 ungültig) → Tab-Sync jetzt nur wenn isViewInitialized() (zoomLevels != null) - Layout-Fix: CaveMapView-Statusleiste unter dem Canvas (wie WorldMapView), nicht mehr darüber → Canvas-Höhe jetzt identisch → Position-Sync 1:1 korrekt - Auto-Load aus CaveMapView.redraw() entfernt; deferred fitToView() stattdessen - Pfad-Split: Sandwich-Maske (solid→void≥3→solid per Spalte) verhindert False-Positives an Klippen-Füßen; Wege werden nur als unterirdisch erkannt wenn in ihrer X,Z-Spalte ein echter Hohlraum (Tunnel) vorliegt Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5705,8 +5705,8 @@ public class EditorApp extends Application {
|
|||||||
if (!isSelected) return;
|
if (!isSelected) return;
|
||||||
if (!worldMapView.isLoaded()) {
|
if (!worldMapView.isLoaded()) {
|
||||||
worldMapView.loadAndRender();
|
worldMapView.loadAndRender();
|
||||||
} else if (caveMapView != null && caveMapView.isLoaded()) {
|
} else if (caveMapView != null && caveMapView.isLoaded() && caveMapView.isViewInitialized()) {
|
||||||
// Sync nach Layout-Pass damit Canvas-Größe korrekt ist
|
// Sync nur wenn CaveMap korrekt initialisiert (canvas > 0, zoomLevels gesetzt)
|
||||||
final double s = caveMapView.getScale(), px = caveMapView.getPanX(), py = caveMapView.getPanY();
|
final double s = caveMapView.getScale(), px = caveMapView.getPanX(), py = caveMapView.getPanY();
|
||||||
javafx.application.Platform.runLater(() -> worldMapView.setView(s, px, py));
|
javafx.application.Platform.runLater(() -> worldMapView.setView(s, px, py));
|
||||||
} else {
|
} else {
|
||||||
@@ -5721,8 +5721,8 @@ public class EditorApp extends Application {
|
|||||||
if (!isSelected) return;
|
if (!isSelected) return;
|
||||||
if (!caveMapView.isLoaded()) {
|
if (!caveMapView.isLoaded()) {
|
||||||
caveMapView.loadAndRender();
|
caveMapView.loadAndRender();
|
||||||
} else if (worldMapView.isMapReady()) {
|
} else if (worldMapView.isMapReady() && worldMapView.isViewInitialized()) {
|
||||||
// Sync nach Layout-Pass damit Canvas-Größe korrekt ist
|
// Sync nur wenn WorldMap korrekt initialisiert
|
||||||
final double s = worldMapView.getScale(), px = worldMapView.getPanX(), py = worldMapView.getPanY();
|
final double s = worldMapView.getScale(), px = worldMapView.getPanX(), py = worldMapView.getPanY();
|
||||||
javafx.application.Platform.runLater(() -> caveMapView.setView(s, px, py));
|
javafx.application.Platform.runLater(() -> caveMapView.setView(s, px, py));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ public class CaveMapView extends VBox {
|
|||||||
private double panX = 0, panY = 0, scale = 1.0;
|
private double panX = 0, panY = 0, scale = 1.0;
|
||||||
private double[] zoomLevels = null;
|
private double[] zoomLevels = null;
|
||||||
private int zoomLevelIdx = 0;
|
private int zoomLevelIdx = 0;
|
||||||
private boolean autoLoaded = false;
|
|
||||||
|
|
||||||
public CaveMapView(Supplier<Stage> stageSupplier) {
|
public CaveMapView(Supplier<Stage> stageSupplier) {
|
||||||
this.stageSupplier = stageSupplier;
|
this.stageSupplier = stageSupplier;
|
||||||
@@ -121,7 +120,7 @@ public class CaveMapView extends VBox {
|
|||||||
backBtn
|
backBtn
|
||||||
);
|
);
|
||||||
|
|
||||||
progress.setMaxWidth(Double.MAX_VALUE);
|
progress.setPrefWidth(160);
|
||||||
progress.setVisible(false);
|
progress.setVisible(false);
|
||||||
|
|
||||||
canvasPane.getChildren().add(canvas);
|
canvasPane.getChildren().add(canvas);
|
||||||
@@ -132,11 +131,15 @@ public class CaveMapView extends VBox {
|
|||||||
canvas.widthProperty().addListener(obs -> redraw());
|
canvas.widthProperty().addListener(obs -> redraw());
|
||||||
canvas.heightProperty().addListener(obs -> redraw());
|
canvas.heightProperty().addListener(obs -> redraw());
|
||||||
|
|
||||||
statusLbl.setPadding(new Insets(4, 8, 4, 8));
|
// Status-Leiste identisch wie WorldMapView: unter dem Canvas
|
||||||
|
HBox statusBar = new HBox(8, statusLbl, progress);
|
||||||
|
statusBar.setAlignment(javafx.geometry.Pos.CENTER_LEFT);
|
||||||
|
statusBar.setPadding(new Insets(4, 8, 4, 8));
|
||||||
|
statusBar.setStyle("-fx-background-color: #2a2a3a;");
|
||||||
|
|
||||||
setupMouse();
|
setupMouse();
|
||||||
|
|
||||||
getChildren().addAll(bar, progress, statusLbl, canvasPane);
|
getChildren().addAll(bar, canvasPane, statusBar);
|
||||||
setStyle("-fx-background-color: #1a1a1a;");
|
setStyle("-fx-background-color: #1a1a1a;");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,6 +265,8 @@ public class CaveMapView extends VBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLoaded() { return mapFxImage != null; }
|
public boolean isLoaded() { return mapFxImage != null; }
|
||||||
|
/** True wenn Zoom-Levels korrekt initialisiert (Canvas hatte Größe > 0 beim Laden). */
|
||||||
|
public boolean isViewInitialized() { return zoomLevels != null; }
|
||||||
|
|
||||||
public void ensureFitToView() { if (mapFxImage != null) fitToView(); }
|
public void ensureFitToView() { if (mapFxImage != null) fitToView(); }
|
||||||
|
|
||||||
@@ -292,10 +297,11 @@ public class CaveMapView extends VBox {
|
|||||||
Platform.runLater(() -> statusLbl.setText("Berechne Cave-Maske…"));
|
Platform.runLater(() -> statusLbl.setText("Berechne Cave-Maske…"));
|
||||||
boolean[] caveMask = WorldMapView.loadCaveMask(mapData);
|
boolean[] caveMask = WorldMapView.loadCaveMask(mapData);
|
||||||
|
|
||||||
// Pfade: Y-basierte Trennung (voxelSurface > path_y + clearance)
|
// Pfade: Sandwich+Y-basierte Trennung (echter Tunnel: solid→void≥3→solid)
|
||||||
Platform.runLater(() -> statusLbl.setText("Berechne VoxelSurface…"));
|
Platform.runLater(() -> statusLbl.setText("Berechne VoxelSurface…"));
|
||||||
float[] voxelSurface = WorldMapRenderer.bakeVoxelSurface(mapData);
|
float[] voxelSurface = WorldMapRenderer.bakeVoxelSurface(mapData);
|
||||||
List<float[][]>[] splitChains = WorldMapView.splitPathChains(allPaths, voxelSurface);
|
boolean[] sandwichMask = WorldMapView.bakeSandwichMask();
|
||||||
|
List<float[][]>[] splitChains = WorldMapView.splitPathChains(allPaths, voxelSurface, sandwichMask);
|
||||||
List<float[][]> cavePaths = splitChains[1];
|
List<float[][]> cavePaths = splitChains[1];
|
||||||
|
|
||||||
// Gebäude + Areas: Cave-Maske (keine Y-Info)
|
// Gebäude + Areas: Cave-Maske (keine Y-Info)
|
||||||
@@ -367,9 +373,9 @@ public class CaveMapView extends VBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void redraw() {
|
private void redraw() {
|
||||||
if (!autoLoaded && mapFxImage == null && !loading.get()) {
|
// Nachgelagerte Initialisierung: Canvas hatte beim Laden evtl. Größe 0
|
||||||
autoLoaded = true;
|
if (mapFxImage != null && zoomLevels == null) {
|
||||||
loadAndRender();
|
fitToView();
|
||||||
}
|
}
|
||||||
GraphicsContext gc = canvas.getGraphicsContext2D();
|
GraphicsContext gc = canvas.getGraphicsContext2D();
|
||||||
double w = canvas.getWidth(), h = canvas.getHeight();
|
double w = canvas.getWidth(), h = canvas.getHeight();
|
||||||
|
|||||||
@@ -359,6 +359,9 @@ public class WorldMapView extends VBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** True wenn Zoom-Levels korrekt initialisiert (Canvas hatte Größe > 0 beim Laden). */
|
||||||
|
public boolean isViewInitialized() { return zoomLevels != null; }
|
||||||
|
|
||||||
/** Lädt alle Weltdaten neu und baut das Render-Modell (teuer: I/O + Berechnungen). */
|
/** Lädt alle Weltdaten neu und baut das Render-Modell (teuer: I/O + Berechnungen). */
|
||||||
public void loadAndRender() {
|
public void loadAndRender() {
|
||||||
if (loading.getAndSet(true)) return;
|
if (loading.getAndSet(true)) return;
|
||||||
@@ -377,7 +380,9 @@ public class WorldMapView extends VBox {
|
|||||||
List<PlacedWater> waters = WaterBodyIO.load();
|
List<PlacedWater> waters = WaterBodyIO.load();
|
||||||
List<PlacedModel> models = PlacedModelIO.load();
|
List<PlacedModel> models = PlacedModelIO.load();
|
||||||
List<PlacedWaterfall> waterfalls = WaterfallIO.load();
|
List<PlacedWaterfall> waterfalls = WaterfallIO.load();
|
||||||
float[] voxelSurf = bakeVoxelHeights(mapData);
|
VoxelData vd = bakeVoxelData(mapData);
|
||||||
|
float[] voxelSurf = vd.voxelSurface();
|
||||||
|
boolean[] sandwichMask = vd.sandwichMask();
|
||||||
|
|
||||||
Platform.runLater(() -> statusLbl.setText("Berechne Modell…"));
|
Platform.runLater(() -> statusLbl.setText("Berechne Modell…"));
|
||||||
WorldMapRenderModel model = buildRenderModel(
|
WorldMapRenderModel model = buildRenderModel(
|
||||||
@@ -405,7 +410,7 @@ public class WorldMapView extends VBox {
|
|||||||
|
|
||||||
Platform.runLater(() -> statusLbl.setText("Berechne Cave-Maske…"));
|
Platform.runLater(() -> statusLbl.setText("Berechne Cave-Maske…"));
|
||||||
boolean[] caveMask = loadCaveMask(model.mapData());
|
boolean[] caveMask = loadCaveMask(model.mapData());
|
||||||
List<float[][]>[] splitChains = splitPathChains(allPathChains, model.voxelSurface());
|
List<float[][]>[] splitChains = splitPathChains(allPathChains, model.voxelSurface(), sandwichMask);
|
||||||
List<float[][]> newPathChains = splitChains[0]; // nur Oberfläche
|
List<float[][]> newPathChains = splitChains[0]; // nur Oberfläche
|
||||||
List<float[]> newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false);
|
List<float[]> newBuildingFootprints = filterBuildingsByCave(allBuildingFootprints, caveMask, false);
|
||||||
List<PlacedArea> surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false);
|
List<PlacedArea> surfaceAreas = filterAreasByCave(new ArrayList<>(model.areas()), caveMask, false);
|
||||||
@@ -1109,24 +1114,31 @@ public class WorldMapView extends VBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Teilt Pfad-Ketten an Tunnel-Grenzen auf (Y-basiert, präzise).
|
* Teilt Pfad-Ketten an Tunnel-Grenzen auf.
|
||||||
* Nutzt die Y-Koordinate der Pfad-Knoten (Index 2) und prüft ob sie unterhalb
|
* Kombiniert Sandwich-Prüfung (solid→void≥3→solid = echter Tunnel) mit Y-Koordinate,
|
||||||
* von solidem Voxel-Material liegen (voxelSurface > node_y + MIN_CEILING).
|
* um False-Positives an Klippen-Füßen zu vermeiden.
|
||||||
* Rückgabe: Index 0 = Oberflächensegmente, Index 1 = Tunnel-Segmente.
|
* Rückgabe: Index 0 = Oberflächensegmente, Index 1 = Tunnel-Segmente.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
static List<float[][]>[] splitPathChains(List<float[][]> chains, float[] voxelSurface) {
|
static List<float[][]>[] splitPathChains(List<float[][]> chains, float[] voxelSurface,
|
||||||
|
boolean[] sandwichMask) {
|
||||||
List<float[][]> surface = new ArrayList<>();
|
List<float[][]> surface = new ArrayList<>();
|
||||||
List<float[][]> cave = new ArrayList<>();
|
List<float[][]> cave = new ArrayList<>();
|
||||||
for (float[][] chain : chains) {
|
for (float[][] chain : chains) {
|
||||||
splitChainByVoxelSurface(chain, voxelSurface, surface, cave);
|
splitChainByVoxelSurface(chain, voxelSurface, sandwichMask, surface, cave);
|
||||||
}
|
}
|
||||||
return new List[]{surface, cave};
|
return new List[]{surface, cave};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final float PATH_UNDERGROUND_CLEARANCE = 3.0f;
|
private static final float PATH_UNDERGROUND_CLEARANCE = 3.0f;
|
||||||
|
|
||||||
private static boolean isNodeUnderground(float[] node, float[] voxelSurface) {
|
/**
|
||||||
|
* Prüft ob ein Knoten unter der Erde liegt.
|
||||||
|
* Bedingung: In dieser X,Z-Spalte liegt ein Sandwich (solid→void≥3→solid)
|
||||||
|
* UND das höchste Solid ist ≥ PATH_UNDERGROUND_CLEARANCE über der Knoten-Y.
|
||||||
|
* Das Sandwich-Kriterium verhindert False-Positives bei Wegen am Klippenfuß.
|
||||||
|
*/
|
||||||
|
private static boolean isNodeUnderground(float[] node, float[] voxelSurface, boolean[] sandwichMask) {
|
||||||
if (node.length < 3) return false;
|
if (node.length < 3) return false;
|
||||||
float wy = node[2];
|
float wy = node[2];
|
||||||
if (Float.isNaN(wy)) return false;
|
if (Float.isNaN(wy)) return false;
|
||||||
@@ -1136,26 +1148,29 @@ public class WorldMapView extends VBox {
|
|||||||
int pz = Math.round((node[1] + WH) / WS * (VS - 1));
|
int pz = Math.round((node[1] + WH) / WS * (VS - 1));
|
||||||
px = Math.max(0, Math.min(VS - 1, px));
|
px = Math.max(0, Math.min(VS - 1, px));
|
||||||
pz = Math.max(0, Math.min(VS - 1, pz));
|
pz = Math.max(0, Math.min(VS - 1, pz));
|
||||||
|
// Sandwich-Prüfung: nur dort wo echter Hohlraum vorliegt (nicht bloß Klippe)
|
||||||
|
if (sandwichMask != null && !sandwichMask[pz * VS + px]) return false;
|
||||||
float vs = voxelSurface[pz * VS + px];
|
float vs = voxelSurface[pz * VS + px];
|
||||||
return !Float.isNaN(vs) && vs > wy + PATH_UNDERGROUND_CLEARANCE;
|
return !Float.isNaN(vs) && vs > wy + PATH_UNDERGROUND_CLEARANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void splitChainByVoxelSurface(float[][] chain, float[] voxelSurface,
|
private static void splitChainByVoxelSurface(float[][] chain, float[] voxelSurface,
|
||||||
|
boolean[] sandwichMask,
|
||||||
List<float[][]> surface, List<float[][]> cave) {
|
List<float[][]> surface, List<float[][]> cave) {
|
||||||
if (chain.length < 2) return;
|
if (chain.length < 2) return;
|
||||||
List<float[]> current = new ArrayList<>();
|
List<float[]> current = new ArrayList<>();
|
||||||
boolean currentCave = isNodeUnderground(chain[0], voxelSurface);
|
boolean currentCave = isNodeUnderground(chain[0], voxelSurface, sandwichMask);
|
||||||
current.add(chain[0]);
|
current.add(chain[0]);
|
||||||
|
|
||||||
for (int i = 0; i < chain.length - 1; i++) {
|
for (int i = 0; i < chain.length - 1; i++) {
|
||||||
float[] a = chain[i];
|
float[] a = chain[i];
|
||||||
float[] b = chain[i + 1];
|
float[] b = chain[i + 1];
|
||||||
boolean bCave = isNodeUnderground(b, voxelSurface);
|
boolean bCave = isNodeUnderground(b, voxelSurface, sandwichMask);
|
||||||
|
|
||||||
if (currentCave == bCave) {
|
if (currentCave == bCave) {
|
||||||
current.add(b);
|
current.add(b);
|
||||||
} else {
|
} else {
|
||||||
float[] crossing = findPathCrossing(a, b, voxelSurface, currentCave);
|
float[] crossing = findPathCrossing(a, b, voxelSurface, sandwichMask, currentCave);
|
||||||
current.add(crossing);
|
current.add(crossing);
|
||||||
if (current.size() >= 2) {
|
if (current.size() >= 2) {
|
||||||
(currentCave ? cave : surface).add(current.toArray(new float[0][]));
|
(currentCave ? cave : surface).add(current.toArray(new float[0][]));
|
||||||
@@ -1172,13 +1187,14 @@ public class WorldMapView extends VBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Bisektion mit Y-Interpolation: 12 Iterationen ≈ 0.05m Genauigkeit. */
|
/** Bisektion mit Y-Interpolation: 12 Iterationen ≈ 0.05m Genauigkeit. */
|
||||||
private static float[] findPathCrossing(float[] a, float[] b, float[] vs, boolean aIsUnderground) {
|
private static float[] findPathCrossing(float[] a, float[] b, float[] vs, boolean[] sm,
|
||||||
|
boolean aIsUnderground) {
|
||||||
float ax = a[0], az = a[1], ay = a.length > 2 ? a[2] : 0f;
|
float ax = a[0], az = a[1], ay = a.length > 2 ? a[2] : 0f;
|
||||||
float bx = b[0], bz = b[1], by = b.length > 2 ? b[2] : 0f;
|
float bx = b[0], bz = b[1], by = b.length > 2 ? b[2] : 0f;
|
||||||
for (int i = 0; i < 12; i++) {
|
for (int i = 0; i < 12; i++) {
|
||||||
float mx = (ax + bx) * 0.5f, mz = (az + bz) * 0.5f, my = (ay + by) * 0.5f;
|
float mx = (ax + bx) * 0.5f, mz = (az + bz) * 0.5f, my = (ay + by) * 0.5f;
|
||||||
float[] mid = {mx, mz, my};
|
float[] mid = {mx, mz, my};
|
||||||
if (isNodeUnderground(mid, vs) == aIsUnderground) { ax = mx; az = mz; ay = my; }
|
if (isNodeUnderground(mid, vs, sm) == aIsUnderground) { ax = mx; az = mz; ay = my; }
|
||||||
else { bx = mx; bz = mz; by = my; }
|
else { bx = mx; bz = mz; by = my; }
|
||||||
}
|
}
|
||||||
return new float[]{(ax + bx) * 0.5f, (az + bz) * 0.5f, (ay + by) * 0.5f};
|
return new float[]{(ax + bx) * 0.5f, (az + bz) * 0.5f, (ay + by) * 0.5f};
|
||||||
@@ -1373,7 +1389,9 @@ public class WorldMapView extends VBox {
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float[] bakeVoxelHeights(MapData mapData) {
|
private record VoxelData(float[] voxelSurface, boolean[] sandwichMask) {}
|
||||||
|
|
||||||
|
private VoxelData bakeVoxelData(MapData mapData) {
|
||||||
int VS = WorldMapRenderer.VOXEL_SURFACE_RES;
|
int VS = WorldMapRenderer.VOXEL_SURFACE_RES;
|
||||||
// Bevorzuge Live-Chunks aus dem Editor-Speicher, falle auf Disk zurück
|
// Bevorzuge Live-Chunks aus dem Editor-Speicher, falle auf Disk zurück
|
||||||
List<VoxelChunk> chunks = voxelChunkSupplier != null
|
List<VoxelChunk> chunks = voxelChunkSupplier != null
|
||||||
@@ -1381,10 +1399,11 @@ public class WorldMapView extends VBox {
|
|||||||
: VoxelChunkIO.loadAll();
|
: VoxelChunkIO.loadAll();
|
||||||
boolean allEmpty = chunks.stream().allMatch(VoxelChunk::isEmpty);
|
boolean allEmpty = chunks.stream().allMatch(VoxelChunk::isEmpty);
|
||||||
if (allEmpty) { chunks = VoxelChunkIO.loadAll(); }
|
if (allEmpty) { chunks = VoxelChunkIO.loadAll(); }
|
||||||
|
boolean[] sandwichMask = new boolean[VS * VS];
|
||||||
if (chunks.isEmpty()) {
|
if (chunks.isEmpty()) {
|
||||||
float[] empty = new float[VS * VS];
|
float[] empty = new float[VS * VS];
|
||||||
java.util.Arrays.fill(empty, Float.NaN);
|
java.util.Arrays.fill(empty, Float.NaN);
|
||||||
return empty;
|
return new VoxelData(empty, sandwichMask);
|
||||||
}
|
}
|
||||||
// Kern-Logik ist dieselbe wie WorldMapRenderer.bakeVoxelSurface, aber mit
|
// Kern-Logik ist dieselbe wie WorldMapRenderer.bakeVoxelSurface, aber mit
|
||||||
// Live-Chunks statt VoxelChunkIO.loadAll().
|
// Live-Chunks statt VoxelChunkIO.loadAll().
|
||||||
@@ -1422,7 +1441,57 @@ public class WorldMapView extends VBox {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return voxelSurface;
|
// Sandwich-Maske: pro Spalte prüfen ob solid→void(≥3)→solid vorliegt (echter Tunnel)
|
||||||
|
buildSandwichMaskInto(chunks, sandwichMask, VS, WH, WS);
|
||||||
|
return new VoxelData(voxelSurface, sandwichMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Berechnet pro Voxel-Spalte, ob eine Tunnel-Struktur (solid→void≥3→solid) vorliegt.
|
||||||
|
* Nur cy=0-Chunks werden berücksichtigt; Klippen ohne Hohlraum werden NICHT markiert.
|
||||||
|
*/
|
||||||
|
private static void buildSandwichMaskInto(List<VoxelChunk> chunks, boolean[] out, int VS,
|
||||||
|
float WH, float WS) {
|
||||||
|
for (VoxelChunk chunk : chunks) {
|
||||||
|
if (chunk.isEmpty() || chunk.cy != 0) continue;
|
||||||
|
for (int lz = 0; lz < VoxelChunk.SIZE; lz++) {
|
||||||
|
float worldZ = VoxelChunk.toWorldZ(chunk.cz, lz);
|
||||||
|
int vsPz = Math.round((worldZ + WH) / WS * (VS - 1));
|
||||||
|
if (vsPz < 0 || vsPz >= VS) continue;
|
||||||
|
for (int lx = 0; lx < VoxelChunk.SIZE; lx++) {
|
||||||
|
float worldX = VoxelChunk.toWorldX(chunk.cx, lx);
|
||||||
|
int vsPx = Math.round((worldX + WH) / WS * (VS - 1));
|
||||||
|
if (vsPx < 0 || vsPx >= VS) continue;
|
||||||
|
int idx = vsPz * VS + vsPx;
|
||||||
|
if (out[idx]) continue;
|
||||||
|
int phase = 0, voidCnt = 0;
|
||||||
|
for (int ly = 0; ly < VoxelChunk.SIZE; ly++) {
|
||||||
|
byte d = chunk.getDensity(lx, ly, lz);
|
||||||
|
if (phase == 0) {
|
||||||
|
if (d > 0) phase = 1;
|
||||||
|
} else if (phase == 1) {
|
||||||
|
if (d <= 0) { phase = 2; voidCnt = 1; }
|
||||||
|
} else {
|
||||||
|
if (d <= 0) { voidCnt++; }
|
||||||
|
else {
|
||||||
|
if (voidCnt >= 3) { out[idx] = true; break; }
|
||||||
|
phase = 1; voidCnt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wie buildSandwichMaskInto, aber lädt Chunks von Disk (für CaveMapView). */
|
||||||
|
static boolean[] bakeSandwichMask() {
|
||||||
|
int VS = WorldMapRenderer.VOXEL_SURFACE_RES;
|
||||||
|
boolean[] sm = new boolean[VS * VS];
|
||||||
|
List<VoxelChunk> chunks;
|
||||||
|
try { chunks = VoxelChunkIO.loadAll(); } catch (Exception e) { return sm; }
|
||||||
|
buildSandwichMaskInto(chunks, sm, VS, WorldMapRenderer.WORLD_HALF, WorldMapRenderer.WORLD_SIZE);
|
||||||
|
return sm;
|
||||||
}
|
}
|
||||||
|
|
||||||
private WorldMapRenderModel buildRenderModel(
|
private WorldMapRenderModel buildRenderModel(
|
||||||
|
|||||||
Reference in New Issue
Block a user