From 71ba297657bf08e2d4daaa52733811f3e2f91baf Mon Sep 17 00:00:00 2001 From: Mario Date: Sun, 21 Jun 2026 10:01:37 +0200 Subject: [PATCH] Root-Bone XZ in-memory einfrieren beim Clip-Apply (kein Drift mehr ingame) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Beim Anwenden eines Clips auf ein Modell (AnimationLibrary.applyTo) wird der Root-Joint-Track jetzt in-memory gepatcht: X und Z werden auf Frame-0-Wert eingefroren, Y (Höhenachse) bleibt vollständig frei. Damit bleibt das Mesh immer über der Physik-Kapsel, sit_down/Jump/Bounce laufen korrekt weiter. J3O-Dateien werden nicht verändert. Co-Authored-By: Claude Sonnet 4.6 --- .../game/animation/AnimationLibrary.java | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) 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 dae3098..f6ba3ed 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 @@ -9,6 +9,8 @@ import com.jme3.scene.Spatial; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.jme3.math.Vector3f; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -115,6 +117,7 @@ public class AnimationLibrary extends BaseAppState { return false; } + target = snapRootBoneXZ(target, sc.getArmature()); ac.addAnimClip(target); log.info("[AnimLib] Clip '{}' zu AnimComposer von '{}' hinzugefügt", clipName, model.getName()); if (clipName.equals("sit_down")) { @@ -257,6 +260,46 @@ public class AnimationLibrary extends BaseAppState { return null; } + /** + * Friert X und Z des Root-Knochens (kein Eltern-Joint) auf den Wert von Frame 0 ein. + * Y (Höhenachse, JME3 Y-Up) bleibt vollständig erhalten — sit_down / Jump / Bounce laufen korrekt. + * 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; + List> newTracks = new ArrayList<>(); + boolean modified = false; + for (AnimTrack track : clip.getTracks()) { + if (!(track instanceof TransformTrack tt) || !(tt.getTarget() instanceof Joint j)) { + newTracks.add(track); + continue; + } + if (j.getParent() != null) { + newTracks.add(track); + continue; + } + Vector3f[] translations = tt.getTranslations(); + if (translations == null || translations.length == 0) { + newTracks.add(track); + continue; + } + float f0x = translations[0].x; + float f0z = translations[0].z; + Vector3f[] snapped = new Vector3f[translations.length]; + for (int i = 0; i < translations.length; i++) { + snapped[i] = new Vector3f(f0x, translations[i].y, f0z); + } + 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); + } + if (!modified) return clip; + AnimClip result = new AnimClip(clip.getName()); + result.setTracks(newTracks.toArray(new AnimTrack[0])); + return result; + } + /** 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={}",