diff --git a/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java b/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java index f6ba3ed..0155f1a 100644 --- a/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java +++ b/blight-game/src/main/java/de/blight/game/animation/AnimationLibrary.java @@ -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> 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={}",