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 {
|
try {
|
||||||
Spatial model = loadFresh(assetPath);
|
Spatial model = loadFresh(assetPath);
|
||||||
// SkinningControl nur aktiv lassen wenn eine Animation läuft,
|
// SkinningControl deaktiviert + Modell unsichtbar bis Animation läuft:
|
||||||
// sonst kollabiert das Mesh durch uninitalisierte Skin-Matrizen.
|
// 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);
|
setSkinningEnabled(model, false);
|
||||||
|
model.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
|
|
||||||
// Im Animations-Editor soll der Charakter immer am Ursprung stehen.
|
// Im Animations-Editor soll der Charakter immer am Ursprung stehen.
|
||||||
// Eventuelle Translation die beim GLB-Export eingebacken wurde entfernen.
|
// 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
|
// SkinningControls auf dem gesamten Modell aktivieren – AnimComposer und
|
||||||
// SkinningControl sitzen oft auf verschiedenen Geschwisterknoten.
|
// SkinningControl sitzen oft auf verschiedenen Geschwisterknoten.
|
||||||
setSkinningEnabled(currentModel, true);
|
setSkinningEnabled(currentModel, true);
|
||||||
|
// Modell erst nach Animation sichtbar machen (CullHint aus loadModel)
|
||||||
|
currentModel.setCullHint(com.jme3.scene.Spatial.CullHint.Inherit);
|
||||||
playOnSpatial(currentModel, clipName);
|
playOnSpatial(currentModel, clipName);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,6 +384,7 @@ public class AnimPreviewState extends BaseAppState {
|
|||||||
if (currentModel != null) {
|
if (currentModel != null) {
|
||||||
stopOnSpatial(currentModel);
|
stopOnSpatial(currentModel);
|
||||||
setSkinningEnabled(currentModel, false);
|
setSkinningEnabled(currentModel, false);
|
||||||
|
currentModel.setCullHint(com.jme3.scene.Spatial.CullHint.Always);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -579,16 +579,22 @@ public class PlayerInputControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean tryPlay(String clip) {
|
private boolean tryPlay(String clip) {
|
||||||
if (animComposer == null || !animLib.ensureApplied(clip, visual)) {
|
if (animComposer == null || !animLib.applyTo(clip, visual)) {
|
||||||
log.info("[Anim] tryPlay('{}') → ensureApplied FAILED", clip);
|
log.info("[Anim] tryPlay('{}') → applyTo FAILED", clip);
|
||||||
return false;
|
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);
|
com.jme3.anim.tween.action.Action action = animComposer.setCurrentAction(clip);
|
||||||
log.info("[Anim] setCurrentAction('{}') → {}", clip, action != null ? "OK" : "FAILED");
|
log.info("[Anim] setCurrentAction('{}') → {}", clip, action != null ? "OK" : "FAILED");
|
||||||
if (action != null) {
|
if (action == null) {
|
||||||
runningClip = clip;
|
return false;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
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());
|
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.
|
// 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");
|
Node rotNode = new Node("charRot");
|
||||||
loaded.setLocalTranslation(0, offsetY, 0);
|
loaded.setLocalTranslation(0, offsetY, 0);
|
||||||
rotNode.attachChild(loaded);
|
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");
|
Node wrapper = new Node("character");
|
||||||
wrapper.attachChild(rotNode);
|
wrapper.attachChild(rotNode);
|
||||||
@@ -359,30 +366,32 @@ public class WorldScene extends BaseAppState {
|
|||||||
return buildCharacter();
|
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) {
|
private void stripEmbeddedClips(Spatial model, String modelPath) {
|
||||||
com.jme3.anim.AnimComposer ac = de.blight.game.animation.RetargetingSystem.findAnimComposer(model);
|
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();
|
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())) {
|
for (com.jme3.anim.AnimClip c : new java.util.ArrayList<>(ac.getAnimClips())) {
|
||||||
ac.removeAnimClip(c);
|
ac.removeAnimClip(c);
|
||||||
}
|
}
|
||||||
String rel = modelPath.replace('/', java.io.File.separatorChar);
|
java.nio.file.Path assetRoot = AnimationLibrary.findAssetRoot();
|
||||||
for (String base : ASSET_SEARCH_ROOTS) {
|
java.nio.file.Path file = assetRoot.resolve(modelPath.replace('/', java.io.File.separatorChar));
|
||||||
java.nio.file.Path file = java.nio.file.Paths.get(base).resolve(rel);
|
if (!java.nio.file.Files.exists(file)) {
|
||||||
if (!java.nio.file.Files.exists(file)) continue;
|
log.warn("[WorldScene] Modelldatei nicht gefunden zum Speichern: {} (assetRoot={})",
|
||||||
try {
|
file.toAbsolutePath(), assetRoot.toAbsolutePath());
|
||||||
com.jme3.export.binary.BinaryExporter.getInstance().save(model, file.toFile());
|
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
|
||||||
log.info("[WorldScene] {} eingebettete Clips aus '{}' entfernt: {}", count, modelPath, file);
|
return;
|
||||||
} 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());
|
||||||
}
|
}
|
||||||
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
|
assetManager.deleteFromCache(new com.jme3.asset.ModelKey(modelPath));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user