Line Segement Intersection — works sometimes, sometimes not

自古美人都是妖i 提交于 2021-01-28 17:39:50

问题


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 ];
  }

回答1:


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))



回答2:


A simpler solution is to check if one segment ends are on different half-planes in respect to the other segment and vice-versa. This requires no divisions:

function side(a, b, p) {
    return (p.x - a.x)*(b.y - a.y) + (p.y - a.y)*(a.x - b.x);
}

function xsect(a0, b0, a1, b1) {
    return (side(a0, b0, a1) * side(a0, b0, b1) < 0 &&
            side(a1, b1, a0) * side(a1, b1, b0) < 0)
}

Things are more annoying if you need to include boundary points and/or collinear segments intersection (note also that the intersection point of two segments even with integer coordinates may be impossible to represent exactly with floating point numbers without approximation - e.g. (0, 0)-(1, 10) with (0, 1)-(10, 1)).




回答3:


Line intercepts

The following functions finds the intercept, if any, as a point {x, y} of two lines defined by two points {p1:{x,y}, p2{x,y}} on each line.

The first two function are for line segments (each has a fixed length) and the last two functions are infinitely long lines.

Line segment intercept

As 4 points p1 = {x,y}, p2 = {x,y}, p3 = {x,y}, p4 = {x,y} defining end points of two line segments. Returns point only if there is an intercept, else return undefined.

function interceptSegs(p1, p2, p3, p4, p = {}) {
    const x1 = p2.x - p1.x, y1 = p2.y - p1.y;
    const x2 = p4.x - p3.x, y2 = p4.y - p3.y;
    const c = x1 * y2 - y1 * x2;
    if (c) {
        const x3 = p1.x - p3.x, y3 = p1.y - p3.y;
        const u = (x1 * y3 - y1 * x3) / c;
        if (u >= 0 && u <= 1) {
            const u = (x2 * y3 - y2 * x3) / c;
            if (u >= 0 && u <= 1) {
                p.x = p1.x + x1 * u;
                p.y = p1.y + y1 * u;    
                return p;
            }
        }
    }
}

OR As two lines l1 = {p1:{x,y}, p2{x,y}}, l2 = {p1:{x,y}, p2{x,y}} returns point only if there is an intercept.

function interceptSegs(l1, l2) {
    const a = {x: l1.p2.x - l1.p1.x, y: l1.p2.y - l1.p1.y};
    const b = {x: l2.p2.x - l2.p1.x, y: l2.p2.y - l2.p1.y};
    const c = a.x * b.y - a.y * b.x;
    if (c) {
        const e = {x: l1.p1.x - l2.p1.x, y: l1.p1.y - l2.p1.y};
        const u = (a.x * e.y - a.y * e.x) / c;
        if (u >= 0 && u <= 1) {
            const u = (b.x * e.y - b.y * e.x) / c;
            if (u >= 0 && u <= 1) {
                return {x: l1.p1.x + a.x * u, y: l1.p1.y + a.y * u};
            }
        }
    }
}

Intercept lines

Similar to above functions but assumes lines are infinitely long. Will return a point or undefined if lines are parallel.

function intercept(p1, p2, p3, p4, p = {}) {
    const x1 = p2.x - p1.x, y1 = p2.y - p1.y;
    const x2 = p4.x - p3.x, y2 = p4.y - p3.y;
    const c = x1 * y2 - y1 * x2;
    if (c) {
        let u = (x1 * (p1.y - p3.y) - y1 * (p1.x - p3.x)) / c;
        p.x = p3.x + x2 * u;
        p.y = p3.y + y2 * u;    
        return p;
    }
}

OR As lines l1, l2 returns point only if there is an intercept. Returns undefined if line are parallel.

function interceptLines(l1, l2) {
    const a = {x: l1.p2.x - l1.p1.x, y: l1.p2.y - l1.p1.y};
    const b = {x: l2.p2.x - l2.p1.x, y: l2.p2.y - l2.p1.y};
    const c = a.x * b.y - a.y * b.x;
    if (c) {
        const u = (a.x * (l1.p1.y - l2.p1.y) - a.y * (l1.p1.x - l2.p1.x)) / c;
        return {x: l2.p1.x + b.x * u, y: l2.p1.y + b.y * u};
    }
}


来源:https://stackoverflow.com/questions/58694842/line-segement-intersection-works-sometimes-sometimes-not

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!