fix: resolve GLB character T-pose by fixing skeleton binding order and skin update timing
Two root causes fixed: 1. Skeleton binding fix was skipped because `skeleton` variable was null at the time of the check (assigned AFTER the mesh fix block). Moved skeleton discovery BEFORE the mesh binding fix so the MeshInstance.skeleton path is correctly set to "..". 2. Skin deformation wasn't triggering because `_update_skin()` (which calls `force_update_all_bone_transforms()`) was throttled to 30fps AND ran BEFORE the AnimationPlayer's `_process()` due to tree order. AnimationPlayer.advance() sets bone poses but does NOT emit `bone_pose_changed`, so the MeshInstance never knew to re-compute its vertex buffers. Fixed by using `call_deferred` to ensure `_update_skin()` runs AFTER all `_process()` calls (including AP), and removing the throttling so it runs every frame. Co-Authored-By: Claude 4.7 <[email protected]>
This commit is contained in:
@@ -66,6 +66,16 @@ func load_model(path: String) -> void:
|
||||
scene.position = Vector3(0, position_y_offset * scale_factor, 0)
|
||||
print("SkinnedPlayerModel: applied scale %.4f, y_offset %.4f" % [scale_factor, position_y_offset])
|
||||
|
||||
# Find skeleton FIRST (needed for mesh binding fix below)
|
||||
skeleton = _find_skeleton(scene)
|
||||
if skeleton:
|
||||
print("SkinnedPlayerModel: found skeleton '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()])
|
||||
# Cache bone indices for fast lookups in procedural animations
|
||||
for i in range(skeleton.get_bone_count()):
|
||||
_bone_cache[skeleton.get_bone_name(i)] = i
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - no skeleton found")
|
||||
|
||||
# Find mesh instance for first-person mode
|
||||
_mesh_instance = scene.find_child("Tda Miku for fbx_mesh", true, false)
|
||||
if not _mesh_instance:
|
||||
@@ -92,16 +102,6 @@ func load_model(path: String) -> void:
|
||||
print("SkinnedPlayerModel: scene tree:")
|
||||
_print_tree(scene, 0)
|
||||
|
||||
# Find skeleton
|
||||
skeleton = _find_skeleton(scene)
|
||||
if skeleton:
|
||||
print("SkinnedPlayerModel: found skeleton '%s' with %d bones" % [skeleton.name, skeleton.get_bone_count()])
|
||||
# Cache bone indices for fast lookups in procedural animations
|
||||
for i in range(skeleton.get_bone_count()):
|
||||
_bone_cache[skeleton.get_bone_name(i)] = i
|
||||
else:
|
||||
print("SkinnedPlayerModel: WARNING - no skeleton found")
|
||||
|
||||
# Find AnimationPlayer
|
||||
animation_player = _find_animation_player(scene)
|
||||
if animation_player:
|
||||
@@ -370,7 +370,6 @@ func play_animation(anim_name: String) -> void:
|
||||
|
||||
var _last_played: String = ""
|
||||
var _missing_anim_warned: Array = []
|
||||
var _skin_update_acc: float = 0.0
|
||||
|
||||
func _warn_missing(anim_name: String) -> void:
|
||||
## Warn once per missing animation, not every frame.
|
||||
@@ -392,14 +391,14 @@ func _process(delta: float) -> void:
|
||||
elif _last_played == "":
|
||||
print("SkinnedPlayerModel: NOT playing")
|
||||
|
||||
# Force skeleton skin update — GLTF runtime loaded models need explicit
|
||||
# bone pose re-application to trigger MeshInstance vertex deformation.
|
||||
# Throttled to ~30fps to avoid per-frame overhead.
|
||||
if skeleton and animation_player.is_playing():
|
||||
_skin_update_acc += delta
|
||||
if _skin_update_acc > 0.033:
|
||||
_skin_update_acc = 0.0
|
||||
_update_skin()
|
||||
# Force skeleton skin update EVERY frame, deferred to ensure it runs
|
||||
# AFTER the AnimationPlayer has advanced its poses this frame.
|
||||
# GLTF runtime loaded models need this to emit bone_pose_changed,
|
||||
# which triggers the MeshInstance to re-compute its skin vertex buffers.
|
||||
# Without deferral, this runs before AnimationPlayer._process() (due to
|
||||
# tree order), so the MeshInstance would render with stale poses.
|
||||
if skeleton:
|
||||
call_deferred("_update_skin")
|
||||
|
||||
# Get movement state from parent PlayerMovementController
|
||||
var parent = get_parent()
|
||||
|
||||
Reference in New Issue
Block a user