高效的数学算法来计算交叉点

时间:2008-12-22 01:20:50

标签: algorithm math performance intersection lines

对于我正在开发的游戏,我需要一种可以计算交叉点的算法。我已经解决了这个问题,但我这样做的方式真的很讨厌,我希望这里有人能有更优雅的解决方案。

一对点表示它们之间绘制的线的终点。给定两对点,绘制的线是否相交,如果是,在什么时候?

所以例如调用行(A.x,A.y) - (B.x,B.y)和(C.x,C.y) - (D.x,D.y)

有人能想到解决方案吗?任何语言的解决方案都可以。

编辑:我应该更清楚一点,如果交叉点超出了线段的长度,算法必须返回false。

9 个答案:

答案 0 :(得分:60)

这里的大多数答案似乎都遵循以下一般观点:

  1. 找到通过给定点的两条直线的交点。
  2. 确定交叉点是否属于两个线段。
  3. 但是当交叉不经常发生时,更好的方法可能是扭转这些步骤:

    1. y = ax + b (线路经过A,B)和 y = cx + d (线路经过C,D)的形式表示直线
    2. 查看 y = ax + b
    3. 的C和D是否在的同一侧
    4. 查看A和B在 y = cx + d
    5. 的同一侧是
    6. 如果上述答案都是,则 是一个交叉点。否则没有交集。
    7. 找到交叉点,如果有的话。
    8. 注意:要执行步骤2,只需检查(C.y - a(C.x) - b)和(D.y - a(D.x) - b)是否具有相同的符号。第3步类似。第5步只是两个方程式的标准数学运算。

      此外,如果您需要将每个线段与(n-1)个其他线段进行比较,则对所有线路预先计算步骤1可节省您的时间。

答案 1 :(得分:17)

如果你有机会,你应该真正检查碰撞检测圣经,“实时碰撞检测”,如果你计划做任何非平凡的事情。我不是一个专业的游戏程序员,我理解并且可以毫不费力地应用它中的概念。

enter image description here

Amazon - Real Time Collision Detection

基本上,无论如何,进行一系列线路交叉测试都很昂贵。你所做的是在复杂的多边形上使用诸如边界框(轴对齐或方向)之类的东西。这将允许您快速执行最坏情况O(N ^ 2)检查每个“对象”之间的冲突。然后,您可以通过仅使用彼此靠近的对象的交叉点来使用空间树(二进制分区或四叉树)来进一步加快速度。

这允许您修剪许多碰撞测试。最好的优化根本就没有做任何事情。只有在边界框之间发生碰撞时,才会执行昂贵的线交叉以确定对象是否真正相交。这允许您在保持速度合理的同时缩放对象的数量。

答案 2 :(得分:13)

float x12 = x1 - x2;
float x34 = x3 - x4;
float y12 = y1 - y2;
float y34 = y3 - y4;

float c = x12 * y34 - y12 * x34;

if (fabs(c) < 0.01)
{
  // No intersection
  return false;
}
else
{
  // Intersection
  float a = x1 * y2 - y1 * x2;
  float b = x3 * y4 - y3 * x4;

  float x = (a * x34 - b * x12) / c;
  float y = (a * y34 - b * y12) / c;

  return true;
}

公式取自: Line-line intersection - Wikipedia

答案 3 :(得分:3)

public struct PointD
{
    public double X { get; set; }
    public double Y { get; set; }
}

/// <summary>
/// Find the intersection point between two lines.
/// </summary>
/// <param name="IntersectPoint">The intersection point. A <see cref="Esteem.Geometry.PointD">PointD</see> structure.</param>
/// <param name="L1StartPoint">The starting point of first line. A PointD structure.</param>
/// <param name="L1EndPoint">The end point of first line. A PointD structure.</param>
/// <param name="L2StartPoint">The starting point of second line. A PointD structure.</param>
/// <param name="L2EndPoint">The end point of second line. A PointD structure.</param>
/// <param name="L1IntersectPos">The intersection position at first line.</param>
/// <param name="L2IntersectPos">The intersection position at second line.</param>
/// <returns>Returns a boolean. True if there is intersection, false otherwise.</returns>
/// <remarks>The formula is taken from comp.graphics.algorithms Frequently Asked Questions.</remarks>
public static bool LineIntersect(out PointD IntersectPoint, PointD L1StartPoint, PointD L1EndPoint, PointD L2StartPoint, PointD L2EndPoint, out double L1IntersectPos, out double L2IntersectPos) 
{
    IntersectPoint = new PointD();
    double ay_cy, ax_cx, px, py;
    double dx_cx = L2EndPoint.X - L2StartPoint.X,
        dy_cy = L2EndPoint.Y - L2StartPoint.Y,
        bx_ax = L1EndPoint.X - L1StartPoint.X,
        by_ay = L1EndPoint.Y - L1StartPoint.Y;

    double de = (bx_ax) * (dy_cy) - (by_ay) * (dx_cx);
    //double tor = 1.0E-10;     //tolerance


    L1IntersectPos = 0;      L2IntersectPos = 0;
    if (Math.Abs(de)<0.01)
        return false;
    //if (de > -tor && de < tor) return false; //line is in parallel

    ax_cx = L1StartPoint.X - L2StartPoint.X;
    ay_cy = L1StartPoint.Y - L2StartPoint.Y;
    double r = ((ay_cy) * (dx_cx) - (ax_cx) * (dy_cy)) / de;
    double s = ((ay_cy) * (bx_ax) - (ax_cx) * (by_ay)) / de;
    px = L1StartPoint.X + r * (bx_ax);
    py = L1StartPoint.Y + r * (by_ay);

    IntersectPoint.X = px;  //return the intersection point
    IntersectPoint.Y = py;  //return the intersection position
    L1IntersectPos = r;      L2IntersectPos = s;

    return true; //indicate there is intersection
}

要检查交叉点是否超出行的原始长度,请确保0<r<10<s<1

答案 4 :(得分:3)

可以节省大量时间的简单优化是在进入交点计算之前检查线的轴对齐边界框。
如果边界框完全不相交,那么您可以确定没有交叉点 当然,这取决于您拥有的数据。如果你在每张支票上都很可能有一个交叉点,那么你可能会发现自己在一张总是正确的支票上浪费时间。

答案 5 :(得分:2)

下面是MathWorld中描述的我的直线交叉点。对于一般碰撞检测/交叉,您可能需要查看Separating Axis Theorem。我在SAT上发现this tutorial非常有用。

    /// <summary>
    /// Returns the intersection point of the given lines. 
    /// Returns Empty if the lines do not intersect.
    /// Source: http://mathworld.wolfram.com/Line-LineIntersection.html
    /// </summary>
    public static PointF LineIntersection(PointF v1, PointF v2, PointF v3, PointF v4)
    {
        float tolerance = 0.000001f;

        float a = Det2(v1.X - v2.X, v1.Y - v2.Y, v3.X - v4.X, v3.Y - v4.Y);
        if (Math.Abs(a) < float.Epsilon) return PointF.Empty; // Lines are parallel

        float d1 = Det2(v1.X, v1.Y, v2.X, v2.Y);
        float d2 = Det2(v3.X, v3.Y, v4.X, v4.Y);
        float x = Det2(d1, v1.X - v2.X, d2, v3.X - v4.X) / a;
        float y = Det2(d1, v1.Y - v2.Y, d2, v3.Y - v4.Y) / a;

        if (x < Math.Min(v1.X, v2.X) - tolerance || x > Math.Max(v1.X, v2.X) + tolerance) return PointF.Empty;
        if (y < Math.Min(v1.Y, v2.Y) - tolerance || y > Math.Max(v1.Y, v2.Y) + tolerance) return PointF.Empty;
        if (x < Math.Min(v3.X, v4.X) - tolerance || x > Math.Max(v3.X, v4.X) + tolerance) return PointF.Empty;
        if (y < Math.Min(v3.Y, v4.Y) - tolerance || y > Math.Max(v3.Y, v4.Y) + tolerance) return PointF.Empty;

        return new PointF(x, y);
    }

    /// <summary>
    /// Returns the determinant of the 2x2 matrix defined as
    /// <list>
    /// <item>| x1 x2 |</item>
    /// <item>| y1 y2 |</item>
    /// </list>
    /// </summary>
    public static float Det2(float x1, float x2, float y1, float y2)
    {
        return (x1 * y2 - y1 * x2);
    }

答案 6 :(得分:2)

(我会留下这个评论,除了我还没弄明白怎么做。)

我想补充一点,作为边界框测试的替代/补充,您还可以测试线的中点之间的距离是否大于线的总长度的一半。如果是这样,线条可能不会相交。

想象一下每条线的最小封闭圆,然后测试圆形交叉点。这允许预先计算(至少对于静态线和保持其长度的线)以及排除许多潜在交叉点的有效方式。

答案 7 :(得分:1)

那么,数学和标量产品可以帮到那里 - 假设你想要分段[AB]和[CD]
- 假设线的线交叉是M

当且仅当为时,M在段[ÅB]内   矢量(AB)。矢量(AM)&gt; = 0

  矢量(AB)。向量(MB)&gt; = 0

点“。”表示标量积:u。 v = ux * vx + uy * vy

所以,你的算法是:
- 找到M
- 当且仅当下面的4 eq为真时,M在两个段内     矢量(AB)。矢量(AM)&gt; = 0
    矢量(AB)。矢量(MB)&gt; = 0
    矢量(CD)。矢量(CM)&gt; = 0
    矢量(CD)。矢量(MD)> = 0

希望这有帮助

答案 8 :(得分:0)

private function Loop(e:Event):void
{
    var x12:Number = Ball1.x - Ball2.x;
    var x34:Number = Ball3.x - Ball4.x;
    var y12:Number = Ball1.y - Ball2.y;
    var y34:Number = Ball3.y - Ball4.y;

    // Det
    var c:Number = x12 * y34 - y12 * x34;

    if (Math.abs(c) < 0.01)
    {
        Circle.visible = false;
    }
    else
    {
        var a:Number = Ball1.x * Ball2.y - Ball1.y * Ball2.x;
        var b:Number = Ball3.x * Ball4.y - Ball3.y * Ball4.x;
        var px:Number = (a * x34 - b * x12) / c;
        var py:Number = (a * y34 - b * y12) / c;

        var Btwn12x:Boolean = (px >= Math.min(Ball1.x, Ball2.x)) && (px <= Math.max(Ball1.x, Ball2.x));
        var Btwn12y:Boolean = (py >= Math.min(Ball1.y, Ball2.y)) && (py <= Math.max(Ball1.y, Ball2.y));
        var Btwn34x:Boolean = (px >= Math.min(Ball3.x, Ball4.x)) && (px <= Math.max(Ball3.x, Ball4.x));
        var Btwn34y:Boolean = (py >= Math.min(Ball3.y, Ball4.y)) && (py <= Math.max(Ball3.y, Ball4.y));

        var Btwn12:Boolean = Btwn12x && Btwn12y;
        var Btwn34:Boolean = Btwn34x && Btwn34y;

        if(Btwn12 && Btwn34)
        {
            Circle.visible = true;
            Circle.x = px;
            Circle.y = py;
        }
        else
        {
            Circle.visible = false;
        }
    }
}