123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- @tool
- extends Node3D
- @export var skeleton_path: NodePath:
- set(value):
- # TODO: Manually copy the code from this method.
- _set_skeleton_path(value)
- @export var bone_name: String = ""
- @export_enum("_process", "_physics_process", "_notification", "none") var update_mode: int = 0:
- set(value):
- update_mode = 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:
- print(name, " - IK_LookAt: updating skeleton using _process...")
- elif update_mode == 1:
- set_physics_process(true)
- if debug_messages:
- print(name, " - IK_LookAt: updating skeleton using _physics_process...")
- elif update_mode == 2:
- set_notify_transform(true)
- if debug_messages:
- print(name, " - IK_LookAt: updating skeleton using _notification...")
- else:
- if debug_messages:
- print(name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")
- @export_enum("X-up", "Y-up", "Z-up") var look_at_axis: int = 1
- @export_range(0.0, 1.0, 0.001) var interpolation: float = 1.0
- @export var use_our_rotation_x: bool = false
- @export var use_our_rotation_y: bool = false
- @export var use_our_rotation_z: bool = false
- @export var use_negative_our_rot: bool = false
- @export var additional_rotation: Vector3 = Vector3()
- @export var position_using_additional_bone: bool = false
- @export var additional_bone_name: String = ""
- @export var additional_bone_length: float = 1
- @export var debug_messages: bool = false
- var skeleton_to_use: Skeleton3D = null
- var first_call: bool = true
- var _editor_indicator: Node3D = 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:
- print(name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")
- if Engine.editor_hint:
- _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:
- 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 printan error.
- if bone == -1:
- if debug_messages:
- 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 = global_transform.origin * skeleton_to_use.global_transform
- # 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:
- 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:
- self_euler = -self_euler
- # Apply this node's rotation euler on each axis, if wanted/required.
- if use_our_rotation_x:
- rest_euler.x = self_euler.x
- if use_our_rotation_y:
- rest_euler.y = self_euler.y
- if use_our_rotation_z:
- 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, interpolation, true)
- func _setup_for_editor():
- # To see the target in the editor, let's create a MeshInstance3D,
- # add it as a child of this node, and name it.
- _editor_indicator = MeshInstance3D.new()
- add_child(_editor_indicator)
- _editor_indicator.name = &"(EditorOnly) Visual indicator"
- # Make a sphere mesh for the MeshInstance3D
- 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 StandardMaterial3D for the sphere and give it the editor
- # gizmo texture so it is textured.
- var indicator_material = StandardMaterial3D.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 MeshInstance3D.
- indicator_mesh.material = indicator_material
- _editor_indicator.mesh = indicator_mesh
- 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:
- skeleton_path = new_value
- return
- # Assign skeleton_path to whatever value is passed.
- skeleton_path = new_value
- if skeleton_path == null:
- if debug_messages:
- 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 Skeleton3D:
- skeleton_to_use = temp
- if debug_messages:
- print(name, " - IK_LookAt: attached to (new) skeleton")
- else:
- skeleton_to_use = null
- if debug_messages:
- print(name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
- else:
- if debug_messages:
- print(name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
|