Come gestire il rilevamento delle collisioni perfetto con i pixel con la rotazione?


8

Qualcuno ha qualche idea su come ottenere il rilevamento di una perfetta collisione con pixel rotazionali con Bitmap in Android? O in generale per quella materia? Al momento ho array di pixel ma non so come manipolarli in base a un numero arbitrario di gradi.

Risposte:


11

Non ho familiarità con Android, quindi non so quali strumenti hai a disposizione, ma posso dirti un modo per implementarlo in termini generali. Quanto sarà facile dipende da ciò che Android offre per te. Avrai bisogno di matrici o almeno semplificheranno molto i calcoli.

Per i principianti fare un controllo delle collisioni con la delimitazione e tornare immediatamente se non si scontrano per evitare ulteriori calcoli. Questo è logico perché se i riquadri di delimitazione non si scontrano è garantito che nessun pixel si scontrerà.

Successivamente, se è necessario un controllo di collisione perfetto dei pixel, il punto più importante è che è necessario eseguire quel controllo nello stesso spazio . Questo può essere fatto prendendo ogni pixel dallo sprite A, applicando una serie di trasformazioni al fine di portarli nello spazio locale dello sprite B, e quindi verificare se si scontra con qualsiasi pixel in quella posizione sullo sprite B. Una collisione si verifica quando entrambi i pixel controllato sono opachi.

Quindi, la prima cosa di cui hai bisogno è costruire una matrice mondiale per ciascuno degli sprite. Probabilmente ci sono tutorial online che ti insegnano come crearne uno, ma in pratica dovrebbe essere una concatenazione di alcune matrici più semplici nel seguente ordine:

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

L'utilità di questa matrice è che moltiplicando un punto nello spazio locale - e per esempio se ottieni i pixel usando un metodo come bitmap.getPixelAt(10,20)allora 10,20 è definito nello spazio locale - dalla matrice mondiale corrispondente lo sposterà nello spazio mondiale:

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

E se inverti le matrici puoi anche andare nella direzione opposta, cioè trasformare i punti dallo spazio del mondo in ciascuno degli spazi locali dello sprite a seconda della matrice che hai usato:

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

Quindi, al fine di spostare un punto dallo spazio locale di sprite A nello spazio locale di sprite B , prima lo trasformi usando la matrice mondiale di sprite A, al fine di portarlo nello spazio del mondo, e quindi usando la matrice inversa del mondo di sprite B , per farlo entrare lo spazio locale di sprite B:

LocalA * WorldMatrixA -> World * InverseWorldMatrixB -> LocalB

Dopo la trasformazione, controlli se il nuovo punto rientra nei limiti dello sprite B e, in tal caso, controlli il pixel in quella posizione proprio come hai fatto per lo sprite A. Quindi l'intero processo diventa qualcosa del genere (in pseudocodice e non testato) :

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
Mi piace perché è elegante, ma l'uso di matrici come questa - moltiplicando una matrice 3x3 per pixel per fotogramma - introdurrà alcune penalità di prestazioni piuttosto serie per qualsiasi bitmap tranne quelle più piccole, specialmente sulla maggior parte dei dispositivi Android.
3Daveva il

2
@DavidLively Effettivamente, questo è un processo costoso dal punto di vista computazionale. E penso che anche se i calcoli con la matrice vengono srotolati in calcoli regolari con la trigonometria - forse tralasciando il ridimensionamento se non viene utilizzato - sarebbe comunque più o meno lo stesso. Potrebbero esserci altre soluzioni, ma non riesco a pensare a nessuna. In definitiva, la soluzione migliore sarebbe quella di valutare se il rilevamento perfetto della collisione dei pixel sia davvero necessario. E alcuni giochi che usano le collisioni perfette dei pixel (come i primi giochi di Sonic), invece, astraggono i personaggi in una serie di punti di collisione.
David Gouveia,

buon punto; Sto distruggendo il mio cervello cercando di trovare una soluzione alternativa che non implichi la sottrazione di bitmap e la ricerca di picchi. Probabilmente c'è un'opportunità per un articolo in questo da qualche parte. :) Mantra: ottimale vs sufficiente ... ottimale vs sufficiente ..
3Dave

Grazie mille è un'ottima spiegazione. Al momento ho impostato il rilevamento delle collisioni con delimitazione del riquadro e, in tal caso, controllo la sovrapposizione dei pixel tra le due bitmap ma solo nel rettangolo sovrapposto. Tutto funziona alla grande e non richiede troppo processore. Il problema si verifica quando ruoto l'immagine. I pixel stessi non vengono ruotati nella bitmap. Sto solo disegnando un'immagine ruotata. Quindi, quando continuo a controllare la collisione dei pixel, utilizza ancora i vecchi pixel. Mi piace quello che hai detto sulla mappatura dei pixel sulle coordinate del mondo. Questo potrebbe benissimo essere quello che devo fare. Grazie!!!
DRiFTy,

+1 Mi sono sempre chiesto come funziona l'algoritmo, grazie @DavidGouveia
Vishnu,

2

Mentre la risposta di David Gouveia sembra corretta, non è la soluzione migliore dal punto di vista delle prestazioni. Ci sono diverse importanti ottimizzazioni che devi fare:

  1. sistemare ed evitare controlli non necessari controllando prima con una semplice collisione circolare: per ottenere il centro e il raggio dei tuoi folletti in qualsiasi rotazione, ottenere le coordinate minima e massima xey di tutti e 4 i vertici (già ruotati): quindi puoi costruire un cerchio che si chiude nello sprite di:

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

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

  2. ora non dovresti avere troppi candidati da verificare rasterizzando le immagini già trasformate (ruotate) fondamentalmente usando un semplice algoritmo di affinamento del testo affine . Fondamentalmente tieni traccia di 4 linee per sprite rasterizzata: 2 linee che vanno dal vertice a ai vertici successivi della casella ruotata (bec) 2 linee che vanno nello "spazio bitmap" dal vertice u1 / v1 ai vertici successivi u2 / v2 e u3 / v3: Nota: ho cercato su Google questa immagine e mostra un triangolo, le tue caselle sono solo due triangoli. È vitale per questo algoritmo disegnare linee orizzontali (ecco perché si chiama " rasterizer ") per evitare "buchi" all'interno della bitmap a causa di errori di arrotondamento. Calcolando le linee con un algoritmo di Bresenham, per ogni pixel sono necessarie solo 4 aggiunte e 4 confronti (e talvolta due aggiunte aggiuntive, a seconda della pendenza) Quello che fai è scrivere il tuo texturemapper poligonale, tuttavia senza la costosa (e difficile da ottimizzare) correzione 3d. texturemapping affine

  3. puoi facilmente ridurre la risoluzione delle bitmap di collisione (ad esempio per fattore 2) e risparmiare ancora più tempo. un controllo delle collisioni sulla metà della risoluzione deve essere impercettibile.

  4. Se si utilizza l'accellerazione grafica, potrebbe essere possibile utilizzare una sorta di controllo buffer accelerato HW (stencil?) Per evitare di codificare il rasterizer da soli.

Il problema principale è: Java non è molto veloce nell'accedere ai dati bitmap memorizzati in un array 2D. Consiglierei di archiviare i dati in un array monodimensionale per evitare almeno 1 controllo indexOutOfBounds per ogni accesso. Utilizza anche dimensioni power-of-2 (come 64x64, 128x128, ecc.). In questo modo puoi calcolare l'offset tramite bit-shifting e non moltiplicazione). Puoi anche ottimizzare l'accesso alla seconda trama per eseguirlo solo se il primo ha valore! = 0 (trasparente)

Tutti questi problemi sono stati risolti nei motori di rendering software, potrebbe essere utile esaminare il codice sorgente

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.