123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- .. _doc_inverse_kinematics:
- Inverse kinematics
- ==================
- This tutorial is a follow-up of :ref:`doc_working_with_3d_skeletons`.
- Before continuing on, I'd recommend reading some theory, the simplest
- article I could find is this:
- http://freespace.virgin.net/hugo.elias/models/m_ik2.htm
- Initial problem
- ~~~~~~~~~~~~~~~
- Talking in Godot terminology, the task we want to solve here is to position
- our 2 angles we talked about above so, that the tip of the lowerarm bone is
- as close to the target point (which is set by the target Vector3()) as possible
- using only rotations. This task is calculation-intensive and never
- resolved by analytical equation solving. So, it is an underconstrained
- problem, which means there is an unlimited number of solutions to the
- equation.
- .. image:: img/inverse_kinematics.png
- For easy calculation, in this chapter we consider the target being a
- child of Skeleton. If this is not the case for your setup you can always
- reparent it in your script, as you will save on calculations if you
- do so.
- In the picture you see the angles alpha and beta. In this case we don't
- use poles and constraints, so we need to add our own. On the picture
- the angles are 2D angles living in a plane which is defined by bone
- base, bone tip and target.
- The rotation axis is easily calculated using the cross-product of the bone
- vector and the target vector. The rotation in this case will be always in
- positive direction. If ``t`` is the Transform which we get from the
- get_bone_global_pose() function, the bone vector is
- ::
- t.basis[2]
- So we have all the information we need to execute our algorithm.
- In game dev it is common to resolve this problem by iteratively closing
- to the desired location, adding/subtracting small numbers to the angles
- until the distance change achieved is less than some small error value.
- Sounds easy enough, but there are Godot problems we need to resolve
- there to achieve our goal.
- - **How to find coordinates of the tip of the bone?**
- - **How to find the vector from the bone base to the target?**
- For our goal (tip of the bone moved within area of target), we need to know
- where the tip of our IK bone is. As we don't use a leaf bone as IK bone, we
- know the coordinate of the bone base is the tip of the parent bone. All these
- calculations are quite dependent on the skeleton's structure. You can use
- pre-calculated constants as well. You can add an extra bone at the tip of the
- IK bone and calculate using that.
- Implementation
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- We will use an exported variable for the bone length to make it easy.
- ::
- export var ik_bone = "lowerarm"
- export var ik_bone_length = 1.0
- export var ik_error = 0.1
- Now, we need to apply our transformations from the IK bone to the base of
- the chain. So we apply a rotation to the IK bone then move from our IK bone up to
- its parent, apply rotation again, then move to the parent of the
- current bone again, etc. So we need to limit our chain somewhat.
- ::
- export var ik_limit = 2
- For the ``_ready()`` function:
- ::
- var skel
- func _ready():
- skel = get_node("arm/Armature/Skeleton")
- set_process(true)
- Now we can write our chain-passing function:
- ::
- func pass_chain():
- var b = skel.find_bone(ik_bone)
- var l = ik_limit
- while b >= 0 and l > 0:
- print( "name":", skel.get_bone_name(b))
- print( "local transform":", skel.get_bone_pose(b))
- print( "global transform":", skel.get_bone_global_pose(b))
- b = skel.get_bone_parent(b)
- l = l - 1
- And for the ``_process()`` function:
- ::
- func _process(delta):
- pass_chain(delta)
- Executing this script will pass through the bone chain, printing bone
- transforms.
- ::
- extends Spatial
- export var ik_bone = "lowerarm"
- export var ik_bone_length = 1.0
- export var ik_error = 0.1
- export var ik_limit = 2
- var skel
- func _ready():
- skel = get_node("arm/Armature/Skeleton")
- set_process(true)
- func pass_chain(delta):
- var b = skel.find_bone(ik_bone)
- var l = ik_limit
- while b >= 0 and l > 0:
- print("name: ", skel.get_bone_name(b))
- print("local transform: ", skel.get_bone_pose(b))
- print( "global transform:", skel.get_bone_global_pose(b))
- b = skel.get_bone_parent(b)
- l = l - 1
- func _process(delta):
- pass_chain(delta)
- Now we need to actually work with the target. The target should be placed
- somewhere accessible. Since "arm" is an imported scene, we better place
- the target node within our top level scene. But for us to work with target
- easily its Transform should be on the same level as the Skeleton.
- To cope with this problem we create a "target" node under our scene root
- node and at runtime we will reparent it copying the global transform,
- which will achieve the desired effect.
- Create a new Spatial node under the root node and rename it to "target".
- Then modify the ``_ready()`` function to look like this:
- ::
- var skel
- var target
- func _ready():
- skel = get_node("arm/Armature/Skeleton")
- target = get_node("target")
- var ttrans = target.get_global_transform()
- remove_child(target)
- skel.add_child(target)
- target.set_global_transform(ttrans)
- set_process(true)
|