inverse_kinematics.rst 5.0 KB

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