Rotations as a Special Case of Vector Transformations
Rotations, as a class of linear vector transformations1, represent rigid body transformations that change the direction of a vector within a given plane.
Efficient and accurate handling of such transformations poses challenges not only for game and animation engines, but also for simulations and real-time systems, where the limitations of floating-point arithmetic and operation throughput can negatively impact the computational outcomes ([📖She97], [📖Kui99]).
This work introduces vector rotation with a focus on the relations between its two- and three-dimensional formulations.
We begin in the two-dimensional plane by deriving classical rotation matrices from linear combinations in orthogonal bases.
We then generalize this approach by introducing Rodrigues' rotation formula, which provides a compact expression for rotation around arbitrary axes in three-dimensional space. We verify that the standard rotation matrices emerge as special cases from this general form.
Finally, we illustrate the application of vector rotation in the context of embedded coordinate systems within large scene graphs, demonstrating how transformations propagate across hierarchical structures in real-time environments such as video games.
Vector Rotation as a Linear Combination
Given an angle and an orthonormal basis in three-dimensional space, rotating a vector around one of the cardinal axes can be computed with a rotation matrix .
The following matrices define rotations about the , and axes, respectively:
The matrix directly arises from rotating a vector in the two-dimensional -plane. This connection will be established in the following.
In an orthonormal basis with the standard basis vectors , , any vector of unit length can be expressed as
This follows directly by when writing as a linear combination of the two linear independent vectors :
Substituting with , , we have:
or, as a matrix multiplication
(Observe how represent the identity matrix ).
In Figure 1, the angle represents the amount of rotation of around the origin2.
Plot-Code (Python)
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Arc, FancyArrowPatch
fig, ax = plt.subplots(figsize=(6, 6))
# Plot setup
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
ax.set_xticks([-1, 0, 1])
ax.set_yticks([-1, 0, 1])
ax.set_aspect('equal')
ax.grid(True, linestyle=':', linewidth=0.5)
for spine in ax.spines.values():
spine.set_visible(False)
ax.spines['bottom'].set_position('zero')
ax.spines['left'].set_position('zero')
ax.spines['bottom'].set_visible(True)
ax.spines['left'].set_visible(True)
ax.xaxis.set_ticks_position('bottom')
ax.yaxis.set_ticks_position('left')
ax.text(1.05, 0.02, r"$x$", fontsize=14, va='bottom')
ax.text(0.02, 1.05, r"$y$", fontsize=14, ha='left')
# Vecors
origin = np.array([0, 0])
theta = np.radians(45)
cos_theta = np.cos(theta)
sin_theta = np.sin(theta)
opposite = np.array([0, np.sin(theta)])
v1 = np.array([np.cos(theta), np.sin(theta)])
v2 = np.array([1, 0])
ax.quiver(*origin, *v1, angles='xy', scale_units='xy', scale=1, color='r')
ax.quiver(*origin, *v2, angles='xy', scale_units='xy', scale=1, alpha = 0.5, color='r')
offset = 0.01
ax.text(v1[0] - 0.5, v1[1] -0.3, r"$\vec{v'}$", fontsize=12, color='r')
ax.text(v2[0] - 0.2, v2[1] -0.12, r"$\vec{v}$", fontsize=12, color='r')
# COSINE
ax.plot([0, cos_theta], [-0.02, -0.02], color='black', linestyle='--', linewidth=1)
ax.text(cos_theta / 2 - 0.05, -0.1, r"$\cos(\Theta)$", fontsize=10)
# SINE
start = np.array([cos_theta, 0])
end = start + opposite
ax.plot([start[0], end[0]], [start[1], end[1]], color='black', linewidth=1, linestyle='--')
ax.text(cos_theta + 0.05, sin_theta / 2 - 0.05, r"$\sin(\Theta)$", fontsize=10)
# Unit Circle
circle = plt.Circle((0, 0), 1, color='black', linewidth=0.2, fill=False, transform=ax.transData, linestyle='--')
ax.add_patch(circle)
arrow = FancyArrowPatch(
posA=(0.5, 0),
posB=(np.cos(theta)*0.5, np.sin(theta)*0.51),
connectionstyle="arc3,rad=0.2",
arrowstyle='->',
color='blue',
linestyle='--',
mutation_scale=15,
lw=1.5
)
ax.add_patch(arrow)
ax.text(0.5, 0.2, r"$\Theta$", color='blue', fontsize=16)
plt.show()
We claim that
holds for any orthonormal basis, not necessarily the standard basis (which turns out to be just a special case once we have proven the claim).
We require to ensure that has unit length. This condition is immediately satisfied by choosing , as derived in the following proof.
Expressing the linear combination with cosine and sine
Let , , , , .
Claim: The linear combination can be written as and yields a normalized vector :
Proof:
We compute the dot products , to relate the cosines of the angles (between and ) and (between and ), respectively:
Since , we find
We now solve for and . Since , we have
Since and , we have
Solving analogously for , we obtain
We have shown that
and substitute into the linear combination:
From we can derive
and finally rewrite the linear combination as
Dividing by yields the normalized vector . We therefore receive
Verifying the normalization
Under the given assumptions, we verify that , confirming that is normalized:
In order for to hold, it is required that , as given by the assumption.
Substituting and by and , respectively, we confirm that
Determining the angle θ between the vectors
Claim: is the angle between .
This follows trivially since we have already shown that both and are unit vectors. By definition of the dot product, we have:
which is illustrated in Figure 1.
Generalization to arbitrary lengths
One particular result we get from this - and which will be useful when applying rotations in around arbitrary axes - is the fact that, for any perpendicular vectors , where , the linear combination
yields a vector satisfying
Following the previous proof, we now treat as vectors with arbitrary, but equal length, resulting in the (normalized) linear combination
To cancel out the denominators on the right-hand side, we multiply the equation by 3, yielding
Thus, we obtain a vector with the same magnitude as and .
Rotation around arbitrary points
When rotating a point by a given angle, the transformation depends on the chosen center of rotation.
Figure 2 illustrates the point rotated by . Here, is rotated around the origin of the coordinate plane, resulting in , which can be obtained via the matrix-vector product
where4
Plot-Code (Python)
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
from matplotlib.ticker import MultipleLocator
from matplotlib.patches import Wedge
# plot layout
fig, ax = plt.subplots(figsize=(6, 6))
ax.set_xlim(-1, 8)
ax.set_ylim(-1, 8)
ax.set_aspect('equal')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(False)
ax.axhline(0, color='black', linewidth=1.5)
ax.axvline(0, color='black', linewidth=1.5)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))
theta = math.radians(20)
p_x = 6
p_y = 4
pc = 'blue'
r_x = 2
r_y = 3
rc = 'red'
# point
p = np.array([p_x, p_y])
p_len = np.linalg.norm(p)
ax.quiver(0, 0, p_x, p_y, angles='xy', scale_units='xy', scale=1, color=pc, width=0.005, linestyle='--',alpha=0.2)
# center of rotation
r = np.array([r_x, r_y])
#ax.quiver(0, 0, r_x, r_y, angles='xy', scale_units='xy', scale=1, color=rc, width=0.005, linestyle='--',alpha=0.2)
# rotated p
pr = np.array([
p_x * np.cos(theta) - p_y * np.sin(theta),
p_x * np.sin(theta) + p_y * np.cos(theta)
])
ax.quiver(0, 0, pr[0], pr[1], angles='xy', scale_units='xy', scale=1, color=pc, width=0.005, linestyle='--',alpha=0.2)
circle_p = plt.Circle((p_x, p_y), 0.08, color=pc, fill=True)
ax.add_patch(circle_p)
# p, p'
circle_p = plt.Circle((p_x, p_y), 0.08, color='blue', fill=True)
circle_pr = plt.Circle((pr[0], pr[1]), 0.08, color='blue', fill=True)
ax.add_patch(circle_p)
ax.add_patch(circle_pr)
#r
circle_r = plt.Circle((r_x, r_y), 0.08, color=rc, fill=True)
ax.add_patch(circle_r)
arc_radius = 4.2
arc = Arc((0, 0),
arc_radius,
arc_radius,
angle=0,
theta1=np.degrees(np.arctan2(p_y, p_x)),
theta2=20 + np.degrees(np.arctan2(p_y, p_x)),
edgecolor=pc)
ax.add_patch(arc)
# texts
ax.text(p_x + 0.2, p_y + 0.2, 'p', color=pc, fontsize=12)
ax.text(pr [0] + 0.2, pr[1] + 0.2 , 'p\'', color=pc, fontsize=12)
ax.text(1, 1, r'$\theta$', color=pc, fontsize=14)
ax.text(r_x - 0.2, r_y + 0.2, 'r ', color=rc, fontsize=12)
plt.show()
Choosing a different center of rotation
Figure 3 illustrates the rotation around . is computed such that
- the rotation center becomes the origin of all points in the coordinate plane
- rotation is applied
- the rotated point is translated back.
Using , we get the expression:

Plot-Code (Python)
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
from matplotlib.ticker import MultipleLocator
from matplotlib.patches import Wedge
# plot layout
fig, ax = plt.subplots(figsize=(6, 6))
ax.set_xlim(-1, 8)
ax.set_ylim(-1, 8)
ax.set_aspect('equal')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(False)
ax.axhline(0, color='black', linewidth=1.5)
ax.axvline(0, color='black', linewidth=1.5)
ax.xaxis.set_major_locator(MultipleLocator(1))
ax.yaxis.set_major_locator(MultipleLocator(1))
theta = math.radians(20)
p_x = 6
p_y = 4
pc = 'blue'
r_x = 2
r_y = 3
rc = 'red'
# point
p = np.array([p_x, p_y])
p_len = np.linalg.norm(p)
ax.quiver(0, 0, p_x, p_y, angles='xy', scale_units='xy', scale=1, color=pc, width=0.005, linestyle='--',alpha=0.2)
# center of rotation
r = np.array([r_x, r_y])
#ax.quiver(0, 0, r_x, r_y, angles='xy', scale_units='xy', scale=1, color=rc, width=0.005, linestyle='--',alpha=0.2)
# rotated p
pr = np.array([
p_x * np.cos(theta) - p_y * np.sin(theta),
p_x * np.sin(theta) + p_y * np.cos(theta)
])
ax.quiver(0, 0, pr[0], pr[1], angles='xy', scale_units='xy', scale=1, color=pc, width=0.005, linestyle='--',alpha=0.2)
circle_p = plt.Circle((p_x, p_y), 0.08, color=pc, fill=True)
ax.add_patch(circle_p)
# p, p'
circle_p = plt.Circle((p_x, p_y), 0.08, color='blue', fill=True)
circle_pr = plt.Circle((pr[0], pr[1]), 0.08, color='blue', fill=True)
ax.add_patch(circle_p)
ax.add_patch(circle_pr)
#r
circle_r = plt.Circle((r_x, r_y), 0.08, color=rc, fill=True)
ax.add_patch(circle_r)
arc_radius = 4.2
arc = Arc((0, 0),
arc_radius,
arc_radius,
angle=0,
theta1=np.degrees(np.arctan2(p_y, p_x)),
theta2=20 + np.degrees(np.arctan2(p_y, p_x)),
edgecolor=pc)
ax.add_patch(arc)
# texts
ax.text(p_x + 0.2, p_y + 0.2, 'p', color=pc, fontsize=12)
ax.text(pr [0] + 0.2, pr[1] + 0.2 , 'p\'', color=pc, fontsize=12)
ax.text(1, 1, r'$\theta$', color=pc, fontsize=14)
ax.text(r_x - 0.2, r_y + 0.2, 'r ', color=rc, fontsize=12)
plt.show()
Constructing an orthogonal basis for linear combination
As an alternative to explicitly translating a point to the origin, applying rotation and translating it back, we can directly construct an orthogonal basis around and express the rotated point as a linear combination, as shown in the previous section:
Here, is the vector perpendicular to with the same magnitude, i.e. .
To obtain