I couldn't be so wrong.
Granted, I probably (hopefully?) won't need IK for playing animations at runtime, but creating poses for an animation that needs grounded foot, for example, proved to be a true exercise to patience. Moving the hip box, or the character pivot, would change the feet position. Getting that foot back to the former position was quite time-consuming. With the hundreds, probably thousands, of poses I will need to work on, I cannot allow such a basic feature to be difficult to do.
Before I lay out my plans to tackle on this issue, first off a little explanation about how the current system is designed.
The Character() class is nothing but a hierarchy of cubes, starting from the Hip, mimicking a human skeleton. The tree goes as follows:
- Hip
- Abdomen
- Chest
- Left Shoulder
- Left Upper Arm
- Left Lower Arm
- Left Hand
- Right Shoulder
- Right Arm
- Right Lower Arm
- Right Hand
- Neck
- Head
- Left Upper Leg
- Left Lower Leg
- Left Foot
- Right Upper Leg
- Right Lower Leg
- Right Foot
Once I rotate one of the cubes above, every child will follow suit. Rotating and moving the Hip box will, obviously, move everything along. For basic moves, like throwing a punch, the first frame would be a wind-up, a preparation, that does minor movement on the Hip.
Moving the Hip will obviously move the Feet -- and here lies the problem.
To avoid a "feet slide" effect, I have to adjust the Feet back into the previous frame position by rotating every Leg segment (upper and lower leg, three axes each) until its foot is back in place. This is just... not nice to do. It's annoying. I bet it's a nightmare for animators.
No wonder Inverse Kinematics is a thing.
A while ago, I came across this paper that explained in good detail how Inverse Kinematics work. The following image was quite inspiring:
Emphasis on "Limbs with variable length."
So I started having ideas, and the following sketch came out:
Emphasis on "Limbs with variable length."
So I started having ideas, and the following sketch came out:
The green diamond represents the Hip box. In the current implementation, if I move it, it's like I'm dragging the whole character around. Same happens if I rotate it.
The yellow lines represent the "bones" as they are. They follow the parenting scheme shown earlier. They don't stretch: they just rotate around their pivot points.
The blue lines are new limbs, "stretchy" limbs. They also rotate.
To achieve the IK effect I need, I am going to allow both feet and hands to be "frozen" in space. This is represented by the orange circle in the picture above, where I chose to freeze ("ground") the Right Foot. Once a part is grounded/frozen, moving the Hip will keep the Right Foot in its place, while everything else will follow the parent as normal.
For that, I will have to change the Bone Tree to the following:
- Hip
- Abdomen
- Chest
- Left Stretchy Arm
- Left Upper Arm
- Left Lower Arm
- Left Hand
- Right Stretchy Arm
- Right Upper Arm
- Right Lower Arm
- Right Hand
- Neck
- Head
- Left Stretchy Leg
- Left Upper Leg
- Left Lower Leg
- Left Foot
- Right Stretchy Leg
- Right Upper Leg
- Right Lower Leg
- Right Foot
The above Tree may look "wonky" as there are some loose ends. Namely, the lower arms and legs. They no longer have hands and feet as children.
Without proper coding, the above tree would allow a pose where the hands/feet would be disconnected from their limbs. Since I'm not doing a Mortal Kombat game with "Fatalities" and stuff, I need hands and feet to stay connected.
If no parts are frozen, nothing changes. Once the Hip box is moved/rotated, everything moves and rotates along with it.
Let's isolate one limb to understand how it goes when a hand or foot is frozen:
- Stretchy Leg
- Upper Leg
- Lower Leg
- Foot (frozen)
Where:
- Stretchy Leg: an instance of THREE.Object3D(). This object will be invisible, and will have two socket points, called "Upper Leg" and "Foot". The former is located at the object's pivot point, while the latter is initially located at -Y where Y = total length of the leg (upper + lower). This would position the Foot at the end of the leg.
- Upper Leg and Lower Leg: instances of Bone(), one of the core game objects. It's basically a composition of a THREE.Mesh() with several socket points. When I attach another Bone to a socket, the attached bone becomes a child of that Mesh. In this case, the Lower Leg is the child of Upper Leg.
- Foot: an instance of Bone that will be appended to the Stretchy Leg's socket point called "Foot". Usually, socket points are fixed, but in this particular case, the socket location will be dynamic, although restricted: it will only move along the Y axis. This will be the stretching effect taking place.
So, when I freeze "Foot", whenever I move the Hip box, I need to change the following:
1 - The location of the socket named "Foot".
This involves moving the socket point along its Y axis. To calculate world-space X, the Stretchy Leg object itself will have to be rotated around its pivot. This will create a right triangle, where the final value of Y equals its hypotenuse value. I haven't yet figured out the full formula for this, and I already see a complexity factor as of when the Hip box is moved along the Z axis. I'm not a Math guru, so this will require some study.
2 - The angles of Upper Leg and Lower Leg.
Once the leg stretches as a result of a Hip re-positioning, the Foot will be disconnected from the Lower Leg. So I need to angle the leg segments in a way that the lower end of the lower leg connects back with the foot.
Both legs are children of the Stretchy Leg object. So I just need to find two angles, from each leg segment. This will form a triangle with three known sizes: one is Y, the distance between pivot and socket in the "Stretchy Leg" object; and the other two are fixed values representing the lengths of upper and lower legs. With those three values, I can calculate the two angles I need with this formula. It does sound complex, so I will probably do some more digging to see if I can find a simpler, faster formula.
Once I get the two angles right, the end of the lower leg will connect back with the pivot of the foot.
There's an additional approach I need to research about which involves converting local to world coordinates. ThreeJS offers helpers for those. Basically, I save the world coordinates (converting from local to world) of the "Foot" socket before the Hip moves, then re-apply them (converting from world to local) after the Hip is moved. This would, in theory, keep the feet at its original place. Then it would just be a matter of making the leg armatures reach the newly positioned socket point.
Either way, if I end up doing this "stretchy limbs" thing, defining a pose for the arms and legs would actually become quite easier:
- Rotating the stretchy limb around the Y axis would "point the elbow/knee" into a given direction.
- Stretching and squeezing the new limb would "bend" the knee/elbow to a relative value, where 100 would be "totally stretched out" and 0 would be "totally bent".
And the best thing is, I still get to keep the old behavior. I just don't need to freeze anything.
The whole thing however, as far as formulas go, is starting to sound a bit too complex.
When something starts getting too complex, I tend to step back and rethink about it.
I really, really like to keep things simple. Sometimes, simplicity comes from thinking outside the box. Or, being clever. Sometimes I pull some good stuff off, but I'm not usually that clever. Not in regards of Math at least. I'm okay at Math. I probably should be better in this field of work.
Or maybe it's supposed to be complex. It's Math after all.
Bottom line is, between experiencing a horrible time making poses, and figuring out a way to make IK work -- as complex as it needs to be -- I will probably stick with the latter. By the chance this project actually works down the line and I end up getting help from outside animators, they won't hate me.


No comments:
Post a Comment