issue #7

3d: Part II

By MA SNART

This time: translation and rotation across 2d planes...

Introduction to Translation and Rotation...
Welcome back to part 2 of my 'Introduction to 3D graphics' series of articles. Last time I told you of the different 'spaces' that exist in the typical 3D graphics system: Object, World and Camera. I also wrote that 3D models, residing in Object space, must be 'transformed' to World space, and then further 'transformed' into Camera space in order to view them. However, I didn't cover how to perform the 'transformation'. So, that is what I will explain in this article. However I won't cover a real 3D type system this time, but rather a 2D system like that of the 'Asteroids' game. This is because I feel that the 3D calculations would overly confuse many of you. Fear not, though, as I will use what I present here as a foundation to build off of when it's time to add the other 2 axis into the equation.

 
 

First off I will need to describe what it is I'm trying to do here:

Do you remember the old arcade game 'asteroids'?. Well, guess what? That is exactly what I hope you could make after reading this article! Well, not exactly; as I'm only going to get you going with how the 'player's ship' works. You'll have to work out the rest. So the code that follows [once you type it into the IDE] will display a small ship that you can rotate and move around, just like the 'Asteroids' ship :). Please NOTE that I wrote the code without testing [so it may not work :( ] and it isn't optimized and is very un-structured...Sorry!

Because this is only [for the time being] going to be 2D, we will only need to concentrate on 2 dimentions: The X and Y [just like regular sprite graphics :)]. What this also means is that all of our rotations will be along the Z-axis: there isn't going to be a test on this, but keep this in mind for later articles. Our points are in the X and Y plains and we are rotating along the Z-axis.

The beginning
Fire up QB1.1 (or 4.5) and type the following in:

DEFINT A-Z
SCREEN 7,0,1,0
WINDOW (-100,-100)-(100,100)

First of we are defining all variables beginning A-Z as integers [helps speed things up!]. We are then going to enter into SCREEN 7 mode [moan all you want...I can't hear ya] and the extra 0,1,0 stuff tells QB that we are using the default setting [the first 0] and the active page is number 1 [this is where all the drawing will happen] and finally that the visible page is number 0 [this is what you will see on the screen]. I hope none of that was very new to you :)

The last line will put the display into a 'window', or what you can see, on screen, is what is visible within the range of numbers passed. Basically what this does is re-number the screen. If you were to PSET a pixel at 0,0 it would now show up at the center of the screen; likewise, if you PSET to -100,-100, it would show up in the upper-left corner [as if you had PSET at 0,0]. This is very handy in that it turns the screen into a cartesian graphing system: From the screen center -Y travels up, +Y down, -X travels left and +X right. This is the same type of system used in real 3D graphics [It also has the side benefit of negating the need to 'transform' World space to Camera space...more on this later].

Now enter this into QB:

TYPE pntdat
x AS INTEGER
y AS INTEGER
END TYPE

TYPE lndat
v1 AS pntdat
v2 AS pntdat
col AS INTGER
END TYPE


TYPE entdat
loc AS pntdat
vect AS pntdat
ang AS INTEGER
END TYPE

These are going to be our custom variable classes...
The first one 'pntdat' stands for POINT DATA. It contains the location of a point by measuring along the X and Y axis. This type will be used by the others [so pay attention!] Type 'lndat' stands for LINE DATA. These have v1 and v2 [both meaning VERTACY] of 'pntdat' type data. These represent the end points of a line. 'col' stands for COLOR...er...what color the line is...[each line is used in Object space]. Finally, we have type 'entdat' which means ENTITY DATA. It contains 'loc' [LOCATION] a 'pntdat' type that indicates the current position of the ENTITY. 'vect' [VECTER] indicates in what direction the ENTITY is moving [also a 'pntdat' type]. 'ang' [ANGLE] indicates at what angle the entity is facing [remember ENTITIES exist in World space...and they use Objects to define them].

Next stuff:


DIM lns(4) AS lndat
DIM player AS entdat

Now we're getting to the meat of the proggy. 'lns' [LINES] stores the model of the ship, and 'player' stores our only active ENTITY...

Now enter:


FOR i=1 TO 4

READ LNS(i).v1.x
READ lns(i).v1.y
READ lns(i).v2.x
READ lns(i).v2.y
READ lns(i).col

NEXT i


DATA 0,-9,4,4,15
DATA 4,4,0,1,7
DATA -4,4,0,1,7
DATA 0,-9,-4,4,15

What this will do is enter our space ship into the space provided by the 'lns' array. The ship model, of course, exists in the DATA statements [all values are in relation to Object space's reference point [or 0,0]].

The main-loop:
Now type in:

DO

LINE (-100,-100)-(100,100),0,BF

First we set-up our DO/LOOP routine. Then we draw a black box to erase the screen [remember that the top-left corner of the screen is at -100,-100 and the bottom right is at 100,100]

Now comes the fun part....

co! = COS( player.ang * 3.141593 / 180)
si! = SIN( player.ang * 3.141593 / 180)

What we do here is get the SINE and COSINE of the angle indicating the direction that the player is facing. In order to do that we must convert the integer angle measurement into radians [ you do that by multiplying by PI then dividing by 180 :) ].

Here we go again...

FOR i = 1 TO 4

rx1 = lns(i).v1.x * co! + lns(i).v1.y * si!
ry1 = lns(i).v1.y * co! - lns(i).v1.x * si!

rx2 = lns(i).v2.x * co! + lns(i).v2.y * si!
ry2 = lns(i).v2.y * co! - lns(i).v2.x * si!

There you have it: the rotation. Each end-point of the line [indicated by v1 and v2] is rotated around the reference point of Object space [again at 0,0]. This formula is actually the same as a z-axis rotation in a true 3D system. The formula explained:

rotated_x = ( original_x * cosine_of_angle ) + ( original_y * sine_of_angle )

rotated_y = ( original_y * cosine_of_angle ) - ( original_x * sine_of_angle )

The reasoning behind how it works isn't a subject for this article, but at this point in time, rotated_x and y are STILL in Object space. If it was transferred to World space it would always be located at the World space reference point [0,0]. So what we will do is add the VECTOR indicated by the player's location to each end-point:

type away:

fx1 = rx1 + player.loc.x
fy1 = ry1 + player.loc.y

fx2 = rx2 + player.loc.x
fy2 = ry2 + player.loc.y

Now at this point the line has been 'transformed' to World space, but because the Camera doesn't move and the World space has already been transformed by the WINDOW statement earlier, we can draw the line now.

So type:

LINE (fx1,fy1)-(fx2,fy2),lns(i).col

NEXT i

Alright!...there you go...one Object transformed to World space!...

Now type in:

SELECT CASE INKEY$

CASE CHR$(27)
quit = 1

CASE CHR$(0)+CHR$(75)
player.ang = player.ang-3

IF player.ang < 0 THEN player.ang = player.ang + 360

CASE CHR$(0)+CHR$(75)
player.ang = player.ang+3

IF player.ang > 360 THEN player.ang = player.ang - 360

CASE " "

vx! = (-.3) * si!
vy! = (-.3) * co!

player.vect.x = player.vect.x + vx!
player.vect.y = player.vect.y + vy!

END SELECT

Okay that's our 'get key press' routine. The 'ESC' key will quit the demo, the left arrow will cause the angle to decrease, the right will increase the angle, and 'SPACE BAR' will make the ship thrust forward [I will explain this formula at a later time].

Now type:

player.loc.x = player.loc.x + player.vect.x
player.loc.y = player.loc.y + player.vect.y

IF player.loc.x > 100 then player.loc.x = player.loc.x - 201
IF player.loc.x < -100 then player.loc.x = player.loc.x + 201

IF player.loc.y > 100 then player.loc.y = player.loc.y - 201
IF player.loc.y < -100 then player.loc.y = player.loc.y + 201

This will enable the ship to have 'zero gravity' effects. If you constantly add the ENTITIES vector back into it's location, then the next bit of code will 'wrap' the ship around to the other side of the World space.

Finish the proggy with:

PCOPY 1,0

LOOP UNTIL quit = 1

END

Okay that SHOULD give you a little white ship to move around on the screen [and hopefully a bit of knowledge of translation and rotation]. Just so you can remember: To 'transform' from Object to World space, you rotate THEN transform [try switching the rotate and translate order to see what happens :) ]. This is NOT a rule written in stone however, BUT for this series of articles, keep it in mind!

Next time I'll add multiple ENTITIES to the mix and transform the World to Camera space as well...see ya!

 

Back to Top





This tutorial originally appeared in QBasic: The Magazine Issue 7.