Chapter 2: Forces
Don’t underestimate the Force.
—Darth Vader
Alexander Calder was a 20th-century American artist known for his kinetic sculptures that balance form and motion. His “constellations” were sculptures consisting of interconnected shapes and wire that demonstrate tension, balance, and the ever-present pull of gravitational attraction.
In the final example of Chapter 1, I demonstrated how to calculate a dynamic acceleration based on a vector pointing from a circle on the canvas to the mouse position. The resulting motion resembled a magnetic attraction between shape and mouse, as if a force was pulling the circle in toward the mouse. In this chapter, I’ll detail the concept of a force and its relationship to acceleration. The goal, by the end of this chapter, is to build a simple physics engine and understand how objects move around a canvas, responding to a variety of environmental forces.
A physics engine is a computer program (or code library) that simulates the behavior of objects in a physical environment. With a p5.js sketch, the objects are 2D shapes, and the environment is a rectangular canvas. Physics engines can be developed to be highly precise (requiring high-performance computing) or real time (using simple and fast algorithms). This chapter focuses on building a rudimentary physics engine, with an emphasis on speed and ease of understanding.
Forces and Newton’s Laws of Motion
Let’s begin by taking a conceptual look at what it means to be a force in the real world. Just like the word vector, the term force can have a variety of meanings. It can indicate a powerful physical intensity, as in “They pushed the boulder with great force,” or a powerful influence, as in “They’re a force to be reckoned with!” The definition of force that I’m interested in for this chapter is more formal and comes from Sir Isaac Newton’s three laws of motion:
A force is a vector that causes an object with mass to accelerate.
Hopefully, you recognize the first part of the definition: a force is a vector. Thank goodness you just spent a whole chapter learning what vectors are and how to program with them! I’ll start from there by explaining how Newton’s three laws of motion relate to what you already know about vectors; then I’ll illustrate the rest of the force definition as I go.
Newton’s First Law
Newton’s first law is commonly stated as follows:
An object at rest stays at rest, and an object in motion stays in motion.
However, this is missing an important element related to forces. I could expand the definition by stating:
An object at rest stays at rest, and an object in motion stays in motion, at a constant speed and direction unless acted upon by an unbalanced force.
When Newton came along, the prevailing theory of motion—formulated by Aristotle—was nearly 2,000 years old. It stated that if an object is moving, some sort of force is required to keep it moving. Unless that moving thing is being pushed or pulled, it will slow down or stop. This theory was borne out through observation of the world. For example, if you toss a ball, it falls to the ground and eventually stops moving, seemingly because the force of the toss is no longer being applied.
This older theory, of course, isn’t true. As Newton established, in the absence of any forces, no force is required to keep an object moving. When an object (such as the aforementioned ball) is tossed in Earth’s atmosphere, its velocity changes because of unseen forces such as air resistance and gravity. An object’s velocity will remain constant only in the absence of any forces or only if the forces that act on it cancel each other out, meaning the net force adds up to zero. This is often referred to as equilibrium (see Figure 2.1). The falling ball will reach a terminal velocity (which stays constant) once the force of air resistance equals the force of gravity.
Considering a p5.js canvas, I could restate Newton’s first law as follows:
An object’s velocity vector will remain constant if it’s in a state of equilibrium.
In other words, in a Mover
class, the update()
function shouldn’t apply any mathematical operations on the velocity vector unless a nonzero net force is present.
Newton’s Third Law
Let me set aside Newton’s second law (arguably the most important law for the purposes of this book) for a moment and move on to his third law. This law is often stated as follows:
For every action, there is an equal and opposite reaction.
The way this law is stated frequently causes confusion. For one, it sounds like one force causes another. Yes, if you push someone, that someone may actively decide to push you back. But this isn’t the action and reaction Newton’s third law has in mind.
Let’s say you push against a wall. The wall doesn’t actively decide to push you back, and yet it still provides resistance with an equal force in the opposite direction. There’s no “origin” force. Your push simply includes both forces, referred to as an action/reaction pair. A better way of stating Newton’s third law might therefore be the following:
Forces always occur in pairs. The two forces are of equal strength but in opposite directions.
This still causes confusion because it sounds like these forces would always cancel each other out. This isn’t the case. Remember, the forces act on different objects. And just because the two forces are equal doesn’t mean that the objects’ movements are equal (or that the objects will stop moving).
Consider pushing on a stationary truck. Although the truck is far more massive than you, a stationary truck (unlike a moving one) will never overpower you and send you flying backward. The force your hands exert on the truck is equal and opposite to the force exerted by the truck on your hands. The outcome depends on a variety of other factors. If the truck is small and parked on an icy street, you’ll probably be able to get it to move. On the other hand, if it’s very large and on a dirt road and you push hard enough (maybe even take a running start), you could injure your hand.
And what if, as in Figure 2.2, you are wearing roller skates when you push on that truck?
You’ll accelerate away from the truck, sliding along the road while the truck stays put. Why do you slide but not the truck? For one, the truck has a much larger mass (which I’ll get into with Newton’s second law). Other forces are at work too—namely, the friction of the truck’s tires and your roller skates against the road.
Considering p5.js again, I could restate Newton’s third law as follows:
If you calculate a p5.Vector
called f
that represents a force of object A on object B, you must also apply the opposite force that object B exerts on object A. You can calculate this other force as p5.Vector.mult(f, -1)
.
You’ll soon see that in the world of coding simulation, it’s often not necessary to stay true to Newton’s third law. Sometimes, such as in the case of gravitational attraction between bodies (see Example 2.8), I’ll want to model equal and opposite forces in my example code. Other times, such as a scenario where I’ll say, “Hey, there’s some wind in the environment,” I’m not going to bother to model the force that a body exerts back on the air. In fact, I’m not going to bother modeling the air at all! Remember, the examples in this book are taking inspiration from the physics of the natural world for the purposes of creativity and interactivity. They don’t require perfect precision.
Newton’s Second Law
Now it’s time for most important law for you, the p5.js coder: Newton’s second law. It’s stated as follows:
Force equals mass times acceleration.
Or:
Why is this the most important law for this book? Well, let’s write it a different way:
Acceleration is directly proportional to force and inversely proportional to mass. Consider what this means if you’re pushed. The harder you’re pushed, the faster you’ll speed up or slow down (accelerate). On the other hand, the bigger you are, the less effective a force is at accelerating you!
Weight vs. Mass
Mass isn’t to be confused with weight. Mass is a measure of the amount of matter in an object (measured in kilograms). An object that has a mass of 1 kilogram on Earth would have a mass of 1 kilogram on the moon.
Weight, though often mistaken for mass, is technically the force of gravity on an object. From Newton’s second law, you can calculate weight as mass times the acceleration of gravity (). Weight is measured in newtons, a unit that indicates the magnitude of the gravitational force. Because weight is tied to gravity, an object on the moon weighs one-sixth as much as it does on Earth.
Related to mass is density, which is defined as the amount of mass per unit of volume (grams per cubic centimeter, for example).
In the world of p5.js, what is mass anyway? Aren’t we dealing with pixels? Let’s start simple and say that in a pretend pixel world, all objects have a mass equal to 1. Anything divided by 1 equals itself, and so, in this simple world, we have this:
I’ve effectively removed mass from the equation, making the acceleration of an object equal to force. This is great news. After all, Chapter 1 described acceleration as the key to controlling the movement of objects in a canvas. I said that the position changes according to the velocity, and the velocity according to acceleration. Acceleration seemed to be where it all began. Now you can see that force is truly where it all begins.
Let’s take the Mover
class, with position, velocity, and acceleration:
class Mover {
constructor() {
this.position = createVector();
this.velocity = createVector();
this.acceleration = createVector();
}
}
Now the goal is to be able to add forces to this object, with code like this:
mover.applyForce(wind);
Or like this:
mover.applyForce(gravity);
Here wind
and gravity
are p5.Vector
objects. According to Newton’s second law, I could implement this applyForce()
method as follows:
applyForce(force) {
this.acceleration = force;
Newton’s second law at its simplest
}
This looks pretty good. After all, acceleration = force is a literal translation of Newton’s second law (in a world without mass). Nevertheless, this code has a pretty big problem, which I’ll quickly encounter when I return to my original goal: creating an object that responds to wind and gravity forces. Consider this code:
mover.applyForce(wind);
mover.applyForce(gravity);
mover.update();
Imagine you’re the computer for a moment. First, you call applyForce()
with wind
, and so the Mover
object’s acceleration is now assigned the wind
vector. Second, you call applyForce()
with gravity
. Now the Mover
object’s acceleration is set to the gravity
vector. Finally, you call update()
. What happens in update()
? Acceleration is added to velocity:
this.velocity.add(this.acceleration);
If you run this code, you won’t see an error in the console, but zoinks! There’s a major problem. What’s the value of acceleration
when it’s added to velocity
? It’s equal to the gravity
vector, meaning wind
has been left out! Anytime applyForce()
is called, acceleration
is overwritten. How can I handle more than one force?
Force Accumulation
The answer is that the forces must accumulate, or be added together. This is stated in the full definition of Newton’s second law itself, which I now confess to having simplified. Here’s a more accurate way to put it:
Net force equals mass times acceleration.
In other words, acceleration is equal to the sum of all forces divided by mass. At any given moment, there might be 1, 2, 6, 12, or 303 forces acting on an object. As long as the object knows how to add them together (accumulate them), it doesn’t matter how many forces there are. The sum total will give you the object’s acceleration (again, ignoring mass). This makes perfect sense. After all, as you saw in Newton’s first law, if all the forces acting on an object add up to zero, the object experiences an equilibrium state (that is, no acceleration).
I can now revise the applyForce()
method to take force accumulation into account:
applyForce(force) {
this.acceleration.add(force);
Newton’s second law, but with force accumulation, adding all input forces to acceleration
}
I’m not finished just yet, though. Force accumulation has one more piece. Since I’m adding all the forces together at any given moment, I have to make sure that I clear acceleration
(set it to 0
) before each time update()
is called. Consider a wind force for a moment. Sometimes wind is very strong, sometimes it’s weak, and sometimes there’s no wind at all. For example, you might write code that creates a gust of wind when holding down the mouse:
if (mouseIsPressed) {
let wind = createVector(0.5, 0);
mover.applyForce(wind);
}
When the mouse is released, the wind should stop, and according to Newton’s first law, the object should continue moving at a constant velocity. However, if I forget to reset acceleration
to 0
, the gust of wind will still be in effect. Even worse, it will add onto itself from the previous frame! Acceleration, in a time-based physics simulation, has no memory; it’s calculated based on the environmental forces present at any given moment (frame) in time. This is different from, say, position. An object must remember its previous location in order to move properly to the next.
One way to clear the acceleration for each frame is to multiply the acceleration
vector by 0
at the end of update()
:
update() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.acceleration.mult(0);
Clear acceleration after it’s been applied.
}
Being able to accumulate and apply forces gets me closer to a working physics engine, but at this point I should note another detail that I’ve been glossing over, besides mass. That’s the time step, the rate at which the simulation updates. The size of the time step affects the accuracy and behavior of a simulation, which is why many physics engines incorporate the time step as a variable (often denoted as dt
, which stands for delta time, or the change in time). For simplicity, I’m instead choosing to assume that every cycle through draw()
represents one time step. This assumption may not be the most accurate, but it allows me to focus on the key principles of the simulation.
I’ll let this assumption stand until Chapter 6, when I’ll examine the impact of different time steps while covering third-party physics libraries. Right now, though, I can and should address the massive elephant in the room that I’ve so far been ignoring: mass.
Exercise 2.1
Using forces, simulate a helium-filled balloon floating upward and bouncing off the top of a window. Can you add a wind force that changes over time, perhaps according to Perlin noise?
Factoring In Mass
Newton’s second law is really , not . How can I incorporate mass into the simulation? To start, it’s as easy as adding a this.mass
instance variable to the Mover
class, but I need to spend a little more time here because of another impending complication.
First, though, I’ll add mass:
class Mover {
constructor() {
this.position = createVector();
this.velocity = createVector();
this.acceleration = createVector();
this.mass = ????;
Add mass as a number.
}
}
Units of Measurement
Now that I’m introducing mass, it’s important to make a quick note about units of measurement. In the real world, things are measured in specific units: two objects are 3 meters apart, the baseball is moving at a rate of 90 miles per hour, or this bowling ball
has a mass of 6 kilograms. Sometimes you do want to take real-world units into consideration. In this chapter, however, I’m going to stick with units of measurement in pixels (“These two circles are 100 pixels apart”) and frames of animation (“This circle is moving at a rate of 2 pixels per frame,” the aforementioned time step).
In the case of mass, p5.js doesn’t have any unit of measurement to use. How much mass is in any given pixel? You might enjoy inventing your own p5.js unit of mass to associate with those values, like “10 pixeloids” or “10 yurkles.”
For demonstration purposes, I’ll tie mass to pixels (the larger a circle’s diameter, the larger the mass). This will allow me to visualize the mass of an object, albeit inaccurately. In the real world, size doesn’t indicate mass. A small metal ball could have a much higher mass than a large balloon because of its higher density. And for two circular objects with equal density, I’ll also note that mass should be tied to the formula for the area of a circle: . (This will be addressed in Exercise 2.11, and I’ll say more about and circles in Chapter 3.)
Mass is a scalar, not a vector, as it’s just one number describing the amount of matter in an object. I could get fancy and compute the area of a shape as its mass, but it’s simpler to begin by saying, “Hey, the mass of this object is . . . um, I dunno . . . how about 10?”
constructor() {
this.position = createVector(random(width), random(height));
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
this.mass = 10;
}
This isn’t so great, since things become interesting only when I have objects with varying mass, but it’s enough to get us started. Where does mass come in? I need to divide force by mass to apply Newton’s second law to the object:
applyForce(force) {
force.div(mass);
this.acceleration.add(force);
Newton’s second law (with force accumulation and mass)
}
Yet again, even though the code looks quite reasonable, it has a major problem. Consider the following scenario with two Mover
objects, both being blown away by a wind force:
let moverA = new Mover();
let moverB = new Mover();
let wind = createVector(1, 0);
moverA.applyForce(wind);
moverB.applyForce(wind);
Again, imagine you’re the computer. Object moverA
receives the wind force—(1, 0)—divides it by mass
(10), and adds it to acceleration:
Action | Vector Components |
---|---|
moverA receives the wind force. | (1, 0) |
moverA divides the wind force by a mass of 10. | (0.1, 0) |
Now you move on to object moverB
. It also receives the wind force—(1, 0). Wait, hold on a second. What’s the value of the wind force? Taking a closer look, it’s actually now (0.1, 0)! Remember that when you pass an object (in this case, p5.Vector
) into a function, you’re passing a reference to that object. It’s not a copy! So if a function makes a change to that object (which, in this case, it does by dividing by the mass), that object is permanently changed. But I don’t want moverB
to receive a force divided by the mass of object moverA
. I want it to receive the force in its original state—(1, 0). And so I must protect the original vector and make a copy of it before dividing by mass.
Fortunately, the p5.Vector
class has a convenient method for making a copy: copy()
. It returns a new p5.Vector
object with the same data. And so I can revise applyForce()
as follows:
applyForce(force) {
let f = force.copy();
Make a copy of the vector before using it.
f.div(this.mass);
Divide the copy by mass.
this.acceleration.add(f);
}
Let’s take a moment to recap what I’ve covered so far. I’ve defined what a force is (a vector), and I’ve shown how to apply a force to an object (divide it by mass and add it to the object’s acceleration vector). What’s missing? Well, I have yet to figure out how to calculate a force in the first place. Where do forces come from?
Exercise 2.2
You could write applyForce()
in another way, using the static method div()
instead of copy()
. Rewrite applyForce()
by using the static method. For help with this exercise, review static methods in “Static vs. Nonstatic Methods”.
applyForce(force) {
let f = p5.Vector.div(force, this.mass);
this.acceleration.add(f);
}
Creating Forces
This section presents two ways to create forces in a p5.js world:
- Make up a force! After all, you’re the programmer, the creator of your world. There’s no reason you can’t just make up a force and apply it.
- Model a force! Forces exist in the physical world, and physics textbooks often contain formulas for these forces. You can take these formulas and translate them into source code to model real-world forces in JavaScript.
To begin, I’ll focus on the first approach. The easiest way to make up a force is to just pick a number (or two numbers, really). Let’s start with simulating wind. How about a wind force that points to the right and is fairly weak? Assuming an object mover
, the code would read as follows:
let wind = createVector(0.01, 0);
mover.applyForce(wind);
The result isn’t terribly interesting but is a good place to start. I create a p5.Vector
object, initialize it, and pass it into a Mover
object (which in turn will apply it to its own acceleration). To finish off this example, I’ll add one more force, gravity (pointing down), and engage the wind force only when the mouse is pressed.
let gravity = createVector(0, 0.1);
mover.applyForce(gravity);
if (mouseIsPressed) {
let wind = createVector(0.1, 0);
mover.applyForce(wind);
}
Now I have two forces, pointing in different directions and with different magnitudes, both applied to the object mover
. I’m beginning to get somewhere. I’ve built a world, an environment with forces that act on objects!
Let’s look at what happens now when I add a second object with a variable mass. To do this, you’ll probably want to do a quick review of OOP. Again, I’m not covering all the basics of programming here (for that, you can check out any of the intro p5.js books or video tutorials listed in “The Coding Train Connection”). However, since the idea of creating a world filled with objects is fundamental to all the examples in this book, it’s worth taking a moment to walk through the steps of going from one object to many.
This is where I left the Mover
class. Notice that it’s identical to the Mover
class created in Chapter 1, with two additions, mass
and a new applyForce()
method:
class Mover {
constructor() {
this.mass = 1;
For now, set the mass equal to 1 for simplicity.
this.position = createVector(width / 2, 30);
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
}
applyForce(force) {
Newton’s second law
let f = p5.Vector.div(force, this.mass);
this.acceleration.add(f);
Receive a force, divide by mass, and add to acceleration.
}
update() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
Motion 101 from Chapter 1
this.acceleration.mult(0);
Now add clearing the acceleration each time!
}
show() {
stroke(0);
fill(175);
circle(this.position.x, this.position.y, this.mass * 16);
Scale the size according to mass. Stay tuned for an improvement on this to come later in the chapter!
}
checkEdges() {
if (this.position.x > width) {
this.position.x = width;
this.velocity.x *= -1;
} else if (this.position.x < 0) {
this.velocity.x *= -1;
this.position.x = 0;
}
Somewhat arbitrarily, I’ve decided that an object bounces when it hits the edges of the canvas.
if (this.position.y > height) {
this.velocity.y *= -1;
this.position.y = height;
Even though I said not to touch position and velocity directly, exceptions exist. Here, I’m doing so as a quick way to reverse the direction of the object when it reaches the edge.
}
}
}
Now that the class is written, I can create more than one Mover
object:
let moverA = new Mover();
let moverB = new Mover();
But there’s an issue. Look again at the Mover
object’s constructor:
constructor() {
this.mass = 1;
this.position = createVector(width / 2, 30);
Every object has a mass of 1 and a position of (width / 2, 30).
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
}
Right now, every Mover
object is made exactly the same way. What I want are Mover
objects of variable mass that start at variable positions. A nice way to accomplish this is with constructor arguments:
constructor(x, y, mass) {
this.mass = mass;
this.position = createVector(x, y);
Now set these variables with arguments.
this.velocity = createVector(0, 0);
this.acceleration = createVector(0, 0);
}
Notice that the mass and position are no longer set to hardcoded numbers, but rather are initialized via the x
, y
, and mass
arguments passed to the constructor. This means I can create a variety of Mover
objects—big ones, small ones, ones that start on the left side of the canvas, ones that start on the right, and everywhere in between:
let moverA = new Mover(100, 30, 10);
A large mover on the left side of the canvas
let moverB = new Mover(400, 30, 2);
A smaller mover on the right side of the canvas
I could choose to initialize the values in all sorts of ways (random, Perlin noise, in a grid, and so on). Here I’ve just picked some numbers for demonstration purposes. I’ll introduce other techniques for initializing a simulation throughout this book.
Once the objects are declared and initialized, the rest of the code follows as before. For each object, pass the forces in the environment to applyForce()
and enjoy the show!
function draw() {
background(255);
let gravity = createVector(0, 0.1);
moverA.applyForce(gravity);
moverB.applyForce(gravity);
Make up a gravity force and apply it.
if (mouseIsPressed) {
let wind = createVector(0.1, 0);
moverA.applyForce(wind);
moverB.applyForce(wind);
}
Make up a wind force and apply it when the mouse is clicked.
moverA.checkEdges();
moverA.update();
moverA.show();
moverB.checkEdges();
moverB.update();
moverB.show();
}
Notice that every operation in the code is written twice, once for moverA
and once for moverB
. In practice, an array would make more sense than separate variables to manage multiple Mover
objects, particularly as their number increases. That way, I’d have to write each operation only once and use a loop to apply it to each Mover
in the array. I’ll demonstrate this later in the chapter and cover arrays in greater detail in Chapter 4.
Exercise 2.3
Instead of objects bouncing off the edge of the wall, create an example that includes an invisible force pushing back on the objects to keep them in the window. Can you weight the force according to the object’s distance from an edge so that the closer it is, the stronger the force?
Exercise 2.4
Fix the bouncing off the sides of the canvas so that the circle changes direction when its edge hits the side, rather than its center.
Exercise 2.5
Create a wind force that’s variable. Can you make it interactive? For example, think of a fan located where the mouse is and pointed toward the circles.
When you run the code in Example 2.2, notice that the small circle responds more dramatically to the forces applied to it than the large one. This is because of the formula acceleration = force divided by mass. Mass is in the denominator, so the larger it is, the smaller the acceleration. This makes sense for the wind force—the more massive an object, the harder it should be for the wind to push it around—but is it accurate for a simulation of Earth’s gravitational pull?
If you were to climb to the top of the Leaning Tower of Pisa and drop two balls of different masses, which one would hit the ground first? According to legend, Galileo performed this exact test in 1589, discovering that they fell with the same acceleration, hitting the ground at the same time. Why? I’ll dive deeper into this shortly, but the quick answer is that even though the force of gravity is calculated relative to an object’s mass—so that the bigger the object, the stronger the force—that force is canceled out when you divide by the mass to determine the acceleration. Therefore, the acceleration of gravity for different objects is equal.
A quick fix to the sketch—one that moves a step closer to realistically modeling a force rather than simply making up a force—is to implement this scaling by multiplying the gravity force by mass.
let gravity = createVector(0, 0.1);
Made-up gravity force
let gravityA = p5.Vector.mult(gravity, moverA.mass);
moverA.applyForce(gravityA);
Scale by mover A’s mass.
let gravityB = p5.Vector.mult(gravity, moverB.mass);
moverB.applyForce(gravityB);
Scale by mover B’s mass.
The objects now fall at the same rate. I’m still basically making up the gravity force by arbitrarily setting it to 0.1, but by scaling the force according to the object’s mass, I’m making it up in a way that’s a little truer to Earth’s actual force of gravitational attraction. Meanwhile, because the strength of the wind force is independent of mass, the smaller circle still accelerates to the right more quickly when the mouse is pressed. (The online code for this example also includes a solution to Exercise 2.4, with the addition of a radius
variable in the Mover
class.)
Modeling a Force
Making up forces will actually get you quite far—after all, I just made up a pretty good approximation of Earth’s gravity. Ultimately, the world of p5.js is an orchestra of pixels, and you’re the conductor, so whatever you deem appropriate to be a force, well by golly, that’s the force it should be! Nevertheless, there may come a time when you find yourself wondering, “But how does it all really work?” That’s when modeling forces, instead of just making them up, enters the picture.
Parsing Formulas
In a moment, I’m going to write out the formula for friction. This won’t be the first time
you’ve seen a formula in this book; I just finished up the discussion of Newton’s second law, (or force equals mass times acceleration). You hopefully didn’t spend a lot
of time worrying about that formula, because it’s just a few characters and symbols. Nevertheless, it’s a scary world out there. Just take a look at the equation for a normal distribution, which I covered (without presenting the formula) in “A Normal Distribution of Random Numbers”: