Ecere SDK

These tutorials, although still very informative and valuable, are somewhat obsolete and no longer supported.

Please visit ecere.org for my current cross platform Object Oriented, GUI & hardware-accelerated 3D Graphics technology.

3D Transformations

Second part of The 3D Coding Blackhole tutorial series

Saving coordinates

It's now time to start coding some starfield simulator... So what will be our fundamental structure, in which the description of every object will be stored? To answer this question, we'll ask another one: What are the types of coordinates we need? The most obvious are:

But we must not forget intermediate coordinates, such as:

Here are the fundamental basic structures:

typedef struct
{
   short x, y;
}_2D;

typedef struct
{
   float x, y, z;
}_3D;

And here comes the structure for a set of coordinates that we'll call vertex, since the word "vertex" refers to the meeting of two or more edges of a poyhedron. Our vertices can simply be regarded as vectors described with different systems.

typedef struct
{
   _3D Local;
   _3D World;
   _3D Aligned;
}Vertex_t;

Implementing a matrix system

We're gonna store all our matrices in 4x4 array of floating-point numbers. So we'll have this matrix when we'll need transfromations:

float matrix[4][4];

then we create some function to copy a temporary matrix to the global matrix:

void MAT_Copy(float source[4][4], float dest[4][4])
{
    int i,j;
    for(i=0; i<4; i++)
      for(j=0; j<4; j++)
         dest[i][j]=source[i][j];
} 

Quite simple? Now, the big part with matrix, multiplying two matrices together. It's now time to try to understand the big formula in the maths tutorial, with this code:

void MAT_Mult(float mat1[4][4], float mat2[4][4], float dest[4][4])
{
   int i,j;
   for(i=0; i<4; i++)
      for(j=0; j<4; j++)
         dest[i][j]=mat1[i][0]*mat2[0][j]+
                    mat1[i][1]*mat2[1][j]+
                    mat1[i][2]*mat2[2][j]+
                    mat1[i][3]*mat2[3][j];
}

Did you get it now?? Isn't it way clearer? Now let's do the product of a vector by a matrix:

void VEC_MultMatrix(_3D *Source,float mat[4][4],_3D *Dest)
{
    Dest->x=Source->x*mat[0][0]+
            Source->y*mat[1][0]+
            Source->z*mat[2][0]+
                      mat[3][0];
    Dest->y=Source->x*mat[0][1]+
            Source->y*mat[1][1]+
            Source->z*mat[2][1]+
                      mat[3][1];
    Dest->z=Source->x*mat[0][2]+
            Source->y*mat[1][2]+
            Source->z*mat[2][2]+
                      mat[3][2];
}

And here's your matrix system, working perfectly!

Implementing a trigonometric system

I know almost every C compiler in the world comes up with a maths library with trigonometric functions, but don't ever use them every time you need one simple sine!! The computation of sines and cosines is a multitude of factorials and divides! Build yourself trigonometric tables instead. First decide the number of degrees you want, then allocate some place to hold the values:

float SinTable[256], CosTable[256];

Then use a macro that will bother about putting every degree positive, and doing the wrap-around when it goes over the number of degrees, then return you the good value. If you use a number of degree which is a power of two, you can use a "&", which is much faster, instead of a "%" to do so. For a 256-degrees based trigonometric system:

#define SIN(x) SinTable[ABS((int)x&255)]
#define COS(x) CosTable[ABS((int)x&255)]

Once you've defined everything, create an initializing function that you will call at the beginning of your program:

void M3D_Init()
{
   int d;
   for(d=0; d<256; d++)
   {
       SinTable[d]=sin(d*PI/128.0);
       CosTable[d]=cos(d*PI/128.0);
   }
}

Creating the transformation matrices

Now here is what the transformation matrices look like, written in C:

float mat1[4][4], mat2[4][4];

void MAT_Identity(float mat[4][4])
{
    mat[0][0]=1; mat[0][1]=0; mat[0][2]=0; mat[0][3]=0;
    mat[1][0]=0; mat[1][1]=1; mat[1][2]=0; mat[1][3]=0;
    mat[2][0]=0; mat[2][1]=0; mat[2][2]=1; mat[2][3]=0;
    mat[3][0]=0; mat[3][1]=0; mat[3][2]=0; mat[3][3]=1;
}

void TR_Translate(float matrix[4][4],float tx,float ty,float tz)
{
   float tmat[4][4];
   tmat[0][0]=1;  tmat[0][1]=0;  tmat[0][2]=0;  tmat[0][3]=0;
   tmat[1][0]=0;  tmat[1][1]=1;  tmat[1][2]=0;  tmat[1][3]=0;
   tmat[2][0]=0;  tmat[2][1]=0;  tmat[2][2]=1;  tmat[2][3]=0;
   tmat[3][0]=tx; tmat[3][1]=ty; tmat[3][2]=tz; tmat[3][3]=1;
   MAT_Mult(matrix,tmat,mat1);
   MAT_Copy(mat1,matrix);
}

void TR_Scale(float matrix[4][4],float sx,float sy, float sz)
{
   float smat[4][4];
   smat[0][0]=sx; smat[0][1]=0;  smat[0][2]=0;  smat[0][3]=0;
   smat[1][0]=0;  smat[1][1]=sy; smat[1][2]=0;  smat[1][3]=0;
   smat[2][0]=0;  smat[2][1]=0;  smat[2][2]=sz; smat[2][3]=0;
   smat[3][0]=0;  smat[3][1]=0;  smat[3][2]=0;  smat[3][3]=1;
   MAT_Mult(matrix,smat,mat1);
   MAT_Copy(mat1,matrix);
}

void TR_Rotate(float matrix[4][4],int ax,int ay,int az)
{
   float xmat[4][4], ymat[4][4], zmat[4][4];
   xmat[0][0]=1;        xmat[0][1]=0;        xmat[0][2]=0;        xmat[0][3]=0;
   xmat[1][0]=0;        xmat[1][1]=COS(ax);  xmat[1][2]=SIN(ax);  xmat[1][3]=0;
   xmat[2][0]=0;        xmat[2][1]=-SIN(ax); xmat[2][2]=COS(ax);  xmat[2][3]=0;
   xmat[3][0]=0;        xmat[3][1]=0;        xmat[3][2]=0;        xmat[3][3]=1;

   ymat[0][0]=COS(ay);  ymat[0][1]=0;        ymat[0][2]=-SIN(ay); ymat[0][3]=0;
   ymat[1][0]=0;        ymat[1][1]=1;        ymat[1][2]=0;        ymat[1][3]=0;
   ymat[2][0]=SIN(ay);  ymat[2][1]=0;        ymat[2][2]=COS(ay);  ymat[2][3]=0;
   ymat[3][0]=0;        ymat[3][1]=0;        ymat[3][2]=0;        ymat[3][3]=1;

   zmat[0][0]=COS(az);  zmat[0][1]=SIN(az);  zmat[0][2]=0;        zmat[0][3]=0;
   zmat[1][0]=-SIN(az); zmat[1][1]=COS(az);  zmat[1][2]=0;        zmat[1][3]=0;
   zmat[2][0]=0;        zmat[2][1]=0;        zmat[2][2]=1;        zmat[2][3]=0;
   zmat[3][0]=0;        zmat[3][1]=0;        zmat[3][2]=0;        zmat[3][3]=1;

   MAT_Mult(matrix,ymat,mat1);
   MAT_Mult(mat1,xmat,mat2);
   MAT_Mult(mat2,zmat,matrix);
}

How to create perspective

How can you create the illusion of something seeming close to you, and something far away on a 2D screen?? The question of perspective has always been a problem for artists and programmers. Different methods has been used. But as strange as it can seem, the most realistic one only consist in a simple division. Here's the formula we will use to project our 3D world on a 2D screen: P( f ):(x, y, z)==>( fx / z + XOrigin, fy / z + YOrigin )
Here f is the "focal distance". It represents the distance from the viewer to the screen, and is usually between 80 and 200. XOrigin and YOrigin are the coordinates of the center of the video display. You want to know what a Project function would look like?

#define FOCAL_DISTANCE 200
void Project(vertex_t * Vertex)
{
   if(!Vertex->Aligned.z)
	Vertex->Aligned.z=1;
   Vertex->Screen.x = FOCAL_DISTANCE * Vertex->Aligned.x / Vertex->Aligned.z + XOrigin;
   Vertex->Screen.y = FOCAL_DISTANCE * Vertex->Aligned.y / Vertex->Aligned.z + YOrigin;
}

You like the line z = 1?? I hate divides by zero... They're everywhere...

Transforming objects

Now that you have all the necessary tools to transform vertices, you should know the main steps you need to execute.

Copyright © 1996-1998 Jerome St-Louis

www.ecere.com