Algorithm 5
[Home] [Overview] [History] [Algorithms] [Books] [Web Sites] [Gift Shop]


Intersections of Lines, Segments
and Planes (2D and 3D)

by Dan Sunday

Line and Segment Intersections
Plane Intersections
Line-Plane Intersection
Intersection of 2 Planes
Intersection of 3 Planes
Implementations
intersect2D_2Segments()
inSegment()
intersect3D_SegmentPlane()
intersect3D_2Planes()
References

The intersection of geometric primitives is a fundamental construct in many computer graphics and modeling applications ([Foley et al, 1996], [Mortenson, 1990], [O'Rourke, 1998]).  Here we look at the algorithms for the simplest 2D and 3D linear primitives: lines, segments and planes.

Line and Segment Intersections

For computing intersections of lines and segments in 2D and 3D, it is best to use the parametric equation representation.  Other representations are discussed in Algorithm 2 about the Distance of a Point to a Line, Ray or Segment.  It is shown there how to convert from other representations to the parametric one. 

In any dimension, the parametric equation of a line defined by two points P0 and P1 can be written as

P(s) = P0 + s (P1-P0) = P0 + s u,

where the parameter s is a real number and u=P1-P0 is a line direction vector.  Using this representation P(0)=P0, P(1)=P1, and when 0<s<1, P(s) is a point on the finite segment between P0 and P1 where s is the fraction of P(s)'s distance along the whole P0P1 line segment.  That is, s = d(P0,P(s)) / d(P0,P1).  Further, if s<0 then P(s) is outside the segment on the P0 side, and if s>1 then P(s) is outside the segment on the P1 side.

Let two lines be given by equations: P(s) = P0 + s (P1-P0) and Q(t) = Q0 + t (Q1-Q0), either or both of which could be a finite segment or a  ray.  These lines are parallel when and only when their directions are collinear, namely when the two vectors u=P1-P0 and v=Q1-Q0 are linearly related as u=Cv for some real number C.  For u=(uk) and v=(vk), this means that all ratios uk/vk have the same value, or that u1/v1=uk/vk for all k, which is equivalent to the conditions that all u1vk-ukv1=0.  In 2D, with u=(u1,u2) and v=(v1,v2), this is the  perp product [Hill, 1994] condition u^ · v = u1v2-u2v1 = 0  where u^=(-u2,u1) is perpendicular to u ("^" is called the perp operator).   This condition says that two lines in the Euclidean plane are parallel when they are both perpendicular to the same direction u^.  When true, the two lines are either coincident or do not intersect at all.

Coincidence is easily checked by testing if a point on one line, say P0, also lies on the other line Q(t).   That is, there exists a t0 such that: P0=Q(t0)=Q0+t0v.  If w=(wk)=P0-Q0, then this is equivalent to the condition that w=t0v for some t0 which is the same as w1vk-wkv1=0 for all k.  In 2D, this is the perp product condition: w^ · v = w1v2-w2v1 = 0.  If these conditions hold, one can solve for t0, and the infinite lines are coincident.  If one line (but not the other) is a finite segment, then it is the coincident intersection.  However, if both lines are finite segments, then they may (or may not) overlap.  In this case, solve for t0 and t1 such that P0=Q(t0) and P1=Q(t1).  If the segment intervals [t0,t1] and [0,1] are disjoint, then there is no intersection.  Otherwise, intersect the intervals (using max and min operations) to get [r0,r1] = [t0,t1] Ç [0,1].  Then the intersection segment is Q(r0)Q(r1) = P0P1 Ç Q0Q1.  This works in any dimension.

When the two lines are not parallel, they may intersect in a unique point.  In 2D Euclidean space, they always intersect.  In higher dimensions they may miss each other and not intersect.  But if they do intersect, then their linear projections onto a 2D plane will also intersect.  So, one can simply restrict to two coordinates (for which u or v are nonzero), compute the 2D intersection point I at P(sI) and Q(tI) for those two coordinates, and then test if P(sI)=Q(tI) for all coordinates.  To compute the 2D intersection point I=P(sI)=Q(tI), consider the two lines and the associated vectors in the diagram:

To determine sI, we have the vector equality P(s)-Q0 = w + su  where w=P0-Q0.   At the intersection point, the vector P(s)-Q0 is perpendicular to v^, and this is equivalent to the perp product condition that v^ · (w+su) = 0.  Solving this equation, we get:

Note that the denominator v^ · u = 0 only when the lines are parallel as previously discussed.  Similarly, solving for Q(tI), we get:

The denominators are the same up to sign, since u^·v = -v^·u, and should only be computed once if we want to know both sI and tI.

If one of the two lines is a finite segment (or a ray), say P0P1, then the intersect point is inside the segment only when 0<=sI<=1 (or sI>=0 for a ray).  If both lines are segments, then both solution parameters, sI and tI, must be in the [0,1] interval for the segments to intersect.  Although this sounds simple enough, the code for the intersection of two segments is a bit delicate since many special cases need to be checked (see our implementation intersect2D_2Segments()).

Plane Intersections

Planes are represented as shown in Algorithm 4 about the Distance of a Point to a Plane.

Line-Plane Intersection

In 3D, a line L is either parallel to a plane p or intersects it in a single point.  Let L be given by the parametric equation: P(s) = P0 + s(P1-P0) = P0 + su, and the plane p be given by the point V0 on it and it's normal vector n.  We first check if L is parallel to p by testing if n·u=0 which means that the line direction vector u is perpendicular to the plane normal n.  If this is true, then L and p are parallel and either never intersect or else L lies totally in the plane p.  Disjointness or coincidence can be determined by testing whether any specific point of L, say P0, is contained in p, that is whether it satisfies the implicit line equation: (P-V0) = 0.

If the line and plane are not parallel, then L and p intersect in a unique point P(sI) which is computed using a method similar to the one for the intersection of two lines in 2D.  Consider the diagram:

At the intersect point, the vector P(s)-V0 = w+su,  where w=P0-V0, is perpendicular to n.  This is equivalent to the dot product condition: n · (w+su) = 0.  Solving we get:

If the line L is a finite segment from P0 to P1, then one just has to check that 0<=sI<=1 to verify that there is an intersection between the segment and the plane.  For a positive ray, there is an intersection with the plane when sI>=0.

Intersection of 2 Planes

In 3D, two planes p1 and p2 are either parallel or they intersect in a single straight line L.  Let pi (i=1,2) be given by a point Vi plus a normal vector ni, and have an implicit equation: ni · P + di = 0.  The planes p1 and p2 are parallel whenever their normal vectors n1 and n2 are parallel, and this is equivalent to the condition that: n1×n2 = 0.  In software, one usually tests if  |n1×n2| < d  where division by d would cause overflow, and uses this as the robust condition for n1 and n2 being parallel.  When not parallel, u = n1×n2 ³ d > 0 is a direction vector for the intersection line L since u is perpendicular to both n1 and n2, and thus is parallel to both planes as shown in the following diagram.  If |u| is small, it is best to scale it so that |u| = 1, making u a unit direction vector.

After computing n1×n2 (3 adds + 6 multiplies), to fully determine the intersection line, we still need to find a specific point on it; that is, we need to find a point P0=(x0,y0,z0) that lies in both planes.  We can do this by finding a common solution of the implicit equations for p1 and p2.  But there are only two equations in the 3 unknowns since the point P0 can lie anywhere on the 1-dimensional line L which is another degree of freedom.  So we need another constraint to solve for a specific P0.  There are a number of ways this could be done:

(A) Direct Linear Equation.  One could set one coordinate to zero, say z0=0, and then solve for the other two.  But this will only work when L intersects the plane z0=0.  This is will be true when the z-coordinate uz of u =(ux,uy,uz) is nonzero.  So, one must first select a nonzero coordinate of u, and then set that coordinate of P0 = 0.  One should choose the coordinate with the largest absolute value, as this will give the most robust computations.  Suppose that uz¹0, then P0=(x0,y0,0) lies on L.  Solving the two equations: a1x0+b1y0+d1=0 and a2x0+b2y0+d2=0, we get:

and the parametric equation of L is:

The denominator here is equal to the non-zero 3rd coordinate of u.  So, ignoring the test for a nonzero coordinate, and equating division with multiplication, the total operations for this solution = 5 adds + 12 multiplies.

(B) Line Intersect Point.  If one knows a specific line in one plane (for example, two points in the plane), and this line intersects the other plane, then its point of intersection "I" will lie in both planes.  Thus, this will be a point on the line of intersection for the two planes, and the parametric equation of L is: P(s) = I + s (n1×n2).  To compute n1×n2 and the intersection point (given the line) has a total number of operations = 11 adds + 19 multiplies.

One way of constructing a line in one plane that must intersect the other plane is to project one plane's normal vector onto the other plane.  This gives a line that must always be orthogonal to the line of the planes' intersection.  So, the projection of n2 on p1 defines a line that intersects p2 in the sought for point P0 on L.  More specifically, project the two points 0=(0,0,0) and n2=(nx2,ny2,nz2) to p1(0) and p1(n2) respectively.  Then the projected line in p1 is L1: Q(t) = p1(0) + t p1(n2), and intersection of it with p2 can be computed.  In the most efficient case, where both n1 and n2 are unit normal vectors and the constant p1(0) is pre-stored, the total operations = 17 adds + 22 multiplies.

(C) 3 Plane Intersect Point.  One can select a third plane p3 with an implicit equation n3 · P = 0 where the normal vector n3 = n1×n2 and d3=0 (meaning it passes through the origin).  This always works since: (1) L is perpendicular to p3 and thus intersects it, and (2) the vectors n1, n2, and n3 are linearly independent.  Thus the planes p1, p2 and p3 intersect in a unique point P0 which must be on L.  Using the formula for the intersection of 3 planes (see the next section), where d3=0 for p3, we get:

and the parametric equation of L is:

.

The number of operations for this solution = 11 adds + 23 multiplies.

This formula can be simplified when n1 and n2 are unit normal vectors, and n1 · n2 = cos q where q is the angle between these vectors.  Then, n1 · n1 = n2 · n2 = 1, and |n1×n2|2 = sin2 q = 1 - cos2 q = 1 - (n1 · n2)2 for the denominator.  Finally, we use the identity for right-association of the cross-product; namely, for any three (3D) vectors a, b, and c, it is always true that: a×(b×c) = (a · c)b - (a · b)c.  Applying this to the numerator, and simplifying, we get: (d2n1-d1n2)×(n1×n2) = (d2 cos q - d1) n1 + (d1 cos q - d2) n2 .  Thus,

This solution expresses the point P0 on L as a linear combination of the vectors n1 and n2 that are a basis spanning the plane p3 through the origin.  The number of operations = 11 adds + 20 multiplies.

But the bottom line is that the most efficient method is the direct solution (A) that only uses 5 adds + 12 multiplies to compute the equation of the intersection line.

[Footnote: I would like to acknowledge a fruitful email exchange with Nick Polyayev in July 2001 discussing alternative methods for determining the intersection line for 2 planes.  This resulted in a greatly improved presentation of this section.]

Intersection of 3 Planes

In 3D, three planes p1, p2 and p3 can intersect (or not) in the following ways:

Geometric Relation Intersection Algebraic Condition
All 3 planes are parallel nj×nk=0 for all j,k=1,3
They coincide A plane n1·V1=n1·V2=n1·V3
They are disjoint None n1·V1¹n1·V2¹n1·V3¹n1·V1

Just two planes are parallel, and
the 3rd plane cuts each in a line
[Note: the 2 parallel planes may coincide]

2 parallel lines
[coincide => 1 line]
Only one nj×nk=0 for j¹k

No two planes are parallel, so pairwise they intersect in 3 lines nj×nk¹0 for all j¹k
The intersection lines are parallel n1·(n2×n3)=0
They coincide 1 line test a point from one line
They are disjoint 3 parallel lines
No intersection lines are parallel A unique point n1·(n2×n3)¹0

As shown in the illustrations:

One should test for the most frequent case of a unique intersect point first, namely that n1·(n2×n3)¹0, since this excludes the other cases.  When the intersection is a unique point, it is given by the formula:

which can verified by showing that this P0 satisfies the parametric equations for p1, p2 and p3.

However, there can be a problem with the robustness of this computation when the denominator n1·(n2×n3) is very small.  In this case, it would be best to get a robust line of intersection for two of the planes, and then compute the point where this line intersects the third plane.


Implementations

Here are some sample "C++" implementations of these algorithms. 

// Copyright 2001, softSurfer (www.softsurfer.com)
// This code may be freely used and modified for any purpose
// providing that this copyright notice is included with it.
// SoftSurfer makes no warranty for this code, and cannot be held
// liable for any real or imagined damage resulting from its use.
// Users of this code must verify correctness for their application.

// Assume that classes are already given for the objects:
//    Point and Vector with
//        coordinates {float x, y, z;}
//        operators for:
//            == to test equality
//            != to test inequality
//            Point  = Point ± Vector
//            Vector = Point - Point
//            Vector = Scalar * Vector    (scalar product)
//            Vector = Vector * Vector    (3D cross product)
//    Line and Ray and Segment with defining points {Point P0, P1;}
//        (a Line is infinite, Rays and Segments start at P0)
//        (a Ray extends beyond P1, but a Segment ends at P1)
//    Plane with a point and a normal {Point V0; Vector n;}
//===================================================================

#define SMALL_NUM  0.00000001 // anything that avoids division overflow
// dot product (3D) which allows vector operations in arguments
#define dot(u,v)   ((u).x * (v).x + (u).y * (v).y + (u).z * (v).z)
#define perp(u,v)  ((u).x * (v).y - (u).y * (v).x)  // perp product (2D)
 

// intersect2D_2Segments(): the intersection of 2 finite 2D segments
//    Input:  two finite segments S1 and S2
//    Output: *I0 = intersect point (when it exists)
//            *I1 = endpoint of intersect segment [I0,I1] (when it exists)
//    Return: 0=disjoint (no intersect)
//            1=intersect in unique point I0
//            2=overlap in segment from I0 to I1
int
intersect2D_Segments( Segment S1, Segment S2, Point* I0, Point* I1 )
{
    Vector    u = S1.P1 - S1.P0;
    Vector    v = S2.P1 - S2.P0;
    Vector    w = S1.P0 - S2.P0;
    float     D = perp(u,v);

    // test if they are parallel (includes either being a point)
    if (fabs(D) < SMALL_NUM) {          // S1 and S2 are parallel
        if (perp(u,w) != 0 || perp(v,w) != 0) {
            return 0;                   // they are NOT collinear
        }
        // they are collinear or degenerate
        // check if they are degenerate points
        float du = dot(u,u);
        float dv = dot(v,v);
        if (du==0 && dv==0) {           // both segments are points
            if (S1.P0 != S2.P0)         // they are distinct points
                return 0;
            *I0 = S1.P0;                // they are the same point
            return 1;
        }
        if (du==0) {                    // S1 is a single point
            if (inSegment(S1.P0, S2) == 0)  // but is not in S2
                return 0;
            *I0 = S1.P0;
            return 1;
        }
        if (dv==0) {                    // S2 a single point
            if (inSegment(S2.P0, S1) == 0)  // but is not in S1
                return 0;
            *I0 = S2.P0;
            return 1;
        }
        // they are collinear segments - get overlap (or not)
        float t0, t1;                   // endpoints of S1 in eqn for S2
        Vector w2 = S1.P1 - S2.P0;
        if (v.x != 0) {
                t0 = w.x / v.x;
                t1 = w2.x / v.x;
        }
        else {
                t0 = w.y / v.y;
                t1 = w2.y / v.y;
        }
        if (t0 > t1) {                  // must have t0 smaller than t1
                float t=t0; t0=t1; t1=t;    // swap if not
        }
        if (t0 > 1 || t1 < 0) {
            return 0;     // NO overlap
        }
        t0 = t0<0? 0 : t0;              // clip to min 0
        t1 = t1>1? 1 : t1;              // clip to max 1
        if (t0 == t1) {                 // intersect is a point
            *I0 = S2.P0 + t0 * v;
            return 1;
        }

        // they overlap in a valid subsegment
        *I0 = S2.P0 + t0 * v;
        *I1 = S2.P0 + t1 * v;
        return 2;
    }

    // the segments are skew and may intersect in a point
    // get the intersect parameter for S1
    float     sI = perp(v,w) / D;
    if (sI < 0 || sI > 1)               // no intersect with S1
        return 0;

    // get the intersect parameter for S2
    float     tI = perp(u,w) / D;
    if (tI < 0 || tI > 1)               // no intersect with S2
        return 0;

    *I0 = S1.P0 + sI * u;               // compute S1 intersect point
    return 1;
}
//===================================================================

// inSegment(): determine if a point is inside a segment
//    Input:  a point P, and a collinear segment S
//    Return: 1 = P is inside S
//            0 = P is not inside S
int
inSegment( Point P, Segment S)
{
    if (S.P0.x != S.P1.x) {    // S is not vertical
        if (S.P0.x <= P.x && P.x <= S.P1.x)
            return 1;
        if (S.P0.x >= P.x && P.x >= S.P1.x)
            return 1;
    }
    else {    // S is vertical, so test y coordinate
        if (S.P0.y <= P.y && P.y <= S.P1.y)
            return 1;
        if (S.P0.y >= P.y && P.y >= S.P1.y)
            return 1;
    }
    return 0;
}
//===================================================================

// intersect3D_SegmentPlane(): intersect a segment and a plane
//    Input:  S = a segment, and Pn = a plane = {Point V0; Vector n;}
//    Output: *I0 = the intersect point (when it exists)
//    Return: 0 = disjoint (no intersection)
//            1 = intersection in the unique point *I0
//            2 = the segment lies in the plane
int
intersect3D_SegmentPlane( Segment S, Plane Pn, Point* I )
{
    Vector    u = S.P1 - S.P0;
    Vector    w = S.P0 - Pn.V0;

    float     D = dot(Pn.n, u);
    float     N = -dot(Pn.n, w);

    if (fabs(D) < SMALL_NUM) {          // segment is parallel to plane
        if (N == 0)                     // segment lies in plane
            return 2;
        else
            return 0;                   // no intersection
    }
    // they are not parallel
    // compute intersect param
    float sI = N / D;
    if (sI < 0 || sI > 1)
        return 0;                       // no intersection

    *I = S.P0 + sI * u;                 // compute segment intersect point
    return 1;
}
//===================================================================

// intersect3D_2Planes(): the 3D intersect of two planes
//    Input:  two planes Pn1 and Pn2
//    Output: *L = the intersection line (when it exists)
//    Return: 0 = disjoint (no intersection)
//            1 = the two planes coincide
//            2 = intersection in the unique line *L
int
intersect3D_2Planes( Plane Pn1, Plane Pn2, Line* L )
{
    Vector   u = Pn1.n * Pn2.n;         // cross product
    float    ax = (u.x >= 0 ? u.x : -u.x);
    float    ay = (u.y >= 0 ? u.y : -u.y);
    float    az = (u.z >= 0 ? u.z : -u.z);

    // test if the two planes are parallel
    if ((ax+ay+az) < SMALL_NUM) {       // Pn1 and Pn2 are near parallel
        // test if disjoint or coincide
        Vector   v = Pn2.V0 - Pn1.V0;
        if (dot(Pn1.n, v) == 0)         // Pn2.V0 lies in Pn1
            return 1;                   // Pn1 and Pn2 coincide
        else
            return 0;                   // Pn1 and Pn2 are disjoint
    }

    // Pn1 and Pn2 intersect in a line
    // first determine max abs coordinate of cross product
    int      maxc;                      // max coordinate
    if (ax > ay) {
        if (ax > az)
             maxc = 1;
        else maxc = 3;
    }
    else {
        if (ay > az)
             maxc = 2;
        else maxc = 3;
    }

    // next, to get a point on the intersect line
    // zero the max coord, and solve for the other two
    Point    iP;               // intersect point
    float    d1, d2;           // the constants in the 2 plane equations
    d1 = -dot(Pn1.n, Pn1.V0);  // note: could be pre-stored with plane
    d2 = -dot(Pn2.n, Pn2.V0);  // ditto

    switch (maxc) {            // select max coordinate
    case 1:                    // intersect with x=0
        iP.x = 0;
        iP.y = (d2*Pn1.n.z - d1*Pn2.n.z) / u.x;
        iP.z = (d1*Pn2.n.y - d2*Pn1.n.y) / u.x;
        break;
    case 2:                    // intersect with y=0
        iP.x = (d1*Pn2.n.z - d2*Pn1.n.z) / u.y;
        iP.y = 0;
        iP.z = (d2*Pn1.n.x - d1*Pn2.n.x) / u.y;
        break;
    case 3:                    // intersect with z=0
        iP.x = (d2*Pn1.n.y - d1*Pn2.n.y) / u.z;
        iP.y = (d1*Pn2.n.x - d2*Pn1.n.x) / u.z;
        iP.z = 0;
    }
    L->P0 = iP;
    L->P1 = iP + u;
    return 2;
}
//===================================================================


References

Foley, van Dam, Feiner & Hughes, Computer Graphics (2nd Edition in C), Section 3.12 "Clipping Lines" (1996)

Francis Hill, "The Pleasures of 'Perp Dot' Products" in Graphics Gems IV (1994)
[Note: the first critical definition has a typo, and should be: a^ = (-ay, ax).]

Michael Mortenson, Computer Graphics Handbook : Geometry and Mathematics (1990)

Joseph O'Rourke, Computational Geometry in C (2nd Edition), Chapter 7 "Search and Intersection" (1998)


Copyright © 2001-2006 softSurfer.  All rights reserved.
Email comments and suggestions to
feedback@softsurfer.com