Charakter-Liegend-Fix: T-Pose im Editor, CullHint im Spiel, Strip in src+bin

Editor (AnimPreviewState):
- __tpose__ AnimClip mit Einzel-Frame-Track für Root-Joints (behebt NPE in
  ClipAction.doInterpolate; leerer Clip hatte null-tracks-Array)
- Beim Laden: eingebettete Clips automatisch strippt und in src+bin speichern;
  danach __tpose__ abspielen → Charakter steht in Bind-Pose
- stopAll(): zurück zur T-Pose statt SkinningControl deaktivieren
- Achsen-Indikator immer fest auf (0,0,0) statt bounding-box-Mitte

Spiel (WorldScene):
- CullHint.Always beim Laden, Inherit nach Animation-Setup (kein liegender Charakter)
- stripEmbeddedClips: speichert jetzt in src + bin + build (alle bekannten Pfade);
  besseres Logging bei fehlendem Pfad

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:25:23 +02:00
parent d3c6e8ed77
commit b3b943e588
2 changed files with 117 additions and 32 deletions

View File

@@ -307,7 +307,6 @@ public class WorldScene extends BaseAppState {
}
}
playerInput.setAnimationContext(animLib, setName, AnimationLibrary.findAssetRoot());
// Charakter sichtbar machen: idle-Animation läuft jetzt
if (characterVisual != null) {
characterVisual.setCullHint(Spatial.CullHint.Inherit);
}
@@ -347,8 +346,8 @@ public class WorldScene extends BaseAppState {
Node rotNode = new Node("charRot");
loaded.setLocalTranslation(0, offsetY, 0);
rotNode.attachChild(loaded);
// Charakter verstecken bis Animation läuft: das Armature-Node hat Rx(90°) aus dem
// GLB-Import → ohne laufende Animation liegt das Mesh auf dem Boden.
// Verstecken bis idle-Animation läuft: Armature hat Rx(90°) aus GLB-Import
// → ohne Animation liegt das Mesh (SkinningControl inaktiv oder Bind-Pose unklar).
rotNode.setCullHint(Spatial.CullHint.Always);
Node wrapper = new Node("character");
@@ -367,11 +366,18 @@ public class WorldScene extends BaseAppState {
}
private static final String[] MODEL_SAVE_ROOTS = {
"blight-assets/src/main/resources",
"blight-assets/bin/main",
"blight-assets/build/resources/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()) {
log.info("[WorldScene] Keine eingebetteten Clips in '{}' (AnimComposer={})",
modelPath, ac != null ? "leer" : "nicht gefunden");
log.info("[WorldScene] Keine eingebetteten Clips in '{}' ({})",
modelPath, ac != null ? "AnimComposer leer" : "kein AnimComposer");
return;
}
int count = ac.getAnimClips().size();
@@ -379,19 +385,22 @@ public class WorldScene extends BaseAppState {
for (com.jme3.anim.AnimClip c : new java.util.ArrayList<>(ac.getAnimClips())) {
ac.removeAnimClip(c);
}
java.nio.file.Path assetRoot = AnimationLibrary.findAssetRoot();
java.nio.file.Path file = assetRoot.resolve(modelPath.replace('/', java.io.File.separatorChar));
if (!java.nio.file.Files.exists(file)) {
log.warn("[WorldScene] Modelldatei nicht gefunden zum Speichern: {} (assetRoot={})",
file.toAbsolutePath(), assetRoot.toAbsolutePath());
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
return;
String rel = modelPath.replace('/', java.io.File.separatorChar);
int saved = 0;
for (String root : MODEL_SAVE_ROOTS) {
java.nio.file.Path file = java.nio.file.Paths.get(root).resolve(rel);
if (!java.nio.file.Files.exists(file)) continue;
try {
com.jme3.export.binary.BinaryExporter.getInstance().save(model, file.toFile());
log.info("[WorldScene] Gespeichert ({}): {}", root, file.toAbsolutePath());
saved++;
} catch (Exception e) {
log.warn("[WorldScene] Speichern fehlgeschlagen ({}): {}", file, e.getMessage());
}
}
try {
com.jme3.export.binary.BinaryExporter.getInstance().save(model, file.toFile());
log.info("[WorldScene] {} Clips entfernt, Datei gespeichert: {}", count, file.toAbsolutePath());
} catch (Exception e) {
log.warn("[WorldScene] Speichern fehlgeschlagen ({}): {}", file, e.getMessage());
if (saved == 0) {
log.warn("[WorldScene] Modell nicht gespeichert kein Pfad gefunden für '{}' (CWD={})",
modelPath, java.nio.file.Paths.get(".").toAbsolutePath());
}
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
}