ピクセルパーフェクトな衝突検出を回転で処理するにはどうすればよいですか?


8

誰かがAndroidでビットマップを使用して回転ピクセルパーフェクト衝突検出を達成する方法について何かアイデアを持っていますか?それとも一般的にはそれについてですか?私は現在ピクセル配列を持っていますが、任意の度数に基づいてそれらを操作する方法がわかりません。

回答:


11

私はAndroidに慣れていないので、あなたが自由に使えるツールはわかりませんが、これを一般的な用語で実装する方法を説明できます。それがどれほど簡単になるかは、Androidが提供する機能によって異なります。マトリックスが必要になるか、少なくとも計算が大幅に簡略化されます。

まず、境界ボックスの衝突チェックを行い、衝突しない場合は、それ以上の計算を回避するためにすぐに戻ります。境界ボックスが衝突しない場合、衝突するピクセルもないことが保証されているため、これは論理的です。

その後、ピクセルパーフェクトコリジョンチェックが必要な場合、最も重要なポイントは、そのチェックを同じ空間で実行する必要があることです。これは、スプライトAから各ピクセルを取得し、一連の変換を適用してそれらをスプライトBのローカル空間に取り込み、スプライトB上のその位置にあるピクセルと衝突するかどうかを確認することで実行できます。チェックは不透明です。

したがって、最初に必要なのは、各スプライトのワールドマトリックスを作成することです。おそらく、オンラインでチュートリアルを作成する方法を教えるチュートリアルがありますが、基本的には、いくつかの単純なマトリックスを次の順序で連結したものでなければなりません。

Translation(-Origin) * Scale * Rotation * Translation(Position)

このマトリックスの有用性は、ローカルスペース内のポイントを乗算することで(たとえばbitmap.getPixelAt(10,20)、10,20のようなメソッドを使用してピクセルをローカルスペースで定義した場合)、対応するワールドマトリックスによって、ワールドスペースに移動します。

LocalA * WorldMatrixA -> World
LocalB * WorldMatrixB -> World

また、マトリックスを反転すると、逆方向に移動することもできます。つまり、使用したマトリックスに応じて、ポイントをワールドスペースから各スプライトのローカルスペースに変換します。

World * InverseWorldMatrixA -> LocalA
World * InverseWorldMatrixB -> LocalB

したがって、スプライトAのローカルスペースからスプライトBのローカルスペースにポイントを移動するには、まずスプライトAのワールドマトリックスを使用してポイントを変換し、それをワールドスペースに取得してから、スプライトBのワールドマトリックスを使用してそれを取得します。スプライトBのローカル空間:

LocalA * WorldMatrixA -> World * InverseWorldMatrixB -> LocalB

変換後、新しいポイントがスプライトBの境界内にあるかどうかを確認し、そうである場合は、スプライトAの場合と同じようにその位置のピクセルを確認します。したがって、プロセス全体は次のようになります(疑似コードおよび未テスト)。 :

bool PixelCollision(Sprite a, Sprite B)
{
    // Go over each pixel in A
    for(i=0; i<a.Width; ++i)
    {
        for(j=0; j<a.Height; ++j)
        {
            // Check if pixel is solid in sprite A
            bool solidA = a.getPixelAt(i,j).Alpha > 0;

            // Calculate where that pixel lies within sprite B's bounds
            Vector3 positionB = new Vector3(i,j,0) * a.WorldMatrix * b.InverseWorldMatrix;

            // If it's outside bounds skip to the next pixel
            if(positionB.X<0 || positionB.Y<0 || 
               positionB.X>=b.Width || positionB.Y>=b.Height) continue;

            // Check if pixel is solid in sprite B
            bool solidB = b.getPixelAt(positionB.X, positionB.Y).Alpha > 0;

            // If both are solid then report collision
            if(solidA && solidB) return true;
        }
    }
    return false;
}

1
エレガントなのでこれが好きですが、このようなマトリックスを使用する-フレームごとのピクセルごとに3x3マトリックスを乗算すると、特にほとんどのAndroidデバイスでは、最小のビットマップ以外にかなり深刻なパフォーマンスペナルティが発生します。
3Dave

2
@DavidLively確かに、これは計算コストの高いプロセスです。そして、マトリックス計算が三角法を使用した通常の計算に展開されていても、使用されていない場合はスケーリングを省略している可能性がありますが、それでもほとんど同じです。他の解決策があるかもしれませんが、私は何も考えられませんでした。結局のところ、最善の解決策は、ピクセル完全衝突検出が本当に必要かどうかを考えることです。また、ピクセル完全衝突を使用する一部のゲーム(最初のSonicゲームなど)は、代わりにキャラクターを一連の衝突ポイントに抽象化します。
David Gouveia

いい視点ね; 私は、ビットマップを差し引いてピークを探すことを含まない代替ソリューションを考え出そうとして頭を悩ませています。これのどこかにおそらく論文の機会があるでしょう。:)マントラ:最適vs十分...最適vs十分..
3Dave

本当に素晴らしい説明に感謝します。私は現在、境界ボックスの衝突検出をセットアップしており、それが発生した場合は、2つのビットマップ間のピクセルのオーバーラップをチェックしますが、オーバーラップする長方形のみをチェックします。これはすべてうまく機能し、プロセッサに負荷がかかりすぎません。画像を回転すると問題が発生します。ピクセル自体はビットマップで回転されません。回転した画像を描いています。したがって、ピクセルの衝突をチェックし続けると、まだ古いピクセルを使用しています。ピクセルを世界座標にマッピングすることについてあなたが言ったことが好きです。これは私がやらなければならないことかもしれません。ありがとう!!!
DRiFTy

+1私は常にアルゴリズムがどのように機能するのか疑問に思っていました、ありがとう@DavidGouveia
Vishnu

2

David Gouveiaの答えは正しいように聞こえますが、パフォーマンスの観点からは、これは最良のソリューションではありません。実行する必要があるいくつかの重要な最適化があります。

  1. 単純な円の衝突で最初にチェックすることにより、不要なチェックをソートして回避します。任意の回転でスプライトの中心と半径を取得するには、4つすべての(すでに回転した)頂点の最小および最大のx座標とy座標を取得します。スプライトで閉じた円:

    center = max_x-min_x / 2、max_y-min_y / 2

    半径= max(max_x-min_x、max_y-min_y)

  2. これで、基本的に単純なアフィンテクスチャマッピングアルゴリズムを使用して、既に変換された(回転された)画像をラスタライズしてチェックする候補が多すぎないはずです。基本的には、ラスタライズされたスプライトごとに4行を追跡します。頂点aから回転ボックスの次の頂点(bおよびc)に向かう2行頂点u1 / v1から次の頂点u2 / v2に「ビットマップ空間」に進む2行およびu3 / v3:注:この画像をググると、三角形が表示されます。ボックスは2つの三角形にすぎません。このアルゴリズムでは、丸めエラーによるビットマップ内の「穴」を回避するために、水平線を描画することが重要です(これが「ラスタライザ」と呼ばれる理由です)。bresenhamアルゴリズムでラインを計算する、各ピクセルに必要なのは4つの追加と4つの比較(および傾斜によっては2つの追加の追加)だけです。ただし、独自のポリゴンテクスチャマッパーを作成しますが、コストのかかる(最適化が難しい)3D補正は必要ありません。 アフィンテクスチャマッピング

  3. 衝突ビットマップの解像度を簡単に下げ(たとえば、ファクター2で)、さらに時間を節約できます。解像度の半分の衝突チェックは気付かれません。

  4. グラフィックアクセラレーションを使用している場合、ラスタライザを自分でコーディングすることを回避するために、ある種のHW加速バッファチェック(ステンシル?)を使用できる可能性があります。

主な問題は次のとおりです。Javaは、2D配列に格納されているビットマップデータへのアクセスが高速ではありません。アクセスごとに少なくとも1つのindexOutOfBoundsチェックを回避するために、データを1次元配列に格納することをお勧めします。また、2の累乗の次元(64x64、128x128など)を使用します。これにより、乗算ではなくビットシフトによってオフセットを計算できます。最初のテクスチャに値!= 0(透明)がある場合にのみ、2番目のテクスチャアクセスを最適化して実行することもできます。

これらの問題はすべてソフトウェアレンダリングエンジンで解決されました。ソースコードを調べると役立つ場合があります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.