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:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -249,9 +249,11 @@ public class AnimPreviewState extends BaseAppState {
|
||||
|
||||
try {
|
||||
Spatial model = loadFresh(assetPath);
|
||||
// SkinningControl nur aktiv lassen wenn eine Animation läuft,
|
||||
// sonst kollabiert das Mesh durch uninitalisierte Skin-Matrizen.
|
||||
// SkinningControl deaktiviert + Modell unsichtbar bis Animation läuft:
|
||||
// das Armature-Node hat Rx(90°) aus dem GLB-Import → ohne Animation liegt
|
||||
// das Mesh auf dem Boden (Bind-Pose × Rx90° = liegender Charakter).
|
||||
setSkinningEnabled(model, false);
|
||||
model.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||
|
||||
// Im Animations-Editor soll der Charakter immer am Ursprung stehen.
|
||||
// Eventuelle Translation die beim GLB-Export eingebacken wurde entfernen.
|
||||
@@ -348,6 +350,8 @@ public class AnimPreviewState extends BaseAppState {
|
||||
// SkinningControls auf dem gesamten Modell aktivieren – AnimComposer und
|
||||
// SkinningControl sitzen oft auf verschiedenen Geschwisterknoten.
|
||||
setSkinningEnabled(currentModel, true);
|
||||
// Modell erst nach Animation sichtbar machen (CullHint aus loadModel)
|
||||
currentModel.setCullHint(com.jme3.scene.Spatial.CullHint.Inherit);
|
||||
playOnSpatial(currentModel, clipName);
|
||||
}
|
||||
|
||||
@@ -380,6 +384,7 @@ public class AnimPreviewState extends BaseAppState {
|
||||
if (currentModel != null) {
|
||||
stopOnSpatial(currentModel);
|
||||
setSkinningEnabled(currentModel, false);
|
||||
currentModel.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user