Détection de collision et réponse de collision

Avez-vous déjà vu quelqu'un passer à travers un mur? Probablement pas. Dans la réalité il est impossible de passer au travers des surfaces solides, et étant donné que les jeux cherchent généralement à copier la réalité ce comportement doit être reproduit. C'est pourquoi une bonne détection des collisions et des réponses de collision sont requises. La détection de collision doit déterminer si les objets se sont déplacés au travers ou dans des obstacles à chaque step et si c'est le cas la réponse de collision doit définir la position à laquelle l'objet peut se déplacer sans rentrer dans un obstacle. 

La réponse de collision la plus courante pour les jeux à la première et troisième personne font glisser les objets le long des murs, s'ils essaient de se déplacer à travers. Cela a l'avantage que le joueur ne s'arrête pas de se déplacer lorsqu'il va contre un mur, ce qui est important, étant donné qu'un blocage soudain peut être vraiment irritant dans les jeux au gameplay rapide. Avec Ultimate 3D 2.0 et son système de détection de collisions basé sur le ray tracing cela fut très difficile d'obtenir une détection des collisions fiable. C'était fondamentalement la plus grande faiblesse d'Ultimate 3D. Pour Ultimate 3D 2.1 j'ai complètement revu le système de détection de collision et développé un système de réponse de collision intégré. Maintenant la détection de collisions et la réponse de collision est une des forces d'Ultimate 3D. Les credits pour cette réalisation vont à Kasper Fauerby qui a écrit un document décrivant la technique sur laquelle sont basés les mécanismes de détection et de réponse de collision. 

Puisque le système est intégré au moteur, il est très facile à utiliser et bien assez rapide, puisque qu'aucun code GML lent n'a besoin d'être éxécuté. Le système de réponse de colision glissant, décrit plus haut, peut être implémenté avec seulement cinq lignes de code. Il vous suffit simplement de définir tous les objets qui doivent subir le contrôle des fonctions de détection de collision comme solide, au travers d'une fonction Ultimate 3D et appeller une fonctions pour les objets, qui devront effectuer la détection de collision et la réponse de collision. Et vous êtes bon.


Détection de collision basée sur les ellipsoïdes

Avant que nous puissions en venir à cette fonction magique, qui résout tous vos problèmes (du moins ceux liés aux collisions), nous avons besoins de faire un peu de théorie. La première considération nécessaire est, quelle forme peut être utilisée pour une bonne réponse de collision. Elle ne doit pas avoir de coins, pour éviter que cela puisse s'accrocher quelque part, elle devra être suffisamment polyvalente pour contenir des objets avec des formes très différentes et elle doit être facile à décrire. Le choix évident est un éllipsoïde (une sphère mise à l'échelle). Ainsi nous n'aurons plus jamais besoin de penser aux détections de collisions pour les modèles, en mouvement, des personnages, nous aurons uniquement à penser à la détection des collisions pour les ellipsoïdes en mouvement. L'illustration suivante décrit un éllipsoïde. 

CollisionEllipsoid.png

Les jeux sont basés habituellement sur des grilles (frame based) et de ce fait les objets ne se déplacent pas véritablement en continu, ils sautent d'une position à une autre à chaque step. Durant un step l'objet n'est peut être pas en collision avec quoi que ce soit, et au step suivant il peut déjà se retrouver à l'intérieur d'une géométrie. La figure suivante explique cela.

CollisionSweptEllipsoid.png Illustration not available

Il ne suffit pas de vérifier si la zone verte est traversée par un obstacle, il n'est pas suffisant de vérifier si la zone jaune est traversée par un obstabcle et il n'est pas suffisant non plus, de vérifier si la zone rouge est traversée par un obstacle. Toutes ces zones doivent être vérifiées. Ainsi la forme pour laquelle les collisions doivent être détectées en réalité, n'est pas un éllipsoïde, mais un ellipsoïde étendu (swept ellipsoid). Un ellipsoide étendu est fait de deux ellipsoïdes, et d'un cylindre mis à l'échelle, les reliants. C'est la forme avec laquelle fonctionne Ultimate 3D. Cela devrait être suffisamment de théorie pour maintenant.

La fonction suivante effectue un test d'intersection entre la géométrie donnée, et l'ellipsoïde étendu (swept ellipsoid) donné, puis retourne si une collision a été détectée (true) ou non (false).

CheckSweptEllipsoidIntersection(
ObjectID,
StartX,StartY,StartZ,
DestinationX,DestinationY,DestinationZ,
RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,
RoomIndex
)

ObjectID
L'ID de l'objet pour lequel vous voulez effectuer la détection de collision. Cela peut être l'ID d'un modèle, d'un terrain ou d'un objet primitif ou même le mot clé all. Dans ce dernier cas tous les objets, qui sont définis comme solides à l'aide de la fonction SetObjectSolidity(...) et qui sont dans la bonne room seront vérifiés et contrôlés. En addition à cela, tous les objets qui sont définis comme globalement solides au travers de SetObjectSolidity(...), seront vérifiés et contrôlés, quelle que soit la room dans laquelle ils sont (Ndt: Rendez vous au chapitre précédent, pour comprendre le fonctionnement des rooms, ce ne sont pas les rooms de Game Maker)

StartX, StartY, StartZ
La position de départ de l'ellipsoïde étendu (voir l'illustration ci-dessus).

DestinationX, DestinationY, DestinationZ
La position de destination de l'ellipsoïde étendu (voir la figure ci-dessus). Ces paramètres peuvent égaler  StartX/Y/Z. Dans ce cas là, Ultimate 3D effectuera un test d'intersection d'ellipsoïde non-étendu, qui sera donc moins intensif, en matière de temps de calcul.

RotX, RotY, RotZ, ScalingX, ScalingY, ScalingZ
La rotation et l'échelle de l'ellipsoïde. L'ellipsoïde est une sphere (une sphère de rayon un) mise à l'échelle par ScalingX/Y/Z et pivotée par RotX/Y/Z.

RoomIndex
Ce paramètre n'a de sens que si all est spécifié pour le premier paramètre. Si ce n'est pas le cas les objets de toutes les rooms seront contrôlés et vérifiés. Sinon, seuls les objets présents dans la room avec l'indice donné et les objets globalement solides seront contrôlés et vérifiés (Voyez SetObjectRoom(...)).  

Juste savoir qu'une collision s'est produite reste cependant tout à fait inutile. Plus d'informations sont nécessaires pour permettre de répondre à la collision (dans la mesure où vous n'utilisez pas le système de réponse de collision intégré). Pour cette raison il existe un ensemble de fonctions, qui retournent des données pertinentes sur les collisions qui ont lieux. Toutes ces fonctions retournent des données à propos des résultats des derniers appels à CheckSweptEllipsoidIntersection(...).

Cette fonction retourne le nombre de triangles, qui ont été traversés par l'ellipsoïde étendu, défini dans le dernier appel à CheckSweptEllipsoidIntersection(...).

GetTriangleIntersectionCount()

Cette fonction retourne le moment où le triangle traversé donné a été traversé par l'ellipsoïde étendu spécifié dans le dernier appel à CheckSweptEllipsoidIntersection(...). La valeur de retour reste dans l'interval allant de 0.0 à 1.0, ou 0.0 se réfère au moment auquel l'ellipsoïde était à la position de départ de la sphère étendue et 1.0 se réfère au moment auquel il était à la position de destination.

GetIntersectionTime(
IntersectedTriangleIndex
)

IntersectedTriangleIndex
L'indice du triangle traversé pour lequel vous souhaitez retrouver le moment de collision. C'est une valeur entière allant de 0 à GetTriangleIntersectionCount()-1.


Cette fonction donne la position à laquelle l'ellipsoïde étendu spécifié au dernier appel de CheckSweptEllipsoidIntersection(...), a traversé en premier le premier triangle traversé.

GetIntersectionPosition(
OutputVectorID,
IntersectedTriangleIndex,
)

OutputVectorID
Référez vous à la description dans le chapitre à propos des fonctions mathématiques.

IntersectedTriangleIndex
L'indice du triangle traversé (référez vous à la description de GetIntersectionTime(...) pour plus de détails).


Cette fonction sort les normales du triangle traversé donné. La normale d'un triangle est un vecteur normalisé, qui reste perpendiculaire au triangle et pointe loin devant depuis sa face avant. 

GetIntersectedTriangleNormal(
OutputVectorID,
IntersectedTriangleIndex
)

OutputVectorID
Voir la description dans le chapitre sur les fonctions mathématiques.

IntersectedTriangleIndex
L'indice du triangle traversé (référez vous à la description de GetIntersectionTime(...) pour plus de détails).


Cette fonction sort la normale du triangle traversé donné multiplié par un facteur, de sorte que l'ellipsoïde aura quitté le triangle si le vecteur retourné est ajouté à la position de destination de l'ellipsoïde. Cela peut être utilisé pour des réponses de collisions très simple, mais cela ratera rapidement, si plusieurs triangles sont impliqués dans la collision.

GetPushAwayVector(
OutputVectorID,
IntersectedTriangleIndex
)

OutputVectorID
Voir la description dans le chapitre sur les fonctions mathématiques.

IntersectedTriangleIndex
L'indice du triangle traversé (référez vous à la description de GetIntersectionTime(...) pour plus de détails).


Avec ces fonctions vous pouvez programmer des réponses de collision personnalisées. Par exemple vous pouvez faire rebondir les objets en arrière s'ils rencontrent un obstacle, ou vous pouvez les déformer dépendemment de la façon dont ils se sont rencontrés. Quoi qu'il en soit ces mécanismes de réponse de collision ne sont usuellement pas ce qui est voulu. Le mécanisme de réponse de collision le plus commun reste encore celui avec la glissement. Et c'est celui auquel on va venir désormais. Comme je l'ai déjà dit seulement une fonction est requise pour cela. Elle réalise la détection de collision et la réponse de collision automatiquement. La voici.

Cette fonction vérifie si l'ellipsoïde donné entre en collision avec le(s) objet(s) donnés, sur son chemin de la position de départ à la position de destination. Si le vecteur n'est pas traversé, ce qui est retourné est égal à la position de destination, sinon une position libre à laquelle l'ellipsoïde peut se déplacer est retournée.

AttemptMoveToPosition(
OutputVectorID,
ObjectID,
StartX,StartY,StartZ,
DestinationX,DestinationY,DestinationZ,
RotX,RotY,RotZ,ScalingX,ScalingY,ScalingZ,
RoomIndex
)

OutputVectorID
Voir la description dans le chapitre sur les fonctions mathématiques.

ObjectID, StartX, StartY, StartZ, DestinationX, DestinationY, DestinationZ, RotX, RotY, RotZ, ScalingX, ScalingY, ScalingZ, RoomIndex
Voir la description de CheckSweptEllipsoidIntersection(...).


Pour un exemple sur la manière d'utiliser cette fonction, regardez le code dans l'évènement step de l'objet de caméra dans le SDK. Bien entendu une fonction de plus nécessite d'être expliquée ici. Elle a déjà été mentionée plus haut et elle est réellement simple, quoi qu'il en soit en voici une explication.

Cette fonction change l'état de solidité du modèle, du primitif ou de l'objet de terrain par lequel elle est appellée.

SetObjectSolidity(
SolidityState,
GlobalSolidityState,
)

SolidityState
Le nouvel état de solidité, qui peut être défini. Cela peut être true, pour obtenir le comportement où l'objet sera vérifié, si all est spécifié comme paramètre pour ObjectID et si RoomIndex correspond à l'indice de room de cet objet; lors d'un appel à CheckSweptEllipsoidIntersection(...), AttemptMoveToPosition(...) ou CheckRayIntersection(...). Cela peut être false pour obtenir le comportement où l'objet ne sera pas vérifié si all est spécifié comme paramètre pour ObjectID.

GlobalSolidityState
Le nouvel état de solidité globale à définir. Cela peut être true, pour faire en sorte que l'objet sera toujours vérifié et contrôlé, si all est spécifié comme ObjectID. Ou bien false pour faire en sorte que cela ne sera pas vérifié dans ce cas. La différence entre SolidityState et GlobalSolidityState réside dans l'idée que GlobalSolidityState est indépendant du paramètre RoomIndex. L'utilisation de cet état fait sens pour les objets, qui sont dans les rooms, qui utilisent la visibilité forcée, comme les portes (reportez vous à SetRoomVisibilityEnforcement(...)).


Cette fonction combinée avec le moteur de portail (en particulier avec SetObjectRoom(...)) offre un moyen vraiment commode et efficace pour spécifier quels objets ont besoins d'êtres pris en compte par la detection automatique de collision. Vous pouvez l'utiliser de sorte que les objets effectuent toujours la détection de collision seulement pour les objets, qui sont dans la même room et pour les objets qui sont entre les rooms, comme les portes (celles-ci peuvent avoir la solidité globale de définie). Cela rend la detection de collision vraiment efficace.

Ray tracing

            Une autre méthode, qui est importante pour la détection des collisions  est le ray tracing ou pour être plus précis le test d'intersection entre les mesh et les rayons, qu'on appellera plus communément le ray-mesh intersection test. Un test de ray tracing vérifie si un rayon donné entre en collision avec une pièce de géométrie. Ceci est utile, par exemple, si vous souhaitez vérifier si une arme pointe sur quelque objet, pour définir ce qui sera touché lorsque l'arme tirera. Pour vous donner une meilleure compréhension de ce qu'est le ray tracing, voici une explication plus approfondie.

Imaginez que vous avez un rayon. Si vous ne vous en souvenez pas des maths à l'école, un rayon est une ligne droit qui débute en un point et qui est infiniment longue. Une bonne manière d'imaginer cela est un pointeur laser. La position à laquelle est le pointeur laser, est l'origine du rayon et le laser émet un rayon infiniment long (en théorie). Si vous utiliser le pointeur laser pour pointer un objet quelconque, vous verrez un petit point à la position où le rayon rencontre l'objet. C'est le point d'intérêt, en anglais Point of Interest (POI). L'illustration suivante évoque cela.

CollisionRaytracing.png Illustration not available

Un test d'intersection ray-mesh utilise une méthode mathématique pour tester si le rayon donné traverse les triangles de l'objet donné. Si c'est le cas, cette méthode calcule la position à laquelle le rayon entre en collision en premier avec l'objet (le point bleu sur l'illustration). La fonction de ray tracing est très similaire à CheckSweptEllipsoidIntersection(...), mais étant donné que les rayons sont plus rapides à décrire que les ellipsoïdes étendus, elle est nettement plus simple.

Cette fonction vérifie si le rayon donné traverse la géométrie de les ou l'objet donné(s). Si c'est le cas elle retourne la distance depuis l'origine du rayon à la position d'intersection la plus proche, sinon elle retourne 100,000.

CheckRayIntersection(
ObjectID,
RayOriginX,RayOriginY,RayOriginZ,
RayDirectionLongitude,RayDirectionLatitude,
RoomIndex
)

ObjectID
Voir la description de CheckSweptEllipsoidIntersection(...).

RayOriginX, RayOriginY, RayOriginZ
L'originie du rayon.

RayDirectionLongitude, RayDirectionLatitude
La direction du rayon. Pour obtenir de plus amples informations sur ces deux paramètres, jetez un oeil à la description de la fonction Move(...).

RoomIndex
Reportez vous à la description de CheckSweptEllipsoidIntersection(...).


Une fois encore, il existe quelques fonctions pour obtenir plus d'informations sur le triangle traversé.

Cette fonction sort la normale d'un triangle, qui a été traversée par le rayon spécifié dans le dernier appel à CheckRayIntersection(...). La normale d'un triangle est un vecteur normalisé, qui reste perpendiculaire au triangle et pointe loin devant depuis la face avant de ce dernier.

GetRayTracingNormal(
OutputVectorID
)

OutputVectorID
Voir la description dans le chapitre sur les fonctions mathématiques.


Cette fonction retourne l'indice du matériau, qui est utilisé par le triangle traversé lors du dernier appel à CheckRayIntersection(...), qui a été utilisé avec un objet de modèle comme paramètre pour ObjectID.

GetIntersectedMaterialIndex()

En utilisant ces fonctions, vous pouvez en apprendre davantage à propos de ce qui entoure un objet. Par exemple vous pouvez envoyer un rayon droit sous les hanches du personnage pour définir à quelle distance est le sol. Ensuite vous pouvez utiliser GetRayTracingNormal(...) pour déterminer la force du dénivelé du sol à cette position. Et après quoi vous pouvez utiliser GetIntersectedMaterialIndex() pour déterminer, si le personnage se tient sur du métal ou de l'herbe.



© Christoph Peters. Certains droits réservés. (Traduction FR 04/2008, Franck Vernel / Damien Buhl).

Creative Commons License XHTML 1.0 Transitional