voxel umgestellt
This commit is contained in:
@@ -930,6 +930,16 @@ public class SharedInput {
|
|||||||
public volatile int bakeTotal = 0;
|
public volatile int bakeTotal = 0;
|
||||||
/** 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;
|
||||||
|
/** JME → JFX: Anzahl dirty Chunks in der Session (0 = nichts zu backen). */
|
||||||
|
public volatile int sessionDirtyVoxelCount = 0;
|
||||||
|
/** JFX → JME: Nur dirty Chunks backen (Bake-on-Exit). */
|
||||||
|
public volatile boolean bakeOnExitRequested = false;
|
||||||
|
/** JFX → JME: Laufenden Bake-on-Exit abbrechen. */
|
||||||
|
public volatile boolean bakeCancelRequested = false;
|
||||||
|
/** JME → JFX: Bake-on-Exit abgeschlossen (true) oder abgebrochen (false). */
|
||||||
|
public volatile boolean bakeOnExitSuccess = false;
|
||||||
|
/** JME → JFX: Bake-on-Exit ist fertig (Erfolg oder Abbruch). */
|
||||||
|
public volatile boolean bakeOnExitDone = false;
|
||||||
|
|
||||||
/** JFX → JME: Höhen-Overlay ein/aus (Terrain / Voxel / Baked im 100m-Radius). */
|
/** JFX → JME: Höhen-Overlay ein/aus (Terrain / Voxel / Baked im 100m-Radius). */
|
||||||
public volatile boolean debugHeightOverlayEnabled = false;
|
public volatile boolean debugHeightOverlayEnabled = false;
|
||||||
@@ -937,22 +947,20 @@ public class SharedInput {
|
|||||||
/** JFX → JME: 1m-Gitternetz auf Terrain/Voxel ein/aus. */
|
/** JFX → JME: 1m-Gitternetz auf Terrain/Voxel ein/aus. */
|
||||||
public volatile boolean showGrid = true;
|
public volatile boolean showGrid = true;
|
||||||
|
|
||||||
/**
|
/** JME → JME: Keys von Chunks die nach einem Bake neu geladen werden müssen. */
|
||||||
* Per BFS selektierte gebackene Chunks (chunkKey-kodiert).
|
public final java.util.concurrent.ConcurrentLinkedQueue<Long> sculptInvalidateKeys
|
||||||
* Thread-sicher; JME schreibt, JFX liest (nur size() für Anzeige).
|
= new java.util.concurrent.ConcurrentLinkedQueue<>();
|
||||||
*/
|
|
||||||
public final java.util.Set<Long> markedDeleteBakedKeys =
|
/** JFX → JME: Bereinigung starten (Flags unten bestimmen was). */
|
||||||
java.util.concurrent.ConcurrentHashMap.newKeySet();
|
public volatile boolean cleanupUndergroundRequested = false;
|
||||||
/** JME/JFX → JME: Wireframe-Visualisierung der Selektion neu berechnen. */
|
/** JFX → JME: unterirdische Chunks (cy < 0) entfernen. */
|
||||||
public volatile boolean markedBakedSelectionDirty = false;
|
public volatile boolean cleanupUndergroundVoxels = false;
|
||||||
/** JFX → JME: selektierte Chunks löschen. */
|
/** JFX → JME: verwaiste .j3o ohne zugehörige .blvc löschen. */
|
||||||
public volatile boolean deleteMarkedBakedRequested = false;
|
public volatile boolean cleanupOrphanedBaked = false;
|
||||||
/** JFX → JME: selektierte Chunks aus Pre-Bake-Backup wiederherstellen. */
|
/** JFX → JME: alle geladenen Chunks als dirty markieren (→ Bake-on-Exit). */
|
||||||
public volatile boolean revertMarkedBakedRequested = false;
|
public volatile boolean markAllChunksDirtyRequested = false;
|
||||||
/** JFX → JME: unterirdische Voxel-Spalten (Oberfläche < Basis-Terrain) löschen. */
|
/** JME → JFX: Ergebnis der Bereinigung (null = läuft noch / nicht gestartet). */
|
||||||
public volatile boolean cleanupUndergroundRequested = false;
|
public volatile String cleanupUndergroundStatus = null;
|
||||||
/** JME → JFX: Ergebnis der Unterirdisch-Bereinigung (null = läuft noch / nicht gestartet). */
|
|
||||||
public volatile String cleanupUndergroundStatus = null;
|
|
||||||
|
|
||||||
/** Terrain-Slot (0-7) für flache Voxel-Flächen, -1 = kein Slot. */
|
/** Terrain-Slot (0-7) für flache Voxel-Flächen, -1 = kein Slot. */
|
||||||
public volatile int voxelFlatSlot = -1;
|
public volatile int voxelFlatSlot = -1;
|
||||||
|
|||||||
@@ -71,8 +71,6 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
private Material bakedMat = null;
|
private Material bakedMat = null;
|
||||||
private boolean gridVisible = true;
|
private boolean gridVisible = true;
|
||||||
private Material highlightMat = null;
|
private Material highlightMat = null;
|
||||||
private Material selectionWireframeMat = null;
|
|
||||||
private final Set<Long> appliedSelectionKeys = new HashSet<>();
|
|
||||||
|
|
||||||
// ── innere Klasse ─────────────────────────────────────────────────────────
|
// ── innere Klasse ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -125,10 +123,6 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
highlightMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
highlightMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
highlightMat.setColor("Color", new ColorRGBA(1f, 0.65f, 0f, 1f));
|
highlightMat.setColor("Color", new ColorRGBA(1f, 0.65f, 0f, 1f));
|
||||||
|
|
||||||
selectionWireframeMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
|
||||||
selectionWireframeMat.setColor("Color", new ColorRGBA(0.1f, 1f, 0.45f, 1f));
|
|
||||||
selectionWireframeMat.getAdditionalRenderState().setWireframe(true);
|
|
||||||
|
|
||||||
brushIndicator = buildBrushIndicator();
|
brushIndicator = buildBrushIndicator();
|
||||||
app.getRootNode().attachChild(brushIndicator);
|
app.getRootNode().attachChild(brushIndicator);
|
||||||
|
|
||||||
@@ -138,45 +132,11 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
@Override
|
@Override
|
||||||
protected void cleanup(Application application) {
|
protected void cleanup(Application application) {
|
||||||
saveAllDirty();
|
saveAllDirty();
|
||||||
// Wireframe-Material zurücksetzen
|
|
||||||
for (long key : appliedSelectionKeys) {
|
|
||||||
EditableMesh em = meshes.get(key);
|
|
||||||
if (em != null && em.geo != null && normalMat != null) {
|
|
||||||
em.geo.setMaterial(normalMat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appliedSelectionKeys.clear();
|
|
||||||
sculptRoot.removeFromParent();
|
sculptRoot.removeFromParent();
|
||||||
if (brushIndicator != null) brushIndicator.removeFromParent();
|
if (brushIndicator != null) brushIndicator.removeFromParent();
|
||||||
meshes.clear();
|
meshes.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectionWireframe() {
|
|
||||||
Set<Long> current = new HashSet<>(input.markedDeleteBakedKeys);
|
|
||||||
// Wireframe von nicht mehr selektierten Chunks entfernen
|
|
||||||
Iterator<Long> it = appliedSelectionKeys.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
long key = it.next();
|
|
||||||
if (!current.contains(key)) {
|
|
||||||
EditableMesh em = meshes.get(key);
|
|
||||||
if (em != null && em.geo != null) {
|
|
||||||
em.geo.setMaterial(key == selectedKey ? highlightMat : normalMat);
|
|
||||||
}
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Wireframe auf neu selektierte Chunks anwenden
|
|
||||||
for (long key : current) {
|
|
||||||
if (!appliedSelectionKeys.contains(key)) {
|
|
||||||
EditableMesh em = meshes.get(key);
|
|
||||||
if (em != null && em.geo != null) {
|
|
||||||
em.geo.setMaterial(selectionWireframeMat);
|
|
||||||
appliedSelectionKeys.add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override protected void onEnable() {}
|
@Override protected void onEnable() {}
|
||||||
@Override protected void onDisable() {}
|
@Override protected void onDisable() {}
|
||||||
|
|
||||||
@@ -195,12 +155,6 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
if (input.sculptRescanNeeded) {
|
if (input.sculptRescanNeeded) {
|
||||||
input.sculptRescanNeeded = false;
|
input.sculptRescanNeeded = false;
|
||||||
rescanBakedChunks();
|
rescanBakedChunks();
|
||||||
input.markedBakedSelectionDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.markedBakedSelectionDirty) {
|
|
||||||
input.markedBakedSelectionDirty = false;
|
|
||||||
updateSelectionWireframe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateBrushIndicator();
|
updateBrushIndicator();
|
||||||
@@ -262,6 +216,12 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
// ── Scan / Laden ──────────────────────────────────────────────────────────
|
// ── Scan / Laden ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private void rescanBakedChunks() {
|
private void rescanBakedChunks() {
|
||||||
|
// Neu gebackene Chunks aus dem Cache werfen damit sie frisch geladen werden
|
||||||
|
Long invalidKey;
|
||||||
|
while ((invalidKey = input.sculptInvalidateKeys.poll()) != null) {
|
||||||
|
EditableMesh old = meshes.remove(invalidKey);
|
||||||
|
if (old != null) old.node.removeFromParent();
|
||||||
|
}
|
||||||
// Geladene Meshes entfernen, deren Baked-Dateien nicht mehr existieren
|
// Geladene Meshes entfernen, deren Baked-Dateien nicht mehr existieren
|
||||||
Iterator<Map.Entry<Long, EditableMesh>> it = meshes.entrySet().iterator();
|
Iterator<Map.Entry<Long, EditableMesh>> it = meshes.entrySet().iterator();
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
@@ -862,6 +822,13 @@ public class SculptedMeshEditorState extends BaseAppState {
|
|||||||
return ((long)(cx & 0xFFFF)) | (((long)(cy & 0xFFFF)) << 16) | (((long)(cz & 0xFFFF)) << 32);
|
return ((long)(cx & 0xFFFF)) | (((long)(cy & 0xFFFF)) << 16) | (((long)(cz & 0xFFFF)) << 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Zeigt oder verbirgt alle gebackenen Sculpt-Meshes (beim Betreten/Verlassen des Voxel-Tabs). */
|
||||||
|
public void setBakedVisible(boolean visible) {
|
||||||
|
if (sculptRoot != null) {
|
||||||
|
sculptRoot.setCullHint(visible ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Raycast gegen alle gebackenen Voxel-Meshes; gibt den nächsten Treffpunkt oder null zurück. */
|
/** Raycast gegen alle gebackenen Voxel-Meshes; gibt den nächsten Treffpunkt oder null zurück. */
|
||||||
public com.jme3.math.Vector3f raycastGeometry(com.jme3.math.Ray ray) {
|
public com.jme3.math.Vector3f raycastGeometry(com.jme3.math.Ray ray) {
|
||||||
if (sculptRoot == null) return null;
|
if (sculptRoot == null) return null;
|
||||||
|
|||||||
@@ -1389,7 +1389,6 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
private void processTextureEdits() {
|
private void processTextureEdits() {
|
||||||
SharedInput.TextureEdit edit;
|
SharedInput.TextureEdit edit;
|
||||||
int processed = 0;
|
int processed = 0;
|
||||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
|
||||||
while ((edit = input.textureEditQueue.poll()) != null && processed < MAX_EDITS_PER_FRAME) {
|
while ((edit = input.textureEditQueue.poll()) != null && processed < MAX_EDITS_PER_FRAME) {
|
||||||
processed++;
|
processed++;
|
||||||
float jmeX = (float)(edit.screenX() * input.viewportScaleX);
|
float jmeX = (float)(edit.screenX() * input.viewportScaleX);
|
||||||
@@ -1402,7 +1401,6 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
SurfaceHit hit = raycastSurfaceWithType(ray);
|
SurfaceHit hit = raycastSurfaceWithType(ray);
|
||||||
if (hit == null) continue;
|
if (hit == null) continue;
|
||||||
Vector3f contact = hit.point;
|
Vector3f contact = hit.point;
|
||||||
if (!hit.voxelMesh && ves != null && ves.terrainTypeAt(contact.x, contact.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) continue;
|
|
||||||
|
|
||||||
int selIdx = input.textureTool.textureIndex.getSelectedIndex();
|
int selIdx = input.textureTool.textureIndex.getSelectedIndex();
|
||||||
float str = (float) input.textureTool.brushStrength.getValue();
|
float str = (float) input.textureTool.brushStrength.getValue();
|
||||||
@@ -1763,37 +1761,21 @@ public class TerrainEditorState extends BaseAppState {
|
|||||||
brushRadius = (layer == 0)
|
brushRadius = (layer == 0)
|
||||||
? (float) input.heightTool.brushRadius.getValue()
|
? (float) input.heightTool.brushRadius.getValue()
|
||||||
: (float) input.textureTool.brushRadius.getValue();
|
: (float) input.textureTool.brushRadius.getValue();
|
||||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
|
||||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
|
||||||
contactPoint = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (layer == 3) {
|
} else if (layer == 3) {
|
||||||
contactPoint = raycastSurface(ray);
|
contactPoint = raycastSurface(ray);
|
||||||
if (contactPoint != null) {
|
if (contactPoint != null) {
|
||||||
brushRadius = (float) input.grassTool.brushRadius.getValue();
|
brushRadius = (float) input.grassTool.brushRadius.getValue();
|
||||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
|
||||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
|
||||||
contactPoint = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (layer == SharedInput.LAYER_GRASS_VERTEX) {
|
} else if (layer == SharedInput.LAYER_GRASS_VERTEX) {
|
||||||
contactPoint = raycastSurface(ray);
|
contactPoint = raycastSurface(ray);
|
||||||
if (contactPoint != null) {
|
if (contactPoint != null) {
|
||||||
brushRadius = (float) input.grassVertexTool.brushRadius.getValue();
|
brushRadius = (float) input.grassVertexTool.brushRadius.getValue();
|
||||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
|
||||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
|
||||||
contactPoint = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (layer == SharedInput.LAYER_TERRAIN_GRASS) {
|
} else if (layer == SharedInput.LAYER_TERRAIN_GRASS) {
|
||||||
contactPoint = raycastSurface(ray);
|
contactPoint = raycastSurface(ray);
|
||||||
if (contactPoint != null) {
|
if (contactPoint != null) {
|
||||||
brushRadius = (float) input.terrainGrassTool.brushRadius.getValue();
|
brushRadius = (float) input.terrainGrassTool.brushRadius.getValue();
|
||||||
VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class);
|
|
||||||
if (ves != null && ves.terrainTypeAt(contactPoint.x, contactPoint.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) {
|
|
||||||
contactPoint = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.jme3.texture.Texture;
|
|||||||
import com.jme3.texture.Texture2D;
|
import com.jme3.texture.Texture2D;
|
||||||
import com.jme3.texture.image.ColorSpace;
|
import com.jme3.texture.image.ColorSpace;
|
||||||
import com.jme3.util.BufferUtils;
|
import com.jme3.util.BufferUtils;
|
||||||
|
import de.blight.common.SculptedMeshIO;
|
||||||
import de.blight.common.VoxelChunk;
|
import de.blight.common.VoxelChunk;
|
||||||
import de.blight.common.VoxelChunkIO;
|
import de.blight.common.VoxelChunkIO;
|
||||||
import de.blight.editor.SharedInput;
|
import de.blight.editor.SharedInput;
|
||||||
@@ -104,6 +105,8 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
* nicht mehrfach pro Edit-Event.
|
* nicht mehrfach pro Edit-Event.
|
||||||
*/
|
*/
|
||||||
private final Set<Long> dirtyChunksThisFrame = new HashSet<>();
|
private final Set<Long> dirtyChunksThisFrame = new HashSet<>();
|
||||||
|
/** Session-weite dirty Chunks (für Bake-on-Exit). Nur im JME-Thread modifizieren. */
|
||||||
|
private final Set<Long> sessionDirtyChunks = new HashSet<>();
|
||||||
|
|
||||||
// ── Timers ───────────────────────────────────────────────────────────────
|
// ── Timers ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -212,6 +215,7 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
this.cam = app.getCamera();
|
this.cam = app.getCamera();
|
||||||
|
|
||||||
voxelRoot = new Node("voxelEditorRoot");
|
voxelRoot = new Node("voxelEditorRoot");
|
||||||
|
voxelRoot.setCullHint(Spatial.CullHint.Always); // nur im Voxel-Tab sichtbar
|
||||||
app.getRootNode().attachChild(voxelRoot);
|
app.getRootNode().attachChild(voxelRoot);
|
||||||
|
|
||||||
// TerrainEditorState wurde vor uns attached und ist zu diesem Zeitpunkt initialisiert
|
// TerrainEditorState wurde vor uns attached und ist zu diesem Zeitpunkt initialisiert
|
||||||
@@ -323,38 +327,115 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selektierte Baked-Chunks löschen
|
// Bake-on-Exit: nur dirty Chunks backen
|
||||||
if (input.deleteMarkedBakedRequested) {
|
if (input.bakeOnExitRequested) {
|
||||||
input.deleteMarkedBakedRequested = false;
|
input.bakeOnExitRequested = false;
|
||||||
Set<Long> toDelete = new HashSet<>(input.markedDeleteBakedKeys);
|
input.bakeCancelRequested = false;
|
||||||
input.markedDeleteBakedKeys.clear();
|
List<VoxelChunk> toBake = new ArrayList<>();
|
||||||
input.markedBakedSelectionDirty = true;
|
for (long key : sessionDirtyChunks) {
|
||||||
|
VoxelChunk c = chunks.get(key);
|
||||||
|
if (c != null) toBake.add(c);
|
||||||
|
}
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
deleteMarkedBaked(toDelete);
|
bakeAll(toBake);
|
||||||
|
if (!input.bakeCancelRequested) {
|
||||||
|
app.enqueue(() -> {
|
||||||
|
sessionDirtyChunks.clear();
|
||||||
|
input.sessionDirtyVoxelCount = 0;
|
||||||
|
});
|
||||||
|
input.bakeOnExitSuccess = true;
|
||||||
|
} else {
|
||||||
|
input.bakeOnExitSuccess = false;
|
||||||
|
}
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
log.error("Baked-Löschen fehlgeschlagen: {}", e.getMessage(), e);
|
log.error("Bake-on-Exit fehlgeschlagen: {}", e.getMessage(), e);
|
||||||
input.bakeStatusMsg = "FEHLER: " + e.getMessage();
|
input.bakeStatusMsg = "FEHLER: " + e.getMessage();
|
||||||
|
input.bakeOnExitSuccess = false;
|
||||||
|
} finally {
|
||||||
|
input.bakeOnExitDone = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selektierte Chunks aus Pre-Bake-Backup wiederherstellen
|
// Bereinigung
|
||||||
if (input.revertMarkedBakedRequested) {
|
if (input.cleanupUndergroundRequested) {
|
||||||
input.revertMarkedBakedRequested = false;
|
input.cleanupUndergroundRequested = false;
|
||||||
Set<Long> toRevert = new HashSet<>(input.markedDeleteBakedKeys);
|
final boolean doUnderground = input.cleanupUndergroundVoxels;
|
||||||
input.markedDeleteBakedKeys.clear();
|
final boolean doOrphaned = input.cleanupOrphanedBaked;
|
||||||
input.markedBakedSelectionDirty = true;
|
|
||||||
executor.submit(() -> {
|
executor.submit(() -> {
|
||||||
|
int removedVoxel = 0;
|
||||||
|
int removedBaked = 0;
|
||||||
try {
|
try {
|
||||||
revertMarkedBaked(toRevert);
|
if (doUnderground) {
|
||||||
} catch (Throwable e) {
|
// Unterirdische Chunks (cy < 0): .blvc löschen, aus Memory entfernen
|
||||||
log.error("Markierter-Revert fehlgeschlagen: {}", e.getMessage(), e);
|
List<long[]> toRemove = new ArrayList<>();
|
||||||
input.bakeStatusMsg = "Revert FEHLER: " + e.getMessage();
|
for (Map.Entry<Long, VoxelChunk> e : new ArrayList<>(chunks.entrySet())) {
|
||||||
|
VoxelChunk c = e.getValue();
|
||||||
|
if (c.cy < 0) toRemove.add(new long[]{e.getKey(), c.cx, c.cy, c.cz});
|
||||||
|
}
|
||||||
|
for (long[] t : toRemove) {
|
||||||
|
try { VoxelChunkIO.delete((int)t[1], (int)t[2], (int)t[3]); } catch (Exception ignored) {}
|
||||||
|
final long key = t[0];
|
||||||
|
app.enqueue(() -> {
|
||||||
|
chunks.remove(key);
|
||||||
|
VoxelChunkNode node = nodes.remove(key);
|
||||||
|
if (node != null) node.removeFromParent();
|
||||||
|
});
|
||||||
|
removedVoxel++;
|
||||||
|
}
|
||||||
|
// Auch .blvc-Dateien mit cy < 0 die nicht im Memory sind
|
||||||
|
for (int[] coord : VoxelChunkIO.loadAll().stream()
|
||||||
|
.filter(c -> c.cy < 0)
|
||||||
|
.map(c -> new int[]{c.cx, c.cy, c.cz})
|
||||||
|
.toList()) {
|
||||||
|
long key = chunkKey(coord[0], coord[1], coord[2]);
|
||||||
|
if (!chunks.containsKey(key)) {
|
||||||
|
try { VoxelChunkIO.delete(coord[0], coord[1], coord[2]); removedVoxel++; } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (doOrphaned) {
|
||||||
|
// Verwaiste gebackene .j3o ohne zugehörige .blvc löschen
|
||||||
|
for (int[] cxyz : SculptedMeshIO.findAllBakedChunks()) {
|
||||||
|
int cx = cxyz[0], cy = cxyz[1], cz = cxyz[2];
|
||||||
|
if (!VoxelChunkIO.exists(cx, cy, cz) && !VoxelChunkIO.preBakeExists(cx, cy, cz)) {
|
||||||
|
for (int lod = 0; lod <= 3; lod++) {
|
||||||
|
try { Files.deleteIfExists(VoxelChunkIO.getBakedPath(cx, cy, cz, lod)); } catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
removedBaked++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<String> parts = new ArrayList<>();
|
||||||
|
if (doUnderground) parts.add(removedVoxel + " unterirdische Chunks entfernt");
|
||||||
|
if (doOrphaned) parts.add(removedBaked + " verwaiste Bakes gelöscht");
|
||||||
|
input.cleanupUndergroundStatus = String.join(", ", parts);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Bereinigung fehlgeschlagen: {}", e.getMessage());
|
||||||
|
input.cleanupUndergroundStatus = "Fehler: " + e.getMessage();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alle Chunks (Memory + Disk) als dirty markieren → Bake-on-Exit backt alles neu
|
||||||
|
if (input.markAllChunksDirtyRequested) {
|
||||||
|
input.markAllChunksDirtyRequested = false;
|
||||||
|
for (long key : chunks.keySet()) sessionDirtyChunks.add(key);
|
||||||
|
// Chunks die noch nicht geladen sind von Disk nachladen, damit der Bake sie kennt
|
||||||
|
executor.submit(() -> {
|
||||||
|
List<VoxelChunk> allDisk = VoxelChunkIO.loadAll();
|
||||||
|
app.enqueue(() -> {
|
||||||
|
for (VoxelChunk c : allDisk) {
|
||||||
|
long key = chunkKey(c.cx, c.cy, c.cz);
|
||||||
|
if (!chunks.containsKey(key)) chunks.put(key, c);
|
||||||
|
sessionDirtyChunks.add(key);
|
||||||
|
}
|
||||||
|
input.sessionDirtyVoxelCount = sessionDirtyChunks.size();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Voxel-Texturen (Slot/Normal-Map) aktualisiert?
|
// Voxel-Texturen (Slot/Normal-Map) aktualisiert?
|
||||||
if (input.voxelTexturesChanged) {
|
if (input.voxelTexturesChanged) {
|
||||||
input.voxelTexturesChanged = false;
|
input.voxelTexturesChanged = false;
|
||||||
@@ -501,6 +582,8 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
addNodeForChunk(key, chunk);
|
addNodeForChunk(key, chunk);
|
||||||
}
|
}
|
||||||
dirtyChunksThisFrame.add(key);
|
dirtyChunksThisFrame.add(key);
|
||||||
|
sessionDirtyChunks.add(key);
|
||||||
|
input.sessionDirtyVoxelCount = sessionDirtyChunks.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -561,6 +644,8 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
addNodeForChunk(key, chunk);
|
addNodeForChunk(key, chunk);
|
||||||
}
|
}
|
||||||
dirtyChunksThisFrame.add(key);
|
dirtyChunksThisFrame.add(key);
|
||||||
|
sessionDirtyChunks.add(key);
|
||||||
|
input.sessionDirtyVoxelCount = sessionDirtyChunks.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -736,18 +821,6 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
int modeIdx = input.voxelTool.mode.getSelectedIndex();
|
int modeIdx = input.voxelTool.mode.getSelectedIndex();
|
||||||
boolean isHorizontal = input.voxelTool.horizontal;
|
boolean isHorizontal = input.voxelTool.horizontal;
|
||||||
|
|
||||||
// Baked-Selektierungs-Modus: keine Voxel-Bearbeitung, nur BFS-Selektion.
|
|
||||||
// Nach der Aktion automatisch auf Sinus (Modus 0) zurückschalten.
|
|
||||||
if (modeIdx == de.blight.editor.tool.VoxelTool.MODE_DELETE_BAKED) {
|
|
||||||
if (action >= 0) {
|
|
||||||
markConnectedBakedStructure(hit.pos.x, hit.pos.y, hit.pos.z);
|
|
||||||
} else {
|
|
||||||
input.markedDeleteBakedKeys.clear();
|
|
||||||
input.markedBakedSelectionDirty = true;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isCave = (modeIdx == de.blight.editor.tool.VoxelTool.MODE_REMOVE);
|
boolean isCave = (modeIdx == de.blight.editor.tool.VoxelTool.MODE_REMOVE);
|
||||||
boolean isColumn = !isCave;
|
boolean isColumn = !isCave;
|
||||||
boolean lower = action < 0;
|
boolean lower = action < 0;
|
||||||
@@ -865,6 +938,8 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
addNodeForChunk(key, chunk);
|
addNodeForChunk(key, chunk);
|
||||||
}
|
}
|
||||||
dirtyChunksThisFrame.add(key);
|
dirtyChunksThisFrame.add(key);
|
||||||
|
sessionDirtyChunks.add(key);
|
||||||
|
input.sessionDirtyVoxelCount = sessionDirtyChunks.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1058,6 +1133,8 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
long key = chunkKey(chunk.cx, chunk.cy, chunk.cz);
|
long key = chunkKey(chunk.cx, chunk.cy, chunk.cz);
|
||||||
if (!chunk.isEmpty() && !nodes.containsKey(key)) addNodeForChunk(key, chunk);
|
if (!chunk.isEmpty() && !nodes.containsKey(key)) addNodeForChunk(key, chunk);
|
||||||
dirtyChunksThisFrame.add(key);
|
dirtyChunksThisFrame.add(key);
|
||||||
|
sessionDirtyChunks.add(key);
|
||||||
|
input.sessionDirtyVoxelCount = sessionDirtyChunks.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1086,95 +1163,6 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Baked-Chunk Markierung ────────────────────────────────────────────────
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flood-fill BFS: Markiert alle zusammenhängend gebackenen Chunks,
|
|
||||||
* ausgehend vom angeklickten Chunk. Bisherige Selektion wird vorher geleert.
|
|
||||||
* LMB = markieren; RMB = Selektion leeren (wird in applyEdit gehandhabt).
|
|
||||||
*/
|
|
||||||
private void markConnectedBakedStructure(float hitX, float hitY, float hitZ) {
|
|
||||||
input.markedDeleteBakedKeys.clear();
|
|
||||||
|
|
||||||
int startCx = VoxelChunk.worldXToCx(hitX);
|
|
||||||
int startCz = VoxelChunk.worldZToCz(hitZ);
|
|
||||||
int startCy = VoxelChunk.worldYToCy(hitY);
|
|
||||||
|
|
||||||
// Nächsten gebackenen Chunk in der Nähe des Treffers finden
|
|
||||||
boolean found = false;
|
|
||||||
outer:
|
|
||||||
for (int delta = 0; delta <= 4; delta++) {
|
|
||||||
for (int dcy : (delta == 0 ? new int[]{0} : new int[]{delta, -delta})) {
|
|
||||||
if (VoxelChunkIO.bakedExists(startCx, startCy + dcy, startCz)) {
|
|
||||||
startCy = startCy + dcy;
|
|
||||||
found = true;
|
|
||||||
break outer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!found) return;
|
|
||||||
|
|
||||||
// 6-Richtungs-BFS durch angrenzende gebackene Chunks
|
|
||||||
Set<Long> visited = new HashSet<>();
|
|
||||||
Queue<long[]> queue = new ArrayDeque<>();
|
|
||||||
long startKey = chunkKey(startCx, startCy, startCz);
|
|
||||||
visited.add(startKey);
|
|
||||||
queue.add(new long[]{startCx, startCy, startCz});
|
|
||||||
|
|
||||||
int[][] dirs = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
|
|
||||||
while (!queue.isEmpty()) {
|
|
||||||
long[] cur = queue.poll();
|
|
||||||
int cx = (int)cur[0], cy = (int)cur[1], cz = (int)cur[2];
|
|
||||||
for (int[] d : dirs) {
|
|
||||||
int ncx = cx + d[0], ncy = cy + d[1], ncz = cz + d[2];
|
|
||||||
long nk = chunkKey(ncx, ncy, ncz);
|
|
||||||
if (!visited.contains(nk) && VoxelChunkIO.bakedExists(ncx, ncy, ncz)) {
|
|
||||||
visited.add(nk);
|
|
||||||
queue.add(new long[]{ncx, ncy, ncz});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Alle gefundenen Chunks selektieren + Wireframe signalisieren
|
|
||||||
for (long key : visited) {
|
|
||||||
input.markedDeleteBakedKeys.add(key);
|
|
||||||
}
|
|
||||||
input.markedBakedSelectionDirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Löscht die selektierten gebackenen Chunks (alle LODs + Sculpt-Overlays).
|
|
||||||
* Läuft im Hintergrund-Thread.
|
|
||||||
*/
|
|
||||||
private void deleteMarkedBaked(Set<Long> keys) {
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
input.bakeStatusMsg = "Keine Chunks markiert.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int deleted = 0;
|
|
||||||
for (long key : keys) {
|
|
||||||
int cx = (int)(key & 0xFFFF); if (cx >= 0x8000) cx -= 0x10000;
|
|
||||||
int cy = (int)((key >> 16) & 0xFFFF); if (cy >= 0x8000) cy -= 0x10000;
|
|
||||||
int cz = (int)((key >> 32) & 0xFFFF); if (cz >= 0x8000) cz -= 0x10000;
|
|
||||||
for (int lod = 0; lod < 3; lod++) {
|
|
||||||
try {
|
|
||||||
Files.deleteIfExists(VoxelChunkIO.getBakedPath(cx, cy, cz, lod));
|
|
||||||
} catch (IOException e) {
|
|
||||||
log.warn("Baked LOD{} löschen fehlgeschlagen ({},{},{}): {}",
|
|
||||||
lod, cx, cy, cz, e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (de.blight.common.SculptedMeshIO.exists(cx, cy, cz)) {
|
|
||||||
try { de.blight.common.SculptedMeshIO.delete(cx, cy, cz); }
|
|
||||||
catch (Exception e) { log.warn("Sculpt-Overlay löschen fehlgeschlagen: {}", e.getMessage()); }
|
|
||||||
}
|
|
||||||
deleted++;
|
|
||||||
}
|
|
||||||
input.sculptRescanNeeded = true;
|
|
||||||
input.bakeStatusMsg = "Gelöscht: " + deleted + " Chunk" + (deleted != 1 ? "s" : "") + ".";
|
|
||||||
log.info("Markierte Baked-Chunks gelöscht: {}.", deleted);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Terrain-Höhe (schneller O(1)-Zugriff) ─────────────────────────────────
|
// ── Terrain-Höhe (schneller O(1)-Zugriff) ─────────────────────────────────
|
||||||
|
|
||||||
private float terrainH(float worldX, float worldZ) {
|
private float terrainH(float worldX, float worldZ) {
|
||||||
@@ -2746,6 +2734,9 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
float bakeSmoothStr = input.bakeSmoothStrength;
|
float bakeSmoothStr = input.bakeSmoothStrength;
|
||||||
float bakeCliffStr = input.bakeCliffNoiseStrength;
|
float bakeCliffStr = input.bakeCliffNoiseStrength;
|
||||||
for (VoxelChunk chunk : nonEmpty) {
|
for (VoxelChunk chunk : nonEmpty) {
|
||||||
|
if (input.bakeCancelRequested) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (bakeChunk(chunk, blurredMap, bakeSmoothStr, bakeCliffStr)) {
|
if (bakeChunk(chunk, blurredMap, bakeSmoothStr, bakeCliffStr)) {
|
||||||
successfullyBaked.add(chunk);
|
successfullyBaked.add(chunk);
|
||||||
} else {
|
} else {
|
||||||
@@ -2756,111 +2747,21 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
input.bakeDone = baked;
|
input.bakeDone = baked;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Voxel-Daten löschen: nur für erfolgreich gebackene Chunks
|
// Voxeldaten (.blvc) bleiben erhalten: nach dem Baken sind Raw-Voxels weiterhin
|
||||||
for (VoxelChunk chunk : successfullyBaked) {
|
// im Voxel-Tab sichtbar und editierbar. .blvc und .j3o koexistieren.
|
||||||
chunk.clear();
|
|
||||||
try { VoxelChunkIO.delete(chunk.cx, chunk.cy, chunk.cz); }
|
|
||||||
catch (Exception e) { log.warn("Voxel-Datei löschen fehlgeschlagen ({},{},{}): {}",
|
|
||||||
chunk.cx, chunk.cy, chunk.cz, e.getMessage()); }
|
|
||||||
}
|
|
||||||
app.enqueue(() -> {
|
|
||||||
for (VoxelChunk chunk : successfullyBaked) {
|
|
||||||
long key = chunkKey(chunk.cx, chunk.cy, chunk.cz);
|
|
||||||
VoxelChunkNode node = nodes.remove(key);
|
|
||||||
if (node != null) node.removeFromParent();
|
|
||||||
chunks.remove(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
int failed = nonEmpty.size() - successfullyBaked.size();
|
int failed = nonEmpty.size() - successfullyBaked.size();
|
||||||
String msg = "Fertig: " + successfullyBaked.size() + " Chunk"
|
String msg = "Fertig: " + successfullyBaked.size() + " Chunk"
|
||||||
+ (successfullyBaked.size() != 1 ? "s" : "") + " gebacken"
|
+ (successfullyBaked.size() != 1 ? "s" : "") + " gebacken"
|
||||||
+ (failed > 0 ? ", " + failed + " fehlgeschlagen (Voxel erhalten)" : "") + ".";
|
+ (failed > 0 ? ", " + failed + " fehlgeschlagen (Voxel erhalten)" : "") + ".";
|
||||||
// bakeTotal/bakeDone werden vom UI-Thread nach Empfang der Statusmeldung zurückgesetzt
|
// bakeTotal/bakeDone werden vom UI-Thread nach Empfang der Statusmeldung zurückgesetzt
|
||||||
|
for (VoxelChunk c : successfullyBaked)
|
||||||
|
input.sculptInvalidateKeys.add(chunkKey(c.cx, c.cy, c.cz));
|
||||||
input.bakeStatusMsg = msg;
|
input.bakeStatusMsg = msg;
|
||||||
input.sculptRescanNeeded = true;
|
input.sculptRescanNeeded = true;
|
||||||
log.info("Voxel-Bake abgeschlossen – {}.", msg);
|
log.info("Voxel-Bake abgeschlossen – {}.", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Stellt die selektierten Chunks auf den Vor-Bake-Zustand zurück:
|
|
||||||
* - Wenn ein Pre-Bake-Backup (.blvc.prebake) existiert, wird es eingespielt.
|
|
||||||
* - In jedem Fall werden die Baked-J3O-Dateien gelöscht, so dass
|
|
||||||
* die aktuellen .blvc-Voxeldaten wieder als ungebackene Meshes sichtbar sind.
|
|
||||||
*/
|
|
||||||
private void revertMarkedBaked(Set<Long> keys) {
|
|
||||||
if (keys.isEmpty()) {
|
|
||||||
input.bakeStatusMsg = "Keine Chunks markiert.";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int restored = 0, noBackup = 0, failed = 0;
|
|
||||||
List<VoxelChunk> reloadedChunks = new java.util.ArrayList<>();
|
|
||||||
|
|
||||||
for (long key : keys) {
|
|
||||||
int cx = (int)(key & 0xFFFF); if (cx >= 0x8000) cx -= 0x10000;
|
|
||||||
int cy = (int)((key >> 16) & 0xFFFF); if (cy >= 0x8000) cy -= 0x10000;
|
|
||||||
int cz = (int)((key >> 32) & 0xFFFF); if (cz >= 0x8000) cz -= 0x10000;
|
|
||||||
try {
|
|
||||||
boolean hadBackup = de.blight.common.VoxelChunkIO.preBakeExists(cx, cy, cz);
|
|
||||||
boolean hasBlvc = de.blight.common.VoxelChunkIO.exists(cx, cy, cz);
|
|
||||||
|
|
||||||
if (!hadBackup && !hasBlvc) {
|
|
||||||
// Kein Backup vorhanden – J3O behalten, sonst entsteht leerer Bereich
|
|
||||||
noBackup++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hadBackup) {
|
|
||||||
// Backup vorhanden → .blvc aus Backup wiederherstellen
|
|
||||||
de.blight.common.VoxelChunkIO.restorePreBake(cx, cy, cz);
|
|
||||||
de.blight.common.VoxelChunkIO.deletePreBake(cx, cy, cz);
|
|
||||||
}
|
|
||||||
// Baked J3O-Dateien löschen, damit das ungebackene Mesh sichtbar wird
|
|
||||||
for (int lod = 0; lod < 3; lod++) {
|
|
||||||
Files.deleteIfExists(de.blight.common.VoxelChunkIO.getBakedPath(cx, cy, cz, lod));
|
|
||||||
}
|
|
||||||
// Chunk-Daten (aus Backup oder aktuellem .blvc) in die Voxel-Engine laden
|
|
||||||
if (de.blight.common.VoxelChunkIO.exists(cx, cy, cz)) {
|
|
||||||
VoxelChunk chunk = de.blight.common.VoxelChunkIO.load(cx, cy, cz);
|
|
||||||
reloadedChunks.add(chunk);
|
|
||||||
}
|
|
||||||
restored++;
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Markierter-Revert fehlgeschlagen ({},{},{}): {}", cx, cy, cz, e.getMessage());
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<VoxelChunk> toAdd = new java.util.ArrayList<>(reloadedChunks);
|
|
||||||
app.enqueue(() -> {
|
|
||||||
for (VoxelChunk chunk : toAdd) {
|
|
||||||
long k = chunkKey(chunk.cx, chunk.cy, chunk.cz);
|
|
||||||
VoxelChunkNode oldNode = nodes.remove(k);
|
|
||||||
if (oldNode != null) oldNode.removeFromParent();
|
|
||||||
chunks.put(k, chunk);
|
|
||||||
addNodeForChunk(k, chunk);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
input.sculptRescanNeeded = true;
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (restored > 0) {
|
|
||||||
sb.append("Bake rückgängig: ").append(restored).append(" Chunk")
|
|
||||||
.append(restored != 1 ? "s" : "").append(".");
|
|
||||||
}
|
|
||||||
if (noBackup > 0) {
|
|
||||||
if (sb.length() > 0) sb.append(" ");
|
|
||||||
sb.append(noBackup).append(" ohne Backup – unverändert.");
|
|
||||||
}
|
|
||||||
if (failed > 0) {
|
|
||||||
if (sb.length() > 0) sb.append(" ");
|
|
||||||
sb.append(failed).append(" fehlgeschlagen.");
|
|
||||||
}
|
|
||||||
if (sb.length() == 0) sb.append("Nichts wiederhergestellt.");
|
|
||||||
input.bakeStatusMsg = sb.toString();
|
|
||||||
log.info("Markierter-Revert: {} erledigt, {} fehlgeschlagen.", restored, failed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Bäckt einen einzelnen Chunk. Gibt true zurück wenn erfolgreich, false bei Fehler. */
|
/** Bäckt einen einzelnen Chunk. Gibt true zurück wenn erfolgreich, false bei Fehler. */
|
||||||
private boolean bakeChunk(VoxelChunk original, Map<Long, VoxelChunk> blurredMap,
|
private boolean bakeChunk(VoxelChunk original, Map<Long, VoxelChunk> blurredMap,
|
||||||
float smoothStrength, float cliffNoiseStrength) {
|
float smoothStrength, float cliffNoiseStrength) {
|
||||||
@@ -3428,11 +3329,6 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
brushIndicator.setCullHint(Spatial.CullHint.Always);
|
brushIndicator.setCullHint(Spatial.CullHint.Always);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Im Selektierungs-Modus nur Mauszeiger, kein Brush-Indikator
|
|
||||||
if (input.voxelTool.mode.getSelectedIndex() == de.blight.editor.tool.VoxelTool.MODE_DELETE_BAKED) {
|
|
||||||
brushIndicator.setCullHint(Spatial.CullHint.Always);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
float mx = input.mouseScreenX;
|
float mx = input.mouseScreenX;
|
||||||
float my = input.mouseScreenY;
|
float my = input.mouseScreenY;
|
||||||
if (mx < 0) {
|
if (mx < 0) {
|
||||||
@@ -3599,11 +3495,22 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// ── Layer-Wechsel-Reaktion ─────────────────────────────────────────────────
|
// ── Layer-Wechsel-Reaktion ─────────────────────────────────────────────────
|
||||||
|
|
||||||
private void onVoxelLayerChanged(boolean entered) {
|
private void onVoxelLayerChanged(boolean entered) {
|
||||||
if (basePlane != null)
|
if (basePlane != null) {
|
||||||
basePlane.setCullHint(entered ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
basePlane.setCullHint(entered ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||||
if (chunkGrid != null)
|
}
|
||||||
|
if (chunkGrid != null) {
|
||||||
chunkGrid.setCullHint(entered ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
chunkGrid.setCullHint(entered ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||||
|
}
|
||||||
// Wireframe-Zustand beim Layer-Wechsel NICHT ändern — wird global via Ctrl+G gesteuert.
|
// Wireframe-Zustand beim Layer-Wechsel NICHT ändern — wird global via Ctrl+G gesteuert.
|
||||||
|
// Raw-Voxels: sichtbar im Voxel-Tab, versteckt außerhalb
|
||||||
|
if (voxelRoot != null)
|
||||||
|
voxelRoot.setCullHint(entered ? Spatial.CullHint.Inherit : Spatial.CullHint.Always);
|
||||||
|
SculptedMeshEditorState smes = getStateManager().getState(SculptedMeshEditorState.class);
|
||||||
|
if (entered) {
|
||||||
|
if (smes != null) smes.setBakedVisible(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (smes != null) smes.setBakedVisible(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Zeigt/verbirgt die Voxel-Chunk-Nodes (wird vom SculptedMeshEditorState gesteuert). */
|
/** Zeigt/verbirgt die Voxel-Chunk-Nodes (wird vom SculptedMeshEditorState gesteuert). */
|
||||||
|
|||||||
@@ -21,17 +21,16 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class VoxelTool extends EditorTool {
|
public class VoxelTool extends EditorTool {
|
||||||
|
|
||||||
public static final int MODE_SINUS = 0;
|
public static final int MODE_SINUS = 0;
|
||||||
public static final int MODE_SPIKE = 1;
|
public static final int MODE_SPIKE = 1;
|
||||||
public static final int MODE_PLATEAU = 2;
|
public static final int MODE_PLATEAU = 2;
|
||||||
public static final int MODE_SMOOTH = 3;
|
public static final int MODE_SMOOTH = 3;
|
||||||
public static final int MODE_REMOVE = 4;
|
public static final int MODE_REMOVE = 4;
|
||||||
public static final int MODE_RESET = 5;
|
public static final int MODE_RESET = 5;
|
||||||
public static final int MODE_DELETE_BAKED = 6;
|
|
||||||
|
|
||||||
public final ChoiceToolParameter mode = new ChoiceToolParameter(
|
public final ChoiceToolParameter mode = new ChoiceToolParameter(
|
||||||
"Modus",
|
"Modus",
|
||||||
new String[]{"Sinus", "Spike", "Plateau", "Smooth", "Aushöhlen", "Zurücksetzen", "Baked selektieren"},
|
new String[]{"Sinus", "Spike", "Plateau", "Smooth", "Aushöhlen", "Zurücksetzen"},
|
||||||
MODE_SINUS,
|
MODE_SINUS,
|
||||||
new String[]{
|
new String[]{
|
||||||
"img/editor/terraintool_sinus.png",
|
"img/editor/terraintool_sinus.png",
|
||||||
@@ -39,7 +38,6 @@ public class VoxelTool extends EditorTool {
|
|||||||
"img/editor/terraintool_plateau.png",
|
"img/editor/terraintool_plateau.png",
|
||||||
"img/editor/terraintool_smooth.png",
|
"img/editor/terraintool_smooth.png",
|
||||||
null,
|
null,
|
||||||
null,
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -336,22 +336,8 @@ public class TerrainPanelController {
|
|||||||
panel.getChildren().addAll(new Separator(), buildVoxelTextureSlotsUI());
|
panel.getChildren().addAll(new Separator(), buildVoxelTextureSlotsUI());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backen-Button (nur beim VoxelTool)
|
// Bake-Parameter + Bereinigung (nur beim VoxelTool)
|
||||||
if (tool instanceof de.blight.editor.tool.VoxelTool) {
|
if (tool instanceof de.blight.editor.tool.VoxelTool) {
|
||||||
Label bakeStatus = new Label("");
|
|
||||||
bakeStatus.setWrapText(true);
|
|
||||||
bakeStatus.setStyle("-fx-text-fill: #555; -fx-font-size: 11;");
|
|
||||||
|
|
||||||
javafx.scene.control.ProgressBar bakeBar = new javafx.scene.control.ProgressBar(0);
|
|
||||||
bakeBar.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
bakeBar.setVisible(false);
|
|
||||||
bakeBar.setManaged(false);
|
|
||||||
|
|
||||||
Label bakeBarLabel = new Label("");
|
|
||||||
bakeBarLabel.setStyle("-fx-font-size: 10; -fx-text-fill: #444;");
|
|
||||||
bakeBarLabel.setVisible(false);
|
|
||||||
bakeBarLabel.setManaged(false);
|
|
||||||
|
|
||||||
// ── Bake-Parameter ────────────────────────────────────────────────
|
// ── Bake-Parameter ────────────────────────────────────────────────
|
||||||
Label smoothLbl = new Label(String.format("Glättung: %.1f", input.bakeSmoothStrength));
|
Label smoothLbl = new Label(String.format("Glättung: %.1f", input.bakeSmoothStrength));
|
||||||
smoothLbl.setStyle("-fx-font-size: 11;");
|
smoothLbl.setStyle("-fx-font-size: 11;");
|
||||||
@@ -373,107 +359,14 @@ public class TerrainPanelController {
|
|||||||
cliffLbl.setText(String.format("Klippen-Noise: %.1f", n.floatValue()));
|
cliffLbl.setText(String.format("Klippen-Noise: %.1f", n.floatValue()));
|
||||||
});
|
});
|
||||||
|
|
||||||
Button bakeBtn = new Button("Voxels backen (J3O)");
|
// ── Bereinigung ──────────────────────────────────────────────────────
|
||||||
bakeBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
bakeBtn.setStyle("-fx-background-color: #4a7eba; -fx-text-fill: white;");
|
|
||||||
bakeBtn.setOnAction(e -> {
|
|
||||||
input.bakeVoxelsRequested = true;
|
|
||||||
bakeStatus.setText("");
|
|
||||||
bakeBtn.setDisable(true);
|
|
||||||
bakeBar.setProgress(0);
|
|
||||||
bakeBar.setVisible(true);
|
|
||||||
bakeBar.setManaged(true);
|
|
||||||
bakeBarLabel.setVisible(true);
|
|
||||||
bakeBarLabel.setManaged(true);
|
|
||||||
bakeBarLabel.setText("Starte...");
|
|
||||||
});
|
|
||||||
|
|
||||||
javafx.animation.Timeline bakePoller = new javafx.animation.Timeline(
|
|
||||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
|
||||||
int total = input.bakeTotal;
|
|
||||||
int done = input.bakeDone;
|
|
||||||
if (total > 0) {
|
|
||||||
bakeBar.setProgress((double) done / total);
|
|
||||||
bakeBarLabel.setText(done + " / " + total + " Chunks");
|
|
||||||
}
|
|
||||||
String msg = input.bakeStatusMsg;
|
|
||||||
if (msg != null) {
|
|
||||||
input.bakeStatusMsg = null;
|
|
||||||
input.bakeTotal = 0;
|
|
||||||
input.bakeDone = 0;
|
|
||||||
bakeStatus.setText(msg);
|
|
||||||
bakeBar.setVisible(false);
|
|
||||||
bakeBar.setManaged(false);
|
|
||||||
bakeBarLabel.setVisible(false);
|
|
||||||
bakeBarLabel.setManaged(false);
|
|
||||||
bakeBtn.setDisable(false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
bakePoller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
|
||||||
bakePoller.play();
|
|
||||||
|
|
||||||
// ── Selektion: Löschen / Bake rückgängig (Modus "Baked selektieren") ──
|
|
||||||
Label selInfoLabel = new Label("Keine Auswahl");
|
|
||||||
selInfoLabel.setStyle("-fx-font-size: 11; -fx-text-fill: #555;");
|
|
||||||
Label selHintLabel = new Label("LMK: Struktur wählen | RMK: Auswahl leeren");
|
|
||||||
selHintLabel.setStyle("-fx-font-size: 10; -fx-text-fill: #888;");
|
|
||||||
selHintLabel.setWrapText(true);
|
|
||||||
|
|
||||||
Button deleteBtn = new Button("Löschen");
|
|
||||||
deleteBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
deleteBtn.setStyle("-fx-background-color: #ba4a4a; -fx-text-fill: white;");
|
|
||||||
deleteBtn.setDisable(true);
|
|
||||||
deleteBtn.setOnAction(e -> {
|
|
||||||
int n = input.markedDeleteBakedKeys.size();
|
|
||||||
javafx.scene.control.Alert confirm = new javafx.scene.control.Alert(
|
|
||||||
javafx.scene.control.Alert.AlertType.CONFIRMATION,
|
|
||||||
n + " Chunk" + (n != 1 ? "s" : "") + " unwiderruflich löschen?",
|
|
||||||
javafx.scene.control.ButtonType.YES,
|
|
||||||
javafx.scene.control.ButtonType.NO);
|
|
||||||
confirm.setTitle("Gebackene Chunks löschen");
|
|
||||||
confirm.setHeaderText(null);
|
|
||||||
confirm.initOwner((javafx.stage.Stage) ctx.stageSupplier().get());
|
|
||||||
confirm.showAndWait().ifPresent(btn -> {
|
|
||||||
if (btn == javafx.scene.control.ButtonType.YES) {
|
|
||||||
input.deleteMarkedBakedRequested = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Button revertBtn = new Button("Bake rückgängig");
|
|
||||||
revertBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
revertBtn.setStyle("-fx-background-color: #7a5a2a; -fx-text-fill: white;");
|
|
||||||
revertBtn.setDisable(true);
|
|
||||||
revertBtn.setOnAction(e -> input.revertMarkedBakedRequested = true);
|
|
||||||
|
|
||||||
javafx.animation.Timeline selPoller = new javafx.animation.Timeline(
|
|
||||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
|
||||||
int n = input.markedDeleteBakedKeys.size();
|
|
||||||
boolean has = n > 0;
|
|
||||||
selInfoLabel.setText(has
|
|
||||||
? n + " Chunk" + (n != 1 ? "s" : "") + " ausgewählt"
|
|
||||||
: "Keine Auswahl");
|
|
||||||
deleteBtn.setDisable(!has);
|
|
||||||
revertBtn.setDisable(!has);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
selPoller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
|
||||||
selPoller.play();
|
|
||||||
|
|
||||||
// ── Unterirdische Voxel bereinigen ──────────────────────────────────
|
|
||||||
Label cleanupStatus = new Label("");
|
Label cleanupStatus = new Label("");
|
||||||
cleanupStatus.setWrapText(true);
|
cleanupStatus.setWrapText(true);
|
||||||
cleanupStatus.setStyle("-fx-text-fill: #555; -fx-font-size: 11;");
|
cleanupStatus.setStyle("-fx-text-fill: #555; -fx-font-size: 11;");
|
||||||
|
|
||||||
Button cleanupBtn = new Button("Unterirdische Voxel bereinigen");
|
Button cleanupBtn = new Button("Bereinigen...");
|
||||||
cleanupBtn.setMaxWidth(Double.MAX_VALUE);
|
cleanupBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
cleanupBtn.setOnAction(e -> {
|
cleanupBtn.setOnAction(e -> showCleanupDialog(cleanupBtn, cleanupStatus));
|
||||||
input.cleanupUndergroundStatus = null;
|
|
||||||
input.cleanupUndergroundRequested = true;
|
|
||||||
cleanupBtn.setDisable(true);
|
|
||||||
cleanupStatus.setText("Läuft...");
|
|
||||||
});
|
|
||||||
|
|
||||||
javafx.animation.Timeline cleanupPoller = new javafx.animation.Timeline(
|
javafx.animation.Timeline cleanupPoller = new javafx.animation.Timeline(
|
||||||
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
new javafx.animation.KeyFrame(javafx.util.Duration.millis(200), ev -> {
|
||||||
@@ -490,9 +383,7 @@ public class TerrainPanelController {
|
|||||||
|
|
||||||
panel.getChildren().addAll(new Separator(),
|
panel.getChildren().addAll(new Separator(),
|
||||||
smoothLbl, smoothSlider, cliffLbl, cliffSlider,
|
smoothLbl, smoothSlider, cliffLbl, cliffSlider,
|
||||||
bakeBtn, bakeBar, bakeBarLabel, bakeStatus,
|
new Separator(), cleanupBtn, cleanupStatus);
|
||||||
new Separator(), cleanupBtn, cleanupStatus,
|
|
||||||
new Separator(), selInfoLabel, selHintLabel, deleteBtn, revertBtn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1360,4 +1251,64 @@ public class TerrainPanelController {
|
|||||||
return String.format("%.1f MB", bytes / (1024.0 * 1024));
|
return String.format("%.1f MB", bytes / (1024.0 * 1024));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showCleanupDialog(Button triggerBtn, Label statusLbl) {
|
||||||
|
javafx.scene.control.CheckBox cbUnderground = new javafx.scene.control.CheckBox(
|
||||||
|
"Unterirdische Voxel-Chunks entfernen (cy < 0)");
|
||||||
|
javafx.scene.control.CheckBox cbOrphaned = new javafx.scene.control.CheckBox(
|
||||||
|
"Verwaiste gebackene Meshes (.j3o ohne .blvc) löschen");
|
||||||
|
javafx.scene.control.CheckBox cbRebakeAll = new javafx.scene.control.CheckBox(
|
||||||
|
"Alle Chunks als dirty markieren (Alles neu backen)");
|
||||||
|
cbUnderground.setSelected(true);
|
||||||
|
cbOrphaned.setSelected(true);
|
||||||
|
|
||||||
|
Label rebakeWarning = new Label(
|
||||||
|
"Achtung: Markiert alle geladenen Chunks dirty. Beim naechsten Tab-Wechsel\n" +
|
||||||
|
"wird alles neu gebacken – kann je nach Groesse der Karte sehr lange dauern.");
|
||||||
|
rebakeWarning.setStyle("-fx-text-fill: #b05000; -fx-font-size: 10;");
|
||||||
|
rebakeWarning.setWrapText(true);
|
||||||
|
rebakeWarning.visibleProperty().bind(cbRebakeAll.selectedProperty());
|
||||||
|
rebakeWarning.managedProperty().bind(cbRebakeAll.selectedProperty());
|
||||||
|
|
||||||
|
javafx.scene.layout.VBox content = new javafx.scene.layout.VBox(8,
|
||||||
|
cbUnderground, cbOrphaned,
|
||||||
|
new Separator(),
|
||||||
|
cbRebakeAll, rebakeWarning);
|
||||||
|
content.setPadding(new javafx.geometry.Insets(12));
|
||||||
|
|
||||||
|
javafx.scene.control.ButtonType okType = new javafx.scene.control.ButtonType("Ausführen",
|
||||||
|
javafx.scene.control.ButtonBar.ButtonData.OK_DONE);
|
||||||
|
javafx.scene.control.ButtonType cancelType = new javafx.scene.control.ButtonType("Abbrechen",
|
||||||
|
javafx.scene.control.ButtonBar.ButtonData.CANCEL_CLOSE);
|
||||||
|
javafx.scene.control.Dialog<javafx.scene.control.ButtonType> dialog =
|
||||||
|
new javafx.scene.control.Dialog<>();
|
||||||
|
dialog.setTitle("Bereinigen");
|
||||||
|
dialog.setHeaderText(null);
|
||||||
|
dialog.getDialogPane().setContent(content);
|
||||||
|
dialog.getDialogPane().getButtonTypes().addAll(okType, cancelType);
|
||||||
|
|
||||||
|
// Owner setzen damit kein Java-Default-Icon im Taskbar
|
||||||
|
javafx.stage.Window owner = triggerBtn.getScene() != null ? triggerBtn.getScene().getWindow() : null;
|
||||||
|
if (owner instanceof javafx.stage.Stage s) dialog.initOwner(s);
|
||||||
|
|
||||||
|
dialog.showAndWait().ifPresent(result -> {
|
||||||
|
if (result != okType) return;
|
||||||
|
boolean doUnderground = cbUnderground.isSelected();
|
||||||
|
boolean doOrphaned = cbOrphaned.isSelected();
|
||||||
|
boolean doRebakeAll = cbRebakeAll.isSelected();
|
||||||
|
|
||||||
|
if (doRebakeAll) {
|
||||||
|
input.markAllChunksDirtyRequested = true;
|
||||||
|
statusLbl.setText("Alle Chunks als dirty markiert.");
|
||||||
|
}
|
||||||
|
if (doUnderground || doOrphaned) {
|
||||||
|
input.cleanupUndergroundVoxels = doUnderground;
|
||||||
|
input.cleanupOrphanedBaked = doOrphaned;
|
||||||
|
input.cleanupUndergroundStatus = null;
|
||||||
|
input.cleanupUndergroundRequested = true;
|
||||||
|
triggerBtn.setDisable(true);
|
||||||
|
statusLbl.setText("Läuft...");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ package de.blight.editor.toolset.topbar;
|
|||||||
|
|
||||||
import de.blight.editor.SharedInput;
|
import de.blight.editor.SharedInput;
|
||||||
import de.blight.editor.ToolContext;
|
import de.blight.editor.ToolContext;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Orientation;
|
import javafx.geometry.Orientation;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.control.*;
|
||||||
import javafx.scene.layout.*;
|
import javafx.scene.layout.*;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.Stage;
|
||||||
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public class TopBarController {
|
public class TopBarController {
|
||||||
|
|
||||||
@@ -32,6 +38,8 @@ public class TopBarController {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
private final SharedInput input;
|
private final SharedInput input;
|
||||||
|
private ToggleGroup layerGroup;
|
||||||
|
private Runnable pendingTabSwitchAction = null;
|
||||||
|
|
||||||
// ToggleButtons – pre-created so getters work before buildTop() is called
|
// ToggleButtons – pre-created so getters work before buildTop() is called
|
||||||
private final ToggleButton auswahlBtn = new ToggleButton("[k] Auswahl");
|
private final ToggleButton auswahlBtn = new ToggleButton("[k] Auswahl");
|
||||||
@@ -77,7 +85,7 @@ public class TopBarController {
|
|||||||
playToolBtn.setStyle(BOLD);
|
playToolBtn.setStyle(BOLD);
|
||||||
|
|
||||||
// Mutually exclusive toggle group
|
// Mutually exclusive toggle group
|
||||||
ToggleGroup layerGroup = new ToggleGroup();
|
layerGroup = new ToggleGroup();
|
||||||
auswahlBtn.setToggleGroup(layerGroup);
|
auswahlBtn.setToggleGroup(layerGroup);
|
||||||
baseBtn.setToggleGroup(layerGroup);
|
baseBtn.setToggleGroup(layerGroup);
|
||||||
voxelMergedBtn.setToggleGroup(layerGroup);
|
voxelMergedBtn.setToggleGroup(layerGroup);
|
||||||
@@ -262,52 +270,57 @@ public class TopBarController {
|
|||||||
auswahlBtn.setSelected(true);
|
auswahlBtn.setSelected(true);
|
||||||
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
||||||
|
|
||||||
auswahlBtn.setOnAction(e -> {
|
Stage ownerStage = (Stage) ctx.stageSupplier().get();
|
||||||
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
|
||||||
|
auswahlBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
|
input.activeLayer = SharedInput.LAYER_AUSWAHL;
|
||||||
input.deselectAllOnAuswahl = true;
|
input.deselectAllOnAuswahl = true;
|
||||||
ctx.setRightPanel().accept(deps.worldPanelController().buildAuswahlPanel());
|
ctx.setRightPanel().accept(deps.worldPanelController().buildAuswahlPanel());
|
||||||
});
|
}, ownerStage));
|
||||||
baseBtn.setOnAction(e -> {
|
baseBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.heightTool.brushRadius.setValue(input.voxelTool.brushRadius.getValue());
|
input.heightTool.brushRadius.setValue(input.voxelTool.brushRadius.getValue());
|
||||||
input.heightTool.brushStrength.setValue(input.voxelTool.brushStrength.getValue());
|
input.heightTool.brushStrength.setValue(input.voxelTool.brushStrength.getValue());
|
||||||
input.heightTool.plateauHeight.setValue(input.voxelTool.plateauTarget.getValue());
|
input.heightTool.plateauHeight.setValue(input.voxelTool.plateauTarget.getValue());
|
||||||
input.activeLayer = 0; input.activeTool = input.heightTool;
|
input.activeLayer = 0; input.activeTool = input.heightTool;
|
||||||
ctx.setRightPanel().accept(deps.toolPanel());
|
ctx.setRightPanel().accept(deps.toolPanel());
|
||||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||||
});
|
}, ownerStage));
|
||||||
voxelMergedBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.voxelController().buildPanel()));
|
voxelMergedBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.voxelController().buildPanel()));
|
||||||
textureBtn.setOnAction(e -> {
|
textureBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.activeLayer = 4; input.activeTool = input.textureTool;
|
input.activeLayer = 4; input.activeTool = input.textureTool;
|
||||||
ctx.setRightPanel().accept(deps.toolPanel());
|
ctx.setRightPanel().accept(deps.toolPanel());
|
||||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||||
});
|
}, ownerStage));
|
||||||
grassBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.grassController().buildPanel()));
|
grassBtn.setOnAction(e -> guardVoxelExit(
|
||||||
stoneBtn.setOnAction(e -> {
|
() -> ctx.setRightPanel().accept(deps.grassController().buildPanel()), ownerStage));
|
||||||
|
stoneBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.activeLayer = SharedInput.LAYER_STONE;
|
input.activeLayer = SharedInput.LAYER_STONE;
|
||||||
input.activeTool = input.stoneTool;
|
input.activeTool = input.stoneTool;
|
||||||
ctx.setRightPanel().accept(deps.toolPanel());
|
ctx.setRightPanel().accept(deps.toolPanel());
|
||||||
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
deps.terrainController().showToolParameters(deps.toolPanel(), input.activeTool);
|
||||||
});
|
}, ownerStage));
|
||||||
objPlaceBtn.setOnAction(e -> {
|
objPlaceBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
deps.assetController().expandModelsNode();
|
deps.assetController().expandModelsNode();
|
||||||
deps.assetController().openOverlay();
|
deps.assetController().openOverlay();
|
||||||
deps.objectController().activatePlaceMode();
|
deps.objectController().activatePlaceMode();
|
||||||
});
|
}, ownerStage));
|
||||||
atmosphaereBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.atmosphereController().buildPanel()));
|
atmosphaereBtn.setOnAction(e -> guardVoxelExit(
|
||||||
wasserMergedBtn.setOnAction(e -> ctx.setRightPanel().accept(deps.waterController().buildPanel()));
|
() -> ctx.setRightPanel().accept(deps.atmosphereController().buildPanel()), ownerStage));
|
||||||
wegeBtn.setOnAction(e -> {
|
wasserMergedBtn.setOnAction(e -> guardVoxelExit(
|
||||||
|
() -> ctx.setRightPanel().accept(deps.waterController().buildPanel()), ownerStage));
|
||||||
|
wegeBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.activeLayer = SharedInput.LAYER_PATH_NETWORK;
|
input.activeLayer = SharedInput.LAYER_PATH_NETWORK;
|
||||||
ctx.setRightPanel().accept(deps.worldPanelController().buildWegePanel());
|
ctx.setRightPanel().accept(deps.worldPanelController().buildWegePanel());
|
||||||
});
|
}, ownerStage));
|
||||||
zonenBtn.setOnAction(e -> {
|
zonenBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.activeLayer = SharedInput.LAYER_ZONEN;
|
input.activeLayer = SharedInput.LAYER_ZONEN;
|
||||||
ctx.setRightPanel().accept(deps.worldPanelController().buildZonenPanel());
|
ctx.setRightPanel().accept(deps.worldPanelController().buildZonenPanel());
|
||||||
});
|
}, ownerStage));
|
||||||
playToolBtn.setOnAction(e -> {
|
playToolBtn.setOnAction(e -> guardVoxelExit(() -> {
|
||||||
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
|
input.activeLayer = SharedInput.LAYER_PLAY_TOOL;
|
||||||
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
input.playToolMode = SharedInput.PlayToolMode.EDIT;
|
||||||
ctx.setRightPanel().accept(deps.gameController().buildPlayToolPanel());
|
ctx.setRightPanel().accept(deps.gameController().buildPlayToolPanel());
|
||||||
});
|
}, ownerStage));
|
||||||
|
|
||||||
worldHintLabel = new Label(camHintText());
|
worldHintLabel = new Label(camHintText());
|
||||||
worldHintLabel.setStyle("-fx-text-fill: #555;");
|
worldHintLabel.setStyle("-fx-text-fill: #555;");
|
||||||
@@ -332,4 +345,69 @@ public class TopBarController {
|
|||||||
worldToolBar = toolBar;
|
worldToolBar = toolBar;
|
||||||
return new VBox(menuBar, toolBar);
|
return new VBox(menuBar, toolBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Voxel-Exit-Guard ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
private void guardVoxelExit(Runnable action, Stage ownerStage) {
|
||||||
|
if (input.activeLayer != SharedInput.LAYER_VOXEL || input.sessionDirtyVoxelCount == 0) {
|
||||||
|
action.run();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
voxelMergedBtn.setSelected(true);
|
||||||
|
pendingTabSwitchAction = action;
|
||||||
|
showVoxelBakeDialog(ownerStage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showVoxelBakeDialog(Stage ownerStage) {
|
||||||
|
Stage dialog = new Stage();
|
||||||
|
dialog.initOwner(ownerStage);
|
||||||
|
dialog.initModality(Modality.APPLICATION_MODAL);
|
||||||
|
dialog.setTitle("Voxel backen...");
|
||||||
|
dialog.setResizable(false);
|
||||||
|
|
||||||
|
Label statusLabel = new Label("Backe geänderte Chunks...");
|
||||||
|
ProgressBar bar = new ProgressBar(0);
|
||||||
|
bar.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
|
||||||
|
Button cancelBtn = new Button("Abbrechen");
|
||||||
|
cancelBtn.setMaxWidth(Double.MAX_VALUE);
|
||||||
|
cancelBtn.setOnAction(e -> input.bakeCancelRequested = true);
|
||||||
|
|
||||||
|
VBox vbox = new VBox(10, statusLabel, bar, cancelBtn);
|
||||||
|
vbox.setPadding(new Insets(16));
|
||||||
|
vbox.setMinWidth(320);
|
||||||
|
|
||||||
|
dialog.setScene(new javafx.scene.Scene(vbox));
|
||||||
|
|
||||||
|
input.bakeOnExitRequested = true;
|
||||||
|
input.bakeOnExitDone = false;
|
||||||
|
input.bakeOnExitSuccess = false;
|
||||||
|
|
||||||
|
Timeline poller = new Timeline(new KeyFrame(Duration.millis(100), ev -> {
|
||||||
|
int total = input.bakeTotal;
|
||||||
|
int done = input.bakeDone;
|
||||||
|
if (total > 0) {
|
||||||
|
bar.setProgress((double) done / total);
|
||||||
|
statusLabel.setText("Backe... " + done + " / " + total);
|
||||||
|
}
|
||||||
|
if (input.bakeOnExitDone) {
|
||||||
|
input.bakeOnExitDone = false;
|
||||||
|
dialog.close();
|
||||||
|
if (input.bakeOnExitSuccess) {
|
||||||
|
if (pendingTabSwitchAction != null) {
|
||||||
|
pendingTabSwitchAction.run();
|
||||||
|
pendingTabSwitchAction = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pendingTabSwitchAction = null;
|
||||||
|
voxelMergedBtn.setSelected(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
poller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
||||||
|
|
||||||
|
dialog.setOnShown(e -> poller.play());
|
||||||
|
dialog.setOnHidden(e -> poller.stop());
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_15_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_15_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_15_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_15_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_0_16_baked_lod2.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_m1_15_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_m1_15_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_m1_15_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_m1_15_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_16_m1_16_baked_lod2.j3o
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_15_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_15_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_15_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_15_baked_lod1.j3o
Normal file
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_16.blvc.prebake
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_16.blvc.prebake
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_0_16_baked_lod2.j3o
Normal file
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_15_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_15_baked_lod0.j3o
Normal file
Binary file not shown.
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod0.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod0.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod1.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod1.j3o
Normal file
Binary file not shown.
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod2.j3o
Normal file
BIN
blight-map/src/main/map/chunks/voxel_17_m1_16_baked_lod2.j3o
Normal file
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user