Basic Physics


The scope of what physics deals with is huge. To say "we're going to talk about physics" is not very specific. We're certainly not going to be modeling quantum mechanics in our games , so we should start out by defining the area of physics relevant to us.

Rigid Body Dynamics

Rigid body dynamics is the study of the way that objects move about and interact, assuming that during acceleration and collision, the bodies do not change shape. (They are rigid.) In real life, when any two things collide, they deform to some degree; even during acceleration, the objects deform. However, if we assume that bodies are perfectly rigid ”that is, they do not deform at all ”the math is greatly simplified.

For the purposes of nearly all games, rigid bodies can be assumed. Soft bodies that collide lose momentum as some of the force of the collision. The lost momentum is the force that deforms the soft body. To make our games more realistic, we can simply reduce the momentum of our rigid body objects to appear to act naturally while saving ourselves the burdensome calculations for a technically realistic reaction.

We need to define several properties of rigid bodies. You are probably familiar with them, but to be sure we're all on the same page, I'll go ahead and define the properties of mass, velocity, acceleration, and force now.

Note  

To be completely accurate, most game objects would be modeled by soft bodies that are in fact quite difficult. They would be subject to small amounts of deformation that would not show up to the naked eye in a game. For that reason, rigid bodies can be assumed in nearly all circumstances. The math involved in modeling soft bodies is quite complex and well beyond the scope of this book. It's generally only needed for computer animation and scientific modeling. In games, we can just fake it with rigid bodies, and everything still looks fine. This includes commercialsized games.

Mass

Mass is the amount of matter within an object. It's closely related to weight; in fact, we measure mass by weighing the object. Mass has a lot to do with the way objects move and interact. For example, you can easily throw a small stone 100 feet. However, moving a massive boulder 100 feet might be impossible for you. That's because the stone and the boulder have different masses. Those masses affect the way forces act on the objects. We'll define soon exactly how mass is related to forces.

Velocity

Velocity is the speed and direction an object is moving. It's also typical to think of velocity as a vector. The direction of the vector defines the direction of the object's movement, whereas the magnitude of the vector defines the speed the object is moving.

Don't confuse velocity with acceleration, which is our next property.

Acceleration

Acceleration is the rate at which an object's velocity is changing. In other words, if an object is speeding up, it will have a positive acceleration. If an object is slowing down, it will have a negative acceleration. If the velocity of an object is not changing at all, the object will have an acceleration of 0.

Force

Force specifies the pressure one object puts on another. If one object is moving and collides with another object, the moving object exerts a force on the second object, and vice versa. These forces can be measured, and when they are, the word force is used to describe them.

Now that we have definitions for mass, velocity, acceleration, and force, we can talk about the equations that relate those four properties to each other.

Newton's Laws of Motion

Sir Isaac Newton was the first person to formally define the way in which mass, velocity, acceleration, and forces interact, which compose the three laws of motion. These three laws clearly define object interaction and give equations to do so. After looking at these three laws, we'll have nearly everything we need to add simple physics to our games.

Newton's First Law

The first law of motion is often stated as follows :

  • An object at rest tends to stay at rest, and an object in motion tends to stay in motion with the same speed and in the same direction unless acted upon by another force.

What this is saying is that if an object is moving in some direction with some speed, it will continue to do so unless something changes that. In real life, things like friction and gravity cause the motion of objects to decay until they are at rest again, but the same is not true in space. In space, where the forces of gravity and friction are not as immediately visible, an object moving at some velocity will continue to move with the same velocity until it runs into something else. If you take away friction and gravity, a moving object will continue to move forever, or until some other force is applied.

The second half of the law says that if an object is at rest, it tends to stay at rest. If an object's velocity is 0, it won't suddenly start moving unless some force is applied to it.

This first law of motion is often referred to as the law of inertia. Inertia is the product of an object's mass and its velocity. Inertia is defined in the following equation, where I is inertia, m is mass, and v is velocity:

This means that if two objects have the same velocity but different masses, the heavier one will have more inertia. I think of inertia like this: "How much is this going to hurt if it hits me?" A bullet will hurt even though it has a small mass because its velocity is extremely high. A bulldozer will hurt as well even though its velocity is low because it has a huge mass. What would really hurt is a bulldozer hitting you moving with the velocity of a speeding bullet. What wouldn't hurt you much is a bullet hitting you at the speed of a bulldozer because both the mass and velocity are low.

Newton's Second Law

Newton's second law is related to the first but has more to do with what happens when some force acts upon an object. This second law is often stated as follows:

  • The acceleration of an object is directly proportional to the magnitude of the force applied to it, in the same direction as the force, and inversely proportional to the mass of the object.

Let's break this down into three pieces because Newton is really saying three things here. First, "The acceleration of an object is directly proportional to the magnitude of the force applied to it." Here, Newton is saying that if you apply force to an object, the object will accelerate quickly if your force is strong and slowly if your force is weak. In other words, the harder you push the object, the faster it will accelerate.

The next piece to look at is "The acceleration of an object is... in the same direction as the force applied to it." Newton is saying here that the direction in which you apply force will be the same direction as the acceleration of the object. That's pretty obvious too; if you apply force to a ball, it will move in the same direction that you applied force.

Finally, the last piece says that "the acceleration of an object [will be] inversely proportional to the mass of the object." That means that it takes more force to move a heavy object than a light one. If the same force is applied to two objects ”one heavy and one light ”the heavy object won't move much but the light object will go flying. Inversely proportionate means that as one grows big, the other grows small, and vice versa.

When you put this law into an equation using F for force, m for mass, and a for acceleration you get the following:

The preceding equation says that the acceleration is equal to the force divided by the mass. This is a mathematical translation of "directly proportionate to the force" and "inversely proportionate to the mass." We now have a way to determine how much an object with a given mass accelerates if a given force is applied.

As with any equation, if all the variables but one have known values, you can solve the equation for the unknown and plug in known values to solve. In this way, the preceding equation can be used to find mass if force and acceleration are known, as well as find force if mass and acceleration are known. The way it's currently solved finds acceleration when force and mass are known because that's usually our situation as game programmers.

We usually know the mass of every object. Then some user comes along and applies force to an object by clicking the mouse or pressing a key. Because we had to have coded that force, we should now know the force. The next step for us is to find the acceleration of the object so that we can move the appropriate object on the stage. We use the preceding equation to find the new acceleration.

With the acceleration in hand, we can find the velocity. From there, we can plot our object's new position. I'm getting ahead of myself again; let's get back to Newton's laws.

Newton's Third Law

The third law of motion is a little bit harder to grasp, but not much. It is most often stated as follows:

  • For every action, there is an equal and opposite reaction.

What that means is that if I push against a wall, the wall will push back with equal and opposite force. If it doesn't, the wall will fall down. Seem hokey to you? Well, think about it this way: If you have a marble on the floor and you roll another marble toward it, when they hit, both marbles move away from the point of contact between them. That's because the moving marble came in and applied a force to the stationary marble. But the stationary marble pushed back on the moving marble. The result is that both marbles are now moving, but neither is moving as fast as the first marble was before contact.

The key point to see in this example is that the total inertia of both balls never increases . At first, the moving ball had all the inertia and the stationary one had none. After contact, however, the moving ball gave some of its inertia away to the stationary ball. The result is that neither ball moved very fast after the collision.

What if the stationary ball was heavy and the moving ball was light? In that case, the moving ball would bounce off without budging the stationary ball. Nearly all the inertia in the moving ball would stay in that ball, but its direction would change. This demonstrates all of Newton's laws working together.

Now that we've covered Newton's laws, we're ready to see how all this works in action. First, I'm going to walk through the steps involved in general terms with no code so that the theory can sink in. From there, we'll look at a bit of script.

Moving Objects

To get started, let's assume our world has one object sitting motionless obeying Newton's first law. Now we apply a force to the ball. The force comes in the form of a vector because it has a magnitude and a direction. The force causes an abrupt increase in acceleration, which depends on the mass of the object. We know the mass of the object and we know the force applied, so we can calculate the exact acceleration.

After we know the acceleration, we can use it to update the velocity. Previously, the velocity was 0, but when we add the acceleration, it won't be 0 anymore. This new velocity tells us how fast we are moving and in what direction. From this, we can update the position of the object.

Something seems to be missing, though. Do you know what it is? We've forgotten to talk about time. Time is obviously part of what's going on because objects move through time as well as space, and velocity is measured in distance per unit time (5 miles per hour and so on). What this means is that to simulate physics, we need a time increment. If we define some small unit of time, such as 100 milliseconds , we can update our object positions based on that time interval.

Therefore, if our object has some new velocity based on a force that we applied, the object will move over time. As time intervals go by, the object's position will be updated based on its velocity. What about the acceleration? The last time we looked , acceleration was nonzero because of the force we applied. Well, the story goes like this: If we apply the force instantly and then stop, our acceleration will affect velocity one time and then become a 0 acceleration afterward. In other words, to keep acceleration nonzero, we must apply force every frame.

Another problem with our example is that our object, after it has its velocity changed to nonzero, will continue to move forever. This is the correct behavior if the object is in space, but on earth where there is an atmosphere, the object will be affected by yet another force we need to talk about: friction.

Friction

Friction is the effect caused by objects rubbing against each other. The objects can be anything, including air or water. When objects rub together, some of their inertia is converted into heat. This converted energy causes a reduction in inertia. If you push a ball across the floor, the friction of the air on the ball causes the ball to eventually stop rolling. And yes, technically some heat is emitted from this activity, but it's not enough to notice.

Note  

You might not be able to notice the heat emitted from a ball rolling across the floor, but imagine a space capsule reentering the earth's atmosphere. Or if you want something closer to home, rub your fingers quickly across your pants for a few seconds. You'll find your leg grows warm almost immediately. These are all examples of friction at work.

Modeling friction accurately is a complex process. The math involved is not trivial; it requires a healthy amount of advanced calculation. Fortunately for us as game developers, we can cheat. In a similar way to the use of rigid bodies that don't actually exist in nature, we're going to simplify friction so that it's easy to use in our games.

Friction takes the form of a force. That means it get worked into the acceleration like any other force. The complex part of friction is figuring out exactly what the friction force is going to be. The equations for that can be large for real-world friction. However, in our games, there is a much easier way to handle things.

In most games, friction is going to be a force in the exact opposite direction as velocity. In other words, if the object is moving one way, friction is pushing back on it in the opposite direction, essentially slowing down the object.

The tricky part is figuring out the magnitude of the friction force. In real life, this value is based on many things, including the air pressure, air temperature, mass, smoothness, velocity, gravity, and so on. For our games, we can usually take a much simpler approach. Generally, we can create our friction force using the velocity of the object. The faster an object is moving, the more friction force there will be. In these cases, you can take the velocity of the moving object, find a vector in the opposite direction, make this new vector's magnitude equal to a fraction of the original velocity, and apply that to the acceleration.

In other words, friction is a force applied to acceleration that has magnitude relative to the velocity, but in the opposite direction. Later when acceleration is applied to velocity, this friction force will reduce the velocity by some amount, but never more than the velocity.

In fact, it's friction that will end up causing a maximum velocity. The force that pushes an object forward (like a car) has a maximum value based on the horsepower of the engine. Eventually, the velocity gets so large (going really fast) that the friction force equals the force that's pushing the object forward. The net result is a 0 acceleration because the pushing force is in the opposite direction as the friction force. If the net acceleration each frame is 0, the car's velocity will remain constant.

The example of the car's maximum velocity is in contrast to an example in which an object is pushed forward instantly and then the force goes away. Kicking a ball is an example of an instant force. In a case like this, the acceleration is large when the ball is kicked, causing a sizeable jump in velocity. However, after the kick, the acceleration has only one component: the friction force. This eventually causes the ball's velocity to reach 0 when it comes to rest.

Now that we understand mass, velocity, acceleration, and friction, we're ready to develop a simple example to illustrate these properties in action.

Marble Example

This isn't going to be a game; it will be more of an interactive demonstration. The idea is to put a marble on the stage and allow the user to click on it. When the user clicks the marble, force is applied to it, starting the marble in motion. The marble moves around the stage based on its inertia. Each time the user clicks the marble, a force is applied. In this way, the user can move the marble around the stage by clicking on it.

The amount of force to apply to the marble is based on the distance from the center of the marble when the user clicks. The further from the center the user clicks, the more force that's applied to the marble.

Finally, the mass of each marble is based on its size . The bigger a marble is, the more mass it has. Although the user can click more than one marble on the stage, the marbles do not collide with each other. Instead, the marbles ignore each other for now. After we've implemented the marbles' movement, we'll talk about collision response and then come back to our marble example and implement collisions.

Setting the Stage

I've included this marble example on the CD in the Chapter 11 directory. All our marble examples are in there because this won't be the only one. As we delve deeper into movement and collision response, we'll be adding parts to form a series of marble examples. Although this won't be a fully developed game, it will certainly lay the groundwork for a marbles game that you can create on your own.

There is little setup to our first marble example. I've created two symbols: one called marble container and the other called marble. I have instantiated marble container one time at coordinates (0,0) on the stage. I've instantiated the marble clip (which contains a blue ball inside) four times inside the ball container clip. I've also scaled the marbles to various sizes. As has been my habit since we covered issues of timing, I've upped the frame rate to 100 fps and will be updating marbles on an interval. Figure 11.8 shows the marbles of different sizes randomly positioned.

click to expand
Figure 11.8: The marbles are all different sizes and scattered randomly. We're going to let the user click marbles to apply forces to them.

Implementing the Marble Physics

The first thing we need to do is set everything up. Instead of an initGame function, I'm just dumping the initialization code at the top of my script. Let's look at each piece of it now.

Initializing the Marbles

First we need to define our time interval. This will be the frequency at which we update our marbles. Twenty milliseconds should be fine, so we'll use that, as follows:

 t = 20 

Now we need to define our friction coefficient. This number will be a percentage of the velocity that we want to give up to air friction every frame. I've just guesstimated a number of 0.1 (10%). If this is too large or too small, we can adjust it later:

 frictionCoef = 0.1; 

Now we need to set up each marble in our marble container. I've chosen to do this using a for in loop, which you should be familiar with from previous games:

 for(marble in marbleContainer){       var m = marbleContainer[marble]; 

The previous script gives us a loop for each marble with m as the particular marble for each iteration. We're ready to set up the properties of m . We begin with acceleration, which I have abbreviated acc . The acceleration starts out at 0, as follows:

 m.acc = {x:0,y:0}; 

Now we do the same for velocity, which I abbreviate vel . The velocity begins at 0, just like acceleration:

 m.vel = {x:0,y:0}; 

Now we need to record the radius of the marble. We'll need this in the next marbles example to do collision detection, and we're going to use it in this version to determine the mass of the marble. The radius is equal to half the width of the movie clip, which gives us the following:

 m.radius = m._width/2; 

Now we need to determine the mass of the marble. We could just use the radius, but I've chosen to take the radius and multiply it by some constant ”in this case, 1.25. That gives us a bigger spread of masses based on the sizes of the balls. This, as with most of the constants in this example, is created off the top of my head by guesstimating what is required. As we start testing, we can tweak these values to get the marbles moving as we would like to see them move.

Mass is 1.25 times the radius, as follows:

 m.mass = 1.25 * m.radius; 

Finally, we need to attach an event handle to deal with the mouse clicking. I've used a function called hitMarble that we'll define later. For now, I simply attach the hitMarble function to the onRelease handler for the marble:

 m.onRelease = hitMarble; 

That completes the loop for each marble, so we can close that block:

 } 

We need to create an interval that will update our marbles every t milliseconds. I'm calling my marble update function updateMarbles , so the interval creation code is as follows:

 setInterval(updateMarbles, t); 

That completes the initialization of the example. Now all we need to do is implement the two functions we're using: hitMarble and updateMarbles .

Implementing hitMarble

As you've seen in the previous code, hitMarble is called when the user clicks a marble (it's on the onRelease handler for each marble). To implement hitMarble , we need to open the function:

 function hitMarble(){ 

Now we need to determine the force to apply (both its direction and its magnitude). We don't need the exact magnitude, though ”only the component parts. We create a vector called force that has an x component that is the difference of the x position of the marble and the x position of the mouse pointer. We do the same for force 's y component:

 var force = {x: this._x - _root._xmouse, y: this._y - _root._ymouse}; 

As I mentioned before, the components of force are created from the difference in the marble position and the mouse pointer. In other words, the closer the user is to the center of the marble when he clicks, the softer the force will be. Close in, the force will be small, but out near the edge of the marble, the force will be large.

Now we need to apply this force to the acceleration of the marble, but before we do, we need to work on it. To begin, we know from Newton that the acceleration is equal to the force divided by the mass. However, the way our numbers will work out, when we divide our force vector by the mass, it becomes too small to move the marble very far. To compensate for this, I'm going to multiply each component of the force vector by 10 to make it bigger. Then when we divide this by the mass, our net acceleration will be large enough to move the marble a healthy distance.

Like I said, we multiply force by 10 and then divide that by the mass. We do this once for each component of the acceleration, as follows:

 this.acc.x += (force.x * 10) / this.mass;     this.acc.y += (force.y * 10) / this.mass; 

Notice that I'm using += behind the acceleration. That's because we want this force to be in addition to any other forces that have been or will be applied to the acceleration. This new force is added to whatever acceleration we have. Notice that we are setting variables here, not the actual location properties of the marble movie clips. This will be done in the following section.

That completes the hitMarble function, so we close it:

 } 
Implementing updateMarbles

Now we're into the heart of the implementation. The updateMarbles function is responsible for doing all of the calculations to the marble on a frame-by-frame basis. We've already set up the interval to call updateMarbles every t milliseconds, so let's get started with its implementation.

The first thing we do, after opening our function, is to go into another for in loop that will iterate over every marble so that they all get updated:

 function updateMarbles(){       for(marble in marbleContainer){             var m = marbleContainer[marble]; 

Now we're inside the iteration loop and our marble for this iteration is named m . The first thing we need to do to m is determine the amount of air friction to apply to it. This slows the marble down if it's moving. The amount of air friction to apply is determined by the current velocity of the marble.

To create the friction vector, we start with the marble's velocity and then reverse it. Remember that the air friction will be pushing in the opposite direction of the velocity, so we begin by finding the opposite direction of velocity, as follows:

 friction = {x: -m.vel.x, y: -m.vel.y}; 

As it stands now, friction points in the opposite direction of velocity, but it also has the same magnitude as velocity. If we apply friction as it is now, it slows our ball to a complete stop after just one iteration. To compensate, we need to scale down the friction vector by some scalar. The scalar we use is called the friction coefficient, and I've already defined it to be 0.1.

We scale both components of friction by the frictionCoef and then add to the marble's acceleration:

 m.acc.x +=(friction.x * frictionCoef);             m.acc.y +=(friction.y * frictionCoef); 

Notice again that I'm using += in the application of the force to acceleration. That's because there might be other forces at work on the acceleration. The air friction is just added into whatever was already there in the acceleration vector. To tie this back to the theory for vectors, we are adding the components of the new vector (friction) to the components of the existing vector (acceleration). This is just vector addition.

Now that we have the new acceleration, we can use it to update the velocity, as follows:

 m.vel.x += m.acc.x;             m.vel.y += m.acc.y; 

Again, I'm using += because the velocity is only being updated. The new acceleration gets its part in the new velocity by being added in to what's already there.

Finally, we're ready to add the new velocity into the position of the marble. This is the part that will be obvious to the user because the marble will move:

 m._x += m.vel.x;             m._y += m.vel.y; 

There is only one thing left to do to this marble before we move on. We must zero out the acceleration. That's because after the acceleration is applied, it goes away unless some force pushes it again in the next frame. If the user clicks on the marble or the air pressure pushes against the marble in the next frame, the acceleration is altered from 0 again:

 m.acc.x = m.acc.y = 0; 

Because that completes both the for in loop that iterates over the marbles as well as the updateMarbles function, we can close both:

 } } 

At this point, our demo is ready to try out. Go ahead and test it by clicking on the marbles at various places and see what happens.

Caution  

If you click on the edge of a small marble and then click on the edge of a large marble, each marble appears to move a similar distance. That's because the force is based on the distance from the center of the marble. In a large marble, the user can get further from the center, thereby producing a larger force. Try to click the same distance from the center on a large and small marble, and you should see the smaller marble moving further. That's because the force is the same, but the mass of the small marble is smaller, causing it to move faster and further.

Testing

When playing around with the marble example, you probably noticed something odd going on. The marbles work great until they have slowed down almost to a stop. When the marbles slow down, they seem to slide around a bit like they are on ice. They eventually come to a halt, but not before several moments of sliding slowly beforehand.

This is happening because our velocity becomes extremely small (much less than 1) and because air friction is the only thing slowing down the marble. We've created the friction using a 0.1 scalar to velocity. The marble doesn't continue to slow down correctly when its velocity becomes so small.

If you are thinking, "There must be some inherent flaw in our physics model," you're over- reacting . Our entire physics model is based on rigid bodies, which only occur in computers and in our minds, but not in real life. Because our method of operation thus far has been to cheat, we'll just cheat some more to get our balls to stop sliding.

We do this by defining the number 0 to be a good bit bigger than it actually is. By defining 0 to be something like 0.01 or below, we can force our velocity and acceleration to 0 when they get small enough to start causing the slide effect.

We begin by defining a constant, which I have set at 0.01. This definition goes at the top of the script:

 ZERO = 0.01; 

Now we go into updateMarbles . Just before we update the new position, we add the following two lines of script:

 if(m.vel.x >= -ZERO && m.vel.x <= ZERO) m.vel.x = 0; if(m.vel.y >= -ZERO && m.vel.y <= ZERO) m.vel.y = 0; 

As you can see, we're using our ZERO constant to form a range around the real 0. If either component of our velocity goes below 0.01 and above -0.01, we force it to be exactly 0. If you test the example now, you'll see the slide effect is gone.

Another thing you might be thinking is this: "These marbles move like they are on ice." They do. That's because our 0.1 coefficient of friction is too low. If we were modeling a hockey puck on ice, this might work. For marbles on the floor, we'll need to increase the constant to 0.2 or so.

Tip  

I've used the term cheating a lot in this chapter, but it's probably not the right word. You shouldn't think of what we are doing as cheating. You cannot write software that models the real world perfectly. Instead, in all circumstances, whether it's for scientific purposes, games, or anything else, the point of a model is to predict or exhibit behavior that conforms to nature up to some tolerance or margin of error. In games, the margin of error we are shooting for is to convince the user that things are acting like they should in real life. Whatever it takes to meet that goal is what should go into our physics model, and nothing else. It's not really cheating; it's doing the job, but only as well as is required.

Collision Response

Our previous example's marbles are moving around the stage like good little marbles except for when they come into contact. Currently, they just slide over the top of one another, which is clearly not what we want. We need to develop a collision response system to handle this. Also, we'll want to keep the balls inside the stage area by implementing a collision response to the edges of the stage.

The actual collision response depends on the shape of the objects being collided. As we introduce different shapes , the way we resolve collisions will change. Let's look at the simplest example first: a circle colliding with a point.

Circle to Point Collision

Imagine that we have a circle (ball) moving around the stage. Also on the stage are several points that the circle can collide with. Figure 11.9 shows this idea graphically. The circle will be moving but the points will be stationary.


Figure 11.9: Collisions can occur between a circle that is moving and several points that are stationary.

Now imagine what happens when the circle collides with a point. First we have to determine when this occurs, but that's a simple hit test. We can do that with the distance formula to find the distance from the point to the center of the circle. If this distance is shorter than the radius of the circle, we have a hit.

After we have a hit, we have a situation as in Figure 11.10.


Figure 11.10: The circle has collided with the pin. We have options as to how to proceed from here.

We now have a small dilemma on our hands. We need to resolve this collision, but right now, the pin is inside the ball. That clearly can't happen in real life, so we're going to need to move the ball back to some position before it hits the pin. After the ball is reset, we can resolve the collision.

There are several ways we can do this, and those ways give us varying degrees of accuracy in exchange for how much work we want to put in. To be exact, we should really find the specific point of contact between the ball and the circle. We can do this, but it's overkill for most games.

Because our time increment is so small (20 milliseconds), if we pretend the collision happened wherever our circle was last time around, the collision will be close enough to the real collision location that it will easily trick the eye of our user.

We reset the ball to its original position before the collision. We know that the next time the ball moves, it's going to collide with the pin, so we assume that the collision happens right here instead of somewhere between here and where the circle will be in the next update.

Tip  

It's not that hard to find the actual point of collision. We can do it with a parametric equation in terms of time. It does make both the math and the code complex enough that we don't want to do it if we don't have to. Personally, I've never had a case where finding the exact point of collision was necessary. Usually the position of the object on the last iteration is close enough to get us an approximate collision location.

At this point, things get a bit tricky. We need to determine the velocity after the collision. How do we do that? You can probably imagine how the ball must behave. Consider Figure 11.11, which shows the circle with its current velocity before the collision and its position and velocity after the collision.

click to expand
Figure 11.11: The circle needs to bounce off the point in a realistic way.

You can see in Figure 11.11 that the new velocity has to be some kind of reflection. To obtain the reflection, we must figure out what must be reflected as well as the line against which to reflect. In our case, the vector to reflect is the velocity of the circle. The line that we want to reflect against is the collision vector (the vector from the point to the circle's center).

Performing a vector reflection is going to take a bit of hocus pocus on my part. There is a mathematical basis for the operation I'm going to perform, but its explanation is beyond the scope of this chapter. What is important is that we know of an operation we can use to find the reflected velocity, given a vector to reflect against.

The operation I'm going to pull out of the air is this: Assuming our velocity and collision vectors have been normalized, we can find our reflected velocity by using the following equation:

In this equation, v and c are inside parentheses with the dot product. This produces a number that is then multiplied by 2. The result is used as a scalar multiplier to c . The scaled vector c is then subtracted from v to form the collision vector.

That's it. I've given you an equation that you can use to determine the reflected velocity after collision. From there, it's just a matter of scaling the velocity back up to the desired magnitude, and you are finished with the collision response.

Tip  

This equation essentially reflects one normal vector against another normal vector. Because the dot product gives you the amount of v that is in the direction of c, you can use this information as a negative scalar to c, which is in turn removed from v.

Implementing Physics Functions

Now that I've gone over the theory, we should look at implementation. To start, let's talk about some functions that are the ActionScript equivalent of the equations we've talked about so far. After we've developed them, keep in mind that we can use them in the rest of the scripts in this chapter, so we need to add them to the appropriate files.

We are going to need the dist functions that we used in several chapters. Here they are again in case you need them. The dist function returns the actual distance based on the coordinates given, and dist2 returns the distance between the coordinates squared:

 function dist(x1,y1,x2,y2){      return Math.sqrt(dist2(x1,y1,x2,y2)); } function dist2(x1,y1,x2,y2){      return Math.pow(x1-x2,2) + Math.pow(y1-y2,2); } 

We also need to normalize vectors over the next few sections. This function is going to take a vector object that we pass it and scale the vector down to a total distance of 1 by finding the distance from (0,0) to the end of the vector and dividing the x and y components of the vector by that number:

 function normalize(v){      var d = dist(0, 0, v.x, v.y);      v.x = v.x / d;      v.y = v.y / d; } 

To scale our vector to other magnitudes , we find the ratio of the new magnitude to the old magnitude. The resulting ratio is multiplied by both components of the vector, as follows:

 function scaleVectorToMag(v, origionalMag, targetMag){      var changeFactor = targetMag/origionalMag;      v.x = v.x * changeFactor;      v.y = v.y * changeFactor; } 

We have the dot product where we multiply the x components of two vectors and add them to the value of the y components multiplied by each other:

 function dotProduct(v1, v2){       return (v1.x * v2.x) + (v1.y * v2.y); 

With these functions in place, let's go back to working with the marbles and the collisions between them.

Tip  

The normalize function could be a call to scaleVectorToMag with a target magnitude of 1. Although it is probably better style to call the other function instead of duplicating code, this gives another code example of how vector scaling works.

Implementing the Collision Response for Points

To get the concept of points into our marble example, I have added a symbol called point container to the library. I have instantiated this once, just like the marble container. Inside the point container instance, which I have named pointContainer , I have placed several instances of another new symbol called point . The point symbol contains a small red circle that is designed to represent a point that the marbles can collide with.

Because we will need to use the distance equation to find the distance between the marbles and the points, let's go ahead and add in the distance equation function at the end of the code listing:

 function dist(x1,y1,x2,y2){       return Math.sqrt(Math.pow(x1-x2,2) + Math.pow(y1-y2,2)); } 

To implement the collision response, we need to make some changes to the updateMarbles function. We begin by recording a temporary version of the ball's position before we alter it with velocity. After we update the marble's position, we need to test all the points for a collision. Consider the following modifications to updateMarbles that gets us to that point:

 function updateMarbles(){       for(marble in marbleContainer){             var m = marbleContainer[marble];             friction = {x: -m.vel.x, y: -m.vel.y};             m.acc.x +=(friction.x * frictionCoef);             m.acc.y +=(friction.y * frictionCoef);             m.vel.x += m.acc.x; m.vel.y += m.acc.y;             if(m.vel.x >= -ZERO && m.vel.x <= ZERO) m.vel.x = 0;             if(m.vel.y >= -ZERO && m.vel.y <= ZERO) m.vel.y = 0;  var oldpos = {x: m._x, y: m._y};  m._x += m.vel.x; m._y += m.vel.y;  for(point in pointContainer){   var p = pointContainer[point];   // hit test and collision response goes here   }  m.acc.x = m.acc.y = 0;       } } 

As I said, we've recorded the original position in a variable called oldpos . From here, we need to fill in the new for in loop, which iterates over all the points in pointContainer .

We can do the hit test for the marble against each point by comparing the distance from the center of the circle to the point against the radius of the circle. If the radius is greater than the distance, we have a hit. The following script, when added to the inside of the preceding for in loop, produces the proper hit test:

 if(dist(p._x, p._y, m._x, m._y) <= m.radius){ 

Now we're ready to code the response. We begin by putting the marble back where it was before the collision:

 m._x = oldpos.x;                          m._y = oldpos.y; 

After our velocity has been altered, we're going to need to know the magnitude it had before the change. (You'll see why soon.) To handle this, I'm going to record the current velocity's magnitude. We find the magnitude by computing the distance from (0,0) to the vector's terminal point. Because we already know the distance formula, we can use that to find the magnitude of the velocity, as follows:

 var oldMag = dist(0, 0, m.vel.x, m.vel.y); 

Now we need to find the collision vector from the point to the center of the circle:

 var collision = {x: p._x - m._x, y: p._y - m._y}; 
Caution  

You must find the collision vector starting at the point and moving toward the circle. If you do it backward, your reflection will not be correct. Think of it as the vector of the force the immovable point is exerting in response to the collision by the marble.

At this point, we're ready to begin our reflection. As I said, we start by normalizing both vectors. Because normalization is a common operation, I'm encapsulating it in a function called normalize . The implementation of this function is given after our collision response is finished. For now, just assume that the normalize function will normalize a vector properly. We normalize both the velocity and collision vectors:

 normalize(m.vel);                          normalize(collision); 

Now we're ready to implement the formula I stated for finding a new vector after collision with the point. First we must find the dot product of the velocity and collision vectors:

 var tempDotProd = dotProduct(m.vel, collision); 

It's time to adjust the velocity by subtracting 2 times tempDotProd times the collision vector. We do this for each component of the velocity, one at a time:

 m.vel.x -= 2 * tempDotProd * collision.x;                          m.vel.y -= 2 * tempDotProd * collision.y; 

Our velocity now points in the correct direction to respond to the collision. (It's been reflected.) All that's left to do is scale the velocity so that it has the same magnitude it had before we normalized it. Recall that we recorded this old magnitude in the variable oldMag .

Earlier in this chapter, I talked about how to scale vectors. Scaling a vector to a given magnitude is another common operation, so I'm going to encapsulate it in a function called scaleVectorToMag , which takes three arguments.

The first argument to scaleVectorToMag is a reference to the vector that we want scaled. The second argument is the magnitude of the vector we are passing in. The third argument is the desired or target magnitude that we should scale to.

In our case, we know the desired magnitude; it's oldMag . The current magnitude is determined with the following script:

 var currentMag = dist(0, 0, m.vel.x, m.vel.y); 

Now we're ready to scale the reflected velocity vector to match the old magnitude using our scaleVectorToMag function:

 scaleVectorToMag(m.vel, currentMag, oldMag); 

That completes our collision response, so we can close our hit test block:

 } 
Note  

Please make sure that dist , dist2 , normalize , and scaleVectorToMag are added to this .fla.

You should now be able to test the example and watch the marbles bounce off the points properly. To make things clear, I want to add a bit of code to our collision response. By using the drawing API explained in Chapter 6, "Objects: Critter Attack ," we can easily look at the velocity vector, collision vector, and reflected velocity vector. What follows is a complete version of the hit test portion of our updateMarbles function with the new drawing additions in bold:

 if(dist(p._x, p._y, m._x, m._y) <= m.radius){                   m._x = oldpos.x;                   m._y = oldpos.y;                   var oldMag = dist(0, 0, m.vel.x, m.vel.y);                   var collision = {x: p._x - m._x, y: p._y - m._y};                   normalize(m.vel);                   normalize(collision);  //clear the lines drawn the last time we collided   m.clear();   //draw the original velocity vector in blue   m.moveTo(0,0);   m.lineStyle(4, 0x00ffff);   m.lineTo(m.vel.x*30, m.vel.y*30);   //draw the collision vector and its opposite in red   m.moveTo(0,0);   m.lineStyle(0, 0xff0000);   m.lineTo(collision.x*30, collision.y*30);   m.moveTo(0,0);   m.lineTo(-collision.x*30, -collision.y*30);  var tempDotProd = dotProduct(m.vel, collision);                   m.vel.x -= 2 * tempDotProd * collision.x;                   m.vel.y -= 2 * tempDotProd * collision.y;                   var currentMag = dist(0, 0, m.vel.x, m.vel.y);                   scaleVectorToMag(m.vel, currentMag, oldMag);  var tempVel = {x: m.vel.x, y: m.vel.y};   normalize(tempVel);   m.moveTo(0,0);   m.lineStyle(2, 0x00ff00);   m.lineTo(tempVel.x*30, tempVel.y*30);  } 

Here's what's happening. I first wait for the vectors to be normalized and then draw a line from the center of the circle using the velocity vector. This line has a thickness of 4, a color of blue, and is labeled in Figure 11.12. Then I draw two more lines ”one in the direction of the collision vector and the other in the opposite direction of the collision vector. This line has its thickness set to 0, giving us a hairline . These lines have their color set to red. This red collision line is labeled in Figure 11.12. Finally, after the reflected velocity has been found, I draw it in with a line thickness of 2 and a line color of green. This reflected velocity is labeled in Figure 11.12. Note that I multiply the vectors by 30 when I draw them. That's to make them large and easy to see.

click to expand
Figure 11.12: The velocity, reflected velocity, and collision vectors are drawn so that you can see exactly how things are resolving.

I suggest you play around with the marbles Example 2 on the CD to verify that the reflections are working properly.

Circle to Circle Collision

In our marble example, we have circles moving around, and we need to handle the collision of the two. You might be thinking "Uh oh. Collision with points was really hard; this is probably going to kill me!" Don't worry. We've actually done almost all the work already. Handling the collisions between two circles is exactly the same as collision with a point, with one small addition. Let's talk about exactly what that difference is.

We handle the collision response between circles by finding the same collision vector we found with the point collision. Then we find the reflection velocity in the same way as before. The difference comes from the fact that we are going to apply a force to each circle, instead of just one circle. We apply the collision force to the first circle as if the circle had hit a point, and we apply a force to the other circle in the opposite direction.

Now comes the additional piece: We must scale each of these new velocity vectors so that both marbles share the force of the collision. The amount of force given to each circle is based on the relative masses of the circles. This is a function of the third law of motion we talked about earlier.

As you can see, most of the nitty-gritty collision response was done in our previous example. All we need to do here is some fancy scaling. Let's get right into the implementation in our marble example.

Implementing the Collision Response for Marbles

Because we already have several marbles on the stage, we don't need to add anything to set this up, other than additions to the script.

To begin, we need to add the collision detection. This can go under the for in loop that iterates over the points. (This is right before we reset acceleration to 0.) To iterate over all the marbles, we use yet another for in loop. However, we need to be careful because we're already inside a for in loop that iterates for all the marbles. We'll need to use different variable names here so that we can distinguish our collision test marble from our update marble.

The update marble is named m , so I'm going to call our collision test marble cm for collision marble. We also need to ensure that m and cm are not referring to the same marble. Our logic will fail if we start hit testing a marble against itself. Keeping everything straight with our new for in loop gives us the following script:

 for(collisionMarble in marbleContainer){                var cm = marbleContainer[collisionMarble];                if(m != cm){                     //hit test and collision response goes here                }          } 

Now we're ready to fill in our hit test logic, which we do using the distance formula again. However, instead of testing the distance from the point to the circle, we test the distance from one circle to the other. We compare this distance to the sum of the radii of the circles. If the distance is less than the sum of the radii, we have a hit:

 if(dist(m._x, m._y, cm._x, cm._y) < m.radius + cm.radius){                         //collision response goes here                   } 

The collision response is going to be familiar to you with some minor modifications. We begin by moving the update marble ( m ) back to its original position:

 m._x = oldpos.x;                          m._y = oldpos.y; 

We record our current velocity for use later:

 var oldMag = dist(0, 0, m.vel.x, m.vel.y); 

Then we find our collision vector using the circle centers:

 var collision = {x: cm._x - m._x, y: cm._y - m._y}; 

After that, we normalize the velocity and collision vectors:

 normalize(m.vel);                          normalize(collision); 

From there, we take the dot product of the velocity and collision vectors:

 var tempDotProd = dotProduct(m.vel, collision); 

Then we use the reflection formula to determine our reflected velocity for the update marble:

 m.vel.x -= 2 * tempDotProd * collision.x;                          m.vel.y -= 2 * tempDotProd * collision.y; 

Next we determine the current magnitude of the reflected velocity vector:

 var currentMag = dist(0, 0, m.vel.x, m.vel.y); 

Now we need to diverge from what we did before. We have to take the oldMag and distribute it to both marbles, based on mass. This is a standard algebraic scaling operation, done by taking the ratio of masses and multiplying it by the oldMag . The oldMag must be divided by 2 before the ratio is multiplied because two new vectors are sharing in it. The update marble will receive a new magnitude kept in a variable called newMMag , and the collision marble will receive a new magnitude kept in a variable called newCmMag , as follows:

 var newMMag = (cm.mass/m.mass)*(oldMag/2);               var newCmMag = (m.mass/cm.mass)*(oldMag/2); 

Now we can go ahead and scale our update marble to match newMMag :

 scaleVectorToMag(m.vel, currentMag, newMMag); 

The update marble is now out of the way, and we can finish our work by determining the reflected velocity for the collision marble. Remember that the collision marble is going to get a force in the opposite direction, as did the update marble. To employ this, we use addition instead of subtraction in the reflection formula, as follows:

 cm.vel.x += 2 * tempDotProd * collision.x;               cm.vel.y += 2 * tempDotProd * collision.y; 

That will reflect the velocity of the collision marble in the opposite direction than we reflected the update marble. Now we can determine the current magnitude of this collision vector's velocity and scale it to match newCmMag :

 var currentMag = dist(0, 0, cm.vel.x, cm.vel.y);               scaleVectorToMag(cm.vel, currentMag, newCmMag); 

I already closed the blocks in our earlier code, so I don't need to do that again now. I have included additional drawing code that highlights the velocity, collision, and reflected velocity vectors graphically. If you examine the marbles example 3 on the CD, you'll see these lines being drawn during marble-to-marble collisions. You should play around with the marbles example 3 to see the marble-on-marble collision and notice how the marbles of different masses interact.

Tip  

Our marble examples are not optimized to keep things as clear and simple as possible. In a real game, you would want to short circuit your collision testing when one collision has been found. Because the marbles are being reset to their previous position on collision, no more hit testing needs to be done for that marble. I have not implemented this in our marble example because it would obfuscate the collision response logic, which is the point of the examples. In a real game, your collision detection is often the most CPU- intensive part, so you must take care to do no unnecessary work in that portion of your code.

From here, we could get into circles colliding with lines and line segments, triangles , and even polygons with arbitrary numbers of sides. For the most part, that would result in us discussing more sophisticated forms of collision detection. The resolution of collisions is pretty much covered by our vector reflection formula (until you need angular velocity, which is described in a later section).

In any case, we have reached a good stopping point in our physics examples. From here, I have a couple more topics that I want to talk briefly about; after that, we're ready to start building Pachinko .




Macromedia Flash MX 2004 Game Programming
Macromedia Flash MX 2004 Game Programming (Premier Press Game Development)
ISBN: 1592000363
EAN: 2147483647
Year: 2004
Pages: 161

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