Zwischenstand gesichert bei den voxel poxoxeln
This commit is contained in:
@@ -58,11 +58,10 @@ void main() {
|
|||||||
vec2 uvY = vWorldPos.xz / m_TexScale;
|
vec2 uvY = vWorldPos.xz / m_TexScale;
|
||||||
vec2 uvZ = vWorldPos.xy / m_TexScale;
|
vec2 uvZ = vWorldPos.xy / m_TexScale;
|
||||||
|
|
||||||
// Flach bis ~22,5° Gefälle (deckt sich mit der Bake-Schwelle in VoxelEditorState,
|
// Flach bis ~45° Gefälle. LOD0-Smoothing (3 Passes, 0.3 Stärke) kann die Normalen
|
||||||
// die Rampen bis 22,5° glättet – vorher lag diese Grenze bei ~15° und sorgte dafür,
|
// an Rampenfüßen (14°-Rampe, ideal normal.y≈0.97) auf 0.70-0.85 herunterziehen.
|
||||||
// dass geometrisch glatt gebackene, aber 15-22,5° steile Rampen trotzdem komplett
|
// Untere Grenze 0.70 (≈45°) – echte Klippen (normal.y<0.70) zeigen weiter Gebirge.
|
||||||
// mit der Klippen-Textur eingefärbt wurden statt mit der Flach-Textur.
|
float flatBlend = smoothstep(0.70, 0.90, vNormal.y);
|
||||||
float flatBlend = smoothstep(0.90, 0.95, vNormal.y);
|
|
||||||
float steepBlend = 1.0 - flatBlend;
|
float steepBlend = 1.0 - flatBlend;
|
||||||
|
|
||||||
// Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar.
|
// Flat: reines XZ-UV wie das Terrain (uvY = worldPos.xz / texScale), kein Triplanar.
|
||||||
|
|||||||
@@ -2111,78 +2111,160 @@ public class VoxelEditorState extends BaseAppState {
|
|||||||
// Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend
|
// Fix v6 (aktuell): Steigungs-Fortsetzung komplett gestrichen. Reiner Distanz-Blend
|
||||||
// von der (ring-gemittelten) Kanten-Höhe zur tatsächlichen Basisterrain-Höhe. Folgt
|
// 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.
|
// 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
|
// Zwei getrennte Passes wegen unterschiedlicher Schutz-Logik:
|
||||||
// daneben eine niedrigere Terasse oder das Basisterrain-Tal liegt. Das erzeugt
|
|
||||||
// vertikale Klippen an allen Plateau-Kanten.
|
|
||||||
//
|
//
|
||||||
// Fix: Für jede Spalte den nächsten Nachbarn mit Höhe < h0 suchen
|
// Pass A – Basisterrain-Übergang:
|
||||||
// (Voxel-Spalte aus hfMap ODER Basisterrain außerhalb). Wenn gefunden bei
|
// Sucht nur Nicht-hfMap-Zellen (echtes Basisterrain). Voxel-Spalten in
|
||||||
// Chebyshev-Distanz d mit mittlerer Höhe avgLow:
|
// hfMap blocken die Sicht natürlich (Kliffwände sind in hfMap → werden
|
||||||
// rampH = avgLow + d / 4.0
|
// übersprungen). MAX_TERRAIN_DIFF verhindert tiefe Klippen.
|
||||||
// Spalte wird auf min(smoothH, rampH) abgesenkt – nie angehoben.
|
// Kein Ring-Vorfilter nötig – der Schutz kommt durch hfMap-Blocking.
|
||||||
// Entspricht 4 Schritte vor / 1 Schritt runter, deckt beide Bedingungen ab:
|
//
|
||||||
// „Tal darunter" (avgLow << h0) und „<1m über Basisterrain" (kleines dH).
|
// Pass B – Voxel-zu-Voxel-Übergang:
|
||||||
// Ringsuche je Spalte unabhängig von anderen Schritt-2b-Ergebnissen →
|
// Sucht nur hfMap-Zellen. Ring-1-Vorfilter: alle Voxel auf gleicher Höhe
|
||||||
// keine Kettenfortpflanzung, keine Divergenz.
|
// → Plateau-Innenzelle → überspringen.
|
||||||
{
|
{
|
||||||
final float RAMP_RATIO = 4.0f;
|
final float RAMP_RATIO = 4.0f;
|
||||||
final int MAX_RAMP_R = 20;
|
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<>();
|
Map<Long, Float> terrainCache2b = new HashMap<>();
|
||||||
|
|
||||||
for (Map.Entry<Long, Float> entry : hfMap.entrySet()) {
|
for (Map.Entry<Long, Float> entry : hfMap.entrySet()) {
|
||||||
long hk0 = entry.getKey();
|
long hk0 = entry.getKey();
|
||||||
float h0raw = entry.getValue(); // Original-Voxelhöhe
|
float h0raw = entry.getValue();
|
||||||
float h0sm = smoothMap.getOrDefault(hk0, h0raw); // Schritt-2-Ausgabe
|
float h0sm = smoothMap.getOrDefault(hk0, h0raw);
|
||||||
int colX2b = (int)(hk0 / 60001L) - 30000;
|
int colX2b = (int)(hk0 / 60001L) - 30000;
|
||||||
int colZ2b = (int)(hk0 % 60001L) - 30000;
|
int colZ2b = (int)(hk0 % 60001L) - 30000;
|
||||||
|
|
||||||
// Ringsuche nach nächstem niedrigeren Nachbarn (Voxel oder Terrain)
|
float minRampH = h0sm;
|
||||||
float sumLowH = 0f;
|
// Terrain ÜBER dem Voxel: Voxel nach oben auf Terrain-Niveau heben.
|
||||||
int lowCount = 0;
|
// Kein +0.1f-Offset an Ring-1 → Ring-1-Oberfläche liegt exakt auf Terrain
|
||||||
int lowDist = -1;
|
// (nahtloser Übergang). Innere Ringe steigen per 4:1-Rampe weiter an.
|
||||||
|
float minRaiseH = Float.POSITIVE_INFINITY;
|
||||||
|
|
||||||
outer2b:
|
// ── Pass A: Basisterrain ──────────────────────────────────────────
|
||||||
for (int r = 1; r <= MAX_RAMP_R; r++) {
|
// 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 dz2b = -r; dz2b <= r; dz2b++) {
|
||||||
for (int dx2b = -r; dx2b <= r; dx2b++) {
|
for (int dx2b = -r; dx2b <= r; dx2b++) {
|
||||||
if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue;
|
if (Math.max(Math.abs(dx2b), Math.abs(dz2b)) != r) continue;
|
||||||
long hkN = (long)(colX2b + dx2b + 30000) * 60001L
|
long hkN = (long)(colX2b + dx2b + 30000) * 60001L
|
||||||
+ (colZ2b + dz2b + 30000);
|
+ (colZ2b + dz2b + 30000);
|
||||||
Float hN = hfMap.get(hkN);
|
if (hfMap.containsKey(hkN)) continue;
|
||||||
float neighborH;
|
Float cached = terrainCache2b.get(hkN);
|
||||||
if (hN != null) {
|
if (cached == null) {
|
||||||
neighborH = hN; // Voxel-Spalte: Originalhöhe
|
cached = terrainH((float)(colX2b + dx2b),
|
||||||
} else {
|
(float)(colZ2b + dz2b));
|
||||||
Float cached = terrainCache2b.get(hkN);
|
terrainCache2b.put(hkN, cached);
|
||||||
if (cached == null) {
|
|
||||||
cached = terrainH((float)(colX2b + dx2b),
|
|
||||||
(float)(colZ2b + dz2b));
|
|
||||||
terrainCache2b.put(hkN, cached);
|
|
||||||
}
|
|
||||||
neighborH = cached; // Basisterrain
|
|
||||||
}
|
}
|
||||||
if (neighborH < h0raw) {
|
float th = cached;
|
||||||
sumLowH += neighborH;
|
if (th >= h0sm) {
|
||||||
lowCount++;
|
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) {
|
if (maxThAtR > Float.NEGATIVE_INFINITY) {
|
||||||
lowDist = r;
|
float candidate = maxThAtR + 0.1f + (r - 1) / RAMP_RATIO;
|
||||||
break outer2b;
|
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;
|
if (minRampH < h0sm) {
|
||||||
float rampH = avgLowH + lowDist / RAMP_RATIO;
|
if (log.isTraceEnabled()) {
|
||||||
if (rampH < h0sm) {
|
log.trace(String.format("Schritt2b (%d,%d) h=%.2f → %.2f",
|
||||||
smoothMap.put(hk0, rampH);
|
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 -->
|
<!-- Material warnt bei linear-color-space Texturen ohne passenden Parameter – bekannt, kein Fehler -->
|
||||||
<logger name="com.jme3.material.Material" level="ERROR"/>
|
<logger name="com.jme3.material.Material" level="ERROR"/>
|
||||||
<logger name="de.blight.game.animation.FootIKControl" level="DEBUG"/>
|
<logger name="de.blight.game.animation.FootIKControl" level="DEBUG"/>
|
||||||
|
<logger name="de.blight.editor.state.VoxelEditorState" level="TRACE"/>
|
||||||
|
|
||||||
<root level="INFO">
|
<root level="INFO">
|
||||||
<appender-ref ref="CONSOLE"/>
|
<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