Skip to main content.

Ray/plane intersections

Here, I'll go over how to test for the intersection between a ray and a plane. I've also provided code at the bottom as a public domain work.

Defining our shapes

If you take the dot product of two vectors that are perpendicular, then the result is zero. This means that we can define our plane as follows:

Po
=
origin of the plane
Pn
=
plane surface normal
V
=
any point on the plane
[Equation 1:] (V - Po) • Pn = 0

The above equation imposes a simple requirement: the vector from any point on the plane to any other point on the plane must be perpendicular to the plane's normal. The "plane origin" is just a point that we already know is on the plane; it doesn't have to be at any specific part of the plane (e.g. a centerpoint), and it isn't otherwise "special."

Our ray, meanwhile, is most usefully defined as:

Ro
=
the ray's origin
Rd
=
the ray's direction
Hp
=
the position at which the ray hits some surface
Hd
=
the hit distance: the distance from the ray origin to the hit position
Hp = Ro + RdHd

Solving for the ray

We check for intersections by substituting our ray equation into our plane equation:

(Ro + RdHd - Po) • Pn = 0

We want to solve for Hd. Let's start by expanding that dot product operation:

(Rox + RdxHd - Pox)Pnx + (Roy + RdyHd - Poy)Pny + (Roz + RdzHd - Poz)Pnz = 0 (RoxPnx + RdxPnxHd - PoxPnx) + (RoyPny + RdyPnyHd - PoyPny) + (RozPnz + RdzPnzHd - PozPnz) = 0

Let's group the terms that are multiplied by Hd:

(RdxPnx + RdyPny + RdzPnz)Hd + RoxPnx - PoxPnx + RoyPny - PoyPny + RozPnz - PozPnz = 0

Now, we can recombine some of these multiplications-and-additions back into dot products:

(RdxPnx + RdyPny + RdzPnz)Hd + (Ro - Po) • Pn = 0 (RdPn)Hd + (Ro - Po) • Pn = 0

Of course, we want to get Hd onto one side of the equation, by itself:

(RdPn)Hd = -(Ro - Po) • Pn (RdPn)Hd = (Po - Ro)Pn [Equation 2:] Hd = ((Po - Ro) • Pn) ÷ (RdPn)

Voila! Every ray that intersects the plane will have a solution for the above equation, which means that if the above equation is unsolveable — that is, if it would involve a division by zero — then the ray doesn't intersect the plane.

That said, we also need to double-check one more condition:

Hd ≥ 0

We need to make sure that the hit distance isn't negative — that our hit position isn't behind our ray.

Free code!

The following C++ code is licensed under CC0 (full legal text / summary), and so is effectively a public domain work. You can incorporate it into your programs without the need for attribution, payment, or similar. (That said, linking back here would be polite!)

This code makes use of the GLM library for vector math.

// Compute the intersection of a ray and a plane with infinite bounds. The ray direction 
// must be normalized. Returns the hit distance, from the ray's origin; to get the hit 
// position, multiply that by the ray's direction and then add the ray's origin.
bool ray_plane_intersection(
   const glm::vec3& ray_origin,
   const glm::vec3& ray_direction, // must be normalized

   const glm::vec3& plane_origin,
   const glm::vec3& plane_normal,

   float& hit_distance
) {
   constexpr float EPSILON = 1e-8;
   //
   auto denom = glm::dot(plane_normal, ray_direction);
   //
   // Non-zero check (accounting for floating-point imprecision):
   //
   if (denom > EPSILON || denom < -EPSILON) {
      auto Hd = glm::dot(plane_origin - ray_origin, plane_normal) / denom;
      if (Hd >= 0) {
         hit_distance = Hd;
         return true;
      }
   }
   return false;
}