修改圆形 - 无限线碰撞以使用线段

时间:2014-12-09 04:07:18

标签: c# math intersection

好吧,所以我需要的是如果一条线穿过圆圈,它返回true。如果直线穿过圆圈,它也会返回true。一切都是假的。基本上是一个线段

以下是我尝试过的一些代码:

示例:

        Vector2 pointA, pointB;
        pointA = locationA;
        pointB = locationB;

        pointA.Normalize();
        pointB.Normalize();

        float dx = pointB.X - pointA.X;
        float dy = pointB.Y - pointA.Y;

        float dr = (float)Math.Sqrt((dx * dx) + (dy * dy));

        float D = (pointA.X * pointB.Y) - (pointB.X * pointA.Y);

        float delta = (circle.radius * circle.radius) * (dr * dr) - (D * D);

        if (delta > 0) return true;
        else return false;

或者

        float dx = locationB.X - locationA.X;
        float dy = locationB.Y - locationA.Y;

        float a = (dx * dx) + (dy * dy);

        float b = 2 * (dx * (locationA.X - circle.position.X) +
                       dy * (locationA.Y - circle.position.Y));

        float c = (circle.position.X * circle.position.X) +
                  (circle.position.Y * circle.position.Y);

        c += (locationA.X * locationA.X) +
             (locationA.Y * locationA.Y);

        c -= 2 * (circle.position.X * locationA.X + circle.position.Y * locationA.Y);
        c -= circle.radius * circle.radius;

        float delta = b * b - 4 * a * c;

        if (delta < 0)
            return false;
        else
            return true;

我试过这段代码以为它是针对线段的,但经过测试后,我意识到这是用于无限线检测。

有没有人知道有这个数学/伪/代码的网站?我所有的谷歌搜索都没有用。同样在Wolfram上,我只能找到无限的线圈碰撞。

非常感谢! Shyy,

编辑:

更新了尝试:

        Vector2 d = locationB - locationA;
        Vector2 p = locationA - circle.position;

        Vector2 x = locationA + d;
        Vector2 radius = x - circle.position;

        Vector2 rSq = (d * d) + (p * d) + (d * p) + (p * p);

        Vector2 t = (d * d) + 2 * (p * d) + (p * p) - (radius * radius);

        Vector2 u = d * d;
        Vector2 v = 2 * (p * d);
        Vector2 w = (p * p) - (radius * radius); // circle.radius did not work does not work here, as radius was float. Converted to Vector2 above.

        Vector2 r = (u * (t * t)) + v * t + w;

2 个答案:

答案 0 :(得分:3)

假设您的线段位于点ab之间。 将其视为parametric equation。第一,

    let d = b - a

所以你的线段描述如下:

    a + td for all real numbers between 0 and 1

包含它的无限行描述如下:

    a + td for all real numbers

因为如果t为负数或> 1,您将继续在同一行,但不再在该段内。

同时,对于圆边上的任何点,以下情况都是正确的:

    length(point - center) = radius

现在我们有了定义无限线和圆边的方程。如果它们之间有交叉点x,则x必须满足两个等式:

    x = a + td for some real number t

    length(x - center) = radius

将两者放在一起,我们正在寻找满足以下等式的t值:

    length(a + td - center) = radius

关于这个问题公式的好处是,如果我们找到一个交集,我们就会知道它的t值,所以测试它是否在段内只需要更多的工作。


如果我们定义p = a - center,那么我们的等式变得更简单:

    length(td + p) = radius

扩展矢量长度的定义并将两边平方:

    sqrt((td + p) · (td + p)) = radius

    (td + p) · (td + p) = radius²

由点积的分布:

    (td + p) · td + (td + p) · p = radius²

    (td · td) + (p · td) + (td · p) + (p · p) = radius²

做一些代数来展示t

    t² (d · d) + 2t (p · d) + (p · p) - radius² = 0

让我们通过为所有非t术语定义变量来简化它,这些术语都是标量,而不是向量:

    let u = d · d
        v = 2 (p · d)
        w = (p · p) - radius²

所以现在我们的等式是:

    ut² + vt + w = 0

这是二次方程。我们可以使用quadratic formula来解决它。


因为我们的二次方程式是t,所以它的根将是t的值,我们可以将其插回到参数线方程中。

如果二次方程没有实根,则无限直线根本不与圆边相交,因此该段肯定不会。

如果它有一个或两个实根,则该线与圆边相交,但该段可能不相交。对于每个根r,我们需要检查是否

    0 <= r <= 1

如果是这样,那么通过我们的参数化定义,该段与

处的圆相交
    a + rd

自然地,如果方程有2个根,这对两个都是正确的,那么圆和线段在2个位置相交。


在代码中实现此数学运算,从最后向后工作以确定需要计算的值。你的目标是解决二次方程ut² + vt + w = 0。这将为您提供0,1或2个根。然后,如果至少有一个根位于true范围内,则会返回0 <= t <= 1

因此,从uvw的定义开始向后工作,直到您获得ab,{{1}的所有内容}和center - 函数的输入。


由于OP无法将上面的数学转换为代码,因此这里有一些代码。

radius

正如@Cimbali指出的那样,这种方法不会检测到该段何时完全位于圆内。如果您需要识别该案例,您只需检查是否:

Vector2 d = locationB - locationA;
Vector2 p = locationA - circle.position;
double u = d*d;
double v = 2*p*d;
double w = p*p - circle.radius*circle.radius;

// apply quadratic formula
// NOTE: THIS CODE IS PROBABLY NOT NUMERICALLY STABLE.
// LOOK IN A NUMERICAL ANALYSIS BOOK
// IF YOU NEED TO SOLVE THIS PROBLEM FOR REAL.
double discriminant = v*v - 4*u*w;
if (discriminant < 0) {
    // no real roots, infinite line does not intersect circle
}
else if (discriminant == 0) {
    // 1 real root, infinite line is tangent to circle.
    // if tangent lines don't count, then return false here. otherwise...
    double t = -v / (2*u);
    return (t >= 0 && t <= 1);
}
else {
    // 2 real roots, infinite line intersects circle
    double t0 = (-v + Math.Sqrt(discriminant)) / (2*u);
    double t1 = (-v - Math.Sqrt(discriminant)) / (2*u);
    return (t0 >= 0 && t0 <= 1) || (t1 >= 0 && t1 <= 1);
}

但也可能有更少的计算工作量。

答案 1 :(得分:1)

作为对上一个答案(japreiss')的补充,您可以看到该段是否包含在圆圈中,没有更多的数学和一些额外的逻辑。

您可以使用相同的推理和符号来ut² + vt + w <= 0,您想要验证[0,1]中的所有t。

如果它们存在,您解决​​了它的根r1r2。你知道,在两端,你的多项式发散到无穷大(因为u > 0)所以它在根之间是负的。这也意味着多项式总是非负的,少于两个根。因此,如果r1 <= 0 && r2 >= 1完全包含它。

因此,初始表达u,v和w保持不变,并解决二阶多项式的根。然后,最终的逻辑是:

  • 如果没有真正的根,则返回false
  • 如果是单个根r,则返回true iff r >= 0 || r <= 1
  • 如果两个根r1r2(通过构造r1 < r2)返回false iff r1 > 1 || r2 < 0

实际上,这两个测试错误(至少)根在[0,1]中,因此该段与圆相交,或者如果没有,那么[r1,r2]必然与[0,1]重叠,意思是该段包含在圆圈中。