The Geometry of the Dot Product
The Dot Product is a fundamental building block for vector operations in video games and simulations. A solid understanding is crucial for applications involving view-related coordinate transformations and even physical modeling within a game world. For many practical use cases, the dot product offers an elegant alternative to constructing explicit visibility cones or relying on computationally expensive raytracing algorithms.
This article provides an introductory exploration of the theory of the dot product and its geometric interpretation.
An example involving field-of-view-calculations illustrates how the dot product can simplify visibility modeling and decision-making in games.
Additional proofs establish key lemmas that support further applications of the dot product and related operations in both 2D and 3D.
Geometric Interpretation
The dot-product takes two vectors and returns the sum of the products of their corresponding components. Given two vectors
the dot product yields a scalar value.
If , the two vectors are perpendicular to each other. We will derive this in the following.
Orthogonality of Unit Vectors
We will first have a look at a special case, namely when are both unit vectors.
In Figure 1, the radius of the unit circle is represented by the vectors and . Thus, for both and the following trivially holds:

Plot-Code (Python)
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Arc
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, color='b')
offset = 0.01
ax.text(v1[0] - 0.5, v1[1] -0.3, r"$\vec{a}$", fontsize=12, color='r')
ax.text(v2[0] - 0.2, v2[1] -0.12, r"$\vec{b}$", fontsize=12, color='b')
# 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.5, fill=False, transform=ax.transData)
ax.add_patch(circle)
# Angle Arc
angle_deg = np.degrees(np.arccos(np.dot(v1, v2)))
arc = Arc(origin, 0.4, 0.4, angle=0, theta1=0, theta2=angle_deg, edgecolor='green')
ax.add_patch(arc)
ax.text(0.08, 0.02, r"$\Theta$", color='green')
plt.show()
Clearly, .
Additionally can easily be shown since
- the cosine represents the quotient of the adjacent side and the hypotenuse:
- the sine represents the quotient of the opposite side and the hypotenuse:
Solving for and respectively gives us
If , it follows directly that .
Orthogonality of Vectors with arbitrary length
Let's take a look at the common case when and are of arbitrary length and recap the Law of Cosines:
This relationship is shown in Figure 2

Plot-Code (Python)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
a = np.array([4, 1])
b = np.array([1, 5])
c = b - a
a_len = np.linalg.norm(a)
b_len = np.linalg.norm(b)
dot_prod = np.dot(a, b)
cos_theta = dot_prod / (a_len * b_len)
theta_rad = np.arccos(cos_theta)
theta_deg = np.degrees(theta_rad)
angle_a = np.degrees(np.arctan2(a[1], a[0]))
theta1 = angle_a
theta2 = angle_a + theta_deg
fig, ax = plt.subplots(figsize=(6, 6))
ax.quiver(0, 0, a[0], a[1], angles='xy', scale_units='xy', scale=1, color='red')
ax.text(a[0] / 2, a[1] / 2 - 0.5, r'$\vec{a}$', color='red', fontsize=12, ha='right', va='bottom')
ax.quiver(0, 0, b[0], b[1], angles='xy', scale_units='xy', scale=1, color='blue')
ax.text(b[0] / 2 - 0.5, b[1] / 2, r'$\vec{b}$', color='blue', fontsize=12, ha='left', va='bottom')
ax.quiver(a[0], a[1], c[0], c[1], angles='xy', scale_units='xy', scale=1, color='black')
ax.text(a[0] + c[0] / 2 + 1, a[1] + c[1] / 2, r'$\vec{c} = \vec{b} - \vec{a}$', color='black', fontsize=12, ha='center', va='top')
arc = Arc((0, 0), 1.0, 1.0, angle=0, theta1=theta1, theta2=theta2, edgecolor='green')
ax.add_patch(arc)
ax.text(0.11, 0.11, r"$\Theta$", color='green', fontsize=14)
ax.set_xlim(-1, 5)
ax.set_ylim(-1, 6)
ax.set_aspect('equal')
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.grid(True)
plt.show()
First of all, let's substitute the variables with scalar values of our vectors and :
Note that , so we can substitute this, too. Additionally, :
We will start with simplifying the terms: