La programmation d'effets de demos old-school (Assembleur + C) - Algo - Programmation
Marsh Posté le 18-09-2003 à 21:51:22
Prérequis
Avant toute chose, il convient d'expliquer certaines notions indispensables.
Le balayage vertical
L'image d'un moniteur est formée grace au balayage d'un faisceau d'électrons. Ce faisceau part du haut gauche de l'écran, et trace une ligne jusqu'à l'extreme droite.
Lorsque la ligne est finie, le faisceau se positionne au début de la ligne suivante, puis rebalaie de gauche à droite pour tracer une nouvelle ligne, etc...
Une fois l'image dessinée, le faisceau se trouve donc en bas à droite de l'écran. A ce moment là, il remonte en haut à gauche, et redessine une nouvelle image présente en mémoire vidéo. Ceci à une fréquence variable dépendante du moniteur. Une fréquence de 60 Hz signifie que le tube sera balayé 60 fois par seconde, on aura donc 60 images en une seconde.
Le double buffering
Les démos sont avant tout des animations, donc un défilement d'images, comme dans un film.
Une animation est composée de 3 phases :
- Dessin d'une image selon un algo précis,
- Effacement de l'écran,
- Dessin de l'image suivante.
Si on réalisait ces opérations pendant le balayage du faisceau, il en résulterait un scintillement dû à l'effacement de l'écran, ainsi qu'a une déformation de l'image, car un morceau de l'image précedente apparaitra alors que le faisceau est en train de dessiner la suivante.
Il a donc fallu trouver une technique pour éviter ces effets de bord. Cette technique est le double buffering.
Le principe est très simple, calqué sur un dessin animé : un dessin animé est une suite de planches statiques qu'on fait défiler à haute vitesse afin d'obtenir l'illusion du mouvement.
Le double buffering consiste à dessiner l'image sur une portion de la mémoire hors mémoire écran (le buffer), puis à remplacer l'image actuellement en cours d'affichage par ce buffer. Pendant que le faisceau d'électrons dessine une image, on calcule l'image suivante en mémoire dans le buffer, puis on copie ensuite ce buffer dans la mémoire vidéo.
On élimine ainsi l'effet de scintillement du à l'effacement de l'écran, puisque toutes les opérations de tracé seront effectuées sur le buffer hors écran, qui sera affiché par la suite.
Le transfert du buffer vers la mémoire vidéo doit se faire à un moment précis ! En effet, si on le transfère pendant le balayage vertical de l'écran, ceci provoquera des superpositions d'images fort désagréables. Il faut donc afficher une nouvelle image une fois que l'image précédente est entièrement affichée.
Ceci correspond en fait au moment ou le faisceau d'électrons se situe en bas à droite de l'écran et remonte vers le haut gauche. C'est le moment idéal pour copier le buffer dans la mémoire écran, permattant ainsi d'obtenir une animation fluide et non déformée.
Ce moment s'appelle le retour de balayage vertical, ou Vertical Blank (VBL)
Ces notions sont la clé de voute des démos. Pour qu'une animation soit fluide, elle devra être calculée pendant le balayage de l'écran. Si le temps de calcul dépasse le temps nécessaire au faisceau pour dessiner l'écran, il en résultera une animation saccadée.
Le jeu des démomakers consistait donc à optimiser au maximum leurs démos, à gagner le moindre cycle machine, pour réaliser les calculs dans l'intervalle d'un balayage d'écran. D'ou l'utilisation de l'assembleur, et le recours à des optimisations de fou
Prochain article : le défilement horizontal d'étoiles, ou starfield horizontal
Marsh Posté le 18-09-2003 à 21:51:48
bravo et merci, je suis pas un specialiste alors je juge pas le fond mais chapeau pour la forme
je vais jouer un peu avec tout ça maintenant
edit: avec mon preumsage, je suis entre 2 des tes posts, si tu veux supprime le ou deplace le si il gene
Marsh Posté le 18-09-2003 à 22:21:46
pas lu, mais je flagge...dès que j'ai le temps je le lis!
Marsh Posté le 18-09-2003 à 22:24:10
uriel a écrit : |
non c'est bon, c'est moi qui avais posté 2 fois la même chose (putain de RTC )
Marsh Posté le 19-09-2003 à 00:43:26
très bien écris et très intéressant, vivement la suite .
Marsh Posté le 19-09-2003 à 06:51:45
Lu, et c'est un bon départ!
Si tu arrives à tout rendre aussi intéressant chapeau!
Marsh Posté le 19-09-2003 à 08:47:14
skeye a écrit : Lu, et c'est un bon départ! |
surtout non sticky
Marsh Posté le 19-09-2003 à 09:16:34
et
Marsh Posté le 19-09-2003 à 09:45:14
Ça a l'air bien intéressant tout ça
T'aurais pu faire un GIF animé pour le screenshot du starfield
Marsh Posté le 19-09-2003 à 09:47:23
Le starfield !
Le starfield !
Le starfield !
Le starfield !
Marsh Posté le 19-09-2003 à 09:49:30
rien à battre du starfield
moi je veux du plasma
edit: nan je déconne, je veux tout
Marsh Posté le 19-09-2003 à 09:57:37
antp a écrit : |
j'ai meme pas pu faire de copie d'écran, vu que je court circuite le systeme ! donc adieu la touche imprim'écran
Marsh Posté le 19-09-2003 à 10:15:30
C'était beau..
Ca m'a ému..
Moi je vote pour le sinescroll.. Avec des changements de palette par dessus pquoi pas.. Qd je repense à l'intro du crack de princeofpersia.. J'y ai passé plus de temps que sur le jeu..
Marsh Posté le 19-09-2003 à 10:38:01
H4dd3R a écrit : C'était beau.. |
Sur ce genre d'effets, la palette n'est pas "par dessus", mais il s'agit en fait d'une unique couleur. L'astuce est que tu changes la signification de cette couleur au début de chaque nouvelle ligne horizontale. C'est ce genre de combine qui était permise par le copper de l'Amiga. Tu pouvais ainsi afficher largement plus de 32 couleurs à l'écran, en n'utilisant uniquement qu'un seul registre de couleur (la couleur 0 en l'occurence, affectée au registre dff180h sur l'Amiga).
Marsh Posté le 19-09-2003 à 10:43:06
Ok je vois.. Quel abominable bidouillage!!
Tu codais pour la scène démo amiga à l'époque Harko?
Une petite note pour le premier post:
le PC a fait son succès en remplaçant les coprocesseurs amiga par de la puissance brute.. Certes.. Mais maintenant on reprend la direction inverse (GPU, EAX sur la carte son). A voir pour combien de temps.
Marsh Posté le 19-09-2003 à 10:44:08
Harkonnen a écrit : |
et tu va nous dire que t'es infoutu de nous faire un dump du backbuffer en tga non compressé pe ?
Marsh Posté le 19-09-2003 à 11:04:28
Harkonnen a écrit : |
Et VirtualPC ?
Marsh Posté le 19-09-2003 à 11:25:48
antp a écrit : |
je l'ai pas
Marsh Posté le 23-09-2003 à 23:21:02
LE STARFIELD HORIZONTAL
Télécharger l'éxécutable
Télécharger le source
Télécharger le dos4gw.exe (à mettre dans le meme répertoire que l'éxécutable).
Diverses notions sont à aborder à ce sujet
Le mode 13h
Le mode vidéo 13h est le mode graphique de prédilection pour le codage de démos (avec le mode X, qui fera l'objet d'un prochain article).
Ce mode possède une résolution de 320x200 en 256 couleurs. Il représente en fait un tableau linéaire d'octets. Chaque octet représente une entrée dans la palette de 256 couleurs.
Attention ! Il ne s'agit pas de la couleur, mais de l'index de la couleur dans la palette. Par exemple, la couleur 0 peut contenir la valeur 0xFFFFFF (blanc), la couleur 1 peut contenir la valeur 0xFF0000 (rouge), etc...
La structure du mode 13h est très simple : l'octet 0 représente le point de coordonnées (0,0), l'octet 1 représente le point de coordonnées (1,0), l'octet 320 représente le point (0,1), etc...
De ceci, on peut donc déduire l'adresse de chaque point de coordonnée (x,y) dans l'écran :
Adresse du point = Adresse de base de l'écran + (y * 320) + x
L'adresse de base de l'écran est A000:0000 (héxadécimal). Le point de coordonnées (0,0) se situe donc à l'adresse A000, le point (1,0) se situe à l'adresse A000:0001, etc...
Ecrire un point sur l'écran est donc d'une simplicité déconcertante : on calcul son décalage via la formule donnée plus haut, puis on ajoute ce décalage à l'adresse A000. On y écrit enfin l'octet correspondant à la couleur souhaitée.
Exemple : l'adresse du point (150,100) est donc :
(100 * 320) + 150 = 32150 (7D96 en héxadécimal). On y accèdera donc à l'adresse suivante : A000:7D96
Pour initialiser le mode 13h, on emploie l'interruption 10h du DOS qui prend en paramètre dans ax le mode écran :
Code :
|
Les registres de la carte VGA
La carte vidéo possède de nombreux registres permettant d'influencer ses caractéristiques les plus profondes.
La palette est controlée par des registres, ainsi que le timing horizontal et vertical, le décalage de l'écran, etc...
Les différents registres seront abordés au fur et à mesure des articles. Pour l'article d'aujourd'hui, seuls les registres du DAC régentant la palette et le registre Input Status permettant de détecter le début d'un balayage vertical seront étudiés.
Les registres de palette
Pour initialiser une palette, on applique la procédure suivante (toutes les adresses de registres sont en héxadécimal) :
- On écrit dans le registre 3c8 l'index de la couleur à modifier (parmi les 256 possibles)
- On envoie séquentiellement dans le registre 3c9 les composantes de rouge, vert et bleu de la couleur.
L'écriture dans un registre se fait au moyen de la commande assembleur out, tandis que la lecture est réalisée au moyen de in
Ainsi, pour initialiser un fond d'écran rouge, on saisira ceci :
Code :
|
Le registre Input Status
Le registre Input Status est un registre de la carte VGA qui permet, entre autres, de surveiller le début d'un balayage vertical. Il se trouve à l'adresse 3da.
On utilisera ici son bit n° 3. Ce bit est à 0 si le faisceau est en train de dessiner l'image, et à 1 si le faisceau est en retour de balayage.
Pour tester le début d'un balayage, on procède selon 2 étapes :
- on teste une première fois le bit 3 afin de détecter si un retour de balayage est entamé
- on le reteste à nouveau si le rayon est en retour de balayage.
Une fois ces tests passés, le rayon est en bas de l'écran, on peut entamer le calcul de l'image.
Code :
|
Si vous ne comprenez pas le code assembleur présenté ici, n'hésitez pas à poser des questions.
Le Starfield
Le starfield horizontal représente un défilement d'étoiles de la gauche vers la droite.
Ces étoiles sont des points, pouvant défiler sur plusieurs plans. Chaque plan défile à une vitesse différente : plus le plan est éloigné, plus les étoiles lui appartenant défilent lentement. Ajouté à une coloration adaptée des étoiles, on obtient ainsi un effet de profondeur. Les étoiles situées sur le plan le plus lent (donc censé être le plus éloigné) sont de couleur plus sombre que les étoiles situées sur le plan le plus rapide (donc le plus proche).
Cet effet est un des plus simples qui soit ! Le principe est le suivant :
La palette utilisée ici sera celle proposée par défaut par le BIOS. La couleur 0 de cette palette (couleur de fond) est noire, et les couleurs 22 à 30 représentent un dégradé de gris, parfait dans ce cas précis.
Pour chaque étoile, j'initialise donc aléatoirement :
- son abscisse x (entre 0 et 319),
- son ordonnée y (entre 0 et 199),
- sa vitesse (entre 1 et 4)
- son adresse dans l'écran. Ceci afin de repérer son ancienne position pour l'effacer.
Plus le plan sera éloigné, plus sa vitesse sera lente et donc plus les étoiles seront sombres. Il y a 4 vitesses possibles, chaque vitesse représentant un plan.
Au lieu de calculer l'adresse du point avec la formule donnée plus haut, j'utilise une petite optimisation consistant à stocker dans une table l'adresse du début des 200 lignes. Ainsi je n'ai qu'une addition à faire pour accéder au point, au lieu d'une multiplication et d'une addition (gain insignifiant de nos jours, mais cette technique était utilisée à la glorieuse époque des 486-DX2 )
Un mot sur le source
Le source est largement commenté, et ne présente pas de difficulté majeure. N'hésitez pas à poser des questions.
Pour le compiler, utiliser le compilateur Open Watcom et taper la ligne suivante :
wcl386 /l=dos4g starfield.c
Ce source est en 32 bits, et utilise donc un DOS Extender nommé DOS4GW. Ceci permet de créer du code 32 bits, dans un environnement DOS 16 bits (et oui, c'est de l'old-school )
Ce fichier doit être placé dans le même répertoire que l'éxécutable ou dans le PATH.
Pour ceux qui ont installé Open Watcom, inutile de télécharger ce fichier, il est fourni avec et placé dans le PATH automatiquement ou en éxécutant le fichier setvars.bat
N'hésitez pas à modifier ce source (le nombre d'étoiles, le sens de défilement, etc...)
Prochain article : l'effet de feu
Marsh Posté le 23-09-2003 à 23:49:57
Je lirais l'article demain, ça va être intéressant, old school powaa \o/
Marsh Posté le 24-09-2003 à 06:58:53
génial!
Pas le temps de mater le code source ce matin, mais les explications sont largement assez claires pour comprendre ce qu'il y a dedans!
Marsh Posté le 24-09-2003 à 08:51:33
pas tout regarder mais a premiere vue ca l'air interessant
jverais bien un ptit blur la-dessus
Marsh Posté le 24-09-2003 à 09:07:30
Bravo, mais...
Tu as parlé des adresses 03c8h et 03c9h pour modifier les couleurs.
Donc
Code :
|
Veux dire : modifier la couleur 1 de la palette (255,0,0).
( Qui est du rouge qu'on utilise pas ? ? ? )
Mais çà:
Code :
|
C'est quoi ?
Puis y'a cette addition ( multiplication par 2 ) que je ne comprend pas ?
ypos = ypos+ypos; // nécessaire, car la table d'offsets contient des integer
Enfin :
if (tabstars[i].x > 320)
Ce serait pas plutôt > 319 ?
Marsh Posté le 24-09-2003 à 10:01:31
Mara's dad a écrit :
|
Oui, effectivement, c'est un oubli de ma part
Au début je traçais des points rouges afin de cerner un bug. J'ai oublié de virer cette fonction qui effectivement ne sert à rien
Mara's dad a écrit :
|
Ca aussi, c'est une conséquence du bug que j'avais (en fait, aucun point ne s'affichait).
Le registre 3c6 est un registre 8 bits qui est en fait un masque qui indique les couleurs que tu souhaites modifier dans la palette. En y mettant la valeur ff (255), j'indique que je veux modifier la totalité des couleurs de la palette initiale.
Mara's dad a écrit : |
Le mode 13h contient 200 lignes.
La ligne 0 est située à l'adresse 0 (A000:0000)
La ligne 1 est située à l'adresse 320 (A000:0140)
La ligne 199 est située à l'adresse 63860 (A000:F8C0)
Les adresses étant donc supérieures à la capacité d'un octet (jusqu'à 255), elles sont codées sur 2 octets (16 bits) dans la table (jusqu'à 65535)
Ainsi, la table d'offsets contiendra donc :
Octet 0 : 0 (ligne 0)
Octet 2 : 320 (ligne 1)
Octet 4 : 640 (ligne 2)
Octet 398 : 63860 (ligne 199)
On voit donc que l'adresse d'une nouvelle ligne commence tous les octets pairs. Donc, si je veux récupérer l'adresse de la ligne 199, je dois lire la position 398, donc multiplier le numéro de la ligne par 2.
Mara's dad a écrit : |
Absolument, erreur de ma part
Marsh Posté le 24-09-2003 à 10:11:12
Ah ouais, moi j'avait des chouette tutos pour faire tout ça, j'avait fait un starfield assez chouette, mais sans utiliser d'ASM... J'utilisait TC-DOS, qui peut pas compiler l'assembleur inline
Sinon, je pose un joli drapeau, parce que ça pète tout ça !
Marsh Posté le 24-09-2003 à 10:16:07
Ok, je viens d'examiner le starfield, cai comme les tutos que j'avais choppé ...
Mais comme c'est les meilleurs tutos pour apprendre ça . Je vais m'y remettre à tout ça, parce que ça pète grave .
Faudrait essayer de faire des étoiles plus jolies, plus grandes qu'un pixel, des étoiles de couleurs... je m'y met de suite !
Marsh Posté le 24-09-2003 à 10:20:20
Le Castor a écrit : Ok, je viens d'examiner le starfield, cai comme les tutos que j'avais choppé ... |
ben y'a pas 36 façons de faire ce genre d'effet
Marsh Posté le 24-09-2003 à 12:24:12
Au risque de dire une grosse connerie, les composantes de couleur etaient pas codé sur 6 bits (au lieu de 8) ?
Marsh Posté le 24-09-2003 à 12:33:53
chrisbk a écrit : Au risque de dire une grosse connerie, les composantes de couleur etaient pas codé sur 6 bits (au lieu de 8) ? |
mmm, ça me dit qqchose effectivement
de toute façon, si c'est le cas, ça n'a aucune influence sur le source actuel vu que j'utilise la palette du BIOS (et que de toute façon les bits 6 et 7 doivent être inutilisés, donc on s'en fout da leur valeur)
je regarderais dans mes cartons ce soir, je dois avoir des vieux sources pour confirmer/infirmer
Marsh Posté le 24-09-2003 à 12:37:12
ben vu que tu mets 0xFF non ca change rien, mais si tu veux faire un degradé (par exemple)tu arriveras trop rapidement a saturation
Marsh Posté le 24-09-2003 à 12:52:02
Horko, tu peux nous dire comment installer Open Watcom (1.0 ou 1.1) sur un XP pro.
J'ai laissé l'install se faire toute seule, mais à la compil, j'ai un problème :
Warning! W1008: cannot open clib3r.lib : No such file or directory
Warning! W1008: cannot open emu387.lib : No such file or directory
Bon, après, bien sûr çà déconne à fond...
Pourtant, les libs en question sont bien installées dans c:\watcom\lib386\nt ? ? ?
Marsh Posté le 24-09-2003 à 13:01:05
Mara's dad a écrit : Horko, tu peux nous dire comment installer Open Watcom (1.0 ou 1.1) sur un XP pro. |
Y'a un bat a lancer (setvars je crois, dans le rep de openwatcom) qui regle le pb
Marsh Posté le 18-09-2003 à 21:49:39
Cet article est le premier d'une série consacrée à la programmation d'effets graphiques 2D-3D utilisés dans les démos des années fin 80-fin 90.
Ces effets étaient réalisés pour la plupart, sur l'ordinateur Amiga de Commodore, qui était doté de composants dédiés à différentes taches graphiques (le blitter pour copier rapidement des blocs de mémoire, le copper pour agir sur différentes valeurs telles que la couleur, le décalage, etc... au niveau de la ligne de balayage horizontal).
Cet ordinateur était le paradis des programmeurs de démos, car il était possible de réaliser des effets hallucinants en n'utilisant quasiment pas le processeur central. Il en résultait que ces démos, à priori complexes, tournaient parfaitement sur des machines à 7 Mhz dotées de 1 Mo, voire 512 Ko, de RAM. A l'époque, tout le monde pensait que seul l'Amiga et ses coprocesseurs était capables de réaliser de telles prouesses, et que le PC n'avait qu'à aller se rhabiller du fait de l'absence de coprocesseurs graphiques (on se souvient du slogan arboré par de nombreux groupes de démomakers de l'époque : "Intel Outside" )
Malheureusement, c'était sans compter sur le talent des programmeurs PC et surtout sur la puissance des PC par rapport à l'Amiga. De nombreux essais furent tentés pour égaliser les démos Amiga, avec plus ou moins de bonheur. L'Amiga sortait généralement vainqueur.
Jusqu'à l'apparition d'une démo PC, qui marqua les esprits : Second Reality, du groupe Future Crew. Les amigaïstes prirent une claque monumentale ! La plupart des effets jusque là réservés à l'Amiga se déroulaient sous leurs yeux, sur un PC !!!
"Précalcul", "Bluff", "Tricherie", personne ne voulut y croire. Il fallait bien se rendre à l'évidence : le PC était devenu si puissant que son processeur central était à même d'émuler les coprocesseurs de l'Amiga.
L'autre facteur de déclin de l'Amiga fut l'apparition de la 3D mappée temps réél. Cette technique nécessite un déplacement constant de pixels, sur un mode graphique reposant sur une zone mémoire dont chaque octet représente une couleur de pixel, parmi 256 possibles. La faible résolution de ce mode (320 * 200) est compensée par les 256 couleurs (l'oeil humain est beaucoup plus sensible au nombre de couleurs d'une image qu'à sa résolution).
Ce mode, appelé Chunky sur PC, est inexistant sur Amiga. Sur ce dernier, l'image était constitué de plans de bits superposés. Un point était égal à un bit, et la superposition des plans (donc des bits) donnait la couleur dans la palette. Pour une palette de 16 couleurs (2 ^ 4), il fallait donc 4 plans.
La conséquence de ce mode particulier, appelé bitplane, est que pour écrire une couleur sur l'écran, il fallait donc écrire sur les 4 plans, d'ou une impossibilité de transférer rapidement des pixels d'un endroit de la mémoire écran à un autre (il fallait faire le transfert 4 fois pour une palette de 16 couleurs).
La 3D mappée nécessite une actualisation rapide d'un grand nombre de pixels. Alors que le PC pouvait déplacer un pixel en une passe, l'Amiga avait besoin de 4 passes, et donc subissait une perte de performances flagrante.
De nombreuses solutions furent tentées pour résoudre ceci. La plus populaire fut l'utilisation du copper, qui permettait de simuler un écran chunky en écrivant directement la couleur dans la copperlist.
Malheureusement, le copper était lent, et ne pouvait changer qu'un pixel sur 4 en 320 * 256 sur un balayage horizontal. Ce qui conduisait à une pixellisation poussée, car on se retrouvait avec une résolution de 80 * 256.
Puis, avec l'augmentation de puissance des Amiga, les programmeurs développèrent des algorithmes de conversion Chunky-Bitplane, qui produisaient un résultat non pixellisé, mais très lent.
Entre temps, le PC avait augmenté son nombre de couleurs, alors que l'Amiga restait cantonné à ses 256 couleurs (malgré l'apparition de cartes graphiques, dont l'usage dans le monde de la démo reste confidentiel).
Le PC prit donc de l'essor dans le monde de la démo, au détriment de l'Amiga, et de ses fabuleux coprocesseurs...
Ces articles retraceront donc les algorithmes utilisés par les programmeurs de démos PC pour copier les capacités de l'Amiga. Il s'agira d'effets pour la plupart en 2D, la 3D suivra ensuite (notions de trigonométrie requises : sinus, cosinus principalement).
On apprendra ainsi à faire des starfields (défilement d'étoiles), des plasmas (images psychédéliques colorées), des rotozoom (zoom et rotation temps réél d'une image), des shade bobs (superposition de sprites), etc... Tous ces effets qui firent la gloire des démomakers Amiga.
Suite à mon sondage, ces exemples seront réalisés en C, avec de l'assembleur inline. A ceux qui se demandent "pourquoi l'assembleur", je donne une explication : ces effets seront programmés sans aucune librairie graphique style SDL ou Direct X. Tout sera fait à la main, du calcul des positions jusqu'a l'affichage. Il sera donc nécessaire d'accéder parfois directement au matériel, en court-circuitant le système. Bien que les librairies actuelles sont certainement plus puissantes et rapides que le code qui sera utilisé ici, le but est avant tout d'apprendre les principes, à la façon des pionniers, ceux qui n'avaient sous la main que leur imagination, et une solide référence de la machine sur laquelle ils travaillaient. Et pour un accés direct au matériel, l'assembleur reste indispensable. Je tacherais cependant de l'utiliser au strict minimum, une grande partie de l'algorithme sera réalisé en C.
J'ai retenu le compilateur C Open Watcom, disponible ici :
http://www.openwatcom.org/
Il s'agit d'un compilateur très performant, et permettant d'inclure très facilement de l'assembleur en ligne.
Sur ces bonnes paroles, en avant pour le premier article !
Sommaire
Prérequis
Le starfield horizontal
La notion de temps machine
L'effet de feu
Pour faire plaisir aux afficionados du code portable
Les mathématiques à virgule fixe
Les plasmas en temps réél (sine plasma)
Le mode X
Le starfield 3D
Dosbox et Second Reality
Les bases de la 3D
N'hésitez pas à lire l'ensemble du topic, il fourmille d'astuces et de commentaires précieux
Message édité par Harkonnen le 21-06-2004 à 23:19:04
---------------
J'ai un string dans l'array (Paris Hilton)