Introduction
The purpose of this tutorial is to teach you the fine art of making a Flash 3D-engine. What this means is that we are going to find out how to take a set of 3D coordinates and transform them into 2D coordinates.
Limitations
My engine has a few limitations, some because this is a simpler "tutorial version", some because I was too lazy to implement the functions, some because the functions were to processor-consuming and last but not least those that I couldn't figure out:
- no shadows
- no solid faces
- no curbs
- etc.
The last thing I would like to say is that making a functional 3D engine is a kind of test, and you'll feel much prouder having figured it out all by yourselves. So if you want to give it a try, just check out the math link.
View Plane
As I've already said, we want to transform a set of 3D points into a set of 2D points. To do this we must somehow eliminate the third coordinate, the depth (z)to only have the x and the y left. However we cannot simply discard the depth information because we would lose the perspective. To keep the perspective, we must chose a point of view, the eye of the observer, and a view plane on which all the points are drawn. then for each point we trace the line between the eye and the point. We then calculate the coordinates of the intersection of the line and the plane: these are the 2D-coordinates of the point. Is this a bit complicated? Don't worry, I've made a picture:
To make the calculations a bit simpler (and the 3D engine faster) I've chosen to put the eye on the z-axis (eyex=0 and eyey=0). The view plane is parallel to the xy plane, that way every point on the plane has a fixed z and the 2D coordinates of each point are the x and y of the corresponding point on the view plane.
The closer the view plane is to the object, the bigger it will appear. To change the zoom, we vary the position of the view-plane.
If you still haven't understood, I'm probably not such a good teacher and I can't help you anymore. However if you still want your engine you can just follow my step by step guideline and it will work. You should also take a look at the math page I link to here.
The Basic Movie
All right,let's start.We'll begin by making our basic bricks, the framework for our tutorial. This part will be as short as possible since I suppose that if you are trying this tutorial, you already feel quite at ease with flash.
These are the things we need: Start by creating a new movie approximately 400*500 in size.
Then create 3 layers: actions, buttons, and lines. Create 4 frames on the two last layers and insert ten blank key-frames on the actions layer.
Now name these blank key frames in this order:
- "model", for loading the points and lines
- "start", for initiating all variables, etc.
- "loop", for the basic program loop
- "loop2"
- "draw", for the drawing routine
- "rotx", for the rotation around the x-axis
- "roty", guess
- "rotz", same again
- "zoom", for calculating the zoom
- "drawline", the line-drawing routine
Buttons, Line & Stuff
The buttons
Now just create a simple button. It's only for the rotations and the zoom so you don't even need any second states. Now then again if you want to make it fancy, go ahead, no one will stop you.
Place 8 instances of this button on the buttons layer, towards the bottom of the movie. We'll define their actions later.
The line
This part is a bit more important since it determines whether your lines will be drawn correctly or not. Create a new movie clip and name it baseline. In it, draw a simple black line directed from the top left to the bottom right. Then select this line and set its length to 100px and its width to 100px.
Now you should have a line that's oriented 45° downwards. Also, the good thing about it is that it's lenght in pixels is equal to its length in percent, so you can easily set its size with SetProperty.
Now place one instance of this line on the lines layer. Name it baseline.
The extra stuff
Just add some simple stuff such as a title, your name and preferably mine too (or some kind of a small note saying "original idea by KStor, but now my much better version" etc.)
Also add an editable text-field. Name the corresponding variable zoomvalue. This will be used to display the value of the zoom.
The Math
This part of the tute is purely theoretical. It's a very simple explanation of the math I use (actually you'll notice it's not an explanation at all but just a couple of formulas)You don't have to read it if you don't care about how the tute works, but it's kind of interesting to know. On the other hand, if you want to know more, click here.
The math we use is coordinates geometry in space. Let's start out with some definitions.
Vector
A vector is an abstract kind of notion (that's an easy way of saying I don't know what it is) but very practical. It basically consists of a line drawn from the origin (0,0,0) to a point (x,y,z). It can also be thought of as a direction from one point to another: x units along the x-axis, y units along the y-axis and z units along the z-axis. The vector is defined by the three coordinates x, y and z.
Line equation
A line can be described as all the points aligned with two points A (0x,0y,0z) and B. That way, it is defined by its origin A and the vector (dx,dy,dz) going from A to B. The equation looks like this:
point = org + k * dir
or with the coordinates (x,y,z) = (0x,0y,0z) + k * (dx,dy,dz) ,k being any real number.
Plane equation
A plane is a surface that can be described by a normal vector (dx,dy,dz), ie a direction the plane is facinng, and a value k, indicating the position of the plane along this vector. The equation is:
point * normal = k
or with the coordinates (x,y,z) * (dx,dy,dz) = k, k being any real number.
Intersection of a plane and a line
The line equation looks like this: point = org + u * dir. We have to find the value of u corresponding to the intersection. Using the plane equation, we find
u = (k - org * normal) / (dir * normal)
We then plug this into the line equation and find the coordinates of the point.
The rotations
For the rotations we just apply the following formulas:
Rotatation about the x axis:
x' = x
y' = (cos é * y) - (sin é * z)
z' = (sin é * y) + (cos é * z)
Rotation about the y axis:
x' = (cos é * x) + (sin é * z)
y' = y
z' = -(sin é * x) + (cos é * z)
Rotation about the z axis:
x' = (cos é * x) - (sin é * y)
y' = (sin é * x) + (cos é * y)
z' = z
...é being the angle of the rotation.
The Model
The model is the way our 3D object is coded, the way we define it mathematically. As I said in the introduction my 3D-engine is used mainly for lines, ie two connected points. Therefore, my model consists of two main parts: the coordinates of the points and the lines to be drawn. Added to this are the eyez, the maximum zoom value and the original zoom.
The points
The points are coded in one long variable called points. They are coded this way:
"x1,y1,z1Rx2,y2,z2Rx3...Rxn,yn,zn"
xn, yn and zn are the x, y and z coordinates of point number "n". They contain three digits, so 50 will be coded "050", 6 will be coded "006", etc.
Each point is separated by an "R" to make editing easier.
You must also create a variable named totalpoints that contains the total number n of points.
The lines
The lines are defined by the numbers of their extremeties. For example the line from point 7 to point 11 will be coded "0711". They are stored in a large variable named line:
"p1p'1Rp2p'2Rp3...Rpmp'm"
pm is one extremity of the line number m, p'm is the second extremity. Each point is coded by two digits, so point number 2 will be coded "02" and point 11 will become "11". You'll notice that as a result, my engine only accepts 99 points. If you need more, it's easy to change. However too many points will slow it down quite a bit (I haven't tried).
You must also create a variable named totallines that contains the total number m of lines.
The Model (con't)
The other variables
To this we have to add three extra variables that define the general way the object appears.
First is the eyez, the z coordinate of the point of view. It should be negative. The further it is from the object, the less perspective there will be. In my example I set it to -500, my closest point being at -99. It's a pretty good value.
Second is the maxzoom. This value is the z position of the view plane when the zoom is 100 percent. Be careful so that this value always is bigger than the eyez but smaller than the minimum possible z coordinate for any of your points after a rotation. For example when I rotate my point at z=-99, it will get closer to z=-140, so my maxzoom has to be lower than say 150.
Third is the initial value of the zoom, set between 5 percent and 100. the variable is called zoom.
The cube
These are all the variables you need to set. Now I'm going to give you the variables corresponding to a cube. Open the first key-frame of the actions layer. Then set this:
Set Variable: "totalpoints" = "8"Now your model is set up and we can start building the engine.
Set Variable: "points" = "099,099R099,099,099R-99,099,-99R
099,099,-99R-99,-99,099R099,-99,099R-99,-99,-99R099,-99,-99R-99"
Set Variable: "totallines" = "12"
Set Variable: "lines" = "0102R0103R0105R0804R0806R0807R0506R
0507R0307R0304R0206R0204"
Set Variable: "eyez" = "-500"
Set Variable: "maxzoom" = "-200"
Set Variable: "zoom" = "75"
The Routine
Now comes the real part: the engine program itself.
The loading setup
First we are going to create the initiation routine, the one that starts it all out. Open the second key-frame of the actions layer. This is what we do:
- move the baseline instance so that no one can see it on the picture.
Set Property ("/baseline", X Position) = "1000"
- then let's cut the points variable up into lots of separate coordinate variables:
Set Variable: "n" = "1"
This places the x-coordinate of point 1 in variable x1, the y-coordinate in y1, the z in z1 and the same thing for all other points.
Loop While (n <= totalpoints) Set Variable: "x"&n = Substring (points,( (n-1) * 12 + 1), 3 ) Set Variable: "y"&n = Substring (points, ((n-1 ) * 12 + 5), 3 ) Set Variable: "z"&n = Substring (points, ((n-1) * 12 + 9), 3 ) Set Variable: "n" = n+1 End Loop - now we do the same thing for the lines variable:
Set Variable: "n" = "1"
The first end of point 1 is put in pt11, the second one in pt21, etc.
Loop While (n <=totallines) Set Variable: "pt1"&n = Substring (lines, (n-1)*5+1, 2 ) Set Variable: "pt2"&n = Substring (lines, (n-1)*5+3, 2 ) Set Variable: "n" = n+1 End Loop - set the center of the display. These are the coordinates of point (0,0) in the movie.
Set Variable: "centerx" = "190"
Set Variable: "centery" = "215"
The Routine (con't)
The loop
Next we are just going to set up the basic loop. This will be used later for the zoom and the rotations. Open the 4th frame loop2 and enter
Go to and Play ("loop")
The zoom routine (part 1)
We now have to make a basic zoom routine to transform the zoom value in percent into the z-coordinate of the view plane. We'll make the zoom buttons in a while. They way we do our conversion is to take a percent of the distance between the eyez (minimal zoom position) and the maxzoom position, and add the maxzoom. This way the z of the view plane takes values between eyez and maxzoom when the zoom takes values between 0 and 100 percent. Open the next to last keyframe zoom and enter this:
Set Variable: "depth" = - zoom * (eyez -maxzoom) / 100 + eyezThe depth variable contains the z-coordinate of the zoom plane. The draw command is the central routine of our engine, we'll build it soon.
Call ("draw")
Also add this command to set the zoomvalue variable so the zoom shows up.
Set Variable: "zoomvalue" = "zoom= " & zoom & "%"
The drawline routine
This is the last thing we have to do before the drawing routine it self. This routine will be used to draw the line linking two points. The way it works is that the baseline is duplicated and its size and position are adjusted to correspond to the points (it works, that's all that counts).
Set Variable: "pt1" = eval("pt1"&n)If the number of the line is stored in variable n, the line number n is drawn.
Set Variable: "pt2" = eval("pt2"&n)
Set Property ("line"&n, X Position) = eval("2Dx"&pt1)
Set Property ("line"&n, Y Position) = eval("2Dy"&pt1)
Set Property ("line"&n, X Scale) = eval("2Dx"&pt2) - eval("2Dx"&pt1)
Set Property ("line"&n, Y Scale) = eval("2Dy"&pt2) - eval("2Dy"&pt1)
The drawing routine
This is the very core of the engine: the program that converts the 3D coordinates to 2D coordinates (as explained in The Math). In the fifth frame, draw, enter this:
Set Variable: "normalz" = depth * depth
Normalz is the value k in the plane equation, depth being the z-coordinate of the normal vector of the view-plane (the plane being parallel to the xy plane, the vectors coordinates are (0,0,depth))Set Variable: "n" = "1"
This part is a bit more complicated. We actually convert the 3D coordinates to 2D coordinates. For each point, we first calculate the value u as explained in The Math. Then we recalculate the 2D coordinates by multiplying the 3d coordinates by u. I can't explain exactly what I've done here but if you study the math link I gave you should be able to understand it.
Loop While (n<=totalpoints) Set Variable: "u" = (depth - eyez)/(eval("z"&n) - eyez) If (n<=9) Set Variable: "2Dx0"&n = u * eval("x"&n) + centerx Set Variable: "2Dy0"&n = u * eval("y"&n) + centery Else Set Variable: "2Dx"&n = u * eval("x"&n) + centerx Set Variable: "2Dy"&n = u * eval("y"&n) + centery End If Set Variable: "n" = n + 1 End LoopSet Variable: "n" = "1"
Finally we just draw the the lines by duplicating the basic movie clip and calling the drawline frame.
Loop While (n<=totallines) Duplicate Movie Clip ("/baseline", "line" & n, n) Call ("drawline") Set Variable: "n" = n + 1 End Loop
The Zoom
Let's work out the zoom. We really don't have very much to do, since most of it has alreday been done. Each time the main loop is looped it adds a certain value to the zoom (or substracts). When you roll over the zoom buttons, they change the value added from 0 to a positive or negative value (zoom in or out). When you roll out they reset it to 0.
First open the start frame(n°2) and add this code just after the setting of the xcenter and the ycenter.
Set Variable: "zm" = "0"This initiates the zm variable, which indicates by how much the zoom should be changed.
Now open the loop layer and add this:
Call ("zoom")That way the zoom routine is called every time the engine loops. The draw routine is also called to refresh.
Call ("draw")
Let's continue the zoom routine. Open the next to last frame zoom. Normally we only have two commands: the calculation of the depth and the setting of the zoomvalue. Before this we are going to add a new command:
Set Variable: "zoom" = zoom + zmIf (zoom> 100)
Set Variable: "zoom" = 100
End If
If (zoom <10)> First we add the zm value to the zoom, then we check that it doesn't exceed 100% or go under 10% (under ten percent the object gets too small). Last we set the buttons: double-click on your zoom-in button (any one of your eight buttons). Then set these actions:
On (Roll Over)I don't think this needs to be explained: while the mouse is over the button, the zoom increases by 5 percent at every loop. Now set the same thing for the zoom-out button but with "zm"="-5"
Set Variable: "zm" = "5"
End On
On (Release, Roll Out)
Set Variable: "zm" = "0"
End On
The Rotations
This is the last thing we have to do to have our functional 3D engine. The rotation works in a similar way to the zoom. We set two basic basesin and basecos variables. These contain the values of sin 10° and cos 10°. Then while one of the rotation buttons is rolled over the coordinates of all points are rotated around the corresponding axis by 10° at each loop. The rotation frames work with a sin variable and a cos variable, so the zoombuttons only load values in sin and cos and set an axis variable to x, y or z depending on the axis that should be rotated around.
First we modify the start frame. After all the rest we first set the basesin and basecos:
Set Variable: "basecos" = 0.9848Then we call an initial rotation around the y-axis and the x-axis:
Set Variable: "basesin" = 0.1736
Set Variable: "sin" = basesin
Set Variable: "cos" = basecos
Call ("rotx")
Call ("roty")
Now we change the loop frame. Add this:
Call ("rot"&axis)This way the rotation is called at every loop. Notice that if axis=none then there will be no rotation since there is no rotnone frame.
Now we are going to do the buttons:
- open your first button (say the up-rotation around the x-axis). Enter this
On (Roll Over)
The sign of the sine determines which direction the object turns. When you roll over the button, the axis turned around is set to x, and the sin and cos are stored. When you roll out, the axis is set to none. The principle is the same for all the other buttons, but with different values.
Set Variable: "sin" = - basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "x"
End On
On (Release, Roll Out)
Set Variable: "axis" = "none"
End On - x-axis down rotation
Set Variable: "sin" = basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "x" - y-axis right-rotation
Set Variable: "sin" = - basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "y" - y-axis left-rotation
Set Variable: "sin" = - basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "y" - z-axis rotation(1)
Set Variable: "sin" = - basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "z" - z-axis rotation(2)
Set Variable: "sin" = basesin
Set Variable: "cos" = basecos
Set Variable: "axis" = "z"
The last thing we have to do to make our rotation functional is obviously to program the rot frames. This is a simple use of the math-formulas. Open the rotx frame and enter this
Set Variable: "n" = "1"If you check this out it corresponds to the formulas in The Math. Now do the same for the roty frame:
Loop While (n<=totalpoints) Set Variable: "tmp" = (sin * eval("y"&n)) + ( cos * eval("z"&n)) Set Variable: "y"&n = cos * eval("y"&n) - sin * eval("z"&n) Set Variable: "z"&n = tmp Set Variable: "n" = n + 1 End Loop
Set Variable: "n" = "1"and for the rotz frame:
Loop While (n<=totalpoints) Set Variable: "tmp" = - (sin * eval("x"&n)) + ( cos * eval("z"&n)) Set Variable: "x"&n = cos * eval("x"&n) + sin * eval("z"&n) Set Variable: "z"&n = tmp Set Variable: "n" = n + 1 End Loop
Set Variable: "n" = "1"That's all, your engine is finished!
Loop While (n<=totalpoints) Set Variable: "tmp" = (sin * eval("x"&n)) + ( cos * eval("y"&n)) Set Variable: "x"&n = cos * eval("x"&n) - sin * eval("y"&n) Set Variable: "y"&n = tmp Set Variable: "n" = n + 1 End Loop
Finished
OK, here it is. After hard work the engine is finally finished. You might not have understood everything, the math isn't really easy and I didn't explain very much. However if you study the math link you should be able to understand it pretty easily. To finish off here's some extra stuff I want to tell you:
Actionscript summary
This little paragraph is to deal about other 3D engines. I've seen a few, only two which draw lines and of the same level as mine. Their coding of points is equal or very similar to mine. Their lines were coded without the separating R to distinguish lines. This is both inconsistent with the points coding and not very practical. That's why I used my coding, and I suggest that it could be used as a preliminary standard for Flash 3D engines.
Download the files used in this tutorial. Download (39 kb)