inverse_kinematics.rst 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. .. _doc_inverse_kinematics:
  2. Inverse kinematics
  3. ==================
  4. This tutorial is a follow-up of :ref:`doc_working_with_3d_skeletons`.
  5. Before continuing on, I'd recommend reading some theory, the simplest
  6. article I could find is this:
  7. http://freespace.virgin.net/hugo.elias/models/m_ik2.htm
  8. Initial problem
  9. ~~~~~~~~~~~~~~~
  10. Talking in Godot terminology, the task we want to solve here is to position
  11. our 2 angles we talked about above so, that the tip of the lowerarm bone is
  12. as close to the target point (which is set by the target Vector3()) as possible
  13. using only rotations. This task is calculation-intensive and never
  14. resolved by analytical equation solving. So, it is an underconstrained
  15. problem, which means there is an unlimited number of solutions to the
  16. equation.
  17. .. image:: img/inverse_kinematics.png
  18. For easy calculation, in this chapter we consider the target being a
  19. child of Skeleton. If this is not the case for your setup you can always
  20. reparent it in your script, as you will save on calculations if you
  21. do so.
  22. In the picture you see the angles alpha and beta. In this case we don't
  23. use poles and constraints, so we need to add our own. On the picture
  24. the angles are 2D angles living in a plane which is defined by bone
  25. base, bone tip and target.
  26. The rotation axis is easily calculated using the cross-product of the bone
  27. vector and the target vector. The rotation in this case will be always in
  28. positive direction. If ``t`` is the Transform which we get from the
  29. get_bone_global_pose() function, the bone vector is
  30. ::
  31. t.basis[2]
  32. So we have all the information we need to execute our algorithm.
  33. In game dev it is common to resolve this problem by iteratively closing
  34. to the desired location, adding/subtracting small numbers to the angles
  35. until the distance change achieved is less than some small error value.
  36. Sounds easy enough, but there are Godot problems we need to resolve
  37. there to achieve our goal.
  38. - **How to find coordinates of the tip of the bone?**
  39. - **How to find the vector from the bone base to the target?**
  40. For our goal (tip of the bone moved within area of target), we need to know
  41. where the tip of our IK bone is. As we don't use a leaf bone as IK bone, we
  42. know the coordinate of the bone base is the tip of the parent bone. All these
  43. calculations are quite dependent on the skeleton's structure. You can use
  44. pre-calculated constants as well. You can add an extra bone at the tip of the
  45. IK bone and calculate using that.
  46. Implementation
  47. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  48. We will use an exported variable for the bone length to make it easy.
  49. ::
  50. export var ik_bone = "lowerarm"
  51. export var ik_bone_length = 1.0
  52. export var ik_error = 0.1
  53. Now, we need to apply our transformations from the IK bone to the base of
  54. the chain. So we apply a rotation to the IK bone then move from our IK bone up to
  55. its parent, apply rotation again, then move to the parent of the
  56. current bone again, etc. So we need to limit our chain somewhat.
  57. ::
  58. export var ik_limit = 2
  59. For the ``_ready()`` function:
  60. ::
  61. var skel
  62. func _ready():
  63. skel = get_node("arm/Armature/Skeleton")
  64. set_process(true)
  65. Now we can write our chain-passing function:
  66. ::
  67. func pass_chain():
  68. var b = skel.find_bone(ik_bone)
  69. var l = ik_limit
  70. while b >= 0 and l > 0:
  71. print( "name":", skel.get_bone_name(b))
  72. print( "local transform":", skel.get_bone_pose(b))
  73. print( "global transform":", skel.get_bone_global_pose(b))
  74. b = skel.get_bone_parent(b)
  75. l = l - 1
  76. And for the ``_process()`` function:
  77. ::
  78. func _process(delta):
  79. pass_chain(delta)
  80. Executing this script will pass through the bone chain, printing bone
  81. transforms.
  82. ::
  83. extends Spatial
  84. export var ik_bone = "lowerarm"
  85. export var ik_bone_length = 1.0
  86. export var ik_error = 0.1
  87. export var ik_limit = 2
  88. var skel
  89. func _ready():
  90. skel = get_node("arm/Armature/Skeleton")
  91. set_process(true)
  92. func pass_chain(delta):
  93. var b = skel.find_bone(ik_bone)
  94. var l = ik_limit
  95. while b >= 0 and l > 0:
  96. print("name: ", skel.get_bone_name(b))
  97. print("local transform: ", skel.get_bone_pose(b))
  98. print( "global transform:", skel.get_bone_global_pose(b))
  99. b = skel.get_bone_parent(b)
  100. l = l - 1
  101. func _process(delta):
  102. pass_chain(delta)
  103. Now we need to actually work with the target. The target should be placed
  104. somewhere accessible. Since "arm" is an imported scene, we better place
  105. the target node within our top level scene. But for us to work with target
  106. easily its Transform should be on the same level as the Skeleton.
  107. To cope with this problem we create a "target" node under our scene root
  108. node and at runtime we will reparent it copying the global transform,
  109. which will achieve the desired effect.
  110. Create a new Spatial node under the root node and rename it to "target".
  111. Then modify the ``_ready()`` function to look like this:
  112. ::
  113. var skel
  114. var target
  115. func _ready():
  116. skel = get_node("arm/Armature/Skeleton")
  117. target = get_node("target")
  118. var ttrans = target.get_global_transform()
  119. remove_child(target)
  120. skel.add_child(target)
  121. target.set_global_transform(ttrans)
  122. set_process(true)