From a point and a normal to "which side", line by line
Pin the plane down with one known point on it, \vec{p}_0, and its
normal \vec{n} (the direction it faces). Every other point
\vec{p} on the plane is reachable from
\vec{p}_0 by a step that lies in the plane.
Step 1 — the in-plane step is perpendicular to the normal. If
\vec{p} is on the plane, the vector
\vec{p} - \vec{p}_0 lies flat in the plane, so it is perpendicular
to \vec{n} — and a
dot product
of perpendicular vectors is zero:
\vec{n}\cdot(\vec{p} - \vec{p}_0) = 0.
That is the plane: the set of all points whose displacement from
\vec{p}_0 has no component along the normal.
Step 2 — expand to the standard form. Distribute the dot product and move
the constant to the right. Writing d = \vec{n}\cdot\vec{p}_0:
\vec{n}\cdot\vec{p} - \vec{n}\cdot\vec{p}_0 = 0 \quad\Longrightarrow\quad \vec{n}\cdot\vec{p} = d, \qquad d = \vec{n}\cdot\vec{p}_0.
(In coordinates with \vec{n} = (a,b,c) this is the familiar
ax + by + cz = d.) One vector
\vec{n} and one number d pin the whole
plane.
Step 3 — with a unit normal, the leftover is a signed distance. Take
\vec{n} to be a unit vector,
\lVert\vec{n}\rVert = 1. For any point
\vec{p} in space — on the plane or not — the quantity
s = \vec{n}\cdot\vec{p} - d
is the signed distance from \vec{p} to the plane:
its actual perpendicular distance, carrying a sign.
Step 4 — read the sign as the side. Because the normal has unit length,
s is positive on the side the normal points toward, negative on
the far side, and exactly zero on the plane itself:
s > 0\ (\text{normal's side}), \qquad s = 0\ (\text{on the plane}), \qquad s < 0\ (\text{the other side}).
"Which side am I on?" is the sign of one dot product minus a constant — no square roots, no
division.
A plane is fixed by a point \vec{p}_0 on it and a normal
\vec{n}:
-
Point–normal form — a point \vec{p} is on the
plane exactly when \vec{n}\cdot(\vec{p} - \vec{p}_0) = 0.
-
Standard form — equivalently
\vec{n}\cdot\vec{p} = d with
d = \vec{n}\cdot\vec{p}_0.
-
Signed distance — with a unit normal, the signed distance from
any point \vec{p} to the plane is
s = \vec{n}\cdot\vec{p} - d.
-
Sign ⇒ side —
s > 0 on the normal's side,
s < 0 on the other,
s = 0 on the plane.
The view a camera can see is a box — the frustum — bounded by six planes
(left, right, top, bottom, near, far), each with its normal pointing inward. An
object is visible only if it sits on the positive side of all six. So
frustum culling is just six signed-distance tests: compute
s = \vec{n}\cdot\vec{p} - d against each plane, and the moment any
comes out negative, the object is outside the view and can be skipped. Whole armies are
discarded this way before they ever touch the GPU.
Collision against a flat surface is the same test wearing different
clothes. The signed distance of a ball's centre to the floor plane is how far the
ball is from touching: when s drops below the radius the ball has
penetrated, and the depth r - s tells you exactly how far to push
it back out — along \vec{n}, of course. One equation,
\vec{n}\cdot\vec{p} - d, quietly running both the renderer and the
physics.
The sign of s = \vec{n}\cdot\vec{p} - d tells you the
right side even if \vec{n} isn't unit length — scaling by a
positive number never flips a sign. But the theorem promises more than a side: it promises
s is the actual perpendicular distance to the
plane, and that only holds when \lVert\vec{n}\rVert = 1.
Forget to normalise and every s comes back scaled by
\lVert\vec{n}\rVert — a normal three times too long makes every
"distance" three times too big, while the sign still looks perfectly correct in testing. That's
how the bug hides: collision code comparing s against a ball's
radius, or culling code treating s as a real gap in metres, will
silently misfire — balls stopping short of the floor, or sinking through it — the moment a
plane's normal isn't unit length. Always normalise \vec{n} (and
recompute d from the normalised version) before trusting
s as a distance.