fix: T-posing skinned models — autorig produced an orphan skin with no weights
The model animated its skeleton but rendered a permanent T-pose: the mesh wasn't skin-bound. Root cause was in autorig's binding, introduced while chasing an earlier "skins:0" export: - The earlier "skins:0" was actually caused by bone-heat weighting failing (0 weighted verts → empty skin gets dropped), NOT by the Armature modifier. - The "fix" then stripped the Armature modifier and used parent_type=ARMATURE. That makes the glTF exporter emit a skin OBJECT but with no node.skin reference and no per-vertex JOINTS/WEIGHTS — an orphan skin. The mesh then renders its bind pose (T-pose) forever while the skeleton animates unseen. Fix: keep the standard ARMATURE_AUTO result (Armature modifier + vertex groups). With the nearest-bone fallback ensuring real weights, the exporter now writes a COMPLETE skin (verified: meshnode.skin=0, JOINTS/WEIGHTS present). Also: - skinned_player_model: _ensure_meshes_bound() re-binds any skinned mesh whose skeleton NodePath doesn't resolve at load — graceful degradation instead of a silent T-pose for imperfect GLBs. - smoke test now asserts the spawned player's mesh is bound to its skeleton, so this class of bug fails the test instead of shipping. 30/30 pass. - regenerated miku_test.glb with the corrected pipeline. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e2fbc424a7
commit
22cb0a58b7
@@ -79,6 +79,14 @@ func _test_spawn_with_skin(skin_id: String, expect_skinned: bool) -> void:
|
||||
if _check(skinned != null, "SkinnedModel created for GLB skin"):
|
||||
_check(skinned.loaded, "GLB model loaded")
|
||||
_check(skinned.skeleton != null, "skeleton found in GLB")
|
||||
# The mesh must be bound to the skeleton, else it renders its bind
|
||||
# pose (T-pose) while the skeleton animates invisibly.
|
||||
var bound := false
|
||||
if skinned.skeleton:
|
||||
for mi in skinned.find_children("*", "MeshInstance3D", true, false):
|
||||
if mi.skin != null and mi.get_node_or_null(mi.skeleton) == skinned.skeleton:
|
||||
bound = true
|
||||
_check(bound, "skinned mesh is bound to the skeleton (won't T-pose)")
|
||||
if skinned.animation_player:
|
||||
_check(skinned.animation_player.is_playing(), "animation playing")
|
||||
print(" clips resolved: ", skinned._resolved_clips)
|
||||
|
||||
Reference in New Issue
Block a user