← Collision detection for beginners

[Chapter 11 · Part II · 3D]

Coordinates and distance in space

Three axes instead of two, which way game engines point them, vectors in space, and the distance and midpoint of two points in 3D.

Part II takes the book into space. Most of Part I carries straight over with one more coordinate: a circle becomes a sphere, a rectangle becomes a box, and every test gains a term. A few ideas are new, because space has room for them: the cross product, planes and rays.

The Cartesian coordinate system in space

In space, the Cartesian coordinate system is three mutually perpendicular number axes, the xx-, yy- and zz-axis, that intersect at the origin. Every point in space is represented by exactly one ordered triple (x,y,z)(x, y, z).

xyOP(x, y)plane (2D)xyzOP(x, y, z)space (3D)

Which way is up

A third axis means one more decision: which way it points. This book draws yy up and zz towards you, like Three.js, Godot and OpenGL. Unity turns zz away from you, and Unreal and Blender put zz up. None of the tests in this book care which axis is which, as long as every part of a program agrees.

The figures in Part II are drawn in 3D. Drag the background of one to turn it, and drag a point or a shape to move it. It moves parallel to your screen, so to move something towards you or away, turn the view first. The dashed line under each point drops straight down to the floor, the plane y=0y = 0, which is how you can tell where it is.

Vectors in space

A vector in space is a step along three axes, (x,y,z)(x, y, z), and it’s stored the same way as a point. The step from AA to BB and the dot product each gain a third term:

AB→=B−A=(xB−xA, yB−yA, zB−zA)\overrightarrow{AB} = B - A = (x_{B} - x_{A},\ y_{B} - y_{A},\ z_{B} - z_{A})
u⋅v=uxvx+uyvy+uzvz\mathbf{u} \cdot \mathbf{v} = u_{x}v_{x} + u_{y}v_{y} + u_{z}v_{z}

The dot product means just what it did in the chapter on line segments: it’s positive when two vectors point roughly the same way, zero when they’re perpendicular and negative when they point roughly opposite ways, and with a vector of length 11 it measures how far the other one reaches along it.

TypeScript
// A point, or a vector: { x, y, z } is then the step, not a position.
type Vec3 = { x: number; y: number; z: number };

function subtract(a: Vec3, b: Vec3): Vec3 {
	return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}

function dot(u: Vec3, v: Vec3): number {
	return u.x * v.x + u.y * v.y + u.z * v.z;
}
JavaScript
// A point or a vector is an object like { x: 1, y: 2, z: 3 }.
function subtract(a, b) {
	return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z };
}

function dot(u, v) {
	return u.x * v.x + u.y * v.y + u.z * v.z;
}
Python
import math
from dataclasses import dataclass


@dataclass
class Vec3:
    """A point, or a vector: (x, y, z) is then the step, not a position."""

    x: float
    y: float
    z: float


def subtract(a: Vec3, b: Vec3) -> Vec3:
    return Vec3(a.x - b.x, a.y - b.y, a.z - b.z)


def dot(u: Vec3, v: Vec3) -> float:
    return u.x * v.x + u.y * v.y + u.z * v.z
subtract
time O(1) space O(1)
dot
time O(1) space O(1)

Part II’s functions reuse Part I’s names, subtract and dot included, so keep them in their own file, or module, next to Part I’s.

Distance between two points in space

The distance between two points A(x1,y1,z1)A(x_{1}, y_{1}, z_{1}) and B(x2,y2,z2)B(x_{2}, y_{2}, z_{2}) in space is also the hypotenuse of a right triangle, ACBACB. It just takes one more right triangle, ADCADC, lying flat underneath it, to get the length of the leg ACAC first. From AA, go along the xx-axis to DD, then along the zz-axis to CC, then straight up to BB:

∣AD∣=∣x2−x1∣,∣DC∣=∣z2−z1∣,∣CB∣=∣y2−y1∣|AD| = |x_{2} - x_{1}|, \quad |DC| = |z_{2} - z_{1}|, \quad |CB| = |y_{2} - y_{1}|

From triangle ADCADC:

∣AC∣2=(x2−x1)2+(z2−z1)2|AC|^2 = (x_{2} - x_{1})^2 + (z_{2} - z_{1})^2

and from triangle ACBACB:

∣AB∣=∣AC∣2+(y2−y1)2=(x2−x1)2+(y2−y1)2+(z2−z1)2|AB| = \sqrt{|AC|^2 + (y_{2} - y_{1})^2} = \sqrt{(x_{2} - x_{1})^2 + (y_{2} - y_{1})^2 + (z_{2} - z_{1})^2}

Move AA and BB around, and turn the view until you can see that ADCADC lies flat and ACBACB stands straight up.

xyzDCAB
Drag A or B. Drag the background to turn the view. A = (−2, 0.5, 1.5), B = (1.5, 2.5, −1) · |AD| = 3.5 · |DC| = 2.5 · |CB| = 2 · |AB| = √(3.5² + 2.5² + 2²) ≈ 4.74

Each extra dimension just adds one more squared difference under the root. Math.hypot and Python’s math.hypot take any number of them.

The trick from Part I works here too: to compare distances, compare their squares and skip the root. And the squared distance of two points is the step between them, dotted with itself.

TypeScript
function distance(a: Vec3, b: Vec3): number {
	return Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
}

function distanceSquared(a: Vec3, b: Vec3): number {
	const step = subtract(b, a);
	return dot(step, step);
}
JavaScript
function distance(a, b) {
	return Math.hypot(b.x - a.x, b.y - a.y, b.z - a.z);
}

function distanceSquared(a, b) {
	const step = subtract(b, a);
	return dot(step, step);
}
Python
def distance(a: Vec3, b: Vec3) -> float:
    return math.hypot(b.x - a.x, b.y - a.y, b.z - a.z)


def distance_squared(a: Vec3, b: Vec3) -> float:
    step = subtract(b, a)
    return dot(step, step)
distance
time O(1) space O(1)
distanceSquared
time O(1) space O(1)

Midpoint in space

The point halfway between AA and BB is still the average of their coordinates, all three of them:

M=(x1+x22, y1+y22, z1+z22)M = \left(\frac{x_{1} + x_{2}}{2},\ \frac{y_{1} + y_{2}}{2},\ \frac{z_{1} + z_{2}}{2}\right)

It’s also the centre of the box that has AA and BB as opposite corners, which the chapter on boxes comes back to.

TypeScript
function midpoint(a: Vec3, b: Vec3): Vec3 {
	return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
}
JavaScript
function midpoint(a, b) {
	return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2, z: (a.z + b.z) / 2 };
}
Python
def midpoint(a: Vec3, b: Vec3) -> Vec3:
    return Vec3((a.x + b.x) / 2, (a.y + b.y) / 2, (a.z + b.z) / 2)
midpoint
time O(1) space O(1)

Comments

No comments yet. Questions and corrections are welcome.

Plain text, line breaks kept. Your IP address is stored only as a one-way hash, to limit spam.