==================================================================================
Understanding Ray-Plane Intersection
==================================================================================
Let's walk through the logistics behind the ray-plane intersection test. To
begin with, the equation derives itself from a ray-producing function which
will take a vector and apply it as a directional offset from an arbitrary
origin in the same coordinate space that the plane's origin offset is relative
to.
For the intersection to work, we know that the ray's direction has to be
infinite, so we have no bound placed on our scalar; the scalar is, however,
what we need to know. It may or may not come as a surprise to you that this
formulae relies on properties which are known within the plane equation
itself.
Let's take a quick look at the plane equation:
P * N + d = 0
Any point within the plane should be able to achieve a dot product with its
plane normal that produces a value of zero, providing that the plane's
distance from the world origin itself, d, is also applied as an offset.
Why do we need this d value though? That is a good question, with a simple
answer: a point in the world must always be relative to a given origin. In
order to represent this point as a vector, we can draw a line from the given
origin to the point. When taking the dot product between the point as it
stands within the world, and the normal of a plane, there is no guarantee of
perpendicularity however.
Say, for instance, we have the following scenario:
n|
__|____.______
\
\
_|_ origin
|
There's a line approximately going from the middle of the location marked
"origin" to a dot - this dot is the point on our plane, and origin is the location
with which our values are relative to. Likewise, the 'n' denotes the vertical line
it's adjacent to - the plane's normal.
A dot product between two vectors deals heavily with angles, because the cosine
of the angle between the two vectors plays a central role in the actual value it
produces. The angle itself is produced by placing the vectors' tails against each
other, and can be computationally obtained through the arccosine function (and
ensuring that both vectors are normalized).
Even though this knowledge is trivial for anyone who's studied algebraic
geometry at levels more in depth than what's offered in a pre-calculus 2 course,
it's also very significant: as our example point stands in the world,
to take the dot product as it is now with the plane's normal would produce a value
that would be calculated from the following perspective:
p
.
\ n|
\ |
Vectors, as we know, do not represent positions. Rather, they
represent directions which are associated with arbitrary lengths.
We know that in order for a point to actually reside within the plane,
the plane equation must produce zero when the point is
"plugged in" to it. This point in particular involves a dot product, and in this
case, the dot product between these two vectors will not produce a zero because a)
neither of these vectors are zero vectors, and b) the only way a cosine will
produce a value of zero is if its input is equivalent to 90 degrees,
or pi over 2 radians.
So, we don't have a zero, which leads us back to our initial point:
n|
__|____.______ _
\ |
\ | |d|
_|_ origin |
|
Our equation then might look something like:
P * N + d = 0 <==> P * <0, 1, 0> + 3 = 0
...in a 3D coordinate system where our X-axis increases from left to right, our
Y-axis increases from bottom to top, and our Z-axis increases "out" of the screen
you're reading.
Notice the line we've added, and the |d| right next to it. The absolute value
of d represents the distance from the origin's plane to the plane itself. You
might see where this is going. Here's what happens when we remove this |d| from
the plane's equation, and then bring the plane to our origin:
n|
__|____.___ _|_ origin
|
(It's not quite as simple, unfortunately, to draw a vector to the point using
ascii characters when the plane is parallel with the x-axis)
Which, in dot product land, means:
n|
P<----| (the arrow is just another way of depicting P's head)
Thus, they're perpendicular.
If you take a piece of paper and draw a plane in 2D or 3D with any kind of
normal, regardless of its direction, and which is perpendicular to a plane of
any distance from the world origin, we can use this method to prove that this
equation works.
It's important to note, though, that we need to move the plane 'd' units in the
(positive or negative) direction of its normal so the the plane itself passes
through the origin. As we've just seen, we can produce a vector from the origin to
P's new location which will yield a value of zero when dotted with the plane's
normal.
If we have some ray, defined as R(t) = P0 + tV, where P0 is an arbitrary point
in our world and V is a unit length vector with which t acts as a scalar for,
we can use this definition of the ray within our point-plane equation:
P * N + d = 0 <==> (P0 + tV) * N + d = 0
Solving for t:
(P0 + tV) * N = -d
(P0 * N) + (tV * N) = -d
(P0 * N) + t(V * N) = -d [1]
t(V * N) = -d - (P0 * N)
t = -d - (P0 * N) / (V * N)
= -(d + (P0 * N)) / (V * N)
[1]:
t(V * N) = (tV * N) is a valid property in this context, considering that V is
assumed to be a vector of unit length such that |V| = 1.
V * N = |V||N|cos(theta), where theta is the angle between V and N.
Since |V| = sqrt(x^2 + y^2 + z^2) = 1,
it follows that,
|Vt| = sqrt((tx)^2 + (ty)^2 + (tz)^2)
= sqrt(t^2(x^2) + t^2(y^2) + t^2(z^2))
= t * sqrt(x^2 + y^2 + z^2)
= t * |V|
= t
---------------------------------------------
Our algebraic means of finding t is extremely simple, given that we know some
basic properties. But *why*, geometrically speaking, does this work? In order
to "think" in computer graphics, it can be very beneficial to look beyond the
equation, and consider the logistics behind the environment the equation is
supposed to being working with.
Back to our equation:
t = -(d + (P0 * N)) / (V * N)
Let's take a visual route again with the following illustration:
\
N\
_.*\
.* \ <- the plane
\
P \
.----> V \
_|_o
|
- In this diagram, we have our normal N represented by a few dots. The dot closest
to the plane is the tail, and the one farthest is the head. We can blame
its ghetto simplicity and imaginative requirement on the limitations of ASCII.
- P corresponds to the dot, which represents our world space point, and also the
origin point of the ray.
- V sits next to our unit length vector, which gives the ray its direction.
- o corresponds to the set of "axes" which represent the origin of our coordinate
space.
\
N \
_.*\
.* \
P \
.----> V \
. \
.
.
_.|_o
|
As you can see above, we've added a dotted line to denote P as a vector.
Our corresponding dot product for P and N is as follows:
_.*.
N.* .
. .
. .
* . P
What do we see here? In this case, they're perpendicular! This is a
nice setup, here, because we have ourselves a fantastic mechanism for illustrating
d's purpose overall.
Thinking back to what we've learned from our foray into the plane equation, we
know that d exhibits an *offset* which is proportional to the plane's distance
from the origin. Because our dot product is 0, d's value is the only value which we
have in the numerator.
So,
t = -(d + (P * N)) / (V * N)
becomes,
t = -d / (V * N)
Setting aside the denominator and d's negative sign for a moment, we can scale
V by d itself to see what it produces:
\
N\
_.*\
* \
\
P \
.---------------> V \
. . \
. .
. .
_.|_o . ^ d's distance
|
Clearly, it's not quite enough to unravel the intersection point. How
exactly does our denominator help, then?
Consider how its computation is visually represented:
|x |
----.----->V
_.*
*N
The line underneath the marker, 'x', which lies between the two pipes is our
cosine. Notice how it's a bit shorter than V and N. Since V actually hasn't
been scaled yet, it's still a unit vector. So is N, for that matter. Which
means that our cosine is *guaranteed* to be in the range [-1, 1]. We can tell
here that it's somewhere in (-1, 0), using exclusive bounds.
So, it's less than 0, but not enough to reach -1. Let's forget, for a moment,
the sign of our cosine, and focus on the fact that its absolute value is < 0.
Remember: we're *dividing* by this value. What happens when we divide a
quantity by a value which is less than zero?
The quantity's absolute value will be greater than its previous value before
the division. So, in geometric terms, if V's direction was parallel to N, our
additional adjustment of the denominator would effectively do nothing. And
this makes sense, because the value of the cosine between two parallel unit
vectors facing the same direction is 1. Likewise, two parallel unit vectors
which face in opposing directions will result in -1.
Which brings us to the sign of the numerator: it's negative. Without this sign,
we would be given a negative value for t, because in our case, our ray's direction
faces the front of the plane, which means that the plane's normal opposes it. A
negative value of t, though, would negate the ray's direction; it wouldn't face in
the direction of the plane, and therefore wouldn't actually be able to intersect
it. Consequently, in situations where we wish to know whether a ray faces a given
plane, we know that t < 0 implies that we're not facing the plane at all.
To cement this fact in, consider what happens in a situation like this:
N|
__|__________
^
. _|_
. |
.P
Here, our plane normal faces in the same direction as our ray. Obviously, we have
a valid value of t here. Yet, the vectors are parallel.
Let's take a closer look:
N|
__|__________
^
. _|_ origin
. . |
. *
^ direction to P from origin
Combining the two vectors...
. <-- P's tail
N| .
| *
.p
Ahh, yes: the tails aren't connected. We need to adjust:
|N
|
.
*
.p
Regardless of whether or not we view N as the initial or terminating axis in our
"circle", here, we'll have a cosine of -1.
And that's that.