Copy Link
Add to Bookmark
Report

3D Transformations

DrWatson's profile picture
Published in 
atari
 · 27 Nov 2023

Second part of The 3D Coding Blackhole tutorial series

  • 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 Projectfunction 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.

  • Initialize all the local coordinates of every vertices
  • Set the global matrix to an identity matrix
  • Scale the global matrix with the scaling of the object
  • Rotate the global matrix with the angle of the object
  • Translate the global matrix with the position of the object
  • Multiply the local coordinates by the global matrix to get the world coordinates
  • Set the global matrix to an identity matrix
  • Translate the global matrix with the negative position of the viewer
  • Rotate the global matrix with the negative angle of the viewer
  • Multiply the world coordinates by the global matrix to get the aligned coordinates
  • Project the aligned coordinates to get the screen coordinates

Copyright © 1996-1998 Jerome St-Louis

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT