snapRootBoneXZ: Achsen-Fix für Mixamo-Hips (X=0, Y=0/frei, Z=frei)
In diesen Mixamo-Exporten ist Local-Y die Vorwärts-Richtung (nicht Höhe), Local-Z die Höhe. Bisheriger Code fror Z=0 ein → Charakter 1m zu tief. Und Y war frei → Lauf-Drift blieb. Neue Logik: X → 0 (kein Seiten-Drift) Y → 0 für Lauf-Clips (running/walking/sprinting/running_jump), normalisiert sonst Z → vollständig frei (Höhe und Setz/Aufsteh-Bewegung erhalten) Nur der flachste Bone (Hips) wird modifiziert, alle anderen unberührt. Clips vollständig neu importiert mit korrektem Snap. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<String> 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
|
||||
);
|
||||
|
||||
|
||||
@@ -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<AnimClip> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user