Finding Elbow Positions


One of the glaring issues with the gunswap animator was the lack of elbow movement. It's fairly easy to calculate the hand positions of a juggler throughout a pattern; they are well defined by the dwell path of the pattern, and when a prop is in flight the hand is just moving towards the next catch. The elbows are a bit trickier though. My goal was to define a function that will return the elbow position given the hand position.

This problem is called "inverse kinematics" and it is fairly well studied. However, a google search on the topic yields papers like this. As someone who got a C in linear algebra over 7 years ago, I felt a bit in over my head.

Since Juggling Lab has already solved this problem, I decided to sift through their source code. I've used Juggling Lab as a reference for a lot of parts of this project - the bulk of my siteswap syntax is based on the Juggling Lab syntax. After a little searching I found the section of code I was looking for.

Unfortunately I was having a hard time following the Juggling Lab code, and didn't want to just start copy/pasting things into gunswap without understanding them. So I sought help from the main developer of Juggling Lab, Jack Boyce. Here's what he had to say:

If you think about the connection between the shoulder and the hand as a linkage of two stiff pieces (upper arm and lower arm), then the position of the elbow becomes constrained once the hand and shoulder locations are specified. There is only one degree of freedom left, the rotation of the entire arm around the shoulder-hand axis. (Imagine a chicken flapping its wings to show the motion I'm talking about.)"

Up to this point I had been trying to work out complicated solutions considering the individual rotation angles of the shoulder and elbow. However, thinking about the the problem with one degree of freedom - the "chicken wing angle" - was the key to unlocking the solution.


Below is how I came up with a function $getElbowPosition(\mathbf{S},\mathbf{H},l,\omega,hand)$ that returns elbow coordinates given inputs for the shoulder position $\mathbf{S}$, hand position $\mathbf{H}$, forearm/bicep length $l$, "chicken wing" angle $\omega$, and $hand$. The hand input is just 0/1 (left/right) indicating which direction the chicken wing angle should rotate. A higher chicken wing angle means your elbows are lifted higher. A chicken wing angle of 0 means the plane created by your shoulder/elbow/hand is parallel to the y axis. The juggler in Fig 1. has a high chicken wing angle.

Fig 1.

Fig 2.

Fig 3.

The first step is to transform $\mathbf{H}$ to a coordinate system where $\mathbf{S}$ is at the origin. This gives a new hand position $\mathbf{H'}$.

$$\mathbf{H'} = \mathbf{H} - \mathbf{S}$$

Next we have another transformation. $\mathbf{H''}$ is the hand position in a coordinate system rotated along the y-axis (the dashed lines in Fig. 2). $\mathbf{H''}$ is a simpler vector to work with because it has a 0 z component.

$$\mathbf{H''} = \langle \sqrt{{H'}_{x}^2 + {H'}_{z}^2} , {H'}_{y} , 0 \rangle$$

Now let's define $\theta$ which will be used to go back to $\mathbf{H'}$ from $\mathbf{H''}$, this will be useful later on. Note that in the code I actually use Math.atan2 since Math.atan only goes from -pi/2 to pi/2 and Math.atan2 determines the angle based on the quadrant the input coordinates are in.

$$\theta = \arctan{\frac{{H'}_{z}}{{H'}_{x}}}$$

$h$ is the distance from the shoulder/hand axis to the elbow. This is the height of an isosceles triangle with sides of length $l$.

$$h = \sqrt{l^2 - \frac{1}{4}{|H'|}^2}$$

Now we find unit vectors $\hat{u_1}$ and $\hat{u_2}$ that are both orthogonal to each other and the shoulder/hand axis. These are simple to find because ${H''}_{z} = 0$.

$$\hat{u_{1}} = \frac{\langle {H''}_{y}, -{H''}_{x}, 0 \rangle}{|H'|}$$ $$\hat{u_{2}} = \langle 0, 0, 1 \rangle$$

Here is where the chicken wing angle comes into play. This is the position of the elbow as defined by an equation for a circle around the shoulder/hand axis.

$$\mathbf{E''} = \frac{\mathbf{H''}}{2} + h\hat{u_{1}}\cos{\omega} + h\hat{u_{2}}\sin{\omega}$$

Now we transform $\mathbf{E''}$ to $\mathbf{E'}$ using $\theta$ which we solved for above.

$$\mathbf{E'} = \langle {E''}_{x}cos{\theta} - {E''}_{z}sin{\theta}, {E''}_{y}, {E''}_{x}sin{\theta} + {E''}_{z}cos{\theta} \rangle$$

Finally we transform $\mathbf{E'}$ to $\mathbf{E}$ which is our final result for the elbow position.

$$\mathbf{E} = \mathbf{S} + \mathbf{E'}$$