Ici, nous allons voir les collisions 3D avec des décors fixes.
Heightmap
La Heigtmap (ou carte des hauteurs) est très utilisée dans les jeux 3D pour définir des sols non plats.
Définition
Considérons une généralisation de la collision sur sol 2D courbe, que nous avons vu au-dessus, mais en 3D. Rappelez-vous, l’idée était de savoir, pour tout , quel était le associé avec une fonction . À partir de là, on pouvait marcher sur une courbe.
L’idée est la même ici, savoir pour un donné, quel est le du sol. Autrement dit de connaître .
Le problème, comme plus haut, est de définir cette fonction f(x,y). Afin d’avoir un contrôle des reliefs, nous allons définir cette fonction à partir d’une simple image. Et c’est cette image que nous appellerons Heigtmap.
Voici une Heightmap, en haut, et le terrain qu’elle permettra de générer, en bas. Exemple tiré de Wikipédia.
La heightmap est une image (un BMP par exemple) en noir et blanc, avec des nuances de gris. À partir de cette image, on reconstruit le sol 3D de droite.
Le concept est simple, pour un donné, on regarde la couleur du pixel sur le BMP. Plus le pixel est blanc, plus l’altitude à cette position est élevée.
Regardez, on voit bien que les zones noires à gauche sont représentées par des creux à droite, et les zones blanches par des pics de montagne.
La couleur du pixel à un donné est finalement le attendu. Nous avons donc notre fonction . C’est le BMP.
Applications
Beaucoup de jeux où vous pouvez marcher en extérieur dans un monde fait de relief. Beaucoup de jeux de voitures actuels sont fait avec un Heightmap.
Notez qu’on peut définir une altitude minimale (qui correspondra au pixel noir) et une altitude maximale (qui correspondra au pixel blanc) de notre choix. Cela permet de faire des dénivelés plus ou moins importants, en fonction de nos besoins. Par exemple, à gauche, le sol est peu vallonné, alors qu’il l’est énormément dans le jeu de voiture (Monster Truck).
Calcul de collision
La fonction « simple » de collision sur une HeightMap n’est pas si compliquée. Nous ne définirons pas de structure Image
, nous partirons juste du principe qu’on peut demander la valeur Z
(la blancheur) d’un pixel .
Nous calculerons donc d’abord, pour une AABB3D, le point en bas au centre, de la même façon que nous l’avions fait pour le chapitre sur le sol courbe. Nous considèrerons, pour notre AABB3D, l’axe z
de bas en haut.
bool Collision(AABB3D box, Image Im)
{
float x = box.x + box.w / 2;
float y = box.y + box.h / 2;
float z = box.z + box.d;
int ix = round(x);
int iy = round(y);
int hauteursol = GetPixel(Image, ix, iy);
if (hauteursol > z)
return true;
else
return false;
}
Cette fonction va prendre, pour un x
(ou un y
) donné, la valeur entière la plus proche (la fonction round
) pour trouver l’altitude.
Affinage mathématique
Cette partie requiert certaines connaissances mathématiques universitaires (ou fin de lycée).
Problème de discontinuité
La fonction que nous avons vu juste au-dessus présente un inconvénient majeur, elle nous génère un « escalier ». Cela ne se voit pas trop si le terrain est « serré », c’est-à-dire s’il y a peu de distance entre et (et de même pour ). Mais cela va faire de grandes cassures dans le cas contraire.
Voyons le schéma ci-dessous :
Ce que nous avons vu, c’est le cas A. Imaginez qu’on regarde une seule ligne de notre terrain, et de profil. Les points verts sont les points d’altitude, de coordonnées , , et . Les traits rouges sont les valeurs de la fonction entre et , entre et et entre et .
La fonction round()
arrondit au nombre entier le plus proche, donc typiquement, la limite est au milieu, à , , . Et c’est la que nous avons une belle cassure. Et si nous faisons ainsi avancer notre personnage, quand il atteindra la limite, il va monter ou descendre d’un coup, ce qui pourra être bien moche.
Interpolation linéaire
Si nous regardons pour le moment la courbe, cas A, nous connaissons les valeurs de que pour , , … Mais nous ne les connaissons pas pour par exemple. Un calcul d’interpolation permet de trouver des valeurs correctes pour toutes les valeurs de , même si est un nombre décimal.
Le principe de l’interpolation linéaire est le suivant : pour un donné, nous séparons sa partie entière de sa partie décimale comme ceci :
Par exemple, pour , nous obtenons et .
La formule d’interpolation linéaire est la suivante :
Exemple : soit , . Nous cherchons . et .
Cela fonctionne dans tous les cas, même si nous avons une valeur de entière. Nous cherchons . et
La formule fonctionne donc dans tous les cas, pour un quelconque, compris dans le domaine de la courbe.
Interpolation bilinéaire
Si nous considérons une surface dont nous ne connaissons que et entiers (une Heightmap typiquement), nous pouvons calculer une interpolation avec des réels de la même manière. Tout d’abord, nous prenons la partie entière et décimale de et .
La formule générale est similaire à celle de l’interpolation linéaire :
Cela nous donne la formule de collision suivante :
bool Collision(AABB3D box, Image Im)
{
float x = box.x + box.w / 2;
float y = box.y + box.h / 2;
float z = box.z + box.d;
int ix = (int)(x);
int iy = (int)(y);
float dx = x - ix;
float dy = y - iy;
float hauteursol = ((1 - dx) * GetPixel(Image, ix, iy) + dx * GetPixel(Image, ix + 1, iy)) * (1 - dy)
+((1 - dx) * GetPixel(Image, ix, iy+1) + dx * GetPixel(Image, ix + 1, iy + 1)) * dy;
if (hauteursol > z)
return true;
else
return false;
}
Interpolation cubique et bicubique
L’interpolation cubique permet un lissage beaucoup plus joli de la courbe ou de la surface (bicubique), en utilisant des polynômes de degré 3. C’est le cas C que j’ai dessiné plus haut. Je ne développerai pas cette partie complexe, je voulais juste vous informer que ça existe. C’est cependant peu utilisé dans les jeux, l’interpolation bilinéaire étant souvent suffisante.
Ce chapitre s’étoffera avec le temps pour d’autre types des décors.