Conservation of Momentum

Finally, we get to the meat of the chapter: conservation of momentum. What does that mean? Momentum is conserved? When? Where? How? OK, lets slow down. Momentum conservation has everything to do with collisions, which is something youre very familiar with by now. Youve gone through a whole chapter on collision detection and even faked some collision reactions between two objects. Conservation of momentum is the exact principle you need to respond very realistically to a collision.

Conservation of momentum allows you to say, This object was moving at velocity A and that object was moving at velocity B before the collision. Now, after the collision, this object will move at velocity C and that object will move at velocity D. To break it down further, knowing that velocity is speed and direction, you can say that if you know the speed and direction of two objects just before they collide, you can figure out the speed and direction they will move in after the collision. A very useful thing, as Im sure youll agree.

Theres one catch though: Youll need to know each objects mass. Again, you see why Ive put off this discussion until now. So, in reality, youre saying if you know the mass, speed, and direction of each object before the collision, you can figure out where and how fast the objects will go after they collide.

OK, so thats what conservation of momentum can do for you. But what is it? Basically, the Law of Conservation of Momentum says that the total momentum for a system before a collision will be equal to the total momentum after a collision. But what is this system the law refers to? This is just a collection of objects with momentum. Most discussions also specify that this would be a closed system , which is a system with no other forces or influences acting on it. In other words, you can just ignore anything but the actual collision itself. For our purposes, we will always be considering just the reaction between two objects, so our system will always be something like object A and object B.

The total momentum of the system is simply the combined momentum of all the objects in the system. For us, this will mean the combined momentum of object A and object B. So, if you combine the momentums before the collision, and combine the momentums afterwards, the result should be the same. Well, thats great, you say, but it doesnt really tell me how to find out the new momentums. Patience, were getting there. I told you well be walking through this step by step. A few paragraphs from now, youll probably be wanting me to slow down, because here come the formulas!

Before I jump into the math, heres a suggestion. Dont worry too much at first about trying to figure out how to convert this to real code. Youll get to that soon enough. Just try to look at the next few formulas from a conceptual viewpoint. This plus that equals that plus this. Sure, that makes sense. It will all translate very neatly into code by the end of this chapter.

OK, if combined momentum before and after the collision is the same, and momentum is velocity times mass, then for two objectsobject 0 and object 1you can come up with something like this:

 momentum0 + momentum1 = momentum0Final + momentum1Final 

or

 (m0 * v0) + (m1 * v1) = (m0 * v0Final) + (m1 * v1Final) 

Now, what you want to know is the final velocities for object 0 and object 1. Those would be v0Final and v1Final . The way to solve an equation with two unknowns is to find another equation that has the same two unknowns in it. It just so happens there is such an equation floating around the halls of the physics departments of the world. It has to do with kinetic energy. The funny thing is, you dont even have to know or even care what kinetic energy is all about. You just borrow the formula to help you solve your own problem, dust it off, and give it back when youre done. Heres the formula for kinetic energy:

 KE = 0.5 * m * v  2  

Technically, kinetic energy is not a vector, so although you use the v for velocity in there, it deals with only the magnitude of the velocity. It doesnt care about the direction. But that wont hurt your calculations.

Now, it happens that the kinetic energy before and after a collision remains the same. So, you can do something like this:

 KE0 + KE1 = KE0Final + KE1Final 

or

 (0.5 * m0 * v0  2  ) + (0.5 * m1 * v1  2  ) = (0.5 * m0 * v0Final  2  ) + (.5 * m1 * v1Final  2  ) 

You can then factor out the 0.5 values to get this:

 (m0 * v0  2  ) + (m1 * v1  2  ) = (m0 * v0Final  2  ) + (m1 * v1Final  2  ) 

What do you know? You have a different equation with the same two unknown variables : v0Final and v1Final . You can now factor these out and come up with a single equation for each unknown. Ill save us both a headache in the intervening algebra and just give you the formulas that you wind up with when all is said and done. If youre one of those people who actually like doing algebra equations, or you need some extra credit for school, I invite you to sit down with some paper and a few sharp pencils, and figure it out on your own. You will wind up with the equations that follow (if not, either you made a mistake or Im in big trouble with my publisher).

 (m0  m1) * v0 + 2 * m1 * v1 v0Final = ----------------------------                     m0 + m1           (m1  m0) * v1 + 2 * m0 * v0 v1Final = ----------------------------                     m0 + m1 

And now you see why I said at the beginning of this chapter that you have reached a pinnacle of complexity. Actually, you havent quite reached it yet. Youre about to apply this to one axis, and after that, youre going to dive in and add coordinate rotation to it when you move to two axes. Hold on!

Conservation of momentum on one axis

Now that youve got some formulas, you can start making things move with them. For this first example, start with the same basic ball movie clip FLA file youve been using all along, but place two balls on stage. Name them ball0 and ball1 , and scale ball0 down a bit (or scale ball1 up, if you prefer). Place ball0 way over on the left side and ball1 on the right. Youll be ignoring the y axis this time around, so make sure both movie clips are in the same position vertically. Otherwise, it will be strange to see them bounce if they havent visually touched each other. You can see the basic setup in Figure 11-1 (in ch11_01.fla ).

image from book
Figure 11-1: Setting up the stage for conservation of momentum on one axis

Start off by giving each clip a mass and a velocity on the x axis:

 ball0.vx = 1; ball0.mass = 1; ball1.vx = -1; ball1.mass = 2; 

Then do some basic motion code for one-axis velocity and simple distance-based collision detection:

 function onEnterFrame ():Void {       ball0._x += ball0.vx;       ball1._x += ball1.vx;       var dist:Number = ball1._x - ball0._x;       if(Math.abs(dist) < ball0._width / 2 + ball1._width / 2)       {             // reaction will go here       } } 

Now, the only question is how to code in the reaction. Well, lets take ball0 first. Considering that ball0 is object 0 and ball1 is object 1, you need to apply the following formula:

 (m0  m1) * v0 + 2 * m1 * v1 v0Final = ----------------------------                     m0 + m1 

In ActionScript, this becomes the following code:

 var vx0Final:Number = ((ball0.mass - ball1.mass) *                        ball0.vx + 2 * ball1.mass * ball1.vx)                        / (ball0.mass + ball1.mass); 

It shouldnt be too hard to see where that came from. You can then do the same thing with ball1 , so this:

 (m1  m0) * v1 + 2 * m0 * v0 v1Final = ----------------------------                  m0 + m1 

becomes this:

 var vx1Final:Number = ((ball1.mass - ball0.mass) *                        ball1.vx + 2 * ball0.mass * ball0.vx)                        / (ball0.mass + ball1.mass); 

The final code winds up like this:

 ball0.vx = 1; ball0.mass = 1; ball1.vx = -1; ball1.mass = 2; function onEnterFrame ():Void {       ball0._x += ball0.vx;       ball1._x += ball1.vx;       var dist:Number = ball1._x - ball0._x;       if(Math.abs(dist) < ball0._width / 2 + ball1._width / 2)       {  var vx0Final:Number = ((ball0.mass - ball1.mass) *   ball0.vx + 2 * ball1.mass *   ball1.vx)   / (ball0.mass + ball1.mass);   var vx1Final:Number = ((ball1.mass - ball0.mass) *   ball1.vx + 2 * ball0.mass *   ball0.vx)   / (ball0.mass + ball1.mass);   ball0.vx = vx0Final;   ball1.vx = vx1Final;   ball0._x += ball0.vx;   ball1._x += ball1.vx;  } } 

Note that the calculation for vx0Final uses ball1.vx and vice versa. Thus, I had to store both of them in temporary variables, rather than assigning them directly to the ball0.vx and ball1.vx properties.

Placing the objects

The last two lines of the preceding ActionScript deserve some explanation. After you figure out the new velocities for each ball, you add them back to the balls position. Thats something new. Why do that? Well, remember that in all previous bouncing examples, you needed to reposition the movie clip so that it wasnt embedded in the wall. You just moved it out so it was touching the edge of the wall. You need to do the same thing here, but now you have two moving objects. You dont want them embedded in each other. That would not only look wrong, but it would usually result in the two objects becoming stuck together permanently.

You could place one of the balls just on the edge of the other one. But which one should you move? Whichever one you moved would kind of jump into its new position unnaturally, which would be especially noticeable at low speeds.

There are probably a number of ways to determine the correct placement of the balls, ranging from simple to complex and accurate to totally faked. The simple solution I used for this first example is just to add the new velocity back to the objects, moving them apart again. Ive found that this is pretty realistic and quite simpleaccomplished in two lines of code. Later, in the Solving a potential problem section, Ill show you a problem that can crop up with this method and give you a solution thats a little more robust.

Go ahead and run the ch11_01.fla file. Then change the masses and velocities of each ball until you see whats going on. Change the sizes of each, too. Note that the size doesnt really have anything to do with the reaction. In most cases, the larger object would have a higher mass, and you could probably figure out the relative volume of the two balls and come up with realistic masses for their sizes. However, usually, I just mess around with numbers for mass until things look and feel right. I say something very scientific like, Well, this ball is roughly twice as big, so Ill give it twice the mass.

Optimizing the code

The worst part of this solution is that huge equation right in the middle. Actually, the very worst part is the fact that the code has almost exactly the same equation in there twice. If you could somehow get rid of one of them, Im sure youd feel a lot better. The good news is that you can.

Its going to take a bit more math and algebra, which Im not going to explain in mind-numbing detail. Basically, you need to find the total velocity of the two objects before the condition. Then, after you get the final velocity of one object, you can find the difference between it and the total velocity to get the final velocity for the other object.

You actually find the total velocity by subtracting the velocities of the two objects. That may seem strange, but think of it from the viewpoint of the system. Say the system is two cars on a highway . One is going at 50 mph and the other at 60 mph. Depending on which car youre in, you could see the other car going at 10 mph or ˆ 10 mph. In other words, its either slowly moving ahead of you or falling behind you.

So, before you do anything in terms of collisions, you find out the total velocity (from ball1 s viewpoint) by subtracting ball1.vx from ball0.vx .

 var vxTotal:Number = ball0.vx - ball1.vx; 

Finally, after calculating vx0Final , add that to vxTotal , and youll have vx1Final . Again, this may seem counterintuitive, but try it out, and youll see it works.

 vx1Final = vxTotal + vx0Final; 

Fantastic! Thats better than that horrible double formula. Also, now that the formula for ball1.vx doesnt reference ball0.vx anymore, you can even get rid of the temporary variables. Heres the full code ( ch11_02.fla ):

 ball0.vx = 2; ball0.mass = 1; ball1.vx = -1; ball1.mass = 3; onEnterFrame = function() {       ball0._x += ball0.vx;       ball1._x += ball1.vx;       var dist:Number = ball1._x - ball0._x;       if(Math.abs(dist) < ball0._width / 2 + ball1._width / 2)       {  var vxTotal:Number = ball0.vx - ball1.vx;  ball0.vx = ((ball0.mass - ball1.mass) *                         ball0.vx + 2 * ball1.mass * ball1.vx)                         / (ball0.mass + ball1.mass);  ball1.vx = vxTotal + ball0.vx;  ball0._x += ball0.vx;             ball1._x += ball1.vx;        } } 

Youve gotten rid of quite a few math operations and still have the same resultnot bad.

Now, this isnt one of those formulas that youre necessarily going to understand inside out, unless perhaps you have a physics background. You may not even memorize it, unless youre using it on a very regular basis. Personally, I have this stuff written down somewhere, and I need to pull it out and read it whenever I want to use it. Ill be happy when this book gets published, so Ill know right where to find this formula and not have to dig through piles of paper looking for my notes!

Conservation of momentum on two axes

OK, take a deep breath . Youre going to the next level. So far, youve applied a long-winded formula, but its pretty much plug-and-play. You take the mass and the velocity of the two objects, plug them into the formula, and get your result.

Now Im going to throw one more layer of complexity onto itnamely, another dimension. Ive already given away the strategy at the beginning of the chapter, so you know that youll be using coordinate rotation. Lets take a look at why.

Understanding the theory and strategy

Figure 11-1 illustrates the example you just saw: collision on one dimension.

image from book
Figure 11-2: A one-dimensional collision

As you can see, the objects have different sizes, different masses, and different velocities. The velocities are represented by the arrows coming out from the center of each ball. These are vectors. To refresh your memory, a velocity vector points in the direction of the motion, and its length indicates the speed.

The one-dimensional example was pretty simple, because both velocity vectors were along the x axis. So, you could just add and subtract their magnitudes directly. Now, take a look at Figure 11-3, which shows two balls colliding in two dimensions.

image from book
Figure 11-3: A two-dimensional collision

Now the velocities are in completely different directions. You cant just plug the velocities into the momentum-conservation formula. That would give you completely incorrect results. So, how do you solve this?

Well, you start by making the second diagram look a bit more like the first, by rotating it. First, figure out the angle formed by the positions of the two balls and rotate the whole scene positions and velocitiescounterclockwise by that amount. For example, if the angle is 30 degrees, rotate everything by ˆ 30. This is exactly the same thing you did in Chapter 10 to bounce off an angled surface. The resulting picture looks like Figure 11-4.

image from book
Figure 11-4: A two dimensional collision, rotated

That angle between the two balls is pretty important, and not just to make things look pretty, either. That angle can be called the angle of collision . Its important because its the only part of the balls velocities that you care aboutthe portion of the velocity that lies on that angle.

Now, take a look at the diagram in Figure 11-5. Here, Ive added in vector lines for the vx and vy for both velocities. Note that the vx for both balls lies exactly along the angle of collision.

image from book
Figure 11-5: Draw in the x and y velocities.

As I just said, the only portion of the velocity you care about is the part that lies on the angle of collision. That is now your vx . In fact, you can just forget all about vy for now. Ill take it right out of the picture, as you can see in Figure 11-6.

image from book
Figure 11-6: All you care about is the x velocity.

Does this look familiar? Its the first diagram again! You can easily solve this with the plug-and-play momentum formula. (Notice how Im brainwashing you! Youve already started agreeing with me that the formula for conservation of momentum is easy, havent you?)

When you apply the formula, you wind up with two new vx values. Remember that the vy values never change. But the alteration of the vx alone changed the overall velocity to look something Figure 11-7.

image from book
Figure 11-7: New x velocities, same y velocities, with the result of a new overall velocity

Have you already guessed whats next? You just rotate everything back again, as shown in Figure 11-8, and you have the final real vx and vy for each ball.

image from book
Figure 11-8: Everything rotated back

Well, that all looks very nice in a little line drawing, but now its time to somehow convert all this into code. The hardest part for me is trying to continue to convince you that its easy.

Writing the code

To begin, you need to make a base file that will allow two balls to move at any angles and eventually hit each other. Starting off with pretty much the same FLA file as before, you have two ball movie clips: ball0 and ball1 . I made them a little larger now, as you can see in Figure 11-9, so theres a good chance of them bumping into each other often.

image from book
Figure 11-9: Setting up the stage for conservation of momentum on two axes

Then you need to set up some basic motion code and allow the balls to bounce off the walls.

 var left:Number = 0; var right:Number = Stage.width; var top:Number = 0; var bottom:Number = Stage.height; ball0.vx = Math.random() * 4-2; ball0.vy = Math.random() * 4-2; ball0.mass = 2; ball1.vx = Math.random() * 4-2; ball1.vy = Math.random() * 4-2; ball1.mass = 1; function onEnterFrame():Void {       ball0._x += ball0.vx;       ball0._y += ball0.vy;       ball1._x += ball1.vx;       ball1._y += ball1.vy;       checkWalls(ball0);       checkWalls(ball1); } function checkWalls(ball:MovieClip):Void {       if(ball._x < left + ball._width / 2)       {             ball._x = left + ball._width / 2;             ball.vx *= -1;       }       else if(ball._x > right - ball._width / 2)       {             ball._x = right - ball._width / 2;             ball.vx *= -1;       }       if(ball._y < top + ball._height / 2)       {             ball._y = top + ball._height / 2;             ball.vy *= -1;       }       else if(ball._y > bottom - ball._height / 2)       {             ball._y = bottom - ball._height / 2;             ball.vy *= -1;       } } 

Now, thats all stuff you should be able to do in your sleep by now. You set boundaries, set some random velocities, throw in some mass, move each ball according to its velocity, and check the boundaries. Notice that I pulled the boundary-checking stuff out into its own function, checkWalls , so I could use it twice without typing it in again.

I do the same thing with the collision-checking routine, putting it into a function called checkCollision . So, onEnterFrame becomes this:

 function onEnterFrame():Void {       ball0._x += ball0.vx;       ball0._y += ball0.vy;       ball1._x += ball1.vx;       ball1._y += ball1.vy;  checkCollision(ball0, ball1);  checkWalls(ball0);       checkWalls(ball1); } 

From this point on, Ill show you only the checkCollision function and any other functions I happen to come up with. The rest of the code doesnt change. If you want to see the finished product in all its glory , its in ch011_03.fla .

The beginning of that function is pretty simple. Its just a distance-based collision detection setup.

 function checkCollision(ball0:MovieClip, ball1:MovieClip):Void {       var dx:Number = ball1._x - ball0._x;       var dy:Number = ball1._y - ball0._y;       var dist:Number = Math.sqrt(dx*dx + dy*dy);       if(dist < ball0._width / 2 + ball1._width / 2)       {             // collision handling code here       } } 

See? You have about two- thirds of the code written already, and so far it has been a piece of cake!

The first thing that the collision-handling code needs to do is figure out the angle between the two balls. You can do that with Math.atan2(dy, dx) . (If that didnt spring to your mind even as you were reading it, you might want to review the trigonometry in Chapter 3.) Then you store the cosine and sine calculations, as youll be using them over and over.

 // calculate angle, sine and cosine var angle:Number = Math.atan2(dy, dx); var sine:Number = Math.sin(angle); var cosine:Number = Math.cos(angle); 

Next, you need to do coordinate rotation for the velocity and position of both balls. Lets call the rotated positions x0 , y0 , x1 , and y1 and the rotated velocities vx0 , vy0 , vx1 , and vy1 .

Since you are using ball0 as the pivot point, its coordinates will be 0, 0. That wont change even after rotation, so you can just say this:

 // rotate ball0's position var x0:Number = 0; var y0:Number = 0; 

Next, ball1 s position is in relation to ball0 s position. This corresponds to the distance values youve already figured out, dx and dy . So, you can just rotate those to get ball1 s rotated position:

 // rotate ball1's position var x1:Number = dx * cosine + dy * sine; var y1:Number = dy * cosine - dx * sine; 

Finally, rotate all the velocities. You should see a pattern forming:

 // rotate ball0's velocity var vx0:Number = ball0.vx * cosine + ball0.vy * sine; var vy0:Number = ball0.vy * cosine - ball0.vx * sine; // rotate ball1's velocity var vx1:Number = ball1.vx * cosine + ball1.vy * sine; var vy1:Number = ball1.vy * cosine - ball1.vx * sine; 

Heres all the rotation code in place:

 function checkCollision(ball0:MovieClip, ball1:MovieClip):Void {       var dx:Number = ball1._x - ball0._x;       var dy:Number = ball1._y - ball0._y;       var dist:Number = Math.sqrt(dx*dx + dy*dy);       if(dist < ball0._width / 2 + ball1._width / 2)       {  // calculate angle, sine and cosine   var angle:Number = Math.atan2(dy, dx);   var sine:Number = Math.sin(angle);   var cosine:Number = Math.cos(angle);   // rotate ball0's position   var x0:Number = 0;   var y0:Number = 0;   // rotate ball1's position   var x1:Number = dx * cosine + dy * sine;   var y1:Number = dy * cosine - dx * sine;   // rotate ball0's velocity   var vx0:Number = ball0.vx * cosine + ball0.vy * sine;   var vy0:Number = ball0.vy * cosine - ball0.vx * sine;   // rotate ball1's velocity   var vx1:Number = ball1.vx * cosine + ball1.vy * sine;   var vy1:Number = ball1.vy * cosine - ball1.vx * sine;  } } 

Thats not so horrible yet, right? Well, hang in there. Youre already one-third of the way through the painful stuff.

You can now do a simple one-dimensional collision reaction with vx0 and ball0.mass , and vx1 and ball1.mass . For the earlier one-dimensional example, you had the following code:

 var vxTotal:Number = ball0.vx - ball1.vx; ball0.vx = ((ball0.mass - ball1.mass) *             ball0.vx + 2 * ball1.mass * ball1.vx)             / (ball0.mass + ball1.mass); ball1.vx = vxTotal + ball0.vx; 

You can now rewrite that as follows :

 var vxTotal:Number =  vx0   vx1  ;  vx0  = ((ball0.mass - ball1.mass) *  vx0  + 2 * ball1.mass *  vx1  )        / (ball0.mass + ball1.mass);  vx1  = vxTotal +  vx0  ; 

All you did was replace the ball0.vx and ball1.vx with the rotated version, vx0 and vx1 . Lets plug that into the function:

 function checkCollision(ball0:MovieClip, ball1:MovieClip):Void {       var dx:Number = ball1._x - ball0._x;       var dy:Number = ball1._y - ball0._y;       var dist:Number = Math.sqrt(dx*dx + dy*dy);       if(dist < ball0._width / 2 + ball1._width / 2)       {             // calculate angle, sine and cosine             var angle:Number = Math.atan2(dy, dx);             var sine:Number = Math.sin(angle);             var cosine:Number = Math.cos(angle);             // rotate ball0's position             var x0:Number = 0;             var y0:Number = 0;             // rotate ball1's position             var x1:Number = dx * cosine + dy * sine;             var y1:Number = dy * cosine - dx * sine;             // rotate ball0's velocity             var vx0:Number = ball0.vx * cosine + ball0.vy * sine;             var vy0:Number = ball0.vy * cosine - ball0.vx * sine;             // rotate ball1's velocity             var vx1:Number = ball1.vx * cosine + ball1.vy * sine;             var vy1:Number = ball1.vy * cosine - ball1.vx * sine;  // collision reaction   var vxTotal:Number = vx0 - vx1;   vx0 = ((ball0.mass - ball1.mass) *   vx0 + 2 * ball1.mass * vx1)   / (ball0.mass + ball1.mass);   vx1 = vxTotal + vx0;   // update position   x0 += vx0;   x1 += vx1;  } } 

This code also adds the new x velocities to the x positions, to move them apart, as in the one-dimensional example.

Now that you have updated, postcollision positions and velocities, rotate everything back. Start by getting the unrotated, final positions.

 // rotate positions back var x0Final:Number = x0 * cosine  -  y0 * sine; var y0Final:Number = y0 * cosine  +  x0 * sine; var x1Final:Number = x1 * cosine  -  y1 * sine; var y1Final:Number = y1 * cosine  +  x1 * sine; 

Remember to reverse the + and - in the rotation equations, as you are going in the other direction now. These final positions are actually not quite final. They are in relation to the pivot point of the system, which is ball0 s original position. So, you need to add all of these to ball0 s position to get the actual screen positions. Lets do ball1 first, so that its using ball0 s original position, not the updated one:

 // adjust positions to actual screen positions ball1._x = ball0._x + x1Final; ball1._y = ball0._y + y1Final; ball0._x = ball0._x + x0Final; ball0._y = ball0._y + y0Final; 

Last, but not least, rotate back the velocities. These can be applied directly to the balls vx and vy properties:

 // rotate velocities back ball0.vx = vx0 * cosine - vy0 * sine; ball0.vy = vy0 * cosine + vx0 * sine; ball1.vx = vx1 * cosine - vy1 * sine; ball1.vy = vy1 * cosine + vx1 * sine; 

Lets take a look at the entire, completed function:

 function checkCollision(ball0:MovieClip, ball1:MovieClip):Void {       var dx:Number = ball1._x - ball0._x;       var dy:Number = ball1._y - ball0._y;       var dist:Number = Math.sqrt(dx*dx + dy*dy);       if(dist < ball0._width / 2 + ball1._width / 2)       {             // calculate angle, sine and cosine             var angle:Number = Math.atan2(dy, dx);             var sine:Number = Math.sin(angle);             var cosine:Number = Math.cos(angle);             // rotate ball0's position             var x0:Number = 0;             var y0:Number = 0;             // rotate ball1's position             var x1:Number = dx * cosine + dy * sine;             var y1:Number = dy * cosine - dx * sine;             // rotate ball0's velocity             var vx0:Number = ball0.vx * cosine + ball0.vy * sine;             var vy0:Number = ball0.vy * cosine - ball0.vx * sine;             // rotate ball1's velocity             var vx1:Number = ball1.vx * cosine + ball1.vy * sine;             var vy1:Number = ball1.vy * cosine - ball1.vx * sine;             // collision reaction             var vxTotal:Number = vx0 - vx1;             vx0 = ((ball0.mass - ball1.mass) *                    vx0 + 2 * ball1.mass * vx1)                    / (ball0.mass + ball1.mass);             vx1 = vxTotal + vx0;             // update position             x0 += vx0;             x1 += vx1;             // rotate positions back             var x0Final:Number = x0 * cosine - y0 * sine;             var y0Final:Number = y0 * cosine + x0 * sine;             var x1Final:Number = x1 * cosine - y1 * sine;             var y1Final:Number = y1 * cosine + x1 * sine;             // adjust positions to actual screen positions             ball1._x = ball0._x + x1Final;             ball1._y = ball0._y + y1Final;             ball0._x = ball0._x + x0Final;             ball0._y = ball0._y + y0Final;             // rotate velocities back             ball0.vx = vx0 * cosine - vy0 * sine;             ball0.vy = vy0 * cosine + vx0 * sine;             ball1.vx = vx1 * cosine - vy1 * sine;             ball1.vy = vy1 * cosine + vx1 * sine;       } } 

Play around with this example. Change the size of the ball movie clip instances, the initial velocities, masses, and so on. Become convinced that it works pretty well.

As for that checkCollision function, its a doozy, yes. But if you read the comments, you see its broken up into (relatively) simple chunks . You could probably optimize this, or you could refactor it a bit to remove some of the duplication. In fact, in the name of good practice, Ive gone ahead and done this in ch11_04.fla , which you can see here:

 function checkCollision(ball0:MovieClip, ball1:MovieClip):Void {       var dx:Number = ball1._x - ball0._x;       var dy:Number = ball1._y - ball0._y;       var dist:Number = Math.sqrt(dx*dx + dy*dy);       if(dist < ball0._width / 2 + ball1._width / 2)       {             // calculate angle, sine and cosine             var angle:Number = Math.atan2(dy, dx);             var sine:Number = Math.sin(angle);             var cosine:Number = Math.cos(angle);             // rotate ball0's position             var pos0:Object = {x:0, y:0};             // rotate ball1's position             var pos1:Object = rotate(dx, dy, sine, cosine, true);             // rotate ball0's velocity             var vel0:Object = rotate(ball0.vx,                                      ball0.vy,                                      sine,                                      cosine,                                      true);             // rotate ball1's velocity             var vel1:Object = rotate(ball1.vx,                                      ball1.vy,                                      sine,                                      cosine,                                      true);             // collision reaction             var vxTotal:Number = vel0.x - vel1.x;             vel0.x = ((ball0.mass - ball1.mass) *                       vel0.x + 2 * ball1.mass * vel1.x)                       / (ball0.mass + ball1.mass);             vel1.x = vxTotal + vel0.x;             // update position             pos0.x += vel0.x;             pos1.x += vel1.x;             // rotate positions back             var pos0F:Object = rotate(pos0.x,                                       pos0.y,                                       sine,                                       cosine,                                       false);             var pos1F:Object = rotate(pos1.x,                                       pos1.y,                                       sine,                                       cosine,                                       false);             // adjust positions to actual screen positions             ball1._x = ball0._x + pos1F.x;             ball1._y = ball0._y + pos1F.y;             ball0._x = ball0._x + pos0F.x;             ball0._y = ball0._y + pos0F.y;             // rotate velocities back             var vel0F:Object = rotate(vel0.x,                                       vel0.y,                                       sine,                                       cosine,                                       false);             var vel1F:Object = rotate(vel1.x,                                       vel1.y,                                       sine,                                       cosine,                                       false);             ball0.vx = vel0F.x;             ball0.vy = vel0F.y;             ball1.vx = vel1F.x;             ball1.vy = vel1F.y;       } } function rotate(x:Number,                 y:Number,                 sine:Number,                 cosine:Number,                 reverse:Boolean):Object {       var result:Object = new Object();       if(reverse)       {             result.x = x * cosine + y * sine;             result.y = y * cosine - x * sine;       }       else       {             result.x = x * cosine - y * sine;             result.y = y * cosine + x * sine;       }       return result; } 

Here, Ive made a rotate function that takes in the values it needs and returns an object with updated _x and _y properties. This version isnt quite as easy to read when youre learning the principles involved, but it results in a lot less duplicated code.

Adding more objects

Now, having two movie clips colliding and reacting was no easy task, but you made it. Congratulations. Now lets go for a few more colliding objectssay eight. That sounds like its going to be four times more complex, but its really not. The functions you have now just check two balls at a time, but thats all you really want to do anyway. You put more objects on stage, move them around, and check each one against all the others. And youve already done just that in the collision detection examples (Chapter 9). All you need to do is to plug in the checkCollision function where you would normally do the collision detection.

For this example ( ch11_05.fla ), start with eight balls on the stage, named ball0 through ball7 , as shown in Figure 11-10.

image from book
Figure 11-10: Setting up the stage with multiple objects

After the usual setting of boundaries, you loop through the on-stage balls and assign various properties to them:

 var numBalls:Number = 8; for(var i:Number = 0;i<numBalls;i++) {       var ball:MovieClip = this["ball" + i];       ball.vx = Math.random() * 10 - 5;       ball.vy = Math.random() * 10 - 5;       ball._xscale = ball._yscale =                      ball.mass =                      Math.random() * 250 + 20; } 

Now, you might be asking, why didnt I just attach them at runtime, rather than go through the trouble of putting them on stage, laying them out, and naming each one? The reason is that you need to make sure that when each ball first appears, it does not collide with any of the existing balls. Otherwise, it will become stuck to it, probably for eternity. While there are certainly ways of avoiding that via code, I decided to keep the code simple and clean, and place the movie clips by hand.

The onEnterFrame function is surprisingly simple. It has just two loops : one for basic movement and one for collision detection.

 function onEnterFrame():Void {       for(var i=0;i<numBalls;i++)       {             var ball:MovieClip = this["ball" + i];             ball._x += ball.vx;             ball._y += ball.vy;             checkWalls(ball);       }       for(var i=0;i<numBalls-1;i++)       {             var ballA:MovieClip = this["ball" + i];             for(var j:Number = i+1;j<numBalls;j++)             {                   var ballB:MovieClip = this["ball" + j];                   checkCollision(ballA, ballB);             }       } } 

The first for loop just runs through each ball on stage, moving it and bouncing it off the walls. Then you have a nested loop that compares each ball with every other one, as I covered in the discussion of collision detection (in Chapter 9). In this, you get a reference to two of the balls at a time, name them ballA and ballB , and pass these to the checkCollision function. And, amazingly enough, thats all there is to it! The checkCollision and rotate functions are exactly the same.

To add more balls, just put them on stage, name them ball8 , ball9 , and so on, and update the numBalls variable to the new total.

Solving a potential problem

One word of warning regarding the setup Ive described in this chapter: Its still possible for a pair of movie clips to get kind of stuck together. This mostly happens in a crowded environment with many movie clips bouncing off each other, and it seems worse when they are moving at high speeds. You can also occasionally see this behavior if two or three balls collide in a corner of the stage.

Say you have three balls on stage ball0 , ball1 , and ball2 and they all happen to be really close together. Heres basically what happens:

  • The code moves all three according to their velocities.

  • The code checks ball0 vs. ball1 , and ball0 vs. ball2 . It finds no collision.

  • The code checks ball1 vs. ball2 . These two happen to be hitting , so it does all the calculations for their new velocities and updates their positions so that they are no longer touching. This inadvertently puts ball1 in contact with ball0 . However, this particular combination has already been checked for a collision, so it now goes unnoticed.

  • On the next loop, the code again moves each according to its velocity. This potentially drives ball1 and ball0 even further together.

  • Now the code does notice that ball0 and ball1 are hitting. It calculates their new velocities and adds the new velocities to the positions, to move them apart. But, since they were already touching, this might not be enough to actually separate them. They become stuck.

Again, this mostly occurs when you have a lot of objects in a small space, moving at higher speeds. You probably wont see it happen in the file you just created, which is why Im bringing it up as an after-thought. (If it isnt broken, dont fix it.) But, if you add a bunch more balls into the equation, youll probably see it crop up now and then. So, its good to know where the problem lies. The exact point is in the checkCollision function, specifically the following lines:

 // update position pos0.x += vel0.x; pos1.x += vel1.x; 

This just assumes that the collision occurred due to only the two balls own velocities, and that adding back on the new velocities will separate them. Most of the time, this is true. But the situation I just described is an exception. If you are running into this situation, you need something more stringent in ensuring that the movie clips are definitely separated before moving on. I came up with the following method:

 // update position var absV:Number = Math.abs(vel0.x) + Math.abs(vel1.x); var overlap:Number = (ball0._width / 2 + ball1._width / 2)                       - Math.abs(pos0.x - pos1.x); pos0.x += vel0.x / absV * overlap; pos1.x += vel1.x / absV * overlap; 

This is totally my own creation, so Im not sure how accurate it is, but it seems to work pretty well. It first determines the absolute velocity (a term I made up!). This is the sum of the absolute values of both velocities. For instance, if one velocity is ˆ 5 and the other is 10, the absolute values are 5 and 10, and the total is 5 + 10, or 15.

Next, it determines how much the balls are actually overlapping. It does this by getting their total radii, and subtracting their distance.

Then it moves each ball a portion of the overlap, according to their percent of the absolute velocity. The result is that the balls should be exactly touching each other, with no overlap. Its a bit more complex than the earlier version, but it pretty much clears up the bugs .

In fact, in the next version, I just attached 20 instances and randomly placed them on stage. The ones that overlap still freak out for a few frames , but eventually, due to this new code, they all settle down.

Heres the setup code from ch11_06.fla , which starts with an empty stage and the ball movie clip exported from the library.

  var numBalls:Number = 20;  for(var i:Number = 0;i<numBalls;i++) {  var ball:MovieClip = attachMovie("ball", "ball" + i, i);   ball._x = Math.random() * Stage.width;   ball._y = Math.random() * Stage.height;  ball.vx = Math.random() * 10 - 5;       ball.vy = Math.random() * 10 - 5;       ball._xscale = ball._yscale =                      ball.mass =                      Math.random() * 200 + 50; } 

The rest is all the same, except that it includes the fix I just mentioned.

Of course, youre free to investigate your own solutions to the problem, and if you come up with something that is simpler, more efficient, and more accurate, please share!



Foundation ActionScript. Animation. Making Things Move
Foundation Actionscript 3.0 Animation: Making Things Move!
ISBN: 1590597915
EAN: 2147483647
Year: 2005
Pages: 137
Authors: Keith Peters

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net