Simple 3D Tutorial

Contributed by Talon
======================================================================5/26/2006===
                         Talons Simple 3D Tutorial
                   irc.webchat.org:6667 in #mIRC,#help.mIRC
==================================================================================

As a programmer, I often find myself trying to write things that already exist.
It gives some satisfaction to say, "I did this on my own." even if it is not as
good as what is already out there. Nothing is more impressive than your own work,
even if it is not as good. It's an accomplishment. I often times find myself making
things like this to settle the curious question, "How does this thing work?!".

This tutorial is targetted at the beginner wanting to get into 3D rendering without
using pre-made 3D engines. This is not by any means the only or best way to do 3D
graphics.

==================================================================================
Section 1: Vertices, Vertexes, what are they and how are they defined?
==================================================================================

A vertice is the location of a point on the X axis, Y axis, and Z axis. These are
!!NOT!! screen coordinates. the X, Y, and Z coordinates are defined by the
distance away from the center. Lets map a cube, which is 8 vertices. In the art
below, C represents the center, which is 0,0,0. The numbers show where each vertice
is located on the cube.

1------4
| \    |\
| 5----|-8
| |  C | |
2-|----3 |
 \|     \|
  6------7

Vertice 1 = -50 -50 -50
Vertice 2 = -50 50 -50
Vertice 3 = 50 50 -50
Vertice 4 = 50 -50 -50
Vertice 5 = -50 -50 50
Vertice 6 = -50 50 50
Vertice 7 = 50 50 50
Vertice 8 = 50 -50 50

Vertexes are a map to a screen coordinate, this is in X,Y. Lets assume our drawing
screen is 640x480. We want to know where Vertice 1 is on the screen. Well to
do this we need to do a Perspective Transform (which will be elaborated in the
next section) to convert a 3d point to a 2d point on our screen. both 500's are
a zscale, to avoid the nasty complication of z = 0.

xp = -50 * 500 / (-50 + 500)
yp = -50 * 500 / (-50 + 500)

so: 
xp = -55.555556
yp = -55.555556

Now, you might ask, why did we assume our screen size was 640x480? Simple, we want
to center our object on the screen, so we need 640/2=320 and 480/2=240

Vertex 1.x = 320 + -55.555556
Vertex 1.y = 240 + -55.555556

so:
Vertex 1.x = 264.444444
Vertex 1.y = 184.444444

we now know where at the 3d point is at relative to our view space which is only
2d. We have a 2d Coordinate. Obviously you need to round the x,y since a screen
coordinate cant be in decimals, its always an integer.

==================================================================================
Section 2: Perspective Transforms (Projections)
==================================================================================

All current 3D cards lack what is commonly referred to as a "Geometry Engine".
What this basically means is that they do not understand the concept of true 3D
graphics. It's up to you, the programmer, to use this component to alter the 
x & y values to give the illusion of 3D on a 2D screen.

This trickery is done by using a perspective transform to alter the x & y values
according to the z value. Basically, the further objects are from the viewer,
the smaller they become. This can be approximated by the following equation:

xp = x / z
yp = y / z

As you can see, as z gets larger, x & y get smaller (ie closer together). It's
fairly obvious that if z=0 then this equation will fail in a nasty manner.
This situation can be partially avoided by the following:

xp = x * scale / (z + scale)
yp = y * scale / (z + scale)

==================================================================================
Section 3: 3D Rotational Matrices
==================================================================================

There is not much to describe in this section. Below are our rotational matrices.
The next section will elaborate more on the use of these matrices, so just bounce
between this section and the next so you grasp the concept of what I am illustrating
to you.

--------------------------
|  Rotational X Matrix   |
--------------------------
1      0          0      0
0 Cos(Theta) -Sin(Theta) 0
0 Sin(Theta)  Cos(Theta) 0
0      0          0      1

--------------------------
|  Rotational Y Matrix   |
--------------------------
Cos(Theta)  0 Sin(Theta) 0
    0       1     0      0
-Sin(Theta) 0 Cos(Theta) 0
    0       0     0      1

--------------------------
|  Rotational Z Matrix   |
--------------------------
Cos(Theta) -Sin(Theta) 0 0
Sin(Theta)  Cos(Theta) 0 0
    0           0      1 0
    0           0      0 1


Theta is a degree, which is a number between 0 and 359. Why not 360 you ask?
Simple. 360 is a complete rotation, so 0 and 360 are the same thing.

Theta needs to be in radians. A simple method of converting degrees to radians is:

(Deg * PI) / 180
Example: (1 * 3.14159) / 180 = 0.017453

Some programming languages do not have PI built in, in this case, PI can easily be
calculated by:

PI = ATAN(1) * 4, or you could just set a constant, 3.14159

==================================================================================
Section 4: Multiplying a Matrix to a Vertice
==================================================================================

Ok, I am going to define a matrix call by "m". m1-1 will represent line 1, position
1. Here is a basic Chart, so you can follow along if you dont understand.

m1-1 m1-2 m1-3 m1-4
m2-1 m2-2 m2-3 m2-4
m3-1 m3-2 m3-3 m3-4
m4-1 m4-2 m4-3 m4-4

NewX = (x * m1-1) + (y * m1-2) + (z * m1-3) + m1-4
NewY = (x * m2-1) + (y * m2-2) + (z * m2-3) + m2-4
NewZ = (x * m3-1) + (y * m3-2) + (z * m3-3) + m3-4
w = (x * m4-1) + (y * m4-2) + (z * m4-3) + m4-4

Lets do an example: We will use a Vertice, and a X Matrix which we passed one
degree to:

Vertice:
x = -50
y = -50
z = -50

Matrix:
1    0         0     0 
0 0.999848 -0.017452 0 
0 0.017452  0.999848 0 
0    0         0     1

so, using our multiplication definition above:

NewX = (-50 * 1) + (-50 * 0) + (-50 * 0) + 0
NewY = (-50 * 0) + (-50 * 0.999848) + (-50 * -0.017452) + 0 
NewZ = (-50 * 0) + (-50 * 0.017452) + (-50 *  0.999848) + 0
w = (-50 * 0) + (-50 * 0) + (-50 * 0) + 1

so you should get this result:
NewX = -50 
NewY = -49.1198
NewZ = -50.865
w = 1

Because of our pre-defined rotational matrices, w will ALWAYS be 1, so this is
not important to calculate.

==================================================================================
Section 5: A Basic step by step description of how your program should work
==================================================================================

Ok, Lets assume you have all cube vertices stored, the first step in rotating our
cube is to multiply ALL the vertices by the rotational matrices. lets rotate on the
X, Y, and Z axis by one degree. Heres the steps of what your program should do.

Step 1: Calculate X Matrix by our degree (which needs to be converted to radians)
Step 2: Calculate Y Matrix by our degree (which needs to be converted to radians)
Step 3: Calculate Z Matrix by our degree (which needs to be converted to radians)
Step 4: Multiply our Vertice by the X matrix.
Step 5: Take the results, (new x,y,z) and multiply those by the Y matrix.
Step 6: Take the results, (new x,y,z) and multiply those by the Z matrix.
Step 7: Take the results, (new x,y,z) and do a perspective transformation.
Step 8: draw the Vertex on the screen. (our perspective transformation)
Step 9: Repeat step 4-8 for the other 7 vertices

Later, you should also save the vertexes, so you dont need to re-calc them
when you want to draw faces, which is in the next section.

==================================================================================
Section 6: Defining faces of our 3D object
==================================================================================

Ok, so we got our vertices, and we know how to project them on to our 2D screen.
Now we need to play connect the dots, and draw our faces. Lets go back to our cube
diagram so we can map our faces. as you know, a cube has 6 faces.

1------4
| \    |\
| 5----|-8
| |  C | |
2-|----3 |
 \|     \|
  6------7

Face 1 = 1 2 3 4 1 = Back
Face 2 = 2 6 7 3 2 = Bottom
Face 3 = 3 7 8 4 3 = Right
Face 4 = 1 5 6 2 1 = Left
Face 5 = 1 4 8 5 1 = Top
Face 6 = 8 7 6 5 8 = Front

Lets take Face 1, and define what these numbers mean! I will use "P" to define
a point. basically, we draw a line from P1 to P2, from P2 to P3, from P3 to P4,
and from P4 to P1 and we have one side!

The point is the vertex of a vertice, which is a perspective transformation.

==================================================================================
Section 7: REALLY CRAPPY EXAMPLE THAT WORKS! /render XAngle Yangle ZAngle
==================================================================================

alias multiplymatrix {
  var %x = $calc(($2 * $gettok($1,1,32)) + ($3 * $gettok($1,2,32)) + ($4 * $gettok($1,3,32)) + $gettok($1,4,32))
  var %y = $calc(($2 * $gettok($1,5,32)) + ($3 * $gettok($1,6,32)) + ($4 * $gettok($1,7,32)) + $gettok($1,8,32))
  var %z = $calc(($2 * $gettok($1,9,32)) + ($3 * $gettok($1,10,32)) + ($4 * $gettok($1,11,32)) + $gettok($1,12,32))
  return %x %y %z
}
alias Xmatrix {
  var %sin $sin($1).deg
  var %cos $cos($1).deg
  return 1 0 0 0 0 %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1
}
alias Ymatrix {
  var %sin $sin($1).deg
  var %cos $cos($1).deg
  return %cos 0 %sin 0 0 1 0 0 $calc(0 - %sin) 0 %cos 0 0 0 0 1
}
alias Zmatrix {
  var %sin $sin($1).deg
  var %cos $cos($1).deg
  return %cos $calc(0 - %sin) 0 0 %sin %cos 0 0 0 0 1 0 0 0 0 1
}
alias render {
  if ($window(@Render3D) = $null) { window -p @Render3D -1 -1 640 480 }
  clear @Render3d
  var %xmatrix = $xmatrix($1)
  var %ymatrix = $ymatrix($2)
  var %zmatrix = $zmatrix($3)
  var %cx = $window(@Render3D).dw / 2 , %cy = $window(@Render3D).dh / 2

  var %Vert.1 = -50 -50 -50
  var %Vert.2 = -50 50 -50
  var %Vert.3 = 50 50 -50
  var %Vert.4 = 50 -50 -50
  var %Vert.5 = -50 -50 50
  var %Vert.6 = -50 50 50
  var %Vert.7 = 50 50 50
  var %Vert.8 = 50 -50 50

  var %x = 0
  while (%x < 8) { 
    inc %x
    tokenize 32 $eval($+(%,Vert.,%x),2)
    tokenize 32 $MultiplyMatrix(%XMatrix,$1,$2,$3)
    tokenize 32 $MultiplyMatrix(%YMatrix,$1,$2,$3)
    tokenize 32 $MultiplyMatrix(%ZMatrix,$1,$2,$3)
    var %CalcdX = $int($calc(%cx + ($1 * 500) / ($3 + 500)))
    var %CalcdY = $int($calc(%cy + ($2 * 500) / ($3 + 500)))
    hadd -m Render3D $+(Vertex.,%x) %CalcdX %CalcdY
  }
  loopexample
}

alias loopexample {
  var %tables = 1 2 3 4 1;2 6 7 3 2;3 7 8 4 3;1 5 6 2 1;1 4 8 5 1;8 7 6 5 8
  var %z = 0
  while (%z < 6) { inc %z
    var %table = $gettok(%tables,%z,59)
    var %x = $numtok(%table,32) - 1
    var %y = $numtok(%table,32)
    while (%x) {
      drawline @Render3D 9 1 $hget(Render3D,$+(Vertex.,$gettok(%table,%x,32))) $hget(Render3D,$+(Vertex.,$gettok(%table,%y,32)))
      dec %x
      dec %y
    }
  }
}
All content is copyright by mircscripts.org and cannot be used without permission. For more details, click here.