Kampfsystem für Zweihänder erweitert
This commit is contained in:
@@ -664,18 +664,10 @@ public class AnimPreviewState extends BaseAppState {
|
||||
// snap passiert ausschließlich zur Laufzeit im Spiel via AnimationLibrary.
|
||||
boolean isJ3oSource = animAssetPath.matches("(?i).*\\.j3o$");
|
||||
|
||||
// FBX-Import erfordert ein geladenes Charakter-Modell.
|
||||
// Ohne Ziel-Armatur kann normalizeAndRetargetClip die $AssimpFbx$_Translation-Orphan-Joints
|
||||
// nicht in den Hips-Track falten → Hips-Translation geht verloren → Charakter schrumpft.
|
||||
if (!isJ3oSource && srcArm != null && dstArm == null) {
|
||||
input.animPreviewStatus =
|
||||
"Fehler: FBX-Import erfordert ein geladenes Charakter-Modell. " +
|
||||
"Bitte zuerst das Modell laden, dann die Animation importieren.";
|
||||
LOG.warn("[AnimPreview] FBX-Import ohne Charakter-Modell abgebrochen: {}", animAssetPath);
|
||||
return;
|
||||
}
|
||||
|
||||
com.jme3.anim.Armature snapArm = dstArm != null ? dstArm : srcArm;
|
||||
// Kein Charakter-Modell geladen: Normalisierung gegen eigenes Skelett (Orphan-Fold + Scale-Strip).
|
||||
// Das erzeugte j3o ist später per redirectAnimClipByName auf jedes kompatible Rig anwendbar.
|
||||
com.jme3.anim.Armature effectiveDst = dstArm != null ? dstArm : srcArm;
|
||||
com.jme3.anim.Armature snapArm = effectiveDst;
|
||||
|
||||
int saved = 0;
|
||||
for (AnimClip clip : sourceAC.getAnimClips()) {
|
||||
@@ -688,7 +680,7 @@ public class AnimPreviewState extends BaseAppState {
|
||||
}
|
||||
}
|
||||
AnimClip result;
|
||||
if (!retarget) {
|
||||
if (!retarget && (isJ3oSource || srcArm == null)) {
|
||||
result = clip;
|
||||
} else if (isJ3oSource) {
|
||||
// j3o-Clips wurden bereits beim FBX-Import normalisiert (in Modell-Raum gespeichert).
|
||||
@@ -696,12 +688,15 @@ public class AnimPreviewState extends BaseAppState {
|
||||
result = redirectAnimClipByName(clip, dstArm);
|
||||
} else {
|
||||
// FBX-Import: Normalisierung in Modell-Raum (Orphan-Fold + Bind-Pose-Delta).
|
||||
// Das resultierende j3o ist dann mit plain redirectAnimClipByName kompatibel.
|
||||
result = normalizeAndRetargetClip(clip, dstArm);
|
||||
// Kein Charakter-Modell → effectiveDst == srcArm → reine Normalisierung ohne Retarget.
|
||||
result = normalizeAndRetargetClip(clip, effectiveDst);
|
||||
if (result == null) {
|
||||
LOG.warn("[AnimPreview] normalizeAndRetarget: keine Joints gemappt, versuche Retargeting");
|
||||
result = de.blight.game.animation.RetargetingSystem.retarget(clip, srcArm, dstArm);
|
||||
if (dstArm != null) {
|
||||
result = de.blight.game.animation.RetargetingSystem.retarget(clip, srcArm, dstArm);
|
||||
}
|
||||
}
|
||||
if (result == null) result = clip;
|
||||
}
|
||||
if (result == null) continue;
|
||||
|
||||
|
||||
@@ -997,6 +997,11 @@ public class AnimSetController implements ToolController {
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Aktions-Combo mit Suchfeld ────────────────────────────────────────
|
||||
javafx.scene.control.TextField actionSearch = new javafx.scene.control.TextField();
|
||||
actionSearch.setPromptText("Aktion suchen…");
|
||||
actionSearch.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
ComboBox<de.blight.game.animation.AnimationAction> actionCombo = new ComboBox<>();
|
||||
actionCombo.getItems().addAll(available);
|
||||
javafx.util.Callback<javafx.scene.control.ListView<de.blight.game.animation.AnimationAction>,
|
||||
@@ -1013,29 +1018,85 @@ public class AnimSetController implements ToolController {
|
||||
actionCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
actionCombo.getSelectionModel().selectFirst();
|
||||
|
||||
ComboBox<String> clipCombo = new ComboBox<>();
|
||||
actionSearch.textProperty().addListener((obs, ov, nv) -> {
|
||||
String lower = nv == null ? "" : nv.toLowerCase();
|
||||
actionCombo.getItems().setAll(available.stream()
|
||||
.filter(a -> lower.isEmpty()
|
||||
|| a.displayName().toLowerCase().contains(lower)
|
||||
|| a.name().toLowerCase().contains(lower))
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
if (!actionCombo.getItems().isEmpty()) actionCombo.getSelectionModel().selectFirst();
|
||||
});
|
||||
|
||||
// ── Clip-Combo mit Suchfeld ───────────────────────────────────────────
|
||||
java.util.List<String> allClipNames = new java.util.ArrayList<>(animSetClipListView.getItems());
|
||||
for (String subName : editableSubClips.keySet()) {
|
||||
if (!allClipNames.contains(subName)) allClipNames.add(subName);
|
||||
}
|
||||
|
||||
javafx.scene.control.TextField clipSearch = new javafx.scene.control.TextField();
|
||||
clipSearch.setPromptText("Clip suchen…");
|
||||
clipSearch.setMaxWidth(Double.MAX_VALUE);
|
||||
|
||||
ComboBox<String> clipCombo = new ComboBox<>();
|
||||
clipCombo.getItems().addAll(allClipNames);
|
||||
clipCombo.setMaxWidth(Double.MAX_VALUE);
|
||||
clipCombo.getSelectionModel().selectFirst();
|
||||
|
||||
clipSearch.textProperty().addListener((obs, ov, nv) -> {
|
||||
String lower = nv == null ? "" : nv.toLowerCase();
|
||||
clipCombo.getItems().setAll(allClipNames.stream()
|
||||
.filter(c -> lower.isEmpty() || c.toLowerCase().contains(lower))
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
if (!clipCombo.getItems().isEmpty()) clipCombo.getSelectionModel().selectFirst();
|
||||
});
|
||||
|
||||
// ── Layout ────────────────────────────────────────────────────────────
|
||||
VBox actionBox = new VBox(4, actionSearch, actionCombo);
|
||||
VBox clipBox = new VBox(4, clipSearch, clipCombo);
|
||||
|
||||
javafx.scene.layout.GridPane grid = new javafx.scene.layout.GridPane();
|
||||
grid.setHgap(8); grid.setVgap(6);
|
||||
grid.add(new Label("Aktion:"), 0, 0); grid.add(actionCombo, 1, 0);
|
||||
grid.add(new Label("Clip:"), 0, 1); grid.add(clipCombo, 1, 1);
|
||||
grid.setHgap(8); grid.setVgap(8);
|
||||
grid.add(new Label("Aktion:"), 0, 0); grid.add(actionBox, 1, 0);
|
||||
grid.add(new Label("Clip:"), 0, 1); grid.add(clipBox, 1, 1);
|
||||
javafx.scene.layout.ColumnConstraints cc = new javafx.scene.layout.ColumnConstraints();
|
||||
cc.setHgrow(Priority.ALWAYS);
|
||||
grid.getColumnConstraints().addAll(new javafx.scene.layout.ColumnConstraints(), cc);
|
||||
|
||||
CheckBox weitereCB = new CheckBox("Weitere zuordnen");
|
||||
|
||||
VBox content = new VBox(12, grid, weitereCB);
|
||||
Dialog<ButtonType> dlg = new Dialog<>();
|
||||
dlg.initOwner(ctx.stageSupplier().get());
|
||||
dlg.setTitle("Aktion zuordnen");
|
||||
ButtonType ok = new ButtonType("Hinzufügen", ButtonBar.ButtonData.OK_DONE);
|
||||
dlg.getDialogPane().getButtonTypes().addAll(ok, ButtonType.CANCEL);
|
||||
dlg.getDialogPane().setContent(grid);
|
||||
dlg.getDialogPane().setContent(content);
|
||||
dlg.getDialogPane().setPrefWidth(480);
|
||||
|
||||
// Wenn "Weitere zuordnen" aktiv: Dialog offen lassen, nächste Aktion auto-selektieren.
|
||||
javafx.scene.Node okNode = dlg.getDialogPane().lookupButton(ok);
|
||||
okNode.addEventFilter(javafx.event.ActionEvent.ACTION, ev -> {
|
||||
if (!weitereCB.isSelected()) return;
|
||||
ev.consume();
|
||||
var action = actionCombo.getValue();
|
||||
var clip = clipCombo.getValue();
|
||||
if (action == null || clip == null) return;
|
||||
String entry = action.name() + " → " + clip;
|
||||
if (!animSetActionListView.getItems().contains(entry)) {
|
||||
animSetActionListView.getItems().add(entry);
|
||||
autoSaveAnimSet();
|
||||
}
|
||||
available.remove(action);
|
||||
String lower = actionSearch.getText() == null ? "" : actionSearch.getText().toLowerCase();
|
||||
actionCombo.getItems().setAll(available.stream()
|
||||
.filter(a -> lower.isEmpty()
|
||||
|| a.displayName().toLowerCase().contains(lower)
|
||||
|| a.name().toLowerCase().contains(lower))
|
||||
.collect(java.util.stream.Collectors.toList()));
|
||||
if (!actionCombo.getItems().isEmpty()) actionCombo.getSelectionModel().selectFirst();
|
||||
});
|
||||
|
||||
dlg.showAndWait().ifPresent(bt -> {
|
||||
if (bt != ok) return;
|
||||
var action = actionCombo.getValue();
|
||||
|
||||
Reference in New Issue
Block a user