Zwischenstand: CullHint-Workaround gegen liegenden Charakter + stripEmbeddedClips-Fix

- WorldScene: Charakter wird beim Laden mit CullHint.Always versteckt, erst nach
  setupAnimationContext (idle läuft) wieder sichtbar
- WorldScene: stripEmbeddedClips nutzt jetzt AnimationLibrary.findAssetRoot() statt
  hartkodierter Pfad-Liste; besseres Logging wenn Datei nicht gefunden
- AnimPreviewState: Modell beim Laden versteckt (CullHint.Always), erst bei playClip
  sichtbar; stopAll versteckt Modell wieder
- PlayerInputControl: tryPlay setzt Action VOR SkinningControl-Aktivierung

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 22:03:48 +02:00
parent 7a3b2b8733
commit d3c6e8ed77
12 changed files with 45 additions and 25 deletions

View File

@@ -579,16 +579,22 @@ public class PlayerInputControl {
}
private boolean tryPlay(String clip) {
if (animComposer == null || !animLib.ensureApplied(clip, visual)) {
log.info("[Anim] tryPlay('{}') → ensureApplied FAILED", clip);
if (animComposer == null || !animLib.applyTo(clip, visual)) {
log.info("[Anim] tryPlay('{}') → applyTo FAILED", clip);
return false;
}
// Erst Action setzen, DANN SkinningControl aktivieren
// vermeidet 1 Frame in Bind-Pose × Armature-Rx90° = liegender Charakter.
com.jme3.anim.tween.action.Action action = animComposer.setCurrentAction(clip);
log.info("[Anim] setCurrentAction('{}') → {}", clip, action != null ? "OK" : "FAILED");
if (action != null) {
runningClip = clip;
return true;
if (action == null) {
return false;
}
return false;
if (skinningControl != null && !skinningControl.isEnabled()) {
skinningControl.setEnabled(true);
log.info("[Anim] SkinningControl aktiviert nach Action '{}'", clip);
}
runningClip = clip;
return true;
}
}

View File

@@ -307,6 +307,10 @@ 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);
}
}
// CharacterControl setzt den Spatial auf den Kapsel-Mittelpunkt: radius=0.4, halfCyl=0.5 → 0.9m über dem Boden.
@@ -343,6 +347,9 @@ 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.
rotNode.setCullHint(Spatial.CullHint.Always);
Node wrapper = new Node("character");
wrapper.attachChild(rotNode);
@@ -359,30 +366,32 @@ 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;
if (ac == null || ac.getAnimClips().isEmpty()) {
log.info("[WorldScene] Keine eingebetteten Clips in '{}' (AnimComposer={})",
modelPath, ac != null ? "leer" : "nicht gefunden");
return;
}
int count = ac.getAnimClips().size();
log.info("[WorldScene] Entferne {} eingebettete Clips aus '{}'", count, modelPath);
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());
}
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;
}
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());
}
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
}