Wie gehe ich mit einer pixelgenauen Kollisionserkennung mit Rotation um?

8

Hat jemand eine Idee, wie man mit Bitmaps in Android eine pixelgenaue Rotationskollisionserkennung erreichen kann? Oder generell? Ich habe derzeit Pixel-Arrays, aber ich weiß nicht, wie ich sie basierend auf einer beliebigen Anzahl von Graden manipulieren soll.

DRiFTy
quelle

Antworten:

11

Ich bin mit Android nicht vertraut, daher weiß ich nicht, welche Tools Ihnen zur Verfügung stehen, aber ich kann Ihnen einen Weg nennen, dies allgemein zu implementieren. Wie einfach es sein wird, hängt davon ab, was Android für Sie bereitstellt. Sie werden Matrizen benötigen oder zumindest die Berechnungen erheblich vereinfachen.

Führen Sie zunächst eine Bounding-Box-Kollisionsprüfung durch und kehren Sie sofort zurück, wenn sie nicht kollidieren, um weitere Berechnungen zu vermeiden. Das ist logisch, denn wenn die Begrenzungsrahmen nicht kollidieren, wird garantiert, dass auch keine Pixel kollidieren.

Wenn anschließend eine pixelgenaue Kollisionsprüfung erforderlich ist, ist der wichtigste Punkt, dass Sie diese Prüfung an derselben Stelle durchführen müssen . Dies kann erreicht werden, indem jedes Pixel aus Sprite A entnommen, eine Reihe von Transformationen angewendet wird, um sie in den lokalen Raum von Sprite B zu bringen, und dann überprüft wird, ob es mit einem Pixel an dieser Position auf Sprite B kollidiert. Eine Kollision tritt auf, wenn beide Pixel geprüft sind undurchsichtig.

Das erste, was Sie brauchen, ist, eine Weltmatrix für jeden der Sprites zu erstellen. Es gibt wahrscheinlich Online-Tutorials, in denen Sie lernen, wie man eine erstellt, aber im Grunde sollte es sich um eine Verkettung einiger einfacherer Matrizen in der folgenden Reihenfolge handeln:

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

Der Nutzen dieser Matrix besteht darin, dass durch Multiplizieren eines Punkts im lokalen Raum - und wenn Sie beispielsweise die Pixel mit einer Methode wie bitmap.getPixelAt(10,20)dieser erhalten, 10,20 im lokalen Raum definiert werden - durch die entsprechende Weltmatrix in den Weltraum verschoben wird:

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

Und wenn Sie die Matrizen invertieren , können Sie auch in die entgegengesetzte Richtung gehen, dh Punkte aus dem Weltraum in jeden der lokalen Räume des Sprites transformieren, je nachdem, welche Matrix Sie verwendet haben:

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

Um einen Punkt aus dem lokalen Raum von Sprite A in den lokalen Raum von Sprite B zu verschieben , transformieren Sie ihn zuerst mit der Weltmatrix von Sprite A, um ihn in den Weltraum zu bringen, und verwenden Sie dann die inverse Weltmatrix von Sprite B , um ihn zu erhalten Der lokale Raum von Sprite B:

LocalA * WorldMatrixA -> World * InverseWorldMatrixB -> LocalB

Nach der Transformation überprüfen Sie, ob der neue Punkt innerhalb der Grenzen von Sprite B liegt, und wenn dies der Fall ist, überprüfen Sie das Pixel an dieser Stelle genau wie bei Sprite A. Der gesamte Prozess wird also ungefähr so ​​(in Pseudocode und ungetestet). ::

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;
}
David Gouveia
quelle
1
Ich mag das, weil es elegant ist, aber die Verwendung solcher Matrizen - das Multiplizieren einer 3x3-Matrix pro Pixel und Frame - führt zu einigen ziemlich schwerwiegenden Leistungseinbußen für alle außer den kleinsten Bitmaps, insbesondere auf den meisten Android-Geräten.
3Dave
2
@DavidLively In der Tat wird dies ein teurer rechnerischer Prozess sein. Und ich denke, selbst wenn die Matrixberechnungen in reguläre Berechnungen mit Trigonometrie abgewickelt werden - möglicherweise ohne Skalierung, wenn sie nicht verwendet wird - wäre sie immer noch ziemlich gleich. Es könnte andere Lösungen geben, aber mir fiel keine ein. Letztendlich wäre die beste Lösung zu überlegen, ob eine pixelgenaue Kollisionserkennung überhaupt notwendig ist. Und einige Spiele, die pixelgenaue Kollisionen verwenden (wie die ersten Sonic-Spiele), abstrahieren die Charaktere stattdessen in eine Reihe von Kollisionspunkten.
David Gouveia
guter Punkt; Ich zerbreche mir den Kopf und versuche, eine alternative Lösung zu finden, bei der Bitmaps nicht subtrahiert und nach Spitzen gesucht werden. Es gibt wahrscheinlich irgendwo eine Gelegenheit für eine Zeitung. :) Mantra: optimal gegen ausreichend ... optimal gegen ausreichend ..
3Dave
Vielen Dank, das ist wirklich eine gute Erklärung. Ich habe derzeit eine Bounding-Box-Kollisionserkennung eingerichtet. In diesem Fall überprüfe ich die Pixelüberlappung zwischen den beiden Bitmaps, jedoch nur im überlappenden Rechteck. Das alles funktioniert großartig und ist nicht zu prozessorintensiv. Das Problem tritt auf, wenn ich das Bild drehe. Die Pixel selbst werden in der Bitmap nicht gedreht. Ich zeichne nur ein gedrehtes Bild. Wenn ich also weiterhin die Pixelkollision überprüfe, werden immer noch die alten Pixel verwendet. Mir gefällt, was Sie über die Zuordnung von Pixeln zu Weltkoordinaten gesagt haben. Dies kann sehr gut das sein, was ich tun muss. Vielen Dank!!!
DRiFTy
+1 Ich habe mich immer gefragt, wie der Algorithmus funktioniert. Danke @DavidGouveia
Vishnu
2

Die Antwort von David Gouveia klingt zwar richtig, ist jedoch aus Sicht der Leistung nicht die beste Lösung. Es gibt mehrere wichtige Optimierungen, die Sie vornehmen müssen:

  1. Sortieren und vermeiden Sie unnötige Überprüfungen, indem Sie zuerst mit einer einfachen Kreiskollision prüfen: Um den Mittelpunkt und den Radius Ihrer Sprites bei jeder Drehung zu ermitteln, ermitteln Sie die Minima und Maxima x- und y-Koordinaten aller 4 (bereits gedrehten) Scheitelpunkte. Anschließend können Sie konstruieren ein Kreis, der sich im Sprite schließt durch:

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

    Radius = max (max_x-min_x, max_y-min_y)

  2. Jetzt sollten Sie nicht zu viele Kandidaten haben, um sie zu überprüfen, indem Sie die bereits transformierten (gedrehten) Bilder grundsätzlich mithilfe eines einfachen affinen Texturabbildungsalgorithmus rastern . Grundsätzlich verfolgen Sie 4 Linien pro gerastertem Sprite: 2 Linien vom Scheitelpunkt a zu den nächsten Scheitelpunkten Ihrer gedrehten Box (b und c) 2 Linien im "Bitmap-Raum" vom Scheitelpunkt u1 / v1 bis zu den nächsten Scheitelpunkten u2 / v2 und u3 / v3: Hinweis: Ich habe dieses Bild gegoogelt und es zeigt ein Dreieck. Ihre Felder sind nur zwei Dreiecke. Für diesen Algorithmus ist es wichtig, horizontale Linien zu zeichnen (deshalb wird er als " Rasterizer " bezeichnet), um "Löcher" in der Bitmap aufgrund von Rundungsfehlern zu vermeiden. Durch Berechnung der Linien mit einem Bresenham-AlgorithmusSie benötigen für jedes Pixel nur 4 Additionen und 4 Vergleiche (und manchmal zwei zusätzliche Additionen, abhängig von der Steigung). Sie schreiben Ihren eigenen Polygon-Textur-Mapper, jedoch ohne die kostspielige (und schwer zu optimierende) 3D-Korrektur. affine Texturzuordnung

  3. Sie können die Auflösung der Kollisionsbitmaps leicht reduzieren (z. B. um Faktor 2) und noch mehr Zeit sparen. Eine Kollisionsprüfung der Hälfte der Auflösung sollte nicht bemerkt werden.

  4. Wenn Sie die Grafikbeschleunigung verwenden, können Sie möglicherweise eine Art HW-beschleunigten Puffercheck (Schablone?) Verwenden, um zu vermeiden, dass Sie den Rasterizer selbst codieren.

Das Hauptproblem ist: Java ist beim Zugriff auf in einem 2D-Array gespeicherte Bitmap-Daten nicht sehr schnell. Ich würde empfehlen, die Daten in einem eindimensionalen Array zu speichern, um mindestens 1 indexOutOfBounds-Prüfung für jeden Zugriff zu vermeiden. Verwenden Sie auch Zweierpotenz-Dimensionen (wie 64 x 64, 128 x 128 usw. Auf diese Weise können Sie den Versatz über Bitverschiebung und nicht über Multiplikation berechnen). Sie können den zweiten Texturzugriff auch so optimieren, dass er nur ausgeführt wird, wenn der erste den Wert! = 0 (transparent) hat.

All diese Probleme wurden in Software-Rendering-Engines gelöst. Es könnte nützlich sein, den Quellcode zu untersuchen

Peter Parker
quelle