If you're seeing this message, it means we're having trouble loading external resources for Khan Academy.

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

All this vector math stuff sounds like something we should know about, but why? How will it actually help us write code? The truth of the matter is that we need to have some patience. It will take some time before the awesomeness of using the PVector class fully comes to light. This is actually a common occurrence when first learning a new data structure. For example, when you first learn about an array, it might seem like much more work to use an array than to just have several variables stand for multiple things. But that plan quickly breaks down when you need a hundred, or a thousand, or ten thousand things. The same can be true for PVector. What might seem like more work now will pay off later, and pay off quite nicely. And you don’t have to wait too long, as your reward will come in the next chapter.

Velocity

For now, however, we want to focus on simplicity. What does it mean to program motion using vectors? We’ve seen the beginning of this in the bouncing ball example. An object on screen has a location (where it is at any given moment) as well as a velocity (instructions for how it should move from one moment to the next). Velocity is added to location:

location.add(velocity);

And then we draw the object at that location:

ellipse(location.x,location.y,16,16);

This is Motion 101:

  • Add velocity to location
  • Draw object at location

In the bouncing ball example, all of this code happened inside the ProcessingJS's draw function. What we want to do now is move towards encapsulating all of the logic for motion inside of an object. This way, we can create a foundation for programming moving objects in all of our ProcessingJS programs.

In this case, we’re going to create a generic Mover object that will describe a thing moving around the screen. And so we must consider the following two questions:

  • What data does a mover have?
  • What functionality does a mover have?

Our Motion 101 algorithm tells us the answers to these questions. A Mover object has two pieces of data: location and velocity, which are both PVector objects. We can start by writing the constructor function that initializes those properties to appropriate random values:

var Mover = function() {
  this.position = new PVector(random(width), random(height));
  this.velocity = new PVector(random(-2, 2), random(-2, 2));
};

Its functionality is just about as simple. The Mover needs to move and it needs to be seen. We’ll implement these needs as methods named update() and display(). We’ll put all of our motion logic code in update() and draw the object in display().

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

Mover.prototype.display = function() {
  stroke(0);
  strokeWeight(2);
  fill(127);
  ellipse(this.position.x, this.position.y, 48, 48);
};

If object-oriented programming is at all new to you, one aspect here may seem a bit confusing. After all, we spent the beginning of this chapter discussing PVector. The PVector object is the template for making the location object and the velocity object. So what are they doing inside of yet another object, the Mover object? In fact, this is just about the most normal thing ever. An object is simply something that holds data (and functionality). That data can be numbers, strings, arrays or other objects! We’ll see this over and over again in this course. For example, in the Particles tutorial, we’ll write an object to describe a system of particles. That ParticleSystem object will have as its data an array of Particle objects…and each Particle object will have as its data several PVector objects!

Let’s finish off the Mover object by incorporating a function to determine what the object should do when it reaches the edge of the window. For now let’s do something simple, and just have it wrap around the edges:

Mover.prototype.checkEdges = function() {

  if (this.position.x > width) {
    this.position.x = 0;
  } 
  else if (this.position.x < 0) {
    this.position.x = width;
  }

  if (this.position.y > height) {
    this.position.y = 0;
  } 
  else if (this.position.y < 0) {
    this.position.y = height;
  }
};

Now that the Mover object is finished, we can look at what we need to do in our main program. We first declare and initialize new Mover instance:

var mover = new Mover();

Then we call the appropriate functions in draw:

var draw = function() {
  background(255, 255, 255);
  
  mover.update();
  mover.checkEdges();
  mover.display(); 
};

Here's the full running example. Try playing around with the numbers, commenting out code, and seeing what happens:

Acceleration

OK. At this point, we should feel comfortable with two things: (1) what a PVector is and (2) how we use PVectors inside of an object to keep track of its location and movement. This is an excellent first step and deserves a mild round of applause. Before standing ovations and screaming fans, however, we need to make one more, somewhat bigger step forward. After all, watching the Motion 101 example is fairly boring—the circle never speeds up, never slows down, and never turns. For more interesting motion, for motion that appears in the real world around us, we need to add one more PVector to our Mover object—acceleration.

The strict definition of acceleration we’re using here is: the rate of change of velocity. Let’s think about that definition for a moment. Is this a new concept? Not really. Velocity is defined as the rate of change of location. In essence, we are developing a “trickle-down” effect. Acceleration affects velocity, which in turn affects location (for some brief foreshadowing, this point will become even more crucial in the next chapter, when we see how forces affect acceleration, which affects velocity, which affects location). In code, this reads:

velocity.add(acceleration);
location.add(velocity);

As an exercise, from this point forward, let’s make a rule for ourselves. Let’s write every example in the rest of these tutorials without ever touching the value of velocity and location (except to initialize them). In other words, our goal now for programming motion is: Come up with an algorithm for how we calculate acceleration and let the trickle-down effect work its magic. (In truth, you’ll find reasons to break this rule, but it’s important to illustrate the principles behind our motion algorithm.) And so we need to come up with some ways to calculate acceleration:

  1. A constant acceleration
  2. A totally random acceleration
  3. Acceleration towards the mouse

Algorithm #1, a constant acceleration, is not particularly interesting, but it is the simplest and will help us begin incorporating acceleration into our code.

The first thing we need to do is add another PVector property to the Mover constructor to represent the acceleration. We'll initialize it to (-0.001, 0.01) and keep it at that value forever, since our current algorithm is constant acceleration. You might be thinking, “Gosh, those values seem awfully small!” That’s right, they are quite tiny. It’s important to realize that our acceleration values (measured in pixels) will accumulate over time in the velocity, about thirty times per second depending on our sketch’s frame rate. And so to keep the magnitude of the velocity vector within a reasonable range, our acceleration values should start and remain quite small.

var Mover = function() {
  this.position = new PVector(width/2,height/2);
  this.velocity = new PVector(0, 0);
  this.acceleration = new PVector(-0.001, 0.01);
  this.topspeed = 10;  
};

Notice above that we also started the velocity at 0 - because we know we'll be speeding it up as the program runs, thanks to acceleration. We'll do that in the update() method:

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

Since we are continuously increasing velocity, we run the risk of our velocity values becoming incredibly large, if we leave the program running long enough. We want to limit the velocity to a maximum. We can do that using the PVector limit method, which restricts a vector to a given magnitude.

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

This translates to the following:
What is the magnitude of velocity? If it’s less than 10, no worries; just leave it as is. If it’s more than 10, however, reduce it to 10!

Let’s take a look at the changes to the Mover object, complete with acceleration and limit():

Now on to Algorithm #2, a totally random acceleration. In this case, instead of initializing acceleration in the object’s constructor, we want to pick a new acceleration each cycle, i.e. each time update() is called.

Mover.prototype.update = function() {
  this.acceleration = PVector.random2D();
  this.velocity.add(this.acceleration);
  this.velocity.limit(10);
  this.position.add(this.velocity);  
};

Because the random vector is a normalized one, we can try scaling it with two different techniques:

  1. scaling the acceleration to a constant value:
    acceleration = PVector.random2D();
    acceleration.mult(0.5);

     
  2. scaling the acceleration to a random value:
    acceleration = PVector.random2D();
    acceleration.mult(random(2));

While this may seem like an obvious point, it’s crucial to understand that acceleration does not merely refer to the speeding up or slowing down of a moving object, but rather any change in velocity in either magnitude or direction. Acceleration is used to steer an object, and we’ll see this again and again in future chapters as we begin to program objects that make decisions about how to move about the screen.

 


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.