@tool class_name TrackingBone3D extends SkeletonModifier3D enum Axis { X_plus, Y_plus, Z_plus, X_minus, Y_minus, Z_minus } @export_enum(" ") var bone: String @export var aim_axis := Axis.Z_plus @export var target: Vector3 = Vector3(0, 0, 0) var _skeleton: Skeleton3D var _bone_idx: int 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 bone poses _skeleton = get_skeleton() if not _skeleton: push_error("Expected a skeleton!") _bone_idx = _skeleton.find_bone(bone) func _process_modification() -> void: var pose: Transform3D = _skeleton.global_transform * _skeleton.get_bone_global_pose(_bone_idx) var looked_at: Transform3D = _w_look_at(pose) _skeleton.set_bone_global_pose( _bone_idx, Transform3D( (_skeleton.global_transform.affine_inverse() * looked_at).basis.orthonormalized(), _skeleton.get_bone_global_pose(_bone_idx).origin ) ) func _w_look_at(from: Transform3D) -> Transform3D: var w: Vector3 if aim_axis == Axis.Y_plus: w = from.basis.x elif aim_axis == Axis.X_plus: w = from.basis.z elif aim_axis == Axis.Z_plus: w = from.basis.y elif aim_axis == Axis.Y_minus: w = w.inverse() elif aim_axis == Axis.X_minus: w = from.basis.y.inverse() elif aim_axis == Axis.Z_minus: w = from.basis.y.inverse() var t_v: Vector3 = target - from.origin var v_y: Vector3 = t_v.normalized() var v_z: Vector3 = w.cross(v_y) v_z = v_z.normalized() var v_x: Vector3 = v_y.cross(v_z) from.basis = Basis(v_x, v_y, v_z) return from