Comment gérer la détection de collision au pixel près avec la rotation?

8

Quelqu'un a-t-il des idées sur la façon de réaliser une détection de collision en rotation parfaite avec des bitmaps dans Android? Ou en général d'ailleurs? J'ai actuellement des tableaux de pixels mais je ne sais pas comment les manipuler en fonction d'un nombre arbitraire de degrés.

DRiFTy
la source

Réponses:

11

Je ne connais pas Android, donc je ne sais pas quels outils vous avez à votre disposition, mais je peux vous dire un moyen de mettre en œuvre cela en termes généraux. La facilité de ce processus dépend de ce qu'Android vous propose. Vous allez avoir besoin de matrices ou au moins elles simplifieront beaucoup les calculs.

Pour commencer, effectuez un contrôle de collision de la boîte englobante et revenez immédiatement s'ils ne se heurtent pas afin d'éviter d'autres calculs. C'est logique car si les boîtes englobantes ne se heurtent pas, il est garanti qu'aucun pixel ne se heurtera non plus.

Ensuite, si un contrôle de collision parfait de pixels est nécessaire, le point le plus important est que vous devez effectuer ce contrôle dans le même espace . Cela peut être fait en prenant chaque pixel de l'image-objet A, en appliquant une série de transformations afin de les placer dans l'espace local de l'image-objet B, puis en vérifiant s'il entre en collision avec un pixel quelconque à cette position sur l'image-objet B. Une collision se produit lorsque les deux pixels vérifié sont opaques.

Donc, la première chose dont vous avez besoin est de construire une matrice mondiale pour chacun des sprites. Il existe probablement des didacticiels en ligne qui vous apprennent à en créer un, mais il devrait s'agir essentiellement d'une concaténation de quelques matrices plus simples dans l'ordre suivant:

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

L'utilité de cette matrice est qu'en multipliant un point dans l'espace local - et par exemple si vous obtenez les pixels en utilisant une méthode comme celle-ci, bitmap.getPixelAt(10,20)10,20 est défini dans l'espace local - par la matrice mondiale correspondante le déplacera dans l'espace mondial:

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

Et si vous inversez les matrices, vous pouvez également aller dans la direction opposée, c'est-à-dire transformer des points de l'espace mondial en chacun des espaces locaux du sprite en fonction de la matrice que vous avez utilisée:

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

Donc, pour déplacer un point de l'espace local du sprite A vers l'espace local du sprite B , vous devez d'abord le transformer en utilisant la matrice mondiale du sprite A, afin de le placer dans l'espace mondial, puis en utilisant la matrice du monde inverse du sprite B , pour l'intégrer dans l'espace local de sprite B:

LocalA * WorldMatrixA -> World * InverseWorldMatrixB -> LocalB

Après la transformation, vous vérifiez si le nouveau point tombe dans les limites de l'image-objet B, et si c'est le cas, vous vérifiez le pixel à cet endroit comme vous l'avez fait pour l'image-objet A. Ainsi, le processus entier devient quelque chose comme ça (en pseudocode et non testé) :

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
la source
1
J'aime ça parce que c'est élégant, mais utiliser des matrices comme celle-ci - multiplier une matrice 3x3 par pixel par image - introduira des pénalités de performances assez sérieuses pour tout sauf les plus petits bitmaps, en particulier sur la plupart des appareils Android.
3Dave
2
@DavidLively En effet, ce sera un processus coûteux en termes de calcul. Et je pense que même avec les calculs matriciels déroulés en calculs réguliers avec la trigonométrie - en omettant éventuellement la mise à l'échelle si elle n'est pas utilisée - ce serait toujours à peu près la même chose. Il pourrait y avoir d'autres solutions, mais je ne pouvais penser à aucune. En fin de compte, la meilleure solution serait de se demander si une détection parfaite des collisions de pixels est vraiment nécessaire. Et certains jeux qui utilisent des collisions de pixels parfaits (comme les premiers jeux Sonic) résument plutôt les personnages en une série de points de collision.
David Gouveia
bon point; J'excite mon cerveau en essayant de trouver une solution alternative qui n'implique pas la soustraction de bitmaps et la recherche de pics. Il y a probablement une opportunité pour un article quelque part. :) Mantra: optimal vs suffisant ... optimal vs suffisant ..
3Dave
Merci beaucoup, c'est vraiment une excellente explication. J'ai actuellement une détection de collision de boîte englobante configurée et si cela se produit, je vérifie le chevauchement des pixels entre les deux bitmaps, mais uniquement dans le rectangle qui se chevauchent. Tout cela fonctionne très bien et n'est pas trop gourmand en processeur. Le problème se produit lorsque je fais pivoter l'image. Les pixels eux-mêmes ne sont pas pivotés dans le bitmap. Je dessine juste une image pivotée. Donc, quand je continue de vérifier la collision de pixels, il utilise toujours les anciens pixels. J'aime ce que vous avez dit sur le mappage des pixels aux coordonnées mondiales. C'est peut-être bien ce que je dois faire. Merci!!!
DRiFTy
+1 Je me suis toujours demandé comment fonctionne l'algorithme, merci @DavidGouveia
Vishnu
2

Bien que la réponse de David Gouveia semble correcte, ce n'est pas la meilleure solution du point de vue des performances. Vous devez effectuer plusieurs optimisations importantes:

  1. trier et éviter les vérifications inutiles en vérifiant d'abord avec une simple collision de cercle: pour obtenir le centre et le rayon de vos sprites dans n'importe quelle rotation, obtenez les coordonnées minima et maxima x et y de tous les 4 sommets (déjà tournés): alors vous pouvez construire un cercle se fermant dans le sprite par:

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

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

  2. maintenant, vous ne devriez pas avoir trop de candidats à vérifier en pixellisant les images déjà transformées (tournées) essentiellement en utilisant un algorithme de texturemapping affine simple . Fondamentalement, vous gardez une trace de 4 lignes par image-objet pixellisée: 2 lignes allant du sommet a aux sommets suivants de votre boîte pivotée (b et c) 2 lignes allant dans "l'espace bitmap" du sommet u1 / v1 aux sommets suivants u2 / v2 et u3 / v3: Remarque: J'ai googlé cette image et elle montre un triangle, vos boîtes ne sont que deux triangles. Il est essentiel que cet algorithme dessine des lignes horizontales (c'est pourquoi il est appelé " rasterizer ") pour éviter les "trous" à l'intérieur du bitmap en raison d'erreurs d'arrondi. En calculant les lignes avec un algorithme de Bresenham, vous avez besoin pour chaque pixel de seulement 4 ajouts et 4 comparaisons (et parfois deux ajouts supplémentaires, selon la pente). Ce que vous faites, c'est que vous écrivez votre propre texturemapper polygonal, mais sans la correction 3D coûteuse (et difficile à optimiser). texture affinée

  3. vous pouvez facilement réduire la résolution des bitmaps de collision (par exemple par le facteur 2) et gagner encore plus de temps. un contrôle de collision sur la moitié de la résolution devrait être imperceptible.

  4. Si vous utilisez une accélération graphique, il pourrait être possible d'utiliser une sorte de vérification de tampon accélérée par HW (pochoir?) Pour éviter de coder le rasterizer par vous-même.

Le problème principal est: Java n'est pas très rapide pour accéder aux données bitmap stockées dans un tableau 2D. Je recommanderais de stocker les données dans un tableau à 1 dimension pour éviter au moins 1 vérification indexOutOfBounds pour chaque accès. Utilisez également des dimensions de puissance de 2 (comme 64x64, 128x128, etc. Par cela, vous pouvez calculer le décalage via le décalage de bits et non la multiplication). Vous pouvez également optimiser le deuxième accès de texture pour l'exécuter uniquement si le premier a une valeur! = 0 (transparent)

Tous ces problèmes ont été résolus dans les moteurs de rendu logiciel, il pourrait être utile d'examiner le code source

Peter Parker
la source