Personal tools
shadowfax.org.uk logo
Views

Gravity Simulator

From Shadowfax

Jump to: navigation, search

Two dimensional Netwonian Gravity in ActionScript 3.0

A simulation of gravitation in ActionScript. You can change the zoom using the up and down arrow keys. You can also change the starting positions, masses and speeds of the objects using the on-screen dialog box. Click 'Prev' or 'Next' to cycle through the objects and 'Restart' to start the simulation again with the new numbers. If you want to add a new object, click "Add".

You can change the frame of reference by clicking on 'FoR'. If the Frame of reference is Space, then clicking anwhere on the screen will center the simulation on that point, otherwise the simulation will automatically center itself on the object which is the frame of reference.

Note that due to security constraints within Flash keyboard-input is disabled in full-screen mode (although the arrow keys still work).

The mathematics

The basis for this code is the well known formula which describes gravity.

F = GMm/r2

Where F is force, G is the gravitational constant, M and m are the respective masses of the two bodies and r is the distance between them.

This, combined with Newton's second law, F=ma, gives the values for acceleration (a) as follows:

  • am = GM/r2
  • aM = Gm/r2

These calculations are performed for each pair of bodies in the simulation, and the component accelerations of each of these equations are summed to give the total acceleration per frame.

The simulation is (as a friend of mine has pointed out) non-relativistic, however, if Newtonian mechanics were good enough to put a man on the moon, they're good enough for a hobby project! Also there is no collision detection - so velocities can get very high as objects approach zero distance.

The core code (without the UI bits) is presented below

The Main Code

package 
{
  import flash.display.Sprite;
  import flash.events.Event;
 
  public class Main extends Sprite 
  {
    private var bodyList:Array = new Array();
    private var G:Number = 400;
    private var fc:uint = 0;
    private var zoom:Number = 1;
 
    private var spaceTime:Sprite = new Sprite();
    public static var frameOfReference:int = -1;
 
    public function Main():void 
    {
      addChild(spaceTime);
      // Grid Lines
      graphics.lineStyle(.5,0x000033);
      for (var i:int = 0; i < 2000; i+=50)
      {
        graphics.moveTo(i, 0);
        graphics.lineTo(i, 2000);
        graphics.moveTo(0, i);
        graphics.lineTo(2000,i);
      }
 
      // Starting bodies
      bodyList[0] = new body(1000, 0, 0, 0, 0, zoom);
      bodyList[1] = new body(1, 0, -100, 60, 0, zoom);
      bodyList[2] = new body(1, 0, -200, 40, 0, zoom);
      bodyList[3] = new body(1, 0, -350, 30, 0, zoom);
      bodyList[4] = new body(.1, 0, -360, 23, 0, zoom);
      bodyList[5] = new body(.1, 0, -600, 6, 0, zoom);
 
      for (i = 0; i < bodyList.length; i++)
        spaceTime.addChild(bodyList[i]);
 
      addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
 
    public function onEnterFrame(e:Event):void
    {
      fc++;
 
      // Clear the frame acceleration variable for all objects
      for (var i:int = 0; i < bodyList.length; i++)
      {
        bodyList[i].frameAccX = 0;
        bodyList[i].frameAccY = 0;
      }
 
      // Calculate new frame accelleration variable for all objects
      for (i = 0; i < bodyList.length; i++)
      {
        // No need to calculate for self, or for any objects pairs already dealt with
        for (var j:int = i + 1;  j < bodyList.length; j++)
        {
          // Get r squared & r for the pair
          var rSq:Number = ((bodyList[i].posX - bodyList[j].posX) 
                          * (bodyList[i].posX - bodyList[j].posX))
                          + ((bodyList[i].posY - bodyList[j].posY)
                          * (bodyList[i].posY - bodyList[j].posY));
 
          var r:Number  = Math.sqrt(rSq);
 
          // Calculate the accelerations
          var ai:Number = G * bodyList[j].mass / rSq;
          var aj:Number  = G * bodyList[i].mass / rSq;
 
          // and work out the x and y components of acceleration for each object
          // eg AccX = a * cos(theta) & AccX = a * sin(theta)
          // and cos(theta) = adjacent/hypotenuse
          //                = (x2 - x1)/r 
          bodyList[i].frameAccX += ai * (bodyList[j].posX  - bodyList[i].posX ) / r;
          bodyList[i].frameAccY += ai * (bodyList[j].posY - bodyList[i].posY) / r;
 
          bodyList[j].frameAccX += aj * (bodyList[i].posX  - bodyList[j].posX ) / r;
          bodyList[j].frameAccY += aj * (bodyList[i].posY - bodyList[j].posY) / r;
        }
      }
 
      // Now move the objects 
      for (i = 0; i < bodyList.length; i++)
        // approx 17 ms per frame at 60fps
        // Originally I calculated the actual frame length but this
        // has odd side effects if the simulation gets interupted
        bodyList[i].move(17 / 1000, fc);
 
      // If the frame of reference is not spaceTime, then move spacetime 
      // relative to the frame of reference!
      if (frameOfReference >= 0)
      {
        spaceTime.x = (stage.stageWidth / 2) - bodyList[frameOfReference].x;
        spaceTime.y = (stage.stageHeight / 2) - bodyList[frameOfReference].y;
      }
    }
  }
}

The Stellar Body Code

package  
{
  import flash.display.Sprite;
  import flash.display.Shape;
  import flash.geom.Point;
 
  public class body extends Sprite 
  {
    public var mass:Number;
    public var posX:Number;
    public var posY:Number;
    public var velX:Number;
    public var velY:Number;
 
    public var zoom:Number = 1;
    public var frameAccX:Number;
    public var frameAccY:Number;
 
    private var maxTrail:int = 40;
    private var curTrail:int = 0;
    private var trail:Array = new Array();
 
    public function body(aMass:Number,ax:Number,ay:Number,aVX:Number,
                         aVY:Number, aZoom:Number)
    {
      mass = aMass;
      posX =  ax;
      posY = ay
      velX = aVX;
      velY = aVY;
 
      zoom = aZoom;
 
      draw()
    }
 
    private function draw():void
    {
      // Draw the graphic for the object
      var radius:int;
 
      if (mass < 1) radius = 1
      else if (mass < 20) radius = 2
      else if (mass < 100)radius = 3;
      else if (mass < 500)radius = 4;
      else if (mass < 1500)radius = 5;
      else if (mass < 3000)radius = 6;
      else radius = 7;
 
      graphics.clear();
      graphics.lineStyle(1,0x0099dd);
      graphics.beginFill(0x0077cc);
      graphics.drawCircle(0, 0, radius);
      graphics.endFill();
    }
 
    public function move(elapsed:Number, fc:int):void
    {
      if  (mass != 0)
      {
        // work out the new velocity based on the component accelerations
        velX += frameAccX * elapsed;
        velY += frameAccY * elapsed;
 
        // calculate the real position
        posX += velX * elapsed;
        posY += velY * elapsed;
 
        // and work out the position on-screen based on zoom and screen size
        x = stage.stageWidth/2 + (posX * zoom);
        y = stage.stageHeight/2 + (posY * zoom);
 
        // Add the trails
        if (fc % 10 == 0)
        {
          if (curTrail >= trail.length)
            {
            trail[curTrail] = new Shape();
            trail[curTrail].graphics.lineStyle(1, 0x005599);
            trail[curTrail].graphics.drawCircle(0, 0, .5);
            stage.addChildAt(trail[curTrail],1);
            }
 
          // Calculate the strail position on screen taking into account Frame of Reference
          var pt:Point = new Point(x,y);
          pt = parent.localToGlobal(pt);
 
          trail[curTrail].x = pt.x;
          trail[curTrail].y = pt.y;
          trail[curTrail].visible = true;
          if (++curTrail > maxTrail) curTrail = 0;
        }
      }
    }
  }
}


Leave your comment

Menu