Back Effekte hinzugefügt
This commit is contained in:
@@ -3582,6 +3582,27 @@ public class EditorApp extends Application {
|
||||
bakeBarLabel.setVisible(false);
|
||||
bakeBarLabel.setManaged(false);
|
||||
|
||||
// ── Bake-Parameter ────────────────────────────────────────────────
|
||||
Label smoothLbl = new Label(String.format("Glättung: %.1f", input.bakeSmoothStrength));
|
||||
smoothLbl.setStyle("-fx-font-size: 11;");
|
||||
javafx.scene.control.Slider smoothSlider =
|
||||
new javafx.scene.control.Slider(0, 1, input.bakeSmoothStrength);
|
||||
smoothSlider.setShowTickMarks(false);
|
||||
smoothSlider.valueProperty().addListener((obs, o, n) -> {
|
||||
input.bakeSmoothStrength = n.floatValue();
|
||||
smoothLbl.setText(String.format("Glättung: %.1f", n.floatValue()));
|
||||
});
|
||||
|
||||
Label cliffLbl = new Label(String.format("Klippen-Noise: %.1f", input.bakeCliffNoiseStrength));
|
||||
cliffLbl.setStyle("-fx-font-size: 11;");
|
||||
javafx.scene.control.Slider cliffSlider =
|
||||
new javafx.scene.control.Slider(0, 1, input.bakeCliffNoiseStrength);
|
||||
cliffSlider.setShowTickMarks(false);
|
||||
cliffSlider.valueProperty().addListener((obs, o, n) -> {
|
||||
input.bakeCliffNoiseStrength = n.floatValue();
|
||||
cliffLbl.setText(String.format("Klippen-Noise: %.1f", n.floatValue()));
|
||||
});
|
||||
|
||||
Button bakeBtn = new Button("Voxels backen (J3O)");
|
||||
bakeBtn.setMaxWidth(Double.MAX_VALUE);
|
||||
bakeBtn.setStyle("-fx-background-color: #4a7eba; -fx-text-fill: white;");
|
||||
@@ -3697,8 +3718,9 @@ public class EditorApp extends Application {
|
||||
cleanupPoller.setCycleCount(javafx.animation.Animation.INDEFINITE);
|
||||
cleanupPoller.play();
|
||||
|
||||
panel.getChildren().addAll(new Separator(), bakeBtn,
|
||||
bakeBar, bakeBarLabel, bakeStatus,
|
||||
panel.getChildren().addAll(new Separator(),
|
||||
smoothLbl, smoothSlider, cliffLbl, cliffSlider,
|
||||
bakeBtn, bakeBar, bakeBarLabel, bakeStatus,
|
||||
new Separator(), cleanupBtn, cleanupStatus,
|
||||
new Separator(), selInfoLabel, selHintLabel, deleteBtn, revertBtn);
|
||||
}
|
||||
|
||||
@@ -778,7 +778,11 @@ public class SharedInput {
|
||||
public volatile boolean voxelRedoRequested = false;
|
||||
|
||||
/** JFX → JME: alle Voxel-Chunks als geglättete J3O-Meshes backen. */
|
||||
public volatile boolean bakeVoxelsRequested = false;
|
||||
public volatile boolean bakeVoxelsRequested = false;
|
||||
/** JFX → JME: Stärke des Post-Bake-Laplacian-Smooth (0 = kein Extra-Pass, 1 = stark). */
|
||||
public volatile float bakeSmoothStrength = 0.3f;
|
||||
/** JFX → JME: Amplitude der horizontalen Klippen-Noise-Verschiebung (0 = kein, 1 = ~2 m). */
|
||||
public volatile float bakeCliffNoiseStrength = 0.2f;
|
||||
/** JME → JFX: Anzahl bereits gebackener Chunks (0 = nicht gestartet). */
|
||||
public volatile int bakeDone = 0;
|
||||
/** JME → JFX: Gesamtzahl der zu backenden Chunks (0 = nicht gestartet). */
|
||||
|
||||
@@ -2522,8 +2522,10 @@ public class VoxelEditorState extends BaseAppState {
|
||||
// Nur erfolgreich gebackene Chunks werden gelöscht
|
||||
List<VoxelChunk> successfullyBaked = new java.util.ArrayList<>();
|
||||
int baked = 0;
|
||||
float bakeSmoothStr = input.bakeSmoothStrength;
|
||||
float bakeCliffStr = input.bakeCliffNoiseStrength;
|
||||
for (VoxelChunk chunk : nonEmpty) {
|
||||
if (bakeChunk(chunk, blurredMap)) {
|
||||
if (bakeChunk(chunk, blurredMap, bakeSmoothStr, bakeCliffStr)) {
|
||||
successfullyBaked.add(chunk);
|
||||
} else {
|
||||
log.warn("Chunk ({},{},{}) nicht gebacken – Voxel-Daten bleiben erhalten.",
|
||||
@@ -2639,7 +2641,8 @@ public class VoxelEditorState extends BaseAppState {
|
||||
}
|
||||
|
||||
/** 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) {
|
||||
try {
|
||||
VoxelChunk blurred = blurredMap.get(chunkKey(original.cx, original.cy, original.cz));
|
||||
if (blurred == null) return false;
|
||||
@@ -2647,9 +2650,25 @@ public class VoxelEditorState extends BaseAppState {
|
||||
// Geblurrte Nachbarn für nahtlose Chunk-Grenzen im MC
|
||||
VoxelChunk[] nb = getNeighbors(original.cx, original.cy, original.cz, blurredMap);
|
||||
|
||||
float wx = (float)(original.cx * VoxelChunk.CELLS);
|
||||
float wz = (float)(original.cz * VoxelChunk.CELLS);
|
||||
|
||||
Mesh lod0 = MarchingCubes.smooth(MarchingCubes.build(blurred, 1, nb), 1, 0.3f);
|
||||
if (smoothStrength > 0.01f) {
|
||||
lod0 = MarchingCubes.smooth(lod0, 1, smoothStrength);
|
||||
}
|
||||
if (cliffNoiseStrength > 0.01f) {
|
||||
lod0 = MarchingCubes.perturb(lod0, cliffNoiseStrength, wx, wz);
|
||||
}
|
||||
|
||||
Mesh lod1 = MarchingCubes.smooth(MarchingCubes.build(blurred, 4, nb), 3, 0.4f);
|
||||
if (cliffNoiseStrength > 0.01f) {
|
||||
lod1 = MarchingCubes.perturb(lod1, cliffNoiseStrength * 0.6f, wx, wz);
|
||||
}
|
||||
|
||||
Mesh[] meshes = {
|
||||
MarchingCubes.smooth(MarchingCubes.build(blurred, 1, nb), 1, 0.3f),
|
||||
MarchingCubes.smooth(MarchingCubes.build(blurred, 4, nb), 3, 0.4f),
|
||||
lod0,
|
||||
lod1,
|
||||
MarchingCubes.smooth(MarchingCubes.build(blurred, 16, nb), 2, 0.4f),
|
||||
};
|
||||
|
||||
|
||||
@@ -682,4 +682,172 @@ public final class MarchingCubes {
|
||||
return chunk.getDensity(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verschiebt Vertices auf steilen Flächen (normal.y < 0.707 ≈ 45°) horizontal (XZ)
|
||||
* per kohärentem Value-Noise. Macht Klippen natürlicher und felsiger.
|
||||
* Rand-Vertices (Chunk-Grenzen) bleiben fixiert, Nahtlosigkeit bleibt erhalten.
|
||||
*
|
||||
* @param strength Amplitude: 0 = kein Effekt, 1 = bis ~2 m maximale Verschiebung
|
||||
* @param worldX Weltkoordinate X des Chunk-Ursprungs (cx * CELLS)
|
||||
* @param worldZ Weltkoordinate Z des Chunk-Ursprungs (cz * CELLS)
|
||||
*/
|
||||
public static Mesh perturb(Mesh mesh, float strength, float worldX, float worldZ) {
|
||||
if (mesh == null || strength < 0.001f) return mesh;
|
||||
FloatBuffer posF = mesh.getFloatBuffer(VertexBuffer.Type.Position);
|
||||
if (posF == null) return mesh;
|
||||
int vertCount = posF.capacity() / 3;
|
||||
if (vertCount < 3) return mesh;
|
||||
int triCount = vertCount / 3;
|
||||
|
||||
float[] pos = new float[vertCount * 3];
|
||||
posF.rewind(); posF.get(pos);
|
||||
|
||||
// Vertex-Gruppen aufbauen (gleiche Position → gleiche Gruppe)
|
||||
HashMap<Long, Integer> keyToGroup = new HashMap<>(vertCount / 3 + 16);
|
||||
int[] vertGroup = new int[vertCount];
|
||||
int[] groupFirst = new int[vertCount];
|
||||
int groupCount = 0;
|
||||
for (int v = 0; v < vertCount; v++) {
|
||||
long key = posKey(pos, v);
|
||||
Integer g = keyToGroup.get(key);
|
||||
if (g == null) {
|
||||
keyToGroup.put(key, groupCount);
|
||||
groupFirst[groupCount] = v;
|
||||
vertGroup[v] = groupCount++;
|
||||
} else {
|
||||
vertGroup[v] = g;
|
||||
}
|
||||
}
|
||||
|
||||
float[] gx = new float[groupCount];
|
||||
float[] gy = new float[groupCount];
|
||||
float[] gz = new float[groupCount];
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
int v = groupFirst[g];
|
||||
gx[g] = pos[v*3]; gy[g] = pos[v*3+1]; gz[g] = pos[v*3+2];
|
||||
}
|
||||
|
||||
// Rand-Vertices einfrieren (Chunk-Nahtlosigkeit)
|
||||
float bound = VoxelChunk.CELLS;
|
||||
boolean[] pinned = new boolean[groupCount];
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
float x = gx[g], y = gy[g], z = gz[g];
|
||||
if (x < 0.01f || x > bound - 0.01f ||
|
||||
y < 0.01f || y > bound - 0.01f ||
|
||||
z < 0.01f || z > bound - 0.01f) {
|
||||
pinned[g] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Flächennormalen berechnen und pro Gruppe akkumulieren
|
||||
float[] gnx = new float[groupCount];
|
||||
float[] gny = new float[groupCount];
|
||||
float[] gnz = new float[groupCount];
|
||||
for (int t = 0; t < triCount; t++) {
|
||||
int g0=vertGroup[t*3], g1=vertGroup[t*3+1], g2=vertGroup[t*3+2];
|
||||
float p0x=pos[t*9], p0y=pos[t*9+1], p0z=pos[t*9+2];
|
||||
float p1x=pos[t*9+3], p1y=pos[t*9+4], p1z=pos[t*9+5];
|
||||
float p2x=pos[t*9+6], p2y=pos[t*9+7], p2z=pos[t*9+8];
|
||||
float ex=p1x-p0x, ey=p1y-p0y, ez=p1z-p0z;
|
||||
float fx=p2x-p0x, fy=p2y-p0y, fz=p2z-p0z;
|
||||
float nx=ey*fz-ez*fy, ny=ez*fx-ex*fz, nz=ex*fy-ey*fx;
|
||||
gnx[g0]+=nx; gny[g0]+=ny; gnz[g0]+=nz;
|
||||
gnx[g1]+=nx; gny[g1]+=ny; gnz[g1]+=nz;
|
||||
gnx[g2]+=nx; gny[g2]+=ny; gnz[g2]+=nz;
|
||||
}
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
float len=(float)Math.sqrt(gnx[g]*gnx[g]+gny[g]*gny[g]+gnz[g]*gnz[g]);
|
||||
if (len > 1e-6f) { gnx[g]/=len; gny[g]/=len; gnz[g]/=len; }
|
||||
else { gny[g] = 1f; }
|
||||
}
|
||||
|
||||
// Horizontale Verschiebung auf steile Gruppen anwenden.
|
||||
// fBm (Fractal Brownian Motion) mit 4 Oktaven: gleiche Frequenz auf allen
|
||||
// drei Achsen → jedes Höhenniveau bekommt eigene, unabhängige Verschiebung.
|
||||
// Zwei unkorrelierte fBm-Felder für X und Z (unterschiedliche Offsets).
|
||||
final float STEEP = 0.707f; // cos(45°)
|
||||
final float MAX_D = 4.0f; // m maximale Verschiebung bei strength=1, senkrechter Fläche
|
||||
|
||||
for (int g = 0; g < groupCount; g++) {
|
||||
if (pinned[g]) continue;
|
||||
float ny = gny[g];
|
||||
if (ny >= STEEP) continue;
|
||||
float steepness = 1f - ny / STEEP; // 0 bei 45°, 1 bei senkrecht
|
||||
float wx = worldX + gx[g];
|
||||
float wz = worldZ + gz[g];
|
||||
float wy = gy[g];
|
||||
float noiseX = perturbFbm(wx * 0.08f, wy * 0.08f, wz * 0.08f);
|
||||
float noiseZ = perturbFbm(wx * 0.08f + 31.7f, wy * 0.08f + 11.3f, wz * 0.08f + 67.1f);
|
||||
float disp = steepness * strength * MAX_D;
|
||||
gx[g] += noiseX * disp;
|
||||
gz[g] += noiseZ * disp;
|
||||
}
|
||||
|
||||
// Positionen in Buffer schreiben
|
||||
for (int v = 0; v < vertCount; v++) {
|
||||
int g = vertGroup[v];
|
||||
pos[v*3] = gx[g]; pos[v*3+2] = gz[g];
|
||||
}
|
||||
posF.rewind(); posF.put(pos); posF.rewind();
|
||||
|
||||
// Normalen aus geänderter Geometrie neu berechnen
|
||||
java.util.Arrays.fill(gnx, 0f); java.util.Arrays.fill(gny, 0f); java.util.Arrays.fill(gnz, 0f);
|
||||
for (int t = 0; t < triCount; t++) {
|
||||
int g0=vertGroup[t*3], g1=vertGroup[t*3+1], g2=vertGroup[t*3+2];
|
||||
float p0x=pos[t*9], p0y=pos[t*9+1], p0z=pos[t*9+2];
|
||||
float p1x=pos[t*9+3], p1y=pos[t*9+4], p1z=pos[t*9+5];
|
||||
float p2x=pos[t*9+6], p2y=pos[t*9+7], p2z=pos[t*9+8];
|
||||
float ex=p1x-p0x, ey=p1y-p0y, ez=p1z-p0z;
|
||||
float fx=p2x-p0x, fy=p2y-p0y, fz=p2z-p0z;
|
||||
float nx=ey*fz-ez*fy, ny=ez*fx-ex*fz, nz=ex*fy-ey*fx;
|
||||
gnx[g0]+=nx; gny[g0]+=ny; gnz[g0]+=nz;
|
||||
gnx[g1]+=nx; gny[g1]+=ny; gnz[g1]+=nz;
|
||||
gnx[g2]+=nx; gny[g2]+=ny; gnz[g2]+=nz;
|
||||
}
|
||||
FloatBuffer normF = mesh.getFloatBuffer(VertexBuffer.Type.Normal);
|
||||
if (normF != null) {
|
||||
normF.rewind();
|
||||
for (int v = 0; v < vertCount; v++) {
|
||||
int g = vertGroup[v];
|
||||
float len=(float)Math.sqrt(gnx[g]*gnx[g]+gny[g]*gny[g]+gnz[g]*gnz[g]);
|
||||
if (len > 1e-6f) normF.put(gnx[g]/len).put(gny[g]/len).put(gnz[g]/len);
|
||||
else normF.put(0f).put(1f).put(0f);
|
||||
}
|
||||
normF.rewind();
|
||||
}
|
||||
|
||||
mesh.updateBound();
|
||||
return mesh;
|
||||
}
|
||||
|
||||
/** fBm (Fractal Brownian Motion), 4 Oktaven, Ergebnis in [-1, 1]. */
|
||||
private static float perturbFbm(float x, float y, float z) {
|
||||
float v = 0f, amp = 1f, sumAmp = 0f;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
v += perturbNoise(x, y, z) * amp;
|
||||
sumAmp += amp;
|
||||
x *= 2.1f; y *= 2.1f; z *= 2.1f;
|
||||
amp *= 0.5f;
|
||||
}
|
||||
return v / sumAmp;
|
||||
}
|
||||
|
||||
/** Trilinear interpoliertes Value-Noise in [-1, 1]. Kohärent, deterministisch. */
|
||||
private static float perturbNoise(float x, float y, float z) {
|
||||
int ix=(int)Math.floor(x), iy=(int)Math.floor(y), iz=(int)Math.floor(z);
|
||||
float fx=x-ix, fy=y-iy, fz=z-iz;
|
||||
fx=fx*fx*(3-2*fx); fy=fy*fy*(3-2*fy); fz=fz*fz*(3-2*fz);
|
||||
float v00 = perturbHash(ix,iy,iz) + fx*(perturbHash(ix+1,iy,iz) - perturbHash(ix,iy,iz));
|
||||
float v10 = perturbHash(ix,iy+1,iz) + fx*(perturbHash(ix+1,iy+1,iz) - perturbHash(ix,iy+1,iz));
|
||||
float v01 = perturbHash(ix,iy,iz+1) + fx*(perturbHash(ix+1,iy,iz+1) - perturbHash(ix,iy,iz+1));
|
||||
float v11 = perturbHash(ix,iy+1,iz+1)+fx*(perturbHash(ix+1,iy+1,iz+1)-perturbHash(ix,iy+1,iz+1));
|
||||
return (v00 + fy*(v10-v00)) + fz*((v01 + fy*(v11-v01)) - (v00 + fy*(v10-v00)));
|
||||
}
|
||||
|
||||
private static float perturbHash(int x, int y, int z) {
|
||||
int h = x * 374761393 + y * 1103515245 + z * -2012135261;
|
||||
h ^= (h >>> 15); h *= 0x9e3779b9; h ^= (h >>> 12);
|
||||
return ((h >>> 1) & 0xFFFF) * (2f / 65535f) - 1f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user