Implementing Physics


Time to dive a little bit deeper into the implementation of the physics required for your Racing Game. You saw some of the features of the many powerful physics engines available today, but because it is not easy to implement them and because they are not written in .NET you are going to continue implementing physics in your own way.

You handled some of the car controlling and simple gravity at the beginning of the chapter. The thing you are currently missing is an accurate collision detection system for your car. The car should be stopped when you crash into one of the guard rails. You are also going to check some of the more complex situations like driving through loopings. Loopings are actually not hard to implement. Thanks to the track generation code from the last chapter they can be added to your tracks quite easily by just putting two of the spline points on top of each other, and when driving along the road the loopings are almost handled automatically if your physics system for the car is handling all the car forces correctly.

Handling Loopings

Before you continue with the hard stuff (collision detection and response, which is actually most of the code in the CarPhysics class), please take a look at Figure 13-8, which shows in a simplified way how the forces act on your car when you drive through a looping.

image from book
Figure 13-8

If you just put the car on the top part of the looping without applying any forces, gravity (red) will pull it down and you will not be able to even move or drive the car because you will lose contact with the road immediately. So to even get to the top part of the looping the car must be always be pushed toward the road, which is upwards at the top of the looping. This force must be stronger than the gravity or else your car will lose contact with the road and begin to fall down again (see Figure 13-9). It is important that you have enough momentum to keep the car on the road. Momentum is the force the car had from the car’s previous movement forces in the current time frame you are looking at.

image from book
Figure 13-9

If you just drive straight up a wall (with a ramp in front of it to get some momentum first), the gravity pulls you down and after a while you are going to lose all of the momentum and finally fall down. Because a looping is always changing the direction of the car, you are able to keep the car on the road relatively easy. Beating gravity is no big deal if you have a centrifugal force that is stronger than the gravity. Figure 13-10 shows you an example of just circling around a ball attached to a string. Even with a slow swinging force you will be able to let the ball fly in circles and the faster you go, the less you will even notice the gravity the ball usually has. It will just get harder to keep the string in your hand because the ball is pulling on it stronger and stronger thanks to the centrifugal force.

image from book
Figure 13-10

You can see the centrifugal force pushes the ball away from the hand and the gravity is relatively slow in comparison to that force when you move the ball fast enough. The momentum will keep the ball in the circulating motion even if you stop moving your hand.

Spring Physics

Most formulas for your game are simplified and the car movement is not as complex as it could be. You do not care much about the car engine, about any of the car internals or use. It is possible to implement many more parameters to use the power, rpm, starting and stalling forces, better braking and friction formulas, and so on. For a more detailed description about car physics please check out the following link at http://www.racer.nl/reference/carphys.htm.

Because of your simplified car logic many of these resulting effects are not implemented like pitching the car backwards when you accelerate and forward when you brake. But you still can implement something like this in a simplified way yourself.

All car wheels are connected to springs on the car allowing the car to stay more stable on the road even if you drive over smaller bumps on the road. When you accelerate the wheels push the car forward but it takes a while until enough movement energy is able to move the huge mass of the car itself. The effect becomes even more apparent when you suddenly stop the car. The wheels stop, but the car itself is still in motion and the still moving car mass pulls the entire car forward and tilts the car a little bit (see Figure 13-11).

image from book
Figure 13-11

Gravity always pushes you down, but different parts of the car, especially the wheels and the rest of the car, behave differently. The wheels are attached to springs on the car allowing the car to pitch forward and backwards, which will happen when you accelerate or decelerate and even if you just stay on a road that is not straight (if you drive uphill, the car will be pulled backwards).

You do not really have a representation of the springs and you don’t even render the wheels the way they are shown in Figure 13-11. The car wheels will go into the ground if the car is pitched, but because you don’t see the car from the side you will not notice it in the game. The chase camera is always behind the car and you are going to see most of the pitch effect just from the top of and behind the car.

Because the effect is relatively simply described you can just pitch the car whenever the speed changes for it:

  • When accelerating, pitch the car backwards - the faster you are accelerating, the bigger the pitch angle is going to be.

  • When braking, pitch the car forward - this pitch effect is usually stronger than the accelerating pitch effect because you brake much faster than you accelerate the car.

You already saw this behavior in the last game, XNA Shooter, where your own ship was rotated horizontally and vertically when you moved in either direction. The same formula can be used for the car too. To fix the problem that the wheels and the rest of the car would disappear into the road, you are going to limit the effect:

  // Calculate pitch depending on the force float speedChange = speed - oldSpeed; // Limit speed change, never apply more than 8 mph change per sec. if (speedChange < -8 * moveFactor)   speedChange = -8 * moveFactor; if (speedChange > 8 * moveFactor)   speedChange = 8 * moveFactor; carPitch = speedChange; 

When the car is not changing its speed the car pitch value will stay 0 and behave correctly, and if you are accelerating or decelerating the car also behaves the correct way. But even after applying a better smoothing formula the effect does not look very convincing. In the real world the car would pitch forward and then backwards swinging out until the springs of the cars lose all of their forces to their friction.

All you need now is to let the car swing forward and backwards with a simple spring formula. The SpringPhysicsObject class (see Figure 13-12) of the Racing Game helps you with that. It calculates the position of a single object with a certain mass attached to a spring with a specific spring constant. This position swings up and down, but it slows down through the friction constant also used in this class. You are going to use a big mass and a strong friction. This results in a slow pitch effect for the car and the pitch effect becomes weaker very quickly. You don’t want the car to bounce around like crazy.

image from book
Figure 13-12

This helper class is directly used in the CarPhysics class and initialized with the CarMass constant, 1.5 for the friction and 120 for the spring constant (both relatively high values). The initial position for the virtual spring object is 0 (car in its normal state):

  /// <summary> /// Car pitch physics helper for a simple spring effect for /// accelerating, decelerating and crashing. /// </summary> SpringPhysicsObject carPitchPhysics = new SpringPhysicsObject(   CarMass, 1.5f, 120, 0); 

To update the current virtual spring object position when the car changes the speed you can use the ChangePos helper method:

  carPitchPhysics.ChangePos(speedChange); 

And finally you just call Simulate with the current time factor for this frame to simulate the pitch behavior, then when rendering you are going to use the pos value of this class to get the current pitch value:

  // Handle pitch spring carPitchPhysics.Simulate(moveFactor); 

The interesting part of the SpringPhysicsObject class is obviously the Simulate method; all the other methods just set some values. To understand what the spring formula does you have to know about the restoring force for the spring by applying Hooke’s law to the well-known F = m*a formula.

Hooke’s law describes how stress is directly proportional to strain by using a constant pressure value. This way elasticity, spring, stress, and strain physics can be described.

If you want to read more about Hooke’s law please read a good formulary book or check out the Internet. Wikipedia and Wolfram Research (http://scienceworld.wolfram.com) have great articles about many physics problems.

You are just going to use the simple spring formula F = –k*x where F is the restoring force that pulls the object attached to your spring back or forward and k is the spring constant. Figure 13-13 describes this formula and the effect it has over time on the spring object you are going to use for the car pitch effect.

When no force is acting on the object and it is in the initial position (0) it will just stay that way. But if you pull it down the spring expands and forces the object back to its initial position. When it reaches the position 0 again, the spring restoring force is also back to zero, but the speed of the attached object is still fast and its momentum still pushes it up. The spring restoring force becomes negative now and prevents the object from moving up anymore when it reaches its highest position of 1. Now the restoring force is –1 and that pulls the object down again and when it is at the bottom position, the game starts from the beginning again.

The only thing that slows you down is friction, which should be very strong in your case to make sure the car does not bounce around too much. Please also note that you are ignoring gravity here and it is not really important for your car pitch effect because the car handles gravity itself, and for the spring effect you can safely ignore the gravity because it pulls everything down to the same direction anyway and does not affect any calculations.

The code to make this all work is in the Simulate method and you should be able to quickly understand it with the help of the knowledge you just acquired:

  /// <summary> /// Simulate spring formular using the timeChange. /// The velocity is increased by the timeChange * force / mass, /// the position is then timeChange * velocity. /// </summary> public void Simulate(float timeChange) {   // Calculate force again   force += -pos * springConstant;   // Calculate velocity   velocity = force / mass;   // And apply it to the current position   pos += timeChange * velocity;   // Apply friction   force *= 1.0f - (timeChange * friction); } // Simulate(timeChange) 

The SpringPhysicsObject class can be found in the Game Physics namespace (see Figure 13-13). If you want to add more physics classes and try to implement more physics behaviors, you should do it in this namespace. This way it gets easier when you try to reuse some of the physics in future projects; just copy over and use the physics namespace.

image from book
Figure 13-13

Collision Detection

The final task for this chapter is to do the collision detection for the car and the guard rails. The car cannot collide with any other cars because you are the only car on the road and I made sure that there are no other objects on the road you could collide with. Collision detection and especially the response would be a lot more complex if you allowed other objects on the road. For example, having lanterns, signs, trash cans, and so on on the road would mean that you would have to kick them away with your car if you hit them. Then again these objects need to collide with each other and also with the surrounding world.

If you have a good physics engine this would be possible, but it is still a lot of work to tweak these objects, the general physics constants, and constantly test all the different kinds of collisions that can occur. It would probably be fun to implement such techniques and play around with them, but since I don’t even have a full-blown physics engine for XNA, even getting the basics up and running would take longer than the whole project took.

The principles for collision detection and collision response are still the same, but you can simplify them to your needs and only involve the car and the guard rails. Because the guard rails are indestructible the only object that is affected by any collision is your car.

The biggest part of the physics in the Rocket Commander game was the asteroid-to-asteroid collision detection and optimization. The collision response is not very complex and thanks to the many unit tests even the basic collision detection could be done in a very simple way.

PhysicsAsteroidManager

For example, the TestAsteroidPhysicsSmallScene unit test of the PhysicsAsteroidManager class in Rocket Commander shows a good way to test collision detection. The unit test allows you to press 1–7 to test out various scenarios and shows the result of an asteroid collision event.

Figure 13-14 shows this unit test in action by using two asteroids of the same size flying toward each other and bouncing off in opposite directions after the collision happens. The method SetupScene is used to set the asteroids to their initial positions while the unit test itself handles all the collision testing.

image from book
Figure 13-14

  case 2:   // Collide with crazy movement vectors   AddAsteroid(new Vector3(-35, +19, 8) * (-2),     12.5f, asteroidModel.ObjectSize,     new Vector3(-35, +19, 8));   AddAsteroid(new Vector3(+15, 40, 14) * (-2),     12.5f, asteroidModel.ObjectSize,     new Vector3(+15, 40, 14));   break; 

The TestAsteroidPhysicsSmallScene unit test then calls the HandleSectorPhysics method, which checks all asteroids in a certain sector and does a simple bounding spheres test if they are too close to each other and if you should handle such a collision case.

The method not only checks the asteroids from your own sector, but also all asteroids from all surrounding sectors, which could very well collide with one of your own asteroids. Even with the very good sector optimization there are still several thousand collision detection checks going on in Rocket Commander each frame and it gets slower the more asteroids you have. For this reason it is a good thing that Rocket Commander XNA now uses multiple threads to handle the physics and the rendering code in two different threads.

The collision detection adds up the radiuses of both asteroids and checks if they are smaller together than physically possible. If so, a collision has occurred and you have to handle it. The following code just shows the collision test for one single sector. The code for testing all surrounding sectors is similar, but a lot longer.

  // Only check this sector! Crosscheck with any other asteroid in // this sector. foreach (Asteroid otherAsteroid in thisSectorAsteroids)   if (asteroid != otherAsteroid)   {     float maxAllowedDistance =       otherAsteroid.collisionRadius +       asteroid.collisionRadius;   // Distance smaller than max. allowed distance?   if ((otherAsteroid.position      asteroid.position).LengthSq() <     maxAllowedDistance * maxAllowedDistance)   {     HandleAsteroidCollision(asteroid, otherAsteroid);   } // if (otherAsteroid.position) } // foreach if (asteroid) 

The HandleAsteroidCollision method now handles a collision and it moves the asteroids away from each other so they don’t intersect with each other:

  // Put both circles outside of the collision // Add 1% to add a little distance between collided objects! otherAsteroid.position = middle +   otherPositionRel * otherAsteroid.collisionRadius * 1.015f; asteroid.position = middle +   positionRel * asteroid.collisionRadius * 1.015f; 

Then the method reflects both movement vectors of the two asteroids away from the collision plane (the purple line in Figure 13-14) by using the total collision force and applying it to the masses of the asteroids. This way a smaller asteroid gets pushed away by a bigger asteroid with a far greater force than the other way around. Check out the unit tests to see this behavior in action. The method is actually quite long and does a lot of extra checking, calculates masses and directions, and even adds rotation speed to the asteroids, but the code for handling the collision looks basically like this:

  // Normalize movement Vector3 asteroidDirection = asteroid.movement; asteroidDirection.Normalize(); Vector3 otherAsteroidDirection = otherAsteroid.movement; otherAsteroidDirection.Normalize(); // Get collision strength (1 if pointing in same direction, // 0 if 90 degrees) for both asteroids. float asteroidCollisionStrength = Math.Abs(Vector3.Dot(   asteroidDirection, asteroidNormal)); float otherAsteroidCollisionStrength = Math.Abs(Vector3.Dot(   otherAsteroidDirection, otherAsteroidNormal)); // Calculate reflection vectors from the asteroid direction and the // normal towards the reflection plane. Vector3 asteroidReflection =   ReflectVector(asteroidDirection, asteroidNormal); Vector3 otherAsteroidReflection =   ReflectVector(otherAsteroidDirection, otherAsteroidNormal); // Make sure the strength is calculated correctly // We have also to correct the reflection vector if the length was 0, // use the normal vector instead. if (asteroidDirection.Length() <= 0.01f) {   asteroidCollisionStrength = otherAsteroidCollisionStrength;   asteroidReflection = asteroidNormal; } // if (asteroidDirection.Length) if (otherAsteroidDirection.Length() <= 0.01f) {   otherAsteroidCollisionStrength = asteroidCollisionStrength;   otherAsteroidReflection = otherAsteroidNormal; } // if (otherAsteroidDirection.Length) // Ok, now the complicated part, everything above was really easy! asteroid.movement = asteroidReflection *   // So, first we have to reflect our current movement speed.   // This will be scaled to 1-strength to only account the reflection   // amount (imagine a collision with a static wall). In most cases   // Strength is close to 1 and this reflection will be very small.   ((1 - asteroidCollisionStrength) * asteroidSpeed +   // And finally we have to add the impuls, which is calculated   // by the formula ((m1-m2)*v1 + 2*m2*v2)/(m1+m2), see   // http://de.wikipedia.org/wiki/Sto%C3%9F_%28Physik%29 for more help.   (asteroidCollisionStrength *   (Math.Abs(asteroidMass - otherAsteroidMass) * asteroidSpeed +   (2 * otherAsteroidMass * otherAsteroidSpeed)) / bothMasses));   // Same for other asteroid, just with asteroid and otherAsteroid   // inverted.   otherAsteroid.movement = otherAsteroidReflection *     // Same as above.     ((1 - otherAsteroidCollisionStrength) * otherAsteroidSpeed +    (otherAsteroidCollisionStrength *    (Math.Abs(otherAsteroidMass - asteroidMass) * otherAsteroidSpeed +    (2 * asteroidMass * asteroidSpeed)) / bothMasses)); 




Professional XNA Game Programming
Professional XNA Programming: Building Games for Xbox 360 and Windows with XNA Game Studio 2.0
ISBN: 0470261285
EAN: 2147483647
Year: 2007
Pages: 138

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