ik_look_at.gd 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. @tool
  2. extends Node3D
  3. @export var skeleton_path: NodePath:
  4. set(value):
  5. # TODO: Manually copy the code from this method.
  6. _set_skeleton_path(value)
  7. @export var bone_name: String = ""
  8. @export_enum("_process", "_physics_process", "_notification", "none") var update_mode: int = 0:
  9. set(value):
  10. update_mode = value
  11. # Set all of our processes to false.
  12. set_process(false)
  13. set_physics_process(false)
  14. set_notify_transform(false)
  15. # Based on the value of passed to update, enable the correct process.
  16. if update_mode == 0:
  17. set_process(true)
  18. if debug_messages:
  19. print(name, " - IK_LookAt: updating skeleton using _process...")
  20. elif update_mode == 1:
  21. set_physics_process(true)
  22. if debug_messages:
  23. print(name, " - IK_LookAt: updating skeleton using _physics_process...")
  24. elif update_mode == 2:
  25. set_notify_transform(true)
  26. if debug_messages:
  27. print(name, " - IK_LookAt: updating skeleton using _notification...")
  28. else:
  29. if debug_messages:
  30. print(name, " - IK_LookAt: NOT updating skeleton due to unknown update method...")
  31. @export_enum("X-up", "Y-up", "Z-up") var look_at_axis: int = 1
  32. @export_range(0.0, 1.0, 0.001) var interpolation: float = 1.0
  33. @export var use_our_rotation_x: bool = false
  34. @export var use_our_rotation_y: bool = false
  35. @export var use_our_rotation_z: bool = false
  36. @export var use_negative_our_rot: bool = false
  37. @export var additional_rotation: Vector3 = Vector3()
  38. @export var position_using_additional_bone: bool = false
  39. @export var additional_bone_name: String = ""
  40. @export var additional_bone_length: float = 1
  41. @export var debug_messages: bool = false
  42. var skeleton_to_use: Skeleton3D = null
  43. var first_call: bool = true
  44. var _editor_indicator: Node3D = null
  45. func _ready():
  46. set_process(false)
  47. set_physics_process(false)
  48. set_notify_transform(false)
  49. if update_mode == 0:
  50. set_process(true)
  51. elif update_mode == 1:
  52. set_physics_process(true)
  53. elif update_mode == 2:
  54. set_notify_transform(true)
  55. else:
  56. if debug_messages:
  57. print(name, " - IK_LookAt: Unknown update mode. NOT updating skeleton")
  58. if Engine.editor_hint:
  59. _setup_for_editor()
  60. func _process(_delta):
  61. update_skeleton()
  62. func _physics_process(_delta):
  63. update_skeleton()
  64. func _notification(what):
  65. if what == NOTIFICATION_TRANSFORM_CHANGED:
  66. update_skeleton()
  67. func update_skeleton():
  68. # NOTE: Because get_node doesn't work in _ready, we need to skip
  69. # a call before doing anything.
  70. if first_call:
  71. first_call = false
  72. if skeleton_to_use == null:
  73. _set_skeleton_path(skeleton_path)
  74. # If we do not have a skeleton and/or we're not supposed to update, then return.
  75. if skeleton_to_use == null:
  76. return
  77. if update_mode >= 3:
  78. return
  79. # Get the bone index.
  80. var bone: int = skeleton_to_use.find_bone(bone_name)
  81. # If no bone is found (-1), then return and optionally printan error.
  82. if bone == -1:
  83. if debug_messages:
  84. print(name, " - IK_LookAt: No bone in skeleton found with name [", bone_name, "]!")
  85. return
  86. # get the bone's global transform pose.
  87. var rest = skeleton_to_use.get_bone_global_pose(bone)
  88. # Convert our position relative to the skeleton's transform.
  89. var target_pos = global_transform.origin * skeleton_to_use.global_transform
  90. # Call helper's look_at function with the chosen up axis.
  91. if look_at_axis == 0:
  92. rest = rest.looking_at(target_pos, Vector3.RIGHT)
  93. elif look_at_axis == 1:
  94. rest = rest.looking_at(target_pos, Vector3.UP)
  95. elif look_at_axis == 2:
  96. rest = rest.looking_at(target_pos, Vector3.FORWARD)
  97. else:
  98. rest = rest.looking_at(target_pos, Vector3.UP)
  99. if debug_messages:
  100. print(name, " - IK_LookAt: Unknown look_at_axis value!")
  101. # Get the rotation euler of the bone and of this node.
  102. var rest_euler = rest.basis.get_euler()
  103. var self_euler = global_transform.basis.orthonormalized().get_euler()
  104. # Flip the rotation euler if using negative rotation.
  105. if use_negative_our_rot:
  106. self_euler = -self_euler
  107. # Apply this node's rotation euler on each axis, if wanted/required.
  108. if use_our_rotation_x:
  109. rest_euler.x = self_euler.x
  110. if use_our_rotation_y:
  111. rest_euler.y = self_euler.y
  112. if use_our_rotation_z:
  113. rest_euler.z = self_euler.z
  114. # Make a new basis with the, potentially, changed euler angles.
  115. rest.basis = Basis(rest_euler)
  116. # Apply additional rotation stored in additional_rotation to the bone.
  117. if additional_rotation != Vector3.ZERO:
  118. rest.basis = rest.basis.rotated(rest.basis.x, deg2rad(additional_rotation.x))
  119. rest.basis = rest.basis.rotated(rest.basis.y, deg2rad(additional_rotation.y))
  120. rest.basis = rest.basis.rotated(rest.basis.z, deg2rad(additional_rotation.z))
  121. # If the position is set using an additional bone, then set the origin
  122. # based on that bone and its length.
  123. if position_using_additional_bone:
  124. var additional_bone_id = skeleton_to_use.find_bone(additional_bone_name)
  125. var additional_bone_pos = skeleton_to_use.get_bone_global_pose(additional_bone_id)
  126. rest.origin = additional_bone_pos.origin - additional_bone_pos.basis.z.normalized() * additional_bone_length
  127. # Finally, apply the new rotation to the bone in the skeleton.
  128. skeleton_to_use.set_bone_global_pose_override(bone, rest, interpolation, true)
  129. func _setup_for_editor():
  130. # To see the target in the editor, let's create a MeshInstance3D,
  131. # add it as a child of this node, and name it.
  132. _editor_indicator = MeshInstance3D.new()
  133. add_child(_editor_indicator)
  134. _editor_indicator.name = &"(EditorOnly) Visual indicator"
  135. # Make a sphere mesh for the MeshInstance3D
  136. var indicator_mesh = SphereMesh.new()
  137. indicator_mesh.radius = 0.1
  138. indicator_mesh.height = 0.2
  139. indicator_mesh.radial_segments = 8
  140. indicator_mesh.rings = 4
  141. # Create a new StandardMaterial3D for the sphere and give it the editor
  142. # gizmo texture so it is textured.
  143. var indicator_material = StandardMaterial3D.new()
  144. indicator_material.flags_unshaded = true
  145. indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
  146. indicator_material.albedo_color = Color(1, 0.5, 0, 1)
  147. # Assign the material and mesh to the MeshInstance3D.
  148. indicator_mesh.material = indicator_material
  149. _editor_indicator.mesh = indicator_mesh
  150. func _set_skeleton_path(new_value):
  151. # Because get_node doesn't work in the first call, we just want to assign instead.
  152. # This is to get around a issue with NodePaths exposed to the editor.
  153. if first_call:
  154. skeleton_path = new_value
  155. return
  156. # Assign skeleton_path to whatever value is passed.
  157. skeleton_path = new_value
  158. if skeleton_path == null:
  159. if debug_messages:
  160. print(name, " - IK_LookAt: No Nodepath selected for skeleton_path!")
  161. return
  162. # Get the node at that location, if there is one.
  163. var temp = get_node(skeleton_path)
  164. if temp != null:
  165. if temp is Skeleton3D:
  166. skeleton_to_use = temp
  167. if debug_messages:
  168. print(name, " - IK_LookAt: attached to (new) skeleton")
  169. else:
  170. skeleton_to_use = null
  171. if debug_messages:
  172. print(name, " - IK_LookAt: skeleton_path does not point to a skeleton!")
  173. else:
  174. if debug_messages:
  175. print(name, " - IK_LookAt: No Nodepath selected for skeleton_path!")