web-dev-qa-db-ja.com

交差点を計算するための効率的な数学アルゴリズム

私が開発しているゲームには、交差点を計算できるアルゴリズムが必要です。私は問題を解決しましたが、私がそれをした方法は本当に厄介であり、私はここの誰かがよりエレガントな解決策を持っていることを望んでいます。

点のペアは、それらの間に描かれた線の終点を表します。 2組の点が与えられた場合、描画された線は交差しますか?交差する場合、どの点で交差しますか?

したがって、たとえば(A.x、A.y)-(B.x、B.y)および(C.x、C.y)-(D.x、D.y)の行を呼び出します

誰もが解決策を考えることができますか?あらゆる言語のソリューションで対応できます。

編集: 私が明確にすべき点は、交差点が線分の長さを超えている場合、アルゴリズムはfalseを返す必要があります。

37
Mitch

すでにここにある答えのほとんどは、次の一般的な考え方に従っているようです。

  1. 与えられた点を通る2本の直線の交点を見つけます。
  2. 交差が両方の線分に属するかどうかを判断します。

しかし、交差が頻繁に発生しない場合は、おそらくこれらの手順を逆にする方が良いでしょう。

  1. y = ax + b(A、Bを通過する線)およびy =の形式で直線を表すcx + d(C、Dを渡す行)
  2. cとDが同じ側 ofy = ax + bかどうかを確認
  3. aとBが同じ側 ofy = cx + dかどうかを確認
  4. 上記の答えが両方ともnoの場合、そこにis交差点があります。それ以外の場合、交差点はありません。
  5. 交差点がある場合はそれを見つけます。

注:ステップ2を実行するには、(Cy-a(C.x)-b)および(Dy-a(D.x)-b)が同じ記号、ステップ3も同様、ステップ5は2つの方程式の標準的な数学です。

さらに、各ラインセグメントを(n-1)個の他のラインセグメントと比較する必要がある場合、すべてのラインに対してステップ1を事前計算することで時間を節約できます。

62
PolyThinker

機会があれば、衝突検出のバイブル、「些細なことではないことをするつもりなら、リアルタイム衝突検出」を実際にチェックしてください。私はプロのゲームプログラマーではないので、理解し、ほとんど問題なくその概念を適用できました。

enter image description here

Amazon-リアルタイム衝突検出

基本的に、一連のライン交差テストを実行すると、何があっても費用がかかります。あなたがしているのは、複雑なポリゴンの上にバウンディングボックス(軸が整列または方向付けされている)などを使用することです。これにより、各「オブジェクト」間の衝突の最悪の場合のO(N ^ 2)チェックを迅速に行うことができます。その後、空間ツリー(Binary PartitioningまたはQuadTrees)を使用して、互いに近いオブジェクトの交差部分のみをチェックすることにより、さらに高速化できます。

これにより、多数の衝突テストをプルーニングできます。最適な最適化は、何もしないことです。境界ボックス間で衝突が発生した場合にのみ、高価な線の交差を行って、オブジェクトが本当に交差するかどうかを判断します。これにより、速度を適度に保ちながらオブジェクトの数を増やすことができます。

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

から取得した数式:
直線-交差点-ウィキペディア

13
Tamara Wijsman
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<1および0<s<1

3
Graviton

時間を大幅に節約できる簡単な最適化は、交差の計算に入る前に、線の軸に沿った境界ボックスをチェックすることです。
境界ボックスが完全にばらばらであれば、交差点がないことを確認できます。
もちろん、これはあなたが持っているデータに依存しています。すべてのチェックで交差点が存在する可能性が非常に高い場合は、常に正しいチェックに時間を浪費することになります。

3
shoosh

以下は、 MathWorld で説明されている私の線と線の交差点です。一般的な衝突検出/交差点については、 分離軸定理 をご覧ください。 SATの このチュートリアル が非常に有益であることがわかりました。

    /// <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);
    }
2
Ozgur Ozcitak

(これを行う方法をまだ理解していない場合を除き、コメントとして残しておきます。)

境界ボックステストの代替/補完として、ラインの中間点間の距離がラインの合計長の半分よりも大きいかどうかをテストすることもできます。その場合、線は交差しない可能性があります。

各線の最小の囲む円を想像して、円の交差をテストします。これにより、事前計算(少なくとも静的な行、およびその長さを保持する行)が可能になり、多くの潜在的な交差を除外する効率的な方法が可能になります。

2
Jo Are By

さて、数学とスカラー積がそこに役立ちます。
-セグメント[AB]と[CD]を交差させたいとします
-線の交点がMであるとします

Mがセグメント[ÅB]内にあるのは、次の場合のみ
Vector(AB)。 Vector(AM)> = 0
そして
Vector(AB)。ベクトル(MB)> = 0

どこにドット「。」はスカラー積を示します:u v = ux * vx + uy * vy

だから、あなたのアルゴリズムは:
-Mを見つける
-Mは、以下の4 eqが真である場合にのみ、両方のセグメント内にあります
Vector(AB)。 Vector(AM)> = 0
Vector(AB)。ベクトル(MB)> = 0
Vector(CD)。ベクトル(CM)> = 0
Vector(CD)。ベクトル(MD)> = 0

お役に立てれば

1
Pascal T.
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;
        }
    }
}
0
Galindo