fix: use TYPE_ROTATION_3D=2 with Quaternion values and clean up dead code

This commit is contained in:
2026-06-25 15:03:43 -04:00
parent 7e40085b80
commit f776fe59e7
+52 -228
View File
@@ -168,31 +168,31 @@ func _create_walk_anim() -> Animation:
# Add rotation tracks for each bone using type=1 (ROTATION)
_add_rot_track(anim, "LeftUpperLeg", [
{"t": 0.0, "euler": Vector3(-0.6, 0, 0)},
{"t": cycle * 0.5, "euler": Vector3(0.6, 0, 0)},
{"t": cycle, "euler": Vector3(-0.6, 0, 0)}
{"t": 0.0, "rot": Vector3(-0.6, 0, 0)},
{"t": cycle * 0.5, "rot": Vector3(0.6, 0, 0)},
{"t": cycle, "rot": Vector3(-0.6, 0, 0)}
])
_add_rot_track(anim, "RightUpperLeg", [
{"t": 0.0, "euler": Vector3(0.6, 0, 0)},
{"t": cycle * 0.5, "euler": Vector3(-0.6, 0, 0)},
{"t": cycle, "euler": Vector3(0.6, 0, 0)}
{"t": 0.0, "rot": Vector3(0.6, 0, 0)},
{"t": cycle * 0.5, "rot": Vector3(-0.6, 0, 0)},
{"t": cycle, "rot": Vector3(0.6, 0, 0)}
])
_add_rot_track(anim, "LeftUpperArm", [
{"t": 0.0, "euler": Vector3(-0.4, 0, 0)},
{"t": cycle * 0.5, "euler": Vector3(0.4, 0, 0)},
{"t": cycle, "euler": Vector3(-0.4, 0, 0)}
{"t": 0.0, "rot": Vector3(-0.4, 0, 0)},
{"t": cycle * 0.5, "rot": Vector3(0.4, 0, 0)},
{"t": cycle, "rot": Vector3(-0.4, 0, 0)}
])
_add_rot_track(anim, "RightUpperArm", [
{"t": 0.0, "euler": Vector3(0.4, 0, 0)},
{"t": cycle * 0.5, "euler": Vector3(-0.4, 0, 0)},
{"t": cycle, "euler": Vector3(0.4, 0, 0)}
{"t": 0.0, "rot": Vector3(0.4, 0, 0)},
{"t": cycle * 0.5, "rot": Vector3(-0.4, 0, 0)},
{"t": cycle, "rot": Vector3(0.4, 0, 0)}
])
_add_rot_track(anim, "Spine", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": cycle * 0.25, "euler": Vector3(0, 0.1, 0)},
{"t": cycle * 0.5, "euler": Vector3(0, 0, 0)},
{"t": cycle * 0.75, "euler": Vector3(0, -0.1, 0)},
{"t": cycle, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": cycle * 0.25, "rot": Vector3(0, 0.1, 0)},
{"t": cycle * 0.5, "rot": Vector3(0, 0, 0)},
{"t": cycle * 0.75, "rot": Vector3(0, -0.1, 0)},
{"t": cycle, "rot": Vector3(0, 0, 0)}
])
return anim
@@ -207,24 +207,24 @@ func _create_jump_anim() -> Animation:
anim.loop_mode = Animation.LOOP_LINEAR
_add_rot_track(anim, "LeftUpperArm", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 0.5, "euler": Vector3(-1.2, 0, 0)},
{"t": 1.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 0.5, "rot": Vector3(-1.2, 0, 0)},
{"t": 1.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "RightUpperArm", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 0.5, "euler": Vector3(-1.2, 0, 0)},
{"t": 1.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 0.5, "rot": Vector3(-1.2, 0, 0)},
{"t": 1.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "LeftUpperLeg", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 0.5, "euler": Vector3(-0.3, 0, 0)},
{"t": 1.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 0.5, "rot": Vector3(-0.3, 0, 0)},
{"t": 1.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "RightUpperLeg", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 0.5, "euler": Vector3(-0.3, 0, 0)},
{"t": 1.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 0.5, "rot": Vector3(-0.3, 0, 0)},
{"t": 1.0, "rot": Vector3(0, 0, 0)}
])
return anim
@@ -234,34 +234,35 @@ func _create_idle_anim() -> Animation:
anim.loop_mode = Animation.LOOP_LINEAR
_add_rot_track(anim, "Spine", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 1.0, "euler": Vector3(0.02, 0, 0)},
{"t": 2.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 1.0, "rot": Vector3(0.02, 0, 0)},
{"t": 2.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "Head", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 1.0, "euler": Vector3(0.03, 0.02, 0)},
{"t": 2.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 1.0, "rot": Vector3(0.03, 0.02, 0)},
{"t": 2.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "LeftUpperArm", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 1.0, "euler": Vector3(0.02, 0, 0.05)},
{"t": 2.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 1.0, "rot": Vector3(0.02, 0, 0.05)},
{"t": 2.0, "rot": Vector3(0, 0, 0)}
])
_add_rot_track(anim, "RightUpperArm", [
{"t": 0.0, "euler": Vector3(0, 0, 0)},
{"t": 1.0, "euler": Vector3(0.02, 0, -0.05)},
{"t": 2.0, "euler": Vector3(0, 0, 0)}
{"t": 0.0, "rot": Vector3(0, 0, 0)},
{"t": 1.0, "rot": Vector3(0.02, 0, -0.05)},
{"t": 2.0, "rot": Vector3(0, 0, 0)}
])
return anim
func _add_rot_track(anim: Animation, bone_name: String, keyframes: Array) -> void:
var track = anim.add_track(0) # TYPE_VALUE = 0 (required in Godot 4.2.1)
var track = anim.add_track(Animation.TYPE_ROTATION_3D) # TYPE_ROTATION_3D = 2
anim.track_set_path(track, "MikuRig/Skeleton3D:" + bone_name)
anim.track_set_interpolation_type(track, Animation.INTERPOLATION_LINEAR)
for kf in keyframes:
# Use Vector3 euler angles - Godot 4.2.1 accepts both Vector3 and Quaternion
anim.track_insert_key(track, kf["t"], kf["euler"])
# Rotation tracks require Quaternion values
var quat = Quaternion.from_euler(kf["rot"])
anim.track_insert_key(track, kf["t"], quat)
func _is_animation_broken(ap: AnimationPlayer, anim_name: String) -> bool:
## Returns true if the animation has near-zero motion (Blender GLTF track stripping).
@@ -277,21 +278,15 @@ func _is_animation_broken(ap: AnimationPlayer, anim_name: String) -> bool:
var key_count = anim.track_get_key_count(t)
if key_count < 2:
continue
var path = String(anim.track_get_path(t))
var is_rotation_track = path.ends_with(":rotation")
for i in range(1, key_count):
var v = anim.track_get_key_value(t, i)
if v is Vector3:
var prev_v = anim.track_get_key_value(t, i - 1)
var delta: float
if is_rotation_track:
# For rotation tracks, sum absolute euler angle differences
delta = abs(v.x - prev_v.x) + abs(v.y - prev_v.y) + abs(v.z - prev_v.z)
else:
delta = v.distance_to(prev_v)
max_delta = max(max_delta, delta)
# Threshold: rotation < 0.02 rad total (~1.1 deg) or position < 0.005 units
return max_delta < 0.02
var prev = anim.track_get_key_value(t, i - 1)
if v is Vector3 and prev is Vector3:
max_delta = max(max_delta, v.distance_to(prev))
elif v is Quaternion and prev is Quaternion:
max_delta = max(max_delta, v.angle_to(prev))
# Threshold: < 0.005 units of motion
return max_delta < 0.005
func _get_bone_name(bone_name: String) -> String:
## Returns the bone name as stored in the skeleton (uses cache).
@@ -299,177 +294,6 @@ func _get_bone_name(bone_name: String) -> String:
return bone_name
return bone_name
# ═══════════════════════════════════════════════════════════════════════════════
# CODE-DRIVEN ANIMATION SYSTEM
# Godot 4.2.1 AnimationPlayer cannot rotate bones via ":rotation" track paths
# on runtime-loaded GLB skeletons. We apply rotations directly each frame.
# ═══════════════════════════════════════════════════════════════════════════════
func _create_locomotion_data(loop: bool, speed_mult: float = 1.0) -> Dictionary:
## Creates a walk/run animation data structure.
## Returns: {length, loop, tracks: [{bone, keyframes: [{t, rot: Vector3}]}]}
var cycle: float = 1.0 / locomotion_cycle_speed * speed_mult
var t1: float = 0.0
var t2: float = cycle * 0.25
var t3: float = cycle * 0.5
var t4: float = cycle * 0.75
var t5: float = cycle
var tracks: Array = []
# Legs: opposite phase walk cycle
_add_catmull_rot_track(tracks, "LeftUpperLeg", [t1, t2, t3, t4, t5],
[-0.6, -0.1, 0.6, -0.1, -0.6], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "LeftLowerLeg", [t1, t2, t3, t4, t5],
[0.1, 0.5, 0.8, 0.3, 0.1], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "RightUpperLeg", [t1, t2, t3, t4, t5],
[0.6, -0.1, -0.6, -0.1, 0.6], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "RightLowerLeg", [t1, t2, t3, t4, t5],
[0.3, 0.1, 0.1, 0.5, 0.3], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
# Arms: opposite to legs
_add_catmull_rot_track(tracks, "LeftUpperArm", [t1, t2, t3, t4, t5],
[0.5, 0.0, -0.5, 0.0, 0.5], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "LeftLowerArm", [t1, t2, t3, t4, t5],
[-0.8, -0.3, -0.2, -0.5, -0.8], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "RightUpperArm", [t1, t2, t3, t4, t5],
[-0.5, 0.0, 0.5, 0.0, -0.5], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
_add_catmull_rot_track(tracks, "RightLowerArm", [t1, t2, t3, t4, t5],
[-0.2, -0.5, -0.8, -0.3, -0.2], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
# Spine twist
_add_catmull_rot_track(tracks, "Spine", [t1, t2, t3, t4, t5],
[0.0, 0.05, 0.0, -0.05, 0.0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0])
# Hips bounce (position offset)
_add_catmull_pos_track(tracks, "Hips", [t1, t2, t3, t4, t5],
[0, 0, 0, 0, 0], [0, 0.02, 0, 0.02, 0], [0, 0, 0, 0, 0])
return {"length": cycle, "loop": loop, "tracks": tracks}
func _create_jump_data() -> Dictionary:
## Creates jump animation data.
var tracks: Array = []
var t1: float = 0.0
var t2: float = 0.5
var t3: float = 1.0
_add_keyframe_rot_track(tracks, "LeftUpperArm", [
{"t": t1, "rot": Vector3(-0.8, 0, 0.3)},
{"t": t2, "rot": Vector3(-1.2, 0, 0.5)},
{"t": t3, "rot": Vector3(-0.8, 0, 0.3)}
])
_add_keyframe_rot_track(tracks, "RightUpperArm", [
{"t": t1, "rot": Vector3(-0.8, 0, -0.3)},
{"t": t2, "rot": Vector3(-1.2, 0, -0.5)},
{"t": t3, "rot": Vector3(-0.8, 0, -0.3)}
])
_add_keyframe_rot_track(tracks, "LeftLowerArm", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(-0.4, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "RightLowerArm", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(-0.4, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "LeftUpperLeg", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(-0.3, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "RightUpperLeg", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(-0.3, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "Spine", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(0.1, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
return {"length": 1.0, "loop": true, "tracks": tracks}
func _create_idle_data() -> Dictionary:
## Creates idle animation data.
var tracks: Array = []
var t1: float = 0.0
var t2: float = 1.0
var t3: float = 2.0
_add_keyframe_rot_track(tracks, "Spine", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(0.02, 0, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "Head", [
{"t": t1, "rot": Vector3(0, 0, 0)},
{"t": t2, "rot": Vector3(0.03, 0.02, 0)},
{"t": t3, "rot": Vector3(0, 0, 0)}
])
_add_keyframe_rot_track(tracks, "LeftUpperArm", [
{"t": t1, "rot": Vector3(0, 0, 0.05)},
{"t": t2, "rot": Vector3(0.02, 0, 0.08)},
{"t": t3, "rot": Vector3(0, 0, 0.05)}
])
_add_keyframe_rot_track(tracks, "RightUpperArm", [
{"t": t1, "rot": Vector3(0, 0, -0.05)},
{"t": t2, "rot": Vector3(0.02, 0, -0.08)},
{"t": t3, "rot": Vector3(0, 0, -0.05)}
])
_add_keyframe_pos_track(tracks, "Hips", [
{"t": t1, "pos": Vector3(0, 0, 0)},
{"t": t2, "pos": Vector3(0, 0.005, 0)},
{"t": t3, "pos": Vector3(0, 0, 0)}
])
return {"length": 2.0, "loop": true, "tracks": tracks}
func _add_catmull_rot_track(tracks: Array, bone_name: String, times: Array, x: Array, y: Array, z: Array) -> void:
var kf: Array = []
for i in range(times.size()):
kf.append({"t": times[i], "rot": Vector3(x[i], y[i], z[i])})
tracks.append({"bone": bone_name, "keyframes": kf})
func _add_catmull_pos_track(tracks: Array, bone_name: String, times: Array, x: Array, y: Array, z: Array) -> void:
var kf: Array = []
for i in range(times.size()):
kf.append({"t": times[i], "pos": Vector3(x[i], y[i], z[i])})
tracks.append({"bone": bone_name, "keyframes": kf})
func _add_keyframe_rot_track(tracks: Array, bone_name: String, keyframes: Array) -> void:
var kf: Array = []
for k in keyframes:
kf.append({"t": k["t"], "rot": k["rot"]})
tracks.append({"bone": bone_name, "keyframes": kf})
func _add_keyframe_pos_track(tracks: Array, bone_name: String, keyframes: Array) -> void:
var kf: Array = []
for k in keyframes:
kf.append({"t": k["t"], "pos": k["pos"]})
tracks.append({"bone": bone_name, "keyframes": kf})
func _add_animation_to_player(ap: AnimationPlayer, anim_name: String, anim: Animation) -> void:
## Adds an animation to the AnimationPlayer via its default library.
var lib_name: String = ""
if not ap.has_animation_library(lib_name):
ap.add_animation_library(lib_name, AnimationLibrary.new())
var lib = ap.get_animation_library(lib_name)
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 recomputing all bone transforms.
## GLTF runtime loaded models need this because AnimationPlayer's internal
## bone pose updates don't always trigger the visual pipeline's dirty flags.
## force_update_all_bone_transforms() recomputes the full bone hierarchy
## and emits bone_pose_changed, which causes MeshInstance to update vertex buffers.
skeleton.force_update_all_bone_transforms()
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