Inigo Quilez   ::     ::  

IK stands for "Inverse Kinematics" and it's a complex topic, but this article talks about one particular simplifcation, the most extreme of the simplifications possible in fact, that allows for an analytic solution to the problem. Under such simplification the problem reduces to the following: we have a point fixed in space and two pieces or bones of known lengths chained together and joing at a point x, such that the first piece of length r1 connects the fixed point to the joing point x, and the second piece of length r2 connects x with a target location p. The goal here is to figure out where x is in space (ie, its coordinates) given we only know p (ie, its coordinates) and the lengths r1 and r2.


The basic IK setup


Unfortunately, if you look for information on how to solve this you'll find people solving the problem (ie, finding x) with trigonometry. I think this is unfortunate for multiple reasons, one of which is that we can use vectors and geometry instead to solve the problem in a simpler and more elegant way. I haven't seen this method before, but I wouldn't be surprised if other people derived it too the way I've done here. But regardless, I'm writing this article about this particular method of solving IK here because most probably you won't bump into it otherwise. So here it is:


Solution


The first thing we are going to do is to focus on identifying where the solution x must be in space. I'll remove the axis from the diagram above to make it cleaner and draw those two circles, so we can more easily see that x must be r1 units from the origin, by definition, so it must lay in a circle centered at the origin. It must also be r2 units away from p, so it must also belong to a circle centered at p and radius r2:



Thinking of the solution this way makes it immediately obvious that actually two solutions exist for x, since the two circles intersect at two different locations. Now, in order to find such intersections, let's express the diagram with equations, one for each circle:

|x|² = r1²
|x-p|² = r2²


Now we can expand the binomial in the second equation to get

|x|² - 2(xp) + |p|² = r2²

And then we can replace |x|² from the first equation into this one,

r1² - 2(xp) + |p|² = r2²

and rearrange things to get

(xp) = ½( r1² - r2² + |p|²)

This is our first bit of information. It's still one equation with two variables (the two coordinates of x), so we need to extact more information from the geometry before we can solve for x. And that information is that the line connecting the two solutions of x is perpendicular to the line going from the origin to p. Or in other words, there's a point q in that line that is exactly halfway between the two solutions, and which we can find by projecting x into the line that connects the origin and p.



So, let's compute q as a projection, through a dot product:

q = p (xp) / |p

Now we can take the expression we computed for (xp) earlier and substitute it here to get:

q = p [ ½ + ½(r1² - r2²) / |p|²]  (for. 1)

This is great, we are getting closer. To make it there, looking at the diagram we see that we can express x as

x = q ± s q  (for. 2)

for some scalar s, where q⊥ is the perpendicular to q. Basically s is the offset in the direction |q| from q to x, measured in units of |q|. Luckily we can find s easily by injecting this description of x into the very first equation, that of the circle of radius r1:

|x|² = r1²

to get:

|q ± s q|² = r1²

Then we expand the binomial:

|q|² ± 2s(qq) + s²|q|² = r1²

and continue by noting that the dot product between q and q is zero, for they are perpendicular. And we also note that the length of q is the same as the length of q by definition, so we can simplify the above equation to:

|q|² (1 + s²) = r1²

And finaly we solve for s:

s = ±√r1²/|q|² - 1  (for. 3)

Here the two solutions of the square root represent the two possible locations of x. And of course, if the argument of the square root is negative it means the two circles don't intersect an our problem as no solution, ie, we moved p far enough that it's impossible to connect it to the origin with the two pieces of length r1 and r2 that we have. In that case we can either signal to the called that no solution exist, or we can force s to be non-negative anyways, which translates into stretching r1 and r2, which can be an okey solution when the stretching is minimal and happens only because of minuscule precision problems in our floating point computations.



Code


The code is a simple transcription of the formulas 1, 2 and 3 above:

vec2 solve( vec2 p, float r1, float r2 ) { vec2 q = p*(0.5+0.5*(r1*r1-r2*r2)/dot(p,p)); float s = r1*r1/dot(q,q)-1.0; s = max(s,0.0); //if( s<0.0 ) return vec2(-inf); // or we could instead signal no solution return q + vec2(-q.y,q.x)*sqrt(s); }

In the implementation above I'm choosing the positive square root but you probably will want to be flexible and pick the one that makes most sense for your particular configuration.

Anyhow, we can optimize this code a bit by using some algebra, so that we can rewrite the above code with a single division:

vec2 solve( vec2 p, float r1, float r2 ) { float h = dot(p,p); float w = h + r1*r1 - r2*r2; float s = max(4.0*r1*r1*h - w*w,0.0); return (w*p + vec2(-p.y,p.x)*sqrt(s)) * 0.5/h; }

Lastly, here's the code running live in Shadertoy for reference, with some mouse interactivity so you can see how the system responds to different configurations: https://www.shadertoy.com/view/ldlGR7





Conclusion


As you can see, we solved the problem by reasoning geometrically without involving trigonometry, and our implementation also doesn't make use of trigonometric functions. Generally, as I have expressed other times, if you find yourself using trigonometric functions to solve some 2D or 3D geometrical problem, you are probably doing things in a less elegant and efficient way. This of course is a matter of opinion, but now you've heard mine :).

In any case, this IK formulation is very useful to do simple tasks when animating articulated objects, such as the legs of this animated creature in the following Shadertoy example: https://www.shadertoy.com/view/Mss3zM (click Play to animate it, and its title to see the source code)