8

Given two 2D line segments, A and B, how do I calculate the length of the shortest 2D line segment, C, which connects A and B?

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • Define connect. Do you mean connect their ends, or find the shortest line segment between any point on the lines? – strager Feb 12 '09 at 13:10
  • @strager, in Euclidean geometry, they're either parallel or the endpoints are closer, so you check vectors A1-B1, A1-B2, A2-B1 and A2-B2. – paxdiablo Feb 12 '09 at 13:15
  • 2D or 3D? The 2D case is almost trivial, while the 3D case needs more complex algorithms. – fbonnet Feb 12 '09 at 13:15
  • @Pax: I see no evidence to the contrary. Whatever happened to “innocent until proven guilty”? – Konrad Rudolph Feb 12 '09 at 13:20
  • Line segments have endpoints so even if they are not parallel, the are not garanteed to intersect: L1: (0,0) - (2,2); L2: (3,3) - (0,5). – Toon Krijthe Feb 12 '09 at 13:22
  • 1
    The closest point must be one of the end points of one of the line segments. Can I somehow use the distances from that end point to each end point of the other segment, to create a ratio value which I can use to interpolate along the other segment? –  Feb 12 '09 at 13:42
  • possible duplicate of [Shortest distance between two line segments](http://stackoverflow.com/questions/2824478/shortest-distance-between-two-line-segments) – Veedrac Jun 09 '14 at 15:09

7 Answers7

7

Consider your two line segments A and B to be represented by two points each:

line A represented by A1(x,y), A2(x,y)

Line B represented by B1(x,y) B2(x,y)

First check if the two lines intersect using this algorithm.

If they do intersect, then the distance between the two lines is zero, and the line segment joining them is the intersection point.

If they do not intersect, Use this method: http://paulbourke.net/geometry/pointlineplane/ to calculate the shortest distance between:

  1. point A1 and line B
  2. Point A2 and line B
  3. Point B1 and line A
  4. Point B2 and line A

The shortest of those four line segments is your answer.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
Alterlife
  • 6,557
  • 7
  • 36
  • 49
  • Additionally: first check for A and B crossing each other (A1 + vector (A1->A2) * a = B1 + vector (B1->B2) * b with a and b real numbers != 0). Second check for A and B being parallel (i.e. vector (A1->A2) * a = vector (B1->B2) with being a real number != 0). – Bodo Thiesen Feb 12 '09 at 14:40
  • I don't belive this answer is correct. for one as per above if the lines cross the distance shortest between lines is zero. yet the shortest distance from any end point to the other line > 0. – David Waters Feb 12 '09 at 16:53
  • You're both right: it's necessary to check for intersection. However, I think it's unnecessary to check if the lines are parallel. – Alterlife Feb 13 '09 at 04:07
  • I think the intersection check needed is whether the line *segments* intersect, not the extended lines (which usually will). But it's not clear how to determine that ("this algorithm" isn't given?), even after checking the link for the point-to-line(-segment) determination. – Rob Parker Feb 13 '09 at 23:06
  • @BodoThiesen Your check for intersection requires checking that both a and b are between 0 and 1, otherwise the intersection is off the end of the line segments. – Eyal Jan 08 '18 at 19:26
4

This page has information you may be looking for.

strager
  • 88,763
  • 26
  • 134
  • 176
  • an answer for 3d will work for 2d, 2d just a special case for 3d where z always == 0. so in the sudo code at the bottom z(i) == z(j) == 0 – David Waters Feb 12 '09 at 16:50
3

Using the general idea of Afterlife's and Rob Parker's algorithms above, here's a C++ version of a set of methods to get the minimum distance between 2 arbitrary 2D segments. This will handle overlapping segments, parallel segments, intersecting and non-intersecting segments. In addition, it uses various epsilon values to protect against floating point imprecision. Finally, in addition to returning the minimum distance, this algorithm will give you the point on segment 1 nearest to segment 2 (which is also the intersection point if the segments intersect). It would be pretty trivial to also return the point on [p3,p4] nearest to [p1,p2] if so desired, but I'll leave that as an exercise for the reader :)

// minimum distance (squared) between vertices, i.e. minimum segment length (squared)
#define EPSILON_MIN_VERTEX_DISTANCE_SQUARED 0.00000001

// An arbitrary tiny epsilon.  If you use float instead of double, you'll probably want to change this to something like 1E-7f
#define EPSILON_TINY 1.0E-14

// Arbitrary general epsilon.  Useful for places where you need more "slop" than EPSILON_TINY (which is most places).
// If you use float instead of double, you'll likely want to change this to something like 1.192092896E-04
#define EPSILON_GENERAL 1.192092896E-07

bool AreValuesEqual(double val1, double val2, double tolerance)
{
    if (val1 >= (val2 - tolerance) && val1 <= (val2 + tolerance))
    {
        return true;
    }

    return false;
}


double PointToPointDistanceSquared(double p1x, double p1y, double p2x, double p2y)
{
    double dx = p2x - p1x;
    double dy = p2y - p1y;
    return (dx * dx) + (dy * dy);
}


double PointSegmentDistanceSquared( double px, double py,
                                    double p1x, double p1y,
                                    double p2x, double p2y,
                                    double& t,
                                    double& qx, double& qy)
{
    double dx = p2x - p1x;
    double dy = p2y - p1y;
    double dp1x = px - p1x;
    double dp1y = py - p1y;
    const double segLenSquared = (dx * dx) + (dy * dy);
    if (AreValuesEqual(segLenSquared, 0.0, EPSILON_MIN_VERTEX_DISTANCE_SQUARED))
    {
        // segment is a point.
        qx = p1x;
        qy = p1y;
        t = 0.0;
        return ((dp1x * dp1x) + (dp1y * dp1y));
    }
    else
    {
        t = ((dp1x * dx) + (dp1y * dy)) / segLenSquared;
        if (t <= EPSILON_TINY)
        {
            // intersects at or to the "left" of first segment vertex (p1x, p1y).  If t is approximately 0.0, then
            // intersection is at p1.  If t is less than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t >= -EPSILON_TINY)
            {
                // intersects at 1st segment vertex
                t = 0.0;
            }
            // set our 'intersection' point to p1.
            qx = p1x;
            qy = p1y;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
        }
        else if (t >= (1.0 - EPSILON_TINY))
        {
            // intersects at or to the "right" of second segment vertex (p2x, p2y).  If t is approximately 1.0, then
            // intersection is at p2.  If t is greater than that, then there is no intersection (i.e. p is not within
            // the 'bounds' of the segment)
            if (t <= (1.0 + EPSILON_TINY))
            {
                // intersects at 2nd segment vertex
                t = 1.0;
            }
            qx = p2x;
            qy = p2y;
            // Note: If you wanted the ACTUAL intersection point of where the projected lines would intersect if
            // we were doing PointLineDistanceSquared, then qx would be (p1x + (t * dx)) and qy would be (p1y + (t * dy)).
        }
        else
        {
            // The projection of the point to the point on the segment that is perpendicular succeeded and the point
            // is 'within' the bounds of the segment.  Set the intersection point as that projected point.
            qx = ((1.0 - t) * p1x) + (t * p2x);
            qy = ((1.0 - t) * p1y) + (t * p2y);
            // for debugging
            //ASSERT(AreValuesEqual(qx, p1x + (t * dx), EPSILON_TINY));
            //ASSERT(AreValuesEqual(qy, p1y + (t * dy), EPSILON_TINY));
        }
        // return the squared distance from p to the intersection point.
        double dpqx = px - qx;
        double dpqy = py - qy;
        return ((dpqx * dpqx) + (dpqy * dpqy));
    }
}


double SegmentSegmentDistanceSquared(   double p1x, double p1y,
                                        double p2x, double p2y,
                                        double p3x, double p3y,
                                        double p4x, double p4y,
                                        double& qx, double& qy)
{
    // check to make sure both segments are long enough (i.e. verts are farther apart than minimum allowed vert distance).
    // If 1 or both segments are shorter than this min length, treat them as a single point.
    double segLen12Squared = PointToPointDistanceSquared(p1x, p1y, p2x, p2y);
    double segLen34Squared = PointToPointDistanceSquared(p3x, p3y, p4x, p4y);
    double t = 0.0;
    double minDist2 = 1E+38;
    if (segLen12Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
    {
        qx = p1x;
        qy = p1y;
        if (segLen34Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
        {
            // point to point
            minDist2 = PointToPointDistanceSquared(p1x, p1y, p3x, p3y);
        }
        else
        {
            // point - seg
            minDist2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y);
        }
        return minDist2;
    }
    else if (segLen34Squared <= EPSILON_MIN_VERTEX_DISTANCE_SQUARED)
    {
        // seg - point
        minDist2 = PointSegmentDistanceSquared(p3x, p3y, p1x, p1y, p2x, p2y, t, qx, qy);
        return minDist2;
    }

    // if you have a point class and/or methods to do cross products, you can use those here.
    // This is what we're actually doing:
    // Point2D delta43(p4x - p3x, p4y - p3y);    // dir of p3 -> p4
    // Point2D delta12(p1x - p2x, p1y - p2y);    // dir of p2 -> p1
    // double d = delta12.Cross2D(delta43);
    double d = ((p4y - p3y) * (p1x - p2x)) - ((p1y - p2y) * (p4x - p3x));
    bool bParallel = AreValuesEqual(d, 0.0, EPSILON_GENERAL);

    if (!bParallel)
    {
        // segments are not parallel.  Check for intersection.
        // Point2D delta42(p4x - p2x, p4y - p2y);    // dir of p2 -> p4
        // t = 1.0 - (delta42.Cross2D(delta43) / d);
        t = 1.0 - ((((p4y - p3y) * (p4x - p2x)) - ((p4y - p2y) * (p4x - p3x))) / d);
        double seg12TEps = sqrt(EPSILON_MIN_VERTEX_DISTANCE_SQUARED / segLen12Squared);
        if (t >= -seg12TEps && t <= (1.0 + seg12TEps))
        {
            // inside [p1,p2].   Segments may intersect.
            // double s = 1.0 - (delta12.Cross2D(delta42) / d);
            double s = 1.0 - ((((p4y - p2y) * (p1x - p2x)) - ((p1y - p2y) * (p4x - p2x))) / d);
            double seg34TEps = sqrt(EPSILON_MIN_VERTEX_DISTANCE_SQUARED / segLen34Squared);
            if (s >= -seg34TEps && s <= (1.0 + seg34TEps))
            {
                // segments intersect!
                minDist2 = 0.0;
                qx = ((1.0 - t) * p1x) + (t * p2x);
                qy = ((1.0 - t) * p1y) + (t * p2y);
                // for debugging
                //double qsx = ((1.0 - s) * p3x) + (s * p4x);
                //double qsy = ((1.0 - s) * p3y) + (s * p4y);
                //ASSERT(AreValuesEqual(qx, qsx, EPSILON_MIN_VERTEX_DISTANCE_SQUARED));
                //ASSERT(AreValuesEqual(qy, qsy, EPSILON_MIN_VERTEX_DISTANCE_SQUARED));
                return minDist2;
            }
        }
    }

    // Segments do not intersect.   Find closest point and return dist.   No other way at this
    // point except to just brute-force check each segment end-point vs opposite segment.  The
    // minimum distance of those 4 tests is the closest point.
    double tmpQx, tmpQy, tmpD2;
    minDist2 = PointSegmentDistanceSquared(p3x, p3y, p1x, p1y, p2x, p2y, t, qx, qy);
    tmpD2 = PointSegmentDistanceSquared(p4x, p4y, p1x, p1y, p2x, p2y, t, tmpQx, tmpQy);
    if (tmpD2 < minDist2)
    {
        qx = tmpQx;
        qy = tmpQy;
        minDist2 = tmpD2;
    }
    tmpD2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y, t, tmpQx, tmpQy);
    if (tmpD2 < minDist2)
    {
        qx = p1x;
        qy = p1y;
        minDist2 = tmpD2;
    }
    tmpD2 = PointSegmentDistanceSquared(p2x, p2y, p3x, p3y, p4x, p4y, t, tmpQx, tmpQy);
    if (tmpD2 < minDist2)
    {
        qx = p2x;
        qy = p2y;
        minDist2 = tmpD2;
    }

    return minDist2;
}
Community
  • 1
  • 1
devnullicus
  • 83
  • 1
  • 6
  • This doesn't compile, the line `minDist2 = PointSegmentDistanceSquared(p1x, p1y, p3x, p3y, p4x, p4y);` expects additional arguments. – Richard Oct 17 '17 at 05:05
  • Ah, sorry about that - I apparently forgot to include the version of the method that doesn't need t, qx, and qy. Since those are just return args anyway, you can just add dummy values there and ignore the results returned. – devnullicus Oct 20 '17 at 04:42
2

Gernot Hoffmann paper (algorithm and Pascal code):

http://www.fho-emden.de/~hoffmann/xsegdist03072004.pdf

Nicolai
  • 3,698
  • 3
  • 30
  • 34
2

Quick tip: if you want to compare distances based on points, it's not necessary to do the square roots.

E.g. to see if P-to-Q is a smaller distance than Q-to-R, just check (pseudocode):

square(P.x-Q.x) + square(P.y-Q.y) < square(Q.x-R.x) + square(Q.y-R.y)
joel.neely
  • 30,725
  • 9
  • 56
  • 64
2

Afterlife's said, "First check if the two lines intersect using this algorithm," but he didn't indicate what algorithm he meant. Obviously, it's the intersection of the line segments not the extended lines which matters; any non-parallel line segments (excluding coincident endpoints which don't define a line) will intersect, but the distance between the line segments would not necessarily be zero. So I assume he meant "line segments" rather than "lines" there.

The link Afterlife gave is a very elegant approach to finding the closest point on a line (or line segment, or ray) to another arbitrary point. This works for finding the distance from each endpoint to the other line segment (constraining the calculated parameter u to be no less than 0 for a line segment or ray and to be no more than 1 for a line segment), but it doesn't handle the possiblity that an interior point on one line segment is closer than either endpoint because they actually intersect, thus the extra check about intersection is required.

As for the algorithm for determining line-segment intersection, one approach would be to find the intersection of the extended lines (if parallel then you're done), and then determine whether that point is within both line segments, such as by taking the dot-product of the vectors from the intersection point, T, to the two endpoints:

((Tx - A1x) * (Tx - A2x)) + ((Ty - A1y) * (Ty - A2y))

If this is negative (or "zero") then T is between A1 and A2 (or at one endpoint). Check similarly for the other line segment. If either was greater than "zero" then the line segments do not intersect. Of course, this depends on finding the intersection of the extended lines first, which may require expressing each line as an equation and solving the system by Gaussian reduction (etc).

But there may be a more direct way without having to solve for the intersection point, by taking the cross-product of the vectors (B1-A1) and (B2-A1) and the cross product of the vectors (B1-A2) and (B2-A2). If these cross-products are in the same direction, then A1 and A2 are on the same side of line B; if they are in opposite directions, then they are on opposite sides of line B (and if 0, then one or both are on line B). Similarly check the cross-products of vectors (A1-B1) and (A2-B1) and of (A1-B2) and (A2-B2). If any of these cross-products is "zero", or if the endpoints of both line segments fall on opposite sides of the other line, then the line segments themselves must intersect, otherwise they do not intersect.

Of course, you need a handy formula for computing a cross-product of two vectors from their coordinates. Or if you could determine the angles (being positive or negative), you wouldn't need the actual cross-product, since it's the direction of the angles between the vectors which we actually care about (or the sine of the angle, really). But I think the formula for cross-product (in 2-D) is simply:

Cross(V1,V2) = (V1x * V2y) - (V2x * V1y)

This is the z-axis component of the 3-D cross-product vector (where the x and y components must be zero, because the initial vectors are in the plane z=0), so you can simply look at the sign (or "zero").

So, you could use one of these two methods to check for line-segment intersection in the algorithm Afterlife describes (referencing the link).

Rob Parker
  • 4,078
  • 1
  • 25
  • 26
0

This page has a nice short description for finding the shortest distance between two lines, although @strager's link includes some code (in Fortran!)

Ian Hopkinson
  • 3,412
  • 4
  • 24
  • 28