Axis-angle rotation: build Rodrigues’ formula from three vector terms

Rotate a vector around any nonzero axis. Normalize the direction, follow Rodrigues’ parallel and perpendicular terms, and understand the equivalent descriptions at zero and 180 degrees.

By 12 min read

What you will learn

  • Normalize a nonzero rotation axis and apply a signed angle with a stated convention.
  • Calculate the parallel, cosine, and cross-product terms of Rodrigues’ formula.
  • Connect the vector formula to a three-by-three rotation matrix.
  • Explain why zero-angle and half-turn rotations have nonunique axis descriptions.

Before you start

Rotating (1, 0, 1) by 90° around the z axis gives (0, 1, 1). The z part stays put while the x-y part turns. Rodrigues’ rotation formula applies that same idea to an axis pointing in any direction.

You will split the vector into pieces, calculate their new directions, and add them back together. This makes the formula useful beyond a memorized three-by-three matrix.

Describe one turn by its axis and angle

An axis-angle representation specifies a unit direction u and an angle θ. Every proper three-dimensional rotation can be represented this way. The axis is a line through the origin, and u gives that line an orientation.

This lesson uses active rotations, column vectors, and a fixed right-handed orthonormal frame. The geometric vector moves, while its old and new coordinates use the same frame. Positive θ follows the right-hand rule around u: point your right thumb along u and curl your fingers in the turning direction.

For u = (0, 0, 1), a positive quarter-turn carries +x toward +y. The rotation-matrices lesson uses the same convention. A coordinate-frame change is a separate operation, with source and destination labels.

The pair describes the resulting orientation change, not the history of turns used to reach it. Modern Robotics introduces this representation as a unit axis combined with an angle.

Normalize the axis before rotating

Convert an entered direction a to a unit axis:

u = a / ‖a‖₂, provided ‖a‖₂ > 0

The directions (0, 0, 1) and (0, 0, 2) produce the same u. Axis magnitude does not double the angle. Using the unnormalized direction directly in a formula that assumes unit length can produce a transformation that stretches vectors.

The zero vector has no direction and cannot be normalized. The experiment reports that input as invalid and requires a nonzero axis, even at θ = 0. Any chosen unit axis works for the identity; the zero vector cannot supply one.

The controls show degrees, while the trigonometric functions in code take radians. Convert with θ_rad = θ_deg × π/180. The unit axis is dimensionless and uses the same frame as the input vector.

Separate the three vector terms

First project v onto the unit axis:

v∥ = u(u · v)
v⊥ = v − v∥

The parallel part stays fixed. The perpendicular part lies in the plane that turns around u. Within that plane, u × v points a positive quarter-turn from v⊥ and has the same length as v⊥.

Rotate in that plane with a cosine-and-sine combination:

v′ = v∥ + v⊥ cos θ + (u × v) sin θ

The experiment calls these the parallel term, cosine term, and cross term. Its cosine term uses v⊥, excluding the unchanged axial part. Substituting the definitions gives the common form of Rodrigues’ formula:

v′ = v cos θ + (u × v) sin θ + u(u · v)(1 − cos θ)

Both forms give the same result, although they group the terms differently. The University of Illinois derives this formula by rotating within the perpendicular plane. Review the cross product if its sign or magnitude is unfamiliar.

Calculate a quarter-turn and a diagonal turn

Use a = (0, 0, 2), v = (1, 0, 1), and θ = 90°. Normalization gives u = (0, 0, 1), and u · v = 1.

QuantityValue
Parallel part v∥(0, 0, 1)
Perpendicular part v⊥(1, 0, 0)
Cross product u × v(0, 1, 0)
Cosine term, since cos 90° = 0(0, 0, 0)
Cross term, since sin 90° = 1(0, 1, 0)
Sum v′(0, 1, 1)

Input and output both have length √2 and signed projection 1 onto u. The axial part survives while the perpendicular part turns through a quarter-circle.

Now use u = (1, 1, 1)/√3, θ = 120°, and v = (1, 0, 0). The three terms are:

  • Parallel: (1/3, 1/3, 1/3).
  • Cosine: (−1/3, 1/6, 1/6), using cos 120° = −1/2.
  • Cross: (0, 1/2, −1/2), using sin 120° = √3/2.

Their sum is (0, 1, 0). This rotation cycles the basis directions: x goes to y, y to z, and z to x. Its axis does not align with a coordinate axis.

Inspect the terms and their projections

The initial state reproduces the z-axis quarter-turn. The three terms connect head to tail in each projection, ending at the rotated vector.

Interactive experiment

Build a turn from three vector terms

Choose an axis direction and angle, then inspect the preserved parallel part and the two terms that turn its perpendicular part.

0
0
2
90°

The entered direction is normalized to unit length. At least one component must be nonzero. Positive angles follow the right-hand rule about that unit axis, through the origin of a fixed right-handed frame.

  • Long blue dashes: rotation axis
  • Solid green: parallel term
  • Short black dashes: cosine term
  • Solid amber: cross term

The open circle marks the input; the filled dot marks the output. An open square ends the parallel term. Overlapping or zero-length projected terms remain separate in the numerical readouts.

XY projection

XY projectionThis view drops z. Input z is 1.000; output z is 1.000. The three terms connect head to tail to reach the filled output dot. Both views use the same coordinate scale.-1-1011xy
This view drops z. Input z is 1.000; output z is 1.000. The three terms connect head to tail to reach the filled output dot. Both views use the same coordinate scale.

XZ projection

XZ projectionThis view drops y. Input y is 0.000; output y is 1.000. The three terms connect head to tail to reach the filled output dot. Both views use the same coordinate scale.-1-1011xz
This view drops y. Input y is 0.000; output y is 1.000. The three terms connect head to tail to reach the filled output dot. Both views use the same coordinate scale.
Axis length
2.000
Unit axis
(0.000, 0.000, 1.000)
Rotated vector
(0.000, 1.000, 1.000)
Parallel term
(0.000, 0.000, 1.000)
Cosine term
(0.000, 0.000, 0.000)
Cross term
(0.000, 1.000, 0.000)
Input length
1.414
Output length
1.414
Axis projection before
1.000
Axis projection after
1.000
Recovered input
(1.000, 0.000, 1.000)
Rotation matrix R
0.000-1.0000.000
1.0000.0000.000
0.0000.0001.000

The signed projection on the unit axis and the full three-dimensional length stay unchanged.

Parallel term = u(u · v); cosine term = (v − u(u · v)) cos θ; cross term = (u × v) sin θ. Axis projection is the signed scalar u · v. Recovered input applies Rᵀ to the output. Values display three decimals while calculations retain their precision.

Both views use the same coordinate scale. The xy view omits z; the xz view omits y. A term can appear as a point because all of its change occurs along the omitted coordinate.

Read the full three-coordinate values when terms overlap. Axis projection before and after give the signed scalar u · v. That value remains unchanged by this rotation.

Choose 120° about the diagonal to reproduce the second calculation. With Input parallel to axis, the perpendicular and cross terms vanish, so the vector stays fixed for every angle. Zero vector also stays zero, while the axis still defines a rotation of other vectors.

Starting from the default, set Axis z to 1. Normalization keeps the same rotation. Set it to zero, leaving all three components zero, and the experiment reports the invalid axis without substituting another direction or retaining a stale output.

The axis controls use half steps from −2 to 2. Angles use 15° steps from −180° to 180°. Coordinates display three decimals while calculations retain their precision.

Build the skew matrix with the correct sign

Define K so that multiplying by it computes u × v:

K = [u]× =
[0, −u_z, uᵧ;
u_z, 0, −uₓ;
−uᵧ, uₓ, 0]
Kv = u × v

K is skew-symmetric: Kᵀ = −K. For u = (0, 0, 1), K(1, 0, 0) = (0, 1, 0). Reversing the cross-product order reverses that sign and the intended positive turn.

Rodrigues’ matrix form is:

R = cos θ I + sin θ K + (1 − cos θ)uuᵀ
R = I + sin θ K + (1 − cos θ)K²

For unit u, K² = uuᵀ − I, which makes the expressions equivalent. Applying R to a column vector recovers the vector formula. For the z unit axis, R becomes the familiar z-axis rotation matrix.

With θ in radians, the second expression also equals exp(θK), the matrix exponential. Modern Robotics derives this form from constant angular velocity. The experiment evaluates the closed formula directly.

Check what the rotation preserves

The parallel part is perpendicular to the plane containing the other two terms. Within that plane, v⊥ and u × v are perpendicular and have equal lengths. Using cos²θ + sin²θ = 1 gives:

‖v′‖₂² = ‖v∥‖₂² + ‖v⊥‖₂² = ‖v‖₂²
u · v′ = u · v

The full matrix satisfies RᵀR = I and det(R) = +1. Its transpose undoes the turn: R(u, θ)ᵀ = R(u, −θ). The experiment uses that transpose to calculate Recovered input.

The axis itself stays fixed: Ru = u. One unchanged vector parallel to u does not make R the identity; other vectors can still turn.

For points rotated about an axis through a different pivot c, subtract c, rotate, then add c back: p′ = c + R(p − c). Homogeneous transformations combine those translations with the rotation.

Recognize equivalent descriptions

Reversing both signs leaves the rotation unchanged: R(−u, −θ) = R(u, θ). Adding any integer multiple of 360° to θ also gives the same final rotation.

Two boundary cases matter:

  • Zero angle: R is the identity for every unit axis. The identity has no preferred axis.
  • 180°: R = 2uuᵀ − I. Replacing u with −u leaves that matrix unchanged; its axis line remains meaningful while the sign is ambiguous.

For a principal angle strictly between 0° and 180°, the complete rotation determines an oriented unit axis. MTEX documents the identity and half-turn ambiguities. These uniqueness statements require the complete rotation. One input-output vector pair can leave multiple rotations possible.

For example, a half-turn about (1, 1, 0)/√2 sends (1, 0, 0) to (0, 1, 0). The opposite axis gives the same full matrix. At a general angle such as 90°, reversing the axis alone gives the inverse rotation.

Combining the pair as φ = θu, with θ in radians, gives a rotation vector, also called exponential coordinates. Its length is |θ|. The zero rotation vector encodes identity, but its direction cannot supply a unique axis; the experiment separately requires an axis and angle.

Euler angles describe a sequence of named-axis turns. Unit quaternions encode a rotation using the axis and half-angle. Each representation needs a stated convention and has rules for equivalent coordinates.

Reproduce the calculation in Python

This standard-library example normalizes the entered axis and evaluates the decomposed formula. It rejects a zero or nonfinite axis. The inputs below contain three finite coordinates; the formatter only rounds what you see.

from math import cos, sin, radians, hypot, isfinite


def unit_axis(axis):
    if not all(isfinite(x) for x in axis):
        raise ValueError("Axis components must be finite")
    maximum = max(abs(x) for x in axis)
    if maximum == 0:
        raise ValueError("Axis must be nonzero")
    scaled = tuple(x / maximum for x in axis)
    length = hypot(*scaled)
    return tuple(x / length for x in scaled)


def rotate(vector, axis, degrees):
    u = unit_axis(axis)
    theta = radians(degrees)
    projection = sum(a * b for a, b in zip(u, vector))
    parallel = tuple(x * projection for x in u)
    cross = (u[1] * vector[2] - u[2] * vector[1],
             u[2] * vector[0] - u[0] * vector[2],
             u[0] * vector[1] - u[1] * vector[0])
    return tuple(parallel[i] + (vector[i] - parallel[i]) * cos(theta)
                 + cross[i] * sin(theta) for i in range(3))


def show(vector):
    clean = (0.0 if abs(x) < 0.0005 else x for x in vector)
    return "(" + ", ".join(f"{x:.3f}" for x in clean) + ")"


axis, vector = (0, 0, 2), (1, 0, 1)
u = unit_axis(axis)
output = rotate(vector, axis, 90)
print("Unit axis:", show(u))
print("Rotated:", show(output))
print(f"Output length: {hypot(*output):.3f}")
print(f"Axis projection: {sum(a * b for a, b in zip(u, output)):.3f}")
print("Recovered:", show(rotate(output, axis, -90)))
print("Diagonal 120:", show(rotate((1, 0, 0), (1, 1, 1), 120)))

Expected output:

Unit axis: (0.000, 0.000, 1.000)
Rotated: (0.000, 1.000, 1.000)
Output length: 1.414
Axis projection: 1.000
Recovered: (1.000, 0.000, 1.000)
Diagonal 120: (0.000, 1.000, 0.000)

For numerical work near zero angle, 1 − cos θ = 2 sin²(θ/2) avoids subtracting nearly equal values. Recovering an axis from a measured matrix also needs care near 0° and 180°. This experiment constructs rotations from a supplied axis and does not implement that inverse conversion.

Try it yourself

Exercise 1. Rotate v = (1, 1, 0) by +90° about the entered axis a = (0, 2, 0). Find the unit axis, the three terms, the output, and its signed projection onto the axis.

Show solution 1

The unit axis is u = (0, 1, 0). Since u · v = 1, the parallel term is (0, 1, 0). The perpendicular part is (1, 0, 0), and its cosine term vanishes at 90°.

The cross term is u × v = (0, 0, −1). Adding gives v′ = (0, 1, −1). The signed axial projection stays 1, and the length stays √2.

Exercise 2. Use axis u = (1, 1, 0)/√2 and angle 180°. Where does v = (1, 0, 0) go? What changes if you reverse the axis sign, and why does a zero-angle rotation have more axis freedom?

Show solution 2

The parallel part is (1/2, 1/2, 0), and the perpendicular part is (1/2, −1/2, 0). At 180°, cosine is −1 and sine is zero. The sum is (0, 1, 0).

Reversing the axis leaves 2uuᵀ − I unchanged, so it gives the same complete half-turn. At zero angle, the matrix is I for every unit axis, allowing any axis line as well as either sign.

Continue with unit quaternions to compose rotations using the same axis and angle. Keep the frame and sign conventions alongside the new coordinates.

Sources and further study