Root-Bone XZ in-memory einfrieren beim Clip-Apply (kein Drift mehr ingame)

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 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 10:01:37 +02:00
parent 07a4c6a323
commit 71ba297657

View File

@@ -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<AnimTrack<?>> 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={}",