godot-demo-projects/3d/ik/addons/sade/ik_look_at.gd

214 lines
6.9 KiB
GDScript

tool
extends Spatial
export(NodePath) var skeleton_path setget _set_skeleton_path
export(String) var bone_name = ""
export(int, "_process", "_physics_process", "_notification", "none") var update_mode = 0 setget _set_update
export(int, "X-up", "Y-up", "Z-up") var look_at_axis = 1
export(bool) var use_our_rotation_x = false
export(bool) var use_our_rotation_y = false
export(bool) var use_our_rotation_z = false
export(bool) var use_negative_our_rot = false
export(Vector3) var additional_rotation = Vector3()
export(bool) var position_using_additional_bone = false
export(String) var additional_bone_name = ""
export(float) var additional_bone_length = 1
export(bool) var debug_messages = false
var skeleton_to_use: Skeleton = null
var first_call: bool = true
var _editor_indicator: Spatial = null
func _ready():
set_process(false)
set_physics_process(false)
set_notify_transform(false)
if update_mode == 0:
set_process(true)
elif update_mode == 1:
set_physics_process(true)
elif update_mode == 2:
set_notify_transform(true)
else:
if debug_messages == true:
print (name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")
if Engine.editor_hint == true:
_setup_for_editor()
func _process(_delta):
update_skeleton()
func _physics_process(_delta):
update_skeleton()
func _notification(what):
if what == NOTIFICATION_TRANSFORM_CHANGED:
update_skeleton()
func update_skeleton():
# NOTE: Because get_node doesn't work in _ready, we need to skip
# a call before doing anything.
if first_call == true:
first_call = false
if skeleton_to_use == null:
_set_skeleton_path(skeleton_path)
# If we do not have a skeleton and/or we're not supposed to update, then return.
if skeleton_to_use == null:
return
if update_mode >= 3:
return
# Get the bone index.
var bone: int = skeleton_to_use.find_bone(bone_name)
# If no bone is found (-1), then return and optionally print an error.
if bone == -1:
if debug_messages == true:
print (name, " - IK_LookAt: No bone in skeleton found with name [", bone_name, "]!")
return
# get the bone's global transform pose.
var rest = skeleton_to_use.get_bone_global_pose(bone)
# Convert our position relative to the skeleton's transform.
var target_pos = skeleton_to_use.global_transform.xform_inv(global_transform.origin)
# Call helper's look_at function with the chosen up axis.
if look_at_axis == 0:
rest = rest.looking_at(target_pos, Vector3.RIGHT)
elif look_at_axis == 1:
rest = rest.looking_at(target_pos, Vector3.UP)
elif look_at_axis == 2:
rest = rest.looking_at(target_pos, Vector3.FORWARD)
else:
rest = rest.looking_at(target_pos, Vector3.UP)
if debug_messages == true:
print (name, " - IK_LookAt: Unknown look_at_axis value!")
# Get the rotation euler of the bone and of this node.
var rest_euler = rest.basis.get_euler()
var self_euler = global_transform.basis.orthonormalized().get_euler()
# Flip the rotation euler if using negative rotation.
if use_negative_our_rot == true:
self_euler = -self_euler
# Apply this node's rotation euler on each axis, if wanted/required.
if use_our_rotation_x == true:
rest_euler.x = self_euler.x
if use_our_rotation_y == true:
rest_euler.y = self_euler.y
if use_our_rotation_z == true:
rest_euler.z = self_euler.z
# Make a new basis with the, potentially, changed euler angles.
rest.basis = Basis(rest_euler)
# Apply additional rotation stored in additional_rotation to the bone.
if additional_rotation != Vector3.ZERO:
rest.basis = rest.basis.rotated(rest.basis.x, deg2rad(additional_rotation.x))
rest.basis = rest.basis.rotated(rest.basis.y, deg2rad(additional_rotation.y))
rest.basis = rest.basis.rotated(rest.basis.z, deg2rad(additional_rotation.z))
# If the position is set using an additional bone, then set the origin
# based on that bone and its length.
if position_using_additional_bone:
var additional_bone_id = skeleton_to_use.find_bone(additional_bone_name)
var additional_bone_pos = skeleton_to_use.get_bone_global_pose(additional_bone_id)
rest.origin = additional_bone_pos.origin - additional_bone_pos.basis.z.normalized() * additional_bone_length
# Finally, apply the new rotation to the bone in the skeleton.
skeleton_to_use.set_bone_global_pose_override(bone, rest, 1.0, true)
func _setup_for_editor():
# To see the target in the editor, let's create a MeshInstance,
# add it as a child of this node, and name it.
_editor_indicator = MeshInstance.new()
add_child(_editor_indicator)
_editor_indicator.name = "(EditorOnly) Visual indicator"
# Make a sphere mesh for the MeshInstance
var indicator_mesh = SphereMesh.new()
indicator_mesh.radius = 0.1
indicator_mesh.height = 0.2
indicator_mesh.radial_segments = 8
indicator_mesh.rings = 4
# Create a new SpatialMaterial for the sphere and give it the editor
# gizmo texture so it is textured.
var indicator_material = SpatialMaterial.new()
indicator_material.flags_unshaded = true
indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
indicator_material.albedo_color = Color(1, 0.5, 0, 1)
# Assign the material and mesh to the MeshInstance.
indicator_mesh.material = indicator_material
_editor_indicator.mesh = indicator_mesh
func _set_update(new_value):
update_mode = new_value
# Set all of our processes to false.
set_process(false)
set_physics_process(false)
set_notify_transform(false)
# Based on the value of passed to update, enable the correct process.
if update_mode == 0:
set_process(true)
if debug_messages == true:
print (name, " - IK_LookAt: updating skeleton using _process...")
elif update_mode == 1:
set_physics_process(true)
if debug_messages == true:
print (name, " - IK_LookAt: updating skeleton using _physics_process...")
elif update_mode == 2:
set_notify_transform(true)
if debug_messages == true:
print (name, " - IK_LookAt: updating skeleton using _notification...")
else:
if debug_messages == true:
print (name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")
func _set_skeleton_path(new_value):
# Because get_node doesn't work in the first call, we just want to assign instead.
# This is to get around a issue with NodePaths exposed to the editor.
if first_call == true:
skeleton_path = new_value
return
# Assign skeleton_path to whatever value is passed.
skeleton_path = new_value
if skeleton_path == null:
if debug_messages == true:
print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
return
# Get the node at that location, if there is one.
var temp = get_node(skeleton_path)
if temp != null:
if temp is Skeleton:
skeleton_to_use = temp
if debug_messages == true:
print (name, " - IK_LookAt: attached to (new) skeleton")
else:
skeleton_to_use = null
if debug_messages == true:
print (name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
else:
if debug_messages == true:
print (name, " - IK_LookAt: No Nodepath selected for skeleton_path!")