ik_fabrik.gd 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. @tool
  2. extends Node3D
  3. # A FABRIK IK chain with a middle joint helper.
  4. # The delta/tolerance for the bone chain (how do the bones need to be before it is considered satisfactory)
  5. const CHAIN_TOLERANCE = 0.01
  6. # The amount of interations the bone chain will go through in an attempt to get to the target position
  7. const CHAIN_MAX_ITER = 10
  8. @export var skeleton_path: NodePath:
  9. set(value):
  10. skeleton_path = value
  11. # Because get_node doesn't work in the first call, we just want to assign instead
  12. if first_call:
  13. return
  14. if skeleton_path == null:
  15. if debug_messages:
  16. printerr(name, " - IK_FABRIK: No Nodepath selected for skeleton_path!")
  17. return
  18. var temp = get_node(skeleton_path)
  19. if temp != null:
  20. # If it has the method "get_bone_global_pose" it is likely a Skeleton3D
  21. if temp.has_method("get_bone_global_pose"):
  22. skeleton = temp
  23. bone_IDs = {}
  24. # (Delete all of the old bone nodes and) Make all of the bone nodes for each bone in the IK chain
  25. _make_bone_nodes()
  26. if debug_messages:
  27. printerr(name, " - IK_FABRIK: Attached to a new skeleton")
  28. # If not, then it's (likely) not a Skeleton3D node
  29. else:
  30. skeleton = null
  31. if debug_messages:
  32. printerr(name, " - IK_FABRIK: skeleton_path does not point to a skeleton!")
  33. else:
  34. if debug_messages:
  35. printerr(name, " - IK_FABRIK: No Nodepath selected for skeleton_path!")
  36. @export var bones_in_chain: PackedStringArray:
  37. set(value):
  38. bones_in_chain = value
  39. _make_bone_nodes()
  40. @export var bones_in_chain_lengths: PackedFloat32Array:
  41. set(value):
  42. bones_in_chain_lengths = value
  43. total_length = INF
  44. @export_enum("_process", "_physics_process", "_notification", "none") var update_mode: int = 0:
  45. set(value):
  46. update_mode = value
  47. set_process(false)
  48. set_physics_process(false)
  49. set_notify_transform(false)
  50. if update_mode == 0:
  51. set_process(true)
  52. elif update_mode == 1:
  53. set_process(true)
  54. elif update_mode == 2:
  55. set_notify_transform(true)
  56. else:
  57. if debug_messages:
  58. printerr(name, " - IK_FABRIK: Unknown update mode. NOT updating skeleton")
  59. return
  60. var target: Node3D = null
  61. var skeleton: Skeleton3D
  62. # A dictionary holding all of the bone IDs (from the skeleton) and a dictionary holding
  63. # all of the bone helper nodes
  64. var bone_IDs = {}
  65. var bone_nodes = {}
  66. # The position of the origin
  67. var chain_origin = Vector3()
  68. # The combined length of every bone in the bone chain
  69. var total_length = INF
  70. # The amount of iterations we've been through, and whether or not we want to limit our solver to CHAIN_MAX_ITER
  71. # amounts of interations.
  72. @export var chain_iterations: int = 0
  73. @export var limit_chain_iterations: bool = true
  74. # Should we reset chain_iterations on movement during our update method?
  75. @export var reset_iterations_on_update: bool = false
  76. # A boolean to track whether or not we want to move the middle joint towards middle joint target.
  77. @export var use_middle_joint_target: bool = false
  78. var middle_joint_target: Node3D = null
  79. # Have we called _set_skeleton_path or not already. Due to some issues using exported NodePaths,
  80. # we need to ignore the first _set_skeleton_path call.
  81. var first_call = true
  82. # A boolean to track whether or not we want to print debug messages
  83. var debug_messages = false
  84. func _ready():
  85. if target == null:
  86. # NOTE: You MUST have a node called Target as a child of this node!
  87. # So we create one if one doesn't already exist.
  88. if not has_node("Target"):
  89. target = Node3D.new()
  90. add_child(target)
  91. if Engine.editor_hint:
  92. if get_tree() != null:
  93. if get_tree().edited_scene_root != null:
  94. target.set_owner(get_tree().edited_scene_root)
  95. target.name = &"Target"
  96. else:
  97. target = $Target
  98. # If we are in the editor, we want to make a sphere at this node
  99. if Engine.editor_hint:
  100. _make_editor_sphere_at_node(target, Color.MAGENTA)
  101. if middle_joint_target == null:
  102. if not has_node("MiddleJoint"):
  103. middle_joint_target = Node3D.new()
  104. add_child(middle_joint_target)
  105. if Engine.editor_hint:
  106. if get_tree() != null:
  107. if get_tree().edited_scene_root != null:
  108. middle_joint_target.set_owner(get_tree().edited_scene_root)
  109. middle_joint_target.name = &"MiddleJoint"
  110. else:
  111. middle_joint_target = get_node(^"MiddleJoint")
  112. # If we are in the editor, we want to make a sphere at this node
  113. if Engine.editor_hint:
  114. _make_editor_sphere_at_node(middle_joint_target, Color(1, 0.24, 1, 1))
  115. # Make all of the bone nodes for each bone in the IK chain
  116. _make_bone_nodes()
  117. # Make sure we're using the right update mode
  118. update_mode = update_mode
  119. # Various upate methods
  120. func _process(_delta):
  121. if reset_iterations_on_update:
  122. chain_iterations = 0
  123. update_skeleton()
  124. func _physics_process(_delta):
  125. if reset_iterations_on_update:
  126. chain_iterations = 0
  127. update_skeleton()
  128. func _notification(what):
  129. if what == NOTIFICATION_TRANSFORM_CHANGED:
  130. if reset_iterations_on_update:
  131. chain_iterations = 0
  132. update_skeleton()
  133. ############# IK SOLVER RELATED FUNCTIONS #############
  134. func update_skeleton():
  135. #### ERROR CHECKING conditions
  136. if first_call:
  137. skeleton_path = skeleton_path
  138. first_call = false
  139. if skeleton == null:
  140. skeleton_path = skeleton_path
  141. return
  142. if bones_in_chain == null:
  143. if debug_messages:
  144. printerr(name, " - IK_FABRIK: No Bones in IK chain defined!")
  145. return
  146. if bones_in_chain_lengths == null:
  147. if debug_messages:
  148. printerr(name, " - IK_FABRIK: No Bone3D lengths in IK chain defined!")
  149. return
  150. if bones_in_chain.size() != bones_in_chain_lengths.size():
  151. if debug_messages:
  152. printerr(name, " - IK_FABRIK: bones_in_chain and bones_in_chain_lengths!")
  153. return
  154. ################################
  155. # Set all of the bone IDs in bone_IDs, if they are not already made
  156. var i = 0
  157. if bone_IDs.size() <= 0:
  158. for bone_name in bones_in_chain:
  159. bone_IDs[bone_name] = skeleton.find_bone(bone_name)
  160. # Set the bone node to the currect bone position
  161. bone_nodes[i].global_transform = get_bone_transform(i)
  162. # If this is not the last bone in the bone chain, make it look at the next bone in the bone chain
  163. if i < bone_IDs.size()-1:
  164. bone_nodes[i].look_at(get_bone_transform(i+1).origin + skeleton.global_transform.origin, Vector3.UP)
  165. i += 1
  166. # Set the total length of the bone chain, if it is not already set
  167. if total_length == INF:
  168. total_length = 0
  169. for bone_length in bones_in_chain_lengths:
  170. total_length += bone_length
  171. # Solve the bone chain
  172. solve_chain()
  173. func solve_chain():
  174. # If we have reached our max chain iteration, and we are limiting ourselves, then return.
  175. # Otherwise set chain_iterations to zero (so we constantly update)
  176. if chain_iterations >= CHAIN_MAX_ITER and limit_chain_iterations:
  177. return
  178. else:
  179. chain_iterations = 0
  180. # Update the origin with the current bone's origin
  181. chain_origin = get_bone_transform(0).origin
  182. # Get the direction of the final bone by using the next to last bone if there is more than 2 bones.
  183. # If there are only 2 bones, we use the target's forward Z vector instead (not ideal, but it works fairly well)
  184. var dir
  185. if bone_nodes.size() > 2:
  186. dir = bone_nodes[bone_nodes.size()-2].global_transform.basis.z.normalized()
  187. else:
  188. dir = -target.global_transform.basis.z.normalized()
  189. # Get the target position (accounting for the final bone and it's length)
  190. var target_pos = target.global_transform.origin + (dir * bones_in_chain_lengths[bone_nodes.size()-1])
  191. # If we are using middle joint target (and have more than 2 bones), move our middle joint towards it!
  192. if use_middle_joint_target:
  193. if bone_nodes.size() > 2:
  194. var middle_point_pos = middle_joint_target.global_transform.origin
  195. var middle_point_pos_diff = (middle_point_pos - bone_nodes[bone_nodes.size()/2].global_transform.origin)
  196. bone_nodes[bone_nodes.size()/2].global_transform.origin += middle_point_pos_diff.normalized()
  197. # Get the difference between our end effector (the final bone in the chain) and the target
  198. var dif = (bone_nodes[bone_nodes.size()-1].global_transform.origin - target_pos).length()
  199. # Check to see if the distance from the end effector to the target is within our error margin (CHAIN_TOLERANCE).
  200. # If it not, move the chain towards the target (going forwards, backwards, and then applying rotation)
  201. while dif > CHAIN_TOLERANCE:
  202. chain_backward()
  203. chain_forward()
  204. chain_apply_rotation()
  205. # Update the difference between our end effector (the final bone in the chain) and the target
  206. dif = (bone_nodes[bone_nodes.size()-1].global_transform.origin - target_pos).length()
  207. # Add one to chain_iterations. If we have reached our max iterations, then break
  208. chain_iterations = chain_iterations + 1
  209. if chain_iterations >= CHAIN_MAX_ITER:
  210. break
  211. # Reset the bone node transforms to the skeleton bone transforms
  212. for i in range(0, bone_nodes.size()):
  213. var reset_bone_trans = get_bone_transform(i)
  214. bone_nodes[i].global_transform = reset_bone_trans
  215. # Backward reaching pass
  216. func chain_backward():
  217. # Get the direction of the final bone by using the next to last bone if there is more than 2 bones.
  218. # If there are only 2 bones, we use the target's forward Z vector instead (not ideal, but it works fairly well)
  219. var dir
  220. if bone_nodes.size() > 2:
  221. dir = bone_nodes[bone_nodes.size() - 2].global_transform.basis.z.normalized()
  222. else:
  223. dir = -target.global_transform.basis.z.normalized()
  224. # Set the position of the end effector (the final bone in the chain) to the target position
  225. bone_nodes[bone_nodes.size()-1].global_transform.origin = target.global_transform.origin + (dir * bones_in_chain_lengths[bone_nodes.size()-1])
  226. # For all of the other bones, move them towards the target
  227. var i = bones_in_chain.size() - 1
  228. while i >= 1:
  229. var prev_origin = bone_nodes[i].global_transform.origin
  230. i -= 1
  231. var curr_origin = bone_nodes[i].global_transform.origin
  232. var r = prev_origin - curr_origin
  233. var l = bones_in_chain_lengths[i] / r.length()
  234. # Apply the new joint position
  235. bone_nodes[i].global_transform.origin = prev_origin.lerp(curr_origin, l)
  236. # Forward reaching pass
  237. func chain_forward():
  238. # Set root at initial position
  239. bone_nodes[0].global_transform.origin = chain_origin
  240. # Go through every bone in the bone chain
  241. for i in range(bones_in_chain.size() - 1):
  242. var curr_origin = bone_nodes[i].global_transform.origin
  243. var next_origin = bone_nodes[i + 1].global_transform.origin
  244. var r = next_origin - curr_origin
  245. var l = bones_in_chain_lengths[i] / r.length()
  246. # Apply the new joint position, (potentially with constraints), to the bone node
  247. bone_nodes[i + 1].global_transform.origin = curr_origin.lerp(next_origin, l)
  248. # Make all of the bones rotated correctly.
  249. func chain_apply_rotation():
  250. # For each bone in the bone chain
  251. for i in range(0, bones_in_chain.size()):
  252. # Get the bone's transform, NOT converted to world space
  253. var bone_trans = get_bone_transform(i, false)
  254. # If this is the last bone in the bone chain, rotate the bone so it faces
  255. # the same direction as the next to last bone in the bone chain if there are more than
  256. # two bones. If there are only two bones, rotate the end effector towards the target
  257. if i == bones_in_chain.size() - 1:
  258. if bones_in_chain.size() > 2:
  259. # Get the bone node for this bone, and the previous bone
  260. var b_target = bone_nodes[i].global_transform
  261. var b_target_two = bone_nodes[i-1].global_transform
  262. # Convert the bone nodes positions from world space to bone/skeleton space
  263. b_target.origin = b_target.origin * skeleton.global_transform
  264. b_target_two.origin = b_target_two.origin * skeleton.global_transform
  265. # Get the direction that the previous bone is pointing towards
  266. var dir = (target.global_transform.origin - b_target_two.origin).normalized()
  267. # Make this bone look in the same the direction as the last bone
  268. bone_trans = bone_trans.looking_at(b_target.origin + dir, Vector3.UP)
  269. # Set the position of the bone to the bone target.
  270. # Prior to Godot 3.2, this was not necessary, but because we can now completely
  271. # override bone transforms, we need to set the position as well as rotation.
  272. bone_trans.origin = b_target.origin
  273. else:
  274. var b_target = target.global_transform
  275. b_target.origin = b_target.origin * skeleton.global_transform
  276. bone_trans = bone_trans.looking_at(b_target.origin, Vector3.UP)
  277. # A bit of a hack. Because we only have two bones, we have to use the previous
  278. # bone to position the last bone in the chain.
  279. var last_bone = bone_nodes[i-1].global_transform
  280. # Because we know the length of adjacent bone to this bone in the chain, we can
  281. # position this bone by taking the last bone's position plus the length of the
  282. # bone on the Z axis.
  283. # This will place the position of the bone at the end of the last bone
  284. bone_trans.origin = last_bone.origin - last_bone.basis.z.normalized() * bones_in_chain_lengths[i-1]
  285. # If this is NOT the last bone in the bone chain, rotate the bone to look at the next
  286. # bone in the bone chain.
  287. else:
  288. # Get the bone node for this bone, and the next bone
  289. var b_target = bone_nodes[i].global_transform
  290. var b_target_two = bone_nodes[i+1].global_transform
  291. # Convert the bone nodes positions from world space to bone/skeleton space
  292. b_target.origin = b_target.origin * skeleton.global_transform
  293. b_target_two.origin = b_target_two.origin * skeleton.global_transform
  294. # Get the direction towards the next bone
  295. var dir = (b_target_two.origin - b_target.origin).normalized()
  296. # Make this bone look towards the direction of the next bone
  297. bone_trans = bone_trans.looking_at(b_target.origin + dir, Vector3.UP)
  298. # Set the position of the bone to the bone target.
  299. # Prior to Godot 3.2, this was not necessary, but because we can now completely
  300. # override bone transforms, we need to set the position as well as rotation.
  301. bone_trans.origin = b_target.origin
  302. # The the bone's (updated) transform
  303. set_bone_transform(i, bone_trans)
  304. func get_bone_transform(bone, convert_to_world_space = true):
  305. # Get the global transform of the bone
  306. var ret: Transform3D = skeleton.get_bone_global_pose(bone_IDs[bones_in_chain[bone]])
  307. # If we need to convert the bone position from bone/skeleton space to world space, we
  308. # use the Xform of the skeleton (because bone/skeleton space is relative to the position of the skeleton node).
  309. if convert_to_world_space:
  310. ret.origin = skeleton.global_transform * (ret.origin)
  311. return ret
  312. func set_bone_transform(bone, trans):
  313. # Set the global transform of the bone
  314. skeleton.set_bone_global_pose_override(bone_IDs[bones_in_chain[bone]], trans, 1.0, true)
  315. ############# END OF IK SOLVER RELATED FUNCTIONS #############
  316. func _make_editor_sphere_at_node(node, color):
  317. # So we can see the target in the editor, let's create a mesh instance,
  318. # Add it as our child, and name it
  319. var indicator = MeshInstance3D.new()
  320. node.add_child(indicator)
  321. indicator.name = &"(EditorOnly) Visual indicator"
  322. # We need to make a mesh for the mesh instance.
  323. # The code below makes a small sphere mesh
  324. var indicator_mesh = SphereMesh.new()
  325. indicator_mesh.radius = 0.1
  326. indicator_mesh.height = 0.2
  327. indicator_mesh.radial_segments = 8
  328. indicator_mesh.rings = 4
  329. # The mesh needs a material (unless we want to use the defualt one).
  330. # Let's create a material and use the EditorGizmoTexture to texture it.
  331. var indicator_material = StandardMaterial3D.new()
  332. indicator_material.flags_unshaded = true
  333. indicator_material.albedo_texture = preload("editor_gizmo_texture.png")
  334. indicator_material.albedo_color = color
  335. indicator_mesh.material = indicator_material
  336. indicator.mesh = indicator_mesh
  337. ############# OTHER (NON IK SOLVER RELATED) FUNCTIONS #############
  338. func _make_bone_nodes():
  339. # Remove all of the old bone nodes
  340. # TODO: (not a huge concern, as these can be removed in the editor)
  341. for bone in range(0, bones_in_chain.size()):
  342. var bone_name = bones_in_chain[bone]
  343. if not has_node(bone_name):
  344. var new_node = Node3D.new()
  345. bone_nodes[bone] = new_node
  346. add_child(bone_nodes[bone])
  347. if Engine.editor_hint:
  348. if get_tree() != null:
  349. if get_tree().edited_scene_root != null:
  350. bone_nodes[bone].set_owner(get_tree().edited_scene_root)
  351. bone_nodes[bone].name = bone_name
  352. else:
  353. bone_nodes[bone] = get_node(bone_name)
  354. # If we are in the editor, we want to make a sphere at this node
  355. if Engine.editor_hint:
  356. _make_editor_sphere_at_node(bone_nodes[bone], Color(0.65, 0, 1, 1))