Chapter 1. Vectors
I’m committing crimes with both direction and magnitude.
—Vector, Despicable Me
This book is all about looking at the world around us and developing ways to simulate it with code. In this first part of the book, I’ll start by looking at basic physics: how an apple falls from a tree, how a pendulum swings in the air, how Earth revolves around the sun, and so on. Absolutely everything contained within the book’s first five chapters requires the use of the most basic building block for programming motion, the vector. And so that’s where I’ll begin the story.
The word vector can mean a lot of things. It’s the name of a New Wave rock band formed in Sacramento, California, in the early 1980s, and the name of a breakfast cereal manufactured by Kellogg’s Canada. In the field of epidemiology, a vector is 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 these definitions are worth exploring, they’re not the focus here. Instead, this chapter dives into the Euclidean vector (named for the Greek mathematician Euclid), also known as the geometric vector. When you see the term vector in this book, you can assume it refers to a Euclidean vector, defined as an entity that has both magnitude and direction.
A vector is typically drawn as an arrow, as in Figure 1.1. The vector’s direction is indicated by where the arrow is pointing, and its magnitude by the length of the arrow.
The vector in Figure 1.1 is drawn as an arrow from point A to point B. It serves as an instruction for how to travel from A to B.
The Point of Vectors
Before diving into more details about vectors, I’d like to create a p5.js example that demonstrates why you should care about vectors in the first place. If you’ve watched any beginner p5.js tutorials, read any introductory p5.js textbooks, or taken an introduction to creative coding course (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 bouncing ball sketch.
Example 1.1: Bouncing Ball with No Vectors
let x = 100;
let y = 100;
let xspeed = 2.5;
let yspeed = 2;
function setup() {
createCanvas(640, 240);
}
function draw() {
background(255);
x = x + xspeed;
y = y + yspeed;
Move the ball according to its speed.
if (x > width || x < 0) {
xspeed = xspeed * -1;
}
if (y > height || y < 0) {
yspeed = yspeed * -1;
}
Check for bouncing.
stroke(0);
fill(127);
circle(x, y, 48);
Draw the ball at the position (x, y).
}
In this example, there’s a flat, 2D world—a blank canvas—with a circular shape (a “ball”) traveling around. This ball has properties like position and speed that are represented in the code as variables:
Property | Variable Names |
---|---|
Position | x and y |
Speed | xspeed and yspeed |
In a more sophisticated sketch, you might have many more variables representing other properties of the ball and its environment:
Property | Variable Names |
---|---|
Acceleration | xacceleration and yacceleration |
Target position | xtarget and ytarget |
Wind | xwind and ywind |
Friction | xfriction and yfriction |
You might notice that for every concept in this world (wind, position, acceleration, and the like), there are two variables. And this is only a 2D world. In a three-dimensional (3D) world, you’d need three variables for each property: x
, y
, and z
for position; xspeed
, yspeed
, and zspeed
for speed; and so on. Wouldn’t it be nice to simplify the code to use fewer variables? Instead of starting the program with something like this
let x;
let y;
let xspeed;
let yspeed;
you’d be able to start it with something like this:
let position;
let speed;
Thinking of the ball’s properties as vectors instead of a loose collection of separate values will allow you to do just that.
Taking this first step toward using vectors won’t let you do anything new or magically turn a p5.js sketch into a full-on physics simulation. However, using vectors will help organize your code and provide a set of methods for common mathematical operations you’ll need over and over and over again while programming motion.
As an introduction to vectors, I’m going to stick to two dimensions for quite some time (at least the first several chapters). All these examples can be fairly easily extended to three dimensions (and the class I’ll use, p5.Vector
, allows for three dimensions). However, for the purposes of learning the fundamentals, the added complexity of the third dimension would be a distraction.
Vectors in p5.js
Think of a vector as the difference between two points, or as instructions for walking from one point to another. For example, Figure 1.2 shows some vectors and possible interpretations of them.
These vectors could be thought of in the following way:
Vector | Instructions |
---|---|
(–15, 3) | Walk 15 steps west; turn and walk 3 steps north. |
(3, 4) | Walk 3 steps east; turn and walk 4 steps north. |
(2, –1) | Walk 2 steps east; turn and walk 1 step south. |
You’ve probably already thought this way when programming motion. For every frame of animation (a single cycle through a p5.js draw()
loop), you instruct each object to reposition itself to a new spot a certain number of pixels away horizontally and a certain number of pixels away vertically. This instruction is essentially a vector, as in Figure 1.3; it has both magnitude (how far away did you travel?) and direction (which way did you go?).
The vector sets the object’s velocity, defined as the rate of change of the object’s position with respect to time. In other words, the velocity vector determines the object’s new position for every frame of the animation, according to this basic algorithm for motion: the new position is equal to the result of applying the velocity to the current position.
If velocity is a vector (the difference between two points), what about position? Is it a vector too? Technically, you could argue that position is not a vector, since it’s not describing how to move from one point to another; it’s describing a single point in space. Nevertheless, another way to describe a position is as the path taken from the origin—point (0, 0)—to the current point. When you think of position in this way, it becomes a vector, just like velocity, as in Figure 1.4.
In Figure 1.4, the vectors are placed on a computer graphics canvas. Unlike in Figure 1.2, the origin point (0, 0) isn’t at the center; it’s at the top-left corner. And instead of north, south, east, and west, there are positive and negative directions along the x- and y-axes (with y pointing down in the positive direction).
Let’s examine the underlying data for both position and velocity. In the bouncing ball example, I originally had the following variables:
Property | Variable Names |
---|---|
Position | x , y |
Velocity | xspeed , yspeed |
Now I’ll treat position and velocity as vectors instead, each represented by an object with x
and y
attributes. If I were to write a Vector
class myself, I’d start with something like this:
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Notice that this class is designed to store the same data as before—two floating-point numbers per vector, an x
value and a y
value. At its core, a Vector
object is just a convenient way to store two values (or three, as you’ll see in 3D examples) under one name.
As it happens, p5.js already has a built-in p5.Vector
class, so I don’t need to write one myself. And so this
let x = 100;
let y = 100;
let xspeed = 1;
let yspeed = 3.3;
becomes this:
let position = createVector(100, 100);
let velocity = createVector(1, 3.3);
Notice that the position
and velocity
vector objects aren’t created as you might expect, by invoking a constructor function. Instead of writing new p5.Vector(x, y)
, I’ve called createVector(x, y)
. The createVector()
function is included in p5.js as a helper function to take care of details behind the scenes upon creation of the vector. Except in special circumstances, you should always create p5.Vector
objects with createVector()
. I should note that p5.js functions such as createVector()
can’t be executed outside of setup()
or draw()
, since the library won’t yet be loaded. I’ll demonstrate how to address this in Example 1.2.
Now that I have two vector objects (position
and velocity
), I’m ready to implement the vector-based algorithm for motion: position = position + velocity. In Example 1.1, without vectors, the code reads as follows:
x = x + xspeed;
y = y + yspeed;
Add each speed to each position.
In an ideal world, I would be able to rewrite this as shown here:
position = position + velocity;
Add the velocity vector to the position vector.
In JavaScript, however, the addition operator +
is reserved for primitive values (integers, floats, and the like). JavaScript doesn’t know how to add two p5.Vector
objects together any more than it knows how to add two p5.Font
objects or p5.Image
objects. Fortunately, the p5.Vector
class includes methods for common mathematical operations.
Vector Addition
Before I continue working with the p5.Vector
class and the add()
method, let’s examine vector addition by using the notation found in math and physics textbooks. Vectors are typically written either in boldface type or with an arrow on top. For the purposes of this book, to distinguish a vector (with magnitude and direction) from a scalar (a single value, such as an integer or a floating-point number), I’ll use the arrow notation:
- Vector:
- Scalar:
Let’s say I have the two vectors shown in Figure 1.5.
Each vector has two components, an x and a y. To add the two vectors together, add both x-components and y-components to create a new vector, as in Figure 1.6.
In other words, can be written as follows:
Then, replacing and with their values from Figure 1.6, you get this:
Finally, write the result as a vector:
Addition Properties with Vectors
Addition with vectors follows the same algebraic rules as with real numbers.
The commutative rule:
The associative rule:
Fancy terminology and symbols aside, these rules boil down to quite a simple concept: the result is the same no matter the order in which the vectors are added. Replace the vectors with regular numbers (scalars), and these rules are easy to see:
Commutative:
Associative:
Now that I’ve covered the theory behind adding two vectors together, I can turn to adding vector objects in p5.js. Imagine again that I’m creating my own Vector
class. I could give it a function called add()
that takes another Vector
object as its argument:
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
add(v) {
this.x = this.x + v.x;
this.y = this.y + v.y;
}
New! A function to add another vector to this vector. Add the x-components and the y-components separately.
}
The function looks up the x- and y-components of the two vectors and adds them separately. This is exactly how the built-in p5.Vector
class’s add()
method is written too. Knowing how it works, I can now return to the bouncing ball example with its position + velocity algorithm and implement vector addition:
position = position + velocity;
This does not work!
position.add(velocity);
Add the velocity to the position.
Now you have what you need to rewrite the bouncing ball example with vectors.
Example 1.2: Bouncing Ball with Vectors!
let position;
let velocity;
Instead of a bunch of floats, you now have just two variables.
function setup() {
createCanvas(640, 240);
position = createVector(100, 100);
velocity = createVector(2.5, 2);
Note that createVector() has to be called inside setup().
}
function draw() {
background(255);
position.add(velocity);
if (position.x > width || position.x < 0) {
velocity.x = velocity.x * -1;
}
if (position.y > height || position.y < 0) {
velocity.y = velocity.y * -1;
}
You still sometimes need to refer to the individual components of a p5.Vector and can do so using the dot syntax: position.x, velocity.y, and so forth.
stroke(0);
fill(127);
circle(position.x, position.y, 48);
}
At this stage, you might feel somewhat disappointed. After all, these changes may 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 the power of programming with vectors hasn’t been fully realized just yet. Looking at a bouncing ball and only implementing vector addition is just the first step. As I move forward into a more complex world of multiple objects and multiple forces (which I’ll introduce in Chapter 2) acting on those objects, the benefits of vectors will become more apparent.
I should, however, note an important aspect of the transition to programming with vectors. Even though I’m using p5.Vector
objects to encapsulate two values—the x
and y
of the ball’s position or the x
and y
of the ball’s velocity—under a single variable name, I’ll still often need to refer to the x- and y-components of each vector individually.
The circle()
function doesn’t allow for a p5.Vector
object as an argument. A circle can be drawn with only two scalar values, an x-coordinate and a y-coordinate. And so I must dig into the p5.Vector
object and pull out the x- and y-components by using object-oriented dot syntax:
circle(position, 48);
circle(position.x, position.y, 48);
The same issue arises when testing whether the circle has reached the edge of the window. In this case, I need to access the individual components of both vectors, position
and velocity
:
if ((position.x > width) || (position.x < 0)) {
velocity.x = velocity.x * -1;
}
It may not always be obvious when to directly access an object’s properties versus when to reference the object as a whole or use one of its methods. The goal of this chapter (and most of this book) is to help you distinguish between these scenarios by providing a variety of examples and use cases.
Exercise 1.1
Take one of the walker examples from Chapter 0 and convert it to use vectors.
Exercise 1.2
Find something else you’ve previously made in p5.js using separate x
and y
variables, and use vectors instead.
Exercise 1.3
Extend Example 1.2 into 3D. Can you get a sphere to bounce around a box?
More Vector Math
Addition was really just the first step. Many mathematical operations are commonly used with vectors. Here’s a comprehensive table of the operations available as methods in the p5.Vector
class. Remember, these are not stand-alone functions, but rather methods associated with the p5.Vector
class. When you see the word this in the following table, it refers to the specific vector the method is operating on.
Method | Task |
---|---|
| Adds a vector to this vector |
| Subtracts a vector from this vector |
| Scales this vector with multiplication |
| Scales this vector with division |
| Returns the magnitude of this vector |
| Sets the magnitude of this vector |
| Normalizes this vector to a unit length of 1 |
| Limits the magnitude of this vector |
| Returns the 2D heading of this vector expressed as an angle |
| Rotates this 2D vector by an angle |
| Linear interpolates to another vector |
| Returns the Euclidean distance between two vectors (considered as points) |
| Finds the angle between two vectors |
| Returns the dot product of two vectors |
| Returns the cross product of two vectors (relevant only in three dimensions) |
| Returns a random 2D vector |
| Returns a random 3D vector |
I’ll go through a few of the key methods now. As the examples get more sophisticated in later chapters, I’ll continue to reveal more details.
Vector Subtraction
Having already covered addition, I’ll now turn to subtraction. This one’s not so bad; just take the plus sign and replace it with a minus! Before tackling subtraction itself, however, consider what it means for a vector to become . The negative version of the scalar 3 is –3. A negative vector is similar: the polarity of each of the vector’s components is inverted. So if has the components (x, y), then is (–x, –y). Visually, this results in an arrow of the same length as the original vector pointing in the opposite direction, as depicted in Figure 1.7.
Subtraction, then, is the same as addition, only with the second vector in the equation treated as a negative version of itself:
Just as vectors are added by placing them “tip to tail”—that is, aligning the tip (or end point) of one vector with the tail (or start point) of the next—vectors are subtracted by reversing the direction of the second vector and placing it at the end of the first, as in Figure 1.8.
To actually solve the subtraction, take the difference of the vectors’ components. That is, can be written as shown here:
Inside p5.Vector
, the code reads as follows:
sub(v) {
this.x = this.x - v.x;
this.y = this.y - v.y;
}
The following example demonstrates vector subtraction by taking the difference between two points (which are treated as vectors): the mouse position and the center of the window.
Example 1.3: Vector Subtraction
function draw() {
background(255);
let mouse = createVector(mouseX, mouseY);
let center = createVector(width / 2, height / 2);
Two vectors, one for the mouse location and one for the center of the window
stroke(200);
strokeWeight(4);
line(0, 0, mouse.x, mouse.y);
line(0, 0, center.x, center.y);
Draw the original two vectors.
mouse.sub(center);
Vector subtraction!
stroke(0);
translate(width / 2, height / 2);
line(0, 0, mouse.x, mouse.y);
Draw a line to represent the result of subtraction. Notice that I move the origin with translate() to place the vector.
}
Note the use of translate()
to visualize the resulting vector as a line from the center (width / 2, height / 2)
to the mouse. Vector subtraction is its own kind of translation, moving the “origin” of a position vector. Here, by subtracting the center vector from the mouse vector, I’m effectively moving the starting point of the resulting vector to the center of the canvas. Therefore, I also need to move the origin by using translate()
. Without this, the line would be drawn from the top-left corner, and the visual connection wouldn’t be as clear.
Vector Multiplication and Division
Moving on to multiplication, you have to think a bit differently. Multiplying a vector typically refers to the process of scaling a vector. If I want to scale a vector to twice its size or one-third of its size, while leaving its direction the same, I would say, “Multiply the vector by 2” or “Multiply the vector by 1/3.” Unlike with addition and subtraction, I’m multiplying the vector by a scalar (a single number), not by another vector. Figure 1.9 illustrates how to scale a vector by a factor of 3.
To scale a vector, multiply each component (x and y) by a scalar. That is, can be written as shown here:
As an example, say and . You can calculate as follows:
This is exactly how the mult()
function inside the p5.Vector
class works:
mult(n) {
this.x = this.x * n;
this.y = this.y * n;
The components of the vector are multiplied by a number.
}
Implementing multiplication in code is as simple as the following:
let u = createVector(-3, 7);
u.mult(3);
This p5.Vector is now three times the size and is equal to (–9, 21). See Figure 1.9.
Example 1.4 illustrates vector multiplication by drawing a line between the mouse and the center of the canvas, as in the previous example, and then scaling that line by 0.5.
Example 1.4: Multiplying a Vector
function draw() {
background(255);
let mouse = createVector(mouseX, mouseY);
let center = createVector(width / 2, height / 2);
mouse.sub(center);
translate(width / 2, height / 2);
strokeWeight(2);
stroke(200);
line(0, 0, mouse.x, mouse.y);
mouse.mult(0.5);
Multiplying a vector! The vector is now half its original size (multiplied by 0.5).
stroke(0);
strokeWeight(4);
line(0, 0, mouse.x, mouse.y);
}
The resulting vector is half its original size. Rather than multiplying the vector by 0.5, I could achieve the same effect by dividing the vector by 2, as in Figure 1.10.
Vector division, then, works just like vector multiplication—just replace the multiplication sign (*
) with the division sign (/
). Here’s how the p5.Vector
class implements the div()
function:
div(n) {
this.x = this.x / n;
this.y = this.y / n;
}
And here’s how to use the div()
function in a sketch:
let u = createVector(8, -4);
u.div(2);
Dividing a vector! The vector is now half its original size (divided by 2).
This takes the vector u
and divides it by 2.
More Number Properties with Vectors
As with addition, basic algebraic rules of multiplication apply to vectors.
The associative rule:
The distributive rule with two scalars, one vector:
The distributive rule with two vectors, one scalar:
Variables for position and speed of ball