À la quête d'une démo intéressante à présenter cette semaine, on m'a suggéré d'expliquer le code de mon effet de pixels. Le voici donc en exclusivité pour vous, nettoyé, commenté, expliqué, décortiqué, mais toujours non-optimal.

Je vous laisse regarder ce que je vais expliquer, et après on bosse !
Pixel Effect : La théorieJe pense que ce qui sera intéressant dans cette démo qui n'a rien de très intéressant, c'est la construction progressive du code... Je ne vais donc pas tout balancer en bloc, mais juste décrire les étapes successives, le cheminement de ma pensée en le faisant, etc. On ne sait jamais, ça pourrait servir.

Voyons donc les différentes étapes pré-conception, donc SANS AUCUN CODE...
Définir ce qu'on veut faire...Avant toute chose, on doit donc mettre des mots sur l'effet que l'on veut obtenir. Grâce à cela, on pourra apprécier les difficultés potentielles et voir comment les dépasser...
On cherche donc à faire un effet simple, avec des pixels qui se déplacent pour faire une sorte de transition. Ainsi, chaque pixel de l'image va bouger pour prendre la place d'un pixel dans l'image suivante... Il en ressort plusieurs choses à déterminer :
Sous quelle forme refiler les pixels de chaque image ? On pourrait se faire un joli petit tableau avec la position de chaque pixel, mais ça sera ultra galère pour ajouter des images !!! J'ai donc opté pour avoir une image 'standard' que l'on va 'balayer' (un simple for fera l'affaire) pour lire les pixels, et ajouter 1 'pixel' à déplacer pour chaque pixel de l'image trouvé... Pour des questions pratiques, j'ai foutu ça en GIF dans mon code, mais ça n'a aucune espèce d'importance.
Sauf que voilà, si on refile une image, on la fait de quelle taille

J'ai voulu faire de 'gros' pixels, donc 4x4, pour qu'ils ressortent bien. À partir de là, il faut au moins 1 ou 2 pixels de chaque côté, donc en réalité chaque pixel va 'occuper' en gros une case de 6x6...
256/6 => 42,666666666667 (en arrondissant)
192/6 => 32
J'ai donc opté pour une taille d'image à refiler de 42x32, pour ne rien avoir qui dépasse.
Sous quelle forme stocker les pixels ?Là, à priori, le plus simple sera de faire une bête structure du genre machin[n].x et .y, on n'a rien de spécial à stocker comme informations à part ça.

Il faudra au moins 2 tableaux, un pour l'image de départ, un pour l'image d'arrivée, genre... machin[0][n].x, et machin[1][n].x, etc. Pas grand-chose à voir ici...
Comment faire en sorte que chaque pixel de l'image aille à un emplacement différent ?Là, on va devoir bien gérer le côté aléatoire de la chose... Si on fait des random bêtement, on va forcément se retrouver avec certains pixels ayant 2 ou 3 positions d'arrivée, et d'autres pixels non pris ! Il faut donc un système pour que TOUS les pixels soient sélectionnés à un moment ou un autre.
Le plus simple sera donc de mettre tous les pixels dans un tableau, de tirer au sort un pixel, puis de mettre le dernier pixel de la liste à sa place, et dire qu'on a un pixel de moins... Comme ça, on a un tableau parfaitement 'continu', et on ne risque pas de prendre 2 fois le même pixel ou d'en oublier.

De ce point découle un autre problème : on fait quoi si 2 images n'ont pas le même nombre de pixels

Il faudra donc inclure un système pour faire entrer/sortir d'en dehors de l'écran des pixels si besoin.
Point de vue technique, on va faire comment ?On va avoir pas mal de pixels à gérer (au plus 42x32 = 1344, mais on peut limiter en sachant qu'on ne fera pas d'image toute 'pleine'. J'ai choisi de limiter à 256 pour avoir pas mal de flexibilité, mais 512 ça peut être mieux selon les images. Pour ce faire, j'ai choisi d'utiliser les Sprites3D pour avoir assez de sprites, mais on pourrait faire pareil en pixel plot sans trop de problèmes. Mais bon, l'affichage importe peu dans cette démo, c'est plus le cheminement et la construction du code qui est intéressante.
Préparer les ressourcesOn va avoir besoin de peu de choses : les images, et un pixel.

Pour l'image, voici le GIF que j'utilise. Pour le pixel, on fera un bête tableau à remplir avec une boucle for, rien de bien méchant.
Pixel Effect : La pratiqueEt voilà, le temps est venu !
Première étape : initialiser le tout, sans rien faire d'autre...Tout d'abord, quelques tableaux pour décompresser le GIF, la palette, et foutre le pixel comme il faut :
(c):
u8 pixel[8*8] __attribute__ ((aligned (4))) ; // Pour afficher le pixel :p
u16 pixel_tex; // Texture
u8 buffer[42*96] __attribute__ ((aligned (4))) ; // Buffer pour décoder le gif
u16 palette[256]; // Palette du gif
Rien de méchant, non ?

Ensuite, on déclare les tableaux pour les pixels :
(c):
// Structure de position des pixels, rien de spécial
typedef struct{
s16 x, y;
} pos_type;
// Position des pixels (avant-après)
pos_type pos[2][MAX_PIXELS];
u16 npos[2]; // Nombre de pixels
// Temporaire pour faire le random derrière
pos_type postemp[MAX_PIXELS];
u16 npostemp; // Nombre de pixels
Le tableau 'temporaire' sera pour stocker les pixels et faire la sélection aléatoire... On a donc le tableau pos, qui a
- et [1] pour départ/arrivée, un nombre de pixels (256 dans cette démo), et enfin npos[2], qui définit le nombre de pixels pour le départ et l'arrivée.

Je ne vais pas détailler la fonction void Effect_Init(void), qui n'a aucun intérêt, elle ne fait que charger les images, etc., et c'est assez spécifique PAlib, on s'en tape un peu.
Deuxième étape : charger l'image à afficher et préparer le terrainC'est la fonction void Effect_InitBuffer(u8 *buffer) qui va scanner l'image (le buffer) pour chopper les pixels :
(c):
s32 i, x, y;
npostemp = 0;
// Regarder tous les pixels...
for(y = 0; (y < 32) && (npostemp < MAX_PIXELS); y++){
for(x = 0; (x < 42) && (npostemp < MAX_PIXELS); x++){
if(buffer[x+(y)*42]){
postemp[npostemp].x = 3+x*6;
postemp[npostemp].y = 3+y*6;
npostemp++;
}
}
}
On ajoute donc dans la structure temporaire les positions des différents pixels, rien de bien méchant...
Ensuite, on copie les anciens pixels d'arrivée à la position de départ (puisqu'on va partir de là maintenant) :
(c):
// On backup l'autre
npos[0] = npos[1];
for(i = 0; i < npos[0]; i++){
pos[0][i].x = pos[1][i].x;
pos[0][i].y = pos[1][i].y;
}
Maintenant qu'on a 'libéré' pos[1], on va pouvoir mettre les nouveaux pixels, mais dans un ordre parfaitement ALÉATOIRE. Si on met dans l'ordre "d'acquisition", on aura un résultat médiocre (pixels qui restent trop dans le même coin...).
(c):
u16 random = 0;
npos[1] = npostemp;
// Remplir pos[1] de façon aléatoire
for(i = 0; i < npos[1]; i++){
random = PA_Rand()%npostemp;
pos[1][i].x = postemp[random].x;
pos[1][i].y = postemp[random].y;
npostemp--; // 1 de moins restant
// On fout le dernier à la place de celui utilisé...
postemp[random].x = postemp[npostemp].x;
postemp[random].y = postemp[npostemp].y;
}
Voilà, comme je disais, on prend un pixel dans la liste, et on place à cet emplacement le dernier, histoire de garder une liste en continuité pour simplifier le choix. Et on pratique ainsi pour TOUS les pixels.

On a donc, à ce stade, 2 listes de pixels : pos[0] et pos[1], avec les pixels qui vont aller de pos[0] à pos[1]... Reste à régler le problème du nombre différent de pixels : on va comparer le nombre de pixel dans chaque, et s'il en manque dans l'un ou l'autre, on complètera avec des pixels hors écran.

(c):
u8 small = 0;
if(npos[1] < npos[0]) small = 1; // Plus d'anciens que de nouveaux, il va falloir en faire sortir dehors ^^
for(i = npos[small]; i < npos[!small]; i++){
pos[small][i].x = -3 + (PA_Rand()&1)*(45*6);
pos[small][i].y = 3+(PA_Rand()%34)*6;
}
Voilà, tout est prêt, on va pouvoir effectuer la transition en douceur.
Dernière étape : se bouger le cul !Il est maintenant temps de déplacer tout cela.

Dans Mental Games, j'ai mis une dizaine d'effets différents, mais dans un souci de simplicité je n'en ai mis qu'un seul dans cette démo. Libre à vous d'en coder d'autres.

On a donc, déjà, un tableau de pointeurs sur les fonctions des effets :
(c):
// Tableau avec tous les effets, à vous de coder les autres !
fp Effect[N_EFFECTS] = {Effect0};
(j'avoue que l'intérêt pour un seul effet est plus que limite

)
Ensuite, on prend un effet au hasard et on l'active :
(c):
u16 random = PA_Rand()%N_EFFECTS; // Un seul effet en vrai ;)
Effect[random](); // Effet au hasard :p
À présent, il ne reste qu'un bout de code à voir : la fonction Effect0, que voici rien que pour vous :
(c):
void Effect0(void){
s32 i, n;
u16 limit = npos[0];
if(npos[1] > limit) limit = npos[1];
u8 ok = 0;
for(i = 1; !ok; i++){
ok = 1;
for(n = 0; (n < limit) && (n < i*2); n++){
if(pos[0][n].x != pos[1][n].x){
if(pos[0][n].x < pos[1][n].x) pos[0][n].x += 6;
else pos[0][n].x -= 6;
ok = 0;
}
else if(pos[0][n].y != pos[1][n].y){
if(pos[0][n].y < pos[1][n].y) pos[0][n].y += 6;
else pos[0][n].y -= 6;
ok = 0;
}
PA_3DSetSpriteXY(n, pos[0][n].x, pos[0][n].y);
}
PA_3DProcess();
PA_WaitForVBL();
}
}
Elle est toute simple... On regarde combien est le maximum de pixel (limit), et on utilisera ça par la suite.
Puis, on va faire une boucle qui va continuer tant que tous les pixels ne seront pas arrivés à destination... La variable chargée de cela sera 'ok' : par défaut, elle est à 1 (tout est bon), mais si un seul pixel n'est pas en place, elle passe à 0 et on recommence...
Ensuite, la boucle avec 'n' va passer en revue les pixels, les faire bouger horizontalement vers leur destination, et verticalement si X est déjà bien. La petite subtilité, ici, c'est qu'on arrête n à la valeur i*2... Pourquoi ? Ça permet en fait de faire plus progressif. Si on n'ajoute pas cela, TOUS les pixels vont commencer à se déplacer en même temps, ça va faire un peu bouillie rapide et puis ils vont tous arriver globalement en même temps, ce qui rend moins bien. En limitant n à i*2, on aura donc au plus 2 pixels qui bougent à la première boucle, puis 4, 6, 8, etc. Plus le temps passe, et plus on aura de pixels autorisés à se déplacer. Ça évite que tout se passe d'un coup et en même temps, ça rend mieux ! (mais la valeur de 2 peut être changée selon vos convenances, bien entendu !)
Voilà, vous savez à présent tout.

Sur ce, je vais me coucher et je vous souhaite un bon week-end.
