Ray–Plane Intersection

Floors, walls, ceilings, the still surface of water — the flat world of a game is made of planes. Firing a ray at one is the cheapest intersection in the whole tracer: where the sphere gave us a quadratic, the plane gives us a single linear equation, solved with one division. No discriminant, no \pm — just one t.

From plane equation to one division, line by line

A plane is fixed by a normal n and a constant d: a point P lies on it exactly when its projection onto the normal equals d.

n\cdot P = d.

Step 1 — substitute the ray. A point is on the plane when it satisfies this, and on the ray when it equals O + tD. Demanding both, put the ray in for P:

n\cdot(O + tD) = d.

Step 2 — distribute the dot product. The dot product is linear, so it splits the sum and pulls the scalar t out:

n\cdot O + t\,(n\cdot D) = d.

Step 3 — isolate the only unknown. Everything except t is a known number. Move n\cdot O across and divide by n\cdot D:

t = \frac{d - n\cdot O}{n\cdot D}.

That is the entire intersection — one dot product on top, one on the bottom, one division.

Step 4 — watch for n\cdot D = 0: the parallel case. If the ray's direction is perpendicular to the normal, the ray runs flat along the plane and the denominator vanishes. There is no single crossing point: either the ray never meets the plane (it floats above or below it forever) or it lies entirely within it. Geometrically n\cdot D = 0 means the ray is parallel to the surface — the one case the formula refuses to answer, so we test for it first and report "no hit".

n\cdot D = 0 \quad\Longrightarrow\quad \text{ray parallel to the plane — no intersection.}

Step 5 — reject hits behind the origin. A solved t < 0 sits behind the camera — the plane is in the rear-view mirror, not in front of the ray. Keep it only when t \ge 0.

Step 6 — recover the hit point. As always, feed the surviving t back into the ray:

P_{\text{hit}} = O + t\,D. For a ray P(t) = O + tD and a plane n\cdot P = d:

The formula t = (d - n\cdot O)/(n\cdot D) looks like ordinary arithmetic, so it's tempting to just compute it and move on. But when the ray runs exactly parallel to the plane, n\cdot D = 0, and dividing by it doesn't raise a helpful error in most languages — it quietly produces Infinity, -Infinity, or NaN, depending on the sign of the numerator.

A renderer that skips the check will pass one of those non-values downstream as if it were a real hit distance, corrupting depth comparisons or crashing far from the actual bug. The fix is to test n\cdot D against a small tolerance before dividing: treat it as no intersection (or, in the rarer case where the ray also lies within the plane, as infinitely many intersections), and only divide once the denominator is safely nonzero.

The formula intersects an infinite plane: a sheet of glass stretching forever in every direction. Solving for t always finds the crossing (unless the ray is parallel), even if that crossing is a mile off the edge of the actual floor tile.

Real geometry is bounded — a floor, a quad, a window. So a ray–plane hit is usually only step one: compute P_{\text{hit}} = O + tD on the infinite plane, then test whether that point lies inside the bounded patch. For an axis-aligned rectangle that is a pair of range checks on two coordinates; for an arbitrary quad or triangle it's a containment test in the plane. The plane gives you the where on the infinite sheet cheaply; the boundary test decides whether you actually landed on the surface or shot past its edge into the void.