If you're seeing this message, it means we're having trouble loading external resources on our website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

Main content

Gravitational attraction

Probably the most famous force of all is gravity. We humans on earth think of gravity as an apple hitting Isaac Newton on the head. Gravity means that stuff falls down. But this is only our experience of gravity. In truth, just as the earth pulls the apple towards it due to a gravitational force, the apple pulls the earth as well. The thing is, the earth is just so massive that it overwhelms all the gravity interactions of every other object on the planet. Every object with mass exerts a gravitational force on every other object. And there is a formula for calculating the strengths of these forces, as depicted in the diagram below:
Diagram of gravitational forces between two spheres
Let’s examine this formula a bit more closely.
  • F refers to the gravitational force, the vector we ultimately want to compute and pass into our applyForce() function.
  • G is the universal gravitational constant, which in our world equals 6.67428 x 10^-11 meters cubed per kilogram per second squared. This is a pretty important number if your name is Isaac Newton or Albert Einstein. It’s not an important number if you are a ProcessingJS programmer. Again, it’s a constant that we can use to make the forces in our world weaker or stronger. Just making it equal to one and ignoring it isn’t such a terrible choice either.
  • m1 and m2 are the masses of objects 1 and 2. As we saw with Newton’s second law (F=MA), mass is also something we could choose to ignore. After all, shapes drawn on the screen don’t actually have a physical mass. However, if we keep these values, we can create more interesting simulations in which “bigger” objects exert a stronger gravitational force than smaller ones.
  • r^ refers to the unit vector pointing from object 1 to object 2. As we’ll see in a moment, we can compute this direction vector by subtracting the location of one object from the other.
  • r2 refers to the distance between the two objects squared. Let’s take a moment to think about this a bit more. With everything on the top of the formula—G, m1, m2—the bigger its value, the stronger the force. Big mass, big force. Big G, big force. Now, when we divide by something, we have the opposite. The strength of the force is inversely proportional to the distance squared. The farther away an object is, the weaker the force; the closer, the stronger.
Hopefully by now the formula makes some sense to us. We’ve looked at a diagram and dissected the individual components of the formula. Now it’s time to figure out how we translate the math into ProcessingJS code. Let’s make the following assumptions.
We have two objects, and:
  1. Each object has a PVector location: location1 and location2.
  2. Each object has a numeric mass: mass1 and mass2.
  3. There is a numeric variable G for the universal gravitational constant.
Given these assumptions, we want to compute a PVector force, the force of gravity. We’ll do it in two parts. First, we’ll compute the direction of the force r^ in the formula above. Second, we’ll calculate the strength of the force according to the masses and distance.
Remember when we figured out how to have an object accelerate towards the mouse? We're going to use the same logic here.
A vector is the difference between two points. To make a vector that pointed from the circle to the mouse, we simply subtracted one point from another:
var dir = PVector.sub(mouse, location);
In our case, the direction of the attraction force that object 1 exerts on object 2 is equal to:
var dir = PVector.sub(location1, location2);
Don’t forget that since we want a unit vector, a vector that tells us about direction only, we’ll need to normalize the vector after subtracting the locations:
dir.normalize();
OK, we’ve got the direction of the force. Now we just need to compute the magnitude and scale the vector accordingly.
var m = (G * mass1 * mass2) / (distance * distance);
dir.mult(m);
The only problem is that we don’t know the distance. G, mass1, and mass2 were all givens, but we’ll need to actually compute distance before the above code will work. Didn’t we just make a vector that points all the way from one location to another? Wouldn’t the length of that vector be the distance between two objects?
Well, if we add just one line of code and grab the magnitude of that vector before normalizing it, then we’ll have the distance.
// The vector that points from one object to another
var force = PVector.sub(location1, location2);

// The length (magnitude) of that vector is the distance between the two objects.
var distance = force.mag();

// Use the formula for gravity to compute the strength of the force.
var strength = (G * mass1 * mass2) / (distance * distance);

// Normalize and scale the force vector to the appropriate magnitude.
force.normalize();
force.mult(strength);
Note that I also renamed the PVector “dir” as “force.” After all, when we’re finished with the calculations, the PVector we started with ends up being the actual force vector we wanted all along.
Now that we’ve worked out the math and the code for calculating an attractive force (emulating gravity), we need to turn our attention to applying this technique in the context of an actual ProcessingJS program. Earlier in this section, we created a simple Mover object—an object with PVector’s location, velocity, and acceleration as well as an applyForce(). Let’s take this exact class and put it in a program with:
  • A single Mover object.
  • A single Attractor object (a new object type that will have a fixed location).
The Mover object will experience a gravitational pull towards the Attractor object, as illustrated in the diagram.
We can start by making the new Attractor object very simple—giving it a location and a mass, along with a method to display itself (tying mass to size).
var Attractor = function() {
    this.position = new PVector(width/2, height/2);
    this.mass = 20;
    this.G = 1;
    this.dragOffset = new PVector(0, 0);
    this.dragging = false;
    this.rollover = false;
};

// Method to display
Attractor.prototype.display = function() {
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    fill(175, 175, 175, 200);
    ellipse(this.position.x, this.position.y, this.mass*2, this.mass*2);
};
After defining that, we can create an instance of the Attractor object type.
var mover = new Mover();
var attractor = new Attractor();

draw = function() {
    background(50, 50, 50);

    attractor.display();
    mover.update();
    mover.display();
};
This is a good structure: a main program with a Mover and an Attractor object. The last piece of the puzzle is how to get one object to attract the other. How do we get these two objects to talk to each other?
There are a number of ways we could do this, architecturally. Here are just a few possibilities.
TaskFunction
1. A function that receives both an Attractor and a Mover:attraction(a, m);
2. A method in the Attractor object that receives a Mover:a.attract(m);
3. A method in the Mover object that receives an Attractor:mover.attractedTo(a);
4. A method in the Attractor object that receives a Mover and returns a PVector, which is the attraction force. That attraction force is then passed into the Mover's applyForce() method.var f = a.calculateAttraction(m); mover.applyForce(f);
It’s good to look at a range of options for making objects talk to each other, and you could probably make arguments for each of the above possibilities. Let's start by discarding the first one, since an object-oriented approach is really a much better choice over an arbitrary function not tied to either the Mover or Attractor objects. Whether you pick option 2 or option 3 is the difference between saying “The attractor attracts the mover” or “The mover is attracted to the attractor.” Number 4 seems the most appropriate, at least in terms of where we are in this course. After all, we spent a lot of time working out the applyForce() method, and I think our examples will be clearer if we continue with the same methodology.
In other words, where we once had:
var f = new PVector(0.1, 0); // Made up force
mover.applyForce(f);
We will now have:
var f = a.calculateAttraction(m); // Attraction force between two objects
mover.applyForce(f);
And so our draw() function can now be written as:
draw = function() {
    background(50, 50, 50);

    // Calculate attraction force and apply it
    var f = a.calculateAttraction(m);
    mover.applyForce(f);

    attractor.display();
    mover.update();
    mover.display();
};
We’re almost there. Since we decided to put the calculateAttraction() method inside of the Attractor object type, we’ll need to actually write that function. The function needs to receive a Mover object and return a PVector. And what goes inside that function? All of that nice math we worked out for gravitational attraction!
Attractor.prototype.calculateAttraction = function(mover) {

    // What's the force's direction?
    var force = PVector.sub(this.position, mover.position);    
    var distance = force.mag();
    force.normalize();

    // What's the force's magnitude?
    var strength = (this.G * this.mass * mover.mass) / (distance * distance);
    force.mult(strength);

    // Return the force so it can be applied!
    return force;
};
And we’re done. Sort of. Almost. There’s one small kink we need to work out. Let’s look at the above code again. See that symbol for divide, the slash? Whenever we have one of these, we need to ask ourselves the question: What would happen if the distance happened to be a really, really small number or (even worse!) zero??! Well, we know we can’t divide a number by 0, and if we were to divide a number by something like 0.0001, that is the equivalent of multiplying that number by 10,000! Yes, this is the real-world formula for the strength of gravity, but we don’t live in the real world. We live in the ProcessingJS world. And in the ProcessingJS world, the mover could end up being very, very close to the attractor and the force could become so strong the mover would just fly way off the screen. And so with this formula, it’s good for us to be practical and constrain the range of what distance can actually be. Maybe, no matter where the Mover actually is, we should never consider it less than 5 pixels or more than 25 pixels away from the attractor.
distance = constrain(distance, 5, 25);
For the same reason that we need to constrain the minimum distance, it’s useful for us to do the same with the maximum. After all, if the mover were to be, say, 500 pixels from the attractor (not unreasonable), we’d be dividing the force by 250,000. That force might end up being so weak that it’s almost as if we’re not applying it at all.
Now, it’s really up to you to decide what behaviors you want. But in the case of, “I want reasonable-looking attraction that is never absurdly weak or strong,” then constraining the distance is a good technique.
Let's put it all together now in one program. The Mover object type hasn't changed at all, but now our program includes an Attractor object, and code that ties them together. We've also added code to the program to control the attractor with a mouse, so that it's easier to observe the effects.
And we could, of course, expand this example using an array to include many Mover objects, just as we did with friction and drag. The main change that we've made to the program is to adjust our Mover object to accept mass, x, and y (as we've done in the past), initialize an array of randomly placed Mover objects, and loop over that array to calculate the attraction force on each of them, each time:
var movers = [];
var attractor = new Attractor();

for (var i = 0; i < 10; i++) {
    movers[i] = new Mover(random(0.1, 2), random(width), random(height));
}

draw = function() {
    background(50, 50, 50);

    attractor.display();
    for (var i = 0; i < movers.length; i++) {
        var force = attractor.calculateAttraction(movers[i]);
        movers[i].applyForce(force);

        movers[i].update();
        movers[i].display();
    }
};

This "Natural Simulations" course is a derivative of "The Nature of Code" by Daniel Shiffman, used under a Creative Commons Attribution-NonCommercial 3.0 Unported License.

Want to join the conversation?

  • aqualine seed style avatar for user K B
    I don't get what is the use of debug here & what actually it does?
    (10 votes)
    Default Khan Academy avatar avatar for user
  • piceratops ultimate style avatar for user Emily Bartee
    So I've almost finished step 3 of the Artwork Generator challenge, but the hint code says not only that i have to change my fill, but there's another line of code I need too. (fill(__); __;)
    Anyone know what I need there?
    (5 votes)
    Default Khan Academy avatar avatar for user
    • leaf green style avatar for user Abraham Koshy
      Try
      var Mover = function(mass, x, y, c) {
      this.position = new PVector(x, y);
      this.velocity = new PVector(1, 0);
      this.acceleration = new PVector(0, 0);
      this.mass = mass;
      this.color = c;
      };
      Mover.prototype.display = function() {
      noStroke();
      fill (this.color);
      ellipse(this.position.x, this.position.y, this.mass*16, this.mass*16);
      };
      var movers = [];
      var attractor = new Attractor();
      for (var i = 0; i < 10; i++) {
      movers[i] = new Mover(random(0.1, 2), random(width), random(height), color(random(0,255),random(0,255), random(0,255)));
      }
      draw = function() {
      // background(255, 255, 255);

      // attractor.display();
      for (var i = 0; i < movers.length; i++) {
      fill(this.c);
      var force = attractor.calculateAttraction(movers[i]);
      movers[i].applyForce(force);
      ....
      Hope this helps
      (7 votes)
  • blobby green style avatar for user weimin
    distance = constrain(distance, 5, 25);
    doesn't the above command limit the maximum distance from the mover to attractor to be 25? however, we look at the animation, the distance can be very large, why? any help is much appreciated!
    (3 votes)
    Default Khan Academy avatar avatar for user
    • hopper jumping style avatar for user Michael.Wennerstrom
      The constrain(distance) won't make the mover bonk into some imaginary wall after 25 pixels, the "true" distance between mover and attractor can be lightpixels. Distance's ONLY hold on the mover is through gravitational force between the mover and the attractor: F = GMm/(dist*dist).

      Distance changes the mover's position subtly through kinematics: Force (found with constrain(dist) updates velocity which updates position (which in turn provides the new dist). This loop of kinematics is what prevents the mover from smacking into a 25 pixel wall.

      In fact, 1/(distance*distance) gets quite small. The programmer may have said "let's not put thought to calculating distances past 25 pixels since 500 pixels is just as small (1/25^2 is 0.0016 and 1/500^2 is 0.000004". This technically violates physics (you can escape gravity wells), but serves as a way to avoid large calculations and to ensure the mover doesn't fly away!

      Does this help?
      (9 votes)
  • spunky sam blue style avatar for user kathleensolaar
    Can someone help me on step three in Challenge: Artwork generator, please? I read this page many times, but it still confuses me!
    My code:

    var Attractor = function() {
    this.position = new PVector(width/2, height/2);
    this.mass = 20;
    this.G = 1;
    };

    Attractor.prototype.calculateAttraction = function(m) {
    var force = PVector.sub(this.position, m.position);
    var distance = force.mag();
    distance = constrain(distance, 5, 25);
    force.normalize();
    var strength = (this.G * this.mass * m.mass) / (distance * distance);
    force.mult(strength);
    return force;
    };

    Attractor.prototype.display = function() {
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    ellipse(this.position.x, this.position.y, this.mass*2, this.mass*2);
    };


    var Mover = function(mass, x, y) {
    this.position = new PVector(x, y);
    this.velocity = new PVector(1, 0);
    this.acceleration = new PVector(0, 0);
    this.mass = mass;
    };

    Mover.prototype.applyForce = function(force) {
    var f = PVector.div(force,this.mass);
    this.acceleration.add(f);
    };

    Mover.prototype.update = function() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
    };

    Mover.prototype.display = function() {
    noStroke();
    fill(0, 0, 0, 50);
    ellipse(this.position.x, this.position.y, this.mass*16, this.mass*16);
    };

    var movers = [];
    var attractor = new Attractor();

    for (var i = 0; i < 10; i++) {
    var r = random(color(0,255));
    movers[i] = new Mover(random(0.1, 0.6), random(width), random(height),color(r, 255, 255));
    }


    draw = function() {



    for (var i = 0; i < movers.length; i++) {
    var force = attractor.calculateAttraction(movers[i]);
    movers[i].applyForce(force);

    movers[i].update();
    movers[i].display();
    }
    };
    (3 votes)
    Default Khan Academy avatar avatar for user
  • hopper cool style avatar for user Noah
    Arrgg! I am stuck on step 3 of Artwork Generator. I need some help figuring out what I need to do so all of the different ellipses' colors are different.

    Here is my code so far:
    var Attractor = function() {
    this.position = new PVector(width/2, height/2);
    this.mass = 20;
    this.G = 1;
    };

    Attractor.prototype.calculateAttraction = function(m) {
    var force = PVector.sub(this.position, m.position);
    var distance = force.mag();
    distance = constrain(distance, 5, 25);
    force.normalize();
    var strength = (this.G * this.mass * m.mass) / (distance * distance);
    force.mult(strength);
    return force;
    };

    Attractor.prototype.display = function() {
    ellipseMode(CENTER);
    strokeWeight(4);
    stroke(0);
    ellipse(this.position.x, this.position.y, this.mass*2, this.mass*2);
    };


    var Mover = function(mass, x, y, fill) {

    this.position = new PVector(x, y);
    this.velocity = new PVector(1, 0);
    this.acceleration = new PVector(0, 0);
    this.mass = mass;
    };

    Mover.prototype.applyForce = function(force) {
    var f = PVector.div(force,this.mass);
    this.acceleration.add(f);
    };

    Mover.prototype.update = function() {
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.mult(0);
    };

    Mover.prototype.display = function() {
    noStroke();
    fill(0, 0, 0, 50);
    ellipse(this.position.x, this.position.y, this.mass*16, this.mass*16);
    };

    var movers = [];
    var attractor = new Attractor();

    for (var i = 0; i < 10; i++) {

    movers[i] = new Mover(random(0.1, 2), random(width), random(height));
    }

    draw = function() {



    for (var i = 0; i < movers.length; i++) {
    var force = attractor.calculateAttraction(movers[i]);
    movers[i].applyForce(force);

    movers[i].update();
    movers[i].display();
    }
    };
    (5 votes)
    Default Khan Academy avatar avatar for user
  • leafers tree style avatar for user HomeSpot25
    To complete step 3 you must put '' color(random(0, 225), random(0, 225), random(0, 225))); '' in the for loop. This satisfys the last statement '' Randomly generate a color when you create each new Mover.
    (4 votes)
    Default Khan Academy avatar avatar for user
  • leaf orange style avatar for user Bryce
    In the last program were the "movers" attracted to each other?
    (1 vote)
    Default Khan Academy avatar avatar for user
  • ohnoes default style avatar for user Bram840Thiessen
    Why is the checkEdges method included in the Gravitational Attraction code for the movers if it is unused and incomplete? By incomplete I mean that it has no if statement for if the mover is above the top of the screen.
    (2 votes)
    Default Khan Academy avatar avatar for user
    • aqualine tree style avatar for user Eswag
      Because the mover will never permanently go off the screen because it is constantly being pulled back, the programmers removed the function's code, but still wanted to use the Mover class in the following challenges, so they didn't completely destroy the template
      (2 votes)
  • purple pi purple style avatar for user Zongyao Jin
    The ProcessingJS is so powerful. I was wondering is there a counterpart of ProcessingJS in Python? And also, are there frameworks or packages like Scipy, Numpy, and Matplotlib in Javascript?
    (2 votes)
    Default Khan Academy avatar avatar for user
  • leafers seed style avatar for user Christian Carbone
    Everything was making a lot of sense until the handle hover method, I feel like almost none of what's in it was covered. Drag Offset? Dragging? Rollover? I SORT OF get what they mean, but I don't think i've ever seen that type of thing covered in this course.
    (2 votes)
    Default Khan Academy avatar avatar for user