snapRootBoneXZ: flachsten Joint mit Translations snappen statt nur Tiefe-0

Rigs wo Hips auf Tiefe 1 liegt (Kind des Root) wurden bisher nicht gesnappt
weil nur Tiefe-0-Joints gefunden wurden. Jetzt wird die kleinste Tiefe unter
allen Joints mit Translation-Track gesucht und nur diese Ebene eingefroren.
Passt zu beiden Rig-Strukturen (Root-Translation und Hips-Translation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 10:09:27 +02:00
parent 71ba297657
commit 7dcf16fddf

View File

@@ -261,12 +261,29 @@ public class AnimationLibrary extends BaseAppState {
}
/**
* Friert X und Z des Root-Knochens (kein Eltern-Joint) auf den Wert von Frame 0 ein.
* Friert X und Z des "Hüft-Knochens" auf den Wert von Frame 0 ein.
* Y (Höhenachse, JME3 Y-Up) bleibt vollständig erhalten — sit_down / Jump / Bounce laufen korrekt.
*
* Strategie: findet die kleinste Tiefe unter allen Joints die einen Translation-Track haben.
* Bei Rigs wo Root (Tiefe 0) selbst Translations hat, wird Root gesnappt.
* Bei Rigs wo Hips (Tiefe 1) die erste Ebene mit Translations ist (Root hat nur Rotation),
* wird Hips gesnappt. So passt der Snap zu beiden Rig-Strukturen.
* Erstellt einen neuen in-memory-Clip; J3O-Dateien bleiben unverändert.
*/
private static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) {
if (clip == null || armature == null) return clip;
// Tiefe des flachsten Joints mit Translation-Track bestimmen
int minDepth = Integer.MAX_VALUE;
for (AnimTrack<?> track : clip.getTracks()) {
if (!(track instanceof TransformTrack tt) || !(tt.getTarget() instanceof Joint j)) continue;
Vector3f[] tr = tt.getTranslations();
if (tr != null && tr.length > 0) {
minDepth = Math.min(minDepth, jointDepth(j));
}
}
if (minDepth == Integer.MAX_VALUE) return clip; // keine Translation-Tracks vorhanden
List<AnimTrack<?>> newTracks = new ArrayList<>();
boolean modified = false;
for (AnimTrack<?> track : clip.getTracks()) {
@@ -274,12 +291,8 @@ public class AnimationLibrary extends BaseAppState {
newTracks.add(track);
continue;
}
if (j.getParent() != null) {
newTracks.add(track);
continue;
}
Vector3f[] translations = tt.getTranslations();
if (translations == null || translations.length == 0) {
if (translations == null || translations.length == 0 || jointDepth(j) != minDepth) {
newTracks.add(track);
continue;
}
@@ -291,8 +304,8 @@ public class AnimationLibrary extends BaseAppState {
}
newTracks.add(new TransformTrack(j, tt.getTimes(), snapped, tt.getRotations(), tt.getScales()));
modified = true;
log.debug("[AnimLib] '{}': Root-Joint '{}' XZ={},{} eingefroren, Y frei",
clip.getName(), j.getName(), f0x, f0z);
log.info("[AnimLib] '{}': Tiefe-{}-Joint '{}' XZ={},{} eingefroren, Y frei",
clip.getName(), minDepth, j.getName(), f0x, f0z);
}
if (!modified) return clip;
AnimClip result = new AnimClip(clip.getName());
@@ -300,6 +313,13 @@ public class AnimationLibrary extends BaseAppState {
return result;
}
private static int jointDepth(Joint j) {
int d = 0;
Joint p = j.getParent();
while (p != null) { d++; p = p.getParent(); }
return d;
}
/** Loggt alle Tracks eines Clips: Bone-Name, hat Translation (T), Rotation (R), Scale (S). */
private void dumpClipTracks(com.jme3.anim.AnimClip clip) {
log.info("[ClipDump] '{}' length={}s tracks={}",