@tool class_name TrackingBone3D_head extends SkeletonModifier3D @export_enum(" ") var bone: String @export var target: Node3D = null @export var influence_lerp_seconds: float = 0.5 @export var rotation_lerp_weight: float = 0.5 var _skeleton: Skeleton3D var _bone_idx: int var old_current_pose: Transform3D = Transform3D.IDENTITY func _validate_property(property: Dictionary) -> void: if property.name == "bone": var skeleton: Skeleton3D = get_skeleton() if skeleton: property.hint = PROPERTY_HINT_ENUM property.hint_string = skeleton.get_concatenated_bone_names() func _ready() -> void: # for tweening influence influence = 0 # for tweening bone poses _skeleton = get_skeleton() if not _skeleton: push_error("Expected a skeleton!") _bone_idx = _skeleton.find_bone(bone) # for smooth rotations old_current_pose = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx) func tween_influence(goal_weight: float) -> void: create_tween().tween_method( _interpolate_influence, influence, goal_weight, influence_lerp_seconds ).set_trans(Tween.TRANS_EXPO) func _interpolate_influence(weight: float) -> void: influence = weight func _process_modification() -> void: if target == null: return var current_pose: Transform3D = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx) current_pose.basis = old_current_pose.basis var target_pose: Transform3D = current_pose.looking_at(target.global_position, Vector3.UP) # TODO: replace this specific code with general purpose modifier var axis: Vector3 = target_pose.basis.y target_pose = target_pose.rotated(axis, PI) # https://docs.godotengine.org/en/latest/tutorials/3d/using_transforms.html#interpolating-with-quaternions if rotation_lerp_weight > 0: var from := Quaternion(current_pose.basis.orthonormalized()) var to := Quaternion(target_pose.basis.orthonormalized()) var rot_target := from.slerp(to, rotation_lerp_weight) target_pose.basis = Basis(rot_target) # remember this! old_current_pose = target_pose # yield until target pose has changed, then apply it? _skeleton.set_bone_global_pose( _bone_idx, Transform3D( (_skeleton.global_transform.affine_inverse() * target_pose).basis.orthonormalized(), _skeleton.get_bone_global_pose(_bone_idx).origin ) )