CODINGSDCC TUT'S PAR STEPHBB75 ★ Bezier ★

Sdcc - 24 - BezierCoding Sdcc Tut's Par Stephbb75

Bézier

Mais pourquoi les courbe de Bézier, en fait c'est en regardant une vidéo sur Youtube que je me suis demandé comment réaliser cela sur CPC, une vidéo c'est pas la peine d'y pensé, des sprites c'est bien trop compliqué, en fait comme se sont des dessins, c'est une suite de trais, donc de courbe ...
Bézier, a ne pas confondre avec une belle ville de l'Hérault, ici on vas parler de courbes, les courbes de Bézier, c'est Pierre Bézier ingénieur français en mécanique et électricité qui les inventa les courbes et surfaces de Bézier.
Donc pour tout savoir sur ces courbes (et surface) allez faire un tour sur wikipedia. Du reste le début de ce code commence avec un code récupéré sur wikibooks (Courbe de Bézier en C) donc voici le code source :

typedef struct {
        float x, y;
 } point;

 typedef struct {
       point P[4];
 } Bezier;

 /* Variables globales */
 Bezier Global_Bezier;
 int level=1;

 // Draw a line with color (r,v,b)
 void DrawLine(int x1, int y1, int x2, int y2,float r, float v, float b)
 {
    ICI LE CODE POUR DESSINER UNE LIGNE !
 }

 void DrawBezierBase(Bezier p,float r,float v,float b)
 {
   DrawLine(p.P[0].x,p.P[0].y,p.P[1].x,p.P[1].y,r,v,b);
   DrawLine(p.P[1].x,p.P[1].y,p.P[2].x,p.P[2].y,r,v,b);
   DrawLine(p.P[2].x,p.P[2].y,p.P[3].x,p.P[3].y,r,v,b);
 }

 void DrawBezierRecursive (Bezier b, int level)
 {
         ;if (level <= 0) {
          /* draw a line segment */
             ;  DrawLine((int) (b.P[0].x + 0.5), (int) (b.P[0].y + 0.5),
                              ; (int) (b.P[3].x + 0.5), (int) (b.P[3].y + 0.5),1,1,1);
         ;} else {
               ;  Bezier left, right;               ;  
               ;  /* subdivide into 2 Bezier segments */
               ;  left.P[0].x = b.P[0].x;
               ;  left.P[0].y = b.P[0].y;
               ;  left.P[1].x = (b.P[0].x + b.P[1].x) / 2;
               ;  left.P[1].y = (b.P[0].y + b.P[1].y) / 2;
               ;  left.P[2].x = (b.P[0].x + 2*b.P[1].x + b.P[2].x) / 4;
               ;  left.P[2].y = (b.P[0].y + 2*b.P[1].y + b.P[2].y) / 4;
               ;  left.P[3].x = (b.P[0].x + 3*b.P[1].x + 3*b.P[2].x + b.P[3].x) / 8;
               ;  left.P[3].y = (b.P[0].y + 3*b.P[1].y + 3*b.P[2].y + b.P[3].y) / 8;
               ;  DrawBezierBase(left,0,1,0);
               ;  right.P[0].x = left.P[3].x;
               ;  right.P[0].y = left.P[3].y;
               ;  right.P[1].x = (b.P[1].x + 2*b.P[2].x + b.P[3].x) / 4;
               ;  right.P[1].y = (b.P[1].y + 2*b.P[2].y + b.P[3].y) / 4;
               ;  right.P[2].x = (b.P[2].x + b.P[3].x) / 2;
               ;  right.P[2].y = (b.P[2].y + b.P[3].y) / 2;
               ;  right.P[3].x = b.P[3].x;
               ;  right.P[3].y = b.P[3].y;
               ;  DrawBezierBase(right,0,0,1);
               ;  /* draw the 2 segments recursively */
               ;  DrawBezierRecursive (left, level -1);
               ;  DrawBezierRecursive (right, level -1);
         ;}
 }

 void DrawBezier()
 {
       DrawBezierBase(Global_Bezier,1,0,0);
       DrawBezierRecursive(Global_Bezier,level);
 }


On pourrait prendre se source presque tel quel pour notre programme sur CPC, la seule chose c'est qu'il ne serait pas bien rapide, en effet il utilise les float, et sur CPC (8 bit en plus) leur utilisation est très, très voire même très lente...
On va les remplacer par un int qui vas bien plus vite. J'en voie déjà qui vont me dire que l'on vas perdre en précision, oui c'est sur, mais bon pas temps que cela en fait, avec la résolution du CPC on peut se le permettre sans grande perte.
La fonction DrawBezierBase ne nous intéresse pas, elle permet de dessiner les droite de construction de la courbe, donc on retire.
Autre "optimisation" on va changer tous les divisions par 2, 4 ou 8 par des décalages, cela nous est possible car nous travaillons avec des entiers désormais. Du reste au moment de l'appel de la fonction DrawLine il y a une addition de 0.5, on la retire, on travaille avec des entier !
Donc voici le code que l'on aura pour le CPC :

typedef struct
{
    int x;
    int y;
} point;

typedef struct
{
    point P[4];
} Bezier;


// Variables globales
Bezier* Global_Bezier;
int level;


void DrawBezierRecursive (Bezier* b, int level)
{
    if (level <= 0)
    {
        // draw a line segment
        lineBresenham ((unsigned int) (b->P[0].x), (unsigned char) (b->P[0].y), (unsigned int) (b->P[3].x), (unsigned char) (b->P[3].y), 1);
    }
    else
    {
        Bezier left;
        Bezier right;
        
        // subdivide into 2 Bezier segments
        left.P[0].x = b->P[0].x;
        left.P[0].y = b->P[0].y;
        left.P[1].x = (b->P[0].x + b->P[1].x) >> 1;
        left.P[1].y = (b->P[0].y + b->P[1].y) >> 1;
        left.P[2].x = (b->P[0].x + 2*b->P[1].x + b->P[2].x) >> 2;
        left.P[2].y = (b->P[0].y + 2*b->P[1].y + b->P[2].y) >> 2;
        left.P[3].x = (b->P[0].x + 3*b->P[1].x + 3*b->P[2].x + b->P[3].x) >> 3;
        left.P[3].y = (b->P[0].y + 3*b->P[1].y + 3*b->P[2].y + b->P[3].y) >> 3;
        
        right.P[0].x = left.P[3].x;
        right.P[0].y = left.P[3].y;
        right.P[1].x = (b->P[1].x + 2*b->P[2].x + b->P[3].x) >> 2;
        right.P[1].y = (b->P[1].y + 2*b->P[2].y + b->P[3].y) >> 2;
        right.P[2].x = (b->P[2].x + b->P[3].x) >> 1;
        right.P[2].y = (b->P[2].y + b->P[3].y) >> 1;
        right.P[3].x = b->P[3].x;
        right.P[3].y = b->P[3].y;

        // Dessin les deux segment de facon recurcive
        DrawBezierRecursive (&left, level -1);
        DrawBezierRecursive (&right, level -1);
    }
}


Il nous suffit de rajouter les fonctions lineBresenham, PutPixelFastMode1 et SetPenM1 pour que notre programme soit bon.
Il ne nous suffit plus que de remplir une structure Bézier avec le point de départ, les 2 points de contrôle et le point d'arrivé et cela se dessineras à l'écran.
C'est le source que vous trouverez nommé Bezier0.C (et .BIN), a vous de tester, voici ce que cela donne :

Cela fonctionne, maintenant pour faire des dessins un peut plus sophistiquer ce n'est pas pratique, le mieux serais de mettre les point dans un fichier...
Ok, on y va alors. J'ai choisi en fait de faire un fichier qui mappait la structure Bézier, donc on trouvera une suite de 8 word qui représentent une structure, puis une autre, puis ...
Comme cela il n'y a plus qu'a mettre se fichier en mémoire décaler un pointeur, ce qui demande aucune modification du code, on a même plus besoin (pour le moment) de la déclaration de la structure Bézier, mais il nous faut ajouter la fonction de lecture d'un fichier et modifier le main :

void main()
{
    int i;
    
    SCR_SET_MODE1_s;
    level = 3;

    gotoxy(1,1);printf("Ligne de Bezier en MODE 1");
    gotoxy(1,2);printf("Petite demo de presentation");
    gotoxy(1,15);printf("Une touche pour commencer...");
    KM_WAIT_CHAR_s;
    SCR_SET_MODE1_s; // pour effacer l'ecran...
    
    ReadFileBin("D2data.bin", 0x4000);

    for (i = 0; i < 89; i++)
    {
        DrawBezierRecursive( (0x4000 + (i * 16)), level);
    }    

    gotoxy(1,24);printf("FIN (une touche)");KM_WAIT_CHAR_s;

}


On charge le fichier en 0x4000 puis on calcul l'adresse de chaque structure en mémoire et on affiche.
Le tout se trouve dans le fichier Bezier1.c Voila ce que cela donne :

L'animation est fidèle en temps vis à vis d'un CPC


Pour faire les fichiers que l'on chargeras, c'est a la mano, je vous propose cette article pour les faires.

Cela donne déjà pas mal, mais en premier j'aimerais bien faire une petite pause à un moment du dessin, voir changer de couleur, ou d'autre choses. En second, il faut en fait pour chaque fichier faire un code spécial pour le nombre de courbe a dessiner, on vas donc mette une petite entête (a nous) au fichier, qui contiendras le nombre de point, le mode écran (la pour la suite on continue avec le mode 1, il faut mettre les autres fonctions de dessin pour les autres mode). Et pour le 1er point je vais ajouter une structure pour des commandes (changement de couleurs, ...).

// Entête du fichier de data ...
// !!!!!!!!!!!!!!!!!!!!!
// Attention, elle doit avoir la même longueur que la structure Bezier !!!
typedef struct
{
    int NbPoint;    // nombre de points dans le fichier, c'est le nombre de structure Bezier présente dans le fichier
                    // s'il y a des commandes, elle compte pour une structure Bezier !!
                    // mais l'entête ne compte pas !!!
    int Mode;       // mode graphique 0, 1, 2
    int Ver;        // version (pour le moment 1)
    int Empty4;
    int Empty3;        // Word d'ont on ne tien pas compte pour le moment
    int Empty2;        // cela permet d'avoir la même taille que la structure Bezier
    int Empty1;
    int Empty0;
} FileHeader;

// structure pour les commandes
// !!!!!!!!!!!!!!!!!!!!!
// Attention, elle doit avoir la même longeur que la structure Bezier !!!
typedef struct
{
    int IndicCmd;    // indique que c'est une commande, toujours 0xFFFF comme valeur
    int CmdVersion;    // pour le moment valeur 1
    int CMD;        //  commande en elle même
    int CmdVal;        // valeur asocié à la commande
    int Empty3;        // Word d'ont on ne tien pas compte pour le moment
    int Empty2;        // cela permet d'avoir la même taille que la structure Bezier
    int Empty1;
    int Empty0;
    
} Commande;


Ces deux structures n'ont rien de bien spécial, sauf qu'elles ont à la même longueur que la structure Bézier. A se sujet, j'aurais put ajouter simplement à la structure Bézier un octet ou un word pour la couleur, je n'ai pas choisi cette solution, car on ne va pas changer de couleur pour chaque ligne (quoi que) mais surtout cela prend un peut plus de place en mémoire !

Vous verrez leur utilisation dans le fichier bezier2.c
Il y a un petit soucie dans se fichier, pour effacer l'écran j'utilise la fonction système 0xBBDB, ce qui peut entrainer des problèmes, cela fait repartir la boucle FOR à 0 une fois ce vecteur système appelé. J'ai corrigé se problème dans le fichier bezier3.c en utilisant ma fonction CLS_WithPile().

Voila, dans le ZIP il y a une disquette (Bezier3) avec une petite démo de 9 dessins, se sont les dessins de la vidéo du début d'article, plus une autre image, comme d'habitude je vous rappelle que je ne suis pas un graphiste super calé, donc mes dessins sont ce qu'il son ;-)
Si vous souhaitez faire des dessins, n'hésitez pas ... :-)

Voila, comme d'hab, le tout se trouve dans se ZIP, bonne écriture ;-)


stephbb75

ANNÉE: 2013
★ AUTEUR: Stephbb75

Page précédente : Sdcc - 23 - Slide Show

CPCrulez[Content Management System] v8.7-desktop/cache
Page créée en 144 millisecondes et consultée 893 fois

L'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent  du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko.