Zwischenstand gesichert bei den voxel poxoxeln
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
Reference in New Issue
Block a user