Zwischenstand gesichert bei den voxel poxoxeln
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -2111,41 +2111,116 @@ 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 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
|
||||
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:
|
||||
// ── 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);
|
||||
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);
|
||||
}
|
||||
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 (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));
|
||||
}
|
||||
}
|
||||
|
||||
// ── 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++) {
|
||||
@@ -2153,36 +2228,43 @@ public class VoxelEditorState extends BaseAppState {
|
||||
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);
|
||||
if (hN == null) {
|
||||
if (r <= 3) { hadNonVoxelR1R3 = true; }
|
||||
continue;
|
||||
}
|
||||
neighborH = cached; // Basisterrain
|
||||
}
|
||||
if (neighborH < h0raw) {
|
||||
sumLowH += neighborH;
|
||||
lowCount++;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lowCount > 0) {
|
||||
lowDist = r;
|
||||
break outer2b;
|
||||
// Nach Ring-3: kein gültiger niedrigerer Voxel + kein Terrain → Plateau-Innenzelle
|
||||
if (r == 3 && !hadAnyLower && !hadNonVoxelR1R3) {
|
||||
break voxelPassB;
|
||||
}
|
||||
if (hadAnyLower) {
|
||||
break voxelPassB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lowDist < 0) continue; // kein niedrigerer Nachbar im Suchradius
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
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