]> Untitled Git - william-skin.git/blob - TrackingBone3D_head.gd
Track Node3D objects instead of Vector3 so targets aren't static
[william-skin.git] / TrackingBone3D_head.gd
1 @tool
2
3 class_name TrackingBone3D_head
4 extends SkeletonModifier3D
5
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
10
11 var _skeleton: Skeleton3D
12 var _bone_idx: int
13
14 var old_current_pose: Transform3D = Transform3D.IDENTITY
15
16 func _validate_property(property: Dictionary) -> void:
17         if property.name == "bone":
18                 var skeleton: Skeleton3D = get_skeleton()
19                 if skeleton:
20                         property.hint = PROPERTY_HINT_ENUM
21                         property.hint_string = skeleton.get_concatenated_bone_names()
22
23
24 func _ready() -> void:
25         # for tweening influence
26         influence = 0
27         
28         # for tweening bone poses
29         _skeleton = get_skeleton()
30         if not _skeleton:
31                 push_error("Expected a skeleton!")
32         _bone_idx = _skeleton.find_bone(bone)
33         
34         # for smooth rotations
35         old_current_pose = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx)
36
37
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)
42
43
44 func _interpolate_influence(weight: float) -> void:
45         influence = weight
46
47
48
49 func _process_modification() -> void:
50         if target == null:
51                 return
52
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)
56         
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)
60         
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)
67         
68         # remember this!
69         old_current_pose = target_pose
70         
71         # yield until target pose has changed, then apply it?
72         _skeleton.set_bone_global_pose(
73                 _bone_idx,
74                 Transform3D(
75                         (_skeleton.global_transform.affine_inverse() * target_pose).basis.orthonormalized(), 
76                         _skeleton.get_bone_global_pose(_bone_idx).origin
77                         )
78                 )