[TUTO] Réaliser un tunnel en 3D
Posted: 08 Oct 2012, 17:15
Salut les gens !
Pour ceux qui auraient vu mon récent projet SpeedX 3D, vous vous êtes peut-être possiblement éventuellement demandé (mais c'est pas sûr) comment j'avais réalisé cet effet tunnel 3D, qui est ma foi fort sexy si je ne m'abuse !
Et bien cet effet, non seulement il est super pratique pour épater la galerie, mais en plus il est complètement facile à coder !
Et c'est justement le sujet de ce tuto, coder un tunnel en 3D avec textures, rotations et mouvements du centre s'il vous plaît !
Le principe
Pour réaliser ce tunnel 3D, et avant la pratique, il va forcément falloir un peu de théorie (un peu hein, moi même j'aime pas la théorie ), de façon à savoir comment réaliser tout ça avant de le faire pour de vrai.
Le principe donc, c'est qu'on va utiliser une texture, c'est à dire une image qu'on va appliquer sur les murs du tunnel mais en la déformant pour donner l'illusion de profondeur. Sauf qu'on peut pas afficher l'image déformée comme ça, donc on va parcourir tous les pixels de l'écran pour afficher notre texture point par point, comme ça on pourra y appliquer l'effet de profondeur via des formules mathématiquesinaccessibles au commun des mortels simples comme tout
Réaliser une texture
Pour dessiner une texture, et bien c'est complètement facile : vous prenez une image et c'est fini. Bawé.
Sauf que n'importe quelle image ça peut soit donner des résultats complètement dégueu, soit être difficile à utiliser, donc le mieux est encore de prendre une texture carrée (même largeur que longueur, pour ceux qui auraient oublié leurs cours de CE1 ).
Comme je suis trèèès gentil (mais si mais si) je vous offre une texture gratuite à utiliser pour vos tunnels 3D (et qu'on va utiliser pour le tuto) :
C'est une image bitmap 16*16, donc c'est facile à utiliser. Je sais qu'elle a l'air pitite, mais ça s'adapte très bien en code.
Calculer l'angle
Notre tunnel est composé de plein de répétitions de cette texture, ça ok, mais comment savoir où afficher quoi ?
En fait, chaque point (et pas pixel) à afficher a une abscisse et une ordonnée, qui en fait représentent respectivement un angle et une profondeur :
http://benryves.com/tutorials/tunnel/ (Anglais)
Pourquoi pas pixel ? Hé bien parce qu'un pixel est allumé sur l'écran, alors qu'un point est un objet géométrique. Je développerai ça un peu plus loin.
On doit donc calculer l'angle de chaque point par rapport au centre de l'image. Seulement, un écran de Nspire fait 320*240 pixels, soit ... 76800 angles à calculer Je sais pas vous, mais une boucle où on fait 76800 calculs d'angle, ça me semble pas hyper rapide. On va donc créer ce qu'on appelle une Look Up Table (ou LUT), c'est à dire un tableau de même taille que notre écran qu'on va remplir d'une donnée une bonne fois pour toute, comme ça au lieu de recalculer X fois une valeur via un calcul qui prend une minute 30, on aura juste à aller chercher cette valeur dans la LUT. Mais ça c'est dans la partie pratique
Voici une image qui dit à elle toute seul comment calculer notre angle :
http://benryves.com/tutorials/tunnel/ (Anglais)
Donc l'abscisse X de notre point sera calculée avec la formule :
Calculer la profondeur
Maintenant qu'on sait comment calculer l'angle, c'est à dire l'abscisse de notre point, on va passer à l'ordonnée, c'est à dire la profondeur du point dans le tunnel. Il faut effectivement penser au tunnel comme un objet en 3D et pas comme une projection sur un plan.
Ici c'est encore plus simple, puisque la profondeur se calcule à partir de la distance de chaque pixel au centre.
Par contre, attention ! Pendant ce calcul, il FAUT que le centre de l'écran ait les coordonnées (0,0) ! Il faudra donc appliquer un calcul sur les valeurs x et y.
Même chose que pour l'angle, on ne va pas répéter une grosse division comme ça 76800 fois, donc on va créer une Look Up Table pour sauvegarder nos valeurs.
Calculer la couleur et afficher le point
Maintenant qu'on a l'angle et la profondeur du point, on va pouvoir calculer sa couleur et l'afficher.
"Comment ça, calculer sa couleur ?" me direz-vous (mais si vous le dites, je vous entends d'ici). Et bien je vous rappelle qu'on veut utiliser une texture, donc il faut savoir exactement quel pixel allumer ou pas.
Bah vous savez pas la meilleur ? C'est encore plus simple que le reste.
Et oui, on a juste à utiliser notre texture comme une palette de couleur, et on met le pixel de l'écran de la couleur située à l'adresse (angle, profondeur) sur notre texture. C'est pas beau ça ?
La pratique
Voilà, maintenant qu'on sait de quoi il en ressort, on va pouvoir appliquer tout ça !
Munissez-vous donc de votre moyen préféré de faire des programmes Ndless, et on attaque !
Prérequis
Je sais pas vous, mais j'ai pas spécialement envie de réinventer toutes les commandes du C On va donc utiliser quelques includes :
On va avoir besoin de fonctions mathématiques (d'une seule en fait, l'arc tangente à deux arguments), donc on va devoir utiliser fdlibm.h, qui est une full implémentation de toutes les fonctions mathématiques de math.h, par Hoffa (le créateur de la nSDL en fait). Vous pouvez télécharger fdlibm sur le site de Hoffa.
Une fois téléchargée, il faut lier fdlibm à votre programme pour pouvoir utiliser ses fonctions, vu que c'est une librairie statique. Éditez le makefile de votre programme, et dans la ligne LDFLAGS écrivez ceci :
Ensuite, il vous faudra aussi les commandes pour allumer un pixel et autres trucs de base relatifs à Ndless. Je vous ai créé deux petits fichiers que vous pourrez utiliser pour vos programmes (je ne vais pas détailler toutes les fonctions, vous comprendrez en creusant un peu).
Pour les utiliser, ajoutez-les dans le dossier de votre programme et ajoutez la ligne d'include à votre code :
Définir les variables
Maintenant qu'on a nos includes de prêts, on va définir les variables qu'on va utiliser. Elles ne sont pas très nombreuses :
Initialiser les variables
On n'a que trois variables à initialiser : le buffer (parce qu'on ne connaît pas sa taille) et les LUT (parce qu'elles sont très grandes).
Pour les LUT, on connaît leur taille en éléments, mais il faut faire attention, car on ne connaît pas leur taille en octets, ni même la taille en octets de leurs éléments ; c'est donc un peu compliqué.
Pour le buffer, on doit le faire de la taille de l'écran. Heureusement, le SDK Ndless propose une constante appelée SCREEN_BYTES_SIZE qui est en fait la taille de l'écran en nombre d'éléments (dans os.h ou common.h, je sais plus ). L'initialisation devient alors toute simple :
On a donc notre code entier de pour l'instant :
Voilà, maintenant on peut vraiment y aller
Remplir les LUT
Maintenant qu'on a tout bien initialisé, on va pouvoir commencer à remplir les LUT comme il se doit.
On va donc parcourir tout l'écran pour enregistrer une valeur correspondant à chaque pixel.
Ensuite, on va calculer les coordonnées relatives du pixel courant. Pour cela, il faut que le milieu de l'écran soit à (0,0), donc on a juste à retrancher la moitié de la dimension à x et y.
Et on est bon pour calculer l'angle et la profondeur !
Remarque sur l'angle
En général, les commandes de trigo en programmation renvoient des radians. Ce cas-ci n'est pas une exception, la commande arc tangente renvoie bien des radians. Heureusement, il existe un moyen simple d'avoir la valeur avec l'unité de notre choix via une formule non moins simple :
Avec M_PI la constante p et nombre_de_textures_horizontales le nombre de répétitions horizontales de la texture. En français (:D), ça veut dire que si vous voulez 4 répétitions horizontales de la texture, la texture sera étirée pour que seulement 4 textures mises côte à côte fasse le tour du tunnel. Pour moi ça fait peu, j'ai l'habitude d'afficher 8 répétitions.
On peut donc calculer nos coordonnées et remplir nos LUT :
Et voilà, nos tables sont remplies, c'était rapide !
Calculer la couleur
Maintenant, on entre dans la boucle du jeu, qu'on ne va quitter qu'avec l'appui sur, disons, .
À chaque tour de boucle, on efface le buffer, puis on parcourt l'écran en entier pour calculer la couleur.
Le calcul de la couleur est simple : on prend l'angle correspondant dans la LUT, on lui applique un modulo pour qu'il reste dans la limite de la largeur de la texture, et on fait la même chose avec la profondeur et la hauteur de la texture. Ensuite, on multiplie la profondeur ainsi modulée par la largeur de la texture (car on pense en lignes remplies de colonnes) et on ajoute l'angle modulé.
Une optimisation très intéressante si votre texture a des dimensions en puissances de 2 (2, 4, 8, 16, 32 ...) est de remplacer le modulo par un AND bit à bit.
549 / 16 = 34 ; reste = 5. Donc 549 % 16 = 5.
549 & 15
?
0000001000100101
&
0000000000001111
=
0000000000000101 ? 5.
On a bien le même résultat, les opérations sont donc équivalentes.
Notre variable color contient donc l'octet qu'il faut ? la couleur du pixel courant.
On a plus qu'à allumer le pixel de la bonne couleur :
Et c'est fini ! On a notre tunnel 3D.
Voici le code complet :
Résultat :
Animer le tunnel
Déplacer le tunnel
Maintenant qu'on arrive à dessiner correctement notre tunnel 3D, pourquoi ne pas le faire avancer, reculer, tourner dans les deux sens ? Trop facile !
Pour cela, on a besoin de deux variables supplémentaires, tunnel_rotate et tunnel_zoom.
Et vous savez ce que vous en faites ? Vous les ajoutez à textureX et textureY.
Et c'est tout, c'est fini.
Vous n'avez plus qu'à changer la valeur de ces deux variables pour animer le tunnel
Ce qui donne :
Houlà ... un peu lent nan ? Mais ça ne servirai à rien d'avancer ou de tourner de plus de pixel à la fois, ça resterait saccadé ...
Alors pourquoi ne pas calculer qu'un point sur quatre ? On ferait quatre fois moins de calculs ! La seule chose qui change (en mal hein) c'est que la résolution est réduite de moitié du coup, mais sur un écran aussi grand on peut se le permettre
Donc, il n'y a quasiment rien à faire pour changer ça, si ce n'est remplacer x++ et y++ dans les boucles for par x += 2 et y += 2.
Oups ... je crois qu'on a oublié quelque chose Il faut effectivement afficher 4 pixels à la fois au lieu d'un.
Voilà, là on est bon !
Déplacer l'origine
Et pourquoi on ferait pas un joueur capable de regarder en haut, en bas, à droite, à gauche ? C'est possible, mais c'est un peu plus compliqué.
Le principe est qu'on va travailler avec 2 fois plus de valeurs X et 2 fois plus de valeurs Y (ce qui résulte en un écran 4 fois plus large), puis on va afficher les bons pixels en utilisant un offset au moment de récupérer les valeurs des deux LUT. Pour vous aider, imaginer les LUT comme deux tableaux posés sur une table, et l'écran comme un calque 4 fois plus petit que ces tableaux, que l'on va passer par dessus et déplacer en fonction des offsets.
Là logiquement vous vous dites "bah on n'a qu'à multiplier la taille des LUT par deux". HÉ BAH NON parce que je suis tellement fort que j'ai déjà pensé à ça, du coup la taille des LUT ne change pas.
Vous vous rappelez de vos boucles For d'initialisation et de dessin ? Là où on allait de 2 en 2 avec x et y ?
Avant d'attaquer l'affichage, on va ajouter deux variables à notre liste ; elles vont contenir les offsets à utiliser pour déplacer l'origine du tunnel (le centre quoi).
Ensuite, pour la phase d'affichage, c'est pas extrêmement plus dur : on va aussi de 1 en 1, par contre on va plus que de 0 à w / 2 (et donc aussi h / 2) :
Après ça, on prend nos deux lignes où on extrait les valeurs des LUT et on ajoute nos offsets dedans
Attention ! Comme maintenant nos x et y ne vont plus que jusque w/2 et h/2, il faut les multiplier par 2 au moment d'afficher les pixels :
Et c'est presque fini ! Il ne reste plus qu'à donner un moyen à l'utilisateur de gérer ces offsets.
Ici, j'utilise les touches 2, 4, 6 et 8 pour augmenter ou diminuer les offsets de 2 en 2, à condition que le centre ne sorte pas de l'écran, sinon ça lit hors de la table et c'est pas bon.
Pour si peu de travail, résultat :
Et je poste le code complet de chez complet :
Voilà, nous sommes au bout de ce tutoriel. J'espère vous avoir appris quelque chose, et que vous pourrez ensuite en profiter pour créer vos propres jeux ou animations
À plus les gens !
Pour ceux qui auraient vu mon récent projet SpeedX 3D, vous vous êtes peut-être possiblement éventuellement demandé (mais c'est pas sûr) comment j'avais réalisé cet effet tunnel 3D, qui est ma foi fort sexy si je ne m'abuse !
Et bien cet effet, non seulement il est super pratique pour épater la galerie, mais en plus il est complètement facile à coder !
Et c'est justement le sujet de ce tuto, coder un tunnel en 3D avec textures, rotations et mouvements du centre s'il vous plaît !
Le principe
Pour réaliser ce tunnel 3D, et avant la pratique, il va forcément falloir un peu de théorie (un peu hein, moi même j'aime pas la théorie ), de façon à savoir comment réaliser tout ça avant de le faire pour de vrai.
Le principe donc, c'est qu'on va utiliser une texture, c'est à dire une image qu'on va appliquer sur les murs du tunnel mais en la déformant pour donner l'illusion de profondeur. Sauf qu'on peut pas afficher l'image déformée comme ça, donc on va parcourir tous les pixels de l'écran pour afficher notre texture point par point, comme ça on pourra y appliquer l'effet de profondeur via des formules mathématiques
Réaliser une texture
Pour dessiner une texture, et bien c'est complètement facile : vous prenez une image et c'est fini. Bawé.
Sauf que n'importe quelle image ça peut soit donner des résultats complètement dégueu, soit être difficile à utiliser, donc le mieux est encore de prendre une texture carrée (même largeur que longueur, pour ceux qui auraient oublié leurs cours de CE1 ).
Comme je suis trèèès gentil (mais si mais si) je vous offre une texture gratuite à utiliser pour vos tunnels 3D (et qu'on va utiliser pour le tuto) :
C'est une image bitmap 16*16, donc c'est facile à utiliser. Je sais qu'elle a l'air pitite, mais ça s'adapte très bien en code.
Calculer l'angle
Notre tunnel est composé de plein de répétitions de cette texture, ça ok, mais comment savoir où afficher quoi ?
En fait, chaque point (et pas pixel) à afficher a une abscisse et une ordonnée, qui en fait représentent respectivement un angle et une profondeur :
http://benryves.com/tutorials/tunnel/ (Anglais)
Pourquoi pas pixel ? Hé bien parce qu'un pixel est allumé sur l'écran, alors qu'un point est un objet géométrique. Je développerai ça un peu plus loin.
On doit donc calculer l'angle de chaque point par rapport au centre de l'image. Seulement, un écran de Nspire fait 320*240 pixels, soit ... 76800 angles à calculer Je sais pas vous, mais une boucle où on fait 76800 calculs d'angle, ça me semble pas hyper rapide. On va donc créer ce qu'on appelle une Look Up Table (ou LUT), c'est à dire un tableau de même taille que notre écran qu'on va remplir d'une donnée une bonne fois pour toute, comme ça au lieu de recalculer X fois une valeur via un calcul qui prend une minute 30, on aura juste à aller chercher cette valeur dans la LUT. Mais ça c'est dans la partie pratique
Voici une image qui dit à elle toute seul comment calculer notre angle :
http://benryves.com/tutorials/tunnel/ (Anglais)
Donc l'abscisse X de notre point sera calculée avec la formule :
point.x = atan(x, y)
Calculer la profondeur
Maintenant qu'on sait comment calculer l'angle, c'est à dire l'abscisse de notre point, on va passer à l'ordonnée, c'est à dire la profondeur du point dans le tunnel. Il faut effectivement penser au tunnel comme un objet en 3D et pas comme une projection sur un plan.
Ici c'est encore plus simple, puisque la profondeur se calcule à partir de la distance de chaque pixel au centre.
point.y = screen_resolution / (x² + y²)
Par contre, attention ! Pendant ce calcul, il FAUT que le centre de l'écran ait les coordonnées (0,0) ! Il faudra donc appliquer un calcul sur les valeurs x et y.
Même chose que pour l'angle, on ne va pas répéter une grosse division comme ça 76800 fois, donc on va créer une Look Up Table pour sauvegarder nos valeurs.
Calculer la couleur et afficher le point
Maintenant qu'on a l'angle et la profondeur du point, on va pouvoir calculer sa couleur et l'afficher.
"Comment ça, calculer sa couleur ?" me direz-vous (mais si vous le dites, je vous entends d'ici). Et bien je vous rappelle qu'on veut utiliser une texture, donc il faut savoir exactement quel pixel allumer ou pas.
Bah vous savez pas la meilleur ? C'est encore plus simple que le reste.
pixelColor(x,y) = texture[angle][profondeur]
Et oui, on a juste à utiliser notre texture comme une palette de couleur, et on met le pixel de l'écran de la couleur située à l'adresse (angle, profondeur) sur notre texture. C'est pas beau ça ?
La pratique
Voilà, maintenant qu'on sait de quoi il en ressort, on va pouvoir appliquer tout ça !
Munissez-vous donc de votre moyen préféré de faire des programmes Ndless, et on attaque !
Prérequis
Je sais pas vous, mais j'ai pas spécialement envie de réinventer toutes les commandes du C On va donc utiliser quelques includes :
- Code: Select all
#include <os.h>
#include <common.h>
#include <fdlibm.h>
On va avoir besoin de fonctions mathématiques (d'une seule en fait, l'arc tangente à deux arguments), donc on va devoir utiliser fdlibm.h, qui est une full implémentation de toutes les fonctions mathématiques de math.h, par Hoffa (le créateur de la nSDL en fait). Vous pouvez télécharger fdlibm sur le site de Hoffa.
Une fois téléchargée, il faut lier fdlibm à votre programme pour pouvoir utiliser ses fonctions, vu que c'est une librairie statique. Éditez le makefile de votre programme, et dans la ligne LDFLAGS écrivez ceci :
- Code: Select all
LDFLAGS = -lfdm
Ensuite, il vous faudra aussi les commandes pour allumer un pixel et autres trucs de base relatifs à Ndless. Je vous ai créé deux petits fichiers que vous pourrez utiliser pour vos programmes (je ne vais pas détailler toutes les fonctions, vous comprendrez en creusant un peu).
Pour les utiliser, ajoutez-les dans le dossier de votre programme et ajoutez la ligne d'include à votre code :
- Code: Select all
#include "utils.h"
Définir les variables
Maintenant qu'on a nos includes de prêts, on va définir les variables qu'on va utiliser. Elles ne sont pas très nombreuses :
- Un buffer, où on va allumer les pixels plutôt que sur l'écran pour ne pas voir la scène se dessiner petit à petit.
- Code: Select all
char *screen;
- Nos Look Up Tables. Comme elles sont très grosses, on ne va déclarer qu'une dimension et malloc le reste.
- Code: Select all
double (*angle_lut)[240];
double (*depth_lut)[240];
- Des variables qui vont contenir les coordonnées relatives du point, c'est à dire où le centre de l'écran est à (0,0).
- Code: Select all
double relativeX, relativeY;
- Des variables qui vont contenir les coordonnées de la couleur sur la texture.
- Code: Select all
int textureX, textureY;
- Des variables pour les boucles For qui vont parcourir l'écran.
- Code: Select all
int x, y;
- Une variable qui va contenir la couleur (on pourra éventuellement la modifier).
- Code: Select all
char color;
- Des variables qui vont contenir les dimensions de l'écran.
- Code: Select all
int w = 320, h = 240;
- Et pour finir, notre texture !
- Code: Select all
char texture[256] = {
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
- Code: Select all
int main(void)
{
char *screen;
double (*angle_lut)[240];
double (*depth_lut)[240];
double relativeX, relativeY;
int textureX, textureY;
int w = 320, h = 240, x, y;
char color;
char texture[256] = {
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
Initialiser les variables
On n'a que trois variables à initialiser : le buffer (parce qu'on ne connaît pas sa taille) et les LUT (parce qu'elles sont très grandes).
Pour les LUT, on connaît leur taille en éléments, mais il faut faire attention, car on ne connaît pas leur taille en octets, ni même la taille en octets de leurs éléments ; c'est donc un peu compliqué.
- Code: Select all
angle_lut = malloc(w * sizeof(*angle_lut)); // un tableau de la largeur de l'écran, rempli avec des éléments de la bonne taille
if(!angle_lut) exit(0); // l'initialisation a raté
depth_lut = malloc(w * sizeof(*depth_lut));
if(!depth_lut)
{
free(angle_lut);
exit(0);
}
Pour le buffer, on doit le faire de la taille de l'écran. Heureusement, le SDK Ndless propose une constante appelée SCREEN_BYTES_SIZE qui est en fait la taille de l'écran en nombre d'éléments (dans os.h ou common.h, je sais plus ). L'initialisation devient alors toute simple :
- Code: Select all
screen = malloc(SCREEN_BYTES_SIZE * sizeof(char));
if(!screen)
{
free(angle_lut);
free(depth_lut);
exit(0);
}
On a donc notre code entier de pour l'instant :
- Code: Select all
#include <os.h>
#include <common.h>
#include <fdlibm.h>
#include "utils.h"
#define TEXTURE_WIDTH 16 // pourquoi pas
#define TEXTURE_HEIGHT 16
int main(void)
{
char *screen;
double (*angle_lut)[240];
double (*depth_lut)[240];
double relativeX, relativeY;
int textureX, textureY;
int w = 320, h = 240, x, y;
char color;
char texture[256] = {
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
lcd_ingray(); // Pour fonctionner sur toutes caltos
angle_lut = malloc(w * sizeof(*angle_lut));
if(!angle_lut) exit(0);
depth_lut = malloc(w * sizeof(*depth_lut));
if(!depth_lut)
{
free(angle_lut);
exit(0);
}
screen = malloc(SCREEN_BYTES_SIZE * sizeof(char));
if(!screen)
{
free(angle_lut);
free(depth_lut);
exit(0);
}
Voilà, maintenant on peut vraiment y aller
Remplir les LUT
Maintenant qu'on a tout bien initialisé, on va pouvoir commencer à remplir les LUT comme il se doit.
On va donc parcourir tout l'écran pour enregistrer une valeur correspondant à chaque pixel.
- Code: Select all
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
}
}
Ensuite, on va calculer les coordonnées relatives du pixel courant. Pour cela, il faut que le milieu de l'écran soit à (0,0), donc on a juste à retrancher la moitié de la dimension à x et y.
- Code: Select all
for(y = 0; y < h; y++)
{
relativeY = h / 2 - y; // le positif est en haut et le négatif en bas
for(x = 0; x < w; x++)
{
relativeX = x - w / 2; // le négatif est à gauche et le positif à droite
}
}
Et on est bon pour calculer l'angle et la profondeur !
Remarque sur l'angle
En général, les commandes de trigo en programmation renvoient des radians. Ce cas-ci n'est pas une exception, la commande arc tangente renvoie bien des radians. Heureusement, il existe un moyen simple d'avoir la valeur avec l'unité de notre choix via une formule non moins simple :
angle = atan2(x,y) * (TEXTURE_WIDTH / M_PI) * (nombre_de_textures_horizontales / 2)
Avec M_PI la constante p et nombre_de_textures_horizontales le nombre de répétitions horizontales de la texture. En français (:D), ça veut dire que si vous voulez 4 répétitions horizontales de la texture, la texture sera étirée pour que seulement 4 textures mises côte à côte fasse le tour du tunnel. Pour moi ça fait peu, j'ai l'habitude d'afficher 8 répétitions.
On peut donc calculer nos coordonnées et remplir nos LUT :
- Code: Select all
for(y = 0; y < h; y++)
{
relativeY = h / 2 - y;
for(x = 0; x < w; x++)
{
relativeX = x - w / 2;
depth_lut[x][y] = w*h*2 / max(relativeX * relativeX + relativeY * relativeY, 1); // on évite une division par 0
angle_lut[x][y] = atan2(relativeX, relativeY) * (TEXTURE_WIDTH / M_PI) * 4; // 8 répétitions
}
}
Et voilà, nos tables sont remplies, c'était rapide !
Calculer la couleur
Maintenant, on entre dans la boucle du jeu, qu'on ne va quitter qu'avec l'appui sur, disons, .
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
}
À chaque tour de boucle, on efface le buffer, puis on parcourt l'écran en entier pour calculer la couleur.
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
textureX = angle_lut[x][y];
textureY = depth_lut[x][y];
}
}
}
Le calcul de la couleur est simple : on prend l'angle correspondant dans la LUT, on lui applique un modulo pour qu'il reste dans la limite de la largeur de la texture, et on fait la même chose avec la profondeur et la hauteur de la texture. Ensuite, on multiplie la profondeur ainsi modulée par la largeur de la texture (car on pense en lignes remplies de colonnes) et on ajoute l'angle modulé.
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
textureX = angle_lut[x][y];
textureY = depth_lut[x][y];
color = texture[(textureY % TEXTURE_HEIGHT) * TEXTURE_WIDTH + (textureX % TEXTURE_WIDTH)];
}
}
}
Une optimisation très intéressante si votre texture a des dimensions en puissances de 2 (2, 4, 8, 16, 32 ...) est de remplacer le modulo par un AND bit à bit.
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
textureX = angle_lut[x][y];
textureY = depth_lut[x][y];
color = texture[(textureY & (TEXTURE_HEIGHT - 1)) * TEXTURE_WIDTH + (textureX & (TEXTURE_WIDTH - 1))];
}
}
}
549 / 16 = 34 ; reste = 5. Donc 549 % 16 = 5.
549 & 15
?
0000001000100101
&
0000000000001111
=
0000000000000101 ? 5.
On a bien le même résultat, les opérations sont donc équivalentes.
Notre variable color contient donc l'octet qu'il faut ? la couleur du pixel courant.
On a plus qu'à allumer le pixel de la bonne couleur :
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
textureX = angle_lut[x][y];
textureY = depth_lut[x][y];
color = texture[(textureY & (TEXTURE_HEIGHT - 1)) * TEXTURE_WIDTH + (textureX & (TEXTURE_WIDTH - 1))];
setPixelBuf(screen, x, y, color);
}
}
refresh(screen); // on copie le buffer à l'écran
}
Et c'est fini ! On a notre tunnel 3D.
Voici le code complet :
- Code: Select all
#include <os.h>
#include <common.h>
#include <fdlibm.h>
#include "utils.h"
#define TEXTURE_WIDTH 16
#define TEXTURE_HEIGHT 16
int main(void)
{
char *screen;
double (*angle_lut)[240];
double (*depth_lut)[240];
double relativeX, relativeY;
int textureX, textureY;
int w = 320, h = 240, x, y;
char color;
char texture[256] = {
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
lcd_ingray();
angle_lut = malloc(w * sizeof(*angle_lut));
if(!angle_lut) exit(0);
depth_lut = malloc(w * sizeof(*depth_lut));
if(!depth_lut)
{
free(angle_lut);
exit(0);
}
screen = malloc(SCREEN_BYTES_SIZE * sizeof(char));
if(!screen)
{
free(angle_lut);
free(depth_lut);
exit(0);
}
for(y = 0; y < h; y++)
{
relativeY = h / 2 - y;
for(x = 0; x < w; x++)
{
relativeX = x - w / 2;
depth_lut[x][y] = w*h*2 / max(relativeX * relativeX + relativeY * relativeY, 1);
angle_lut[x][y] = atan2(relativeX, relativeY) * (TEXTURE_WIDTH / M_PI) * 4;
}
}
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y++)
{
for(x = 0; x < w; x++)
{
textureX = angle_lut[x][y];
textureY = depth_lut[x][y];
color = texture[(textureY & (TEXTURE_HEIGHT - 1)) * TEXTURE_WIDTH + (textureX & (TEXTURE_WIDTH - 1))];
setPixelBuf(screen, x, y, color);
}
}
refresh(screen);
}
// On n'oublie pas de libérer tous les malloc
free(screen);
free(angle_lut);
free(depth_lut);
return 0;
}
Résultat :
Animer le tunnel
Déplacer le tunnel
Maintenant qu'on arrive à dessiner correctement notre tunnel 3D, pourquoi ne pas le faire avancer, reculer, tourner dans les deux sens ? Trop facile !
Pour cela, on a besoin de deux variables supplémentaires, tunnel_rotate et tunnel_zoom.
- Code: Select all
int tunnel_rotate = 0, tunnel_zoom = 0;
Et vous savez ce que vous en faites ? Vous les ajoutez à textureX et textureY.
- Code: Select all
textureX = angle_lut[x][y] + tunnel_rotate;
textureY = depth_lut[x][y] + tunnel_zoom;
Et c'est tout, c'est fini.
Vous n'avez plus qu'à changer la valeur de ces deux variables pour animer le tunnel
- Code: Select all
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h; y+=2)
{
for(x = 0; x < w; x+=2)
{
textureX = angle_lut[x][y] + tunnel_rotate;
textureY = depth_lut[x][y] + tunnel_zoom;
color = texture[(textureY & (TEXTURE_HEIGHT - 1)) * TEXTURE_WIDTH + (textureX & (TEXTURE_WIDTH - 1))];
setPixelBuf(screen, x, y, color);
}
}
refresh(screen);
if(isKeyPressed(KEY_NSPIRE_LEFT)) tunnel_rotate++;
if(isKeyPressed(KEY_NSPIRE_RIGHT)) tunnel_rotate--;
if(isKeyPressed(KEY_NSPIRE_UP)) tunnel_zoom++;
if(isKeyPressed(KEY_NSPIRE_DOWN)) tunnel_zoom--;
}
Ce qui donne :
Houlà ... un peu lent nan ? Mais ça ne servirai à rien d'avancer ou de tourner de plus de pixel à la fois, ça resterait saccadé ...
Alors pourquoi ne pas calculer qu'un point sur quatre ? On ferait quatre fois moins de calculs ! La seule chose qui change (en mal hein) c'est que la résolution est réduite de moitié du coup, mais sur un écran aussi grand on peut se le permettre
Donc, il n'y a quasiment rien à faire pour changer ça, si ce n'est remplacer x++ et y++ dans les boucles for par x += 2 et y += 2.
Oups ... je crois qu'on a oublié quelque chose Il faut effectivement afficher 4 pixels à la fois au lieu d'un.
- Code: Select all
setPixelBuf(screen, x, y, color);
setPixelBuf(screen, x+1, y, color);
setPixelBuf(screen, x, y+1, color);
setPixelBuf(screen, x+1, y+1, color);
Voilà, là on est bon !
Déplacer l'origine
Et pourquoi on ferait pas un joueur capable de regarder en haut, en bas, à droite, à gauche ? C'est possible, mais c'est un peu plus compliqué.
Le principe est qu'on va travailler avec 2 fois plus de valeurs X et 2 fois plus de valeurs Y (ce qui résulte en un écran 4 fois plus large), puis on va afficher les bons pixels en utilisant un offset au moment de récupérer les valeurs des deux LUT. Pour vous aider, imaginer les LUT comme deux tableaux posés sur une table, et l'écran comme un calque 4 fois plus petit que ces tableaux, que l'on va passer par dessus et déplacer en fonction des offsets.
Là logiquement vous vous dites "bah on n'a qu'à multiplier la taille des LUT par deux". HÉ BAH NON parce que je suis tellement fort que j'ai déjà pensé à ça, du coup la taille des LUT ne change pas.
Vous vous rappelez de vos boucles For d'initialisation et de dessin ? Là où on allait de 2 en 2 avec x et y ?
- Code: Select all
for(y = 0; y < h; y += 2)
- Code: Select all
for(y = 0; y < h; y++)
...
for(w = 0; y < w; w++)
Avant d'attaquer l'affichage, on va ajouter deux variables à notre liste ; elles vont contenir les offsets à utiliser pour déplacer l'origine du tunnel (le centre quoi).
- Code: Select all
int tunnel_offsetX, tunnel_offsetY;
Ensuite, pour la phase d'affichage, c'est pas extrêmement plus dur : on va aussi de 1 en 1, par contre on va plus que de 0 à w / 2 (et donc aussi h / 2) :
- Code: Select all
for(y = 0; y < h / 2; y++)
{
...
for(x = 0; x < w / 2; x++)
Après ça, on prend nos deux lignes où on extrait les valeurs des LUT et on ajoute nos offsets dedans
- Code: Select all
textureX = angle_lut[x + tunnel_offsetX][y + tunnel_offsetY] + tunnel_rotate;
textureY = depth_lut[x + tunnel_offsetX][y + tunnel_offsetY] + tunnel_zoom;
Attention ! Comme maintenant nos x et y ne vont plus que jusque w/2 et h/2, il faut les multiplier par 2 au moment d'afficher les pixels :
- Code: Select all
setPixelBuf(screen, x*2, y*2, color);
setPixelBuf(screen, x*2+1, y*2, color);
setPixelBuf(screen, x*2, y*2+1, color);
setPixelBuf(screen, x*2+1, y*2+1, color);
Et c'est presque fini ! Il ne reste plus qu'à donner un moyen à l'utilisateur de gérer ces offsets.
- Code: Select all
if(isKeyPressed(KEY_NSPIRE_4)) tunnel_offsetX -= (tunnel_offsetX > 0) * 2;
if(isKeyPressed(KEY_NSPIRE_6)) tunnel_offsetX += (tunnel_offsetX < w/2) * 2;
if(isKeyPressed(KEY_NSPIRE_8)) tunnel_offsetY -= (tunnel_offsetY > 0) * 2;
if(isKeyPressed(KEY_NSPIRE_2)) tunnel_offsetY += (tunnel_offsetY < h/2) * 2;
Ici, j'utilise les touches 2, 4, 6 et 8 pour augmenter ou diminuer les offsets de 2 en 2, à condition que le centre ne sorte pas de l'écran, sinon ça lit hors de la table et c'est pas bon.
Pour si peu de travail, résultat :
Et je poste le code complet de chez complet :
- Code: Select all
#include <os.h>
#include <common.h>
#include <fdlibm.h>
#include "utils.h"
#define TEXTURE_WIDTH 16
#define TEXTURE_HEIGHT 16
int main(void)
{
char *screen;
double (*angle_lut)[240];
double (*depth_lut)[240];
double relativeX, relativeY;
int w = 320, h = 240, x, y;
int textureX, textureY, tunnel_rotate = 0, tunnel_zoom = 0, tunnel_offsetX = w/4, tunnel_offsetY = h/4;
char color;
char texture[256] = {
0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
0xFF,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
lcd_ingray();
angle_lut = malloc(w * sizeof(*angle_lut));
if(!angle_lut) exit(0);
depth_lut = malloc(w * sizeof(*depth_lut));
if(!depth_lut)
{
free(angle_lut);
exit(0);
}
screen = malloc(SCREEN_BYTES_SIZE * sizeof(char));
if(!screen)
{
free(angle_lut);
free(depth_lut);
exit(0);
}
for(y = 0; y < h; y++)
{
relativeY = h/2 - y;
for(x = 0; x < w; x++)
{
relativeX = x - w/2;
depth_lut[x][y] = w*h*2 / max(relativeX * relativeX + relativeY * relativeY, 1);
angle_lut[x][y] = atan2(relativeX, relativeY) * (TEXTURE_WIDTH / M_PI) * 4;
}
}
while(!isKeyPressed(KEY_NSPIRE_ESC))
{
clearBuf(screen);
for(y = 0; y < h/2; y++)
{
for(x = 0; x < w/2; x++)
{
textureX = angle_lut[x + tunnel_offsetX][y + tunnel_offsetY] + tunnel_rotate;
textureY = depth_lut[x + tunnel_offsetX][y + tunnel_offsetY] + tunnel_zoom;
color = texture[(textureY & (TEXTURE_HEIGHT - 1)) * TEXTURE_WIDTH + (textureX & (TEXTURE_WIDTH - 1))];
setPixelBuf(screen, x*2, y*2, color);
setPixelBuf(screen, x*2+1, y*2, color);
setPixelBuf(screen, x*2, y*2+1, color);
setPixelBuf(screen, x*2+1, y*2+1, color);
}
}
refresh(screen);
if(isKeyPressed(KEY_NSPIRE_LEFT)) tunnel_rotate += 2;
if(isKeyPressed(KEY_NSPIRE_RIGHT)) tunnel_rotate -= 2;
if(isKeyPressed(KEY_NSPIRE_UP)) tunnel_zoom += 2;
if(isKeyPressed(KEY_NSPIRE_DOWN)) tunnel_zoom -= 2;
if(isKeyPressed(KEY_NSPIRE_4)) tunnel_offsetX -= (tunnel_offsetX > 0) * 2;
if(isKeyPressed(KEY_NSPIRE_6)) tunnel_offsetX += (tunnel_offsetX < w/2) * 2;
if(isKeyPressed(KEY_NSPIRE_8)) tunnel_offsetY -= (tunnel_offsetY > 0) * 2;
if(isKeyPressed(KEY_NSPIRE_2)) tunnel_offsetY += (tunnel_offsetY < h/2) * 2;
}
// On n'oublie pas de libérer tous les malloc
free(screen);
free(angle_lut);
free(depth_lut);
return 0;
}
Voilà, nous sommes au bout de ce tutoriel. J'espère vous avoir appris quelque chose, et que vous pourrez ensuite en profiter pour créer vos propres jeux ou animations
À plus les gens !