Finis (3D) ... by Brandon Williams
Main Index...

After I finished my last 3d tutorial I felt that I should write one more because I did not want to end this series with something as unpractical as solid objects with backface culling and dynamic lighting. This one will be much more relevant. The great thing about this one is that we really are not going to be talking about any major math stuff so it should be much easier. This time I am going to talk about movement in 3d and creating a really simple "3d Flash world" for you to walk around in. But first I wanted to go through that really basic 3d sample file I gave out in my first tutorial. Even if you downloaded the file from the last tutorial download this new one because I improved quite a few things.

So if you have downloaded the file go ahead and open it up. On the stage you will see that I have a line, a movie clip with the text "3d stuff in here...", and some buttons. The line movie clip connects all the vertices of the box, the buttons control the rotations, and the other movie clip has the main actions. Let me first explain how my movie is structured.

The movie clip with the main actions only rotates points that I defined at the beginning. The line movie clip then grabs those points and connects them. Thats the gist of it...pretty easy. Now let me explain the actions in the "3d stuff in here..." movie clip.

I set up my box vertices in a certain order. It would be hard to explain so here is a picture:

I have an array, "pos", with elements 1 through 8 in it, which hold the positions of each point. Then inside each of those elements in the "pos" array I have another array with three elements. Element zero holds the x-position of the point, element one holds the y-positions, and element two holds the z. Next I have one more array called "ppos", which holds the x- and y-positions of the points when the perspective is added. Those are the numbers that the line script will grab and connect.

Now lets get into the actual script. The very first thing I have is a pretty long initializing script all within an 'onClipEvent (load)' handler. In there I had this first:
   var lineConnectString = new String ("122334415667788515263748");
   for (var j = 0; j <= 11; j++) {
         _root.line.duplicateMovieClip ("line"+j, j);
         _root["line"+j].pointSets = lineConnectString.substr (j * 2,2);
   }

Those few lines takes the string, "lineConnectString" and sends every two numbers to a duplicate of the line movie. The line movie clip then uses those numbers to get the coordinates of the points which it is supposed to connect. The way I came up with the "lineConnectString" is by looking at the picture from above. The script inside the line movie clip is pretty basic so I won't cover that.

Next, I had something like this:

   var width = 50;
   var height = 50;
   var depth = 50;
   pos = new Array ();
   pos[1] = new Array (-width, -height, -depth);
   pos[2] = new Array (width, -height, -depth);
   pos[3] = new Array (width, height, -depth);
   pos[4] = new Array (-width, height, -depth);
   pos[5] = new Array (-width, -height, depth);
   pos[6] = new Array (width, -height, depth);
   pos[7] = new Array (width, height, depth);
   pos[8] = new Array (-width, height, depth);


   ppos = new Array ();
   for (var a = 1; a <= 8; a++) {
         ppos[a] = new Array ();
   }

This sets up the vertices for the cube. You could change around the width, height, and depth variables to something different and make a rectangular box. The variable "pos" is a two-dimensional array which holds the x-, y-, and z-positions of the eight vertices. The "ppos" is also a two-dimensional array which holds the x- and y-positions of the points with the perspective added in. These are the coordinates which the line script will be retrieving. There may have been a better way to do this but I was not too concerned about it.

The rest of the 'onClipEvent (load)' script has to do with initializing constants and one function:

   dtr = Math.PI / 180;
   a = b = c = 0;
   function sineCosine (a, b, c) {
         sa = Math.sin (a * dtr);
         ca = Math.cos (a * dtr);
         sb = Math.sin (b * dtr);
         cb = Math.cos (b * dtr);
         sc = Math.sin (c * dtr);
         cc = Math.cos (c * dtr);
   }
   sineCosine ();
   d = 300;
   centerx = 275;
   centery = 200;

The variable "dtr" is for translating from degrees to radians. Flash likes radians, but it is much easier to work with degrees. If you want to rotate a point by one degree you simply increment a variable by one, easily done with the "++" operator. However if you wanted to rotate a point by the equivalent of one degree in radians you would need to increment an angle by 0.0174532925199433. Thats a little odd so I just do things in degrees and then translate to radians when I need to. The variables a, b, and c are the angle increments at which the points are going to be rotating. I set them to be zero initially.

The function in the above script finds the sine and cosine of all the angles. Note that I first translate the angles to radians. This function is called when the buttons are pressed that change the angle increments. The other variables are constants used for positioning the points. The variable "d" is the distance from your eye to the origin used for perception (like I discussed in my last tutorial "More 3D"). Those last two variables are used to make up for the fact that (0,0) on your computer screen is at the top-left hand corner. "centerx" and "centery" are the coordinates at which the points will rotate around relative to Flash's stage.

And that concludes the initializing script. Everything else is within an 'onClipEvent (enterFrame)' handler. Here is the script I used:

   for (var j = 1; j <= 8; j++) {
         // multiply positions by a x-rotation matrix
         rx1 = pos[j][0];
         ry1 = pos[j][1] * ca + pos[j][2] * -sa;
         rz1 = pos[j][1] * sa + pos[j][2] * ca;
         // multiply new positions by a y-rotation matrix
         rx2 = rx1 * cb + rz1 * sb;
         ry2 = ry1;
         rz2 = rx1 * -sb + rz1 * cb;
         // multiply new positions by a z-rotation matrix
         rx3 = rx2 * cc + ry2 * -sc;
         ry3 = rx2 * sc + ry2 * cc;
         rz3 = rz2;
         // set arrays to new positions
         pos[j][0] = rx3;
         pos[j][1] = ry3;
         pos[j][2] = rz3;
         // add perspective
         per = d / (d+rz3);
         tempx = rx3 * per;
         tempy = ry3 * per;
         // set perspective array
         ppos[j][0] = centerx + tempx;
         ppos[j][1] = centery - tempy;
   }

I left my comments in the script above but I'll still explain it. The above is the main script which rotates all the points around. I put everything in a "for" loop so I can rotate all eight points. The first nine lines rotate the points around the x-, y-, and z-axes. We have already derived the equations so that should look familiar. If you have not already, read this to learn why those equations work. After the rotations I then set the array "pos" equal to the rotated points to update everything. That right there is pretty much all there is to the 3d part of the script.

After the rotations you add the perspective. In my last tutorial I showed you how to add perspective to a point with an ordered triplet of (x,y,z). They were:

   // given (x,y,z) of a point (xp,yp) is the point with perspective
         x*d
   xp = -----
        (z+d)


         y*d
   yp = -----
        (z+d)

Now the reason I set a variable called "per" first is because that is the part that both the perspective equations have in common. All you do is multiply "per" by the "x" and "y" of a point and it is the same as the above two equations. This is also a small optimization because it gets rid of doing the same thing twice which includes a costly division. Then the next two lines I set the perspective arrays for the lines and add in the "centerx" and "centery" variables to center the box with Flash's stage. If I didn't add those two variables to the points' positions the box would be in the top-left hand corner.

And thats about it for the 3d part! See how simple that is? Next is the buttons. There is not really all that much to comment on the buttons so I'll just explain it briefly and let you look at the actions yourself. The only thing that needs done when a button is pressed is that the increment angle variable needs to be changed (either add one or subtract one) and then call the 'sineCosine()' function in the main movie clip with all the actions.

There you go. An extremely basic 3d script. Although I did not add in translation in that script it would be really easy too. You would need to change everything after the rotations to this:

   // translate points by Tx, Ty, and Tz
   fx = rx3 + Tx;
   fy = ry3 + Ty;
   fz = rz3 + Tz;
   // set arrays to new positions
   pos[j][0] = fx;
   pos[j][1] = fy;
   pos[j][2] = fz;
   // add perspective
   per = d / (d+fz);
   tempx = fx * per;
   tempy = fy * per;
   // set perspective array
   ppos[j][0] = centerx + tempx;
   ppos[j][1] = centery - tempy;

Once again pretty simple. Also something to remember is that my scripts could most likely be optimized more. Although I do not think you would see much difference in speed you could simplify it. The reason I wrote it the way I did was so it would be easy to read.

Second: movement in 3D

Just in case you could not tell, although I like this stuff I am not very good at it. I really do have trouble with this most of the time. And something that had me absolutely stumped was movement in 3d. I wanted to make something where a ball was bouncing around in a box in 3d...yes something seemingly simple. I could have done this as long as the box was still and completely upright like this:


That would be easy. But, what I was having trouble with is being able to rotate the box around and getting the ball to move in the same direction. Example: lets say the box was exactly how it is above and a ball was bouncing inside it. However, the ball was only bouncing back and forth, hitting the front face of the box, bouncing off, and then hitting the back face, and coming back. Now lets say that you move around and look at the box from the side (note: I am not talking about rotating the box but instead you)...if you were to look at the box from the side the ball would be bouncing side to side now instead of back and forth right? Well I really did not know how to do it. You wouldn't believe some of the things I was thinking of to do this...I don't even want to talk about because it was some pretty stupid stuff.

Well, in any case I finally figured it out and I was shocked at how simple it was. To do this we are not going to rotate points in the same fashion as we did before. In all the other files hitherto we have rotated a point by increments right? Meaning, if you created a file with the 3d stuff in a continuos loop and you rotated a point by "a" degrees the point would be constantly rotating because "a" only represents an increment.

This time, however, if you set "a" equal to a number other than zero it will rotate the point around "a" degrees only once. So if you want a continuos rotation you are going to have to keep incrementing "a." Here, I made another file to demonstrate what I was talking about: you can see it here and download it here.

If you look at the script there are only a few changes I made. The first was in the initializing script. I added this line:
   inca = incb = incc = 0;

Those variables are used for incrementing the rotation angles. After that I changed the way I set up my function for calculating the sine and cosine of our angles. In the previous file the rotation angles were hardly ever changing. The only time you needed to calculate sine and cosine was when you pressed one of the buttons. This time, however, if there is any rotation at all you need to calculate sine and cosine. Here are the functions I used to optimize this part:

   function angleA () {
         sa = Math.sin (a * dtr);
         ca = Math.cos (a * dtr);
   }
   function angleB () {
         sb = Math.sin (b * dtr);
         cb = Math.cos (b * dtr);
   }
   function angleC () {
         sc = Math.sin (c * dtr);
         cc = Math.cos (c * dtr);
   }
   // initialize
   angleA ();
   angleB ();
   angleC ();

Using those functions you only need to get the sine and a cosine of an angle when that particular angle is changing. Complementary to the above functions I add this just before the "for" loop:

   if (inca != 0) {
         a += inca;
         angleA ();
   } if (incb != 0) {
         b += incb;
         angleB ();
   } if (incc != 0) {
         c += incc;
         angleC ();
   }

First you check if there is any change in the rotation angles. If there is you increment the angle and call the function for that angle. So now we are only using Flash's math objects when it is absolutely necessary. Although I did not do it in my file a look-up table may be profitable here. Just something to try if you want. The last thing I changed in the main script was that I took out the three lines of script that looked like this:

   pos[j][0] = rx3;
   pos[j][1] = ry3;
   pos[j][2] = rz3;

Those lines were used to update the "pos" array. It was also those lines that made a, b, and c only increment rotation angles. Since we do not use those lines anymore the points are never really moving. We temporarily rotate them and plot them, but we never update the array so the points don't ever change. And finally, I changed the button actions to increment the increment variables (inca, incb, and incc) instead of the rotation angles.

So now we have it so that instead of constantly rotating our box's vertices by increments we rotate them by "world" angles, but the vertices never really change their positions. We simply rotate them into place, render, and then put them back. We can do the same thing for movement! Simply have an object move around like regular with x-, y-, and z-velocities and then take its new coordinates and rotate them into angles a, b, and c without ever changing the ball's actual position.

I made a demo of this a few weeks ago that you can see here. That file will also give you a chance to see the backface culling we discussed in my last tutorial in action. But, in that file I am using a kind of "reverse" culling that way you can see through the box. There are a few extra things going on in that file so I made a quick sample file for you. You can see it here and download it here. I didn't add any z-sorting so that may look a little weird because the lines are in front of the ball, and also I made it really quick so there may be some things wrong with it.

Although I commented the code *thoroughly* I am still going to explain the main idea behind what I did and tell you what's new in the script. First, I started with the "finis_td_d" that I just got through explaining as a kind of template for this new one. The only graphic I added was that blue ball on the center of the stage which is a movie clip.

Before I get to the actions for the ball let me tell you what additions I made to the main script inside the "td" movie clip...its the movie clip with the text: "3d stuff in here...". The first new line was this:

   _root.ball.bounds = new Array (width, height, depth);

That takes the width, height, and depth of the box and sends it to the ball script. The actions in the ball uses those values a lot and I didn't want to have to type out the long path every time. Next I added one more element to the "pos" array for the ball:

   pos[9] = new Array (0, 0, 0);

This will make the ball start at the origin. You could change it to whatever you want though. Something very important to note here is that if you wanted to add more points to rotate around you would need to make the ball's position *last* in the "pos" array. The main reason is because if you did not do that the script inside the ball would get messed up. You will see why in a minute.

The next thing that I changed in the initial script is that I pass along a variable called "n" to the ball, which is used to grab the position of the ball inside the "td" movie clip from the ball script. It is always equal to however many points you are rotating around including the ball:

   // how many points you are rotating including the ball
   numTimes = 9;
   _root.ball.n = numTimes;

And the last thing changed in the main script is when you are adding in the perspective:

   per = d / (d+rz3);
   tempx = rx3 * per;
   tempy = ry3 * per;
   if (j != numTimes) {
         // set perspective array
         ppos[j][0] = originx + tempx;
         ppos[j][1] = originy - tempy;
   } else {
         _root.ball._x = originx + tempx;
         _root.ball._y = originy - tempy;
         _root.ball._xscale = _root.ball._yscale = (_root.ball.r*2)*per;
   }

The first three lines of that is nothing new because you always need to do that stuff. However the next part is different. You have a conditional statement checking if the "for" loop increment variable is not equal to the total number of points you are rotating. Why? Because if "j" equals "numItems" that means the coordinates we just rotated are the ball's coordinates. If "j" does not equal "numItems" you are only rotating one of the box's vertices. So? Well if you are messing with one of the box's vertices you only need to update the "ppos" array for the lines. If you are messing with the ball's coordinates you need to do some "set properties" to render the ball. Easy right?

Now most likely you are still confused as to what is happening. I just wanted to cover what was different in the main script before I started explaining the main idea behind everything. So here is what I have going on. The main script, that handles all the 3d, rotates all the vertices of the box as well as one extra point which is the location of the ball. Initially the ball is at the origin (0,0,0). That is no big deal. But, the ball script is where I have all the movement happening. The script in the ball movie clip takes the newest coordinates of where it is supposed to be from the "td" movie clip, then adds a few values to its position (velocity), and finally sends the new coordinates to the "td" movie clip for rendering.

That's an overall view of it. Now lets discuss the actual script in the ball. I have this as an initial script (within the 'onClipEvent (load)' handler):

   vel = new Array (1, 3, 5);
   bpos = new Array ();
   r = this._width/2;

The first variable is an array called "vel" which holds the velocities on all three axes. We will be adding these values to the ball's coordinates to make it move. Next is another array called "bpos". You really do not need this but I used just to keep from getting mixed up. In the rest of the script I set the values of "bpos" equal to the coordinates of the ball plus the velocities, then I check for collisions using the "bpos" array, and finally update the "pos" array in the movie clip "td". And that "r" variable is just the radius of the ball. I used that in the main script for the rotations too.

That's all just the initial stuff. I also kind of outlined what the rest of the script is going to do. Here is the script (all within an 'onClipEvent (enterFrame)' handler):

   for (var c = 0; c <= 2; c++) {
         bpos[c] = _root.td.pos[n][c] + vel[c];
         if (bpos[c] + r > bounds[c]) {
               vel[c] *= -1;
               bpos[c] = bounds[c] - r;
         } else if (bpos[c] - r < -bounds[c]) {
               vel[c] *= -1;
               bpos[c] = -bounds[c] + r;
         }
         _root.td.pos[n][c] = bpos[c];
   }

This is the script which makes all the movement. Instead of dealing with all the axes separately I put everything in a "for" loop. Since all the variables we are dealing with are arrays its easy to do it in a loop because we can access the elements with the increment variable. When "c" is one we are dealing with the x-axis, when "c" is two the y-axis, and when "c" is three the z-axis.

The first thing in the "for" loop is setting "bpos" equal to the position of the ball plus the velocity. After you do that you check for collisions with the box's walls...that is what the conditional statements do. Remember that "bounds" is an array we set in the main script. If there is a collision you change the velocity for that axis to its opposite and you reposition the ball. Finally after all of that you update the "pos" array depending on what "bpos" equals.

And that is it. That is also a very basic model for motion in 3d. What if the walls were not perfectly upright but instead at a slant. The redirection of the ball would be different. I'm not going to explain that here because I was planning on writing something about dynamics which would cover that. However, if you are good with context then to do what I described above you would use the ball's velocity vector to find the vector parallel to the surface it is hitting and the vector normal to the surface and then simply add the two.

Third: A "3D Flash World"

Probably one of the most unpractical uses for Flash yet one of the coolest. This effect has become somewhat fashionable, popularized by sites like Vox Angelica as will as others. Even though you may be "awed" at first, hopefully after reading some of this you will see how to do stuff like that.

I made a quick example for some people in a Flashkit thread (ED. link unavailable) ... you can see it here. Walk around for a second and see if you notice what is going on. I also made a little bit nicer one which you can see here (ED. same file).

First, before we get started, download the source to that first example I linked to above here. Even if you already downloaded it from that thread at Flashkit (if you saw it) download it again because I changed a few things. If you open it up you will see all my graphics. The sky and ground are just gradients that have not been grouped as a symbol or anything, and the mountains are in a movie clip.

The tree is also a movie clip with the majority of the movie's script in there. Something important to note about the graphic inside the tree movie clip: the base of the tree is at the center of the movie clip. You do this because that is the point you are rotating around. What other point on the tree would you want to move around other than where the tree meets the ground? But, because we are doing this we need to make up the difference or else it will look like you are crawling around on the ground...I'll get to that later though.

Also shoved off somewhere to the side is a movie clip with the text "Key Capture" inside, which is used to set variables for rotations and translations depending on which key is pressed.

Nothing big so far. The first script we should look at is in the first frame of the layer called "init". If you open it up you will see these actions:

   for (var d = 1; d <= 20; d++) {
         tree.duplicateMovieClip ("tree"+d, d);
   }
   b = 0;
   Tz = 0;
   h = 30;
   dtr = Math.PI / 180;
   function sineCosine (b) {
         _root.sb = Math.sin (_root.b * _root.dtr);
         _root.cb = Math.cos (_root.b * _root.dtr);
   }
   sineCosine (b);

The first three lines do the obvious, which is to duplicate the trees. Then I initialize a few constants. The variable "b" is the rotation increment, "Tz" is used for translating along the z-axis (forward and backward movement), "h" is going to be your height while walking around, and "dtr" is for changing degrees into radians. The next few lines are for the function which calculates the sine and cosine of the rotation angle, once again *only* used when needed.

The next script we should look at is the one inside the "key capture" movie clip. Since this time we are dealing with rotation increments and translation increments again (as opposed to the "world" values like we discussed before this) our script is pretty easy. Here is the script I used:

   onClipEvent (load) {
         rotationInc = 2;
         translateInc = 10;
   }
   onClipEvent (keyDown) {
         if (Key.isDown (Key.LEFT)) {
               _root.b = -rotationInc;
               _root.sineCosine (b);
         } if (Key.isDown (Key.RIGHT)) {
               _root.b = rotationInc;
               _root.sineCosine (b);
         } if (Key.isDown (Key.UP)) {
               _root.Tz = -translateInc;
         } if (Key.isDown (Key.DOWN)) {
               _root.Tz = translateInc;
         }
   }
   onClipEvent (keyUp) {
         if (Key.getCode() == Key.LEFT) {
               _root.b = 0;
               _root.sineCosine (b);
         } if (Key.getCode() == Key.RIGHT) {
               _root.b = 0;
               _root.sineCosine (b);
         } if (Key.getCode() == Key.UP) {
               _root.Tz = 0;
         } if (Key.getCode() == Key.DOWN) {
               _root.Tz = 0;
         }
   }

Its kind of long but there's nothing to it. The first part initializes the increments for the rotation and translation. The next 'onClipEvent ()' checks where one of the keys are pressed. If a key is pressed you set either "b" or "Tz" on the main stage equal to one of the increments and call the 'sineCosine' function if needed. Then, the last 'onClipEvent ()' checks for when a key is released. When a key is released the the increment for that key is set to zero on the main stage and the 'sineCosine ()' function is called if needed.

Now for the major part of the script. Its all inside the tree movie clip. First is some initial stuff:

   if (this._name == "tree") {
         this._visible = false;
   }
   d = 600;
   xctr = 275;
   yctr = 200;
   x = random (2000) - 1000;
   z = random (2000) - 1000;
   size = 50;

The first three lines remove the tree movie clip that we duplicated to get it out of the way. The "d" variable is once again used for perspective. But, notice how this time I used a much larger number. Since we are dealing with a larger scale of 3d here I used a larger value. If I didn't use such a big value you would have a "fisheye" type look to everything. The next two variables are used to make up for the fact that (0,0) is at the top left hand corner, again. The variables "x" and "z" are initialized to a random number between 1000 and -1000. Those values will be the coordinates where the tree is placed. There is no "y" value because trees are placed out in front of you (z-axis) and to the sides of you (x-axis), not in the air (y-axis). And the last variable, "size", is used to scale the tree. You could use any number you wanted to depending on how big you wanted the trees.

The rest of the script in the tree is where most of the action happens even though it is really short. I'll go through it slowly though. The first thing that happens in the script are the rotations and translations (I left the comments in this time):

   // Y rotation
   x = _root.cb * x - _root.sb * z;
   z = _root.sb * x + _root.cb * z;
   // Translate and make first person viewpoint
   z += _root.Tz - d;

The first two lines should look familiar since it is only a rotation around the y-axis. However the last line may not look so ordinary. That line takes care of the translation and makes your view point first person. I'll explain one thing at a time. Translating of the z-axis is like walking forward and backward...that is all adding "Tz" does to the trees. But, why is that I subtract "d" from the z coordinate too? Remember that "d" represents the distance from your eye to the origin. Well in real life there is no distance from your eye to the origin because your eye is the origin. Therefore you need to temporarily shift the tree's z-position towards you by "d" units, render everything, and then put it back. That will give you the first person type view. Lets look at the rest of the script:

   // if the object is behind you don't render
   if (z < -1*d) {
         this._visible = false;
   } else {
         pers = d / (z + d);
         xp = x * pers;
         scale = Math.abs (pers*size)
         // if the object is off the stage don't render
         if ((xctr + xp) + scale/2 < 0 || (xctr + xp) - scale/2 > 550) {
               this._visible = false;
         } else {
               this._visible = true;
               yp = _root.h * pers;
               this._x = xctr + xp;
               this._y = yctr + yp;
               this._width = this._height = scale;
               this.swapDepths (-z);
         }
   }
   // change back from first person
   z += d;

I wanted this script to do as few actions as possible to add a little bit more speed to the movie. The first thing I do is check if the object is behind you. I discussed this in my last tutorial on 3D so I'm not going to say much about it except that an object is behind you if its z-position is less than the negative of "d". So if an object is behind you its visibility is set to 0 (makes it invisible) and you can skip the rest of the script. Otherwise you run it through one more conditional statement checking if the object is on the stage. Before you can check if an object is on the stage or not you need get its x-position relative to the stage (with perspective that is) and its current scale. Once that is done there is the last conditional which keeps you from doing more calculations than needed.

Before I go on I just want to say something about that last conditional. To simplify things you could create a large movie clip that covers the stage *exactly* and put it on the bottom layer (make sure you cannot see it). Now all you have to do is a hitTest () for the tree and the large movie clip...if there is a hit render, if there is not a hit do not render. That would probably be faster too so you may want to try it out.

Ok...so now we are up to the actual rendering part. The first thing you want to do is set the tree's visibility to one so that the trees do not disappear forever. Then you go ahead and calculate the perspective for the y-position of the tree depending on your height. Since the tree's y-position is always zero you can simply use "h" to make up the difference...or else you would be crawling on the ground.

And finally, you have all the 'set properties'. That part is pretty self explanatory though. The only part left of our "3D world" are the mountains. Ironically, that was the hardest part for me. The part that is difficult is getting the mountains to be seamless as you are rotating around. I had to try quite a few times to get it right.

Here's what you do first...draw some random mountains that take up the entire stage width-wise...make sure the width of the stage is taken up exactly with nothing hanging over or falling short. The height does not matter. Then select all of it, copy and paste it and align the copy right next to the original. Then make *all* of it one movie clip. For the actions in the movie clip I used:

   onClipEvent (load) {
         wid = this._width / 2;
   }
   onClipEvent (enterFrame) {
         this._x -= _root.b*10;
         if (this._x+wid < 550) {
               this._x = 550;
         }
         if (this._x-wid > 0) {
               this._x = 0;
         }
   }

All that script does is move the mountains side to side depending on what "b" equals, then it checks if the mountains go off the stage. If they do you reset them. The bad thing about the script is that I had to do some guess and check kind of stuff. You see that I multiply "b" by 10 in the first line of the "enterFrame" clip event? I just messed with numbers for that until I could get it to look right. I know, bad, bad, bad...but I couldn't help it. I still don't know how to do that the right way.

Well anyway, outside from that one line you should know almost everything there is to know about "3D worlds" in Flash. Also that concludes this tutorial. It also concludes my tutorials for 3D in general. Unless I can think of something else interesting to write about this will be the last definitely. It was nice while it lasted. I think I am going to travel more into math and away from Flash. If you have any suggestions e-mail me.

Good luck.

- Brandon Williams