Detecting a collision is only half the job. Once two things are
touching,
the engine has to respond: push them apart, slide one along the other, snap a
character to the floor, find how far the cursor is from a wire. Every one of those needs the
same primitive — the closest point on a shape to a given point
\vec{p}. Once you have it, the distance to the shape is just
\lVert \vec{p} - \text{closest}\rVert, and the direction to push is
the line between them. Three shapes, all solved by the same move: project, then clamp.
Closest point on a segment, line by line
A segment runs from A to B; find the
point on it nearest to \vec{p}.
Step 1 — drop a perpendicular onto the infinite line. The nearest point on
the whole line through A and B
is the foot of the perpendicular from \vec{p}. Write the line as
A + t(B - A); the foot's parameter t
comes from
projecting
\vec{p} - A onto the direction
B - A:
t = \frac{(\vec{p} - A)\cdot(B - A)}{\lVert B - A\rVert^2}.
Step 2 — clamp t to the segment. A segment is not
an infinite line — it stops at its endpoints. If the projection lands before
A (t < 0) or past
B (t > 1), the true nearest point on the
segment is the endpoint it overshot. So pin t into
[0, 1]:
t^\star = \min\big(1,\ \max(0,\ t)\big).
Step 3 — evaluate at the clamped parameter. The closest point on the segment
is the line point at t^\star:
\vec{q} = A + t^\star (B - A).
That clamp is the whole story of segment distance: interior projections give a perpendicular
foot, exterior ones fall back to an endpoint.
Closest point on a plane and on a box, line by line
Step 4 — closest point on a plane: subtract the signed distance. For a plane
with unit normal \vec{n} and offset
d, the
signed distance
of \vec{p} is s = \vec{n}\cdot\vec{p} - d.
Stepping back along the normal by exactly that much lands on the plane:
\vec{q} = \vec{p} - (\vec{n}\cdot\vec{p} - d)\,\vec{n} = \vec{p} - s\,\vec{n}.
No clamping — a plane is unbounded, so the perpendicular foot is always valid.
Step 5 — closest point on an AABB: clamp each coordinate. Because the box
[\vec{m}, \vec{M}] is a product of independent intervals, the
nearest point is found one axis at a time — clamp each coordinate of
\vec{p} into that axis's interval:
q_a = \min\big(M_a,\ \max(m_a,\ p_a)\big) \quad\text{for each axis } a \in \{x, y, z\}.
If \vec{p} is already inside the box every coordinate passes
through and \vec{q} = \vec{p} (distance zero); if it is outside,
each axis snaps to the nearer face. The same componentwise clamp as Step 2, three times over.
Step 6 — the distance falls out. For any of these shapes, once you have the
closest point \vec{q}, the distance from
\vec{p} to the shape is simply
\operatorname{dist}(\vec{p}, \text{shape}) = \lVert \vec{p} - \vec{q}\rVert,
and for comparisons you keep it squared. One query gives you both the distance and
the point — which is exactly what collision response needs next.
The closest point \vec{q} to a point
\vec{p} on:
-
a segment AB — the clamped projection,
t = \frac{(\vec{p}-A)\cdot(B-A)}{\lVert B-A\rVert^2},
t^\star = \min(1, \max(0, t)),
\vec{q} = A + t^\star(B - A).
-
a plane (\vec{n}, d) with unit normal —
subtract the signed distance,
\vec{q} = \vec{p} - (\vec{n}\cdot\vec{p} - d)\,\vec{n}.
-
an AABB [\vec{m}, \vec{M}] — clamp per axis,
q_a = \min(M_a, \max(m_a, p_a)).
-
distance — to any of these,
\operatorname{dist}(\vec{p}, \text{shape}) = \lVert \vec{p} - \vec{q}\rVert
(squared for comparisons).
The most common job a closest-point query does is undo an overlap. When the narrow phase
reports that two shapes have interpenetrated, the engine must separate them, and it wants to
move them the least distance that does so — along the direction that exits the
collision fastest.
Take a ball that has sunk into a wall. The closest point on the wall to the ball's centre
gives both numbers at once: the penetration depth is
r - \lVert \vec{c} - \vec{q}\rVert (how far the surface is inside
the ball), and the contact normal is the unit direction
(\vec{c} - \vec{q})/\lVert \vec{c} - \vec{q}\rVert from the closest
point back to the centre. Push the ball out along that normal by the depth and the overlap is
gone — the minimal-translation fix. Box-on-box, capsule-on-mesh, character-on-terrain: all
reduce to "find the closest point, push along the line to it by the overlap". The same little
project-and-clamp that measured the distance is what resolves the collision — the backbone of
every physics response, built on
projection and a
clamp.