3 class_name TrackingBone3D_head
4 extends SkeletonModifier3D
6 @export_enum(" ") var bone: String
7 @export var target: Node3D = null
8 @export var influence_lerp_seconds: float = 0.5
9 @export var rotation_lerp_weight: float = 0.5
11 var _skeleton: Skeleton3D
14 var old_current_pose: Transform3D = Transform3D.IDENTITY
16 func _validate_property(property: Dictionary) -> void:
17 if property.name == "bone":
18 var skeleton: Skeleton3D = get_skeleton()
20 property.hint = PROPERTY_HINT_ENUM
21 property.hint_string = skeleton.get_concatenated_bone_names()
24 func _ready() -> void:
25 # for tweening influence
28 # for tweening bone poses
29 _skeleton = get_skeleton()
31 push_error("Expected a skeleton!")
32 _bone_idx = _skeleton.find_bone(bone)
34 # for smooth rotations
35 old_current_pose = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx)
38 func tween_influence(goal_weight: float) -> void:
39 create_tween().tween_method(
40 _interpolate_influence, influence, goal_weight, influence_lerp_seconds
41 ).set_trans(Tween.TRANS_EXPO)
44 func _interpolate_influence(weight: float) -> void:
49 func _process_modification() -> void:
53 var current_pose: Transform3D = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx)
54 current_pose.basis = old_current_pose.basis
55 var target_pose: Transform3D = current_pose.looking_at(target.global_position, Vector3.UP)
57 # TODO: replace this specific code with general purpose modifier
58 var axis: Vector3 = target_pose.basis.y
59 target_pose = target_pose.rotated(axis, PI)
61 # https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions
62 if rotation_lerp_weight > 0:
63 var from := Quaternion(current_pose.basis.orthonormalized())
64 var to := Quaternion(target_pose.basis.orthonormalized())
65 var rot_target := from.slerp(to, rotation_lerp_weight)
66 target_pose.basis = Basis(rot_target)
69 old_current_pose = target_pose
71 # yield until target pose has changed, then apply it?
72 _skeleton.set_bone_global_pose(
75 (_skeleton.global_transform.affine_inverse() * target_pose).basis.orthonormalized(),
76 _skeleton.get_bone_global_pose(_bone_idx).origin