PVector
by Daniel Shiffman
This tutorial is adapted from The Nature of Code by Daniel Shiffman. This work is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported License. If you see any errors or have comments, please let us know.
The most basic building block for programming motion is the vector. And so this is where we begin. Now, the word vector can mean a lot of different things. Vector is the name of a new wave rock band formed in Sacramento, CA in the early 1980s. It's the name of a breakfast cereal manufactured by Kellogg's Canada. In the field of epidemiology, a vector is used to describe an organism that transmits infection from one host to another. In the C++ programming language, a Vector (std::vector) is an implementation of a dynamically resizable array data structure. While all interesting, these are not the definitions we are looking for. Rather, what we want is this vector:
A vector is a collection of values that describe relative position in space.
Vectors: You Complete Me
Before we get into vectors themselves, let's look at a beginner Processing example that demonstrates why it is in the first place we should care. If you've read any of the introductory Processing textbooks or taken a class on programming with Processing (and hopefully you've done one of these things to help prepare you for this book), you probably, at one point or another, learned how to write a simple bouncing ball sketch.
Example: Bouncing Ball with No Vectors
float x = 100;
float y = 100;
float xspeed = 1;
float yspeed = 3.3;
void setup() {
size(200,200);
smooth();
background(255);
}
void draw() {
noStroke();
fill(255,10);
rect(0,0,width,height);
// Add the current speed to the location.
x = x + xspeed;
y = y + yspeed;
// Check for bouncing
if ((x > width) || (x < 0)) {
xspeed = xspeed * -1;
}
if ((y > height) || (y < 0)) {
yspeed = yspeed * -1;
}
// Display at x,y location
stroke(0);
fill(175);
ellipse(x,y,16,16);
}
In the above example, we have a very simple world — a blank canvas with a circular shape (“ball”) traveling around. This “ball” has some properties.
- LOCATION: x and y
- SPEED: xspeed and yspeed
In a more advanced sketch, we could imagine this ball and world having many more properties:
- ACCELERATION: xacceleration and yacceleration
- TARGET LOCATION: xtarget and ytarget
- WIND: xwind and ywind
- FRICTION: xfriction and yfriction
It's becoming more and more clear that for every singular concept in this world (wind, location, acceleration, etc.), we need two variables. And this is only a two-dimensional world, in a 3D world, we'd need x, y, z, xspeed, yspeed, zspeed, etc. Our first goal in this chapter is learn the fundamental concepts behind using vectors and rewrite this bouncing ball example. After all, wouldn't it be nice if we could simple write our code like the following?
Instead of:
float x;
float y;
float xspeed;
float yspeed;
Woudn't it be nice to have. . .
Vector location;
Vector speed;
Vectors aren't going to allow us to do anything new. Using vectors won't suddenly make your Processing sketches magically simulate physics, however, they will simplify your code and provide a set of functions for common mathematical operations that happen over and over and over again while programming motion.
As an introduction to vectors, we're going to live in 2 dimensions for quite some time (at least until we get through the first several chapters.) All of these examples can be fairly easily extended to three dimensions (and the class we will use — PVector — allows for three dimensions.) However, for the time being, it's easier to start with just two.
Vectors: What are they to us, the Processing programmer?
Technically speaking, the definition of a vector is the difference between two points. Consider how you might go about providing instructions to walk from one point to another.
Here are some vectors and possible translations:
You've probably done this before when programming motion. For every frame of animation (i.e. single cycle through Processing's draw() loop), you instruct each object on the screen to move a certain number of pixels horizontally and a certain number of pixels (vertically).
For a Processing programmer, we can now understand a vector as the instructions for moving a shape from point A to point B, an object's “pixel velocity” so to speak.
For every frame:
location = location + velocity
If velocity is a vector (the difference between two points), what is location? Is it a vector too? Technically, one might argue that location is not a vector, it's not describing the change between two points, it's simply describing a singular point in space — a location. And so conceptually, we think of a location as different: a single point rather than the difference between two points.
Nevertheless, another way to describe a location is as the path taken from the origin to reach that location. Hence, a location can be represented as the vector giving the difference between location and origin. Therefore, if we were to write code to describe a vector object, instead of creating separate Point and Vector classes, we can use a single class which is more convenient.
Let's examine the underlying data for both location and velocity. In the bouncing ball example we had the following:
location --> x,y
velocity --> xspeed,yspeed
Notice how we are storing the same data for both — two floating point numbers, an x and a y. If we were to write a vector class ourselves, we'd start with something rather basic:
class PVector {
float x;
float y;
PVector(float x_, float y_) {
x = x_;
y = y_;
}
}
At its core, a PVector is just a convenient way to store two values (or three, as we'll see in 3D examples.).
And so this. . .
float x = 100;
float y = 100;
float xspeed = 1;
float yspeed = 3.3;
. . . becomes . . .
PVector location = new PVector(100,100);
PVector velocity = new PVector(1,3.3);
Now that we have two vector objects (“location” and “velocity”), we're ready to implement the algorithm for motion — location = location + velocity. In the bouncing ball example, without vectors, we had:
// Add the current speed to the location.
x = x + xspeed;
y = y + yspeed;
In an ideal world, we would just be able to rewrite the above to be:
// Add the current velocity vector to the location vector.
location = location + velocity;
However, in Processing, the addition operator '+' is reserved for primitive values (integers, floats, etc.) only. Processing doesn't know how to add two PVector objects together any more than it knows how to add two PFont objects or PImage objects. Fortunately for us, the PVector class is implemented with functions for common mathematical operations.
Vectors: Addition
Before we continue looking at the PVector class and its add() method (purely for the sake of learning since it's already implemented for us in Processing itself), let's examine vector addition using the notation found in math/physics textbooks.
Vectors are typically written as with either boldface type or with an arrow on top. For the purposes of this tutorial, to distinguish a vector from a scalar (scalar refers to a single value, such as integer or floating point), we'll use boldface type:
Vector: v Scalar: x
Let's say I have the following two vectors:
u = (5,2) v = (3,4)
Each vector has two components, an x and a y. To add two vectors together we simply add both x's and both y's. In other words:
w = u + v
translates to:
wx = ux + vx wy = uy + vy
and therefore:
wx = 5 + 3 wy = 2 + 4
and therefore:
w = (8,6)
Now that we understand how to add two vectors together, we can look at how addition is implemented in the PVector class itself. Let's write a function called add() that takes as its argument another PVector object.
class PVector {
float x;
float y;
PVector(float x_, float y_) {
x = x_;
y = y_;
}
// New! A function to add another PVector to this PVector.
// Simply add the x components and the y components together.
void add(PVector v) {
x = x + v.x;
y = y + v.y;
}
}
Now that we see how add is written inside of PVector, we can return to the location + velocity algorithm with our bouncing ball example and implement vector addition:
// Add the current velocity to the location.
location = location + velocity;
location.add(velocity);
And here we are, ready to successfully complete our first goal — rewrite the entire bouncing ball example using PVector.
Example: Bouncing Ball with PVector!
// Instead of a bunch of floats, we now just have two PVector variables.
PVector location;
PVector velocity;
void setup() {
size(200,200);
smooth();
background(255);
location = new PVector(100,100);
velocity = new PVector(2.5,5);
}
void draw() {
noStroke();
fill(255,10);
rect(0,0,width,height);
// Add the current speed to the location.
location.add(velocity);
// We still sometimes need to refer to the individual components of a PVector
// and can do so using the dot syntax (location.x, velocity.y, etc.)
if ((location.x > width) || (location.x < 0)) {
velocity.x = velocity.x * -1;
}
if ((location.y > height) || (location.y < 0)) {
velocity.y = velocity.y * -1;
}
// Display circle at x location
stroke(0);
fill(175);
ellipse(location.x,location.y,16,16);
}
Now, you might feel somewhat disappointed. After all, this may initially appear to have made the code more complicated than the original version. While this is a perfectly reasonable and valid critique, it's important to understand that we haven't fully realized the power of programming with vectors just yet. Looking at a simple bouncing ball and only implementing vector addition is just the first step. As we move forward into looking at more a complex world of multiple objects and multiple forces (we'll cover forces in the next chapter), the benefits of PVector will become more apparent.
We should, however, make note of an important aspect of the above transition to programming with vectors. Even though we are using PVector objects to describe two values — the x and y of location and the x and y of velocity — we still often need to refer to the x and y components of each PVector individually. When we go to draw an object in Processing there's no means for us to say:
ellipse(location,16,16);
The ellipse() function does not allow for a PVector as an argument. An ellipse can only be drawn with two scalar values, an x coordinate and a y coordinate. And so we must dig into the PVector object and pull out the x and y components using object oriented dot syntax.
ellipse(location.x,location.y,16,16);
The same issue arises when it comes time to test if the circle has reached the edge of the window, and we need to access the individual components of both vectors: location and velocity.
if ((location.x > width) || (location.x < 0)) {
velocity.x = velocity.x * -1;
}
Vectors: More Algebra
Addition was really just the first step. There is a long list of common mathematical operations that are used with vectors when programming the motion of objects on the screen. Following is a comprehensive list of all of the mathematical operations available as functions in the PVector class. We'll then go through a few of the key ones now. As our examples get more and more sophisticated we'll continue to reveal the details of these functions.
- add() — add vectors
- sub() — subtract vectors
- mult() — scale the vector with multiplication
- div() — scale the vector with division
- mag() — calculate the magnitude of a vector
- normalize() — normalize the vector to unit length of 1
- limit() — limit the magnitude of a vector
- heading() — the heading of a vector expressed as an angle
- dist() — the euclidean distance between two vectors (considered as points)
- angleBetween() — find the angle between two vectors
- dot() — the dot product of two vectors
- cross() — the cross product of two vectors
Having already run through addition, let's start with subtraction. This one's not so bad, just take the plus sign from addition and replace it with a minus!
Vector subtraction: w = u - v
translates to:
wx = ux - vx wy = uy - v