Afficher des sprites et des BOBs sur Amiga OCS et AGA (2/2)

Cet article est le second – et donc dernier – d’une série de deux consacrée à l’affichage de bitmaps sur Amiga. Dans le premier article, nous avons exploré dans le détail les sprites du hardware. A cette occasion, il est apparu que les sprites sont comme les hobbits : pratiques et colorés, mais petits et peu nombreux. Et même si le hardware permet d’en démultiplier apparemment le nombre en les découpant ou en les répétant, recourir à ces astuces reste assez contraignant.
C’est pourquoi une autre solution a souvent été préférée pour afficher des bitmaps : les BOBs. Le BOB est un bitmap affiché à l’aide du Blitter, le coprocesseur dont la fonctionnalité la plus notoire est la copie des données d’une ou plusieurs zones de mémoire vers une autre.
Des vector balls (avec quelques droites pour mieux constater la 3D)
Comment afficher un BOB ? Et ne faut-il pas alors assurer soi-même toutes ces tâches qui étaient prises en charge automatiquement par le hardware s’agissant de sprites : transparence, recover, clipping, détection de collisions, etc ? Tout cela et plus encore dans ce qui suit.
Cliquez ici pour télécharger l’archive contenant le code et les données des programmes présentés ici.
Cette archive contient plusieurs sources :
  • bobRAW.s pour le simple affichage d’un bob en RAW ;
  • bobRAWB.s pour le simple affichage d’un bob en RAWB ;
  • unlimitedBobs.s pour l’effet de bobs illimités ;
  • vectorBalls.s pour l’effet de vector balls.
NB : Cet article se lit mieux en écoutant l’excellent module composé par Spirit / LSD pour le diskmag Graphevine #14, mais c’est affaire de goût personnel…

Décaler, masquer et combiner au Blitter

Etant pris en charge par le hardware, les sprites présentent de nombreux avantages pour le codeur. En effet, ce dernier n’a pas à gérer la transparence (le masquage) ; la préservation et la restauration du fond sur lequel ils sont affichés (le recover) ; le découpage pouvant aller jusqu’à l’élimination quand ils débordent ou sortent du playfied (le clipping) ; les priorités entre sprites et entre sprites et bitplanes ; la gestion des collisions entre sprites, et entre sprites et bitplanes. Toutefois, les sprites sont comme les hobbits : pratiques et colorés, mais petits et peu nombreux.
Les BOBs ne souffrent pas de telles limites. En effet, un BOB étant un bitmap qui sera affiché parce qu’il est dessiné dans le bitplanes, le codeur dispose de toute latitude pour adapter la taille et la profondeur du BOB. Toutefois, si le hardware simplifie le dessin du BOB, il n’offre aucune facilité pour gérer toutes les tâches évoquées à l’instant en présentant les sprites : masquage, recover, clipping, priorités, collisions.
L’affichage du BOB s’effectue au Blitter. Comme expliqué dans un précédent article, ce coprocesseur permet notamment de combiner plusieurs blocs (les sources A, B et C) par des opérations logiques et d’écrire le résultat dans un autre bloc (la destination D), en décalant de 0 à 15 bits A et B sur la droite, et en masquant le premier et le dernier mot de A.
Rappelons qu’un bloc est ni plus ni moins qu’une séquence de mots en mémoire, dont il est possible d’ignorer périodiquement un certain nombre de mots. Le Blitter propose de décrire un bloc à l’aide d’une adresse de départ, d’une largeur (en mots), d’un modulo, et d’une hauteur (en lignes de mots), comme s’il s’agissait d’un rectangle. Par exemple, un bloc de 28 mots, dont 2 mots sont ignorés tous les 5 mots, est représenté comme un bloc de 5 mots de large sur 4 lignes de hauteur, avec un modulo de 2 mots (la flèche parcourt les mots aux adresses consécutives) :
Un bloc de mémoire tel que vu par le Blitter
Le Blitter offre de nombreuses possibilités pour combiner A, B et C. Dans un premier temps, les bits sont combinés par AND, après avoir été éventuellement inversés. L’ensemble des combinaisons possibles, désignées comme les minterms, sont notées à l’aide des lettres A, B et C (sources non inversées) ou a, b et C (sources inversés). Dans un second temps, les minterms choisis par le codeur sont combinés entre eux par OR :
Phases de la combinaison bit à bit des sources par le Blitter
Tout cela permet de cerner l’intérêt du Blitter pour afficher l’équivalent d’un sprite. Pour cela il faut combiner trois sources :
  • le bitmap ;
  • le masque ;
  • le décor.
Le masque (B) est combiné par AND avec le décor (C) à l’endroit où le BOB doit être affiché. Cela permet d’effacer les pixels du décor où des pixels non transparents du bitmap doivent être affichés. Le bitmap (A) est alors combiné par OR avec le décor (C), à la même position, si bien que ses pixels non transparents sont affichés à place de ceux décor qui viennent d’être effacés.
Un exemple avec une charmante petite fée de 16 x 16 pixels, dessinée par votre serviteur à l’aide de l’excellent Pro Motion NG :
Les étapes de l’affichage d’un BOB
Avant de déterminer quels minterms le Blitter doit combiner par OR pour parvenir à ce résultat, il faut régler un petit problème. Comme mentionné, le Blitter copie non pas des pixels, mais des mots. Partant, comment afficher le bitmap et le masque à partir d’un pixel du décor dont l’abscisse n’est pas forcément multiple de 16 ?
Bien évidemment, cette nécessité a été anticipée par les concepteurs du hardware. Le Blitter permet de décaler A et B sur la droite, d’un nombre de bits compris entre 0 et 15. Le décalage de A est indépendant de celui de B. Lorsque les lignes d’un bloc sont ainsi décalées, les bits chassés sur la droite de la ligne Y sont réintroduits sur la gauche de la ligne Y+1. La ligne 0 est un cas particulier : comme il n’y a pas de ligne précédente, ce sont des 0 qui sont introduits sur sa gauche :
Décalage de 4 bits d'une source de N lignes de 1 mot au Blitter
Comme il est possible de la constater à l’aide de l’exemple d’un décalage de 4 pixels d’un bloc d’un mot de large représenté sur la figure, des bits de poids faible du dernier mot de la dernière ligne se trouvent alors chassés sur la droite et éliminés. En fait, si la largeur du bloc demeure celle du bitmap à afficher, ce bitmap subit une déformation inacceptable. Même exemple de la fée, quand elle subit un décalage de 4 pixels (pour la lisibilité, les pixels de couleur 0, transparente, introduits à gauche de la première ligne sont affichés en rouge) :
Déformation d'un bloc conservant sa largeur à l'occasion d'un décalage
Pour conserver le bitmap entier dans le bloc, il faut augmenter préalablement la largeur du bloc d’un mot sur la droite (pour la lisibilité, les pixels de couleur 0, transparente, introduits à droite des lignes sont affichés en vert). Après le décalage, il faut masquer les bits superflus à gauche et à droite, c’est-à-dire des bits du premier et du dernier mot de chaque ligne du bloc, avant de combiner le bloc avec le bloc correspondant dans le décor. Cela revient à dire que si le bitmap est décalé, son masque doit l’être pareillement :
L'élargissement du bloc impose un masquage

Le cas particulier de A et le cas général de B

A ce stade, il faut préciser que le Blitter peut masquer le premier et le dernier mot de chaque ligne de A. En exploitant cette possibilité, n’est-il pas possible de se dispenser de rajouter une colonne de mots sur la droite du bitmap, et même un masque, dans un cas particulier mais néanmoins fréquent : quand le bitmap est affiché sur un fond où ses pixels opaques ne viennent remplacer que des pixels de couleur 0 ?
De fait, mais pour que cela fonctionne, il ne suffit pas de préciser ces masques. Il faut aussi jouer sur le modulo de A. Modulo ? Kezako ? Pour comprendre comment de quoi il en retourne, il est nécessaire de rentrer dans le détail du déroulement d’une copie au Blitter.
Comme illustré plus tôt, Le Blitter combine les mots des sources A, B et C et copie le résultat dans un mot de la destination D. Il procède mot à mot, incrémentant les adresses contenues dans les registres BLTxPTH et BLTxPTL, par exemple BLTAPTH et BLTAPTL pour A. De plus, à la fin de chaque ligne, le Blitter rajoute à l’adresse de A, B, C et D la valeur contenue dans BLTxMOD, par exemple BLTAMOD. Ainsi, s’il devait s’agir de A, le bloc de la figure devrait être spécifié ainsi avant une copie qui l’implique :
	move.l #blockA,d0
	move.w d0,BLTAPTL(a5)
	swap d0
	move.w d0,BLTAPTH(a5)
	move.w #2*2,BLTAMOD(a5)	;Le modulo est spécifié en octets
;...
blockA:		BLK.W 7*4,0
Rappelons que c’est au moment de la copie que le Blitter est informé de la largeur et de la hauteur des blocs impliqués. En effet, c’est en écrivant une combinaison ces valeurs dans BLTSIZE qu’on les spécifie, en même temps qu’on déclenche la copie (dont il faut attendre le terme en testant deux fois un bit de DMACONR, ce que fait la macro WAIT_BLITTER mentionnée ici) :
BLOCK_WIDTH=7			;En mots !
BLOCK_HEIGHT=4			;En pixels
	move.w #(BLOCK_HEIGHT<<6)!BLOCK_WIDTH,BLTSIZE(a5)
	WAIT_BLITTER
L’astuce consiste à utiliser un modulo… négatif, de -2 pour être plus précis. Ainsi, au terme de la copie d’une ligne Y de A, le Blitter va commencer la ligne Y+1 non pas au premier mot de cette dernière, mais au dernier mot de la ligne Y.
La figure qui suit montre ce qui se passe dans les deux cas s’il s’agissait de copier un bloc de deux mots de large sur trois lignes : avec ou sans colonne de mots supplémentaire :
  • Dans le premier cas, au terme de la copie de la première ligne, le modulo valant 0, le Blitter pointe sur le premier mot de la deuxième ligne (son premier bit est encadré en rouge). Il copie cette ligne en injectant à gauche des bits du dernier mot qu’il a lu, donc de la fin de la première ligne de la colonne de mots supplémentaire (en gris foncé).
  • Dans le second cas, au terme de la copie de la première ligne, le modulo valant -2, le Blitter pointe au même endroit. Ayant copié deux mots à partir du début de la première ligne, il aurait dû pointer sur le premier mot de la troisième ligne, mais en ajoutant le modulo, il s’est trouvé renvoyé un mot en arrière. Il copie cette ligne en injectant à gauche des bits du dernier mot qu’il a lu, donc de la fin de la deuxième ligne de la colonne de mots supplémentaire (en vert foncé).
Copie dans la deuxième ligne de deux mots d'un bloc avec ou sans colonne de mots supplémentaire
Pour éliminer les bits superflus à gauche comme à droite de ceux de la ligne, il ne reste plus qu’à exploiter la possibilité qu’offre le Blitter de masquer le premier et dernier mot de A. Les masques que le Blitter combine à ces mots doivent être spécifiés dans les registres BLTAFWM et BLTALWM, respectivement. La combinaison s’effectuant par AND, les bits des masques correspondant aux bits superflus doivent être effacés, tandis que les autres bits doivent être positionnés. Ces masques n’ont pas à être décalés, car le Blitter les applique au premier et au dernier mot de chaque ligne AVANT de décaler cette dernière. Bref, quel que soit le décalage dans le contexte de l’affichage d’un BOB, leurs valeurs doivent être $FFFF et $0000, respectivement.
Dans la continuité de la figure précédente, la figure suivante décrit ce qui se déroule lors de la copie de la deuxième ligne. Attention, il faut suivre ! Cette copie implique deux mots : le mot constituant la deuxième ligne (en vert) suivi du mot constituant la troisième (en bleu). A la fin de cette copie, tous les bits du mot du second mot, ont été effacés suite au AND avec BLTALWM. Par ailleurs, ses quatre derniers bits ainsi effacés se trouvent prêts à être injectés sur la gauche du premier mot de la troisième ligne (en bleu) lors de sa copie à venir. C’était déjà ce qui s’était passé lors de la copie de la première ligne (en rouge), et c’est pourquoi les bits à 0 injectés lors de la copie de la deuxième ligne (en vert) figurent en vert sombre : ce sont les quatre derniers bits du mot constituant la deuxième ligne (en vert) qui avaient été effacés suite au AND avec BLTALWM.
Masquage des premier et dernier mots de A
Au final, le code pour afficher un BOB de BOB_DX x BOB_DY pixels, composé d’un seul bitplane (à l’adresse bob), sur un fond de DISPLAY_DX x DISPLAY_DY pixels composé d’un seul bitplane (à l’adresse backBuffer), est le suivant :
	lea bob,a0
	move.w #BOB_X,d0
	move.w d0,d1
	and.w #$F,d0
	ror.w #4,d0
	or.w #$0BFA,d0
	move.w d0,BLTCON0(a5)
	lsr.w #3,d1
	and.b #$FE,d1
	move.w #BOB_Y,d0
	mulu #DISPLAY_DX>>3,d0
	add.w d1,d0
	movea.l backBuffer,a1
	lea (a1,d0.w),a1
	move.w #$0000,BLTCON1(a5)
	move.w #$FFFF,BLTAFWM(a5)
	move.w #$0000,BLTALWM(a5)
	move.w #-2,BLTAMOD(a5)
	move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTCMOD(a5)
	move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTDMOD(a5)
	move.l a0,BLTAPTH(a5)
	move.l a1,BLTCPTH(a5)
	move.l a1,BLTDPTH(a5)
	move.w #(BOB_DY<<6)!((BOB_DX+16)>>4),BLTSIZE(a5)
	WAIT_BLITTER
Le rôle des autres registres utilisé dans ce code sera expliqué plus loin.
La solution qui vient d’être présentée est pratique car elle permet de ne pas avoir à introduire de colonne de mots supplémentaire, mais elle ne fonctionne que pour A. En effet, le Blitter ne sait masquer que le premier et le dernier mot d’une ligne de A, qui correspondrait au bitmap. Rien pour B, qui correspondrait au masque.
Il n’en reste pas moins important de savoir que cette possibilité existe, car elle permet d’afficher un bitmap sans avoir à rajouter une colonne de mots supplémentaire, et sans avoir à fournir son masque :
  • Ne pas avoir à rajouter une colonne de mots supplémentaire est intéressant quand le bitmap est une extraction d’une partie d’une image. Par exemple, quand il s’agit de copier une partie d’un bitplane quelque part : cette partie peut donc être prélevée directement.
  • Ne pas avoir à founir de masque est intéressant quand les pixels du bitmap ne viennent remplacer que des pixels de couleur 0. Par exemple, quand il s’agit d’afficher les lettres d’un scroll sur fond de couleur 0, lettres qui ne se chevauchent jamais – une lettre peut être affichée par un simple OR avec le décor, sans utiliser B pour le masque.
Et B, qui sert pour le masque ? Il n’existe pas de registres équivalents à BLTAFWM et BLTALWM pour masquer le premier et le dernier mot de chaque ligne de cette source. Dans ces conditions, l’astuce consistant à utiliser un modulo de -2 pour se dispenser d’avoir à rajouter une colonne supplémentaire de mots à 0 ne peut pas fonctionner.
Comme déjà expliqué, le masque doit être décalé sur la droite, ce qui implique que des bits doivent être injectés sur la gauche et d’autres rejetés sur la droite de chacune de ses lignes, lignes dont la longueur doit être agrandie d’un mot sur la droite.
Or comment positionner les bits superflus à gauche et à droite avant de combiner par AND le masque avec le fond, afin de préserver les bits du fond correspondant ? Impossible d’appliquer des masques BLTBFWM et BLTBLWM, car ils n’existent pas. Par conséquent, il faut que ces bits soient déjà présents dans le masque, donc qu’ils figurent dans une colonne de mots supplémentaire sur sa droite, dont les bits sont à 1.
Autre chose. Le masque est combiné par AND avec le fond, ce qui implique que ses bits doivent être positionnés là où le fond doit être préservé, et effacés autrement. L’inverse est possible, car le Blitter permet de combiner indifféremment B ou son inverse, b (pour NOT B), avec C, qui correspond au fond. Pour l’exemple, c’est d’ailleurs l’inverse qui sera utilisé.
Pour récapituler :
  • A correspond au bitmap, qui doit être décalé. Ses données ne comprennent pas de colonne de mot supplémentaires à 0, mais c’est parce que les masques de premier et de dernier mot BLTAFWM et BLTALWM sont appliqués ;
  • B correspond au masque, qui doit être décalé. Ses données comprennent une colonne de mots supplémentaires à 0, ce il n’existe pas de tels masques pour cette source.
  • C correspond au décor, qui ne doit pas être décalé.

Combiner les minterms, lancer le Blitter et l’attendre

Pour comprendre totalement le code d’affichage d’un BOB qui a été présenté (cas spécifique du BOB dont les pixels opaques sont affichés sur des pixels de couleur 0), et comprendre le code qui va suivre (cas général du BOB dont les pixels opaques sont affichés sur des pixels de couleurs quelconques), il reste à comprendre comment ils permettent de spécifier au Blitter la manière dont les sources A, B et C doivent être combinées pour produire la destination D.
Comme expliqué, le Blitter combine les sources en combinant par OR des minterms, qui sont eux même le produit de combinaisons par AND des sources, éventuellement inversées. Or que s’agit-il de faire, sinon la combinaison suivante :
D=A+bC
L’Amiga Hardware Reference Manual explique comment en déduire les minterms qu’il faut activer en positionnant les bits correspondants dans BLTCON0. Il suffit de rajouter des facteurs neutres aux AND (par exemple, c+C qui vaut nécessairement 1) et de développer puis réduire :
D=A(b+B)(c+C)+bC(a+A)
D=Abc+AbC+ABc+ABC+abC+AbC
D=ABC+ABc+AbC+Abc+abC
Les huit bits de BLTCON0 réservés au minterms sont ceux de son octet de poids faible :
MintermABCABcAbCAbcaBCaBcabCabc
Bit76543210
En l’espèce, cela signifie que l’octet doit prendre la valeur $F2.
Le reste de la configuration du Blitter consiste simplement à spécifier qu’il doit activer A, B, C et D, et à spécifier la valeur du décalage sur la droite de A et celle de celui de B. D’autres bits de bits de BLTCON0 et de BLTCON1 servent à ces usages – se reporter à l’Amiga Hardware Reference Manual, qu’il ne s’agit pas de réécrire ici, pour les identifier.
Maintenant que tout a été présenté, il est possible d’écrire le code d’affichage d’un BOB masqué, dont bob est l’adresse du bitmap et bobMask celle de son masque :
	moveq #0,d1
	move.w #BOB_X,d0
	subi.w #BOB_DX>>1,d0
	move.w d0,d1
	and.w #$F,d0
	ror.w #4,d0
	move.w d0,BLTCON1(a5)
	or.w #$0FF2,d0
	move.w d0,BLTCON0(a5)
	lsr.w #3,d1
	and.b #$FE,d1
	move.w #BOB_Y,d0
	subi.w #BOB_DY>>1,d0
	mulu #DISPLAY_DEPTH*(DISPLAY_DX>>3),d0
	add.l d1,d0
	move.l backBuffer,d1
	add.l d1,d0
	move.w #$FFFF,BLTAFWM(a5)
	move.w #$0000,BLTALWM(a5)
	move.w #-2,BLTAMOD(a5)
	move.w #0,BLTBMOD(a5)
	move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTCMOD(a5)
	move.w #(DISPLAY_DX-(BOB_DX+16))>>3,BLTDMOD(a5)
	move.l #bob,BLTAPTH(a5)
	move.l #bobMask,BLTBPTH(a5)
	move.l d0,BLTCPTH(a5)
	move.l d0,BLTDPTH(a5)
	move.w #(DISPLAY_DEPTH*(BOB_DY<<6))!((BOB_DX+16)>>4),BLTSIZE(a5)
Le lecteur attentif doit encore se gratter la tête. Quelles sont ces valeurs stockées dans les registres BLTCMOD et BLTDMOD ?
Une constante vient de faire son apparition : DISPLAY_DEPTH. Le code permet d’afficher un BOB de DISPLAY_DEPTH bitplanes de profondeur sur un fond de même profondeur. Or cela soulève un enjeu intéressant.
En effet, si les données du bitmap, du masque et du fond sont organisées comme de coutume, celles du bitplane 1 sont suivies de celles du bitplane 2, et ainsi de suite. Cela conduit à afficher les bitplanes du bitmap et du masque un par un dans une boucle, car le modulo utilisé pour passer d’une ligne à l’autre dans D, valant donc (DISPLAY_DX-(BOB_DX+16))>>3 ne peut servir à passer de la dernière ligne utilisée d’un bitplane à la première ligne utilisée dans le suivant :
;En supposant que a2 pointe sur le mot du premier bitplane du fond où le BOB doit être affiché, et ne rappelant pas les valeurs des autres registres BLTAFWM, BLTALWM, BLTCON0, BLTCON1, BLTAMOD, BLTBMOD, BLTCMOD et BLTDMOD...

	lea bob,a0
	lea bobMask,a1
	move.w #DISPLAY_DEPTH-1,d0
_drawBobBitplanes:
	move.l a0,BLTAPTH(a5)
	move.l a1,BLTBPTH(a5)
	move.l a2,BLTCPTH(a5)
	move.l a2,BLTDPTH(a5)
	move.w #(BOB_DY<<6)!((BOB_DX+16)>>4),BLTSIZE(a5)
	lea BOB_Y*(BOB_DX>>3)(a0),a0
	lea BOB_Y*((BOB_DX+16)>>3)(a1),a1
	lea DISPLAY_Y*(DISPLAY_DX>>3)(a2),a2
	WAIT_BLITTER
	dbf d0,_drawBobBitplanes
Cette manière de procéder contraint d’attendre le Blitter après chaque copie dans un bitplane avant de passer au bitplane suivant. Cela ne permet pas de tirer parti du fonctionnement en parallèle du Blitter et du CPU, sauf à l’occasion de la dernière copie – il faudrait alors ne pas appeler la macro WAIT_BLITTER, ce qui contraindrait donc à distinguer les premières boucles de la dernière, pour en rajouter.
N’est-il pas possible de copier en un coup les bitplanes du bitmap et du masque dans ceux des bitplanes ? Tout à fait :
  • Après avoir affiché une ligne d’un bitplane, le hardware ajoute la valeur de BPL1MOD (bitplane impair) ou BPL2MOD (bitplane pair) à l’adresse courante dans le bitplane pour passer à la ligne suivante. La valeur d’un tel registre peut être fixée à (DISPLAY_DEPTH-1)*(DISPLAY_DX>>3) pour entrelacer les lignes des bitplanes (pairs ou impairs, selon le registre) dans les données du fond.
  • Pour sa part, le Blitter permet de fixer la valeur d’un modulo via BLTAMOD, BLTBMOD et BLTCMOD pour A, B et C, et donc ici encore d’entrelacer les lignes des bitplanes (pairs et impairs, sans distinction) dans les données du bitmap, du masque et du fond, donc A, B, C et D.
L’organisation des données est donc très différente selon qu’elle est classique, dite RAW (« brut de fonderie »), ou optimisée, dite RAWB (RAW Blitter). La figure suivante permet de les comparer dans le cas d’un bitmap de 16 pixels de large et de 2 lignes de hauteur :
Organisation des données du bitmap d'un BOB en RAW et RAWB
Pour conclure (car c’est enfin terminé !), les programmes bobRAW.s et bobRAWB.s explorent chacune des solutions pour afficher un BOB de 64 x 64 pixels sur 5 bitplanes :
Affichage d'un BOB de 64 x 64 pixels sur 5 bitplanes
Dans ces programmes, la couleur du fond passe au rouge au début des opérations, et au vert à leur terme. Aucune différence n’est observable, mais c’est parce que le Blitter est attendu systématiquement dans les deux programmes à la fin de toute copie.
Le programme bobRAWB.s pourrait être modifié pour réaliser des opérations au CPU tandis que le BOB est affiché dans les 5 bitplanes. Le programme bobRAW.s, pourrait être modifié pareillement, mais pour permettre de réaliser ces opérations au CPU tandis que le BOB est affiché dans le dernier bitplane seulement. La différence pourrait alors être constatée. Pour cette raison, si le recours au RAW peut s’imposer dans des cas particuliers, c’est généralement c’est le RAWB qu’il convient d’utiliser quand il s’agit d’afficher des BOBs.

Unlimited BOBs : des BOBs à foison, vraiment ?

Les BOBs sont utilisés pour produire des effets très variés, dont les plus notoires sont l’unlimited BOBs, et les vector balls.
« The Amiga possibilities have been burst beyond your imagination. And the result is what you see !!! », proclame fièrement l’auteur d’un scroll de la fameuse Megademo de Dragons. Excessivement vintage, cette démo contient un effet très classique à l’époque, dit unlimited BOBs. Un BOB est visiblement rajouté à chaque trame, et le compteur semble indiquer que cela ne s’arrêtera jamais. Les performances ne sont aucunement pénalisées par cette multiplication des BOBs :
2 000 BOBs dans la trame, et ce n'est pas fini ! (Megademo par Dragons)
Dans la vie, RIEN n’est gratuit, et surtout pas les BOBs. Il y a donc un truc. Avant de rentrer dans les détails, dotons-nous d’un BOB de 16 x 16 pixels en 4 couleurs adéquat, toujours dessiné avec l’excellent Pro Motion NG :
BOB, votre pote rondouillard de 16 x 16 pixels sur deux bitplanes
Ce qui est affiché est une animation où le BOB suit une trajectoire, et dont le principe est le suivant. Le nombre d’images est fixé, par exemple à 3. A chaque trame, l’image est remplacée par la suivante, en rebouclant sur la première. Une série de 3 images de 0 à 2 constitue une période :
  • lors de la période 0, un BOB est affiché aux positions P0, P1 et P3 dans les images 0, 1 et 2, respectivement ;
  • lors de la période 1, un BOB est rajouté aux positions P4, P5 et P6 dans les images 0, 1 et 2 respectivement ;
  • etc.
Ainsi, lorsque l’image 0 est affichée pour la seconde fois, il semble qu’un BOB a été rajouté à la position initiale du BOB précédent, et qu’il suit ce BOB au fil des images suivantes :
Les deux premières périodes d'une animation en trois images
Puisqu’il ne s’agit jamais que d’afficher un nouveau BOB à chaque trame, l’effet ne consomme presque pas de temps de calcul. C’est donc extrêmement simple, mais bien pensé.
Quant à la trajectoire, elle peut être quelconque. Dans le programme unlimitedBobs.s, la position du BOB est calculée sur un cercle dont le rayon oscille entre un minimum et un maximum, ce qui permet de rompre la monotonie :
Des bobs tentaculaires

Les vector balls, des BOBs en 3D

Plus sophistiqué : les vector balls. Une démo du groupe Impact en fournit une belle illustration. Comme il est possible de le constater, il s’agit d’afficher des BOBs figurant des sphères à certaines positions d’un modèle en 3D :
De très jolies vector balls (Vectorballs par Impact)
Le programme vectorBalls.s montre, sans chercher en rien à l’optimiser (pour rester simple, il utilise même un tri à bulles sans condition d’arrêt anticipé !), comment produire un effet de ce type. Le fait qu’un seul BOB soit toujours utilisé ne permet pas de bien visualiser l’effet de profondeur sur la capture d’écran, mais tout y est assurément :
Nos vectorballs. Si, si ! C'est en 3D !
Même s’il est plus élaboré que celui des unlimited BOBs, le programme des vector balls reste simple. A chaque trame, il s’agit de calculer une nouvelle image en suivant ces étapes :
  • effacer les bitplanes ;
  • appliquer quelques rotations aux coordonnées 3D des points du modèle ;
  • projeter ces points pour déterminer leurs coordonnées 2D ;
  • trier ces coordonnées 2D par ordre de profondeur décroissante de la profondeur des coordonnées 3D dont elles découlent ;
  • parcourant la liste des coordonnées 2D dans cet ordre, afficher un BOB dont les coordonnées 2D donnent le centre.
Noter que pour que l’effet soit réussi, il faut éviter qu’une sphère passe devant une autre abruptement, et pour cela bien espacer les points sur le modèle 3D.
Cet effet peut être sophistiqué en utilisant des BOBs représentant des sphères de couleurs différentes et/ou dont le diamètre est variable selon la profondeur, et en animant des parties du modèle indépendamment les unes des autres.

Pour en finir avec les BOBs…

Ces explications sur la manière d’afficher un BOB et d’utiliser le code pour produire deux effets, les unlimited BOBs et les vector balls, étant données, tout semble avoir été dit au fil des deux articles de cette petite série consacrée à l’affichage de bitmaps animés sur Amiga OCS et AGA.
Il ne faut pas perdre de vue que ce ne sont là que des techniques de base, dont l’emploi nécessite un travail de sophistication et d’optimisation pour produire des effets réussis. Or, entre autres, il serait possible d’imaginer une troisième solution : afficher des bitmaps animés au CPU, par exemple pendant que le Blitter affiche un BOB et que le hardware affiche les sprites. On trouve certainement de nombreux exemples d’un tel mix de techniques dans les jeux et les démos sur Amiga. Un ordinateur dont le hardware reste décidemment des plus fascinants !