Pose a figure by angles, not by positions
A field figure is a set of primitives with endpoints, and the fastest way to animate it is to write down where each endpoint sits in pose A, where it sits in pose B, and blend. It hits both poses perfectly. Everything between them is wrong.
Watch the elbow marker against the amber circle, which is drawn at the upper arm's actual length. On the right it rides the circle the whole way, because the bone is a constant and only its angle changes. On the left it cuts inside, the arm visibly shortens through the middle of the swing, and it snaps back to correct at each end.
Why it shortens
Interpolating between two points travels the straight line between them, and the
straight line between two points on a circle is a chord. The bone is the
radius. Halfway through a swing of angle A, the chord's midpoint sits at
L·cos(A/2) from the joint, so the bone reads as shorter by
1 − cos(A/2).
Note what is NOT in that formula: the bone's length. The error is a pure fraction of it, so the table below is the same for a four-head mascot and a realistic adult, and changing the figure cannot invalidate it. That is worth checking rather than assuming, because most of the constants around a demo like this one are figure-dependent.
The formula is the whole guide to when this matters:
- 20° of swing: 1.5% short. Invisible. A breathing idle gets away with it.
- 60°: 13%. Visible as a soft rubberiness you cannot name.
- 90°: 29%. Obviously broken.
- 180°: 100%. The limb passes through the joint.
Which explains the usual discovery path. Small motions look fine, the method gets adopted, and then the first big swing exposes it in a build that is already animating forty things the same wrong way.
What forward kinematics actually is
Nothing more than: a joint's transform is its parent's transform followed by its own rotation. Positions come out of the chain; they are never inputs.
elbow = shoulder + rotate(vec2(upperLen, 0), aShoulder);
hand = elbow + rotate(vec2(lowerLen, 0), aShoulder + aElbow);
The angles add as you go down the chain, which is the part worth saying out loud: a child's stored angle is relative to its parent. That is what makes a wrist stay put when the shoulder moves, and it is why animation data is angles.
The consequences that are not the shrink
- Blends open up. A
sminjoint sized for the correct bone length gaps when the bone contracts underneath it. - Contact points slide. A hand placed on a hip in both poses will not be on the hip in between.
- Anything attached inherits the error: a sleeve, a weapon, a trail spawned at the hand.
- Blending three poses is worse than two, because the errors do not cancel and the shortest path is not the average path.
The one place positions are correct
Inverse kinematics is the opposite ask: you know where the hand must be, and you solve for the angles that put it there. That is still angles at the end. A two-bone IK solution is closed form and short, so reach-for-a-thing does not need a library. The rule stands either way: the figure is stored as angles and the positions are derived, never the reverse.
Rules of thumb
- Store poses as angles. Derive positions from the chain every frame.
- A child's angle is relative to its parent, and the angles accumulate down the chain.
- Bone lengths are constants. If a length changes during an animation, something is wrong.
- Estimate the error as
1 − cos(A/2)before deciding a shortcut is safe. - Debug by drawing a circle of the bone's length around its parent. The joint must stay on it.
One email when something new goes up. No newsletter, no schedule, nothing else.
Double opt-in, so watch for a confirmation email. Unsubscribe any time.