Rotations, mises à l'échelle, déplacement, transformations de textures à partir des pixels.

Rotations, mises à l'échelle, déplacement, transformations de textures à partir des pixels.

Voir version :

Dépendances (dans l'archive) :
mur.bmp

Télécharger :

#include <string.h>
#include <math.h>
#include <sdl/sdl.h>

#ifdef WIN32
#pragma comment(lib,"sdl.lib")
#pragma comment(lib,"sdlmain.lib")
#endif

typedef struct 
{
    float x[2],y[2],o[2];
} Matrix2D;

void InitMatScale(Matrix2D* mat,float sc)
{
    memset(mat,0,sizeof(Matrix2D));
    mat->x[0] = sc;
    mat->y[1] = sc;
}

void InitMatTranslate(Matrix2D* mat,float x,float y)
{
    InitMatScale(mat,1); // ID
    mat->o[0] = x;
    mat->o[1] = y;
}

void InitMatRotate(Matrix2D* mat,float a)
{
    memset(mat,0,sizeof(Matrix2D));
    mat->x[0] = (float)cos(a);
    mat->x[1] = (float)sin(a);
    mat->y[0] = - mat->x[1];
    mat->y[1] = mat->x[0];
};

void SDL_PutPixel32(SDL_Surface *surface, int x, int y, Uint32 pixel)
{
    Uint8 *p;
    if (x<0 || y<0 || x>=surface->w || y>=surface->h)
        return;
    p = (Uint8*)surface->pixels + y * surface->pitch + x * 4;
    *(Uint32*)p = pixel;
}

Uint32 SDL_GetPixel32(SDL_Surface *surface, int x, int y)
{
    Uint8 *p;
    if (x<0 || y<0 || x>=surface->w || y>=surface->h)
        return 0;
    p = (Uint8*)surface->pixels + y * surface->pitch + x * 4;
    return *(Uint32*)p;
}

int InverseMatrix(Matrix2D* in,Matrix2D* out)
{
    float den = in->x[0]*in->y[1] - in->x[1]*in->y[0];
    out->x[0] = in->y[1]/den;
    out->x[1] = - in->x[1]/den;
    out->y[0] = - in->y[0]/den;
    out->y[1] = in->x[0]/den;
    out->o[0] = (in->o[1]*in->y[0]-in->o[0]*in->y[1])/den;
    out->o[1] = (in->o[0]*in->x[1]-in->o[1]*in->x[0])/den;
    return 0;
}

int Multiply(Matrix2D* A,Matrix2D* B,Matrix2D* out)
{
    out->x[0] = B->x[1]*A->y[0] + B->x[0]*A->x[0];
    out->x[1] = B->x[1]*A->y[1] + B->x[0]*A->x[1];
    out->y[0] = B->y[1]*A->y[0] + B->y[0]*A->x[0];
    out->y[1] = B->y[1]*A->y[1] + B->y[0]*A->x[1];
    out->o[0] = B->o[1]*A->y[0] + B->o[0]*A->x[0] + A->o[0];
    out->o[1] = B->o[1]*A->y[1] + B->o[0]*A->x[1] + A->o[1];
    return 0;
}

int GetBoundingBox(Matrix2D* mat,int *xmin,int* xmax,int *ymin,int* ymax)
{
    float coords[4][2];
    int i;
    for(i=0;i<2;i++)
    {
        coords[0][i] = mat->o[i];
        coords[1][i] = mat->o[i] + mat->x[i];
        coords[2][i] = mat->o[i] + mat->y[i];
        coords[3][i] = mat->o[i] + mat->x[i] +  + mat->y[i];
    }
    *xmin = *xmax = (int)coords[0][0];
    *ymin = *ymax = (int)coords[0][1];
    for(i=1;i<4;i++)
    {
        if (coords[i][0]<*xmin)
            *xmin = (int)coords[i][0];
        if (coords[i][0]>*xmax)
            *xmax = (int)coords[i][0];
        if (coords[i][1]<*ymin)
            *ymin = (int)coords[i][1];
        if (coords[i][1]>*ymax)
            *ymax = (int)coords[i][1];
    }
    return 0;
}

int TransfoPoint(Matrix2D* mat,float i,float j,float* x,float* y)
{
    *x = j*mat->y[0] + i*mat->x[0] + mat->o[0];
    *y = j*mat->y[1] + i*mat->x[1] + mat->o[1];
    return 0;
}

int RenderTransformed(SDL_Surface* screen,SDL_Surface* image,Matrix2D* mat)
{
    Matrix2D inversed;
    float x,y;
    int i,j,xmin,xmax,ymin,ymax;
    GetBoundingBox(mat,&xmin,&xmax,&ymin,&ymax);
    InverseMatrix(mat,&inversed);
    for(i=xmin;i<xmax+1;i++)
    {
        for(j=ymin;j<ymax+1;j++)
        {
            Uint32 pix;
            TransfoPoint(&inversed,(float)i,(float)j,&x,&y);
            pix = SDL_GetPixel32(image,(int)(x*image->w),(int)(y*image->h));
            SDL_PutPixel32(screen,i,j,pix);
        }
    }
    return 0;
}

SDL_Surface* Charger(const char* fic)
{
    SDL_Surface *res;
    SDL_Surface* tmp = SDL_LoadBMP(fic);
    if (tmp==NULL)
    {
        printf("Erreur chargement %s\n",fic);
        exit(-1);
    }
    res = SDL_DisplayFormat(tmp);
    SDL_FreeSurface(tmp);
    return res;
}

int Controle(Uint8* keys,Matrix2D* mat)
{
    Matrix2D copy = *mat;
    Matrix2D trans;
    int sens = 1;
    InitMatScale(&trans,1);
    if (keys[SDLK_RIGHT])
        InitMatTranslate(&trans,1,0);
    if (keys[SDLK_LEFT])
        InitMatTranslate(&trans,-1,0);
    if (keys[SDLK_DOWN])
        InitMatTranslate(&trans,0,1);
    if (keys[SDLK_UP])
        InitMatTranslate(&trans,0,-1);
    if (keys[SDLK_a])
    {
        InitMatScale(&trans,1.05f);
        sens = 0;
    }
    if (keys[SDLK_q])
    {
        InitMatScale(&trans,0.95f);
        sens = 0;
    }
    if (keys[SDLK_r])
    {
        InitMatRotate(&trans,0.05f);
        sens = 0;
    }

    if (keys[SDLK_c])
    {
        Matrix2D rotate,T,TI,temp;
        float med = 0.5;
        InitMatRotate(&rotate,0.05f);
        InitMatTranslate(&T,med,med);
        InitMatTranslate(&TI,-med,-med);
        Multiply(&rotate,&TI,&temp);
        Multiply(&T,&temp,&trans);
        sens = 0;
    }

    if (sens==0)
        Multiply(&copy,&trans,mat);
    else
        Multiply(&trans,&copy,mat);
    return 0;
}

int main(int argc,char** argv)
{
    SDL_Surface* screen,*image;
    int numkeys;
    Uint8 * keys;
    Uint32 timer,elapsed;
    Matrix2D mat;
    InitMatScale(&mat,200);
    SDL_Init(SDL_INIT_VIDEO);
    screen=SDL_SetVideoMode(400,320,32,SDL_SWSURFACE|SDL_DOUBLEBUF);
    image = Charger("mur.bmp");
    SDL_LockSurface(image);
    if (!image)
        return -1;
    do 
    {
        timer = SDL_GetTicks();
        SDL_FillRect(screen,NULL,0);
        SDL_PumpEvents();
        keys = SDL_GetKeyState(&numkeys);
        Controle(keys,&mat);
        RenderTransformed(screen,image,&mat);
        SDL_Flip(screen);
        elapsed = SDL_GetTicks() - timer;
        if (elapsed<20)
            SDL_Delay(20-elapsed);
    } while (!keys[SDLK_ESCAPE]);
    SDL_FreeSurface(image);
    SDL_Quit();
    return 0;
}



Commentaires


Voici un petit exemple : 

Lancez le programme :
Utilisez les flèches pour déplacer l'objet.
a et q pour zoomer, dézoomer.
r pour une rotation autour du coin supérieur gauche.
c pour une rotation autour du milieu.

Le concept est simple : je définis une texture (donc mon mur) par un repère O,X,Y, quelque part dans l'écran.
Le O est le point supérieur gauche de la texture.
Et puis on a les vecteurs X et Y, orthogonaux, plus ou moins longs, et je trace le mur des coordonnées (0,0) aux coordonnées (1,1).

Donc si vous agrandissez les vecteurs X et Y, vous avez un carré plus grand. Vous pouvez faire pivoter le repère, le translater...

Le repère peut être défini par une matrice de transformation 3x3 avec les vecteurs colonne placés dans l'ordre X,Y,O
Si le vecteur X a pour coordonnées Xx, et Xy, le vecteur Y a pour coordonnées Yx,Yy et le point O a pour coordonnées Ox, Oy, la matrice est :

| Xx   Yx   Ox  |
| Xy   Yy   Oy  |
|  0    0    1  |

La dernière ligne est 0,0,1 car dans l'espace affine, on met une 3e coordonnée 0 pour les vecteurs, 1 pour les points.

Au début du programme, je définis donc une matrice, si vous suivez la fonction InitMatScale au début du main, qui est donc :

| 200    0    0  |
|   0  200    0  |
|   0    0    1  |

Cela signifie que je veux le point d'ancrage en (0,0) avec un vecteur X(200,0) et le Y(0,200)
Donc typiquement, je veux mon mur entre les pixels (0,0) et (200,200).
Ce que j'ai si je lance le programme.

La fonction qui affiche le mur correctement en fonction de la matrice est RenderTransformed.

Cette fonction prend la boite englobante du repère de la matrice (pour savoir entre quoi et quoi je dois faire mes for), puis va aller chercher le bon pixel de la texture.

Mathématiquement, on dit que :

P = M*Q

M est ma matrice, mon repère. Q est un point dans mon repère M, et je veux le point P dans le repère global. Je l'ai grace à cette formule.
Or moi je vais raisonner à l'envers : j'ai mon point de destination P, et je veux savoir à quel Q il correspond, donc la formule est :

Q = M^(-1) * P

M^(-1) est l'inverse de la matrice M. Donc on voit que je calcule l'inverse, et que je l'applique pour obtenir x,y.
Or le x,y seront des nombres en 0 et 1.

Si je veux un pixel associé de mon mur, je multiplie par sa largeur et la hauteur pour avoir le plus proche. le cast violent en int fait que j'aurai un rendu de type NEAREST.

Maintenant, regardons la fonction Controle qui va me permettre de faire mes effets.

Faire des transformations reviendra juste à multiplier des matrices !
Le sens de multiplication est important, en effet, A*B != B*A pour les matrices.

Contactez moi si vous avez besoin de davantage de détails.


*****************************
Annexe : détail des calculs : 
*****************************

Pour voir le détail des calculs, utilisez directement le lien suivant :

http://maxima-online.org/#?in=A%3Amatrix(%5BXx%2CYx%2COx%5D%2C%5BXy%2CYy%2COy%5D%2C%5B0%2C0%2C1%5D)%3B%0Ainvert(A)%3B%0AB%3Amatrix(%5Bi%5D%2C%5Bj%5D%2C%5B1%5D)%3B%0AA.B%3B%0AC%3Amatrix(%5BIx%2CJx%2CPx%5D%2C%5BIy%2CJy%2CPy%5D%2C%5B0%2C0%2C1%5D)%3B%0AA.C%3B

Ou alors :
http://maxima-online.org/
Puis tapez :

A:matrix([Xx,Yx,Ox],[Xy,Yy,Oy],[0,0,1]);
invert(A);
B:matrix([i],[j],[1]);
A.B;
C:matrix([Ix,Jx,Px],[Iy,Jy,Py],[0,0,1]);
A.C;