The Math Behind Joint Angle Calculation (why atan2 works)

In the MATLAB analysis walkthrough I computed joint angles with a one-liner: atan2d(det(v1, v2), dot(v1, v2)). That worked, but why does it give the angle between two limb segments? This post is the math behind that line — and why the two-argument atan2 is the right tool.

The setup: two vectors from the joint

Take a knee angle. From the knee, draw two vectors: one to the ankle (the shank) and one to the hip (the thigh). The joint angle is simply the angle θ between those two vectors.

MATLAB 2D plot of hip, knee, and ankle points with the two limb vectors from the knee
Two vectors from the knee — to the ankle and to the hip. The angle between them is the joint angle.

The determinant = the cross-product magnitude = an area

For two 2D vectors, the determinant equals the area of the parallelogram they span — which is exactly the magnitude of their cross product. And the cross-product magnitude is |v1|·|v2|·sin(θ).

MATLAB 3D plot showing the cross-product vector pointing out of the plane with magnitude 4
The cross product points out of the 2D plane; its length (here 4) equals the parallelogram area (base 2 × height 2).

Why use det instead of a cross-product function? Because MATLAB’s cross needs 3D vectors (X, Y, Z), and our pose data is 2D. The determinant of the two 2D vectors gives the identical value without padding a Z. In the demo it comes out to 4 — the same as the cross-product magnitude.

The dot product = a projection

The dot product of the same two vectors is |v1|·|v2|·cos(θ) — geometrically, one vector projected onto the other. In the demo it comes out to 2.

Putting it together: tan(θ)

Now divide the cross by the dot:

  • cross = |v1|·|v2|·sin(θ)
  • dot = |v1|·|v2|·cos(θ)
  • cross / dot = sin(θ) / cos(θ) = tan(θ)

The magnitudes cancel, leaving tan(θ). So θ = atan2(det, dot). In the demo, cross = 4 and dot = 2, and the formula returns −63.4° — exactly the angle measured directly.

Derivation: dot equals magnitudes times cos theta, cross equals magnitudes times sin theta, so their ratio is tan theta
dot = |a||b|cosθ, cross = |a||b|sinθ, so cross/dot = tanθ — and θ = atan2(cross, dot).

Why atan2, not plain atan?

The ordinary one-argument atan takes only the ratio, so it throws away the sign information: (−, +) and (+, −) both look negative, and (+, +) and (−, −) both look positive. It can’t tell which quadrant you’re in, and it breaks for angles beyond 90°. For example, a hip angle that is really 153° comes back from atan2d correctly as 153°, but plain atan returns 26° — you’d have to patch it with an if statement (180° − angle).

The two-argument atan2(numerator, denominator) keeps the numerator and denominator separate, so it resolves the full range and preserves sign. For biomechanics that sign is meaningful: at the hip, a back swing comes out negative and a forward swing positive, so you can tell them apart directly from the angle’s sign.

Takeaway

That single line, atan2d(det, dot), is doing something clean and robust: it reads the sine of the angle from the determinant, the cosine from the dot product, and combines them in a way that keeps the sign and the full 0–180° (and beyond) range. That’s why it’s the reliable way to compute 2D joint angles — ready to drop into the analysis pipeline.


About the author

Takashi Fukushima — research & development across Sports & Exercise Science, Human Pose Estimation, Computer Vision, and XR.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top