3 class_name TrackingBone3D_head
4 extends SkeletonModifier3D
6 @export_enum(" ") var bone: String
7 @export var target: Vector3 = Vector3(0, 0, 0)
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:
50 var current_pose: Transform3D = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx)
51 current_pose.basis = old_current_pose.basis
52 var target_pose: Transform3D = current_pose.looking_at(target, Vector3.UP)
54 # TODO: replace this specific code with general purpose modifier
55 var axis: Vector3 = target_pose.basis.y
56 target_pose = target_pose.rotated(axis, PI)
58 # https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions
59 if rotation_lerp_weight > 0:
60 var from := Quaternion(current_pose.basis.orthonormalized())
61 var to := Quaternion(target_pose.basis.orthonormalized())
62 var rot_target := from.slerp(to, rotation_lerp_weight)
63 target_pose.basis = Basis(rot_target)
66 old_current_pose = target_pose
68 # yield until target pose has changed, then apply it?
69 _skeleton.set_bone_global_pose(
72 (_skeleton.global_transform.affine_inverse() * target_pose).basis.orthonormalized(),
73 _skeleton.get_bone_global_pose(_bone_idx).origin