【问题标题】:Line Segement Intersection — works sometimes, sometimes not线段相交——有时有效,有时无效
【发布时间】:2020-03-27 09:59:16
【问题描述】:

我想显示两条线段的交点。分段是动画的,因此它们根据进度开始和停止相交。

所以我有这个代码:

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

我在代码中的某处这样使用:

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
}

事实上,这些交叉点»闪烁«,意味着它们有时有效,有时无效。我在这里缺少什么,我想我在这里遇到了数字问题?

由于@Gilles-Philippe Paillé 在这里对用于计算交点的代码的评论。我住在另一个 Helper 类中,看起来像这样:

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

【问题讨论】:

  • @Gilles-PhilippePaillé 需要 contains 方法,因为只有在两个段都包含该点时才会显示该点。否则它将始终显示,除非该行是线性相关的
  • 抱歉,我删除了评论,因为我意识到dot 做得更多。我明白你的意思。

标签: javascript geometry computational-geometry


【解决方案1】:

一个更简单的解决方案是检查一个段的末端是否相对于另一段位于不同的半平面上,反之亦然。这不需要除法:

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

如果您需要包含边界点和/或共线段的交点,事情会更烦人(另请注意,即使使用整数坐标,两个线段的交点也可能无法在没有近似值的情况下用浮点数精确表示 - 例如 @987654322 @ 和(0, 1)-(10, 1))。

【讨论】:

  • 听起来很有趣!因此,首先检查这些段是否完全相交,而不是检查它们的端点是否在彼此段的不同侧..?
  • @philipp: 如果 A 和 B 相对于 CD 线在不同的半平面上,并且 C/D 相对于 AB 线在不同的半平面上,那么 AB 和 CD 两个线段有一个路口。反之亦然(如果它们相交,则每个线段的端点相对于另一线段的线位于不同的半平面上)。如果您只需要检查两个线段是否相交,则无需计算相交,您只能使用乘法/加法/减法。如果需要交点,还需要除法。
【解决方案2】:

dot 变量实际上是行列式(或二维叉积)。问题是行列式可能是负数。因此,您需要测试行列式的绝对值。此外,Number.EPSILON 是最小的非零数,在数值不准确的情况下没有用处。您应该改用更合理的值:

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

此外,行列式应该使用分割点来计算,而不是边界框的最小值/最大值:

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

【讨论】:

  • 那行不通...我的dot 计算错了吗??
  • @philipp 我添加了一个更正。告诉我它是否有效。
  • 既然我用交叉点进行测试,点不应该总是为零吗?交点在线上?我被扔石头了吗 :) 我的代码不是在挑起数字问题吗?
  • 理论上是的。这实际上取决于您是否要保持 contains 方法独立于 Intersect 方法而不假设该点在线。我还添加了关于 epsilon 值的更正。
  • @philipp 太棒了!我的荣幸。
【解决方案3】:

线截距

以下函数将截距(如果有)作为由每条线上的两个点 {p1:{x,y}, p2{x,y}} 定义的两条线的点 {x, y}

前两个函数用于线段(每个都有固定长度),后两个函数是无限长的线。

线段截距

作为 4 个点 p1 = {x,y}p2 = {x,y}p3 = {x,y}p4 = {x,y} 定义两条线段的端点。仅在有拦截时返回点,否则返回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 两行l1 = {p1:{x,y}, p2{x,y}}l2 = {p1:{x,y}, p2{x,y}} 仅在有截距时才返回点。

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

截断线

类似于上述函数,但假设行是无限长的。如果线平行,将返回一个点或undefined

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 作为l1 行,l2 仅在有截距时才返回点。如果线平行,则返回 undefined

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

【讨论】:

    猜你喜欢
    • 2015-02-11
    • 2012-08-02
    • 2015-03-31
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多