The Physics of Gravity

0
313

Simulating Gravity

Gravity is an interesting phenomenon of physics. Every moment of every day, you are literally falling inward toward the center of the earth. The surface of the earth keeps you from falling farther, but the same force that causes a person parachuting out of an airplane to fall downward, which is the same force that causes satellites in orbit to gradually lose position and require constant adjustments, causes you to fall downward at every moment. It actually takes quite a bit of energy to stand up against the force of the earth’s gravity. Fortunately, a human body is relatively small, so the gravity exerted is not too great that we can’t survive. But try to imagine, the same gravity pulling you toward the center of the earth also keeps the moon in orbit! The moon is gradually losing its orbit, by the way. Over time, it will draw closer and closer to the earth and eventually collide. The time frame is huge, but it is happening nonetheless.

Escape Velocity

To break out of the earth’s gravity field requires a huge amount of thrust! The velocity required to do so is called escape velocity, which is the velocity required to escape the gravity of a planet or another large, massive object. Gravity, according to physics, is called gravitational potential energy. The term escape velocity is somewhat incorrect, but the term has stuck over the years. Velocity, as you have learned in this book, affects movement in a specific direction calculated with cosine (for X) and sine (for Y). Escape velocity is actually a speed, not a velocity, because an object moving at escape speed will break orbit no matter which direction it is moving in.

Earth’s escape velocity is 6.96 miles per second (11.2 kilometers per second). In terms of aircraft speed, that is Mach 34, about 10 times faster than a bullet! But these terms are applicable only to ballistic objects, like a rocket launched from the surface. Ballistic is a term that means something is fired or launched and then the object coasts once it reaches a desired speed. The ballistic rocket that launched the Apollo astronauts toward the moon had to reach escape velocity with two or more rocket stages, plus an outer space thruster that sent the spaceship coasting toward the moon. But a spacecraft lifting off from the earth does not need to reach this ballistic escape velocity if it can just maintain a consistent thrust for a longer period. A U.S. Air Force F-22 Raptor jet, for instance, has enough thrust to go ballistic in terms of its own weight (meaning it can continue to speed up while going straight up, without ever slowing down), with fuel being the only limitation.

Calculating “Gravity”

This is not really “gravity” we’re calculating, but rather the force two objects exert on each other. The result looks very much like the pull of gravity. Just note that the code we’re about to write is simplified for a gameplay mechanic, not for spacecraft trajectories.

The formulas involved in “rocket science” are not overly complicated, but we’re going to use a simpler technique to simulate gravity between any two massive objects. The end result will be similar as far as a game is concerned. What we need to be concerned with is the mass, position, and acceleration factor for each object.

Using these three basic pieces of information, we can cause two massive objects to affect each other gravitationally. During every update, the position, acceleration, and velocity are all updated. While the two objects are far apart, their interaction will be negligible. But as they draw closer, the acceleration factor will affect the velocity, which will cause the two objects to speed up toward each other. In the terms of rocket science, this is called “the whiplash effect.” NASA and other space agencies often use whiplash gravity to propel their spacecraft toward a destination more quickly, costing less fuel as a result. After we have a simulation running, we can try this out!

To cause two massive objects to interact, first we have to calculate the distance between them. This will have a direct effect on the amount of force the objects exert upon each other. This code takes into account the situation in which the two objects have collided, in which case the distance factor is inverted:

[code]
double distX = this.position.X – other.position.X;
double distY = this.position.Y – other.position.Y;
double dist = distX*distX + distY*distY;
double distance = 0;
if (dist != 0) distance = 1 / dist;
[/code]

Next, we use these distance values to calculate the acceleration of the object. Since this is directly affected by the distance to the other object, the acceleration will increase as the objects grow closer, which further increases acceleration. That is the nature of the whiplash effect, and it works well as long as the objects do not collide:

[code]
this.acceleration.X = (float)(-1 * other.mass * distX * distance);
this.acceleration.Y = (float)(-1 * other.mass * distY * distance);
[/code]

Velocity is updated directly with the acceleration values:

[code]
this.velocityLinear.X += this.acceleration.X;
this.velocityLinear.Y += this.acceleration.Y;
[/code]

The position, likewise, is updated directly with the velocity values:

[code]
this.position.X += this.velocityLinear.X;
this.position.Y += this.velocityLinear.Y;
[/code]

It is very easy to lose track of game objects that interact with this gravitational code. If two objects collide, so that the distance between them is zero, then they will fling away from each other at high speed. I recommend adding boundary code to the velocity and acceleration values so that this doesn’t happen in a playable game.

The Gravity Demo

The sample project can be found again under the name “Black Hole Game” in this hour’s resource files. Because all the concepts and code learned during these last remaining chapters is directly applied to the sample game in the final hour, the project will just grow and evolve, and we’ll continue to use the same name, as well as use code developed during previous hours. The example will be based on the game state example in the preceding hour, and we’ll use the PlayingModule.cs file rather than Game1.cs as has been the norm previously. Figure 22.1 shows the example from this hour running.

The “plasma” sprite is rotating around the black hole in an elliptical orbit.
FIGURE 22.1 The “plasma” sprite is rotating around the black hole in an elliptical orbit.

This program requires several bitmap files. You can copy them directly out of the project included in the book resource files, or create your own. For the “black hole” image, I have just created a filled black circle with transparency around the outside. The “plasma” image is an alpha blended particle that I’ve had for quite a while, and have used as a weapon projectile in some past games. The asteroid in this example is just window dressing, included to show how an object with an OrbitalMovement animation looks compared to the calculated trajectories of a MassiveObject object. To get a head start on the gameplay that will be needed for this game, I have added some code to cause the orbiting power satellites (currently represented just as a “plasma” ball) to get sucked into the black hole if they get too close. When this happens, the gravity code is replaced with the OrbitalMovement animation class. In Figure 22.2, we see that the projectile, or “plasma” sprite, has been captured by the black hole.

The “plasma” sprite has been captured by the black hole.
FIGURE 22.2 The “plasma” sprite has been captured by the black hole.

MassiveObject Class

A new class called MassiveObject, which inherits from Sprite, will handle our gravitational needs. Listing 22.1 provides the source code for this new class.

LISTING 22.1 Source Code for the MassiveObject Class

[code]
class MassiveObject : Sprite
{
public double mass;
public Vector2 acceleration;
public float radius,angle;
public bool captured;
public MassiveObject(ContentManager content,
SpriteBatch spriteBatch)
: base(content, spriteBatch)
{
mass = 1.0f;
acceleration = Vector2.Zero;
radius = 50.0f;
angle = 0.0f;
this.captured = false;
}
public void Update(GameTime gameTime)
{
}
public override void Draw()
{
base.Draw();
}
public void Attract(MassiveObject other)
{
//calculate DISTANCE
double distX = this.position.X – other.position.X;
double distY = this.position.Y – other.position.Y;
double dist = distX*distX + distY*distY;
double distance = 0;
if (dist != 0.0) distance = 1 / dist;
//update ACCELERATION (mass * distance)
this.acceleration.X = (float)(-1 * other.mass * distX
* distance);
this.acceleration.Y = (float)(-1 * other.mass * distY
* distance);
//update VELOCITY
this.velocityLinear.X += this.acceleration.X;
this.velocityLinear.Y += this.acceleration.Y;
//update POSITION
this.position.X += this.velocityLinear.X;
this.position.Y += this.velocityLinear.Y;
}
}
[/code]

PlayingModule.cs

This is the main source code file for the Gravity Demo, where the black hole and other MassiveObject (Sprite) objects are created, updated, and drawn. In other words, this is our main source code file. The original game state code is still present in the other files, but that has been removed from PlayingModule.cs. To return to the title screen, it’s still possible to set game.gameMode as before! Listing 22.2 shares the source code for the class.

LISTING 22.2 Source Code for the PlayingModule Class

[code]
public class PlayingModule : IGameModule
{
Game1 game;
SpriteFont font;
Random rand;
MassiveObject blackHole;
MassiveObject asteroid;
MassiveObject plasma;
public PlayingModule(Game1 game)
{
this.game = game;
rand = new Random();
}
public void LoadContent(ContentManager content)
{
font = content.Load<SpriteFont>(“WascoSans”);
blackHole = new MassiveObject(game.Content,
game.spriteBatch);
blackHole.Load(“blackhole”);
blackHole.position = new Vector2(400, 240);
blackHole.velocityAngular = -.05f;
blackHole.scale = 1.0f;
blackHole.mass = 100;
asteroid = new MassiveObject(game.Content,
game.spriteBatch);
asteroid.Load(“asteroid”);
asteroid.columns = 8;
asteroid.totalFrames = 64;
asteroid.size = new Vector2(60, 60);
asteroid.radius = 80;
asteroid.animations.Add(new OrbitalMovement(
new Vector2(400,240), 80, 0, 0.08f));
asteroid.scale = 0.5f;
plasma = new MassiveObject(game.Content,
game.spriteBatch);
plasma.Load(“plasma32”);
plasma.position = new Vector2(200, 240);
plasma.mass = 1;
plasma.velocityLinear = new Vector2(1.0f, 7.0f);
}
public void Update(TouchLocation touch, GameTime gameTime)
{
int time = gameTime.ElapsedGameTime.Milliseconds;
blackHole.Update(gameTime);
blackHole.Rotate();
asteroid.angle += 0.001f;
asteroid.Update(gameTime);
asteroid.Rotate();
asteroid.Animate(time);
asteroid.Animate();
plasma.Update(gameTime);
plasma.Rotate();
plasma.Animate();
if (!plasma.captured)
{
plasma.Attract(blackHole);
if (game.RadialCollision(plasma, blackHole))
{
plasma.captured = true;
OrbitalMovement anim1 = new OrbitalMovement(
blackHole.position, 20 + rand.Next(20),
plasma.rotation, 0.8f);
plasma.animations.Add(anim1);
CycleColorBounce anim2 = new CycleColorBounce(
0, 10, 10, 0);
plasma.animations.Add(anim2);
}
}
}
public void Draw(GameTime gameTime)
{
blackHole.Draw();
asteroid.Draw();
plasma.Draw();
string text = “Position “ +
((int)plasma.position.X).ToString() + “,” +
((int)plasma.position.Y).ToString();
game.spriteBatch.DrawString(font, text,
new Vector2(0, 0), Color.White);
float dist = game.Distance(plasma.position, blackHole.position);
text = “Distance “ + ((int)dist).ToString();
game.spriteBatch.DrawString(font, text,
new Vector2(0, 20), Color.White);
}
}
[/code]

Game1.cs

The source code to Game1.cs has not changed since the example in the preceding hour, but we need some reusable methods in this example added to the Game1.cs file, where they will be more useful. We have seen all of this code before, but just to be thorough, Listing 22.3 contains the complete code for the file with the additions.

LISTING 22.3 Main Source Code for the Example

[code]
public class Game1 : Microsoft.Xna.Framework.Game
{
public enum GameState
{
TITLE = 0,
PLAYING = 1,
OPTIONS = 2,
GAMEOVER = 3
}
public GraphicsDeviceManager graphics;
public SpriteBatch spriteBatch;
public GameState gameState;
SpriteFont font;
Random rand;
TouchLocation oldTouch;
IGameModule[] modules;
public Game1()
{
graphics = new GraphicsDeviceManager(this);
Content.RootDirectory = “Content”;
TargetElapsedTime = TimeSpan.FromTicks(333333);
oldTouch = new TouchLocation();
rand = new Random();
}
protected override void Initialize()
{
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
font = Content.Load<SpriteFont>(“WascoSans”);
modules = new IGameModule[4];
modules[0] = new TitleScreenModule(this);
modules[1] = new PlayingModule(this);
modules[2] = new OptionsModule(this);
modules[3] = new GameOverModule(this);
foreach (IGameModule mod in modules)
{
mod.LoadContent(Content);
}
gameState = GameState.PLAYING;
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back ==
ButtonState.Pressed)
this.Exit();
TouchCollection touchInput = TouchPanel.GetState();
TouchLocation touch = new TouchLocation();
if (touchInput.Count > 0)
{
touch = touchInput[0];
oldTouch = touch;
}
//update current module
modules[(int)gameState].Update(touch ,gameTime);
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.CornflowerBlue);
spriteBatch.Begin(SpriteSortMode.Deferred,
BlendState.AlphaBlend);
//draw current module
modules[(int)gameState].Draw(gameTime);
spriteBatch.End();
base.Draw(gameTime);
}
public bool BoundaryCollision(Rectangle A, Rectangle B)
{
return A.Intersects(B);
}
public bool RadialCollision(Sprite A, Sprite B)
{
float radius1 = A.image.Width / 2;
float radius2 = B.image.Width / 2;
return RadialCollision(A.position, B.position,
radius1, radius2);
}
public bool RadialCollision(Vector2 A, Vector2 B,
float radius1, float radius2)
{
float dist = Distance(A, B);
return (dist < radius1 + radius2);
}
public float Distance(Vector2 A, Vector2 B)
{
double diffX = A.X – B.X;
double diffY = A.Y – B.Y;
double dist = Math.Sqrt(Math.Pow(diffX, 2) +
Math.Pow(diffY, 2));
return (float)dist;
}
public float TargetAngle(Vector2 p1, Vector2 p2)
{
return TargetAngle(p1.X, p1.Y, p2.X, p2.Y);
}
public float TargetAngle(double x1, double y1, double x2,
double y2)
{
double deltaX = (x2 – x1);
double deltaY = (y2 – y1);
return (float)Math.Atan2(deltaY, deltaX);
}
}
[/code]