@@ -43,42 +43,42 @@ Once you have implemented these basic kinematics, you should be able to define s
Note that the skeleton does not yet influence the geometry of the cube in this scene -- that will come in Task 3!
<!---
### Task 2b - Inverse Kinematics
#### Single Target IK
Now that we have a logical way to move joints around, we can implement Inverse Kinematics, which will move the joints around in order to reach a target point. There are a few different ways we can do this, but for this assignment we'll implement an iterative method called gradient descent in order to find the minimum of a function. For a function [[task2_media/0050.png|height=16px]], we'll have the update scheme:
Now that we have a logical way to move joints around, we can implement Inverse Kinematics, which will move the joints around in order to reach a target point. There are a few different ways we can do this, but for this assignment we'll implement an iterative method called gradient descent in order to find the minimum of a function. For a function <imgsrc=task2_media/0050.pngheight="20">, we'll have the update scheme:
[[task2_media/0051.png|height=18px]]
<imgsrc=task2_media/0051.pngheight="20">
Where [[task2_media/0052.png|height=9px]] is a small timestep. For this task, we'll be using gradient descent to find the minimum of the cost function:
Where <imgsrc=task2_media/0052.pngheight="9"> is a small timestep. For this task, we'll be using gradient descent to find the minimum of the cost function:
[[task2_media/0053.png|height=36px]]
<imgsrc=task2_media/0053.png>
Where [[task2_media/0054.png|height=19px]] is the position in world space of the target joint, and [[task2_media/0055.png|height=12px]] is the position in world space of the target point.
Where <imgsrc=task2_media/0054.pngheight="20"> is the position in world space of the target joint, and <imgsrc=task2_media/0055.pngheight="20"> is the position in world space of the target point.
More specifically, we'll be using a technique called Jacobian Transpose, which relies on the assumption that:
[[task2_media/0056.png|height=21px]]
<imgsrc=task2_media/0056.pngheight="20">
Where:
*[[task2_media/0057.png|height=14px]] (n x 1) is the function [[task2_media/0058.png|height=19px]], where [[task2_media/0059.png|height=19px]] is the angle of joint [[task2_media/0060.png|height=14px]] around the axis of rotation
*[[task2_media/0061.png|height=9px]] is a constant
*[[task2_media/0062.png|height=16px]] (3 x n) is the Jacobian of [[task2_media/0063.png|height=14px]]
*<imgsrc=task2_media/0057.pngheight="14"> (n x 1) is the function <imgsrc=task2_media/0058.pngheight="19">, where <imgsrc=task2_media/0059.pngheight="19"> is the angle of joint <imgsrc=task2_media/0060.pngheight="14"> around the axis of rotation
*<imgsrc=task2_media/0061.pngheight="9"> is a constant
*<imgsrc=task2_media/0062.pngheight="16"> (3 x n) is the Jacobian of <imgsrc=task2_media/0063.pngheight="14">
Note that here [[task2_media/0064.png|height=9px]] refers to the number of joints in the skeleton. Although in reality this can be reduced to just the number of joints between the target joint and the root, inclusive, because all joints not on that path should stay where they are, so their columns in [[task2_media/0065.png|height=16px]] will be 0\. So [[task2_media/0066.png|height=9px]] can just be the number of joints between the target and the root, inclusive. Additionally note that since this will get multiplied by [[task2_media/0067.png|height=9px]] anyways, you can ignore the value of [[task2_media/0068.png|height=9px]], and just consider the timestep as [[task2_media/0069.png|height=18px]].
Note that here <imgsrc=task2_media/0064.pngheight="9"> refers to the number of joints in the skeleton. Although in reality this can be reduced to just the number of joints between the target joint and the root, inclusive, because all joints not on that path should stay where they are, so their columns in <imgsrc=task2_media/0065.pngheight="16"> will be 0\. So <imgsrc=task2_media/0066.pngheight="9"> can just be the number of joints between the target and the root, inclusive. Additionally note that since this will get multiplied by <imgsrc=task2_media/0067.pngheight="9"> anyways, you can ignore the value of <imgsrc=task2_media/0068.pngheight="9">, and just consider the timestep as <imgsrc=task2_media/0069.pngheight="18">.
Now we just need a way to calcluate the Jacobian of [[task2_media/0070.png|height=14px]]. For this, we can use the fact that:
Now we just need a way to calcluate the Jacobian of <imgsrc=task2_media/0070.pngheight="14">. For this, we can use the fact that:
[[task2_media/0071.png|height=19px]]
<imgsrc=task2_media/0071.pngheight="20">
Where:
*[[task2_media/0072.png|height=16px]] is the [[task2_media/0073.png|height=16px]] column of [[task2_media/0074.png|height=19px]]
*[[task2_media/0075.png|height=14px]] is the axis of rotation
*[[task2_media/0076.png|height=16px]] is the vector from the base of joint [[task2_media/0077.png|height=14px]] to the end point of the target joint
*<imgsrc=task2_media/0072.pngheight="16"> is the <imgsrc=task2_media/0073.pngheight="16"> column of <imgsrc=task2_media/0074.pngheight="19">
*<imgsrc=task2_media/0075.pngheight="14"> is the axis of rotation
*<imgsrc=task2_media/0076.pngheight="16"> is the vector from the base of joint <imgsrc=task2_media/0077.pngheight="14"> to the end point of the target joint
For a more in-depth derivation of Jacobian transpose (and a look into other inverse kinematics algorithms), please check out [this presentation](https://web.archive.org/web/20190501035728/https://autorob.org/lectures/autorob_11_ik_jacobian.pdf). (Pages 45-56 in particular)
...
...
@@ -88,13 +88,13 @@ Now, all of this will work for updating the angle along a single axis, but we ha
We'll extend this so we can have multiple targets, which will then use the function to minimize:
[[task2_media/0078.png|height=40px]]
<imgsrc=task2_media/0078.png>
which is a simple extension actually. Since each term is independent and added together, we can get the gradient of this new cost function just by summing the gradients of each of the constituent cost functions!
You should implement multi-target IK, which will take a `std::map` of `Joint`s and target points for that joint. Each joint can only have 1 target point.
In order to implement this, you should update `Joint::calculateAngleGradient` and `Skeleton::reachForTarget`. `Joint::calculateAngleGradient` should calculate the gradient of [[task2_media/0079.png|height=14px]] in the x,y, and z directions, and add them to `Joint::ikAngleGradient` for all relevant joints. `Skeleton::reachForTarget` should actually do the gradient descent calculations and update the angles of each joint, saving them with `Joint::setAngle`. In this function, you should probably use a very small timestep, but do several iterations (say, 10s to 100s) of gradient descent in order to speed things up. For even faster and better results, you can also implement a variable timestep instead of just using a fixed one. Note also that the root joint should never be updated.
In order to implement this, you should update `Joint::calculateAngleGradient` and `Skeleton::reachForTarget`. `Joint::calculateAngleGradient` should calculate the gradient of <imgsrc=task2_media/0079.pngheight="14"> in the x,y, and z directions, and add them to `Joint::ikAngleGradient` for all relevant joints. `Skeleton::reachForTarget` should actually do the gradient descent calculations and update the angles of each joint, saving them with `Joint::setAngle`. In this function, you should probably use a very small timestep, but do several iterations (say, 10s to 100s) of gradient descent in order to speed things up. For even faster and better results, you can also implement a variable timestep instead of just using a fixed one. Note also that the root joint should never be updated.
A key thing for this part is to _remember what coordinate frame you're in_, because if you calculate the gradients in the wrong coordinate frame or use the axis of rotation in the wrong coordinate frame your answers will come out very wrong!
...
...
@@ -102,4 +102,4 @@ A key thing for this part is to _remember what coordinate frame you're in_, beca
Once you have IK implemented, you should be able to create a series of joints, and get a particular joint to move to the desired final position you have selected.