What's the distance between a point and a line segment?let A, B be the 2D start and end points of a line segment. Let P be a 2D point.
We think about the problem and graph the problem like this:
P
.
A ------------------------ B
Then we realize that we can re-frame the problem in terms of a right triangle:
P
.
/ |
/ |
/ _|
/ | |
A ------------------------- B
Q
Now for some definitions. Let's define the vector operations that we'll be working with.
class Vec2( object ):
def __init__( self, x, y ):
self.x = x
self.y = y
This represents a 2D vector with properties 'x' and 'y'.
def length( V ):
return sqrt( V.x*V.x + V.y*V.y )
This computes the length of a 2D vector.
def normalize( V ):
V_len = length( V )
return Vec2( V.x / V_len, V.y / V_len )
This computes a new vector that points in the same direction as V, but is of unit length ( length( normalize( V ) ) == 1.0 ).
def dot( V1, V2 ):
return V1.x*V2.x + V1.y*V2.y
This computes the "dot product" between two vectors. I'll clarify this operation in a moment.
Finally, for clarity and succinctness:
V1 + V2 represents Vec2( V1.x+V2.x, V1.y+V2.y )
V1 - V2 represents Vec2( V1.x-V2.x, V1.y-V2.y )
S * V represents Vec2( S*V.x, S*V.y ), which
scales the vector V by a factor of S. So (2.0 * V)
would result in a vector in the same direction as V,
but twice as long.
Goal: find the length of PQFirst, we examine our graph above and write out the known and unknown quantities.
AB = B - A
AP = P - A
AB_len = length( AB )
AP_len = length( AP )
Q = ?
AQ = Q - A
PQ = P - A
It looks like our first step is to compute Q.
A dot product trick
The trick we'll use to solve this is via the following rule:
For any line that passes through A and B, dot( normalize( B - A ), P - A ) is the distance between A and the closest point on the line to P.
Huh?
Let me explain this thoroughly, so you can add it to your own toolbox for your entire life.
Building an intuitive understanding of vector math
Imagine a line segment AB and a point P. Now picture point Q, which is the closest point to P on a line that passes through A and B, just like the graph above. You can use the dot product to find the distance between A and Q.
First, compute the vector AB by computing (B - A).
You can visualize this as follows. Picture a line segment from point A to point B. Now move it so that A is coincident with the origin (0,0). Since you moved it, you didn't change its length and you didn't change its direction, just its location. So the result of (B - A) could be intuitively described as "a line segment that begins at the origin (0,0) whose length and direction are equal to AB's".
The next step in the dot product trick is to set the length of (B - A) to 1.0, which is "unit length". A few related trivia notes:
- if a vector's length is exactly 1.0, then it is called a "unit vector".
- when you set a vector's length to 1.0, you have just "normalized" the vector.
- normalized vectors are a succinct way to represent a direction in space, whether it be 1D, 2D, 3D, or any other D.
- remember the 2D line equation "y = mx + b"? 'm' is the line's slope. In 2D space, slope is just an alternate way of representing a vector's direction. You can compute a vector V's slope as "rise over run" (V.y / V.x). The only advantage of using slope to represent a direction in 2D is that it's efficient to compute and to use. There are two big disadvantages. First, it's not immediately clear how we'd compute slope in 3D. But more importantly, you can't represent vertical lines with a slope. A vertical line has no 'run', so (V.x / 0.0) == undefined. In programming terms, this could cause problems. So slope is generally avoided for those reasons. I explained it here because I am hoping that it helps you to understand vectors more intuitively.
So, let's enumerate some features of unit length vectors:
- they can be used to represent any direction in space.
- the range of each component of a unit length vector is never outside of [-1.0 .. 1.0].
- if you compute the dot product between a unit length vector and a point, you have projected the point onto the vector. The result of the dot product is the distance between the origin and the closest point on the vector. -- dot product trick
- the dot product of two unit length vectors is equal to the cosine of the angle between them. In other words, let V1 and V2 be unit length vectors. dot( V1, V2 ) == cos( angle between V1 and V2 ). Related facts:
-- the dot product of two unit length vectors is always in the range [-1.0 .. 1.0].
-- if V1 points in the same direction as V2 (that is, V1 == V2), then the dot product is 1.0.
-- if V1 is perpendicular to V2, then the dot product is 0.0.
-- if V1 points in the opposite direction of V2 (that is, V1 == -V2), then the dot product is -1.0.
-- And now, for something fun (and totally optional -- if you don't really understand, don't sweat it. But hopefully it will be interesting rather than confusing): Imagine a light bulb at point L. Now imagine a sphere at point S, being lit by that light bulb. Imagine a point on that sphere, P. You can compute the lightbulb's effect on the sphere at that point as follows:
L = lightbulb position
S = sphere position
P = point on sphere
Ldir = normalize( L - P )
surface_normal = normalize( P - S )
light_intensity = max( 0.0, dot( surface_normal, Ldir ) )
# that quanitity is called "NdotL" in computer graphics.
# you would then multiply that quantity by the light's
# "attenuation", which dims the light as it gets further
# away. But that's outside the scope of this example.
# If you're interested in computer graphics,
# the book "Realtime Rendering" is fantastic.
Solving the problem, finally.So, let's use our newfound ninja-guru knowledge of vectors and dot products to solve the original problem. After glancing at the above graph again, we remember we need to compute the length of the line segment PQ. One way to solve it is to compute the point Q, then length( Q - P ) is our answer. Getting down to business:
def dist_point_to_line( A, B, P ):
# represent the line segment AB as a vector.
AB = B - A
# determine the direction of B relative to A.
AB_dir = normalize( AB )
# compute the distance between A and Q using the dot
# product trick. The first argument is a unit length
# vector. The second argument is a point *relative to
# that vector*.
AQ_len = dot( AB_dir, P - A )
# Now that we know the length of AQ, we can compute Q.
# To do this, think of the following equation as "start
# at A; move along the direction AB_dir by AQ_len units;
# that position is Q."
Q = A + AQ_len * AB_dir
# return the length of PQ.
return length( Q - P )
I hope the explanation was been illuminating, and not too confusing. If you have any questions, feel free to ask.