Angle Between 3D Vectors
3D Geometry
Medium
Problem
Compute the angle in radians between two 3D vectors. First obtain their cosine:
c=\frac{\mathbf{v}\cdot\mathbf{w}}{\lVert\mathbf{v}\rVert_2\lVert\mathbf{w}\rVert_2}
Then recover the angle:
\theta=\arccos(c)
Clamp c to [-1,1] before applying arccos to protect against floating-point error. If either vector has zero norm, the angle is undefined, so return np.nan. Otherwise return a Python float in [0,\pi].
Theory
The angle between two vectors is a fundamental concept in geometry and linear algebra. It measures how much two vectors "point in different directions" and ranges from 0 (parallel) to \pi radians or 180 degrees (anti-parallel).
This concept is essential in 3D graphics, physics simulations, robotics, and machine learning.
The Dot Product Connection
The angle between vectors is intimately connected to the dot product. For vectors \mathbf{a} and \mathbf{b}:
\mathbf{a} \cdot \mathbf{b} = ||\mathbf{a}|| \cdot ||\mathbf{b}|| \cdot \cos(\theta)
where:
- \mathbf{a} \cdot \mathbf{b} is the dot product
- ||\mathbf{a}|| and ||\mathbf{b}|| are the magnitudes (lengths)
- \theta is the angle between them
The Formula for the Angle
Solving for \theta:
\cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||}
\theta = \arccos\left(\frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||}\right)
The result is in radians. To convert to degrees: \theta_{deg} = \theta_{rad} \times \frac{180}{\pi}
Computing the Dot Product in 3D
For 3D vectors \mathbf{a} = (a_x, a_y, a_z) and \mathbf{b} = (b_x, b_y, b_z):
\mathbf{a} \cdot \mathbf{b} = a_x b_x + a_y b_y + a_z b_z
This is the sum of component-wise products.
Computing the Magnitude in 3D
The magnitude (Euclidean norm) of a 3D vector:
||\mathbf{a}|| = \sqrt{a_x^2 + a_y^2 + a_z^2}
||\mathbf{b}|| = \sqrt{b_x^2 + b_y^2 + b_z^2}
Step-by-Step Procedure
Step 1: Compute the dot product \mathbf{a} \cdot \mathbf{b}
Step 2: Compute the magnitudes ||\mathbf{a}|| and ||\mathbf{b}||
Step 3: Compute \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||}
Step 4: Apply inverse cosine: \theta = \arccos(\cos(\theta))
Worked Example
Vectors:
- \mathbf{a} = (1, 2, 3)
- \mathbf{b} = (4, 5, 6)
Step 1: Dot product
\mathbf{a} \cdot \mathbf{b} = 1(4) + 2(5) + 3(6) = 4 + 10 + 18 = 32
Step 2: Magnitudes
||\mathbf{a}|| = \sqrt{1^2 + 2^2 + 3^2} = \sqrt{1 + 4 + 9} = \sqrt{14} \approx 3.742
||\mathbf{b}|| = \sqrt{4^2 + 5^2 + 6^2} = \sqrt{16 + 25 + 36} = \sqrt{77} \approx 8.775
Step 3: Cosine of angle
\cos(\theta) = \frac{32}{3.742 \times 8.775} = \frac{32}{32.83} \approx 0.9746
Step 4: Angle
\theta = \arccos(0.9746) \approx 0.226 \text{ radians} \approx 12.9°
The vectors are nearly parallel (small angle).
Special Cases
Parallel vectors (\theta = 0):
\cos(\theta) = 1 \implies \mathbf{a} \cdot \mathbf{b} = ||\mathbf{a}|| \cdot ||\mathbf{b}||
Vectors point in the same direction.
Perpendicular vectors (\theta = 90°):
\cos(\theta) = 0 \implies \mathbf{a} \cdot \mathbf{b} = 0
Vectors are orthogonal.
Anti-parallel vectors (\theta = 180°):
\cos(\theta) = -1 \implies \mathbf{a} \cdot \mathbf{b} = -||\mathbf{a}|| \cdot ||\mathbf{b}||
Vectors point in opposite directions.
Example: Perpendicular Vectors
Vectors:
- \mathbf{a} = (1, 0, 0) (x-axis)
- \mathbf{b} = (0, 1, 0) (y-axis)
Dot product:
\mathbf{a} \cdot \mathbf{b} = 1(0) + 0(1) + 0(0) = 0
Cosine:
\cos(\theta) = \frac{0}{1 \times 1} = 0
Angle:
\theta = \arccos(0) = \frac{\pi}{2} = 90°
The x and y axes are perpendicular, as expected.
Example: Opposite Vectors
Vectors:
- \mathbf{a} = (1, 2, 3)
- \mathbf{b} = (-1, -2, -3) = -\mathbf{a}
Dot product:
\mathbf{a} \cdot \mathbf{b} = 1(-1) + 2(-2) + 3(-3) = -1 - 4 - 9 = -14
Magnitudes:
||\mathbf{a}|| = ||\mathbf{b}|| = \sqrt{14}
Cosine:
\cos(\theta) = \frac{-14}{\sqrt{14} \times \sqrt{14}} = \frac{-14}{14} = -1
Angle:
\theta = \arccos(-1) = \pi = 180°
Using Unit Vectors
If vectors are already normalized (unit length), the formula simplifies:
\cos(\theta) = \hat{\mathbf{a}} \cdot \hat{\mathbf{b}}
No need to divide by magnitudes since ||\hat{\mathbf{a}}|| = ||\hat{\mathbf{b}}|| = 1.
This is why normalizing vectors is common in graphics and physics.
Numerical Stability
Due to floating-point errors, \cos(\theta) might slightly exceed the range [-1, 1].
Problem: \arccos(1.0000001) is undefined.
Solution: Clamp the value:
\cos(\theta) = \max(-1, \min(1, \cos(\theta)))
Then apply \arccos safely.
Alternative: Using Cross Product
The angle can also be found using the cross product magnitude:
||\mathbf{a} \times \mathbf{b}|| = ||\mathbf{a}|| \cdot ||\mathbf{b}|| \cdot \sin(\theta)
Combined with the dot product:
\tan(\theta) = \frac{||\mathbf{a} \times \mathbf{b}||}{\mathbf{a} \cdot \mathbf{b}}
\theta = \arctan2(||\mathbf{a} \times \mathbf{b}||, \mathbf{a} \cdot \mathbf{b})
This method is more numerically stable for very small or very large angles.
The Cross Product in 3D
For completeness, the cross product is:
\mathbf{a} \times \mathbf{b} = \begin{pmatrix} a_y b_z - a_z b_y \\ a_z b_x - a_x b_z \\ a_x b_y - a_y b_x \end{pmatrix}
And its magnitude:
||\mathbf{a} \times \mathbf{b}|| = \sqrt{(a_y b_z - a_z b_y)^2 + (a_z b_x - a_x b_z)^2 + (a_x b_y - a_y b_x)^2}
Applications in 3D Graphics
Lighting calculations:
The angle between surface normal and light direction determines brightness:
\text{intensity} = \max(0, \cos(\theta)) = \max(0, \mathbf{n} \cdot \mathbf{l})
Collision detection:
Angle between velocity and surface normal affects reflection.
Camera orientation:
Angle between view direction and object determines visibility.
Applications in Machine Learning
Cosine similarity:
In high-dimensional spaces, cosine similarity measures vector similarity:
\text{similarity} = \cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||}
Used in text embeddings, recommendation systems, and clustering.
Angular distance:
\text{distance} = \frac{\theta}{\pi} = \frac{\arccos(\text{similarity})}{\pi}
Signed vs Unsigned Angle
The formula gives an unsigned angle in [0, \pi].
For a signed angle (determining rotation direction), you need additional context:
- A reference plane or axis
- The cross product direction
In 2D, signed angle is straightforward using \arctan2.
Generalizing to Higher Dimensions
The same formula works in any dimension:
\cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||} = \frac{\sum_i a_i b_i}{\sqrt{\sum_i a_i^2} \cdot \sqrt{\sum_i b_i^2}}
The geometric interpretation of "angle" extends to n-dimensional space.
Edge Cases
Zero vector:
If either vector is zero, the angle is undefined. Check for ||\mathbf{a}|| = 0 or ||\mathbf{b}|| = 0 before computing.
Nearly parallel vectors:
When \cos(\theta) \approx 1, \arccos can be numerically unstable. The \arctan2 method is more robust.
Very small vectors:
Normalize carefully to avoid division by very small numbers.
Examples
Example 1
- Input
v = [1, 0, 0], w = [0, 1, 0]- Output
1.570796- Explanation
- Orthogonal vectors have cosine zero and an angle of pi divided by two.
Example 2
- Input
v = [1, 2, 3], w = [2, 4, 6]- Output
0
Hints
- Use np.dot(v, w) for the numerator and squared sums for both norms.
- Pass np.clip(cosine, -1.0, 1.0) to np.arccos.
Requirements
- Compute both Euclidean norms and the dot product
- Return np.nan when either norm is zero
- Clamp the cosine before applying np.arccos
- Return a Python float
Constraints
- Both inputs contain exactly three numeric values
- Use NumPy only
Starter Code
import numpy as np
def angle_between_3d(v: list, w: list) -> float:
"""
Returns the angle as a float.
"""
# Write code here
passTest Cases
| Case | Matches | |
|---|---|---|
| Orthogonal vectors (pi/2) | — | public |
| Parallel vectors (0) | — | public |