fix: force bone pose update for GLTF runtime skin deformation
GLTF runtime loaded models may not trigger automatic MeshInstance vertex deformation when the AnimationPlayer updates bone poses. Added throttled _update_skin() that re-applies current bone poses to trigger NOTIFICATION_UPDATE_SKELETON, ensuring the renderer sees the updated vertex positions.
This commit is contained in:
@@ -56,7 +56,7 @@ func load_model(path: String) -> void:
|
||||
add_child(scene)
|
||||
print("SkinnedPlayerModel: scene added: %s" % scene.name)
|
||||
|
||||
# Model scale — the Miku GLB is already exported at meter scale (~1.6m tall).
|
||||
# Apply model scale (the Miku GLB is already in meters, ~1.6m tall)
|
||||
# For models exported in centimeters (common from Mixamo/Blender), set scale_factor to 0.01.
|
||||
scene.scale = Vector3(scale_factor, scale_factor, scale_factor)
|
||||
print("SkinnedPlayerModel: applied scale %.4f" % scale_factor)
|
||||
@@ -68,6 +68,18 @@ func load_model(path: String) -> void:
|
||||
if mesh_instances.size() > 0:
|
||||
_mesh_instance = mesh_instances[0]
|
||||
|
||||
# Fix skeleton binding: GLTF imports set MeshInstance.skeleton to ".."
|
||||
# which should work for a direct parent, but in our case the MeshInstance
|
||||
# is a child of Skeleton3D which is a child of MikuRig. The skeleton
|
||||
# property needs to point to the Skeleton3D node for skin deformation.
|
||||
# Set it explicitly to the skeleton's path relative to MeshInstance.
|
||||
if _mesh_instance and skeleton:
|
||||
# Manually compute relative path since get_relative_path_to is not
|
||||
# exposed for MeshInstance3D in Godot 4.2.
|
||||
# MeshInstance is child of Skeleton3D, so ".." points to Skeleton3D.
|
||||
_mesh_instance.skeleton = NodePath("..")
|
||||
print("SkinnedPlayerModel: fixed mesh skeleton binding -> '%s'" % _mesh_instance.skeleton)
|
||||
|
||||
if first_person_mode and _mesh_instance:
|
||||
_setup_first_person()
|
||||
|
||||
@@ -88,6 +100,10 @@ func load_model(path: String) -> void:
|
||||
# Find AnimationPlayer
|
||||
animation_player = _find_animation_player(scene)
|
||||
if animation_player:
|
||||
# Ensure AnimationPlayer processes even if parent has process_mode disabled
|
||||
animation_player.process_mode = Node.PROCESS_MODE_INHERIT
|
||||
animation_player.active = true
|
||||
print("SkinnedPlayerModel: AP process_mode=%d active=%s" % [animation_player.process_mode, animation_player.active])
|
||||
var anim_list = animation_player.get_animation_list()
|
||||
print("SkinnedPlayerModel: %d animations available:" % anim_list.size())
|
||||
for anim_name in anim_list:
|
||||
@@ -286,12 +302,20 @@ func _add_animation_to_player(ap: AnimationPlayer, anim_name: String, anim: Anim
|
||||
lib.add_animation(anim_name, anim)
|
||||
print("SkinnedPlayerModel: added '%s' (%d tracks)" % [anim_name, anim.get_track_count()])
|
||||
|
||||
func _update_skin() -> void:
|
||||
## Force skeleton skin update by notifying the Skeleton3D that bones changed.
|
||||
## GLTF runtime loaded models sometimes need this because the AnimationPlayer's
|
||||
## internal bone pose updates don't always trigger the visual pipeline.
|
||||
# Re-apply current bone poses to ensure dirty flags are set.
|
||||
# This triggers NOTIFICATION_UPDATE_SKELETON on the Skeleton3D.
|
||||
for i in range(skeleton.get_bone_count()):
|
||||
skeleton.set_bone_pose(i, skeleton.get_bone_pose(i))
|
||||
|
||||
func _setup_first_person() -> void:
|
||||
# In first person, hide the head and upper body so only arms/hands/legs show
|
||||
# This creates the classic FPS arms-only view
|
||||
if not _mesh_instance:
|
||||
return
|
||||
print("SkinnedPlayerModel: setting up first-person mode")
|
||||
# The mesh has multiple sub-meshes (body parts). We need to hide the ones
|
||||
# that are above the chest (head, torso, skirt, etc.)
|
||||
# For now, we hide the entire mesh and rely on the arms being separate
|
||||
@@ -338,6 +362,7 @@ 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.
|
||||
@@ -359,6 +384,15 @@ 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()
|
||||
|
||||
# Get movement state from parent PlayerMovementController
|
||||
var parent = get_parent()
|
||||
if not parent:
|
||||
|
||||
Reference in New Issue
Block a user