7.4 Implementing forces

The base class cForce::force returns a zero vector, but we have 'global' force classes cForceGravity, cForceDrag, and cForceVortex which return non-trivial values. We use them in the Ballworld and Dambuilder games, discussed in Chapter 18: Interesting Worlds. We also have 'relative' steering force classes liked cForceObjectSeek, cForceClassEvade, and cForceEvadeBullet.

Figure 7.2 is a class diagram of all the forces we provide with the Pop Framework.

Figure 7.2. Class diagram of the cForce child classes

The idea is that each force(cCritter*) method should return a force that the critter will divide by its mass and add into its acceleration. The magnitude of the force will depend on the critter's current situation in the world and on a Real _intensity field that cForce has.

Gravity is proportional to mass ? much as electrostatic attraction is proportional to electric charge ? and, again from Newton, we know that the gravitational force (F) between two objects is a gravitational constant (G) times the product of their masses (m1 and m2) divided by the square of the distances between them (D2), to give a force like

For simple simulations, we like to use a much simpler approximation to gravitational force. It's characteristic of games to choose liveliness and speed of execution over a 100% accurate emulation. For a small object near the surface of the Earth, the distance to the Earth's center is so large that the object's motions don't effectively change this distance. In a situation like this, we can lump together the gravitational constant, the Earth's mass, the inverse square of the distance to the Earth into a new constant (g), and then an object of mass (m) will experience a gravitational force (F) of size

It's useful to have the freedom to specify the direction of a 'global' gravity like this, so we'll give our cForceGravity class a _pulldirection as well as its _intensity constant.

```cVector cForceGravity::force(cCritter *pcritter)
{
return _intensity * pcritter->mass() * _pulldirection;
} ```

Remember that the cCritter maintains its mass as a quantity proportional to its density times the cube of the critter's radius.

For simulating something like a solar system, we'd want to implement a more sophisticated kind of object-to-object gravity ? this is left for Exercise 7.5.

We think of drag as being a force like friction. The effect of friction is to slow something to a stop. Normally friction increases with an object's speed, and acts in a direction opposite to the object's motion. Friction is also proportional to an object's area: think of a sliding puck or of an airship moving through the atmosphere pushed by the wind. More area means more drag.

To make our drag force more general, we allow for the possibility that it is a drag relative to some moving fluid. Either we think of objects sliding on a moving surface such as a conveyer belt, or we think of floating objects under the influence of air or water currents. We use a _windvector field to specify the speed and direction of the medium's motion. The effect of drag in a moving medium is to match the object's velocity to the velocity of the medium. If the _windvector is the zero vector, then our generalized drag force is the same as friction. The idea is to continually return a force that will act to accelerate the critter in such a way as to minimize the difference between the critter's velocity and the _windvector.

```cVector cForceDrag::force(cCritter *pcritter)
{
return cVector(area * _intensity * (_windvector ?
pcritter->velocity()));
} ```

One caveat regarding cForceDrag. If you set the _intensity to a large value, there is a danger of overshooting the _windvector and oscillating back and forth. In terms of a simple frictional drag force with zero _windvector, if you define an overly large _intensity constant, then your simulated physics will cause a moving object to jerk backwards due to its counteracting friction force, and then, once it's going backwards, the drag will make the object jerk forwards again, and so on. Generally it's a good idea to keep the intensity of a cForceDrag between 0.0 and 1.0. (Do remember, however, that, in general, if you find some anomalous tweak of your values that enhances your game, go ahead and use the values even if they're not physically realistic.)

It might be fun to have some whirlpools, so we allow for a child of cForceDrag in which the windvector varies from point to point. We call our new force cForceVortex, and we set it up as shown in Figure 7.3. We specify a center of the vortex and, thinking of the eye of a hurricane, we call it _eyeposition. And we give a _spiralangle to determine which way the vortex is moving things: inward, outwards, or in a circular fashion.

Figure 7.3. Vortex force

The idea is that the drag force at a critter position is computed as follows: (a) take the vector that runs from the _eyeposition to the critter position; (b) imagine placing this vector with its tail at the critter position: and (c) rotate this vector couterclockwise by _spiralangle degrees. The further a critter gets from the eye position, the more powerful is the vortex force. Remember that in C++ angles are measured in radians, so we normally think of the angle as some multiple of PI. (By the way, we #define PI in realnumber.h.) So if you want a circular motion, you set _spiralangle equal to PI/2.0. The cForceVortex force method gets implemented like this.

```cVector cForceVortex::force(cCritter *pcritter)
{
_windvector = (pcritter->position() ? _eyeposition);
_windvector.turn(_spiralangle);
return cForceDrag::force(pcritter);
} ```

The next forces we look at are what might be called 'relative' forces as opposed to 'global' forces. Relative forces involve a critter's reaction to some other critter. We provide for two kinds of relative forces. In the cForceObject, we react to some one specific other critter that we're watching. The baseclass cForceObject holds a cCritter *_pnode reference field to a certain critter and its force(pcritter) method considers the pcritter situation relative to _pnode.

There are a variety of cForceObject forces like this that we might implement. Spring forces and relative gravitational forces come to mind. The Pop Framework provides a cForceObjectSpringRod, which is a force for attracting a critter to another critter by a 'spring' while using a 'rod' to keep them from getting too close. You can make amazing wobbly assemblages of things by hooking critters together with these. The cForceObjectSpringRod:force(cCritter *pcritter) does the following.

• If pcritter is closer than the desired _rodlength from the _pnode, we move pcritter out to _rodlength away from _pnode and return a zero force

• Otherwise return a force proportional to the distance between pcritter and _pnode.

The Worms game, described in Chapter 14: 2D Shooting Games, demonstrates a use of the cForceObjectSpringRod. Full code for all the forces can be found in force.cpp.

Another kind of relative cForceObject force is the cForceObjectSeek, which helps the pcritter pursue a _pnode critter. The simplest notion would be to have cForceObjectSeek::force(pcritter) simply return a force along the vector direction from pcritter to the _pnode of the cForceObject.

The Pop Framework uses an improved seeking force suggested by Craig Reynolds (op. cit., p. 154). Reynolds makes the point that rather than applying a force in the desired direction of motion, it's more effective to apply a force in the direction of the difference between the critter's desired motion and its current motion, as shown in Figure 7.4. We will also have the seek force set the critter's speed to its maximum.

Figure 7.4. A seek force

There is a more general kind of relative force we can consider. This is a force in which a critter reacts to any and all members of a specified class of critters that we watch. We create a base class cForceClass for this and give it a CRuntimeClass *_pnodeclass member. As is discussed in Chapter 22: Topics in C++, a CRuntimeClass object keeps track of a class type, basically by storing a string with the name of the type along with some additional information.

One issue in interacting with objects of a certain class type K is whether we also want to interact with objects of class KChild, where KChild is a child class of K. We give the cForceClass a BOOL _includechildclasses field to let the programmer decide.

In the Pop Framework we provide a cForceClassEvade force for evading all objects of a given class, and we derive as child of this a cForceEvadeBullet to evade all cCritterBullet objects. Here's a summary of how cForceEvadeBullet acts.

• If there are no bullets to evade, return a zero force.

• Otherwise find the closest bullet.

• If the closest bullet is moving away from you, return a zero force.

• If this evade direction lies in the same direction the bullet is moving, you're in a 'rabbit running down a railroad track away from a locomotive' situation, which is no good. Rotate your evade direction by 90 degrees.