0

I have created a function to calculate the intersection point of two line segment .

Unfortunantly the code below dosen't work if one of the segment is verticale

    public static Point intersection(Segment s1, Segment s2) {
    double x1 = s1.getP1().getX();
    double y1 = s1.getP1().getY() ;
    double x2 = s1.getP2().getX();
    double y2 = s1.getP2().getY() ;
    double x3 = s2.getP1().getX();
    double y3 = s2.getP1().getY();
    double x4 = s2.getP2().getX();
    double y4 = s2.getP2().getY();

    double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
    if (d == 0) {
        return null;
    }
    double xi = ((x3 - x4) * (x1 * y2 - y1 * x2) - (x1 - x2) * (x3 * y4 - y3 * x4)) / d;
    double yi = ((y3 - y4) * (x1 * y2 - y1 * x2) - (y1 - y2) * (x3 * y4 - y3 * x4)) / d;
    Point p = new Point(xi, yi);
    if (xi < Math.min(x1, x2) || xi > Math.max(x1, x2)) {
        return null;
    }
    if (xi < Math.min(x3, x4) || xi > Math.max(x3, x4)) {
        return null;
    }
    return p;
}

the problem when i have a vertical line segment , this formula

double d = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);

is equal to 0 and the method return null.

How can I handle this exception.

Thank you

user3521250
  • 115
  • 3
  • 14
  • While your question is readable and answerable, there are still some things to improve upon, including explaining each non-obvious variable and including what you've tried so far. You'll get an answer eventually but please see http://stackoverflow.com/help/how-to-ask in the meantime – snickers10m Apr 24 '15 at 17:44
  • 1
    Just check for the vertical case first and deal with it separately. – Paul Boddington Apr 24 '15 at 17:55
  • is there another formula to handle this case? what i want to do is to add some micro value to one of the coordinate , so i will not have a multiplication by 0 , then i will get the intersection point but with some lose of precision – user3521250 Apr 24 '15 at 18:13
  • This formula don't bother about vertical segment. d is not zero when only one of the segments is vertical. d is equal to 0 when segments are parallel - treat this as special case. – MBo Apr 25 '15 at 03:07

3 Answers3

2

Line intersection without special cases

Coming from a background of projective geometry, I'd write the points in homogeneous coordinates:

v1 = [x1, y1, 1]
v2 = [x2, y2, 1]
v3 = [x3, y3, 1]
v4 = [x4, y4, 1]

Then both the line joining two points and the intersection of two lines can be expressed using the cross product:

[x5, y5, z5] = (v1 × v2) × (v3 × v4)

which you can dehomogenize to find the resulting point as

[x5/z5, y5/z5]

without having to deal with any special cases. If your lines are parallel, the last point would lead to a division by zero, though, so you might want to catch that case.

Restriction to segments

The above is for infinite lines, though. You might want to keep the code which returns null if the point of intersection falls outside the bounding box. But if you want real segments, that code is incorrect: you could have a point of intersection which lies outside one of the segments but still inside the bounding box.

A proper check can be implemented using an orientation-checking predicate. The determinant of three of the vectors vi given above will have positive sign if the triangle they form has one orientation, and negative sign for the opposite orientation. So the points v3 and v4 lie on different sides of s1 if

det(v1, v2, v3) * det(v1, v2, v4) < 0

and in a similar way v1 and v2 lie on different sides of s2 if

det(v3, v4, v1) * det(v3, v4, v2) < 0

so if both of these are satisfied, you have an intersection between the segments. If you want to include the segment endpoints, change the < to a in these inequalities.

MvG
  • 57,380
  • 22
  • 148
  • 276
  • This is great generalization. A nitpick: for restriction to segments, you also need to take in to account the special case where endpoint of one segment lies on another. See Introduction to Algorithms 3rd edition, Corman et al, pg 1017. – Shital Shah Aug 18 '15 at 22:45
0

I have tried code it without testing... I hope it works! ^^

public static Point intersection(Segment s1, Segment s2) {
    // Components of the first segment's rect.
    Point v1 = new Point(s1.p2.x - s1.p1.x, s1.p2.y - s1.p1.y); // Directional vector
    double a1 = v.y;
    double b1 = -v.x;
    double c1 = v1.x * s1.p1.y - s1.v.y * s1.p1.x;

    // Components of the second segment's rect.
    Point v2 = new Point(s2.p2.x - s2.p1.x, s2.p2.y - s2.p1.y);
    double a2 = v2.y;
    double b2 = -v2.x;
    double c2 = v2.x * s2.p1.y - s2.v.y * s2.p1.x;

    // Calc intersection between RECTS.
    Point intersection = null;
    double det = a1 * b2 - b1 * a2;
    if (det != 0) {
        intersection = new Point(
            (b2 * (-c1) - b1 * (-c2)) / det;
            (a1 * (-c2) - a2 * (-c1)) / det;
        );
    }

    // Checks ff segments collides.
    if (
        intersection != null &&
        (
            (s1.p1.x <= intersection.x && intersection.x <= s1.p2.x) ||
            (s1.p2.x <= intersection.x && intersection.x <= s1.p1.x)
        ) &&
        (
            (s1.p1.y <= intersection.y && intersection.y <= s1.p2.y) ||
            (s1.p2.y <= intersection.y && intersection.y <= s1.p1.y)
        ) &&
        (
            (s2.p1.x <= intersection.x && intersection.x <= s2.p2.x) ||
            (s2.p2.x <= intersection.x && intersection.x <= s2.p1.x)
        ) &&
        (
            (s2.p1.y <= intersection.y && intersection.y <= s2.p2.y) ||
            (s2.p2.y <= intersection.y && intersection.y <= s2.p1.y)
        )
    )
        return intersection;

    return null;
};
Wandeber
  • 376
  • 3
  • 10
0

Here's my answer. I have tested it to be accurate by making a loop that checks that the answer it gives is the same as the one given by the Boost geometry library and they agree on each test, though the one that I have written below is much much faster than the one in Boost. The test makes every possible line segment where x is and integer in [-3,2] and y is an integer in [-3,2], for all possible pairs of line segments.

The code below considers line segments that touch at endpoints to be intersecting. T-shaped intersections are also considered intersecting. The code is in c++ but would be easily adaptable to any language. It's based on a different stackoverflow answer but that answer did not handle endpoints correctly.

It uses the cross product method, which can report if a point is to the left or to the right of a given ray.

There are some optimizations to be made in the math but doing them showed no performance improvement over compilation with g++ -O2 and sometime the performance even decreased! The compiler is able to do those optimizations so I prefered to leave the code readable.

// is_left(): tests if a point is Left|On|Right of an infinite line.
//    Input:  three points p0, p1, and p2
//    Return: >0 for p2 left of the line through p0 and p1
//            =0 for p2 on the line
//            <0 for p2 right of the line
//    See: Algorithm 1 "Area of Triangles and Polygons"
//    This is p0p1 cross p0p2.
extern inline coordinate_type_fp is_left(point_type_fp p0, point_type_fp p1, point_type_fp p2) {
  return ((p1.x() - p0.x()) * (p2.y() - p0.y()) -
          (p2.x() - p0.x()) * (p1.y() - p0.y()));
}

// Is x between a and b, where a can be lesser or greater than b.  If
// x == a or x == b, also returns true. */
extern inline coordinate_type_fp is_between(coordinate_type_fp a,
                                            coordinate_type_fp x,
                                            coordinate_type_fp b) {
  return x == a || x == b || (a-x>0) == (x-b>0);
}

// https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
extern inline bool is_intersecting(const point_type_fp& p0, const point_type_fp& p1,
                                   const point_type_fp& p2, const point_type_fp& p3) {
  const coordinate_type_fp left012 = is_left(p0, p1, p2);
  const coordinate_type_fp left013 = is_left(p0, p1, p3);
  const coordinate_type_fp left230 = is_left(p2, p3, p0);
  const coordinate_type_fp left231 = is_left(p2, p3, p1);

  if (p0 != p1) {
    if (left012 == 0) {
      if (is_between(p0.x(), p2.x(), p1.x()) &&
          is_between(p0.y(), p2.y(), p1.y())) {
        return true; // p2 is on the line p0 to p1
      }
    }
    if (left013 == 0) {
      if (is_between(p0.x(), p3.x(), p1.x()) &&
          is_between(p0.y(), p3.y(), p1.y())) {
        return true; // p3 is on the line p0 to p1
      }
    }
  }
  if (p2 != p3) {
    if (left230 == 0) {
      if (is_between(p2.x(), p0.x(), p3.x()) &&
          is_between(p2.y(), p0.y(), p3.y())) {
        return true; // p0 is on the line p2 to p3
      }
    }
    if (left231 == 0) {
      if (is_between(p2.x(), p1.x(), p3.x()) &&
          is_between(p2.y(), p1.y(), p3.y())) {
        return true; // p1 is on the line p2 to p3
      }
    }
  }
  if ((left012 > 0) == (left013 > 0) ||
      (left230 > 0) == (left231 > 0)) {
    if (p1 == p2) {
      return true;
    }
    return false;
  } else {
    return true;
  }
}

The test code:

BOOST_AUTO_TEST_CASE(small, *boost::unit_test::disabled()) {
  for (double x0 = -3; x0 < 3; x0++) {
    for (double y0 = -3; y0 < 3; y0++) {
      for (double x1 = -3; x1 < 3; x1++) {
        for (double y1 = -3; y1 < 3; y1++) {
          for (double x2 = -3; x2 < 3; x2++) {
            for (double y2 = -3; y2 < 3; y2++) {
              for (double x3 = -3; x3 < 3; x3++) {
                for (double y3 = -3; y3 < 3; y3++) {
                  point_type_fp p0{x0, y0};
                  point_type_fp p1{x1, y1};
                  point_type_fp p2{x2, y2};
                  point_type_fp p3{x3, y3};
                  linestring_type_fp ls0{p0,p1};
                  linestring_type_fp ls1{p2,p3};
                  BOOST_TEST_INFO("intersection: " << bg::wkt(ls0) << " " << bg::wkt(ls1));
                  BOOST_CHECK_EQUAL(
                      path_finding::is_intersecting(p0, p1, p2, p3),
                      bg::intersects(ls0, ls1));
                }
              }
            }
          }
        }
      }
    }
  }
}
Eyal
  • 5,728
  • 7
  • 43
  • 70