SDL - Przekształcenia w przestrzeni 3D

Przekształcenia w przestrzeni trójwymiarowej wykonuje się podobnie jak na płaszczyźnie - za pomocą odpowiednich macierzy przekształceń. W przestrzeni 3D macierze te posiadają rozmiar 4 x 4. Do ćwiczeń posłuży nam program z poprzednich zajęć, który wyświetlał przemieszczający się kwadrat. Na początek zdefiniujemy kilka procedur.

 

Tworzenie macierzy jednostkowej

Potrzebna nam będzie macierz jednostkowa o wymiarze 4 x 4. Macierz ta zeruje przekształcenia i używamy jej do początkowego ustawienia macierzy przekształceń. Macierz ta ma postać:

 

I =    1 0 0 0  
  0 1 0 0  
  0 0 1 0  
  0 0 0 1  

 

Code::Blocks
// Tworzy macierz jednostkową

void identity(double x[][4])
{
  for(int i = 0; i < 4; i++)
    for(int j = 0; j < 4; j++)
      x[i][j] = i == j ? 1 : 0;
}

 

Mnożenie macierzy

Kolejne przekształcenia dołączamy do macierzy przekształceń mnożąc ją przez macierz określonego przekształcenia. Dlatego potrzebna nam będzie procedura mnożąca dwie macierze o wymiarze 4 x 4:

 

X = X x Y

 

Code::Blocks
// Mnoży macierze X = X x Y

void multiply(double x[][4], double y[][4])
{
    double c[4][4];  // macierz na wynik tymczasowy
    int i,j;

    // mnożymy C = X x Y

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++)
        c[i][j] = x[i][0]*y[0][j] + x[i][1]*y[1][j] + x[i][2]*y[2][j] + x[i][3]*y[3][j];

    // Kopiujemy X = C

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++) x[i][j] = c[i][j];
}

 

Translacja - przesunięcie w przestrzeni 3D

Macierz przesunięcia jest następująca

 

T =    1 0 0 0  
  0 1 0 0  
  0 0 1 0  
  Tx Ty Tz 1  

gdzie:

Tx - przesunięcie na osi OX,
Ty - przesunięcie na osi OY
Tz - przesunięcie na osi OZ

 

Nasza procedura dołącza macierz przesunięcia do macierzy przekształcenia G, czyli wykonuje operację: G = G x T

 

Code::Blocks
// Dołącza translację do macierzy przekształcenia

void translate(double g[][4], double Tx, double Ty, double Tz)
{
    double t[4][4];
    
    identity(t);  // tworzymy w t macierz jednostkową

    t[3][0] = Tx; // wstawiamy przesunięcia
    t[3][1] = Ty;
    t[3][2] = Tz;
    
    multiply(g,t); // dołączamy translację do przekształcenia
}

 

Skalowanie

Macierz skalowania:

 

S =    Sx 0 0 0  
  0 Sy 0 0  
  0 0 Sz 0  
  0 0 0 1  

gdzie:

Sx - skala na osi OX
Sy - skala na osi OY
Sz - skala na osi OZ

 

Procedura dołączenia macierzy skalowania do macierzy przekształcenia: G = G x S

 

Code::Blocks
// Dołącza skalowanie do macierzy przekształcenia

void scale(double g[][4], double Sx, double Sy, double Sz)
{
    double s[4][4];

    identity(s);  // tworzymy w t macierz jednostkową

    s[0][0] = Sx; // wstawiamy skale
    s[1][1] = Sy;
    s[2][2] = Sz;

    multiply(g,s); // dołączamy skalowanie do przekształcenia
}

 

Rotacja - obroty

Macierz rotacji zależy od wyboru osi, wokół której dokonywany jest obrót. Dlatego potrzebujemy 3 różnych macierzy:

 

Rx =    1 0 0 0  
  0 cosα sinα 0  
  0 -sinα cosα 0  
  0 0 0 1  

 

Ry =    cosα 0 -sinα 0  
  0 1 0 0  
  sinα 0 cosα 0  
  0 0 0 1  

 

Rz =    cosα sinα 0 0  
  -sinα cosα 0 0  
  0 0 1 0  
  0 0 0 1  

gdzie:

α - kąt obrotu względem wybranej osi

 

Code::Blocks
// Dołącza rotację do macierzy przekształcenia

void rotate_x(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[1][1] =  ca; r[1][2] = sa;
    r[2][1] = -sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_y(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] = ca; r[0][2] = -sa;
    r[2][0] = sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_z(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] =  ca; r[0][1] = sa;
    r[1][0] = -sa; r[1][1] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

 

Przykładowa aplikacja

 

Program wykorzystuje bibliotekę newgfx.

 

Code::Blocks
// Przekształcenia 3D
// (C)2012 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include "newgfx.h"
#include <cmath>
#include <list>

const double PI=3.1415926535897;

// Definicja typów danych

struct vertex3D
{
    double x,y,z;
};

struct vertex2D
{
    Sint32 x,y;
};

struct line3D
{
    int v1,v2;
};

// Definicja wierzchołków figury

const int VN = 8;  // Liczba wierzchołków

vertex3D V[] = {{-100, 100, 100},{ 100, 100, 100},{ 100,-100, 100},{-100,-100, 100},
                {-100, 100,-100},{ 100, 100,-100},{ 100,-100,-100},{-100,-100,-100}};

// Definicja krawędzi figury

const int LN = 12;  // Liczba krawędzi

line3D L[] = {{0,1},{1,2},{2,3},{3,0},{4,5},{5,6},{6,7},{7,4},{0,4},{1,5},{2,6},{3,7}};

// Tablica współrzędnych ekranowych wierzchołków

vertex2D VE[VN];

// Tworzy macierz jednostkową

void identity(double x[][4])
{
  for(int i = 0; i < 4; i++)
    for(int j = 0; j < 4; j++)
      x[i][j] = i == j ? 1 : 0;
}

// Mnoży macierze X = X x Y

void multiply(double x[][4], double y[][4])
{
    double c[4][4];  // macierz na wynik tymczasowy
    int i,j;

    // mnożymy C = X x Y

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++)
        c[i][j] = x[i][0]*y[0][j] + x[i][1]*y[1][j] + x[i][2]*y[2][j] + x[i][3]*y[3][j];

    // Kopiujemy X = C

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++) x[i][j] = c[i][j];
}

// Dołącza translację do macierzy przekształcenia

void translate(double g[][4], double Tx, double Ty, double Tz)
{
    double t[4][4];

    identity(t);  // tworzymy w t macierz jednostkową

    t[3][0] = Tx; // wstawiamy przesunięcia
    t[3][1] = Ty;
    t[3][2] = Tz;

    multiply(g,t); // dołączamy translację do przekształcenia
}

// Dołącza skalowanie do macierzy przekształcenia

void scale(double g[][4], double Sx, double Sy, double Sz)
{
    double s[4][4];

    identity(s);  // tworzymy w t macierz jednostkową

    s[0][0] = Sx; // wstawiamy skale
    s[1][1] = Sy;
    s[2][2] = Sz;

    multiply(g,s); // dołączamy skalowanie do przekształcenia
}

// Dołącza rotację do macierzy przekształcenia

void rotate_x(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[1][1] =  ca; r[1][2] = sa;
    r[2][1] = -sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_y(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] = ca; r[0][2] = -sa;
    r[2][0] = sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_z(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] =  ca; r[0][1] = sa;
    r[1][0] = -sa; r[1][1] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

int main(int argc, char *argv[])
{
    int waiting = 1;

    SDL_Surface * screen;

    SDL_Event event;

    double kx = PI, ky = 0, kz = 0;      // Kąty pozycji na elipsie

    double alpha = 0;                    // Kąt obrotu sześcianu

    double sx = 0, sy = 100, sz = 1000;  // Współrzędne środka elipsy

    double rx = 700, ry = 600, rz = 900; // Promienie elipsy

    double tx,ty,tz;  // Wektor przesunięcia

    double d = 1000;  // Punkt obserwatora

    double m;         // Mnożnik

    SDL_Rect r;       // Prostokąt wymazywania

    double G[4][4];   // Macierz przekształcenia

    if(!SDL_Init(SDL_INIT_VIDEO))
    {
        atexit(SDL_Quit);

        screen = SDL_SetVideoMode(1280, 1024, 32, SDL_HWSURFACE | SDL_FULLSCREEN);

        // Ustawiamy prostokąt wymazywania

        r.x = r.y = 0;
        r.w = screen->w;
        r.h = screen->h;

        do
        {
            // Obliczamy wektor przesunięcia

            tx = sx + rx * cos(kx);
            ty = sy + ry * sin(ky);
            tz = sz + rz * cos(kz);

            // modyfikujemy kąty

            kx += 0.005;
            ky += 0.0075;
            kz += 0.0125;

            if(kx > PI+PI) kx = 0;
            if(ky > PI+PI) ky = 0;
            if(kz > PI+PI) kz = 0;

            identity(G);           // zerujemy przekształcenie
            scale(G,1,2,3);
            rotate_z(G,-PI/4);
            rotate_y(G,-PI/4);
            rotate_x(G,-PI/4);
            rotate_x(G,alpha);
            rotate_z(G,PI/4);
            rotate_y(G,PI/4);
            rotate_x(G,PI/4);
            translate(G,tx,ty,tz+500); // dołączamy translację

            alpha += 0.01; if(alpha > PI+PI) alpha = 0;

            // Obliczamy współrzędne ekranowe

            for(int i = 0; i < VN; i++)
            {
                double x,y,z;

                // wyliczamy współrzędne po przekształceniu

                x = G[0][0]*V[i].x + G[1][0]*V[i].y + G[2][0]*V[i].z + G[3][0];
                y = G[0][1]*V[i].x + G[1][1]*V[i].y + G[2][1]*V[i].z + G[3][1];
                z = G[0][2]*V[i].x + G[1][2]*V[i].y + G[2][2]*V[i].z + G[3][2];

                m = d / (z + d);  // obliczamy mnożnik

                VE[i].x = (screen->w >> 1) + m * x;
                VE[i].y = (screen->h >> 1) - m * y;
            }


            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            // Rysujemy szkielet figury w kolorze białym

            for(int i = 0; i < LN; i++)
            {
              gfxClipWuLine(screen,VE[L[i].v1].x,VE[L[i].v1].y,VE[L[i].v2].x,VE[L[i].v2].y,0xffffff);
              gfxClipWuLine(screen,VE[L[i].v1].x+1,VE[L[i].v1].y,VE[L[i].v2].x+1,VE[L[i].v2].y,0xffffff);
              gfxClipWuLine(screen,VE[L[i].v1].x,VE[L[i].v1].y+1,VE[L[i].v2].x,VE[L[i].v2].y+1,0xffffff);
              gfxClipWuLine(screen,VE[L[i].v1].x+1,VE[L[i].v1].y+1,VE[L[i].v2].x+1,VE[L[i].v2].y+1,0xffffff);
            }

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            SDL_UpdateRect(screen, 0, 0, 0, 0); // Uaktualniamy ekran

            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            // Wymazujemy szkielet figury

            SDL_FillRect(screen, &r, 0);

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            // Czekamy na klawisz ESCAPE

            if (SDL_PollEvent(&event))
                if ((event.type == SDL_QUIT) ||
                   ((event.type == SDL_KEYDOWN) &&
                    (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 0;
        } while(waiting);
    }

    return 0;
}

 

Drobna zmiana w programie umożliwia tworzenie animacji stereoskopowej, którą obserwujemy przy pomocy specjalnych okularów ze szkłem czerwonym (oko lewe) i niebieskozielonym (oko prawe). Koniecznie zaktualizuj bibliotekę newgfx, ponieważ wymagane są nowe funkcje graficzne.

 

Code::Blocks
// Przekształcenia 3D
// (C)2012 Koło Informatyczne
// I LO w Tarnowie
//-------------------------------

#include "newgfx.h"
#include <cmath>
#include <list>

const double PI=3.1415926535897;

// Definicja typów danych

struct vertex3D
{
    double x,y,z;
};

struct vertex2D
{
    Sint32 x,y;
};

struct line3D
{
    int v1,v2;
};

// Definicja wierzchołków figury

const int VN = 10;  // Liczba wierzchołków

vertex3D V[] = {{-1,0,-1},{1,0,-1},{1,0,1},{-1,0,1},{0,1.43,0},{-2,-0.5,-2},{2,-0.5,-2},{2,-0.5,2},{-2,-0.5,2},{0,3,0}};

// Definicja krawędzi figury

const int LN = 17;  // Liczba krawędzi

line3D L[] = {{0,1},{1,2},{2,3},{3,0},{0,4},{1,4},{2,4},{3,4},{0,5},{1,6},{2,7},{3,8},{5,6},{6,7},{7,8},{8,5},{4,9}};

// Tablica współrzędnych ekranowych wierzchołków

vertex2D VEL[VN],VER[VN];

// Tworzy macierz jednostkową

void identity(double x[][4])
{
  for(int i = 0; i < 4; i++)
    for(int j = 0; j < 4; j++)
      x[i][j] = i == j ? 1 : 0;
}

// Mnoży macierze X = X x Y

void multiply(double x[][4], double y[][4])
{
    double c[4][4];  // macierz na wynik tymczasowy
    int i,j;

    // mnożymy C = X x Y

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++)
        c[i][j] = x[i][0]*y[0][j] + x[i][1]*y[1][j] + x[i][2]*y[2][j] + x[i][3]*y[3][j];

    // Kopiujemy X = C

    for(i = 0; i < 4; i++)
      for(j = 0; j < 4; j++) x[i][j] = c[i][j];
}

// Dołącza translację do macierzy przekształcenia

void translate(double g[][4], double Tx, double Ty, double Tz)
{
    double t[4][4];

    identity(t);  // tworzymy w t macierz jednostkową

    t[3][0] = Tx; // wstawiamy przesunięcia
    t[3][1] = Ty;
    t[3][2] = Tz;

    multiply(g,t); // dołączamy translację do przekształcenia
}

// Dołącza skalowanie do macierzy przekształcenia

void scale(double g[][4], double Sx, double Sy, double Sz)
{
    double s[4][4];

    identity(s);  // tworzymy w t macierz jednostkową

    s[0][0] = Sx; // wstawiamy skale
    s[1][1] = Sy;
    s[2][2] = Sz;

    multiply(g,s); // dołączamy skalowanie do przekształcenia
}

// Dołącza rotację do macierzy przekształcenia

void rotate_x(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[1][1] =  ca; r[1][2] = sa;
    r[2][1] = -sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_y(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] = ca; r[0][2] = -sa;
    r[2][0] = sa; r[2][2] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

void rotate_z(double g[][4], double alpha)
{
    double r[4][4],sa,ca;

    sa = sin(alpha);
    ca = cos(alpha);

    identity(r);    // tworzymy w t macierz jednostkową

    r[0][0] =  ca; r[0][1] = sa;
    r[1][0] = -sa; r[1][1] = ca;

    multiply(g,r); // dołączamy obrót do przekształcenia
}

int main(int argc, char *argv[])
{
    int waiting = 1;

    SDL_Surface * screen;

    SDL_Event event;

    double kx = PI, ky = 0, kz = 0;       // Kąty pozycji na elipsie

    double alpha = 0;                     // Kąt obrotu

    double sx = 0, sy = 100, sz = 6000;   // Współrzędne środka elipsy

    double rx = 700, ry = 600, rz = 5900; // Promienie elipsy

    double tx,ty,tz;  // Wektor przesunięcia

    double d = 1000;  // Punkt obserwatora

    double m;         // Mnożnik

    SDL_Rect r;       // Prostokąt wymazywania

    double G[4][4];   // Macierz przekształcenia

    int espan = 200;  // Rozstaw oczu

    if(!SDL_Init(SDL_INIT_VIDEO))
    {
        atexit(SDL_Quit);

        screen = SDL_SetVideoMode(1280, 1024, 32, SDL_HWSURFACE | SDL_FULLSCREEN);

        // Ustawiamy prostokąt wymazywania

        r.x = r.y = 0;
        r.w = screen->w;
        r.h = screen->h;

        do
        {
            // Obliczamy wektor przesunięcia

            tx = sx + rx * cos(kx);
            ty = sy + ry * sin(ky);
            tz = sz + rz * cos(kz);

            // modyfikujemy kąty

            kx += 0.002;
            ky += 0.001;
            kz += 0.005;

            if(kx > PI+PI) kx = 0;
            if(ky > PI+PI) ky = 0;
            if(kz > PI+PI) kz = 0;

            identity(G);           // zerujemy przekształcenie
            scale(G,100,100,100);
            rotate_z(G,-PI/4);
            rotate_y(G,-PI/4);
            rotate_x(G,-PI/4);
            rotate_x(G,alpha);
            rotate_z(G,PI/4);
            rotate_y(G,PI/4);
            rotate_x(G,PI/4);
            translate(G,tx-espan,ty,tz+500); // dołączamy translację

            alpha += 0.01; if(alpha > PI+PI) alpha = 0;

            // Obliczamy współrzędne ekranowe dla oka prawego

            for(int i = 0; i < VN; i++)
            {
                double x,y,z;

                // wyliczamy współrzędne po przekształceniu

                x = G[0][0]*V[i].x + G[1][0]*V[i].y + G[2][0]*V[i].z + G[3][0];
                y = G[0][1]*V[i].x + G[1][1]*V[i].y + G[2][1]*V[i].z + G[3][1];
                z = G[0][2]*V[i].x + G[1][2]*V[i].y + G[2][2]*V[i].z + G[3][2];

                m = d / (z + d);  // obliczamy mnożnik

                VER[i].x = (screen->w >> 1) + m * x;
                VER[i].y = (screen->h >> 1) - m * y;
            }

            translate(G,espan<<1,0,0);  // przemieszczamy widok w prawo

            // Obliczamy współrzędne ekranowe dla oka lewego

            for(int i = 0; i < VN; i++)
            {
                double x,y,z;

                // wyliczamy współrzędne po przekształceniu

                x = G[0][0]*V[i].x + G[1][0]*V[i].y + G[2][0]*V[i].z + G[3][0];
                y = G[0][1]*V[i].x + G[1][1]*V[i].y + G[2][1]*V[i].z + G[3][1];
                z = G[0][2]*V[i].x + G[1][2]*V[i].y + G[2][2]*V[i].z + G[3][2];

                m = d / (z + d);  // obliczamy mnożnik

                VEL[i].x = (screen->w >> 1) + m * x;
                VEL[i].y = (screen->h >> 1) - m * y;
            }


            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            for(int i = 0; i < screen->w; i += 50)
              gfxVLine(screen,i,0,0x007f7f,screen->h-1);
            for(int i = 10; i < screen->w; i += 50)
              gfxVLine(screen,i,0,0xff0000,screen->h-1);
            for(int i = 0; i < screen->h; i += 50)
              gfxHLine(screen,0,i,0x5f5f5f,screen->w-1);

            // Rysujemy szkielet figury w kolorze czerwonym dla oka lewego
            // i zielonym dla oka prawego. Linie pokrywające się są rysowane
            // w kolorze żółtym.

            for(int i = 0; i < LN; i++)
            {
              gfxClipORLine(screen,VER[L[i].v1].x,VER[L[i].v1].y,VER[L[i].v2].x,VER[L[i].v2].y,0x007f7f);
              gfxClipORLine(screen,VEL[L[i].v1].x,VEL[L[i].v1].y,VEL[L[i].v2].x,VEL[L[i].v2].y,0xff0000);
            }

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            SDL_UpdateRect(screen, 0, 0, 0, 0); // Uaktualniamy ekran

            if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen);

            // Wymazujemy szkielet figury

            SDL_FillRect(screen, &r, 0);

            if(SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen);

            // Czekamy na klawisz ESCAPE

            if (SDL_PollEvent(&event))
                if ((event.type == SDL_QUIT) ||
                   ((event.type == SDL_KEYDOWN) &&
                    (event.key.keysym.sym == SDLK_ESCAPE))) waiting = 0;
        } while(waiting);
    }

    return 0;
}

Poeksperymentuj z tym programem, zmieniaj wartość d oraz span.

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl

W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe