3D Transformations

Second part of The 3D Coding Blackhole tutorial series

Home at http://3Dblackhole.base.org

NOTE: Some part of functions and some members of structures are not
explained here, but we'll discuss them in other tutorials of the series.
They're usually displayed in totality for the sake of similarity between
the different tutorials and with the complete 3D engine.
---------------------------------------------------------------------------

   * Saving coordinates
   * Implementing a matrix system
   * Implementing a trigonometric system
   * Creating the transformation matrices
   * How to create perspective
   * Transforming objects

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:

   * Screen Coordinates: 2D Coo rdinates relative to the origin of the
     video display
   * Local Coordinates: 3D Coordinates relative to the origin of an object

But we must not forget intermediate coordinates, such as:

   * World Coordinates: 3D Coordinates relative to the origin of the 3D
     world
   * Aligned Coordinates: World Coordinates transformed so that the viewer
     position becomes the world origin

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.

  1. Initialize all the local coordinates of every vertices
  2. Set the global matrix to an identity matrix
  3. Scale the global matrix with the scaling of the object
  4. Rotate the global matrix with the angle of the object
  5. Translate the global matrix with the position of the object
  6. Multiply the local coordinates by the global matrix to get the world
     coordinates
  7. Set the global matrix to an identity matrix
  8. Translate the global matrix with the negative position of the viewer
  9. Rotate the global matrix with the negative angle of the viewer
 10. Multiply the world coordinates by the global matrix to get the aligned
     coordinates
 11. Project the aligned coordinates to get the screen coordinates

---------------------------------------------------------------------------
E-mail me at jerstlouis@videotron.ca
Back to Jerome St-Louis's Homepage
Back to The 3D Coding BlackHole

                                                  Last Updated: 08-24-1997

Discuss this article in the forums


Date this article was posted to GameDev.net: 7/16/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Matrices

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!