JeuWeb - Crée ton jeu par navigateur
Article Application de la 3D isométrique sur des tuiles hexagonales - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Art, graphisme, audio (https://jeuweb.org/forumdisplay.php?fid=50)
+--- Sujet : Article Application de la 3D isométrique sur des tuiles hexagonales (/showthread.php?tid=8205)



Application de la 3D isométrique sur des tuiles hexagonales - Xenos - 14-09-2020

Application de la 3D isométrique sur des tuiles hexagonales

La 3D isométrique est un moyen de rendre l'effet de perspective et de profondeur en utilisant des éléments en 2D. Appliquée à une carte composée d'hexagones, cette technique donne un rendu saisissant mais pour bien la comprendre, il est nécessaire de réfléchir à ses implications mathématiques. L'objet de ce tutoriel est d'expliquer pas à pas la théorie de la 3D isométrique lorsqu'elle est utilisée avec des hexagones. Les paragraphes qui suivent contiennent de nombreux schémas mais peu de code. Peut-être qu'une dernière section plus pratique viendra, à l'avenir, s'ajouter… Bonne lecture !

1. Créer des tuiles isométriques

Avant tout, je rappelle la procédure pour créer des tuiles isométriques. C'est le standard utilisé dans le pixel-art, entre autres, et la même manipulation est applicable pour tout objet en 2D, quelle que soit sa forme. Il suffit d'appliquer une rotation de 45° sur la droite puis compresser la figure obtenue en divisant sa hauteur par 2. Ce qui donne ceci pour un carré et un hexagone :

{Image (attachment) /hexa_iso1.png}

On se retrouve donc avec des images rectangulaires contenant des formes tordues, d'où la difficulté de les coller les unes aux autres. Je rappelle que si l'on considère la carte comme un plan orthonormé, l'origine est en haut à gauche (standard en CSS, GD, Flash et j'imagine bien d'autres langages). Idem pour nos images de tuiles qui ressemblent à ça :

{Image (attachment) /hexa_iso2.png}

2. Des formules différentes

En prenant l'exemple d'un carré en 2D en iso, on voit bien que la position de chaque case ne peut être déterminée de la même façon si l'on est en 2D ou en 3D isométrique. L'exemple suivant montre le problème que l'on rencontre si l'on place les tuiles de la même manière en utilisant la formule pour la 2D qui est la suivante :

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_993_f4cd0787c6bc48b1528f192ebd982eb3.png}

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_993_6a93ac21fe10282a9a359b1d27ba4a45.png}

où n est la longueur d'un côté et X et Y les coordonnées à partir de 0 :

{Image (attachment) /hexa_iso3.png}

Avant d'attaquer les hexotuiles, je donne juste la formule permettant d'aligner les carrés iso sur une carte, comme ceci (je marque l'entourage des images pour que vous repériez bien où est l'origine de la tuile) :

{Image (attachment) /hexa_iso4.png}

(pour cet exemple, je ne vais pas utiliser la taille théorique des côtés - c'est à dire la taille en vue 2D - car il est plus facile de se baser sur l'image mais je le ferai pour les hexagones).
Il y a quelques temps j'avais trouvé sur jeflash.com la formule suivante (facilement vérifiable si vous étudiez le schéma précédent) pour placer les tuiles isométriques de forme carrée :

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_d9c9e86c1582754febbd7d0c02cba44f.png}

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_2fa9d94277e8e882ebea16c7340ddec2.png}

(note l'isométrique place des tuiles en abscisses négatives. Il est donc souvent utile de décaler l'ensemble de la carte pour que l'on voit également la zone situées à gauche. Pour cela, il suffit de rajouter une variable "decalage", par exemple à posX. Ce décalage correspond à :

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_ccfe90e836283475133153ff3106e088.png}

3. Étude des hexagones

Maintenant que l'on connait la formule de positionnement de tuiles iso carrées et que l'on sait les contraintes de la représentation isométrique appliquée à la forme la plus simple, on peut commencer à regarder du côté des hexagones. En regardant des maps d'hexagones isométriques, je me suis rendu compte qu'il serait peu commode de travailler de façon abstraite sur la taille des images et qu'il valait mieux tenir compte des dimensions réelles de la figure contenue dans l'image. La formule d'Oncle James contenue dans son tuto est très bien pour des images aux dimensions bien précises. Je l'ai cependant réécrite pour quelle soit applicable d'après les dimensions de l'hexagone en lui même et non pas selon la taille de l'image qui le contient. Tout d'abord, voilà les dimensions d'un hexagone en vue 2D :

{Image (attachment) /hexa_iso5.png}

On constate donc qu'un hexagone de côté n prend une largeur valant n + n/2*2 soit simplement n*2. Pour résumer, un hexagone de 50 pixels de côté prendra 100 pixels de large. Pour calculer la hauteur, je calcule tout d'abord une moitiée de hauteur en me basant sur un triangle rectangle dont l'hypoténuse vaut n et les deux autres angles 60° (la moitié d'un angle d'hexagone) et 30°. Avec un peu de trigonométrie, on apprend que la moitié de la hauteur d'un hexagone fait {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_a84bb66f4afa6ef43d449f322e45fc4d.png} (on aurait pu utiliser {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_2c0a693ae51243048c00b563fe922a8a.png} aussi). Un hexagone fait donc {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_7301ddb9e73f7323942c7f457b1dc96a.png} unités de hauteur.
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_993_5c1eab05d04bf78ce33b47ccb301bd0a.png}

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_3acb9c927661b117908c013537c14115.png}

Sachant celà, réfléchissons à la création d'une carte hexagonale simple (en 2D) dans ce style là :

{Image (attachment) /hexa_iso6.png}

Je n'ai pas tracé de repères mais en regardant bien, on constate qu'en abscisse, une tuile est placée un peu sur la tuile précédente : elle débute à l'endroit où arrive le côté le plus haut de sa voisine (qui vaut n, si on regarde le schéma précédent). Il ne faut pas oublier de compter le coté adjacent de gauche qui fait {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_f7282708b69db46514a66f531d242d0d.png} de largeur. On a donc une tuile qui se place {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_3472ec3b9719be9c2f03ee998f71a20d.png} pixels plus loin que sa voisine de gauche. Si on multiplie par le numéro de colonne (X), on obtient la formule suivante :
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_993_807f7b8781b1aebee75dca893eb0a4fe.png}

Pour la position de la tuile en Y, c'est à peine plus compliqué : on place les hexagones les uns sur les autres. Il nous suffit donc de multiplier le numéro de la ligne (Y, qui commence à 0, ne l'oublions pas) par la hauteur d'un hexagone, ce qui nous donne : posY = Y*sin(60)*2*n. Oui mais là, notre formule est incomplète car les cartes hexagonales nécessitent d'alterner les cases paires et impaires (c'est cet effet dentelé que l'on a en haut et en bas de la carte). Pour celà, on rajoute va simplement décaler d'une demie hauteur de plus les hexagones impairs en X. La formule X%2 renvoie 0 ou 1 : l'opérateur % (le modulo) retourne le reste d'une division euclidienne. A nous de multiplier ce chiffre par une demie hauteur (si la case est paire, ce décalage sera nul). La formule finale de posY est donc la suivante :
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_566697aea42a2b514868a5dbdfc964f6.png}

4. Passons à l'isométrique

Bien, enfin le cœur du sujet ! La difficulté va maintenant être l'association des contraintes liées à l'isométrique (que nous avons vues plus haut) et des contraintes liées à l'hexagonal. Comme précédemment, j'ai passé beaucoup de temps sur l'étude des hexagones en eux-mêmes. Voici le schéma d'un hexagone ayant subi une simple rotation à 45° vers la droite :

{Image (attachment) /hexa_iso7.png}

Cette figure va nous servir jusqu'à la fin pour trouver la formule. J'ai tracé des triangles rectangles dans la figure pour pouvoir connaitre les hauteurs et largeurs des différents côtés de notre hexagone tourné grâce à la trigonométrie. Je ne m'attarde pas sur la figure et je continue. Dans un premier temps, voilà ce que je cherche à obtenir :

{Image (attachment) /hexa_iso8.png}

Si vous avez l'oeil exercé à la vue isométrique, vous constaterez que cette carte est fausse car elle part trop vers le haut. Mais si nous parvenons à placer les tuiles de cette façon, une très légère modification, tenant compte du crènelage propre à l'hexagonal, permettra d'avoir une carte parfaite. Bon, je rajoute un schéma de plus (encore34!) pour voir où est l'origine de nos hexotuiles isométriques :

{Image (attachment) /hexa_iso9.png}

Alors, si on regarde la première ligne et que l'on cherche la position en X, on constate qu'une tuile commence là où se terminent les deux côtés les plus bas de sa voisine de gauche (le deuxième trait rouge, en haut). Il faut donc mesurer la largeur prise par ces deux côtés (qui sont tordus). Pour cela, on va utiliser le schéma de l'hexagone tourné (même si il n'est pas écrasé, la largeur reste la même). Comme les côtés opposés sont identiques, on peut utiliser les formules que j'ai notées et la somme des largeurs des deux côtés fait donc : {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_2119ba4195b22fe2ead1c0f127188628.png}. Il suffit de multiplier par le numéro de colonne, et on place les tuiles à la suite les unes des autres.

Mais ce positionnement en abscisse est valable uniquement pour la première ligne : si vous vous rappelez la formule utilisée par les carrés isométriques, chaque nouvelle ligne est décalée sur la gauche. On va donc reprendre ce principe et regarder de combien sont décalées les lignes les unes par rapport au autres. Un peu d'observation nous apprend que le point le plus à gauche de la deuxième ligne est séparé par deux côtés du point le plus à gauche de la première ligne. Un coup d'œil au schéma précédent et on sait que ce décalage vaut {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_58399f0ffea20f66b66f4cf3165ee6f0.png}. Puisque l'on reproduit ce décalage à chaque nouvelle ligne créée, on multiplie cette petite formule par le numéro de la ligne. La formule finale est donc la suivante :
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_989_47c99a8e0e96abc7b35496d76897c66c.png}

Il reste maintenant à trouver le positionnement vertical. Même chose que précédemment : on regarde le nombre de côtés d'écart. Par exemple, le côté le plus haut de l'hexagone situé en bas à gauche est à 2 côtés de distance du coin le plus haut de son voisin de dessus. On se reporte au schéma des distances et on calcule l'ensemble, ce qui nous donne sin(75)*n + sin(15)*n unités de distance. Mais ce schéma n'était pas écrasé (la dernière étape pour transformer une forme en son équivalent isométrique). On divise donc la valeur obtenue par deux, sans oublier de la multiplier par le nombre de lignes, ce qui nous donne
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_1525a0d7630a1bae505a93b4ed6e6318.png}

Bon, mais on a encore un léger problème, lié à la représentation isométrique. On est obligé de décaler légèrement une case vers le bas quand on ajoute une nouvelle colonne. Si on observe la première ligne bleue sur le schéma contenant les quatre tuiles hexagonales, on se rend compte que, pour la première fois, on ne tombe pas sur un angle. On ne va donc pas pouvoir utiliser la même technique que précédemment (celle du comptage de côtés34) pour mesurer ce décalage. J'ai beaucoup chercher un moyen de trouver la valeur exacte ({Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_cf8f1baf91e12136974126b8e5982a6d.png} s'en rapproche beaucoup) mais je ne l'ai pas trouvé (si un super matheux est motivé, il peut chercher 16). J'ai donc recherché la valeur approchée la plus précise possible à l'aide de sin-1 sur un cas d'application. Bref, le décalage est de sin(13.5)*n à peu de choses près, et comme on en tient compte à chaque nouvelle colonne, il ne faut pas oublier de la multiplier pour chaque valeur de X. On se retrouve donc avec la formule finale suivante :
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_989_47c99a8e0e96abc7b35496d76897c66c.png}

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_300d57ccc00071550407c38099da943a.png}

Ouf. On y est presque ! Cette formule permet donc de générer la carte (fausse) que je vous ai présenté au début du point n°4. Mais une vraie carte hexagonale isométrique ressemble plutôt à ça :

{Image (attachment) /hexa_iso10.png}

Nous devons donc respecter le crènelage des bords et les hexagones doivent suivre une ligne plus naturelle. Pour celà, il suffit juste de modifier la formule précédente. Si on veut le crènelage en abscisses, on décale d'une hauteur d'hexagone toutes les deux colonnes. Pour celà, il suffit remplacer la valeur Y des deux formules précédentes par Y+un arrondi au supérieur de {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_5b967d27a170806b74bfd8f9abceb532.png}, ce qui nous donne :
{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_977.5_b15cd6050fb6115fe816ae9475a4914f.png}

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_982_5f8d85e45b5a5aae156c1b3c7c0280c4.png}

Même principe si on veut le crénelage sur l'axe des ordonnées : on remplace X par X+arrondi.sup(Y/2).

Enfin, la dernière chose à prendre en compte est le décalage à gauche occasionné par la 3D isométrique. Pour compenser tout ça, nous devons donc ajouter une variable "decalage" à posX, pour forcer le placement plus loin vers la droite. Si le crènelage est en abscisse, le décalage est facile à trouver car il est basé sur le décalage négatif de chaque nouvelle ligne {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_989_467bc5f5630c7ede0e761eb7d72387c3.png}. Si on multiplie cela par le nombre total de lignes - 1 (on commence à 0), on a le décalage final.

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_989_35df03dbc71bb6c239462946e381c3ae.png}

Par contre, si notre crènelage est en ordonnées (à gauche), il faut en tenir compte dans la variable décalage. Le calcul est un peu plus compliqué, mais pas bien sorcier : on décale de la largeur du côté de gauche ({Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_991.5_ad46a275e460056b1f4e38dbe34f191f.png}) et on double le chiffre une fois sur deux (le crènelage), ce qui donne :

{Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_989_29a934455c637dfbd8d227daeb798566.png}

Voilà ! Normalement, on a une carte nickel ! Je précise une dernière chose : n'oubliez pas que la valeur n (taille des côtés de l'hexagone) est toujours basée sur la représentation à plat de la tuile (en 2D). C'est à dire qu'il suffit de connaitre la taille de notre hexagone avant la transformation pour utiliser cette formule.

Enfin, prenez garde : beaucoup de langages de programmation utilisent les radians plutôt que les degrés dans les fonctions de trigo. Si nécessaire, vous devez donc convertir vos degrés en radians en multipliant par {Image (attachment) /fetch.php?w=&h=&cache=cache&media=cache_mathplugin%3amath_993_24022b2dfddc0f09db497d4db564dca8.png} et en divisant par 180.


par Harparine