web-dev-qa-db-ja.com

線交差検出アルゴリズム

ユーザーが描いた線が交差しているかどうかを検出しようとしています。線の各ウェイポイントを通過し、線内の他の点と交差するかどうかをチェックする線交差検出アルゴリズムを使用しています。

これはコストのかかる方法であり、パフォーマンスの問題を引き起こし始めています。この問題に推奨されるアルゴリズムは何ですか?そのコード例はありますか?

線は、画面上のユーザーの動きによって収集された座標であるwayPointsで表されます。

ここまで読んでくれてありがとう!すべての助けに感謝します!

    for i in 1 ..<(wayPoints.count-1) {
            for j in 0 ..< i-1 {
                   if let intersection = intersectionBetweenSegments(wayPoints[i], wayPoints[i+1], wayPoints[j], wayPoints[j+1]){
  ...}
   }
  }

    func intersectionBetweenSegments(p0: CGPoint, _ p1: CGPoint, _ p2: CGPoint, _ p3: CGPoint) -> CGPoint? {
        var denominator = (p3.y - p2.y) * (p1.x - p0.x) - (p3.x - p2.x) * (p1.y - p0.y)
        var ua = (p3.x - p2.x) * (p0.y - p2.y) - (p3.y - p2.y) * (p0.x - p2.x)
        var ub = (p1.x - p0.x) * (p0.y - p2.y) - (p1.y - p0.y) * (p0.x - p2.x)
        if (denominator < 0) {
            ua = -ua; ub = -ub; denominator = -denominator
        }

        if ua >= 0.0 && ua <= denominator && ub >= 0.0 && ub <= denominator && denominator != 0 {
            return CGPoint(x: p0.x + ua / denominator * (p1.x - p0.x), y: p0.y + ua / denominator * (p1.y - p0.y))
        }

        return nil
    }
2
user594883

これは元々、説明を求めるコメントですが、長すぎて収まりません。そこで、ここに各質問を投稿し、考えられる回答に基づいて各質問に対する「仮定」の回答を投稿しました。


説明を求める前に、現在の実装にはウェイポイントの数が2次の時間計算量があることを誰もが確認する必要があります。表記はO(N^2)です。

簡単に言うと、ウェイポイントの数が2倍(2倍に増加)になると、実行時間は約4倍(4倍に増加)になります。


(1)処理する必要のあるウェイポイントの一般的な数はいくつですか?一般的な数がない場合は、コードがサポートする必要のあるウェイポイントの可能な最大数を記述します。

たとえば、N(ウェイポイントの数)が数百未満のときにパフォーマンスの問題が発生しない場合は、マウスの動きがそのピクセル数を超えてはならないというソフトウェア制限を課すことができます。これが許容できるかどうかは、ソフトウェアの目的によって異なります。


(2)交差点をチェックする前にウェイポイントの数を減らすために 線の簡略化手法 を使用できますか?

ライン簡略化手法では、マウスの動きを入力として使用すると、ウェイポイントの数を10分の1(10分の1)以上(10分の1以下)に減らすことができます。この縮小を失敗させるには、ユーザーはマウスを必死に動かす必要があります。


(3)QuadTree を自分で実装して使用する方法を知っていますか?

クワッドツリーを使用すると、平均的なケースのO(log N)クエリを、クエリの境界ボックスと重なる境界ボックスのコレクション(ツリー)に取り込むことができます。ペアごとに重複するすべての境界ボックスを見つけるには、最初にすべての境界ボックスを追加してから、各境界ボックスに対してクエリを実行します。これにより、平均的なケースO(N log N)の合計が得られます。これは、元の実装であるO(N^2)よりも優れています。


(4)サポートする必要のある移動領域の幅と高さ(ピクセル単位)はどれくらいですか?

総移動領域(2乗ピクセル、つまり幅と高さの積)が数百万未満であり、数百万バイト(またはビット)を保持できる配列を割り当てることが許可されている場合、それぞれが必要なだけです要素がtrueまたはfalse)の場合、ビットマップ(文字通りビットのマップ)として機能するようにそのような配列を割り当てることができます。

ビットマップはfalseで初期化されます。次に、ユーザーがマウスの動きで「ペイント」すると、ビットマップ内の対応するピクセルがtrueに設定されます。

警告。ユーザーのピクセルトレイルの幅が1ピクセル(1 x 1)しかない場合、次のエッジケースが発生することに注意してください。

トレースが(10, 10)から(11, 11)に移動するとします。トレースの後半で、(10, 11)から(11, 10)に移動してそのセグメントを通過します。ピクセル自体はオーバーラップしないため、このビットマップベースのスキームはこの種の交差を検出できません。

警告の解決策は、以前にペイントされたピクセルがないか3行3列の近傍をチェックする必要があることです。これを行う間、新しくペイントされたばかりのピクセルを無視することも忘れないでください。この警告の回避に役立つ追加情報を格納するために、(ピクセルあたり1ビットではなく)ピクセルあたり1バイトを使用しなければならない可能性があります。

1
rwong

これはコストのかかる方法であり、パフォーマンスの問題を引き起こし始めています。この問題に推奨されるアルゴリズムは何ですか?そのコード例はありますか?

それはあなたが線自体をどのように表現するかに大きく依存します。直線の並置である破線を使用しているようです。

私の頭に浮かぶ最初のアイデアは、単純な領域チェックを行うことです。各行を小さな長方形(境界ボックス)に詰めることができ、2つの境界ボックスが重なっているかどうかを非常に簡単に確認できます。これは、行列式を計算するよりもはるかに安価な、いくつかの減算を行うことで実行できます。領域チェックが失敗した場合、線は確かに交差しないため、行列式を計算する必要はありません。

これにより、複雑さ(nˆ4)から(nˆ2)にジャンプする必要があります。ここで、nは直線内の小さな直線の数です。

これでは不十分な場合、2番目の一般的な手法は、キャンバスを領域に分割し、この領域を二分木として表すことです。キャンバス全体から始めて、半分にカットし、事前に選択した分割数に達するまで、各半分を2つに何度も分割します。 (経験的テストを実行して、データに適した値を決定できます。)次に、各線分をその境界ボックスが収まる領域にアタッチできます。通常は葉ですが、線分が地域の境界。リージョンツリーに各線分をパックすると、交差が発生する可能性のある線分のneighboursを簡単に取得できます。