Two-Bone Inverse Kinematics in 2D: Law of Cosines, Reachability, and Elbow Flips
Two-Bone Inverse Kinematics in 2D: Law of Cosines, Reachability, and Elbow Flips
Search
Ask the AI

Two-Bone Inverse Kinematics in 2D: Law of Cosines, Reachability, and Elbow Flips

Forward kinematics computes an end-point position from joint angles. Inverse kinematics (IK) computes joint angles from a target position. When an animator drags a hand, foot, or weapon, the software is solving this inverse problem. A 2D upper arm and forearm do not need iterative optimization: the law of cosines gives a stable analytic solution.

1. Is the target reachable?

Let the two bone lengths be a and b, the shoulder be S, the target be T, and d = ||T - S||. A target is reachable exactly when:

|a - b| <= d <= a + b

If d > a + b, the chain can only point toward the target at full extension. If d < |a-b|, the target lies inside the inner circle that the folded chain cannot reach. Production code clamps d to this interval instead of passing an invalid value to acos.

2. Solve the elbow with the law of cosines

The shoulder, elbow, and target form a triangle. The elbow interior angle gamma satisfies:

cos(gamma) = (a^2 + b^2 - d^2) / (2ab)
gamma = acos(clamp(cos(gamma), -1, 1))

A rig usually stores the forearm’s rotation relative to the upper arm as pi - gamma. The sign depends on whether screen y grows downward and which elbow solution is selected.

3. Solve the shoulder and preserve elbow direction

The target direction is phi = atan2(Ty-Sy, Tx-Sx). The shoulder triangle angle is:

alpha = acos((a^2 + d^2 - b^2) / (2ad))
shoulder = phi +/- alpha

The plus and minus choices are mirror solutions: elbow-up and elbow-down. A rig must preserve a pole direction or elbow sign. Otherwise, the elbow can flip suddenly when the target crosses the bone line.

4. Numerical example

Use a=150, b=125, and target distance d=200:

cos(gamma) = (150^2 + 125^2 - 200^2) / (2*150*125)
           = -0.05
gamma      = acos(-0.05) = 92.87°

cos(alpha) = (150^2 + 200^2 - 125^2) / (2*150*200)
           = 0.78125
alpha      = 38.62°

Adding target direction phi gives the shoulder angle and elbow position. Drag the blue target below and change both bone lengths. Move the target beyond the reachable circle to see the solution clamp to full extension.

Geometry · IK

Two-Bone Inverse Kinematics

Use the law of cosines to solve shoulder and elbow angles, unreachable targets, and elbow flips.

Drag the blue target

5. Boundary handling for analytic IK

  • Floating-point error: clamp every cosine to [-1, 1] before calling acos.
  • Near full extension: the two solutions converge, so preserve the previous frame’s elbow sign.
  • Target crosses the shoulder: use atan2, not atan(y/x), to preserve quadrant information.
  • Joint limits: solve analytically, constrain shoulder and elbow angles, and then recompute the end point.

6. Boundary cases and expected results

With a=150 and b=125, the reachable distance is [25, 275]. Targets on both sides of that interval test clamping, floating-point protection, and elbow continuity together.

Raw distance dSolve distanceExpected poseRequired check
300275Fully extended toward the targetThe end stays on the outer circle and cosine does not exceed 1
200200Elbow interior angle about 92.87°Both mirror solutions reach the same end point
2525Maximum folded boundaryNo jump from a small denominator
025Stable fold along the previous directionDo not compute the shoulder offset from zero distance
Target crosses the bone lineUnchangedSaved elbow sign selects the sideNo mirror flip between adjacent frames

7. A numerically stable solver outline

delta = target - shoulder
raw_d = length(delta)
direction = raw_d > eps ? delta / raw_d : previous_direction
d = clamp(raw_d, abs(a - b) + eps, a + b - eps)

cos_elbow = clamp((a*a + b*b - d*d) / (2*a*b), -1, 1)
cos_offset = clamp((a*a + d*d - b*b) / (2*a*d), -1, 1)

phi = atan2(direction.y, direction.x)
shoulder_angle = phi + elbow_sign * acos(cos_offset)
elbow_angle = elbow_sign * (pi - acos(cos_elbow))

eps does not change a visible pose; it prevents division by zero at a singular point. Re-run forward kinematics after solving and record ||end-target_clamped||. A reachable target should leave only floating-point error. An unreachable target should leave exactly the clamped distance, not unexplained drift.

The snap at full extension: softening the reach limit

Clamping d into the reachable interval keeps the mathematics safe, but it leaves an obvious visual artefact: the moment the target passes maximum reach, the arm instantly locks straight and stops. Reaching for something distant, the elbow snaps rigid on one frame and nothing moves after that however much further the target goes. Real limbs do not behave that way — they approach straight asymptotically and always keep a little slack.

The standard remedy is soft IK: over the final stretch before the limit, compress the input distance with an exponential so it never reaches the bound. With total length L = a + b and a softening width s:

if d < L - s:
    d_soft = d                                  # far from the limit, pass through
else:
    d_soft = L - s * exp(-(d - (L - s)) / s)

At d = L - s this function matches the straight segment in both value and first derivative, so there is no visible kink; as d grows without bound, d_soft approaches L without ever reaching it. Feed d_soft into the law of cosines instead of d and the elbow retains a slight bend permanently.

s is usually 3% to 8% of total length. Too large and the arm falls short within its normal working range, reading as unable to reach; too small and the softening is imperceptible, leaving the snap intact.

The same treatment applies to the inner bound, though it is rarely needed — characters seldom fold a hand fully into the shoulder.

Declare which space the target lives in

Every formula above assumes the target T and the shoulder S share a coordinate system. Which system that is turns out to determine whether the motion is right at all, and it is routinely left implicit.

For a foot planted on the ground, the target belongs in world space: the body moves forward and the foot stays put until the next step. That is precisely how foot sliding is avoided.

For a hand resting on a hip, the target belongs in character space: as the character walks, the hand travels with the body. Store that target in world coordinates and the moment the character moves the hand stays behind, stretching out as if pinned to the air.

For a hand on a moving door, the target belongs in the door’s space — neither world nor character.

So an IK target cannot be stored as coordinates alone; it needs its parent space alongside:

{ "target": [120, 40], "space": "world" }        # planted foot
{ "target": [18, -30],  "space": "character" }   # hand on hip
{ "target": [4, 12],    "space": "door_handle" } # hand on a door

Transform the target into the shoulder’s space before solving, then apply the formulas above. Structurally this step is input preparation rather than part of the solve, which is exactly why it tends to get written at each call site and end up inconsistent.

Worth noting: foot sliding, the most common flaw in 2D walk cycles, is almost never a solver error — it is the wrong target space. Confirming that first, before digging into the solver, saves a great deal of time.

Add joint limits and the hand no longer reaches the target

The checklist earlier mentioned clamping angles to the character’s allowed range after solving. That reads as a footnote, but it introduces a decision you cannot avoid making explicitly: once an angle is clamped, the end effector is no longer at the target.

The analytic solution is unique for a given elbow direction, so any modification to the angles it returns necessarily moves the end away from the target. There are three ways to handle it, they look completely different on screen, and there is no default answer:

Accept the error. The hand stops short, leaving a gap to the target. Fine when the target is only a guide — gaze following, for instance — and wrong when the hand is supposed to hold a specific object, because the audience sees the hand separate from the cup.

Move the root. Shift the shoulder, or the whole body, toward the target until the hand can reach. This is what a real body does — you lean when something is out of reach. The cost is that moving the root affects everything else, so the offset needs limiting and smoothing, or the body jitters along with the hand.

Stretch the bones. Allow bone length to vary within a small range, typically under ±10%. Unacceptable in realistic work, extremely common on stylised 2D characters, and nearly invisible to the audience — sleeves and implied muscle deformation provide the cover.

What matters is not which you pick but that you pick one and record it in the rig configuration. The default behaviour is the first (silently accept the error), and that happens to be the one most easily spotted in finished work.

Whichever you choose, record the actual distance between end effector and target after solving. On a reachable, unclamped target it should sit at floating-point magnitude; once it is consistently above zero, some limit is binding — and only then is deciding between adjusting the limit, changing strategy, or reworking the animation an informed decision.

One pixel of target jitter, ten degrees of elbow jitter

Analytic IK has a less intuitive property: it amplifies input noise near the limits. A target that jitters by one pixel can produce joint angles that jitter by more than ten degrees. Seeing why means looking at the derivative of acos.

d/dx acos(x) = -1 / sqrt(1 - x^2)

As x approaches ±1, this derivative diverges. And x is exactly the ratio the law of cosines produces — it approaches 1 as the arm nears full extension and -1 as it nears full fold. In other words, near either boundary, a tiny change in input becomes an enormous change in angle.

That explains several apparently unrelated symptoms: an elbow that twitches nervously as the arm straightens; a target that suddenly feels “slippery” to drag near the edge of the reachable circle; the same animation differing visibly between machines because of last-bit floating-point differences. All are the same amplification.

The soft IK from the previous section incidentally mitigates this at the outer bound, because d_soft never reaches L and so the ratio never reaches 1. That is softening’s second benefit beyond appearance, and the reason it is worth more than plain clamping: clamping parks the value exactly at the point of maximum derivative, whereas softening keeps it permanently at a distance from that point.

If the target itself comes from a noisy source — handwriting input, network replication, a physics simulation — smooth the target position before the IK solve rather than smoothing joint angles after it. Filtering afterwards means the filter is working on an already-amplified signal, so it needs to be aggressive enough to suppress it, and that aggression makes the whole motion sluggish. Handle noise before it gets amplified.

8. Analytic IK versus CCD and FABRIK

Prefer the analytic solution for two-bone chains: its cost is fixed, the result is explainable, and it does not jitter because an iteration budget was exhausted. Longer chains such as spines, tails, and tentacles can use CCD or FABRIK, but they still need reachability limits, joint constraints, and temporal continuity.

9. Next: deform the artwork continuously around the joint

IK changes the bone pose but does not repair layer seams. Linear Blend Skinning and Seam Control binds nearby mesh vertices to both bones and explains white dots, cracks, and joint collapse. The prerequisite is Skeletal Transforms and Forward Kinematics.

Leave a Reply

Scroll down