Saubere Kante beim Voxel backen, Höhenindikator hinzugefügt
This commit is contained in:
@@ -14,6 +14,7 @@ import de.blight.eztree.Billboard;
|
|||||||
import de.blight.eztree.TreeOptions;
|
import de.blight.eztree.TreeOptions;
|
||||||
import de.blight.eztree.TreePresets;
|
import de.blight.eztree.TreePresets;
|
||||||
import de.blight.eztree.TreeType;
|
import de.blight.eztree.TreeType;
|
||||||
|
import javafx.embed.swing.SwingFXUtils;
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
@@ -799,10 +800,15 @@ public class EditorApp extends Application {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Kamera-Koordinaten aktualisieren
|
// Kamera-Koordinaten aktualisieren
|
||||||
camCoordsLabel.setText(String.format(
|
String camText = String.format(
|
||||||
"X:%.1f Y:%.1f Z:%.1f Yaw:%.0f° Pitch:%.0f°",
|
"X:%.1f Y:%.1f Z:%.1f Yaw:%.0f° Pitch:%.0f°",
|
||||||
input.camX, input.camY, input.camZ,
|
input.camX, input.camY, input.camZ,
|
||||||
input.camYaw, input.camPitch));
|
input.camYaw, input.camPitch);
|
||||||
|
float moh = input.mouseOverlayHeight;
|
||||||
|
if (input.debugHeightOverlayEnabled && !Float.isNaN(moh)) {
|
||||||
|
camText += String.format(java.util.Locale.US, " H:%.2fm", moh);
|
||||||
|
}
|
||||||
|
camCoordsLabel.setText(camText);
|
||||||
|
|
||||||
// Konsolen-Antwort anzeigen
|
// Konsolen-Antwort anzeigen
|
||||||
String consoleMsg = input.consoleOutput;
|
String consoleMsg = input.consoleOutput;
|
||||||
@@ -1318,9 +1324,12 @@ public class EditorApp extends Application {
|
|||||||
importTexSetItem.setOnAction(e -> openTextureSetImport(primaryStage));
|
importTexSetItem.setOnAction(e -> openTextureSetImport(primaryStage));
|
||||||
importAudioItem.setOnAction(e -> handleAudioImport(primaryStage));
|
importAudioItem.setOnAction(e -> handleAudioImport(primaryStage));
|
||||||
importAnimItem.setOnAction(e -> handleAnimationImport(primaryStage));
|
importAnimItem.setOnAction(e -> handleAnimationImport(primaryStage));
|
||||||
|
MenuItem screenshotItem = new MenuItem("Screenshot");
|
||||||
|
screenshotItem.setOnAction(e -> takeEditorScreenshot());
|
||||||
fileMenu.getItems().addAll(newItem, saveItem, new SeparatorMenuItem(),
|
fileMenu.getItems().addAll(newItem, saveItem, new SeparatorMenuItem(),
|
||||||
importModelLodItem, importTexItem, importTexZipItem, importTexSetItem,
|
importModelLodItem, importTexItem, importTexZipItem, importTexSetItem,
|
||||||
importAudioItem, importAnimItem);
|
importAudioItem, importAnimItem,
|
||||||
|
new SeparatorMenuItem(), screenshotItem);
|
||||||
|
|
||||||
Menu toolsMenu = new Menu("Werkzeuge");
|
Menu toolsMenu = new Menu("Werkzeuge");
|
||||||
MenuItem vegetationsItem = new MenuItem("Vegetations Generator");
|
MenuItem vegetationsItem = new MenuItem("Vegetations Generator");
|
||||||
@@ -1359,9 +1368,9 @@ public class EditorApp extends Application {
|
|||||||
Menu viewMenu = new Menu("Ansicht");
|
Menu viewMenu = new Menu("Ansicht");
|
||||||
MenuItem resetCam = new MenuItem("Kamera zurücksetzen");
|
MenuItem resetCam = new MenuItem("Kamera zurücksetzen");
|
||||||
resetCam.setOnAction(e -> input.addMouseDelta(0, 0));
|
resetCam.setOnAction(e -> input.addMouseDelta(0, 0));
|
||||||
MenuItem viewTexture = new MenuItem("Textur (Ctrl+G)");
|
MenuItem viewTexture = new MenuItem("Textur (Alt+G)");
|
||||||
MenuItem viewWireframe = new MenuItem("Drahtgitter (Ctrl+G)");
|
MenuItem viewWireframe = new MenuItem("Drahtgitter (Alt+G)");
|
||||||
viewTopologyItem = new CheckMenuItem("Topologie-Overlay (Ctrl+T)");
|
viewTopologyItem = new CheckMenuItem("Topologie-Overlay (Alt+T)");
|
||||||
viewTexture.setOnAction(e -> {
|
viewTexture.setOnAction(e -> {
|
||||||
wireframeActive = false;
|
wireframeActive = false;
|
||||||
input.wireframeRequest = 2;
|
input.wireframeRequest = 2;
|
||||||
@@ -1382,8 +1391,14 @@ public class EditorApp extends Application {
|
|||||||
camOrbitItem.setOnAction(e -> input.camMode = SharedInput.CAM_ORBIT);
|
camOrbitItem.setOnAction(e -> input.camMode = SharedInput.CAM_ORBIT);
|
||||||
camFreeItem.setOnAction(e -> input.camMode = SharedInput.CAM_FREEFLY);
|
camFreeItem.setOnAction(e -> input.camMode = SharedInput.CAM_FREEFLY);
|
||||||
|
|
||||||
|
CheckMenuItem debugHeightItem = new CheckMenuItem("Höhen-Overlay (Alt+H)");
|
||||||
|
debugHeightItem.setAccelerator(javafx.scene.input.KeyCombination.keyCombination("Alt+H"));
|
||||||
|
debugHeightItem.setOnAction(e ->
|
||||||
|
input.debugHeightOverlayEnabled = debugHeightItem.isSelected());
|
||||||
|
|
||||||
viewMenu.getItems().addAll(resetCam, new SeparatorMenuItem(), viewTexture, viewWireframe,
|
viewMenu.getItems().addAll(resetCam, new SeparatorMenuItem(), viewTexture, viewWireframe,
|
||||||
new SeparatorMenuItem(), viewTopologyItem,
|
new SeparatorMenuItem(), viewTopologyItem,
|
||||||
|
new SeparatorMenuItem(), debugHeightItem,
|
||||||
new SeparatorMenuItem(), camOrbitItem, camFreeItem);
|
new SeparatorMenuItem(), camOrbitItem, camFreeItem);
|
||||||
|
|
||||||
Menu zeitMenu = new Menu("Zeit");
|
Menu zeitMenu = new Menu("Zeit");
|
||||||
@@ -5673,6 +5688,21 @@ public class EditorApp extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void takeEditorScreenshot() {
|
||||||
|
WritableImage image = primaryStage.getScene().snapshot(null);
|
||||||
|
String timestamp = java.time.LocalDateTime.now()
|
||||||
|
.format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
|
||||||
|
Path dir = BlightHome.resolve("screenshots");
|
||||||
|
try {
|
||||||
|
Files.createDirectories(dir);
|
||||||
|
File out = dir.resolve("editor_" + timestamp + ".png").toFile();
|
||||||
|
ImageIO.write(SwingFXUtils.fromFXImage(image, null), "PNG", out);
|
||||||
|
setStatus("Screenshot gespeichert: " + out.getPath());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
setStatus("Screenshot fehlgeschlagen: " + ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void handleTextureImport(javafx.stage.Window owner) {
|
private void handleTextureImport(javafx.stage.Window owner) {
|
||||||
FileChooser fc = new FileChooser();
|
FileChooser fc = new FileChooser();
|
||||||
fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)");
|
fc.setTitle("Texturen importieren (nicht-PNG wird automatisch konvertiert)");
|
||||||
@@ -8187,6 +8217,7 @@ public class EditorApp extends Application {
|
|||||||
case E -> input.down = pressed;
|
case E -> input.down = pressed;
|
||||||
case SHIFT -> input.shiftHeld = pressed;
|
case SHIFT -> input.shiftHeld = pressed;
|
||||||
case CONTROL -> input.ctrlHeld = pressed;
|
case CONTROL -> input.ctrlHeld = pressed;
|
||||||
|
case ALT -> input.altHeld = pressed;
|
||||||
case ENTER -> {
|
case ENTER -> {
|
||||||
if (pressed && input.activeLayer == SharedInput.LAYER_VOXEL_CLIFF) {
|
if (pressed && input.activeLayer == SharedInput.LAYER_VOXEL_CLIFF) {
|
||||||
input.generateCliffVoxelsRequested = true;
|
input.generateCliffVoxelsRequested = true;
|
||||||
@@ -8228,13 +8259,13 @@ public class EditorApp extends Application {
|
|||||||
input.waterSampleHeightRequested = true;
|
input.waterSampleHeightRequested = true;
|
||||||
}
|
}
|
||||||
case G -> {
|
case G -> {
|
||||||
if (pressed && input.ctrlHeld) {
|
if (pressed && input.altHeld) {
|
||||||
wireframeActive = !wireframeActive;
|
wireframeActive = !wireframeActive;
|
||||||
input.wireframeRequest = wireframeActive ? 1 : 2;
|
input.wireframeRequest = wireframeActive ? 1 : 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case T -> {
|
case T -> {
|
||||||
if (pressed && input.ctrlHeld && viewTopologyItem != null) {
|
if (pressed && input.altHeld && viewTopologyItem != null) {
|
||||||
boolean sel = !viewTopologyItem.isSelected();
|
boolean sel = !viewTopologyItem.isSelected();
|
||||||
viewTopologyItem.setSelected(sel);
|
viewTopologyItem.setSelected(sel);
|
||||||
input.topologyRequest = sel ? 1 : 2;
|
input.topologyRequest = sel ? 1 : 2;
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ public class SharedInput {
|
|||||||
public final java.util.concurrent.atomic.AtomicInteger scrollAccum =
|
public final java.util.concurrent.atomic.AtomicInteger scrollAccum =
|
||||||
new java.util.concurrent.atomic.AtomicInteger();
|
new java.util.concurrent.atomic.AtomicInteger();
|
||||||
|
|
||||||
// ── Shift / Ctrl ─────────────────────────────────────────────────────────
|
// ── Shift / Ctrl / Alt ───────────────────────────────────────────────────
|
||||||
public volatile boolean shiftHeld;
|
public volatile boolean shiftHeld;
|
||||||
public volatile boolean ctrlHeld;
|
public volatile boolean ctrlHeld;
|
||||||
|
public volatile boolean altHeld;
|
||||||
|
|
||||||
// ── Debug-Toggle: Strg+F8 schaltet Raw-Texture-Modus (kein Lighting) ─────
|
// ── Debug-Toggle: Strg+F8 schaltet Raw-Texture-Modus (kein Lighting) ─────
|
||||||
public volatile boolean debugNoLightToggle;
|
public volatile boolean debugNoLightToggle;
|
||||||
@@ -185,6 +186,8 @@ public class SharedInput {
|
|||||||
// ── Mausposition im Viewport (JavaFX-Pixel, -1 = außerhalb) ─────────────
|
// ── Mausposition im Viewport (JavaFX-Pixel, -1 = außerhalb) ─────────────
|
||||||
public volatile float mouseScreenX = -1f;
|
public volatile float mouseScreenX = -1f;
|
||||||
public volatile float mouseScreenY = -1f;
|
public volatile float mouseScreenY = -1f;
|
||||||
|
/** JME → JavaFX: Höhe am Mauszeiger wenn Height-Overlay aktiv (NaN = unbekannt). */
|
||||||
|
public volatile float mouseOverlayHeight = Float.NaN;
|
||||||
|
|
||||||
// ── Speichern ─────────────────────────────────────────────────────────────
|
// ── Speichern ─────────────────────────────────────────────────────────────
|
||||||
public volatile boolean saveRequested = false;
|
public volatile boolean saveRequested = false;
|
||||||
@@ -783,6 +786,9 @@ public class SharedInput {
|
|||||||
/** JME → JFX: Status-Meldung nach Abschluss des Backens oder Löschens. */
|
/** JME → JFX: Status-Meldung nach Abschluss des Backens oder Löschens. */
|
||||||
public volatile String bakeStatusMsg = null;
|
public volatile String bakeStatusMsg = null;
|
||||||
|
|
||||||
|
/** JFX → JME: Höhen-Overlay ein/aus (Terrain / Voxel / Baked im 100m-Radius). */
|
||||||
|
public volatile boolean debugHeightOverlayEnabled = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Per BFS selektierte gebackene Chunks (chunkKey-kodiert).
|
* Per BFS selektierte gebackene Chunks (chunkKey-kodiert).
|
||||||
* Thread-sicher; JME schreibt, JFX liest (nur size() für Anzeige).
|
* Thread-sicher; JME schreibt, JFX liest (nur size() für Anzeige).
|
||||||
|
|||||||
@@ -131,6 +131,23 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
|
|
||||||
private Geometry brushIndicator;
|
private Geometry brushIndicator;
|
||||||
|
|
||||||
|
// ── Höhen-Debug-Overlay ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private Node debugPtsNode;
|
||||||
|
private com.jme3.font.BitmapFont debugFont;
|
||||||
|
private com.jme3.font.BitmapText debugHudText;
|
||||||
|
private java.util.List<com.jme3.font.BitmapText> debugLabelsTerrain = new java.util.ArrayList<>();
|
||||||
|
private java.util.List<com.jme3.font.BitmapText> debugLabelsVoxel = new java.util.ArrayList<>();
|
||||||
|
private java.util.List<com.jme3.font.BitmapText> debugLabelsBaked = new java.util.ArrayList<>();
|
||||||
|
private float[] debugLabelXZ; // [i*2]=x, [i*2+1]=z
|
||||||
|
private float[] debugLabelTH; // Terrain-Höhe
|
||||||
|
private float[] debugLabelVH; // Voxel-Höhe (NaN = kein Voxel)
|
||||||
|
private float[] debugLabelBH; // Baked-Höhe (NaN = kein Treffer)
|
||||||
|
private boolean debugOverlayActive = false;
|
||||||
|
private float debugCenterX = Float.NaN;
|
||||||
|
private float debugCenterZ = Float.NaN;
|
||||||
|
private int debugLastStep = -1;
|
||||||
|
|
||||||
// ── Basis-Terrain-Referenzebene (y = -10) ────────────────────────────────
|
// ── Basis-Terrain-Referenzebene (y = -10) ────────────────────────────────
|
||||||
|
|
||||||
/** Flache Referenzebene bei Welt-Y = -10; nur im LAYER_VOXEL sichtbar. */
|
/** Flache Referenzebene bei Welt-Y = -10; nur im LAYER_VOXEL sichtbar. */
|
||||||
@@ -231,9 +248,12 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
protected void cleanup(Application app) {
|
protected void cleanup(Application app) {
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
voxelRoot.removeFromParent();
|
voxelRoot.removeFromParent();
|
||||||
if (brushIndicator != null) brushIndicator.removeFromParent();
|
if (brushIndicator != null) brushIndicator.removeFromParent();
|
||||||
if (basePlaneNode != null) basePlaneNode.removeFromParent();
|
if (basePlaneNode != null) basePlaneNode.removeFromParent();
|
||||||
if (wireframeActive) applyWireframe(false);
|
if (debugPtsNode != null) debugPtsNode.removeFromParent();
|
||||||
|
if (debugHudText != null) debugHudText.removeFromParent();
|
||||||
|
clearDebugLabels();
|
||||||
|
if (wireframeActive) applyWireframe(false);
|
||||||
nodes.clear();
|
nodes.clear();
|
||||||
chunks.clear();
|
chunks.clear();
|
||||||
}
|
}
|
||||||
@@ -263,6 +283,25 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// Brush-Indikator immer aktualisieren (zeigen/verstecken je nach Layer)
|
// Brush-Indikator immer aktualisieren (zeigen/verstecken je nach Layer)
|
||||||
updateBrushIndicator();
|
updateBrushIndicator();
|
||||||
|
|
||||||
|
// Höhen-Overlay ein/ausschalten und aktualisieren
|
||||||
|
if (input.debugHeightOverlayEnabled != debugOverlayActive) {
|
||||||
|
debugOverlayActive = input.debugHeightOverlayEnabled;
|
||||||
|
applyDebugOverlay(debugOverlayActive);
|
||||||
|
}
|
||||||
|
if (debugOverlayActive) {
|
||||||
|
float cx = cam.getLocation().x, cz = cam.getLocation().z;
|
||||||
|
int step = debugLabelStep(cam.getLocation().y);
|
||||||
|
float halfStep = step / 2f;
|
||||||
|
if (Float.isNaN(debugCenterX)
|
||||||
|
|| Math.abs(cx - debugCenterX) > halfStep
|
||||||
|
|| Math.abs(cz - debugCenterZ) > halfStep
|
||||||
|
|| step != debugLastStep) {
|
||||||
|
debugCenterX = cx; debugCenterZ = cz; debugLastStep = step;
|
||||||
|
rebuildDebugGeometry(cx, cz);
|
||||||
|
}
|
||||||
|
refreshDebugHud();
|
||||||
|
}
|
||||||
|
|
||||||
// Bake angefordert?
|
// Bake angefordert?
|
||||||
if (input.bakeVoxelsRequested) {
|
if (input.bakeVoxelsRequested) {
|
||||||
input.bakeVoxelsRequested = false;
|
input.bakeVoxelsRequested = false;
|
||||||
@@ -1112,6 +1151,21 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
return 0f;
|
return 0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Höchste bekannte Oberfläche an (worldX, worldZ): max(Terrain, Voxel, Baked). */
|
||||||
|
public float heightAt(float worldX, float worldZ) {
|
||||||
|
float h = terrainH(worldX, worldZ);
|
||||||
|
float vh = columnTopWorldY(worldX, worldZ);
|
||||||
|
if (vh > h + 0.1f) h = vh;
|
||||||
|
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||||
|
if (smes != null) {
|
||||||
|
com.jme3.math.Ray r = new com.jme3.math.Ray(
|
||||||
|
new Vector3f(worldX, 500f, worldZ), new Vector3f(0f, -1f, 0f));
|
||||||
|
Vector3f[] hit = smes.raycastGeometryWithNormal(r);
|
||||||
|
if (hit != null && hit[0].y > h) h = hit[0].y;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasTerrainMesh() {
|
private boolean hasTerrainMesh() {
|
||||||
return terrainEditorState != null || terrainQuad != null;
|
return terrainEditorState != null || terrainQuad != null;
|
||||||
}
|
}
|
||||||
@@ -1201,18 +1255,34 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
return results.getClosestCollision().getContactPoint();
|
return results.getClosestCollision().getContactPoint();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Welt-Y des höchsten Solid-Voxels an (worldX, worldZ), oder Terrain-Höhe wenn keine Voxel. */
|
/**
|
||||||
|
* Interpolierte Welt-Y der Voxel-Oberfläche an (worldX, worldZ).
|
||||||
|
* Verwendet dieselbe Lineare Interpolation wie Marching Cubes (t = -d0 / (d1 - d0)),
|
||||||
|
* sodass der zurückgegebene Wert mit dem echten Mesh übereinstimmt.
|
||||||
|
* Gibt Terrain-Höhe zurück wenn keine Voxel vorhanden.
|
||||||
|
*/
|
||||||
public float columnTopWorldY(float worldX, float worldZ) {
|
public float columnTopWorldY(float worldX, float worldZ) {
|
||||||
int cx = VoxelChunk.worldXToCx(worldX);
|
int cx = VoxelChunk.worldXToCx(worldX);
|
||||||
int cz = VoxelChunk.worldZToCz(worldZ);
|
int cz = VoxelChunk.worldZToCz(worldZ);
|
||||||
int lx = Math.max(0, Math.min(VoxelChunk.SIZE - 1, (int) VoxelChunk.worldXToLocal(worldX, cx)));
|
int lx = Math.max(0, Math.min(VoxelChunk.SIZE - 1, (int) VoxelChunk.worldXToLocal(worldX, cx)));
|
||||||
int lzl = Math.max(0, Math.min(VoxelChunk.SIZE - 1, (int) VoxelChunk.worldZToLocal(worldZ, cz)));
|
int lzl = Math.max(0, Math.min(VoxelChunk.SIZE - 1, (int) VoxelChunk.worldZToLocal(worldZ, cz)));
|
||||||
for (int cy = 10; cy >= -2; cy--) {
|
for (int cy = 10; cy >= -2; cy--) {
|
||||||
VoxelChunk chunk = chunks.get(chunkKey(cx, cy, cz));
|
VoxelChunk chunk = chunks.get(chunkKey(cx, cy, cz));
|
||||||
if (chunk == null || chunk.isEmpty()) continue;
|
if (chunk == null || chunk.isEmpty()) continue;
|
||||||
for (int ly = VoxelChunk.SIZE - 1; ly >= 0; ly--) {
|
for (int ly = VoxelChunk.SIZE - 1; ly >= 0; ly--) {
|
||||||
if (chunk.getDensity(lx, ly, lzl) > 0) {
|
float d0 = chunk.getDensity(lx, ly, lzl);
|
||||||
return VoxelChunk.toWorldY(cy, ly);
|
if (d0 > 0) {
|
||||||
|
// Density der Zelle darüber (ggf. nächster Chunk)
|
||||||
|
float d1;
|
||||||
|
if (ly + 1 < VoxelChunk.SIZE) {
|
||||||
|
d1 = chunk.getDensity(lx, ly + 1, lzl);
|
||||||
|
} else {
|
||||||
|
VoxelChunk above = chunks.get(chunkKey(cx, cy + 1, cz));
|
||||||
|
d1 = (above != null) ? above.getDensity(lx, 0, lzl) : -1f;
|
||||||
|
}
|
||||||
|
float t = (Math.abs(d1 - d0) < 0.001f) ? 0.5f
|
||||||
|
: Math.max(0f, Math.min(1f, -d0 / (d1 - d0)));
|
||||||
|
return VoxelChunk.toWorldY(cy, ly) + t;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1490,6 +1560,235 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
* Bäckt alle übergebenen Chunks: speichert .blvc, glättet die Dichte mit
|
* Bäckt alle übergebenen Chunks: speichert .blvc, glättet die Dichte mit
|
||||||
* Nachbar-Lookup, erzeugt LOD0/1/2-Meshes und exportiert sie als .j3o.
|
* Nachbar-Lookup, erzeugt LOD0/1/2-Meshes und exportiert sie als .j3o.
|
||||||
*/
|
*/
|
||||||
|
// ── Höhen-Debug-Overlay ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void applyDebugOverlay(boolean on) {
|
||||||
|
if (on) {
|
||||||
|
if (debugFont == null) {
|
||||||
|
debugFont = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||||
|
}
|
||||||
|
if (debugPtsNode == null) {
|
||||||
|
debugPtsNode = new Node("debugHeightPts");
|
||||||
|
debugPtsNode.setShadowMode(RenderQueue.ShadowMode.Off);
|
||||||
|
}
|
||||||
|
if (debugHudText == null) {
|
||||||
|
debugHudText = new com.jme3.font.BitmapText(debugFont, false);
|
||||||
|
debugHudText.setSize(debugFont.getCharSet().getRenderedSize() * 1.4f);
|
||||||
|
debugHudText.setColor(com.jme3.math.ColorRGBA.Yellow);
|
||||||
|
}
|
||||||
|
app.getRootNode().attachChild(debugPtsNode);
|
||||||
|
app.getGuiNode().attachChild(debugHudText);
|
||||||
|
debugCenterX = Float.NaN; // erzwingt sofortigen Rebuild
|
||||||
|
} else {
|
||||||
|
if (debugPtsNode != null) debugPtsNode.removeFromParent();
|
||||||
|
if (debugHudText != null) debugHudText.removeFromParent();
|
||||||
|
clearDebugLabels();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Baut drei farbige Punkt-Wolken im 100m-Radius um (cx, cz):
|
||||||
|
* Grau = Basis-Terrain
|
||||||
|
* Cyan = Raw-Voxel-Oberfläche
|
||||||
|
* Orange = Gebackene Mesh-Oberfläche (4m-Raster, Downward-Raycast)
|
||||||
|
* Jeder Punkt sitzt an seiner echten Welt-Y-Position, sodass
|
||||||
|
* man von der Seite drei überlagerte Flächen sieht.
|
||||||
|
*/
|
||||||
|
private void rebuildDebugGeometry(float cx, float cz) {
|
||||||
|
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||||
|
clearDebugLabels();
|
||||||
|
|
||||||
|
int ls = debugLastStep;
|
||||||
|
int lr = debugLabelRadius(ls);
|
||||||
|
java.util.List<float[]> lPts = new java.util.ArrayList<>();
|
||||||
|
for (int dz = -lr; dz <= lr; dz += ls) {
|
||||||
|
for (int dx = -lr; dx <= lr; dx += ls) {
|
||||||
|
if (dx * dx + dz * dz > lr * lr) continue;
|
||||||
|
lPts.add(new float[]{cx + dx, cz + dz});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int ln = lPts.size();
|
||||||
|
debugLabelXZ = new float[ln * 2];
|
||||||
|
debugLabelTH = new float[ln];
|
||||||
|
debugLabelVH = new float[ln]; // NaN wenn kein Voxel
|
||||||
|
debugLabelBH = new float[ln];
|
||||||
|
|
||||||
|
com.jme3.math.ColorRGBA colTerrain = com.jme3.math.ColorRGBA.White;
|
||||||
|
com.jme3.math.ColorRGBA colVoxel = new com.jme3.math.ColorRGBA(0.4f, 0.8f, 1f, 1f);
|
||||||
|
com.jme3.math.ColorRGBA colBaked = new com.jme3.math.ColorRGBA(1f, 0.5f, 0.5f, 1f);
|
||||||
|
|
||||||
|
for (int i = 0; i < ln; i++) {
|
||||||
|
float lx = lPts.get(i)[0], lz = lPts.get(i)[1];
|
||||||
|
debugLabelXZ[i * 2] = lx;
|
||||||
|
debugLabelXZ[i * 2 + 1] = lz;
|
||||||
|
float th = terrainH(lx, lz);
|
||||||
|
float vh = columnTopWorldY(lx, lz);
|
||||||
|
debugLabelTH[i] = th;
|
||||||
|
debugLabelVH[i] = (vh > th + 0.1f) ? vh : Float.NaN; // NaN wenn kein Voxel
|
||||||
|
if (smes != null) {
|
||||||
|
com.jme3.math.Ray r = new com.jme3.math.Ray(
|
||||||
|
new Vector3f(lx, 500f, lz), new Vector3f(0f, -1f, 0f));
|
||||||
|
Vector3f[] hit = smes.raycastGeometryWithNormal(r);
|
||||||
|
debugLabelBH[i] = (hit != null) ? hit[0].y : Float.NaN;
|
||||||
|
} else {
|
||||||
|
debugLabelBH[i] = Float.NaN;
|
||||||
|
}
|
||||||
|
debugLabelsTerrain.add(makeLabelBt(colTerrain));
|
||||||
|
debugLabelsVoxel .add(makeLabelBt(colVoxel));
|
||||||
|
debugLabelsBaked .add(makeLabelBt(colBaked));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int debugLabelStep(float camY) {
|
||||||
|
if (camY < 25f) return 1;
|
||||||
|
else if (camY < 70f) return 4;
|
||||||
|
else return 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int debugLabelRadius(int step) {
|
||||||
|
if (step <= 1) return 30;
|
||||||
|
else if (step <= 4) return 60;
|
||||||
|
else return 120;
|
||||||
|
}
|
||||||
|
|
||||||
|
private com.jme3.font.BitmapText makeLabelBt(com.jme3.math.ColorRGBA color) {
|
||||||
|
com.jme3.font.BitmapText bt = new com.jme3.font.BitmapText(debugFont, false);
|
||||||
|
bt.setSize(11f);
|
||||||
|
bt.setColor(color);
|
||||||
|
bt.setText("");
|
||||||
|
bt.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
|
app.getGuiNode().attachChild(bt);
|
||||||
|
return bt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearDebugLabels() {
|
||||||
|
for (com.jme3.font.BitmapText bt : debugLabelsTerrain) bt.removeFromParent();
|
||||||
|
for (com.jme3.font.BitmapText bt : debugLabelsVoxel) bt.removeFromParent();
|
||||||
|
for (com.jme3.font.BitmapText bt : debugLabelsBaked) bt.removeFromParent();
|
||||||
|
debugLabelsTerrain.clear();
|
||||||
|
debugLabelsVoxel .clear();
|
||||||
|
debugLabelsBaked .clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erzeugt eine Geometrie aus kurzen vertikalen Tick-Linien (0.5m hoch) an jedem
|
||||||
|
* Probenpunkt. Mode.Lines macht die Ticks deutlich sichtbarer als einzelne Pixel.
|
||||||
|
* posList: [x0,y0,z0, x1,y1,z1, ...] – Fußpunkte der Ticks.
|
||||||
|
*/
|
||||||
|
private Geometry makeTickCloud(String name, java.util.List<Float> posList,
|
||||||
|
com.jme3.math.ColorRGBA color) {
|
||||||
|
int n = posList.size() / 3;
|
||||||
|
// 2 Vertices pro Tick (Basis + Spitze), je 3 Positions-Floats
|
||||||
|
FloatBuffer posBuf = BufferUtils.createFloatBuffer(n * 6);
|
||||||
|
FloatBuffer colBuf = BufferUtils.createFloatBuffer(n * 8);
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
float x = posList.get(i * 3);
|
||||||
|
float y = posList.get(i * 3 + 1);
|
||||||
|
float z = posList.get(i * 3 + 2);
|
||||||
|
posBuf.put(x).put(y).put(z); // Basis
|
||||||
|
posBuf.put(x).put(y + 0.5f).put(z); // Spitze
|
||||||
|
colBuf.put(color.r).put(color.g).put(color.b).put(color.a);
|
||||||
|
colBuf.put(color.r).put(color.g).put(color.b).put(color.a);
|
||||||
|
}
|
||||||
|
posBuf.rewind(); colBuf.rewind();
|
||||||
|
|
||||||
|
Mesh mesh = new Mesh();
|
||||||
|
mesh.setMode(Mesh.Mode.Lines);
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf);
|
||||||
|
mesh.setBuffer(VertexBuffer.Type.Color, 4, colBuf);
|
||||||
|
mesh.updateBound();
|
||||||
|
|
||||||
|
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
mat.setBoolean("VertexColor", true);
|
||||||
|
mat.getAdditionalRenderState().setDepthTest(false);
|
||||||
|
|
||||||
|
Geometry geo = new Geometry(name, mesh);
|
||||||
|
geo.setMaterial(mat);
|
||||||
|
geo.setShadowMode(RenderQueue.ShadowMode.Off);
|
||||||
|
return geo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aktualisiert den HUD-Text mit den Höhen direkt unterhalb der Kamera.
|
||||||
|
* Position: oben links im Viewport.
|
||||||
|
*/
|
||||||
|
private void refreshDebugHud() {
|
||||||
|
float wx = cam.getLocation().x;
|
||||||
|
float wz = cam.getLocation().z;
|
||||||
|
float th = terrainH(wx, wz);
|
||||||
|
float vh = columnTopWorldY(wx, wz);
|
||||||
|
|
||||||
|
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||||
|
String bakedLine = "";
|
||||||
|
if (smes != null) {
|
||||||
|
com.jme3.math.Ray ray = new com.jme3.math.Ray(
|
||||||
|
new Vector3f(wx, 500f, wz), new Vector3f(0f, -1f, 0f));
|
||||||
|
Vector3f[] hit = smes.raycastGeometryWithNormal(ray);
|
||||||
|
if (hit != null) {
|
||||||
|
bakedLine = String.format(java.util.Locale.US,
|
||||||
|
"\n Baked (orange): %.2fm", hit[0].y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debugHudText.setText(String.format(java.util.Locale.US,
|
||||||
|
"Höhen bei X=%.1f Z=%.1f\n" +
|
||||||
|
" Terrain (grau): %.2fm\n" +
|
||||||
|
" Voxel roh (cyan): %.2fm%s",
|
||||||
|
wx, wz, th, vh, bakedLine));
|
||||||
|
debugHudText.setLocalTranslation(8, cam.getHeight() - 8, 0);
|
||||||
|
|
||||||
|
// ── Projizierte Zahlenlabels ─────────────────────────────────────────
|
||||||
|
if (debugLabelXZ != null) {
|
||||||
|
int ln = debugLabelsTerrain.size();
|
||||||
|
for (int i = 0; i < ln; i++) {
|
||||||
|
float lx = debugLabelXZ[i * 2];
|
||||||
|
float lz = debugLabelXZ[i * 2 + 1];
|
||||||
|
projectLabel(debugLabelsTerrain.get(i), lx, debugLabelTH[i], lz,
|
||||||
|
String.format(java.util.Locale.US, "%.1f", debugLabelTH[i]));
|
||||||
|
float lvh = debugLabelVH[i];
|
||||||
|
if (!Float.isNaN(lvh)) {
|
||||||
|
projectLabel(debugLabelsVoxel.get(i), lx, lvh, lz,
|
||||||
|
String.format(java.util.Locale.US, "%.1f", lvh));
|
||||||
|
} else {
|
||||||
|
debugLabelsVoxel.get(i).setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
|
float lbh = debugLabelBH[i];
|
||||||
|
if (!Float.isNaN(lbh)) {
|
||||||
|
projectLabel(debugLabelsBaked.get(i), lx, lbh, lz,
|
||||||
|
String.format(java.util.Locale.US, "%.1f", lbh));
|
||||||
|
} else {
|
||||||
|
debugLabelsBaked.get(i).setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Mauszeiger-Höhe → SharedInput (echter Raycast gegen alle Geometrien) ──
|
||||||
|
float msx = input.mouseScreenX;
|
||||||
|
if (msx >= 0f) {
|
||||||
|
float msy = cam.getHeight() - input.mouseScreenY;
|
||||||
|
Hit mouseHit = raycastHit(msx, msy);
|
||||||
|
input.mouseOverlayHeight = (mouseHit != null && mouseHit.pos != null)
|
||||||
|
? mouseHit.pos.y : Float.NaN;
|
||||||
|
} else {
|
||||||
|
input.mouseOverlayHeight = Float.NaN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void projectLabel(com.jme3.font.BitmapText bt,
|
||||||
|
float wx, float wy, float wz, String text) {
|
||||||
|
Vector3f screen = cam.getScreenCoordinates(new Vector3f(wx, wy + 0.3f, wz));
|
||||||
|
boolean onScreen = screen.z > 0f && screen.z < 1f
|
||||||
|
&& screen.x > 0 && screen.x < cam.getWidth()
|
||||||
|
&& screen.y > 10 && screen.y < cam.getHeight();
|
||||||
|
if (onScreen) {
|
||||||
|
bt.setText(text);
|
||||||
|
bt.setLocalTranslation(screen.x, screen.y, 0);
|
||||||
|
bt.setCullHint(com.jme3.scene.Spatial.CullHint.Never);
|
||||||
|
} else {
|
||||||
|
bt.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void bakeAll(List<VoxelChunk> toProcess) {
|
private void bakeAll(List<VoxelChunk> toProcess) {
|
||||||
saveAll();
|
saveAll();
|
||||||
List<VoxelChunk> nonEmpty = new java.util.ArrayList<>();
|
List<VoxelChunk> nonEmpty = new java.util.ArrayList<>();
|
||||||
@@ -1595,18 +1894,22 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// ── Schritt 1: Höhenfeld ──────────────────────────────────────────
|
// ── Schritt 1: Höhenfeld ──────────────────────────────────────────
|
||||||
// Key-Schema: (cx*C + bx + 30000) * 60001 + (cz*C + bz + 30000)
|
// Key-Schema: (cx*C + bx + 30000) * 60001 + (cz*C + bz + 30000)
|
||||||
// Überlapp-Voxel (bx = CELLS) werden ausgelassen.
|
// Überlapp-Voxel (bx = CELLS) werden ausgelassen.
|
||||||
Map<Long, Integer> hfMap = new HashMap<>();
|
Map<Long, Float> hfMap = new HashMap<>();
|
||||||
for (VoxelChunk c : nonEmpty) {
|
for (VoxelChunk c : nonEmpty) {
|
||||||
long k = chunkKey(c.cx, c.cy, c.cz);
|
long k = chunkKey(c.cx, c.cy, c.cz);
|
||||||
float[] buf = curBufs.get(k);
|
float[] buf = curBufs.get(k);
|
||||||
for (int bz = 0; bz < C; bz++) {
|
for (int bz = 0; bz < C; bz++) {
|
||||||
for (int bx = 0; bx < C; bx++) {
|
for (int bx = 0; bx < C; bx++) {
|
||||||
for (int by = blurN - 1; by >= 0; by--) {
|
for (int by = blurN - 1; by >= 0; by--) {
|
||||||
if (buf[c.idx(bx, by, bz)] > 0) {
|
float d0 = buf[c.idx(bx, by, bz)];
|
||||||
int wiy = c.cy * C + by;
|
if (d0 > 0) {
|
||||||
|
float d1 = (by + 1 < blurN) ? buf[c.idx(bx, by + 1, bz)] : -1f;
|
||||||
|
float t = (Math.abs(d1 - d0) < 0.001f) ? 0.5f
|
||||||
|
: Math.max(0f, Math.min(1f, -d0 / (d1 - d0)));
|
||||||
|
float wfy = c.cy * C + by + t;
|
||||||
long hk = (long)(c.cx * C + bx + 30000) * 60001L
|
long hk = (long)(c.cx * C + bx + 30000) * 60001L
|
||||||
+ (c.cz * C + bz + 30000);
|
+ (c.cz * C + bz + 30000);
|
||||||
hfMap.merge(hk, wiy, Math::max);
|
hfMap.merge(hk, wfy, Math::max);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1631,11 +1934,16 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
for (int bz = 0; bz < C; bz++) {
|
for (int bz = 0; bz < C; bz++) {
|
||||||
for (int bx = 0; bx < C; bx++) {
|
for (int bx = 0; bx < C; bx++) {
|
||||||
for (int by = VoxelChunk.SIZE - 1; by >= 0; by--) {
|
for (int by = VoxelChunk.SIZE - 1; by >= 0; by--) {
|
||||||
if (nc.getDensity(bx, by, bz) > 0) {
|
float d0 = nc.getDensity(bx, by, bz);
|
||||||
int wiy = ncy * C + by;
|
if (d0 > 0) {
|
||||||
|
float d1 = (by + 1 < VoxelChunk.SIZE)
|
||||||
|
? nc.getDensity(bx, by + 1, bz) : -1f;
|
||||||
|
float t = (Math.abs(d1 - d0) < 0.001f) ? 0.5f
|
||||||
|
: Math.max(0f, Math.min(1f, -d0 / (d1 - d0)));
|
||||||
|
float wfy = ncy * C + by + t;
|
||||||
long hk = (long)(ncx * C + bx + 30000) * 60001L
|
long hk = (long)(ncx * C + bx + 30000) * 60001L
|
||||||
+ (ncz * C + bz + 30000);
|
+ (ncz * C + bz + 30000);
|
||||||
hfMap.merge(hk, wiy, Math::max);
|
hfMap.merge(hk, wfy, Math::max);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1690,9 +1998,9 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// kleinsten Höhe > h0 bzw. größten Höhe < h0 wählen. Betraf 17 von 10002 Spalten
|
// kleinsten Höhe > h0 bzw. größten Höhe < h0 wählen. Betraf 17 von 10002 Spalten
|
||||||
// in den Test-Chunks, max. Abweichung 1,0 (diag_realwall*.jsh im scratchpad).
|
// in den Test-Chunks, max. Abweichung 1,0 (diag_realwall*.jsh im scratchpad).
|
||||||
Map<Long, Float> smoothMap = new HashMap<>(hfMap.size());
|
Map<Long, Float> smoothMap = new HashMap<>(hfMap.size());
|
||||||
for (Map.Entry<Long, Integer> entry : hfMap.entrySet()) {
|
for (Map.Entry<Long, Float> entry : hfMap.entrySet()) {
|
||||||
long hk0 = entry.getKey();
|
long hk0 = entry.getKey();
|
||||||
int h0 = entry.getValue();
|
float h0 = entry.getValue();
|
||||||
int colZ = (int)(hk0 % 60001L) - 30000;
|
int colZ = (int)(hk0 % 60001L) - 30000;
|
||||||
int colX = (int)(hk0 / 60001L) - 30000;
|
int colX = (int)(hk0 / 60001L) - 30000;
|
||||||
|
|
||||||
@@ -1706,15 +2014,15 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// mit der KLEINSTEN Höhe > h0 (bzw. GRÖSSTEN Höhe < h0) wählen, also den
|
// mit der KLEINSTEN Höhe > h0 (bzw. GRÖSSTEN Höhe < h0) wählen, also den
|
||||||
// nächstgelegenen tatsächlichen Stufen-Nachbarn, nicht irgendeinen weiter
|
// nächstgelegenen tatsächlichen Stufen-Nachbarn, nicht irgendeinen weiter
|
||||||
// entfernten. Validiert gegen reale Chunk-Daten (diag_realwall*.jsh).
|
// entfernten. Validiert gegen reale Chunk-Daten (diag_realwall*.jsh).
|
||||||
Integer hUp = null; int dUp = -1;
|
Float hUp = null; int dUp = -1;
|
||||||
Integer hDown = null; int dDownRaw = -1;
|
Float hDown = null; int dDownRaw = -1;
|
||||||
for (int r = 1; r <= RADIUS && (hUp == null || hDown == null); r++) {
|
for (int r = 1; r <= RADIUS && (hUp == null || hDown == null); r++) {
|
||||||
Integer bestUpThisRing = null, bestDownThisRing = null;
|
Float bestUpThisRing = null, bestDownThisRing = null;
|
||||||
for (int dz = -r; dz <= r; dz++) {
|
for (int dz = -r; dz <= r; dz++) {
|
||||||
for (int dx = -r; dx <= r; dx++) {
|
for (int dx = -r; dx <= r; dx++) {
|
||||||
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) continue;
|
if (Math.max(Math.abs(dx), Math.abs(dz)) != r) continue;
|
||||||
long hkN = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000);
|
long hkN = (long)(colX + dx + 30000) * 60001L + (colZ + dz + 30000);
|
||||||
Integer hN = hfMap.get(hkN);
|
Float hN = hfMap.get(hkN);
|
||||||
if (hN == null) continue;
|
if (hN == null) continue;
|
||||||
if (hN > h0 && (bestUpThisRing == null || hN < bestUpThisRing)) bestUpThisRing = hN;
|
if (hN > h0 && (bestUpThisRing == null || hN < bestUpThisRing)) bestUpThisRing = hN;
|
||||||
if (hN < h0 && (bestDownThisRing == null || hN > bestDownThisRing)) bestDownThisRing = hN;
|
if (hN < h0 && (bestDownThisRing == null || hN > bestDownThisRing)) bestDownThisRing = hN;
|
||||||
@@ -1740,9 +2048,13 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// abrupten Sprungs zwischen "geramped" und "unverändert flach".
|
// abrupten Sprungs zwischen "geramped" und "unverändert flach".
|
||||||
float smoothH;
|
float smoothH;
|
||||||
if (hUp == null) {
|
if (hUp == null) {
|
||||||
smoothH = h0; // Plateau: kein höherer Nachbar -> bewusst flach (Originalregel)
|
smoothH = h0; // Plateau
|
||||||
|
} else if (hDown == null) {
|
||||||
|
// Kein niedrigerer Voxel-Nachbar: obere Rampe zum Rand hin auslaufen lassen.
|
||||||
|
float dDown = RADIUS - 1;
|
||||||
|
smoothH = (h0 * dUp + hUp * dDown) / (dUp + dDown);
|
||||||
} else {
|
} else {
|
||||||
float dDown = (hDown != null) ? (dDownRaw - 1) : (RADIUS - 1);
|
float dDown = dDownRaw - 1;
|
||||||
smoothH = (dDown <= 0f) ? h0 : (h0 * dUp + hUp * dDown) / (dUp + dDown);
|
smoothH = (dDown <= 0f) ? h0 : (h0 * dUp + hUp * dDown) / (dUp + dDown);
|
||||||
}
|
}
|
||||||
smoothMap.put(hk0, smoothH);
|
smoothMap.put(hk0, smoothH);
|
||||||
@@ -1856,14 +2168,15 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
extLogged++;
|
extLogged++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.info("Perimeter-Übergang: {} Erweiterungs-Spalten jenseits des Strukturrands erzeugt.", extCount);
|
log.info("Perimeter-Übergang: {} Erweiterungs-Spalten berechnet (aktuell deaktiviert – saubere Kante).", extCount);
|
||||||
// Erweiterungs-Spalten in hfMap/smoothMap einspeisen, damit Schritt 3 sie mitschreibt.
|
// Erweiterungs-Spalten DEAKTIVIERT: liefert saubere Kante an der Voxel-Grenze
|
||||||
for (Map.Entry<Long, Float> e : extensionMap.entrySet()) {
|
// statt der problematischen flachen Rampe. Zum Reaktivieren diesen Block einkommentieren:
|
||||||
|
/* for (Map.Entry<Long, Float> e : extensionMap.entrySet()) {
|
||||||
long hk = e.getKey();
|
long hk = e.getKey();
|
||||||
if (hfMap.containsKey(hk)) continue;
|
if (hfMap.containsKey(hk)) continue;
|
||||||
hfMap.put(hk, Math.round(e.getValue()));
|
hfMap.put(hk, e.getValue());
|
||||||
smoothMap.put(hk, e.getValue());
|
smoothMap.put(hk, e.getValue());
|
||||||
}
|
} */
|
||||||
|
|
||||||
// ── Schritt 3: Dichte anpassen ────────────────────────────────────
|
// ── Schritt 3: Dichte anpassen ────────────────────────────────────
|
||||||
// ALLE Spalten aus hfMap bekommen den Dichte-Gradienten geschrieben, auch
|
// ALLE Spalten aus hfMap bekommen den Dichte-Gradienten geschrieben, auch
|
||||||
@@ -1889,7 +2202,7 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
for (int bx = 0; bx < C; bx++) {
|
for (int bx = 0; bx < C; bx++) {
|
||||||
int colX = c.cx * C + bx;
|
int colX = c.cx * C + bx;
|
||||||
long hk0 = (long)(colX + 30000) * 60001L + (colZ + 30000);
|
long hk0 = (long)(colX + 30000) * 60001L + (colZ + 30000);
|
||||||
Integer h0 = hfMap.get(hk0);
|
Float h0 = hfMap.get(hk0);
|
||||||
Float smoothH = smoothMap.get(hk0);
|
Float smoothH = smoothMap.get(hk0);
|
||||||
if (h0 == null || smoothH == null) continue;
|
if (h0 == null || smoothH == null) continue;
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user