Search code examples
javascriptgeometrycomputational-geometry

Line Segement Intersection — works sometimes, sometimes not


I'd like to display the point of intersection of two line segments. The Segments are animated, so they start and stop to intersect, based on progress.

Therefore I have this code:

class LineSegment {
  constructor(x1,y1,x2,y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
  }

  contains (x,y) {
    const
      x1 = Math.min(this.x1, this.x2),
      y1 = Math.min(this.y1, this.y2), 
      x2 = Math.max(this.x1, this.x2),
      y2 = Math.max(this.y1, this.y2),
      dot = ((x - x1) * (y2 - y1)) - ((y - y1) * (x2 - x1))
    ;

    return  dot <= Number.EPSILON && 
            x >= x1 && x <= x2 && 
            y >= y1 && y <= y2;
  }
}

Somewhere in the code I use that like this:

const 
  seg1 = new LineSegment(…),
  seg2 = new LineSegment(…),
  i    = Intersect(seg1, seg2), //working code that calculates x and y values
                                //for the »unbounded« intersection
  contains = i !== null &&
             seg1.contains(i.x, i.y) &&
             seg2.contains(i.x, i.y)
;

if (contains) {
  //show a circle around x and y

} else {
  //remove that one
}

In fact, those intersections »flicker«, means they sometimes work and sometimes not. What am I missing here, I guess I am running into numerical issues here?

Due to the comment of @Gilles-Philippe Paillé here to code used to calculate the intersection. I lives in another Helper class and looks like this:

intersect ({ a: a2, b: b2, c: c2 }) {
  const 
    {
      a:a1, 
      b:b1, 
      c:c1
    } = this,
    denom = det(a1, a2, b1, b2)
  ;

  //only chuck norris can devide by zero!
  return denom == 0 ?
    null :
    [ -1 * det(b1, c1, b2, c2) / denom,
           det(a1, c1, a2, c2) / denom ];
  }

Solution

  • The dot variable is in reality the determinant (or the 2D cross product). The problem is that the determinant can be negative. Thus you need to test the absolute value of the determinant. Moreover, Number.EPSILON is the smallest non-zero number, which is not useful is case of numerical inaccuracy. You should instead use a more reasonable value:

    Math.abs(dot) <= 1e-8
    

    Moreover, the determinant should be calculated using the segment point, not the bounding box min/max:

    dot = ((x - this.x1) * (this.y2 - this.y1)) - ((y - this.y1) * (this.x2 - this.x1))