diff --git a/blight-assets/src/main/resources/Models/Chars/mainchar.j3o b/blight-assets/src/main/resources/Models/Chars/mainchar.j3o index 607c84f..d291e97 100644 Binary files a/blight-assets/src/main/resources/Models/Chars/mainchar.j3o and b/blight-assets/src/main/resources/Models/Chars/mainchar.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/alive_again.j3o b/blight-assets/src/main/resources/animations/clips/alive_again.j3o index a7891e2..c02ba23 100644 Binary files a/blight-assets/src/main/resources/animations/clips/alive_again.j3o and b/blight-assets/src/main/resources/animations/clips/alive_again.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/get_up_sitting.j3o b/blight-assets/src/main/resources/animations/clips/get_up_sitting.j3o index 532b3de..1c6b149 100644 Binary files a/blight-assets/src/main/resources/animations/clips/get_up_sitting.j3o and b/blight-assets/src/main/resources/animations/clips/get_up_sitting.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/idle.j3o b/blight-assets/src/main/resources/animations/clips/idle.j3o index 8c90286..aac8cab 100644 Binary files a/blight-assets/src/main/resources/animations/clips/idle.j3o and b/blight-assets/src/main/resources/animations/clips/idle.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/idle_jump.j3o b/blight-assets/src/main/resources/animations/clips/idle_jump.j3o index b9d85bc..374e699 100644 Binary files a/blight-assets/src/main/resources/animations/clips/idle_jump.j3o and b/blight-assets/src/main/resources/animations/clips/idle_jump.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/pickup.j3o b/blight-assets/src/main/resources/animations/clips/pickup.j3o index 63da92b..394b217 100644 Binary files a/blight-assets/src/main/resources/animations/clips/pickup.j3o and b/blight-assets/src/main/resources/animations/clips/pickup.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/running.j3o b/blight-assets/src/main/resources/animations/clips/running.j3o index ffcb192..5a89ac2 100644 Binary files a/blight-assets/src/main/resources/animations/clips/running.j3o and b/blight-assets/src/main/resources/animations/clips/running.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/running_jump.j3o b/blight-assets/src/main/resources/animations/clips/running_jump.j3o index 29ffd26..228a147 100644 Binary files a/blight-assets/src/main/resources/animations/clips/running_jump.j3o and b/blight-assets/src/main/resources/animations/clips/running_jump.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/sit_down.j3o b/blight-assets/src/main/resources/animations/clips/sit_down.j3o index b2e29e5..42aaaea 100644 Binary files a/blight-assets/src/main/resources/animations/clips/sit_down.j3o and b/blight-assets/src/main/resources/animations/clips/sit_down.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/sit_down_bench.j3o b/blight-assets/src/main/resources/animations/clips/sit_down_bench.j3o index 7bacac2..bc9a04f 100644 Binary files a/blight-assets/src/main/resources/animations/clips/sit_down_bench.j3o and b/blight-assets/src/main/resources/animations/clips/sit_down_bench.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/sitting.j3o b/blight-assets/src/main/resources/animations/clips/sitting.j3o index b99c62c..512e766 100644 Binary files a/blight-assets/src/main/resources/animations/clips/sitting.j3o and b/blight-assets/src/main/resources/animations/clips/sitting.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/sitting_floor.j3o b/blight-assets/src/main/resources/animations/clips/sitting_floor.j3o index bbabcfd..d6d0833 100644 Binary files a/blight-assets/src/main/resources/animations/clips/sitting_floor.j3o and b/blight-assets/src/main/resources/animations/clips/sitting_floor.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/sprinting.j3o b/blight-assets/src/main/resources/animations/clips/sprinting.j3o index faf84ac..d184823 100644 Binary files a/blight-assets/src/main/resources/animations/clips/sprinting.j3o and b/blight-assets/src/main/resources/animations/clips/sprinting.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/stand_up.j3o b/blight-assets/src/main/resources/animations/clips/stand_up.j3o index 880f7b6..1812335 100644 Binary files a/blight-assets/src/main/resources/animations/clips/stand_up.j3o and b/blight-assets/src/main/resources/animations/clips/stand_up.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/standup.j3o b/blight-assets/src/main/resources/animations/clips/standup.j3o index 88360ec..f3b210d 100644 Binary files a/blight-assets/src/main/resources/animations/clips/standup.j3o and b/blight-assets/src/main/resources/animations/clips/standup.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/tpose.j3o b/blight-assets/src/main/resources/animations/clips/tpose.j3o index 9720d41..465c246 100644 Binary files a/blight-assets/src/main/resources/animations/clips/tpose.j3o and b/blight-assets/src/main/resources/animations/clips/tpose.j3o differ diff --git a/blight-assets/src/main/resources/animations/clips/walking.j3o b/blight-assets/src/main/resources/animations/clips/walking.j3o index 4ce60b4..1bdf353 100644 Binary files a/blight-assets/src/main/resources/animations/clips/walking.j3o and b/blight-assets/src/main/resources/animations/clips/walking.j3o differ diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index d45ebeb..faed198 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -9439,25 +9439,6 @@ public class EditorApp extends Application { refreshCharAnimSetCombo(); charAnimSetCombo.setOnAction(e -> updateCharActionCombosFromSet()); - Button embedAnimBtn = new Button("Animationen einbetten"); - embedAnimBtn.setMaxWidth(Double.MAX_VALUE); - embedAnimBtn.setDisable(true); - javafx.beans.value.ChangeListener embedEnableListener = (obs, ov, nv) -> { - boolean ready = charModelCombo.getValue() != null && !charModelCombo.getValue().isBlank() - && charAnimSetCombo.getValue() != null && !charAnimSetCombo.getValue().isBlank(); - embedAnimBtn.setDisable(!ready); - }; - charModelCombo.valueProperty().addListener(embedEnableListener); - charAnimSetCombo.valueProperty().addListener(embedEnableListener); - embedAnimBtn.setOnAction(e -> { - String modelPath = charModelCombo.getValue(); - String setName = charAnimSetCombo.getValue(); - if (modelPath == null || setName == null) return; - if (charEditorStatusLabel != null) - charEditorStatusLabel.setText("Bette Animationen ein…"); - input.animEmbedRequest.set(new SharedInput.AnimEmbedRequest(modelPath, setName)); - }); - Button stripClipsBtn = new Button("Eingebettete Clips löschen"); stripClipsBtn.setMaxWidth(Double.MAX_VALUE); stripClipsBtn.setDisable(true); @@ -9581,7 +9562,6 @@ public class EditorApp extends Application { charEditContainer.getChildren().addAll( new Label("Modell:"), charModelCombo, new Label("Anim-Set:"), charAnimSetCombo, - embedAnimBtn, stripClipsBtn ); diff --git a/blight-editor/src/main/java/de/blight/editor/state/AnimPreviewState.java b/blight-editor/src/main/java/de/blight/editor/state/AnimPreviewState.java index e71bdd3..a502783 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/AnimPreviewState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/AnimPreviewState.java @@ -668,21 +668,31 @@ public class AnimPreviewState extends BaseAppState { for (Spatial child : n.getChildren()) collectControlTypes(child, out); } - /** Speichert das aktuelle Modell (inkl. aller AnimClips) zurück auf Disk. */ + /** Speichert das aktuelle Modell zurück auf Disk – ohne eingebettete AnimClips. */ private void saveModel() { if (currentModelPath == null || currentModel == null) return; if (!currentModelPath.endsWith(".j3o")) { LOG.warn("[AnimPreview] Speichern übersprungen – kein .j3o: {}", currentModelPath); return; } + AnimComposer ac = findControl(currentModel, AnimComposer.class); + List tempClips = new java.util.ArrayList<>(); + if (ac != null) { + tempClips.addAll(ac.getAnimClips()); + for (AnimClip c : tempClips) ac.removeAnimClip(c); + } Path file = ASSET_ROOT.resolve(currentModelPath.replace('/', java.io.File.separatorChar)); try { BinaryExporter.getInstance().save(currentModel, file.toFile()); - LOG.info("[AnimPreview] Modell gespeichert: {}", currentModelPath); + LOG.info("[AnimPreview] Modell gespeichert (ohne Clips): {}", currentModelPath); assets.deleteFromCache(new ModelKey(currentModelPath)); } catch (Exception e) { input.animPreviewStatus += " | Speicherfehler: " + e.getMessage(); LOG.error("[AnimPreview] Speicherfehler: {}", e.toString()); + } finally { + if (ac != null) { + for (AnimClip c : tempClips) ac.addAnimClip(c); + } } } 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 f77fddf..475c816 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 @@ -117,7 +117,6 @@ 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")) { @@ -129,6 +128,12 @@ public class AnimationLibrary extends BaseAppState { /** Wendet alle geladenen Clips auf {@code model} an (nur wenn es ein Rig hat). */ public void applyAllTo(Spatial model) { if (RetargetingSystem.findSkinningControl(model) == null) return; + AnimComposer ac = RetargetingSystem.findAnimComposer(model); + if (ac != null) { + for (AnimClip c : new java.util.ArrayList<>(ac.getAnimClips())) { + ac.removeAnimClip(c); + } + } int applied = 0; for (String key : clips.keySet()) { if (applyTo(key, model)) applied++; @@ -234,6 +239,9 @@ public class AnimationLibrary extends BaseAppState { for (String name : ac.getAnimClipsNames()) { com.jme3.anim.AnimClip animClip = ac.getAnimClip(name); + if (armature != null) { + animClip = snapRootBoneXZ(animClip, armature); + } clips.put(name, animClip); if (armature != null) armatures.put(name, armature); log.info("[AnimLib] Clip geladen: '{}' aus {}", name, assetKey); @@ -260,15 +268,21 @@ public class AnimationLibrary extends BaseAppState { return null; } + // Clips mit Vorwärts-Root-Motion in Local-Y (rennen, gehen, springen): + // Y wird auf 0 eingefroren → kein Drift. Alle anderen Clips: Y bleibt frei (Hinsetzen usw.) + private static final java.util.Set LOCOMOTION_CLIPS = java.util.Set.of( + "running", "walking", "sprinting", "running_jump" + ); + /** - * 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. + * Entfernt Root-Motion aus dem flachsten Bone mit Translation-Track (typischerweise Hips). + * Nur dieser eine Bone wird modifiziert — alle anderen Bones bleiben unverändert. * - * 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. + * In diesen Mixamo-Exporten ist die Achsenbelegung des Hips-Bones: + * Local X → seitlich → wird auf 0 eingefroren (kein Seiten-Drift) + * Local Y → vorwärts → wird auf 0 eingefroren für Lauf-Clips (kein Vorwärts-Drift) + * bleibt frei für Sitz/Stand-Clips (leichte Neigungsbewegung) + * Local Z → Höhe → IMMER frei lassen (Charakter-Höhe und Setz-Bewegung erhalten) */ public static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) { if (clip == null || armature == null) return clip; @@ -282,7 +296,9 @@ public class AnimationLibrary extends BaseAppState { minDepth = Math.min(minDepth, jointDepth(j)); } } - if (minDepth == Integer.MAX_VALUE) return clip; // keine Translation-Tracks vorhanden + if (minDepth == Integer.MAX_VALUE) return clip; + + boolean isLocomotion = LOCOMOTION_CLIPS.contains(clip.getName()); List> newTracks = new ArrayList<>(); boolean modified = false; @@ -292,20 +308,45 @@ public class AnimationLibrary extends BaseAppState { continue; } Vector3f[] translations = tt.getTranslations(); + // Nur den flachsten Bone anfassen – alle anderen unverändert lassen if (translations == null || translations.length == 0 || jointDepth(j) != minDepth) { newTracks.add(track); continue; } - float f0x = translations[0].x; - float f0z = translations[0].z; + + // Y-Normalisierung für Nicht-Lauf-Clips: Frame-0 auf Bind-Pose-Y + float bindY = j.getInitialTransform().getTranslation().y; + float frame0Y = translations[0].y; + float yOffset = bindY - frame0Y; + + // Diagnostik + float yMin = Float.MAX_VALUE, yMax = -Float.MAX_VALUE; + float xRange = 0, zRange = 0; + for (Vector3f t : translations) { + if (t.y < yMin) yMin = t.y; + if (t.y > yMax) yMax = t.y; + xRange = Math.max(xRange, Math.abs(t.x - translations[0].x)); + zRange = Math.max(zRange, Math.abs(t.z - translations[0].z)); + } + log.info("[AnimLib] snap '{}' root='{}' locomotion={} bindY={} frame0Y={} yOffset={} yRange={} xRange={} zRange={}", + clip.getName(), j.getName(), isLocomotion, + String.format("%.3f", bindY), String.format("%.3f", frame0Y), + String.format("%.3f", yOffset), String.format("%.3f", yMax - yMin), + String.format("%.3f", xRange), String.format("%.3f", zRange)); + Vector3f[] snapped = new Vector3f[translations.length]; for (int i = 0; i < translations.length; i++) { - snapped[i] = new Vector3f(f0x, translations[i].y, f0z); + float newY = isLocomotion + ? 0f // Lauf-Clips: Y einfrieren (Vorwärts-Drift weg) + : (translations[i].y + yOffset); // Rest: Y normalisiert (Neigung erhalten) + snapped[i] = new Vector3f( + 0f, // X immer 0 (Seiten-Drift weg) + newY, + translations[i].z // Z immer frei (Höhe und Setz-Bewegung erhalten) + ); } newTracks.add(new TransformTrack(j, tt.getTimes(), snapped, tt.getRotations(), tt.getScales())); modified = true; - 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()); diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 9d6cd91..c9383f7 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -319,6 +319,7 @@ public class WorldScene extends BaseAppState { if (mc != null && mc.getModelPath() != null) { try { Spatial loaded = assetManager.loadModel(mc.getModelPath()); + stripEmbeddedClips(loaded, mc.getModelPath()); loaded.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); // Auf 1.8 m skalieren – Höhe aus Vertex-Daten (zuverlässiger als BoundingBox @@ -358,6 +359,34 @@ public class WorldScene extends BaseAppState { return buildCharacter(); } + private static final String[] ASSET_SEARCH_ROOTS = { + "blight-assets/src/main/resources", + "blight-assets/build/resources/main", + "blight-assets/bin/main", + "assets", + }; + + private void stripEmbeddedClips(Spatial model, String modelPath) { + com.jme3.anim.AnimComposer ac = de.blight.game.animation.RetargetingSystem.findAnimComposer(model); + if (ac == null || ac.getAnimClips().isEmpty()) return; + int count = ac.getAnimClips().size(); + for (com.jme3.anim.AnimClip c : new java.util.ArrayList<>(ac.getAnimClips())) { + ac.removeAnimClip(c); + } + String rel = modelPath.replace('/', java.io.File.separatorChar); + for (String base : ASSET_SEARCH_ROOTS) { + java.nio.file.Path file = java.nio.file.Paths.get(base).resolve(rel); + if (!java.nio.file.Files.exists(file)) continue; + try { + com.jme3.export.binary.BinaryExporter.getInstance().save(model, file.toFile()); + log.info("[WorldScene] {} eingebettete Clips aus '{}' entfernt: {}", count, modelPath, file); + } catch (Exception e) { + log.warn("[WorldScene] Speichern fehlgeschlagen ({}): {}", file, e.getMessage()); + } + } + assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath)); + } + private MainCharacter findMainCharacter() { java.nio.file.Path charDir = AnimationLibrary.findAssetRoot().resolve("character"); for (GameCharacter c : CharacterIO.loadAll(charDir)) {