third dimension math calulcation

86
Entering the 3d dimension... Hi there! Relsoft again back with an article for you. :*). This article is the first of a series of 3d articles that I'll be serializing in QBASICnews.com . I don't know how far I could take you or how many chapters will I make. It depends upon the user feedback and my free time. ;*) I'm most likely to cover a lot of things that after you've read the whole series, you're likely to be able to make your own FPS render, a 3d strategy game or even a Ragnarok style engine. ;*) I. Course outline What I will be covering in this series are listed below: 1. 3d Projection a. Theory b. Camera c. Translation 2. 2d and 3d rotations a. Sin/Cos b. Polar coordinates c. Proof of rotation d. Transformation e. 3d Optimization 3. 3d coordinate systems a. Cartesian b. Spherical c. Cylindrical d. 3d Model generation e. Polygon 101 4. Polygon fills a. Vectors b. wireframe c. Flat 5. Normals and lightsourcing a. More on Vectors b. Cross product c. Dot Product Lambert shading Gouraud shading Phong shading d. Moving Lightsource e. Multiple Light

Upload: ozzy75

Post on 28-Dec-2015

25 views

Category:

Documents


10 download

DESCRIPTION

For game programming in qbasic

TRANSCRIPT

Page 1: Third dimension math calulcation

Entering the 3d dimension...

Hi there! Relsoft again back with an article for you. :*). This article is the first of a series of 3d articles that I'll be serializing in QBASICnews.com. I don't know how far I could take you or how many chapters will I make. It depends upon the user feedback and my free time. ;*) I'm most likely to cover a lot of things that after you've read the whole series, you're likely to be able to make your own FPS render, a 3d strategy game or even a Ragnarok style engine. ;*)

I. Course outline What I will be covering in this series are listed below:

1. 3d Projection a. Theory b. Camera c. Translation 2. 2d and 3d rotations a. Sin/Cos b. Polar coordinates c. Proof of rotation d. Transformation e. 3d Optimization 3. 3d coordinate systems a. Cartesian b. Spherical c. Cylindrical d. 3d Model generation e. Polygon 101 4. Polygon fills a. Vectors b. wireframe c. Flat 5. Normals and lightsourcing a. More on Vectors b. Cross product c. Dot Product Lambert shading Gouraud shading Phong shading d. Moving Lightsource e. Multiple Light

Page 2: Third dimension math calulcation

f. TextureMapping 6. Multiple objects a. Sorting Methods b. Visibility check c. Depth-Buffering 7. Designing a 3d game engine a. Camera as a vector b. Matrices 8. I don't know yet. ;*)

II. Introduction

The purpose of this article is to try to explain the reasons behind 3d projection and a little on 2d rotation. 3d is only as hard, or as easy, as you want it to be. Don't be afraid as I'll take you to the world of 3d step by step. What you need to be able to run the sample programs that I will be throwing from time to time is any flavor of QuickBASIC(QBASIC,QB4.5,7.1, etc). A little experience in algebra and Trig is also a plus but not necessary. I will also try to explain optimization techniques as we go along the whole series. ;*)

III. 3d cartesian coordinate system

The 3d cartesian coordinate system is almost like the 2d cartesian coordinate system that we grew up with, only with an extra dimension: The Z axis. *There are several other 3d coordinate systems like spherical and cylindrical. I will explain them to you in detail in future issues, but when I talk 3d coordinates for now, its the cartesian coordinate system unless specified. ie. a. 2d p(x,y) b. 3d P(x,y,z) But how do we define a 3d coordinate? Where does the z-axis go? As we know already, in 2d coordinate, the x-axis is

Page 3: Third dimension math calulcation

going to the right and the y-axis is going up. The 2 axes(plural for axis) intersect at p(0,0). Read as "Point 0,0" where the first value is the x(abscissa) and the second value is the y(ordinate). P(0,0) is also called the "Origin". They are also PERPENDICULAR to each other. Perpendicular means a line,plane or a ray(vector) which has a union of 90 degrees. Meaning they form a "+" when they intersect.

See how all the angles(1,2,3,4) are all 90 degrees? That's the essence of perpendicularity. Also be sure that you understand this concept as perpendicularity is used on almost all things that got to do with 3d. *Perpendicular lines/Planes/rays are also called "orthogonal". There is not that much difference in 3d, all the axes are perpendicular to each other. ie: Z axis is perpendicular to the XY Plane, Y axis is perpendicular to the XZ plane as the X axis to the YZ plane. Now how about the directions the axes go? Well, there are two ways to define a 3d system. The "RIGHT-HANDED" and the "LEFT-HANDED" systems. The choice is yours to make, or me in this case because I'm the one writing this article.

a. Left handed system(Fig. 2) The left-handed system means that when increased:

Page 4: Third dimension math calulcation

x goes right y goes Up z goes into the screen (away from you) b. Right Handed system(Fig. 1) When increased: x goes right y goes up z goes out of the screen (Into you) Since most books use the right handed system, I'll use that system. Another reason is that the coordinates when plotted on the screen, resembles a real-world system. Hey, I'm right handed. ;*)

IV. 3d to 2d projection.

As you might have guessed, QB has no PSET3d or LINE3d routine so we have to make one. :*) The beauty of learning the theories and principles behind how things work is that you won't get lost at discussions on forums. :*) So let me start by the principle: Normal way: "The farther the thing from the viewer the smaller it gets" Jocke's way: "I'm gonna kick this ball so far you won't be able to see it." Math way: "Distance is inversely proportional to the size of an object" Trying to make an equation using Jocke's or the English statement would be very hard. So we'll use the Math way:

Size=1/distance so: Newsize=Size/Distance assume: a. OrigSize = 100 Distance = 1 = 100/1

Page 5: Third dimension math calulcation

= 100 b. Origize = 100 Distance = 50 = 100/50 = 2

*This is just an approximation. Just to show you the relationship of size and distance. Now you would want to project and object but how do we do it with the knowledge that we have already learned? Well, First, we have to decide where z =0 is. Turns out that a good way to define z=0 is a number of units away from you. Which means that you can see it when you look at your monitor. A good value is 256. Why 256? Well, this is not entirely the law since you could make it as small or as big as you want it to be, but 256 is a good multiplier(or any power of 2 value) as you will see later. Secondly, where to put the monitor in our 3d space. Think of your monitor as a "camera" that points onto the 3d space and the screen as the LENS(camera lens) perpendicular to the z-axis(Yes, your XY plane). Since (0,0,0) or z=0 is at a distance of 256 looking at the negative direction, our Lens should be 0+256. So that the coordinate of our lens is (0,0,256). Anything more than 256 is behind the camera and should not be plotted. Remember that we are looking on the negative z in right-handed systems.

And why did we use 256? Seasoned programmers would know that 256 is a power of two. Since you can use shifts to multiply and divide, you could make your renders faster by miles as shifts are way faster than divs or muls. ;*) In screen 13, the dimensions of the screen is 320*200 and its center is (160,100). We know that at z =0, the relationship of each x,y,z units is that x and y is one unit wide. So plotting (8,5,0):

Distance= 256 screenx = 160+x screeny = 100-y (the screen y-wise is reversed)

Page 6: Third dimension math calulcation

Then: Screenx = 160 + 8 = 168 Screeny = 100 - 5 = 95 Pset(168,95), col

How about if z = 128? (8,5,128) then: distance = 256 -128 = 128 128 is nearer which means the size of the units should increase. But how much? Since 128 is half of 256, our units should be 2x(twice) the size of the units at z = 0. so.. screenx = 160+x*2 = 160+8*2 screeny = 100-y*2 = 100-y*2 Then: Screenx = 160 + 16 = 176 Screeny = 100 - 10 = 90 Pset(168,95), col

Pretty easy huh? Putting it all together, the projection would look like this: Distance= LENS-z screenx = xcenter+(LENS*x/Distance) screeny = ycenter-(LENS*y/Distance) Now let me explain how each component affects the whole projection formula:

1. Lens We know that LENS, the lens of our camera or monitor in this case, is a multiplier to give your projection a field of view(FOV) and since the camera is 256 units away from (0,0,0) we would want the value of our lens to have a direct correlation with distance. eg: z = 0 Distance = 256-0 = 256 Lens = 256 x = 8 xcenter = 160 =(256*8/256)+160 =168

Page 7: Third dimension math calulcation

(See the relationship already?) * some people use a value of Lens=1 so that it weeds out 2 muls or shifts in the actual projection fomulas but in my experience, the objects does not look "natural".

2. Distance This is just how far a 3d pixel is away from the camera. Since we look in the negative direction, "The farther the distance, the smaller the z value. ie. p(0,0,-100) is father than p(0,0,100). Let us see if this holds true in equation form. a. (0,0,-100) Distance = 256-(-100) 'distribute the [-] sign: Distance = 256+100 Distance = 356 b. (0,0,100) Distance = 256-(+100) Distance = 256-100 Distance = 156 Ahem! 356>156. ;*) What about z=>256 or distance is 0 or less? Well, unless you want to poke yourself in the eye, you wouldn't want to plot em. ;*) Plotting at distance=0 is technically correct but You had to change your projection formula because n/0 is undefined. And in geometry, "Distance is always positive :*)" Here's the formula:

Distance = 0 screenx = xcenter + (LENS*x) screeny = ycenter - (LENS*y)

To test your intelligence, I'll let you think about it yourself. ;*) FINAL PROJECTION EQUATIONS!!! Distance = LENS-z screenx = xcenter+(LENS*x/Distance) screeny = ycenter-(LENS*y/Distance)

Page 8: Third dimension math calulcation

Now let's see if the projection equations would return the same values for (8,5,128): Remember that we returned x=176, y=90) Distance = 256-128=128 screenx = (256*8/128) + 160 screeny =-(256*5/128) + 100 = screenx = (2048/128) + 160 screeny =-(1280/128) + 100 = screenx = (16) + 160 = 176 screeny =-(10) + 100 = 90 Ahem...;*)

V. Translation Translation is just movement of a point from one location to another. To simplify things, I put the translation coords in the form of a camera offsets, camx,camy,camz. Moving the location of the point is just as simple as adding or subtracting values to the camera offsets and subtracting those components; x,y & z from p(x,y,z) ie: Xtranslated= x - camx Ytranslated= y - camx Ztranslated= z - camx

VI. Putting it in action Now for the fun part, plotting!. Let's start by the simplest of all "models"(When I say models I mean an array of points that define a 3d object), the plane. In this case we want to plot a grid of 16*16 plane. As we would want the grid to be centered at x=0 and y=0, the starting x and y values of our grid is negative. We also would want to start at z=0 adding an increment(20) for every y-loop. We also would want to scale the distance between each point, in this case 4.

QBcode: Gsize = 16 size% = Gsize * Gsize '16 * 16 grid DIM SHARED Plane(size% - 1) AS Point3D 'dim out plane

Page 9: Third dimension math calulcation

Scale = 4 'scale factor 'change to a smaller if you want to 'reduce the size. z = 0 'start 256 units away from screen i = 0 'index for pixels HalfSize = Gsize \ 2 '1/2 of our grid for centering FOR y = HalfSize - 1 TO -HalfSize STEP -1 'loop through it FOR x = HalfSize - 1 TO -HalfSize STEP -1 'and calculate each coord Plane(i).x = x * Scale 'make the model bigger Plane(i).y = y * Scale Plane(i).z = z i = i + 1 'increment array index NEXT x z = z + 20 'go out into the screen 20 units every line. NEXT y

Now to project it, 1. start 2. read pixel at location i 3. translate the pixel using p(x,y,z) - cam(x,y,z) 4. project each pixel 5. Plot 6. If I<maxpoints go to start else end QBCode:

FOR i = 0 TO UBOUND(Plane) sx! = Plane(i).x - camx% 'coords sy! = Plane(i).y - camy% 'sub tracted by the sz! = Plane(i).z - camz% 'camera 'we can still directly subtract 'camera offsets to our original 'coords as we are not rotating yet. ;*) Distance% = (LENS - sz!) 'get Distance IF Distance% > 0 THEN 'if dist>>0 then 'Projection formula x% = XCENTER + (LENS * sx! / Distance%) y% = YCENTER - (LENS * sy! / Distance%) PSET (x%, y%), 15 'Draw each star ELSE 'do nothing 'you wouldn't wan't to

Page 10: Third dimension math calulcation

'divide by 0 would ya? :*) 'and in geometry, distance is 'always positive. ;*) END IF NEXT i Now here's the example file of a projected plane: (camera is controlled by AZSXDC) Project.BAS You can even project texts: PROJCHAR.BAS Here's how you can apply the projection equations to a starfield: Projstar.bas

VII. Using sprites instead of pixels Pixels alone tend to be boring after a while. So why not use sprites? Considering we also have to project the size of the sprite or tile, we can't use the normal QB PUT routine, so we have to make a stretch sprite routine for this purpose alone. The algo behind the stretch sprite routine is pretty trivial so I won't explain it here in detail. All you have to remember is that you could zoom or pan on a sprite depending on the parameters, NewHeight and NewWidth. For the actual algo in calculating the new dimensions, here's the formula: NewHeight =OldHeight * LENS/Distance% NewWidth =OldWidth * LENS/Distance% OldWidth and OldHeight are the actual dimensions of the sprite. Ie. If you GET(0,0)-(15,15),Array then the size of the sprite is 16*16. So OldHeight = 16 and OldWidth =16. Distance is the same distance in out projection equations. Same with the LENS. I'll let you figure out the rationale behind the equations yourself. :*)

Page 11: Third dimension math calulcation

Here are some sample files: Meteor.bas Stars.bas Hope you've learned something from this. The best way to learn though is to just play with the values, running the program and see the effect of the changed values. Next time, I will teach you 2d and 3d rotations, polar coordinates, other forms of transformation besides translation, optimizations of 3d rotations using constants, and probably if space will provide 3d model generation(The math way). So you might want to read on:

1. Trig Functions Sin and Cos only 2. Trig Identities Cos/Sin addition laws 3. Right Triangle relationships in Trig functions 4. Polar to cartesian coordinate conversion. *Don't worry even if you don't know a thing about those things I mentioned because I will be teaching you all of those next issue as if you're am 8 year-old kid. ;*) So until next time, Relsoft signing out. Happy coding!!!! Relsoft 2004 [email protected] http://rel.phatcode.net/

Rotations, the how's and why's...

I. Introduction I bet you felt very annoyed by the fact that I only explained projection on my first article right? Well, the series is primarily geared to coders who had no experience in 3d coding and to advance one's knowledge regarding 3d in general. This time around, I will be explaining to you 2d and 3d rotations. "2D rotation in a 3d article?!!! Are you out of your mind?!!!" Hardly, in fact, 2d rotation is the basis of 3d rotation as you will know later. But before I could discuss

Page 12: Third dimension math calulcation

rotations to you, let me start by some basic intermediate and trigonometric math. Don't worry, this is not as hard as you might think. So prepare yourself for some street math. ;*)

II. The polar coordinate system Up to this point, we have used the cartesian coordinate system in 2d or in 3d. Coordinates in these systems are defined as either p(x,y) or p(x,y,z). In the polar coordinate system however, the ordered pair is not represented by x or y but of r and angle originating from the origin or the pole, which is the center of the coordinate system. Given an angle Theta and a radius r the ordered pair would be written as: p(r,theta). r represents the distance from pole, and theta is the measure of the angle from the positive x-axis.

So in the polar system, we only need the length(r), sometimes called the magnitude, and the angle from the positive x-axis. Why discuss polar system when the monitor is best suited for a cartesian system? The answer is that some things can be easily done in the polar coordinate system. And one of those things is "rotation" ;*)

Page 13: Third dimension math calulcation

III. The basic trigonometric functions and their relationship to the Polar and Cartesian systems... There are six basic trig functions. The sine, cosine, tangent, secant, cosecant, and the cotangent. As of the moment, we are interested in just 2, the SINE and COSINE.

Say you have the unit circle above(a unit circle is a circle having a radius of 1), with an angle(theta) at 45 degrees. I already drew the right triangle for you labeled as Y or O(Opposite side), X or A(adjacent side) and r or H(Hypoteneuse. In Trigonometry, there is a mnemonic called the "SOH-CAH-TOA" which roughly means: SOH = Sin(Theta) = Opposite/Hypoteneuse CAH = Cos(Theta) = Adjacent/Hypoteneuse TOA = Tan(Theta) = Take a wild guess. :p Translating it to x,y and r..

sin(theta)=y/r cos(theta)=x/r

Page 14: Third dimension math calulcation

As I said we only need SIN and COS for now. Multiplying both sides by r...

r(Sin(Theta) = y/r)r r(Cos(Theta) = x/r)r = r*(Sin(Theta) = y r*(Cos(Theta) = x = EQ. 1 x = r * cos(Theta) EQ. 1-1 y = r * sin(Theta) Since on a unit circle r = 1 then x = 1 * cos(Theta) y = 1 * sin(Theta) or EQ. 2 x = cos(Theta) EQ. 2-1 y = sin(Theta) By now you should already have realized that Sine has something to do with the y coordinate and Cosine to the x coord. ;*) Now how do we convert from polar to cartesian? Easy, as long as you know the radius and the angle(theta) just pluck the values to EQ's 1 and 1-1. ie:

x = r * cos(Theta) y = r * sin(Theta) Pset(x,y)

Here's and example file: PolRot.Bas

Page 15: Third dimension math calulcation

To change form polar to cartesian: r = Sqr(x^2 + y^2) Theta = ATN(y/x); x<>0 *These 2 would be useful later on but keep it on the sidelines for now. ;*) Before forget, all the other trig functions can be derived from the SIN and COS function. Tan(a) = Sin(a)/Cos(a) Sec(a) = 1/Cos(a) Csc(a) = 1/Sin(a) Cot(a) = 1/Tan(a) = Cos(a)/Sin(a)

IV. Degrees and Radians Okay, this is very important so listen closely. We, as students are used with the degree measurement of angles. Probably because degrees are easy to visualize, so our teachers and beginners math books use it. But it turns out that computer languages, BASIC included, cannot directly accept degree measure in their built in trig functions. Why? Frankly, I don't know. Maybe because radians is an exact measure or the implementors just want to be cooler. :*) Now, since QB won't let you pass degrees to their built-in trig functions, and radians is sometimes a pain to implement(due to the fact that it's a small value), we have to use degree measurement and converting it to radian measure before passing it to the functions. To convert:

1. Degrees to Radians Radians = Degrees*PI/180 2. Radians to Degrees Degrees = Radians*180/PI *PI is a value of the circumference of a circle divided by its diameter. Its actual value is 3.141593... Fun fact: Pi is 180 degrees. Guess what 2*PI is? :*) Fun fact: You can easily calculate PI by PI=ATN(1)*4 V. 2d Rotation

Page 16: Third dimension math calulcation

Using the polar system to rotate a point around the center is pretty easy. But how about rotation from the point's coordinate itself? This is where our 2d coordinate rotation comes from. Rotating from p(x,y) to p(x',y'):

x' = x*cos(Theta) - y*sin(Theta) y' = y*cos(Theta) + x*sin(Theta) Where: x = original x coord y = original y coord x' = rotated x coord y' = rotated y coord But how did those equations came about? Most articles just smack you into these equations and never look back on how those came to be. I bet some of them doesn't know how to derive it themselves. :*). And because I'm different, I will teach you how they came to be. Moreso, you could impress your friends by your geekiness when you tell them you know. :*) V-a. Proof on how the 2d rotation is derived. Remember these equations?

Page 17: Third dimension math calulcation

EQ. 1 x = r * cos(Theta) EQ. 1-1 y = r * sin(Theta) Yep they are the Polar to Cartesian coordinate system conversion. :*) We also need the Angle addition identities

Legend: P = Phi T = Theta Cosine Identity: EQ. 3 Cos(P+T)= Cos(P)*Cos(T)-Sin(P)*Sin(T) Sine Identity: EQ. 3-1 Sin(P+T)= Sin(P)*Cos(T)+Cos(P)*Sin(T) Let (P+T) = Theta(Just one angle)... EQ. 1 becomes: x = r * cos(P+T) EQ. 1-1 y = r * sin(P+T)

Page 18: Third dimension math calulcation

Then by substitution from EQ 1 and 1-1 EQ. 1 becomes: x' = r * (Cos(P)*Cos(T)-Sin(P)*Sin(T)) EQ. 1-1 becomes: y' = r * (Sin(P)*Cos(T)+Cos(P)*Sin(T)) Distributing r: x' = r*Cos(P)*Cos(T) - r*Sin(P)*Sin(T) y' = r*Sin(P)*Cos(T) + r*Cos(P)*Sin(T) And looking back at EQ's 1 and 1-1: Let P = Theta... x = r * cos(P) y = r * sin(P) Then by substitution:

x' = x * Cos(T) - y * Sin(T) y' = y * Cos(T) + x * Sin(T) And Viola!!! That's how you prove the 2d rotation formula. ;*) Final equations:

Newx=oldx*Cos(Theta) - oldy*Sin(Theta) Newy=oldy*Cos(Theta) + oldx*Sin(Theta) *Note: Actually, had I used EQ's 2 and 2-1, the proof would be much easier since r is already removed. Though I believe that using r forces you to understand the concept behind the proof. So as an exercise, why don't you try it yourself? ;*) If you have understood all the stuff that I have written form article 1 up to here, you might have already guessed that our standard 2d rotation is THE SAME AS ROTATING FROM THE Z-AXIS. If you did, good. If not, look again on this figure:

Page 19: Third dimension math calulcation

See, rotating from the z-axis rotates your point on the XY plane. Here's the code supplement which added rotations to our previous starfield. Don't get dizzy. :*) Proj-rot.Bas VI. Let's go 3d!!!! Remember when I said that 3d rotation is almost like 2d rotation? Well, I'm not a man who breaks my word. So let me begin by saying that since rotation on the z-axis takes on the xy plane and rotation on the x-axis takes on the yz plane, where do you think rotation on the y axis take place? Yes, the xz plane!. :*) Now doing these rotations are pretty straightforward, all we have to do is smack the needed values on our 2d rotation equation for each axis and we're good to go. One thing to remember though is "TO USE THE OLD VALUES UNTIL THE NEW ONES ARE FOUND". Which means for a full rotation on all the axes, do not directly put values until they are fully rotated on the axis that they are rotated. Here's the full 3d rotation Equations: *All values are floating point numbers '***Rotation on the Z-axis NewY = y*cos(Thetax) - z*sin(Thetax) NewZ = z*cos(Thetax) + y*sin(Thetax) y = NewY z = NewZ '***Rotation on the Y-axis NewZ = z*cos(Thetay) - x*sin(Thetay) NewX = x*cos(Thetay) + z*sin(Thetay) x = NewX

Page 20: Third dimension math calulcation

'***Rotation on the Z-axis NewX = x*cos(Thetaz) - y*sin(Thetaz) NewY = y*cos(Thetaz) + x*sin(Thetaz) Rotatedx = NewX Rotatedy = NewY Rotatedz = NewZ Your rotated x/y/z are the points completely rotated over the x,y and z axes. I had to save the rotated values at some point to make it work or our rotations wouldn't look right. :*). Its also notable that "THE ORDER IN WHICH YOU ROTATE FROM EACH AXIS IS VERY IMPORTANT". Rotating in z-x-y order would not produce the same result as rotating in the x-y-z order. I'm using x-y-z because of the alphabet. Actually, Kiwidog's rotation is in x-y-z order and since his article started me with 3d, I'm writing this as a tribute to him. As they say, "old habits die hard".:*) Since, QB's implementation of the FPU(The Floating Point Unit) is really crap, we could optimize this by using lookup tables or just calculating some constants before the actual rotation equations. ie.

QBcode: cx! = COS(AngleX!) sx! = SIN(AngleX!) cy! = COS(AngleY!) sy! = SIN(AngleY!) cz! = COS(AngleZ!) sz! = SIN(AngleZ!) FOR i = 0 TO Maxpoints x! = model(i).x y! = model(i).y z! = model(i).z NewY! = (y! * cx!) - (z! * sx!) NewZ! = (z! * cx!) + (y! * sx!) y! = NewY! z! = NewZ! NewZ! = (z! * cy!) - (x! * sy!) NewX! = (x! * cy!) + (z! * sy!) x! = NewX! NewX! = (x! * cz!) - (y! * sz!)

Page 21: Third dimension math calulcation

NewY! = (y! * cz!) + (x! * sz!) Next i

Doing this would speed your render a lot. :*) Here's an example file: 3drot.bas Before I forget, to translate, subtract cam(x,y,z) AFTER rotation. Unless, you'd want your rotations to be off-center. Think about when to use either. Heck, why don't you try it to see the effects? :*) However, there's still a faster way to rotate. Notice the amount of multiplication just to to do a full 3 axis rotation? Yep, 12 multiplies! It turns out that we can reduce this to just 9! But how do we do it? Either by using matrices or weeding out constants using standard algebra. Both methods would work well and would roughly produce the same result. Same nine multiplies, same amount of arithmetic. Though you could directly translate the points using the 4th row of a 4*4 matrix, we can also do it by subtracting our camera value from the rotated coordinate. And if you look closely either the matrix or the algebra method would produce the same constants. :*) *I will touch up on matrices after the texturemapping article so don't worry. :*)

VII. From 12 to 9 There are other articles discussing this type of optimization but sadly, the final 3*3 matrix just does not rotate right. So if you want to derive a final 3*3 matrix yourself from your own rotation order, you have to do it yourself. :*) BTW, the constants we will derive after this is a 3*3 rotation matrix. We just didn't use the matrix way but the algebra 101 way. :*) So now let's begin the headache. Standard 12 mul rotation: Let ox,oy,oz the old coords

Page 22: Third dimension math calulcation

Let nx,ny,nz the new rotated coords cx = cos(anglex) cy = cos(angley) cz = cos(anglez) sx = sin(anglex) sy = sin(angley) sz = sin(anglez) I'm numbering the equations for easy referencing later so you won't get lost in the mess. ****** 1. ny = oy*cx - oz*sx 'x axis 2. nz = oz*cx + oy*sx oy = ny oz = nz 3. nz = oz*cy - ox*sy 'y axis 4. nx = ox*cy + oz*sy oy = ny oz = nz 5. nx = ox*cz - oy*sz 'z axis 6. ny = oy*cz + ox*sz '''All points rotated ;*) ***** *From 12 to 9 multiplies. We will simplify each axis equation starting from the x axis. Not the numbers as they reference equations from our original 12 mul rotation. So... Oz(2) = Nz(2) = Nz = oz*cx + oy*sx * I don't know if this would make sense to you but this I'm trying to minimize the text for the actual math to be understandable. ;*)

Page 23: Third dimension math calulcation

****For X axis.... nx(4) = ox*cy+oz*sy 'orig nx(4) = ox*cy+oz(2)*sy *let's substitute nz(2) to oz nx(4) = ox*cy+[oz*cx+oy*sx]*sy *distribute sy inside nz(2) nx(4) = ox*cy+oz*cx*sy+oy*sx*sy nx(5) = ox*cz-oy*sz 'orig *now substitute nx(4) and ny(1) nx(5) = [ox*cy+oz*cx*sy+oy*sx*sy]*cz -[oy*cx-oz*sx]*sz *distribute cz and sz nx(5) = ox*cy*cz+oz*cx*sy*cz+oy*sx*sy*cz -[oy*cx*sz-oz*sx*sz] *distribute the negative sign(-) and remove parenthesis. (note the change of signs) nx(5)= ox*cy*cz+oz*cx*sy*cz+oy*sx*sy*cz -oy*cx*sz+oz*sx*sz *use the commutative property of addition to reorder the terms in x+y+z order. nx(5) = ox*cy*cz 'X + oy*sx*sy*cz - oy*cx*sz 'y + oz*cx*sy*cz + oz*sx*sz 'Z *factor out x,y and z nx(5) = ox*[cy*cz] 'X + oy*[sx*sy*cz - cx*sz] 'y + oz*[cx*sy*cz + sx*sz] 'Z *We already have precalculated the constants to use(inside square brackets). Let's store 'em. so... xx = cy*cz xy = sx*sy*cz - cx*sz xz = cx*sy*cz + sx*sz ****For Y axis...

Page 24: Third dimension math calulcation

ny(6) = oy(1)*cz + ox(4)*sz ny(6) = [oy*cx - oz*sx]*cz +{ox*cy+[oz*cx+oy*sx]*sy}*sz *distribute cz and sy ny(6) = oy*cx*cz - oz*sx*cz +[ox*cy+oz*cx*sy+oy*sx*sy]*sz ny(6) = oy*cx*cz - oz*sx*sy*cz +ox*cy*sz + oz*cx*sy*sz + oy*sx*sy*sz *Rearrange in x,y,z order ny(6) = ox*cy*sz +oy*cx*cz + oy*sx*sy*sz -oz*sx*cz + oz*cx*sy*sz *Factor out x,y and z ny(6) = ox*cy*sz +oy*cx*cz + oy*sx*sy*sz -oz*sx*cz + oz*cx*sy*sz ny(6) = ox*[cy*sz] +oy*[cx*cz + sx*sy*sz] -oz*[sx*cz + cx*sy*sz] *oz has a (-) sign. Make sx*cz negative so that we could use addition. ny(6) = ox*[cy*sz] +oy*[cx*cz + sx*sy*sz] +oz*[-sx*cz + cx*sy*sz] 'store... yx = cy*sz yy = cx*cz + sx*sy*sz yz = -sx*cz + cx*sy*sz ****For Z axis...(easiest!!!!) nz(3) = oz(2)*cy - ox*sy *substitute nz(2) nz(3) = [oz*cx + oy*sx]*cy - ox*sy *distribute nz(3) = oz*cx*cy + oy*sx*cy - ox*sy nz(3) = - ox*sy + oy*sx*cy + oz*cx*cy *make sy negative as to make ox positive nz(3) = ox*[-sy] + oy*[sx*cy] + oz*[cx*cy]

Page 25: Third dimension math calulcation

zx = -sy zy = sx*cy zz = cx*cy ****Final Precalculated constants!!!! ****This is our final 3*3 Matrix. 'X axis xx = cy*cz xy = sx*sy*cz - cx*sz xz = cx*sy*cz + sx*sz 'Y axis yx = cy*sz yy = cx*cz + sx*sy*sz yz = -sx*cz + cx*sy*sz 'Z axis zx = -sy zy = sx*cy zz = cx*cy We smack the above constants down our original coord and we get the rotated coord without much hassle. Faster and simpler too!!! Final Equations!!!!(9 muls only)

nx = ox*xx + oy*xy + oz*xz ny = ox*yx + oy*yy + oz*yz nz = ox*zx + oy*zy + oz*zz

Speed increase may not be apparent if you're just rotating a cube but try to rotate a 1000 polygon model and you'll see how much speed difference there is. ;*) Here's a sample file benchmarking this against the standard 12 mul rotation.

Page 26: Third dimension math calulcation

3dBench.bas You might want to see what's in store for you on the next article. So here is just one(Two actually) little part of it. :*) 3dwire.bas

Texture.Bas Lastly, don't limit yourself to just points, you can use sprites for better and cooler effects. :*) vecballs.bas Get yourself a stretchsprite routine and you cam make some even cooler stuff!!! stretch.bas From now on I'll be using the 3*3 matrix constants as opposed to the 12 mul rotation so that our renders are a lot faster. And also because it will not only be points that we will rotate later but VECTORS. :*) There is also a better way to rotate than this. I'll take it up when we get to matrices. :*) Now go ahead and code yourself a 3d rotator even if its just a cube. Because next time I'll be discussing to you on how to generate 3d shapes the math way and I'll touch up on polygons so that you can fill your models at runtime and impress your friends. I'll also touch up on 2 more 3d coordinate systems. The SPHERICAL and CYLINDRICAL coordinate systems. :*). So until next 'ish, Happy Coding!!! Credits: Kiwidog for introducing me to the world of 3d Plasma357 for SetVideoSeg SCM for proofreading Biskbart for the tesselation algo Relsoft 2003

Page 27: Third dimension math calulcation

[email protected] rel.phatcode.net

Vectors are cool!!!

It's almost impossible to do graphics programming without using vectors. Almost all math concerning 3d coding use vectors. If you hate vectors, read on and you'll probably love them more than your girlfriend after you've finished reading this article. ;*)

What are vectors?

First off, let me define 2 quantities: The SCALAR and VECTOR quantities. Okay, scalar quantities are just values. One example of it is Temperature. You say, "It's 40 degrees Celsius here", and that's it. No sense of direction. But to define a vector you need a direction or sense. Like when the pilot say's, "We are 40 kilometers north of Midway". So a scalar quantity is just a value while a vector is a value + direction.

Look at the figure below: The arrow(Ray) represents a vector. The "Head" is its "Sense"(direction is not applicable here) and the "Tail" is its starting point. The distance from head to tail is called its "magnitude".

Page 28: Third dimension math calulcation

In this vector there are 2 components, the X and Y component. X is the horizontal and Y is the vertical component. Remember that "ALL VECTOR OPERATIONS ARE DONE WITH ITS COMPONENTS." I like to setup my vectors in this TYPE: Type Vector x as single y as single End TYPE The difference between the "sense" and "direction" is that direction is the line the vector is located while sense can go either way on that line.

Definitions: *|v| means that |v| is the magnitude of v. *Orthogonal vectors are vectors perpendicular to each other. It's sticks up 90 degrees.

Page 29: Third dimension math calulcation

To get a vector between 2 points: 2d: v = (x2 - x1) + (y2 - y1) 3d: v = (x2 - x1) + (y2 - y1) + (z2 - z1) QBcode: vx = x2 - x1 vy = y2 - y1 vz = z2 - z1 where: (x2-x1) is the horizontal component and so on. Vectors are not limited to the cartesian coordinate system. In polar form: v = r * theta

Resolving a vector by its components

Page 30: Third dimension math calulcation

Suppose a vector v has a magnitude 5 and direction given by Theta = 30 degrees. Where theta is the angle the vector makes with the positive x-axis. How do we resolve this vectors' components?

Remember the Polar to Cartesian conversion? v.x = cos(theta) v.y = sin(theta) Let vx = horizontal component Let vy = horizontal component Let Theta = Be the angle So... v.x = |v| * cos(theta) v.x = 5 * cos(30) v.x = 4.33 v.y = |v| * sin(theta) v.y = 5 * sin(30) v.y = 2.50 What I've been showing you is a 2d vector. Making a 3d vector is just adding another component, the Z component. Type Vector x as single y as single z as single End TYPE Operations on vectors needed in 3d engines

Page 31: Third dimension math calulcation

1. Scaling a vector(Scalar multiplication) Purpose:

This is used to scale a vector by a scalar value. Needed in the scaling of models and changing the velocity of projectiles. In equation: v = v * scale In qbcode: v.x = v.x * Scale v.y = v.y * Scale v.z = v.z * Scale

2. Getting the Magnitude(Length) of a vector Purpose:

Used in "Normalizing"(making it a unit vector) a vector. More on this later. Equation: |V| = Sqr(v.x^2 + v.y^2 + v.z^2) QBcode: Mag! = Sqr(v.x^2 + v.y^2 + v.z^2)

3. Normalizing a vector

Purpose:

Used in light sourcing, camera transforms, etc. Makes the vector a "unit-vector" that is a vector having a magnitude of 1. Divides the vector by its length. Equ: v = v ------- |v| QBCode: Mag! = Sqr(v.x^2 + v.y^2 + v.z^2) v.x = v.x / mag! v.y = v.y / mag!

Page 32: Third dimension math calulcation

v.z = v.z / mag! 4. The DOT Product Purpose:

Used in many things like lightsourcing and vector projection. Returns the cosine of the angle between any two vectors ( Assuming the vectors are Normalized) . A Scalar. The dot product is also called the "Scalar" product. Equ: v.w = v.x* w.x + v.y* w.y + v.z* w.z QBCode: Dot! = v.x* w.x + v.y* w.y + v.z* w.z Fun fact: * 2 Vectors are orthogonal if their dot product is 0. Proof: "What is the cosine of 90?" 5. The CROSS product Purpose:

Used in lightsourcing, camera transformation, back-face culling, etc. The cross product of 2 vectors returns another vector that is orthogonal to the plane that has the first 2 vectors. Let's say we have vectors U and F. Equ: U x F = R QB code: R.x = U.y * F.z - F.y * U.z R.y = U.z * F.x - F.z * U.x R.z = U.x * F.y - F.x * U.y

Fun facts: * C is the vector orthogonal to A and B. * C is the NORMAL to the plane that includes A and B. The cross-product of any two vectors can best be remembered by the CRAMERS RULE on DETERMINANTS. Thought of it while taking a bath. I'll tell you when I finish my matrix chapter. * The cross product is exclusive to 3d and its also called the "Vector" product.

Page 33: Third dimension math calulcation

6. Vector Projection Purpose:

Used in resolving the second vector of the camera matrix (Thanks Toshi!). For vectors A and B...

Equ: U.Z * Z

QB code:

Page 34: Third dimension math calulcation

Let N = vector projection of U to Z. The vector parallel to Z.

T! = Vector.Dot(U, Z) N.x = T! * Z.x N.y = T! * Z.y N.z = T! * Z.z 7. Adding vectors

Purpose:

Used in camera and object movements. Anything that you'd want to move relative to your camera. Adding vectors is just the same as adding their components. Let A and B be vectors in 3d, and C is the sum:

Equ: C = A + B C = (ax + bx) + (ay + by) + (az + bz)

QBcode: c.x = a.x + b.x c.y = a.y + b.y c.z = a.z + b.z Now that Most of the Math is out of the way.... Applications

I. WireFraming and BackFace culling

I like to make use of types with my 3d engines. For Polygons:

Type Poly p1 as integer p2 as integer p3 as integer end type

Page 35: Third dimension math calulcation

P1 is the first vertex, p2 second and p3 third. Let's say you have a nice rotating cube composed of points, looks spiffy but you want it to be composed of polygons(Triangles) in this case). If we have a cube with vertices: Vtx1 50, 50, 50 :x,y,z Vtx2 -50, 50, 50 Vtx3 -50,-50, 50 Vtx4 50,-50, 50 Vtx5 50, 50,-50 Vtx6 -50, 50,-50 Vtx7 -50,-50,-50 Vtx8 50,-50,-50 What we need are connection points that define a face. The one below is a Quadrilateral face(4 points)

Face1 1, 2, 3, 4 Face2 2, 6, 7, 3 Face3 6, 5, 8, 7 Face4 5, 1, 4, 8 Face5 5, 6, 2, 1 Face6 4, 3, 7, 8 Face1 would have vertex 1, 2, and 3 as its connection vertices. Now since we want triangles instead of quads, we divide each quad into 2 triangles, which would make 12 faces. It' also imperative to arrange your points in counter-clockwise or clockwise order so that backface culling would work. In this case I'm using counter-clockwise. The following code divide the quads into 2 triangles with vertices arranged in counter-clockise order. Tri(j).idx will be used for sorting. QBcode: j = 1 FOR i = 1 TO 6 READ p1, p2, p3, p4 'Reads the face(Quad) Tri(j).p1 = p1 Tri(j).p2 = p2 Tri(j).p3 = p4 Tri(j).idx = j j = j + 1 Tri(j).p1 = p2 Tri(j).p2 = p3 Tri(j).p3 = p4 Tri(j).idx = j j = j + 1 NEXT i

To render the cube without backface culling, here's the pseudocode:

Page 36: Third dimension math calulcation

1. Do 2. Rotatepoints 3. Project points 4. Sort(Not needed for cubes and other simple polyhedrons) 5. Get Triangles' projected coords ie. x1 = Model(Tri(i).P1).ScreenX y1 = Model(Tri(i).P1).ScreenY x2 = Model(Tri(i).P2).ScreenX y2 = Model(Tri(i).P2).ScreenY x3 = Model(Tri(i).P3).ScreenX y3 = Model(Tri(i).P3).ScreenY 6. Draw Tri x1,y1,x2,y2,x3,y3,color BackFace Culling

Backface culling is also called "Hidden face removal". In essense, it's a way to speed up your routines by NOT showing a polygon if it's not facing towards you. But how do we know what face of the polygon is the "right" face? Let's take a CD as an example, there are 2 sides to a particular CD. One side that the data is to be written and the other side where the label is printed. What if we decide that the Label-side should be the right side? How do we do it? Well it turns out that the answer is our well loved NORMAL. :*) But for that to work, we should *sequentially* arrange our vertices in counter or clockwise order.

If you arranged your polys' vertices in counter- clockwise order as most 3d modelers do, you just get the projected z-normal of the poly and check if its greater than(>)0. If it is, then draw triangle. Of course if you arranged the vertices in clockwise order, then the poly is facing us when the Z-normal is <0.

Counter-Clockwise arrangement of vertices:

Page 37: Third dimension math calulcation

Clockwise Arrangement of vertices:

Page 38: Third dimension math calulcation

Since we only need the z component of the normal to the poly, we could even use the "projected" coords(2d) to get the z component! QBcode: Znormal = (x2 - x1) * (y1 - Y3) - (y2 - y1) * (x1 - X3) IF (Znormal > 0) THEN '>0 so vector facing us Drawpoly x1,y1,x2,y2,x3,y3 end if Here's the example file: 3dwire.bas Sorting There are numerous sorting techniques that I use in my 3d renders here are the most common:

1. Bubble sort (modified) 2. Shell sort 3. Quick sort 4. Blitz sort (PS1 uses this according to Blitz)

I won't go about explaining how the sorting algorithms work. I'm here to discuss how to implement it in your engine. It may not be apparent to you (since you are rotating a simple cube) but you need to sort your polys to make your renders look right. The idea is to draw the farthest polys first and the nearest last. Before we could go about sorting our polys we need a new element in our polytype. Type Poly p1 as integer p2 as integer p3 as integer idx as integer zcenter as integer end type *Idx would be the index we use to sort the polys. We sort via IDX, not by subscript. *Zcenter is the theoretical center of the polygon. It's a 3d coord (x,y,z) To get the center of any polygon or polyhedra(model),you add

Page 39: Third dimension math calulcation

all the 3 coordinates and divide it by the number of vertices(In this case 3). Since we only want to get the z center: QBcode:

Zcenter= Model(Poly(i).p1)).z + Model(Poly(i).p2)).z + Model(Poly(i).p3)).z Zcenter = Zcenter/3 Optimization trick: We don't really need to find the *real* Zcenter since all the z values that were added are going to be still sorted right. Which means... No divide!!! Now you sort the polys like this: QBcode:

FOR i% = Lbound(Poly) TO UBOUND(Poly) Poly(i%).zcenter = Model(Poly(i%).p1).Zr + Model(Poly(i%).p2).Zr + Model(Poly(i%).p3).Zr Poly(i%).idx = i% NEXT i% Shellsort Poly(), Lbound(Poly), UBOUND(Poly)

To Draw the model, you use the index(Poly.idx) QBcode: FOR i = 1 TO UBOUND(Poly) j = Poly(i).idx x1 = Model(Poly(j).p1).scrx 'Get triangles from "projected" x2 = Model(Poly(j).p2).scrx 'X and Y coords since Znormal x3 = Model(Poly(j).p3).scrx 'Does not require a Z coord y1 = Model(Poly(j).p1).scry y2 = Model(Poly(j).p2).scry y3 = Model(Poly(j).p3).scry 'Use the Znormal,the Ray perpendicular(Orthogonal) to the 'Screen defined by the Triangle (X1,Y1,X2,Y2,X3,Y3) 'if Less(>) 0 then its facing in the opposite direction so 'don't plot. If <0 then its facing towards you so Plot. Znormal = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3) IF Znormal < 0 THEN DrawTri x1,y1,x2,y2,x3,y3 END IF NEXT i

Page 40: Third dimension math calulcation

Here's a working example: Sorting.Bas II. Spherical and cylindrical coordinate systems. These 2 systems are extentions of the polar coordinate system. Where polar is 2d these 2 are 3d. :*) a. Cylindrical coordinate system The cylindrical coodinate system is useful if you want to generate models mathematically. Some examples are Helixis, Cylinders(of course), tunnels or any tube-like model. This system works much like 2d, but with an added z component that doesn't need and angle. Here's the equations to convert cylindrical to rectangular coordinate system. Here's the Cylindrical to rectangular coordinate conversion equations. Almost like 2d. Of course this cylinder will coil on the z axis. To test yourself, why dont you change the equations to coil it on the y axis? x = COS(theta) y = SIN(theta) z = z To generate a cylinder: QBcode: i = 0 z! = zdist * Slices / 2 FOR Slice = 0 TO Slices - 1 FOR Band = 0 TO Bands - 1 Theta! = (2 * PI / Bands) * Band Model(i).x = radius * COS(Theta!) Model(i).y = radius * SIN(Theta!) Model(i).z = -z! i = i + 1 NEXT Band z! = z! - zdist NEXT Slice Here's a 9 liner I made using that equation.

9Liner.Bas

Page 41: Third dimension math calulcation

b. Spherical coordinate system This is another useful system. It can be used for Torus and Sphere generation. Here's the conversion:

x = SIN(Phi)* COS(theta) y = SIN(Phi)* SIN(theta) z = COS(Phi)

Where: Theta = Azimuth ; Phi = Elevation

To generate a sphere:

QBcode:

i = 0 FOR SliceLoop = 0 TO Slices - 1 Phi! = PI / Slices * SliceLoop FOR BandLoop = 0 TO Bands - 1 Theta! = 2 * -PI / Bands * BandLoop Model(i).x = -INT(radius * SIN(Phi!) * COS(Theta!)) Model(i).y = -INT(radius * SIN(Phi!) * SIN(Theta!)) Model(i).z = -INT(radius * COS(Phi!)) i = i + 1 NEXT BandLoop NEXT SliceLoop Here's a little particle engine using the spherical coordinate system.

Fountain.bas

Here's an example file to generate models using those equations:

Gen3d.Bas

III. Different Polygon fillers A. Flat Filler Tired of just wireframe and pixels? After making a wireframe demo, you'd want your objects to be solid. The first type of fill that I'll be introducing is a flat triangle filler. What?! But I could use PAINT to do that! Well, you still have to

Page 42: Third dimension math calulcation

understand how the flat filler works because the gouraud and texture filler will be based on it. ;*) Now how do we make a flat filler? Let me introduce you first to the idea of LINEAR INTERPOLATION. How does interpolation work? Let's say you want to make dot on the screen at location (x1,y1) to (x2,y2) in 10 steps? Let A = (x1,y1) B = (x2,y2) Steps = 10 f(x) = (B-A)/Steps So....

QBcode: dx! = (x2-x1)/steps dy! = (y2-y1)/Steps x! = x1 y! = y1 For a = 0 to steps - 1 Pset(x,y), 15 x! = x! + dx! y! = y! + dy! next a That's all to there is to interpolation. :*) Now that we have an idea of what linear interpolation is we could make a flat triangle filler. The 3 types of triangle

A. Flat Filled

1. Flat Bottom

Page 43: Third dimension math calulcation

2. Flat Top

3. Generic Triangle

Page 44: Third dimension math calulcation

In both the Flat Top and Flat bottom cases, it's easy to do both triangles as we only need to interpolate A to B and A to C in Y steps. We draw a horizontal line in between (x1,y) and (x2,y). The problem lies when we want to draw a generic triangle since we don't know if it's a flat top or flat bottom. But it turns out that there is an all too easy way to get around with this. Analyzing the generic triangle, we could just divide the triangle into 2 triangles. One Flat Bottom and One Flat Top!

We draw it with 2 loops. The first loop is to draw the Flat Bottom and the second loop is for the Flat Top.

PseudoCode:

TOP PART ONLY!!!!(FLAT BOTTOM)

1. Interpolate a.x and draw each scanline from a.x to b.x in (b.y-a.y) steps.

ie. a.x = x3 - x1

b.x = y3 - y1

Xstep1! = a.x / b.x

2. Interpolate a.x and draw each scanline from a.x to c.x in (c.y-a.y) steps.

Page 45: Third dimension math calulcation

ie. a.x = x1 - x3

c.x = y1 - y3

Xstep3! = a.x / c.x

3. Draw each scanline(Horizontal line) from a.y to b.y incrementing y with one in each step, interpolating LeftX with Xstep1! and RightX with Xstep3!. You've just finished drawing the TOP part of the triangle!!!

4. Do the same with the bottom-half interpolating from b.x to c.x in b.y steps.

PseudoCode:

1. Sort Vertices IF y2 < y1 THEN SWAP y1, y2 SWAP x1, x2 END IF IF y3 < y1 THEN SWAP y3, y1 SWAP x3, x1 END IF IF y3 < y2 THEN SWAP y3, y2 SWAP x3, x2 END IF 2. Interpolate A to B dx1 = x2 - x1 dy1 = y2 - y1 IF dy1 <> 0 THEN Xstep1! = dx1 / dy1 ELSE Xstep1! = 0 END IF 3. Interpolate B to C dx2 = x3 - x2 dy2 = y3 - y2 IF dy2 <> 0 THEN Xstep2! = dx2 / dy2 ELSE Xstep2! = 0 END IF 4. InterPolate A to C dx3 = x1 - x3 dy3 = y1 - y3 IF dy3 <> 0 THEN Xstep3! = dx3 / dy3

Page 46: Third dimension math calulcation

ELSE Xstep3! = 0 END IF 5. Draw Top Part Lx! = x1 'Starting coords Rx! = x1 FOR y = y1 TO y2 - 1 LINE (Lx!, y)-(Rx!, y), clr Lx! = Lx! + Xstep1! 'increment derivatives Rx! = Rx! + Xstep3! NEXT y 6. Draw Lower Part Lx! = x2 FOR y = y2 TO y3 LINE (Lx!, y)-(Rx!, y), clr Lx! = Lx! + delta2! Rx! = Rx! + delta3! NEXT y

Here's an example file:

FlatTri.bas

B. Gouraud Filled

There is not that much difference between the flat triangle and the gouraud triangle. In the calling sub, instead of just the 3 coodinates, there are 3 paramenters more. Namely: c1,c2,c3. They are the colors we could want to interpolate between vertices. And since you know how to interpolate already, it would not be a problem. :*)

First we need a horizontal line routine that draws with interpolated colors. Here's the code. It's self explanatory.

*dc! is the ColorStep(Like the Xsteps)

QBcode:

HlineG (x1,x2,y,c1,c2) dc! = (c2 - c1)/ (x2 - x1) c! = c1 For x = x1 to x2

Page 47: Third dimension math calulcation

Pset(x , y) , int(c!) c! = c! + dc! next x

Now that we have a horizontal gouraud line, we will modify some code into our flat filler to make it a gouraud filler. I won't give you the whole code, but some important snippets.

1. In the sorting stuff: (You have to do this to all the IF's.

IF y2 < y1 THEN SWAP y1, y2 SWAP x1, x2 SWAP c1, c2 END IF 2. Interpolate A to B; c1 to c2. do this to all vertices.

dx1 = x2 - x1 dy1 = y2 - y1 dc1 = c2 - c1 IF dy1 <> 0 THEN Xstep1! = dx1 / dy1 Cstep1! = dc1 / dy1 ELSE Xstep1! = 0 Cstep1! = 0 END IF

5. Draw Top Part

Lx! = x1 'Starting coords Rx! = x1 Lc! = c1 'Starting colors Rc! = c1 FOR y = y1 TO y2 - 1 HlineG Lx!, Rx!, y, Lc!, Rc! Lx! = Lx! + Xstep1! Rx! = Rx! + Xstep3! Lc! = Lc! + Cstep1! 'Colors Rc! = Rc! + Cstep3! NEXT y

It's that easy! You have to interpolate just 3 more values! Here's the complete example file:

GourTri.Bas

Page 48: Third dimension math calulcation

C. Affine Texture Mapped

Again, there is not much difference between the previous 2 triangle routines from this. Affine texturemapping also involves the same algo as that of the flat filler. That is, Linear interpolation. That's probably why it doesn't look good. :*( But it's fast. :*). If in the gouraud filler you need to interpolate between 3 colors, you need to interpolate between 3 U and 3 V texture coordinates in the affine mapper. That's 6 values in all. In fact, it's almost the same as gouraud filler!

Now we have to modify our Gouraud Horizontal line routine to a textured line routine.

*This assumes that the texture size is square and a power of 2. Ie. 4*4, 16*16, 128*128,etc. And is used to prevent from reading pixels outside the texture. *The texture mapper assumes a QB GET/PUT compatible image. Array(1) = width*8; Array(2) = Height; Array(3) = 2 pixels. * HlineT also assumes that a DEF SEG = Varseg(Array(0)) has been issued prior to the call. TOFF is the Offset of the image in multiple image arrays. ie: TOFF = VARPTR(Array(0)) *TsizeMinus1 is Texturesize -1. QBcode: HlineT (x1,x2,y,u1,u2,v1,v2,Tsize) du! = (u2 - u1)/ (x2 - x1)

Page 49: Third dimension math calulcation

dv! = (v2 - v1)/ (x2 - x1) u! = u1 v! = v1 TsizeMinus1 = Tsize - 1 For x = x1 to x2 'get pixel off the texture using 'direct memory read. The (+4 + TOFF) 'is used to compensate for image 'offsetting. Tu=u! AND TsizeMinus1 Tv=v! AND TsizeMinus1 Texel = Peek(Tu*Tsize + Tv + 4 + TOFF) Pset(x , y) , Texel u! = u! + du! v! = v! + dv! next x

Now we have to modify the rasterrizer to support U and V coords. All we have to do is interpolate between all the coords and we're good to go.

1. In the sorting stuff: (You have to do this to all the IF's. IF y2 < y1 THEN SWAP y1, y2 SWAP x1, x2 SWAP u1, u2 SWAP v1, v2 END IF 2. Interpolate A to B; u1 to u2; v1 to v2. Do this to all vertices. dx1 = x2 - x1 dy1 = y2 - y1 du1 = u2 - u1 dv1 = v2 - v1 IF dy1 <> 0 THEN Xstep1! = dx1 / dy1 Ustep1! = du1 / dy1 Vstep1! = dv1 / dy1 ELSE Xstep1! = 0 Ustep1! = 0 Vstep1! = 0 END IF 5. Draw Top Part Lx! = x1 'Starting coords Rx! = x1 Lu! = u1 'Starting U Ru! = u1 Lv! = v1 'Starting V Rv! = v1

Page 50: Third dimension math calulcation

FOR y = y1 TO y2 - 1 HlineT Lx!, Rx!, y, Lu!, Ru!, Lv!, Rv! Lx! = Lx! + Xstep1! Rx! = Rx! + Xstep3! Lu! = Lu! + Ustep1! 'U Ru! = Ru! + Ustep3! Lv! = Lv! + Vstep1! 'V Rv! = Rv! + Vstep3! NEXT y

Here's the example demo for you to learn from. Be sure to check the algo as it uses fixpoint math to speed things up quite a bit. :*)

Textri.bas

IV. Shading and Mapping Techniques 1. Lambert Shading So you want your cube filled and lightsourced, but don't know how to? The answer is Lambert Shading. And what does Lambert shading use? The NORMAL. Yes, it's the cross-product thingy I was writing about. How do we use the normal you say. First, you have a filled cube composed of triangles (Polys), now we define a vector orthogonal to that plane(Yep, the Normal) sticking out. How do we calculate normals? Easy, use the cross product! PseudoCode: 1. For each poly.. 2. get poly's x, y and z coords 3. define vectors from 3 coords 4. get the cross-product(our normal to a plane) 5. Normalize your normal QBcode: FOR i = 1 TO UBOUND(Poly) P1 = Poly(i).P1 'get poly vertex P2 = Poly(i).P2 P3 = Poly(i).P3 x1 = Model(P1).x 'get coords x2 = Model(P2).x x3 = Model(P3).x y1 = Model(P1).y y2 = Model(P2).y y3 = Model(P3).y Z1 = Model(P1).z Z2 = Model(P2).z Z3 = Model(P3).z

Page 51: Third dimension math calulcation

ax! = x2 - x1 'derive vectors bx! = x3 - x2 ay! = y2 - y1 by! = y3 - y2 az! = Z2 - Z1 bz! = Z3 - Z2 'Cross product xnormal! = ay! * bz! - az! * by! ynormal! = az! * bx! - ax! * bz! znormal! = ax! * by! - ay! * bx! 'Normalize Mag! = SQR(xnormal! ^ 2 + ynormal! ^ 2 + znormal! ^ 2) IF Mag! <> 0 THEN xnormal! = xnormal! / Mag! ynormal! = ynormal! / Mag! znormal! = znormal! / Mag! END IF v(i).x = xnormal! 'this is our face normal v(i).y = ynormal! v(i).z = znormal! NEXT i Q: "You expect me to do this is real-time?!!!" "That square- root alone would make my renders slow as hell!!" A: No. You only need to do this when setting up your renders. ie. Only do this once, and at the top of your proggie. Now that we have our normal, we define a light source. Your light source is also a vector. Be sure that both vectors are normalized. ie. Light.x\ Light.y > The light vector Light.z/ Polynormal.x\ Polynormal.y > The Plane normal Polynormal.z/

Page 52: Third dimension math calulcation

The angle in the pic is the incident angle between the light and the plane normal. the angle is inversely proportional to the intensity of light. So the lesser the angle, the more intense the light. But how do we get the intensity? Fortunately, there is an easy way to calculate the light. All we have to do is get the Dot product between these vectors!!! Since the dot returns a scalar value ,Cosine(angle), we can get the brightness factor by just multiplying the Dot product by the color range!!! In screen 13: Dot*255. QBCode: nx! = PolyNormal.x ny! = PolyNormal.y nz! = PolyNormal.z lx! = LightNormal.x ly! = LightNormal.y lz! = LightNormal.z Dot! = (nx! * lx!) + (ny! * ly!) + (nz! * lz!) IF Dot! < 0 then Dot! = 0 Clr = Dot! * 255 FlatTri x1, y1, x2, y2, x3, y3, Clr end if Here's an example file in action: Lambert.Bas 2. Gouraud Shading After the lambert shading, we progress into gouraud shading.Q: But how do we find a normal to a point? A: You can't. There is no normal to a point. The cross-product is exclusive to planes(3d) so you just can't. You don't have to worry though, as there are ways around this problem.

Page 53: Third dimension math calulcation

What we need to do is to find adjacent faces that the vertex is located and average their face normals. It's an approximation but it works!

Let: V()= Face normal; V2() vertexnormal QBcode: FOR i = 1 TO Numvertex xnormal! = 0 ynormal! = 0 znormal! = 0 FaceFound = 0 FOR j = 0 TO UBOUND(Poly) IF Poly(j).P1 = i OR Poly(j).P2 = i OR Poly(j).P3 = i THEN xnormal! = xnormal! + v(j).x ynormal! = ynormal! + v(j).y znormal! = znormal! + v(j).z FaceFound = FaceFound + 1 'Face adjacent END IF NEXT j xnormal! = xnormal! / FaceFound ynormal! = ynormal! / FaceFound znormal! = znormal! / FaceFound v2(i).x = xnormal! 'Final vertex normal v2(i).y = ynormal! v2(i).z = znormal! NEXT i

Now that you have calculated the vertex normals, you only have to pass the rotated vertex normals into our gouraud filler!!! ie. Get the dot product between the rotated vertex normals and multiply it with the color range. The product is your color coordinates.

QBcode:

IF znormal < 0 THEN nx1! = CubeVTXNormal2(Poly(i).P1).X 'Vertex1 ny1! = CubeVTXNormal2(Poly(i).P1).Y nz1! = CubeVTXNormal2(Poly(i).P1).Z nx2! = CubeVTXNormal2(Poly(i).P2).X 'Vertex2 ny2! = CubeVTXNormal2(Poly(i).P2).Y nz2! = CubeVTXNormal2(Poly(i).P2).Z nx3! = CubeVTXNormal2(Poly(i).P3).X 'Vertex3 ny3! = CubeVTXNormal2(Poly(i).P3).Y nz3! = CubeVTXNormal2(Poly(i).P3).Z lx! = LightNormal.X ly! = LightNormal.Y lz! = LightNormal.Z 'Calculate dot-products of vertex normals Dot1! = (nx1! * lx!) + (ny1! * ly!) + (nz1! * lz!) IF Dot1! < 0 THEN 'Limit

Page 54: Third dimension math calulcation

Dot1! = 0 ELSEIF Dot1! > 1 THEN Dot1! = 1 END IF Dot2! = (nx2! * lx!) + (ny2! * ly!) + (nz2! * lz!) IF Dot2! < 0 THEN Dot2! = 0 ELSEIF Dot2! > 1 THEN Dot2! = 1 END IF Dot3! = (nx3! * lx!) + (ny3! * ly!) + (nz3! * lz!) IF Dot3! < 0 THEN Dot3! = 0 ELSEIF Dot3! > 1 THEN Dot3! = 1 END IF 'multiply by color range clr1 = Dot1! * 255 clr2 = Dot2! * 255 clr3 = Dot3! * 255 GouraudTri x1, y1, clr1, x2, y2, clr2, x3, y3, clr3 END IF

Here's and example file:

Gouraud.Bas

3. Phong Shading(Fake)

Phong shading is a shading technique which utilizes diffuse, ambient and specular lighting. The only way to do Real phong shading is on a per-pixel basis. Here's the equation:

Intensity=Ambient + Diffuse * (L • N) + Specular * (R • V)^Ns

Where:

Ambient = This is the light intensity that the objects reflect upon the environment. It reaches even in shadows.

Diffuse = Light that scatters in all direction

Specular = Light intensity that is dependent on the angle between your eye vector and the reflection vector. As the angle between them increases, the less intense it is.

L.N = The dot product of the Light(L) vector and the Surface Normal(N)

Page 55: Third dimension math calulcation

R.V = The dot product of the Reflection(R) and the View(V) vector.

Ns = is the specular intensity parameter, the greater the value, the more intense the specular light is.

*L.N could be substututed to R.V which makes our equation:

Intensity=Ambient + Diffuse * (L • N) + Specular * (L • N)^Ns

Technically, this should be done for every pixel of the polygon. But since we are making real-time engines and using QB, this is almost an impossibilty. :*(

Fortunately, there are some ways around this. Not as good looking but works nonetheless. One way is to make a phong texture and use environment mapping to simulate light. Another way is to modify your palette and use gouraud filler to do the job. How do we do it then? Simple! Apply the equation to the RGB values of your palette!!!

First we need to calculate the angles for every, color index in our pal. We do this by interpolating our Normals' angle(90 degrees) and Light vectors' angle with the color range.

PseudoCode:

Range = 255 - 0 'screen 13

Angle! = PI / 2 '90 degrees

Anglestep! = Angle!/Range 'interpolate

For Every color index...

Dot! = Cos(Angle!)

'''Apply equation

'RED

Diffuse! = RedDiffuse * Dot!

Specular! = RedSpecular + (Dot! ^Ns)

Red% = RedAmbient! + Diffuse! + Specular!

'GREEN

Page 56: Third dimension math calulcation

Diffuse! = GreenDiffuse * Dot!

Specular! = GreenSpecular + (Dot! ^Ns)

Green% = GreenAmbient! + Diffuse! + Specular!

'BLUE

Diffuse! = BlueDiffuse * Dot!

Specular! = BlueSpecular + (Dot! ^Ns)

Red% = BlueAmbient! + Diffuse! + Specular!

WriteRGB(Red%,Green%,Blue%,ColorIndex)

Angle! = AngleStep!

Loop until maxcolor

* This idea came from a Cosmox 3d demo by Bobby 3999. Thanks a bunch!

Here's an example file:

Phong.bas

4. Texture Mapping

Texture mapping is a type of fill that uses a Texture(image) to fill a polygon. Unlike our previous fills, this one "plasters" an image(the texture) on your cube. I'll start by explaining what are those U and V coordinates in the Affine mapper part of the article. The U and V coordinates are the Horizontal and vertical coordinates of the bitmap(our texture). How do we calculate those coordinates? Fortunately, most 3d modelelers already does this for us automatically. :*).

However, if you like to make your models the math way, that is generating them mathematically, you have to calculate them by yourself. What I do is divide the quad into two triangles and blast the texture coordinates on loadup. Lookat the diagram to see what I mean.

*Textsize is the width or height of the bitmap

QBcode:

Page 57: Third dimension math calulcation

FOR j = 1 TO UBOUND(Poly) u1 = 0 v1 = 0 u2 = TextSize% v2 = TextSize% u3 = TextSize% v3 = 0 Poly(j).u1 = u1 Poly(j).v1 = v1 Poly(j).u2 = u2 Poly(j).v2 = v2 Poly(j).u3 = u3 Poly(j).v3 = v3 j = j + 1 u1 = 0 v1 = 0 u2 = 0 v2 = TextSize% u3 = TextSize% v3 = TextSize% Poly(j).u1 = u1 Poly(j).v1 = v1 Poly(j).u2 = u2 Poly(j).v2 = v2 Poly(j).u3 = u3 Poly(j).v3 = v3 NEXT j

After loading the textures, you just call the TextureTri sub passing the right parameters and it would texture your model for you. It's a good idea to make a 3d map editor that let's you pass texture coordinates, instead of calculating it on loadup. Here's a code snippet to draw a textured poly.

QBcode:

u1 = Poly(i).u1 'Texture Coords v1 = Poly(i).v1 u2 = Poly(i).u2 v2 = Poly(i).v2 u3 = Poly(i).u3 v3 = Poly(i).v3 TextureTri x1, y1, u1, v1, x2, y2, u2, v2, x3, y3, u3, v3, TSEG%, TOFF% END IF

*Tseg% and Toff% are the Segment and Offset of the Bitmap.

Here's an example file:

Texture.Bas

5. Environment Mapping

Page 58: Third dimension math calulcation

Environment mapping(also called Reflection Mapping) is a way to display a model as if it's reflecting a surface in front of it. Your model looks like a warped-up mirror! It looks so cool, I jumped off my chair when I first made one. :*) We texture our model using the texture mapper passing a vertex-normal modified texture coordinate. What does it mean? It means we calculate our texture coordinate using our vertex normals!

Here's the formula:

TextureCoord = Wid/2+Vertexnormal*Hie/2

Where:

Wid = Width of the bitmap

Hei = Height of the bitmap

Now, assuming your texture has the same width and height:

QBcode:

Tdiv2! = Textsize% / 2 FOR i = 1 TO UBOUND(Poly) u1! = Tdiv2! + v(Poly(i).P1).x * Tdiv2! 'Vertex1 v1! = Tdiv2! + v(Poly(i).P1).y * Tdiv2! u2! = Tdiv2! + v(Poly(i).P2).x * Tdiv2! 'Vertex2 v2! = Tdiv2! + v(Poly(i).P2).y * Tdiv2! u3! = Tdiv2! + v(Poly(i).P3).x * Tdiv2! 'Vertex3 v3! = Tdiv2! + v(Poly(i).P3).y * Tdiv2! Poly(i).u1 = u1! Poly(i).v1 = v1! Poly(i).u2 = u2! Poly(i).v2 = v2! Poly(i).u3 = u3! Poly(i).v3 = v3! NEXT i

After setting up the vertex normals and the texture coordinates, inside your rasterizing loop:

1. Rotate Vertex normals

2. Calculate texture coordinates

3. Draw model

Page 59: Third dimension math calulcation

That's it! Your own environment mapped rotating object. ;*)

Here's a demo:

Envmap.bas

Another one that simulates textures with phong shading using a phongmapped texture.

Phong2.bas

Playing with colors!!!

6. Shading in multicolor

Our previous shading techniques, Lambert, gouraud, and phong, looks good but you are limited to a single gradient. Not a good fact if you want to use colors. But using colors in screen 13 limits you to flat shading. I bet you would want a gouraud or phong shaded colored polygons right? Well, lo and behold! There is a little way around this problem. :*)

We use a subdivided gradient palette! A subdivided gradient palette divides your whole palette into gradients of colors limited to its subdivision. Here's a little palette I made using data statements and the gradcolor sub.

If you look closely, each line starts with a dark color and progresses to an intense color. And if you understood how our fillers work, you'll get the idea of modifying the fillers to work with this pal. Okay, since I'm feeling good today, I'll

Page 60: Third dimension math calulcation

just give it to you. After you calculated the Dot-product between the light and poly normals:

*This assumes a 16 color gradient palette. You could make it 32 or 64 if you want. Of course if you make it 32, you should multiply by 32 instead of 16. :*)

QBcode:

Clr1 = (Dot1! * 16) + Poly(j).Clr '16 color grad Clr2 = (Dot2! * 16) + Poly(j).Clr Clr3 = (Dot3! * 16) + Poly(j).Clr GouraudTri x1, y1, Clr1, x2, y2, Clr2, x3, y3, Clr3

Here's an example:

3dColors.bas

7. Translucency

A lot of people have asked me about the algo behind my translucent teapot in Mono and Disco. It's not that hard once you know how to make a translucent pixel. This is not really TRUE translucency, It's a gradient-based blending algorithm. You make a 16 color gradient palette and apply it to the color range(Same grad above. :*)).

PseudoCode:

For Every pixel in the poly...

TempC = PolyPixel and 15

BaseColor = PolyPixel - TempC

DestC = Color_Behind_Poly_Pixel and 15

C = (TempC + DestC)/2

C = C + Basecolor

Pset(x,y),C What this does for every pixel is to average the polygons color with the color behind it(the screen or buffer) and add it

Page 61: Third dimension math calulcation

to the basecolor. The basecolor is the starting color for each gradient. Ie. (0-15): 0 is the base color; (16 to 31): 16 is the base color. Hence the AND 15. Of course, you can make it a 32 color gradient and AND it by 31. :*) Here's a little demo of Box translucency I made for my Bro. Hex. ;*)

Transhex.bas

Here's the 3d translucency demo:

Transluc.Bas

Final Words:

To make good models, use a 3d modeler and import it as an OBJ file as it's easy to read 3d Obj files. Lightwave3d and Milkshape3d can import their models in the OBJ format. In fact I made a loader myself. ;*) Note that some models does not have textures, notably, Ship.l3d, fighter.l3d, etc. The only ones with saved textures are Cubetext, Maze2, TriforcT, and Pacmaze2.

Zipped with OBJs:

LoadObj.zip

Bas File:

LoadL3d.bas

This article is just a stepping stone for you into bigger things like Matrices, viewing systems and object handling. I hope you learned something from this article as this took me a while to write. Making the example files felt great though. :*) Any questions, errors in this doc, etc., you can post questions at http://forum.qbasicnews.com/. Chances are, I would see it there.

Next article, I will discuss Matrices and how to use them effectively on your 3d engine. I would also discuss polygon clipping and probably, if space permits, 3d viewing systems. So bye for now, Relsoft, signing off...

Page 62: Third dimension math calulcation

http://rel.phatcode.net/

[email protected]

Credits:

God for making me a little healthier. ;*)

Dr. Davidstien for all the 3d OBJs.

Plasma for SetVideoSeg

Biskbart for the Torus

Bobby 3999 for the Phong sub

CGI Joe for the original polyfillers

Blitz for the things he taught me.

Toshi for the occasional help

Matrices are your friends

Introduction

Matrices are useful in 3d graphics. Not only are they fast, but once you get used to them, it makes things a lot simpler. Matrices are better than standard equations for these reasons:

1. Actions(transformations) such as scaling, rotation and translation can be easily kept track because you only need to keep track of the matrix and forget about your 3d coordinates.

2. It's a one-pass transform. Which means you can make as many matrices as you want and combine it in one single matrix to do all the transformations for you. Simplicity at its best!!!

3. You are not limited to just the XYZ angle system when

Page 63: Third dimension math calulcation

viewing your virtual world. You could make a Lookat function to make things even simpler!!!(I'll get to that in another article)

For this article, I'm going to discuss matrices and their applications. I'm gonna start with the use of matrices in solving systems of linear equations, the basic operations on matrices and their applications in 3d graphics. I might be able to put in some code and algos in between. Don't worry, matrices are not as hard as you thought they are. :*). I'm gonna be discussing those things you'd need in making a 3d game engine using matrices. Which means that most matrix stuff I'm going to include in here are the easy ones. So without further ado....

Systems of linear equations Remember the linear equation... ax + by = c? No? How about this? y = mx + b? This two equations are just two of the many forms of linear equations. The first one(ax+by=c) is the "standard" form and y=mx+b is the "slope-intercept" form. We'll be discussing the standard form when dealing with matrices.

Given 2 equations: 2x - 3y = 1 and 3x + 2y = 8

How do you get the solution to both equations? The solution is actually the "intersection" of both lines defined by the above equations. For those who have done some algebra, we know that there are a number of ways to find the solution.

They are: 1. Graphing 2. Substitution 3. Elimination Solving via elimination: 2x - 3y = 1 equ 1 3x + 2y = 8 equ 2 Make the coeficients of x the same by multiplying equ 1 by 3 and equ 2 by 2 then subtract.

Page 64: Third dimension math calulcation

3*[2x - 3y = 1]*3 2*[3x + 2y = 8]*2 6x - 9y = 3 6x + 4y = 16 ------------------ - 13y = -13 ---> /-13 y = 1 Using back substitution(equ 1): 2x - 3(1) = 1 2x = 1 + 3 --->/2 x = 2 The solution is (2,1)

However when made into code, this is very cumbersome and not flexible. What we need is an algorithmic way to solve the system. And the answer is the matrix. How do we go about solving this system using a matrix? First let me discus the ECHELON method of solving this system as it's almost parallel to the matrix method except for the last part. Solving via the Echelon method: 2x - 3y = 1 equ 1 3x + 2y = 8 equ 2 1. Multiply both sides of equ 1 by 1/2 so that x will have a coefficient of 1. x - 3/2y = 1/2 equ 3 3x + 2y = 8 2. Eliminate x from equ 2 by adding (-3) times equ 3 to equ 2. -3 *[x - 3/2y = 1/2]* -3 = -3x + 9/2y = -3/2 3x + 2y = 8 --> make 2y and 8 similar to 9/2 y and -3/2. ie. 2y= 4/2y ; 8 = 16/2 -3x + 9/2y = -3/2 3x + 4/2y = 16/2 ---------------------- 13/2y = 13/2

Page 65: Third dimension math calulcation

y = 1 equ 4 so. x - 3/2y = 1/2 equ 3 y = 1 equ 4 * Using back substitution in equ 1, x = 2. * look at the coefficients of x and y. They both have coefficients of 1 arranged diagonally. ie..

1x + y = c 1y = c This is called the TRIANGULAR or LOW ECHELON form of the system. Be sure to remember this as we well be encountering this a lot of times. And now, what you've been waiting for!!!! Solving the system the Matrix way!!!

RULES:

A. Any two rows may be interchanged. This is useful if one of the equations' x-term has a coefficient of 1.

B. The Elements of any row may be multiplied by any non-zero real number.

C. Any row may be changed b adding to its elements a multiple of the elements of another row. 2x - 3y = 1 equ 1 3x + 2y = 8 equ 2 1. We first write the system in rows and columns. This is called the AUGMENTED matrix. Be sure that each equation is in standard form (ax + by = c). * This is parallel to the Echelon method above so be sure to check from time to time.

2 -3 1 3 2 8

*Note we only used the numeric coefficients. *2, -3, 1 , 3, 2 and 8 are called the ELEMENTS of the matrix and 2 is located at row1,col1; 8 at row2, col3; and so on...

Page 66: Third dimension math calulcation

2. To get 1 in row1, col1 we multiply the first row by 1/2.

1 -3/2 1/2 3 2 8

3. Add (-3) times the elements of row1 to row2.

1 -3/2 1/2 0 13/2 13/2

4. To get 1 in row 2, col 2; multiply row 2 by the reciprocal of 13/2 which is 2/13.

1 -3/2 1/2 0 1 1

So... x - 3/2y = 1/2 y = 1 See, triangular form!!! Use back substitution to get x. I'll test your skills with a 3-equation system:

x + y - z = 6 2x - y + z = -9 x - 2y + 3z = 1 We don't need to change the element i r1,c1 since it's already 1. BTW, you can interchange any rows as you like if it makes the solution easier(RuleA). Augmented matrix:

1 1 -1 6 2 -1 1 -9 1 -2 3 1

Page 67: Third dimension math calulcation

1. Eliminate the first element in row 2 by adding (-2) x row 1 to row 2.

1 1 -1 6 0 -3 3 -21 1 -2 3 1

2. Eliminate the first element in row 3 by adding (-1) x row 1 to row 3.

1 1 -1 6 0 -3 3 -21 0 -3 4 -5

3. To get 1 in row2,col2; Multiply row 2 by -1/3.

1 1 -1 6 0 1 -1 7 0 -3 4 7

4. Eliminate the second element in row 3 by adding (3) x row 2 to row 3.

1 1 -1 6 0 1 -1 7 0 0 1 16

5. Translating this matrix to equation form:

x + y - z = 6 y - z = 7 z = 16 (This is the triangular form of the equations) *The method I discussed above is called the

Page 68: Third dimension math calulcation

"GAUSSIAN REDUCTION". If you don't know who Karl F. Gauss is then this article is not for you. :*) j/k

Properties of Matrices It is customary to name Matrices with capital letters. The following is Matrix A.

a11 a12 a13 a14 A = a21 a22 a23 a24

a31 a32 a33 a34 a41 a42 a43 a44

With this notation, the first row and first column is a11(read: "a sub one-one). Matrices are classified according to size(by the number of rows and columns they contain). For example matrix A is a 4 x 4 Matrix. On the other hand our matrix solution above is a 2 x 3 matrix:

1 -3/2 1/2 0 1 1

Certain matrices have special names like a SQUARE matrix as in 3 x 3, 2 x 2, Basically the same number of row and columns. A ROW matrix on the other hand is just a matrix of one row. Guess what a COLUMN matrix is? :*)

Operations on Matrices A. Addition of matrices To add two matrices together, add their corresponding elements. ONLY MATRICES OF THE SAME SIZE CAN BE ADDED. Same goes for subtraction.

5 -6 8 9

+

Page 69: Third dimension math calulcation

-4 6 8 -3

=

5+(-4) -6+6 8+8 9+(-3)

=

1 0 16 -6

B. Multiplication of a Matrix by a scalar value. To multiply a scalar value by a matrix, you just multiply all elements of the matrix by the scalar value.

5 * 2 -3 = 10 -15

0 4 0 20

C. Multiplication of Matrices RULE. You can only multiply matrices if A has the same number of columns as B. ROW by COLUMN. Ohh this is gonna be *messy*. :*) Given:

A = -3 4 2 5 0 4

6 4 B = 2 3

3 -2

Page 70: Third dimension math calulcation

1. Locate row1 of A and col1 of B, then multiply corresponding elements and add the products. Row 1 of A

-3 4 2

* Column 1 of B

6 2 3

=

(-3)(-6) + (4)(2) + (2)(3) = 32

2. Next Row1 of A by Col2 of B

(-3)(4) + (4)(3) + (2)(-2) = -4

+ + = 3. Row 2 of A and Col 1 of B

(5)(-6) + (0)(2) + (4)(3) = -18

4. Lastly, Row 2 of a and col 2 of B

(5)(4) + (0)(3) + (4)(-2) = 12

The product matrix:

32 -4 -18 12

Page 71: Third dimension math calulcation

In general... Cij = Ai1*B1j + Ai2*B2j ...

For Square matrices, there are two ways to multiply as they have the same number of rows and columns. Warning: Multiplication of matrices is not commutative.

So in two 4*4 matrices, the resulting matrix is calculated as... QBcode:

SUB Matrix.MulMatrix (M!(), TM!()) 'Combines 2 matrices M!() and TM!() 'ie. Result = TM x M 'Warning matrix multiplication is not commutative. 'M x TM <> TM x M DIM Result!(1 TO 4, 1 TO 4) 'resultant matrix to be copied to M!() FOR i = 1 TO 4 FOR j = 1 TO 4 Result!(i, j) = 0 FOR k = 1 TO 4 Result!(i, j) = Result!(i, j) + TM!(i, k) * M!(k, j) NEXT k NEXT j NEXT i

Now that we know how to do stuff with matrices, we will now learn its applications is 3d graphics!!!! Woot!!! In 3d graphics its often easier to make the origin(0,0,0) as your viewpoint. ie. Where the lens of your camera is(The LEFT-HANDED system lends itself well with this if you want to make a doom-like engine). Instead of making the camera move around your world, you can make your world move around the camera. Relativity at its best!!!

Your first coordinate

Page 72: Third dimension math calulcation

Unless you want to do shearing and some other special effects, it's convenient to represent your 3d point/vector as [x y z]. I like to make this vector as a column matrix(see column matrix above). Modifying the points position is space requires another matrix. For simplicity, I'll make the the transfomation matrix a ROW matrix, [A B C]. To transform a point, you just multiply the our [x,y,z] vector with the with our ROW matrix. Remember our matrix multiplication property? COLUMN x ROW.

x y * A B C z

= x*A + y*B + z*C Now if a = 1, b=0, c=0 x*1 + y*0 + z*0 = x If a = 0, b=1, c=0 x*0 + y*1 + z*0 = y If a = 0, b=0, c=1 x*0 + y*0 + z*1 = z In matrix form:

1 0 0 --->x row vector A 0 1 0 --->y row vector B 0 0 1 --->z row vector C

*Notice how it looks like the "Triangular" form of the matrix. :*) So to transform and entire 3d point by the vector matrix using the matrix notation above, you just multiply the point vector by the matrix. *x',y',z' are the new points.

x m11 m12 m13 x' y * m21 m22 m23 = y' z m31 m32 m33 z'

Page 73: Third dimension math calulcation

= x' = x*m11 + y*m12 + z*m13 y' = x*m21 + y*m22 + z*m23 z' = x*m31 + y*m32 + z*m33 Now as we will be using a 4x4 matrix, let me introduce you to the matrices we'll be using.

1. The IDENTITY matrix Our initial "do-nothing" matrix. This means that it will produce exactly the same values as was before the transform. We always begin with this matrix.

1 0 0 0 ---> x'= x

0 1 0 0 ---> y'= y

0 0 1 0 ---> z'= z

0 0 0 1

Unless you'd want to do some nasty FX like Shearing,etc., the last row is always 0 0 0 1. 2. The Scaling matrix

Scales the matrix by sx,sy and sz.

sx 0 0 0 ---> x'= x * sx

0 sy 0 0 ---> y'= y *sy

0 0 sz 0 ---> z'= z * sz

0 0 0 1

3. The Translation matrix. Translation means just to "move" the point by Tx, Ty, Tz.

Page 74: Third dimension math calulcation

1 0 0 Tx ---> x'= x + Tx

0 1 0 Ty ---> y'= y + Ty

0 0 1 Tz ---> z'= z + Tz

0 0 0 1

4. The X-axis rotation matrix

Rotates the points in the x-axis by angle. ca = COS(angle) sa = SIN(angle)

1 0 0 0 ---> x'= x

0 ca -sa 0 ---> y'= ca * y - s*z

0 sa ca 0 ---> z'= sa * y + ca * z

0 0 0 1

5. The Y-axis rotation matrix

ca 0 sa 0 ---> x'= ca * x + sa * z

0 1 0 0 ---> y'= y

-sa 0 ca 0 ---> z'= -sa * x + ca * z

0 0 0 1

6. The Z-axis rotation matrix

ca -sa 0 0 ---> x'= ca * x - sa * y

sa ca 0 0 ---> y'= sa * x + ca * y

Page 75: Third dimension math calulcation

0 0 1 0 ---> z'= z

0 0 0 1

Note that the axis of rotation is NOT being transformed in the 3 rotational matrices. Now with all the abstract math out of the way, the fun part....

Applications To make a 3d object rotate around space using matrices, You just combine all the transformation matrices into one final parent matrix and use that matrix to transform your points. Here's the PseudoCODE:

PseudoCODE:

Matrix!() is our final combined matrix Tmatrix!() is a temporary matrix used for transformation. Dim Matrix!(1 to 4, 1 to 4) Dim TMatrix!(1 to 4, 1 to 4) 1. Set Matrix! and Tmatrix as Identity matrices. 2. Set Tmatrix! as Scaling matrix 3. Multiply Matrix! and Tmatrix! 4. Set Tmatrix! as Translate matrix 5. Multiply Matrix! and Tmatrix! 6. Set Tmatrix! as RotX matrix 7. Multiply Matrix! and Tmatrix! 8. [6] but RotY then [7] 9. [6] but ROTZ then [7] 10. For i =0 to numpoints 11. TransformPoints using Matrix 12. Project points 13. next i

*Codes 2 to 9 can be interchanged in any way you want. If you have normals you'd like to rotate, you can use the same transformation matrix to rotate them. I urge you to experiment and play with the order of operations so that you may see how it changes the entire transformation.

Page 76: Third dimension math calulcation

Here's the working QBcode for you to enjoy. MatrxRot.Bas

As an excersise, why don't you make a gouraud filled polygon using matrices to rotate your model and normals? Be sure to rotate with the same matrix. You might say, "But your rotation tutorial has some very fast ready-made matrix constants. It's also fairly faster as we don't have to multiply matrices." Yes those constants are faster than the matrix method but they are limited. Here are some limitations: 1. Those constants have a fixed order of rotation(x,y and then z). If you want to change the order of rotation, you need to do the "messy" factoring I did again. With matrices all you have to do is change the order and that's it. Simple as it can get. 2. To translate points from the camera, you have to subtract your camera vector from your transformed points manually. The matrix way would just use the translation matrix. 3. To translate from the origin, you'd have to subtract a translation vector manually from the original non-rotated points while with matrices, you just translate before rotate. :*) 4. Those constants are limited to angular viewing systems while matrices can handle any viewing system. Most popular of them is the LOOKAT transform(I'll get to that in the next article). 5. The speed difference is not apparent considering all the calculations in both methods are *outside* your rasterizing loop. I never lost a single frame myself. The more the points to transform, the less difference it makes. Some of you may have already seen Matrices defined like this: Translation matrix:

1 0 0 0

Page 77: Third dimension math calulcation

0 1 0 0

0 0 1 0

Tx Ty Tz 1

This is the DirectX and OpenGL system of matrices where rows are swapped with the columns. So be sure to use this system if you're coding via DX or OGL. I'm using the "standard" math notation in this article. Credits: Mark Feldman for his matrix doc. ( I was having problems with the rotation matrices until I read your doc. Thanks!!!) Plasma for SetVideoSeg wildcard for this "series" idea. Hugo Elias for his WuPixel doc. Toshi for his kind comments. 3D ica for there excellent doc. And you the reader of this doc. Biskbart for the torus. Happy Coding!!!! Richard Eric M. Lope (Relsoft) http://rel.phatcode.net/ [email protected] *For questions and suggestions, I hang out at Qbasicnews.com ---> forum. Http:://Forum.Qbasicnews.com

3D Series Chapter 5: Lookat

by Rel (Richard Eric M. Lope)

I. Introduction

Page 78: Third dimension math calulcation

One of the hardest part in making an article is actually starting one. Here I am staring at the monitor for 15 minutes doing nothing but listening to smashing pumpkins. Greatest band (the coolest!!) ever. I can't say the same for zwan though, as they sound so gay. C'mon Corgan!!! Return to your roots!!!

Okay, enough out of topic babble. This time, I'm going to discuss about 3d viewing systems. I would like to reiterate that you should have read my previous articles in 3d before reading this primer as this builds around those previous chapters. If not, here are the links:

• Chapter 1: Entering The 3D Dimension • Chapter 2: Rotation -- The How's and Why's • Chapter 3: Vectors Are Cool! • Chapter 4: Matrices Are Your Friends • Download the first four tutorials • Genso's Junkyard (Relsoft's website)

II. What is a viewing system?

A viewing system is a way to manage your 3d renders easily with a set of rules. This time we are going to use the left-handed system and the lookat transform. This by far is the easiest way to handle a 3d viewing system.

III. Types of viewing systems

There are actually numerous types of viewing systems. But all of them revolve around 3 mother systems.

1. Euler transforms

This is the pitch, yaw, roll way of viewing your world. This is what we have been using all along. Rotation from x, y and z axes. Since it's been already discussed in previous chapters, I won't try to delve on the subject much. This system, although much more "natural", has some major flaws.

a. You need 3 angles and the mouse only returns a 2d ordered pair(x, y). b. Angles are hard to visualize. c. They are prone to "gimbal" lock where angles cancel each other out. The result is

vertigo. Although I have never experienced my engines locking, it's better to be safe than sorry.

2. Quaternions

This my friend, is a way of representing vectors using quats. My opinion is, with you can do with quats, you can do with vectors and matrices. Quats are extensions of Complex numbers. Where complex = i, Quats = i, j, k. You you know how to deal with complex numbers you'd know how to deal with quats. It's just standard algebra if you can remember your (FOIL) technique in multiplying binomials. They're not that hard, and

Page 79: Third dimension math calulcation

lots of people use them not even knowing how to do quat arithmetic, since there are numerous premade stuff on the net to do Quat operations. :*)

Pros:

a. Eliminates gimbal lock b. Great in intepolation c. Sounds cooler

Cons:

d. Probably a fad/trend e. You still have to convert from euler angles> quats > matrices to transform points. f. Most open domain quat operations are in C and this is a QB mag. :*)

3. The lookat transform

This is what I will be discussing in detail from this point onwards. After making countless, looking good, running well 3d engines, I became sick of managing angles. So I asked myself: "What if I there is a way to transform your world space using just 2 points?". The reasons being that, in the real world we only need two points to look. The one we are looking at and the one looking (that's us).

I must admit that I didn't know what the lookat transform does back then as I only heard it from UGL's U3d module by blitz and v1ctor (not their first names). And since only they use it and it's not opensource, I had no way of knowing that it was what I needed. So what I did was try to think of some ways that I could transform my points using 2 coordinates.

Since I know that transforming a point in 3d space requires 3 vectors, all I needed to do was to find values for these vectors. Now I know my two points/coords. Say the camera point and the point we would look at:

(cx, cy, cz) = camera(eye)

(tx, ty, tz) = target(what we are looking)

Finding the 1st(forward) and 3rd(right) vectors are easy enough. To find the vectors:

Forward = target - camera

or:

Forward.x = target.x - camera.x Forward.y = target.y - camera.y Forward.z = target.z - camera.z

Page 80: Third dimension math calulcation

Right = Cross(Forward x Up)

This is assuming I already know my up vector.

The problem is, how the hell do I find the up vector? At that time, I had no idea. My first solution is to align the forward, up and right vectors with the x, y and z axes. Not good enough since I have to use euler angles again. Next was to ask my friends at Qbasicnews on how to find the up vector. No one was able to give me the right answer, as all the links posted pointed to dead ends. Them I tried to "guess" the up vector with nasty results. I was almost about to give up on the matter when I saw a code made by TOSHI HORIE on how to find the right vector!!! After reading the code, I saw that the solution was staring me in the face that after reading the code, I would have liked to kick myself where it hurts a lot. :*(

The solution was actually very simple: "The up vector is your Y-AXIS!!!". Yep good ol' (0,1,0). "Would somebody kick my ballz?" j/k. So I now know how to calculate all the vectors, the only thing that remains is aligning all the vectors to your camera coord. This job is handled by the vector operations:

1. Cross product 2. Dot product

Page 81: Third dimension math calulcation

3. vector projection

See, I told you to read the previous chapters. These operations are discussed in detail in Chapter 3 of the series. So to make a matrix which transforms the points using the camera vector:

Pseudocode: 1. Find forward vector 2. Normalize it 3. Make your up vector (0,1, 0) 4. Align your up vector into the camera origin by subtracting the vector projection of forward from Up. 5. Get their cross product to get the right vector. 6. Smack those values in the transformation matrix and transform your points.

To align the up vector to the camera origin, we need to find the projection of U(up) to F(forward) by dropping a perpendicular from U's head to F. This vector, which actually lies in the direction of F is the projection of U to F. Now what good would this be? Well we could get the Y(up) component of the Up vector be subracting the X(forward) component thereby, aligning the Up vector with the Forward vector's origin. The resulting vector, after Y is copied to Up is a vector perpendicular to the Forward vector.

4. Vector projection

5. Perpendicular vector after aligning

Page 82: Third dimension math calulcation

Here's the lookat function I made. Be sure to note of the up vector as we will have fun with it later on. :*) Matrix.Setcamera is just a function to smack the lookat transform's vector components to a matrix.

Now you only have to use the resulting matrix to transform your points and they would orient themselves the way we wanted to.

QB code:

SUB Matrix.Lookat (M!(), Target AS Vector, Camera AS Vector) STATIC 'This sub returns a trasformation matrix defined fom 3 vectors U,F,R 'This type of viewing system is perfect for FPS's. ;*) 'I intentionally left out the roll angle since I have really no use for it. DIM F AS Vector 'Forward vector DIM U AS Vector 'Up vector DIM R AS Vector 'Right vector F.x = Target.x - Camera.x F.y = Target.y - Camera.y F.z = Target.z - Camera.z Vector.Normalize F 'normalize forward vector U.x = 0 U.y = 1 U.z = 0 Vector.Normalize U

Page 83: Third dimension math calulcation

Dot! = Vector.Dot!(F, U) U.x = U.x - F.x * Dot! 'Align U to F U.y = U.y - F.y * Dot! U.z = U.z - F.z * Dot! Vector.Normalize U 'normalize the Up vector Vector.Cross R, U, F 'R = normal to plane f and u Vector.Normalize R 'Set up camera matrix Matrix.SetCamera M!(), R, U, F END SUB SUB Matrix.SetCamera (M!(), R AS Vector, U AS Vector, F AS Vector) ' [ Rx Uy Fz 0 ] ' [ Rx Uy Fz 0 ] ' [ Rx Uy Fz 0 ] ' [ 0 0 0 1 ] Matrix.SetIdentity M!() M!(1, 1) = R.x M!(1, 2) = R.y M!(1, 3) = R.z M!(2, 1) = U.x M!(2, 2) = U.y M!(2, 3) = U.z M!(3, 1) = F.x M!(3, 2) = F.y M!(3, 3) = F.z END SUB

Now that we know how to transform points using the lookat transform, we would need to design a systems based on the mouse coordinates. Since the mouse has only 2d coords, we only need 2 angles to find a point in 3d space. How do we do that? Well, if you have read chapter 3 of the 3d series I made, it would certainly occur to you that we have to use

Page 84: Third dimension math calulcation

the spherical coordinate system. I told ya. :*) For those who have forgotten the equations in converting spherical coordinates to rectangular coords:

CamLookAT.x = SIN(Phi!) * COS(Theta!) CamLookAT.z = SIN(Phi!) * SIN(Theta!) CamLookAT.y = COS(Phi!)

Where phi! = (Elevation) the angle against the horizon or your mousey and theta!=(azimuth) is the 2d angle that you can make from the horizon or mousex (think of a rainbow).

How do we get Phi and Theta correctly when there are 360 degrees(2PI) in one revolution and the maximum coords of the mouse are just 319 and 199 respectively (in screen 13)? The answer again is conversion. For those of you who have done Allegro GFX and some Democoding you probably already have heard of angles ranging from 0 to 255 or 0 to 512 or any maxvalue which is a power of two. Don't worry we will not use those values but we will in fact, use the same hack to interpolate our angle increments. :*) Here's the formula:

Actualangle = 2*PI/Maxangle

Where:

Actualangle = the value we would pass as an argument to the trig functions SIN and COS.

2*PI = duh? It's one revolution

Maxangle = would either be 320 or 200. :*)

Here's the code to convert mouse coords to spherical angles. Modified a lil bit to work seamlessly. :*)

Theta! = 2 * -PI * (MouseX) / 320 'Azimuth Phi! = PI * MouseY / 200 'elevation CamLookAT.X = COS(Theta!) * SIN(Phi!) 'Spherical system CamLookAT.Y = COS(Phi!) CamLookAT.z = SIN(Theta!) * SIN(Phi!)

Movement (Translation) is just a matter of understanding vector mathematics I discussed in Chapter 3 (This is Chapter 5 already). To move, we need our starting point (Camera Position), the lookat vector (Camera Lookat), and the speed we would like to move.

To get the speed (Magnitude), we multiply the lookat vector by a scalar value. ie. Scalar multiplication.:

Page 85: Third dimension math calulcation

xmove = CamLookAT.X * speed ymove = CamLookAT.Y * speed zmove = CamLookAT.z * speed

To walk forward, we subtract the speed of lookat vector from our camera position (Note that speed here is 3):

Campos.X = Campos.X - CamLookAT.X * 3 Campos.Y = Campos.Y - CamLookAT.Y * 3 Campos.z = Campos.z - CamLookAT.z * 3

To move backward do the reverse:

Campos.X = Campos.X + CamLookAT.X * 3 Campos.Y = Campos.Y + CamLookAT.Y * 3 Campos.z = Campos.z + CamLookAT.z * 3

Since we translated the origin(the camera postion in world space, we also have to translate the origin of our camera lookat vector or our render wouldn't look nice. For that we add the camera position to our lookat vector. ie. vector addition.

CamLookAT.X = CamLookAT.X + Campos.X CamLookAT.Y = CamLookAT.Y + Campos.Y CamLookAT.z = CamLookAT.z + Campos.Z

Now we are ready to transform!!!

Pseudocode:

1. Calculate spherical angles using mouse coords 2. Convert spherical to rectangular coord and put the resulting values on our lookat vector. 3. Move the camera depending on the input. 4. Translate the lookat origin to the relative to the camera origin(vector add) 5. Translate the matrix using the camera origin 6. Transform the matrix using the lookat transform 7. Transform your points. 8. Draw

Page 86: Third dimension math calulcation

That's it!!! Things to remember though is that the origin of rotation is the camera position. What this means is that the camera is not moving but the world space is moving relative to the camera (Einstein). ;*)

The "Puke" cam

The word puke cam came from a review of Spy Hunter on the Ps2/Xbox I saw on TechTV. They called it puke because it would really puke you out of your lunch if you play the game using that mode. You are racing normally but with a rotating camera. Now if that's not gonna make you puke, I don't know what will.;*)

You might think that this mode is as useless as your worn out socks but think of a plane doing a roll, and a car travelling on an angled road. Surely it would be nice to have a way to roll the world around your forward vector. "No! not another boring vector operations again!". Hardly. :*). Implementing a roll on the camera is just plain fun and plain easy. All we have to do is change the UP vector's orientation. "But up is (0,1,0) right?". Yep, but what if we change the up vector to any orientation we want? Well, it turns out that doing something with out up vector permits us to roll the camera. How do we roll the camera? Easy, use the polar to cartesian coordinate conversion.

'Calculate roll angle ra! = RollAngle% * 3.141593 / 180 U.x = COS(ra!) U.y = SIN(ra!) U.z = 0

Clippin' it

The clipping algo that I would introduce here is the easiest one. By easiest doesn't mean it's sucks. Since the ASM triangle filler I used already implements scanline clipping, I should know since I made it. :*), the only clipping we have to do is a near/far clip and 2d x, y clip. Here's the algo.

For every poly if all z coords are > 1 if all z coords are < Farthest distance if some x> 0 or some y >0 or some x <319 or some y<199 Poly draw =True end if end if end if next