Zwischenstand gesichert bei den voxel poxoxeln

This commit is contained in:
2026-08-09 21:48:23 +02:00
parent 936efb7bcc
commit 00a7ccbe47
11 changed files with 135 additions and 53 deletions

View File

@@ -58,11 +58,10 @@ void main() {
vec2 uvY = vWorldPos.xz / m_TexScale;
vec2 uvZ = vWorldPos.xy / m_TexScale;
// Flach bis ~22,5° Gefälle (deckt sich mit der Bake-Schwelle in VoxelEditorState,
// die Rampen bis 22,5° glättet vorher lag diese Grenze bei ~15° und sorgte dafür,
// dass geometrisch glatt gebackene, aber 15-22,5° steile Rampen trotzdem komplett
// mit der Klippen-Textur eingefärbt wurden statt mit der Flach-Textur.
float flatBlend = smoothstep(0.90, 0.95, vNormal.y);
// Flach bis ~45° Gefälle. LOD0-Smoothing (3 Passes, 0.3 Stärke) kann die Normalen
// an Rampenfüßen (14°-Rampe, ideal normal.y≈0.97) auf 0.70-0.85 herunterziehen.
// Untere Grenze 0.70 (≈45°) echte Klippen (normal.y<0.70) zeigen weiter Gebirge.
float flatBlend = smoothstep(0.70, 0.90, vNormal.y);
float steepBlend = 1.0 - flatBlend;
// Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar.

View File

@@ -2111,78 +2111,160 @@ public class VoxelEditorState extends BaseAppState {
// Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend
// von der (ring-gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe. Folgt
// nicht mehr exakt dem Rampen-Winkel, ist aber garantiert monoton/glatt.
// 4:1-Abwärtsrampe bei Stufenabbrüchen (inter-terrace + Basisterrain-Übergang).
// 4:1-Abwärtsrampe bei Stufenabbrüchen.
//
// Schritt 2 lässt Spalten mit hUp=null flach (smoothH=h0), auch wenn direkt
// daneben eine niedrigere Terasse oder das Basisterrain-Tal liegt. Das erzeugt
// vertikale Klippen an allen Plateau-Kanten.
// Zwei getrennte Passes wegen unterschiedlicher Schutz-Logik:
//
// Fix: Für jede Spalte den nächsten Nachbarn mit Höhe < h0 suchen
// (Voxel-Spalte aus hfMap ODER Basisterrain außerhalb). Wenn gefunden bei
// Chebyshev-Distanz d mit mittlerer Höhe avgLow:
// rampH = avgLow + d / 4.0
// Spalte wird auf min(smoothH, rampH) abgesenkt nie angehoben.
// Entspricht 4 Schritte vor / 1 Schritt runter, deckt beide Bedingungen ab:
// „Tal darunter" (avgLow << h0) und „<1m über Basisterrain" (kleines dH).
// Ringsuche je Spalte unabhängig von anderen Schritt-2b-Ergebnissen →
// keine Kettenfortpflanzung, keine Divergenz.
// Pass A Basisterrain-Übergang:
// Sucht nur Nicht-hfMap-Zellen (echtes Basisterrain). Voxel-Spalten in
// hfMap blocken die Sicht natürlich (Kliffwände sind in hfMap → werden
// übersprungen). MAX_TERRAIN_DIFF verhindert tiefe Klippen.
// Kein Ring-Vorfilter nötig der Schutz kommt durch hfMap-Blocking.
//
// Pass B Voxel-zu-Voxel-Übergang:
// Sucht nur hfMap-Zellen. Ring-1-Vorfilter: alle Voxel auf gleicher Höhe
// → Plateau-Innenzelle → überspringen.
{
final float RAMP_RATIO = 4.0f;
final int MAX_RAMP_R = 20;
final float RAMP_RATIO = 4.0f;
final int MAX_RAMP_R = 20;
final int MAX_RAMP_R_TERRAIN = 4;
final float MAX_TERRAIN_DIFF = 1.0f;
final float MAX_VOXEL_DIFF = 1.05f;
// Terrain-Cache: vermeidet wiederholte terrainH()-Aufrufe bei Überlappungen
Map<Long, Float> terrainCache2b = new HashMap<>();
for (Map.Entry<Long, Float> entry : hfMap.entrySet()) {
long hk0 = entry.getKey();
float h0raw = entry.getValue(); // Original-Voxelhöhe
float h0sm = smoothMap.getOrDefault(hk0, h0raw); // Schritt-2-Ausgabe
long hk0 = entry.getKey();
float h0raw = entry.getValue();
float h0sm = smoothMap.getOrDefault(hk0, h0raw);
int colX2b = (int)(hk0 / 60001L) - 30000;
int colZ2b = (int)(hk0 % 60001L) - 30000;
// Ringsuche nach nächstem niedrigeren Nachbarn (Voxel oder Terrain)
float sumLowH = 0f;
int lowCount = 0;
int lowDist = -1;
float minRampH = h0sm;
// Terrain ÜBER dem Voxel: Voxel nach oben auf Terrain-Niveau heben.
// Kein +0.1f-Offset an Ring-1 → Ring-1-Oberfläche liegt exakt auf Terrain
// (nahtloser Übergang). Innere Ringe steigen per 4:1-Rampe weiter an.
float minRaiseH = Float.POSITIVE_INFINITY;
outer2b:
for (int r = 1; r <= MAX_RAMP_R; r++) {
// ── Pass A: Basisterrain ──────────────────────────────────────────
// Pro Ring: höchstes qualifizierendes th (maxThAtR) → Abwärts-Kandidat.
// Gegenfall: Terrain ÜBER h0sm (maxThAboveAtR) → Aufwärts-Kandidat.
float passA_maxThAbove = Float.NEGATIVE_INFINITY; // Terrain ÜBER h0sm (r=1, Diagnose)
for (int r = 1; r <= MAX_RAMP_R_TERRAIN; r++) {
float maxThAtR = Float.NEGATIVE_INFINITY;
float maxThAboveAtR = Float.NEGATIVE_INFINITY;
for (int dz2b = -r; dz2b <= r; dz2b++) {
for (int dx2b = -r; dx2b <= r; dx2b++) {
if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue;
long hkN = (long)(colX2b + dx2b + 30000) * 60001L
+ (colZ2b + dz2b + 30000);
Float hN = hfMap.get(hkN);
float neighborH;
if (hN != null) {
neighborH = hN; // Voxel-Spalte: Originalhöhe
} else {
Float cached = terrainCache2b.get(hkN);
if (cached == null) {
cached = terrainH((float)(colX2b + dx2b),
(float)(colZ2b + dz2b));
terrainCache2b.put(hkN, cached);
}
neighborH = cached; // Basisterrain
if (hfMap.containsKey(hkN)) continue;
Float cached = terrainCache2b.get(hkN);
if (cached == null) {
cached = terrainH((float)(colX2b + dx2b),
(float)(colZ2b + dz2b));
terrainCache2b.put(hkN, cached);
}
if (neighborH < h0raw) {
sumLowH += neighborH;
lowCount++;
float th = cached;
if (th >= h0sm) {
if (r == 1 && th > passA_maxThAbove) {
passA_maxThAbove = th;
}
if ((th - h0sm) <= MAX_TERRAIN_DIFF && th > maxThAboveAtR) {
maxThAboveAtR = th;
}
continue;
}
float diff = h0sm - th;
if (diff > MAX_TERRAIN_DIFF) continue;
if (th > maxThAtR) {
maxThAtR = th;
}
}
}
if (lowCount > 0) {
lowDist = r;
break outer2b;
if (maxThAtR > Float.NEGATIVE_INFINITY) {
float candidate = maxThAtR + 0.1f + (r - 1) / RAMP_RATIO;
if (candidate < minRampH) {
minRampH = candidate;
}
}
// Aufwärts-Kandidat: gleiche Formel wie Abwärts (symmetrisch)
if (maxThAboveAtR > Float.NEGATIVE_INFINITY) {
float raiseCandidate = maxThAboveAtR + (r - 1) / RAMP_RATIO;
if (raiseCandidate < minRaiseH) {
minRaiseH = raiseCandidate;
}
}
}
// Diagnose
if (log.isTraceEnabled() && passA_maxThAbove > Float.NEGATIVE_INFINITY
&& minRampH >= h0sm) {
if (minRaiseH < Float.POSITIVE_INFINITY) {
log.trace(String.format(
"PassA-RAISE (%d,%d) h=%.2f → %.2f (terrainR1=%.2f)",
colX2b, colZ2b, h0sm, minRaiseH, passA_maxThAbove));
} else {
log.trace(String.format(
"PassA-UNTEN (%d,%d) h0sm=%.3f h0raw=%.3f terrainR1=%.3f Terrain zu weit oben (>MAX_TERRAIN_DIFF), kein Kandidat",
colX2b, colZ2b, h0sm, h0raw, passA_maxThAbove));
}
}
if (lowDist < 0) continue; // kein niedrigerer Nachbar im Suchradius
// ── Pass B: Voxel-zu-Voxel ────────────────────────────────────────
// Nur Terrassen-Zellen (h0raw ≤ 4.25m) Bergterrain wird nicht angefasst.
// Pass B als if-Block (kein continue): smoothMap.put() am Ende bleibt für
// Pass-A-Ergebnisse aller Zellen erreichbar.
// Cascade-Schutz: alle Vergleiche ausschließlich mit RAW-Höhen (hN, h0raw),
// nie mit smoothMap gerampte Kliffränder behalten raw ihre Originalhöhe
// und werden nicht fälschlicherweise als niedrigere Nachbarn erkannt.
if (h0raw <= 4.25f) {
boolean hadAnyLower = false;
boolean hadNonVoxelR1R3 = false;
voxelPassB:
for (int r = 1; r <= MAX_RAMP_R; r++) {
for (int dz2b = -r; dz2b <= r; dz2b++) {
for (int dx2b = -r; dx2b <= r; dx2b++) {
if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue;
long hkN = (long)(colX2b + dx2b + 30000) * 60001L
+ (colZ2b + dz2b + 30000);
Float hN = hfMap.get(hkN);
if (hN == null) {
if (r <= 3) { hadNonVoxelR1R3 = true; }
continue;
}
if (hN >= h0raw) continue;
if ((h0raw - hN) >= MAX_VOXEL_DIFF) continue;
hadAnyLower = true;
if ((h0raw - hN) > (float) r) continue;
float candidate = hN + r / RAMP_RATIO;
if (candidate < minRampH) {
minRampH = candidate;
}
}
}
// Nach Ring-3: kein gültiger niedrigerer Voxel + kein Terrain → Plateau-Innenzelle
if (r == 3 && !hadAnyLower && !hadNonVoxelR1R3) {
break voxelPassB;
}
if (hadAnyLower) {
break voxelPassB;
}
}
}
float avgLowH = sumLowH / lowCount;
float rampH = avgLowH + lowDist / RAMP_RATIO;
if (rampH < h0sm) {
smoothMap.put(hk0, rampH);
if (minRampH < h0sm) {
if (log.isTraceEnabled()) {
log.trace(String.format("Schritt2b (%d,%d) h=%.2f → %.2f",
colX2b, colZ2b, h0sm, minRampH));
}
smoothMap.put(hk0, minRampH);
} else if (minRaiseH > h0sm && minRaiseH < Float.POSITIVE_INFINITY) {
// Voxel lag unter dem Terrain → auf Terrain-Niveau anheben
if (log.isTraceEnabled()) {
log.trace(String.format("Schritt2b-RAISE (%d,%d) h=%.2f → %.2f",
colX2b, colZ2b, h0sm, minRaiseH));
}
smoothMap.put(hk0, minRaiseH);
}
}
}

View File

@@ -26,6 +26,7 @@
<!-- Material warnt bei linear-color-space Texturen ohne passenden Parameter bekannt, kein Fehler -->
<logger name="com.jme3.material.Material" level="ERROR"/>
<logger name="de.blight.game.animation.FootIKControl" level="DEBUG"/>
<logger name="de.blight.editor.state.VoxelEditorState" level="TRACE"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>