From 22d9e3920ba95a3dc29dca5fcdcbabfc3bebf38f Mon Sep 17 00:00:00 2001 From: shiffman Date: Fri, 16 Jun 2023 17:44:02 +0000 Subject: [PATCH] Notion - Update docs --- content/01_vectors.html | 2 +- content/02_forces.html | 7 +- content/03_oscillation.html | 8 +- content/04_particles.html | 499 +++++++++--------- content/05_steering.html | 133 +++-- .../noc_4_01_single_particle/particle.js | 5 +- .../05_steering/example_5_9_flocking/boid.js | 173 ++++++ .../05_steering/example_5_9_flocking/flock.js | 24 + .../example_5_9_flocking/index.html | 14 + .../example_5_9_flocking/sketch.js | 31 ++ .../example_5_9_flocking/style.css | 7 + .../example_5_9_flocking_with_binning/boid.js | 190 +++++++ .../flock.js | 24 + .../index.html | 14 + .../sketch.js | 97 ++++ .../style.css | 7 + .../quadtree_part_1_copy/index.html | 18 + .../quadtree_part_1_copy/quadtree.js | 140 +++++ .../quadtree_part_1_copy/sketch.js | 46 ++ .../quadtree_part_1_copy/style.css | 7 + .../sine_cosine_lookup_table/index.html | 13 + .../sine_cosine_lookup_table/sketch.js | 51 ++ .../sine_cosine_lookup_table/style.css | 7 + 23 files changed, 1227 insertions(+), 290 deletions(-) create mode 100644 content/examples/05_steering/example_5_9_flocking/boid.js create mode 100644 content/examples/05_steering/example_5_9_flocking/flock.js create mode 100644 content/examples/05_steering/example_5_9_flocking/index.html create mode 100644 content/examples/05_steering/example_5_9_flocking/sketch.js create mode 100644 content/examples/05_steering/example_5_9_flocking/style.css create mode 100644 content/examples/05_steering/example_5_9_flocking_with_binning/boid.js create mode 100644 content/examples/05_steering/example_5_9_flocking_with_binning/flock.js create mode 100644 content/examples/05_steering/example_5_9_flocking_with_binning/index.html create mode 100644 content/examples/05_steering/example_5_9_flocking_with_binning/sketch.js create mode 100644 content/examples/05_steering/example_5_9_flocking_with_binning/style.css create mode 100644 content/examples/05_steering/quadtree_part_1_copy/index.html create mode 100644 content/examples/05_steering/quadtree_part_1_copy/quadtree.js create mode 100644 content/examples/05_steering/quadtree_part_1_copy/sketch.js create mode 100644 content/examples/05_steering/quadtree_part_1_copy/style.css create mode 100644 content/examples/05_steering/sine_cosine_lookup_table/index.html create mode 100644 content/examples/05_steering/sine_cosine_lookup_table/sketch.js create mode 100644 content/examples/05_steering/sine_cosine_lookup_table/style.css diff --git a/content/01_vectors.html b/content/01_vectors.html index 8a8300c2..1ba2c04c 100644 --- a/content/01_vectors.html +++ b/content/01_vectors.html @@ -726,7 +726,7 @@

Example 1.7: Motion 101 (Velocity)

} }

If object-oriented programming is at all new to you, one aspect here may seem a bit strange. I spent the beginning of this chapter discussing the p5.Vector class, and this class is the template for making the position object and the velocity object. So what are those objects doing inside of yet another object, the Mover object?

-

In fact, this is just about the most normal thing ever. An object is something that holds data (and functionality). That data can be numbers, or it can be other objects (arrays too)! You’ll see this over and over again in this book. In Chapter 4 , for example, I’ll write a class to describe a system of particles. That ParticleSystem object will include a list of Particle objects . . . and each Particle object will have as its data several p5.Vector objects!

+

In fact, this is just about the most normal thing ever. An object is something that holds data (and functionality). That data can be numbers, or it can be other objects (arrays too)! You’ll see this over and over again in this book. In Chapter 4 , for example, I’ll write a class to describe a system of particles. That ParticleSystem object will include a list of Particle objects . . . and each Particle object will have as its data several p5.Vector objects!

At this point, you hopefully feel comfortable with two things: (1) what a vector is, and (2) how to use vectors inside of an object to keep track of its position and movement. This is an excellent first step and deserves a mild round of applause. Before standing ovations are in order, however, you 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 sophisticated motion—the kind of motion that appears in the world around us—one more vector needs to be added to the class: acceleration.

Acceleration

Acceleration is the rate of change of velocity. Think about that definition for a moment. Is it a new concept? Not really. Earlier I defined velocity as the rate of change of position, so in essence I’m developing a “trickle-down” effect. Acceleration affects velocity, which in turn affects position. (To provide some brief foreshadowing, this point will become even more crucial in the next chapter, when I look at how forces like friction affect acceleration, which affects velocity, which affects position.) In code, this trickle-down effect reads:

diff --git a/content/02_forces.html b/content/02_forces.html index 0438fb8f..5fa7f91f 100644 --- a/content/02_forces.html +++ b/content/02_forces.html @@ -1,12 +1,7 @@

Chapter 2. Forces

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 some 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.

-
-

- What is a physics engine? - A physics engine is a computer program (or code library) that simulates the behavior of objects in a physical environment. In our case, the objects are two-dimensional 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). -

-
+

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 two-dimensional 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 will focus on building a rudimentary physics engine, with a focus 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.

diff --git a/content/03_oscillation.html b/content/03_oscillation.html index 64331e41..2b7a5f86 100644 --- a/content/03_oscillation.html +++ b/content/03_oscillation.html @@ -340,7 +340,6 @@

Example 3.4: Polar to Cartesian

let x = r * cos(theta); let y = r * sin(theta); - ellipseMode(CENTER); fill(127); stroke(0); strokeWeight(2); @@ -350,6 +349,13 @@

Example 3.4: Polar to Cartesian

// Increase the angle over time theta += 0.02; } +

Polar to Cartesian conversion is common enough that p5.js includes as handy function to take care of it for you. It’s included as a static method of the p5.Vector class called fromAngle(). It takes an angle in radians and creates a unit vector in Cartesian space that points in the direction specified by the angle. Here’s how that would look in Example 3.4.

+
  // Creating a unit vector pointing in the direction of an angle
+  let position = p5.Vector.fromAngle(theta);
+  // To complete Polar to Cartesian conversion the vector is scaled by r
+  position.mult(r);
+  // Drawing the circle using the x,y components of the vector
+  circle(position.x, position.y, 48);

Are you amazed yet? I’ve demonstrated some pretty great uses of tangent (for finding the angle of a vector) and sine and cosine (for converting from polar to Cartesian coordinates). I could stop right here and be satisfied. But I’m not going to. This is only the beginning. As I’ll show you next, what sine and cosine can do for you goes beyond mathematical formulas and right triangles.

Exercise 3.4

diff --git a/content/04_particles.html b/content/04_particles.html index 2ddb0be3..60d28045 100644 --- a/content/04_particles.html +++ b/content/04_particles.html @@ -4,17 +4,18 @@

Chapter 4. Particle Systems

“That is wise. Were I to invoke logic, however, logic clearly dictates that the needs of the many outweigh the needs of the few.”

— Spock

-

In 1982, William T. Reeves, a researcher at Lucasfilm Ltd., was working on the film Star Trek II: The Wrath of Khan. Much of the movie revolves around the Genesis Device, a torpedo that when shot at a barren, lifeless planet has the ability to reorganize matter and create a habitable world for colonization. During the sequence, a wall of fire ripples over the planet while it is being “terraformed.” The term particle system, an incredibly common and useful technique in computer graphics, was coined in the creation of this particular effect.

+

In 1982, William T. Reeves, a researcher at Lucasfilm Ltd., was working on the film Star Trek II: The Wrath of Khan. Much of the movie revolves around the Genesis Device, a torpedo that, when shot at a barren, lifeless planet, has the ability to reorganize matter and create a habitable world for colonization. During the sequence, a wall of fire ripples over the planet while it’s being “terraformed.” The term particle system, an incredibly common and useful technique in computer graphics, was coined in the creation of this particular effect. As Reeves put it:

“A particle system is a collection of many many minute particles that together represent a fuzzy object. Over a period of time, particles are generated into a system, move and change from within the system, and die from the system.”

-

—William Reeves, "Particle Systems—A Technique for Modeling a Class of Fuzzy Objects," ACM Transactions on Graphics 2:2 (April 1983), 92.

+

—William Reeves, “Particle Systems—A Technique for Modeling a Class of Fuzzy Objects,” ACM Transactions on Graphics 2, no. 2 (April 1983): 92.

Since the early 1980s, particle systems have been used in countless video games, animations, digital art pieces, and installations to model various irregular types of natural phenomena, such as fire, smoke, waterfalls, fog, grass, bubbles, and so on.

-

This chapter is dedicated to looking at implementation strategies for coding a particle system. How do you organize your code? Where do you store information related to individual particles versus information related to the system as a whole? The examples I’ll cover will focus on managing the data associated with a particle system. They’ll use simple dots for the particles and apply only the most basic behaviors (such as gravity). However, by building on this framework and adding more creative ways to render the particles and compute behaviors, you can achieve a variety of effects.

-

4.1 Why You Need Particle Systems

-

I’ve defined a particle system to be a collection of independent objects, often represented by a simple shape or dot. Why does this matter? Certainly, the prospect of modeling some of the phenomena listed (waterfalls!) is attractive and potentially useful. But really, there’s an even better reason to explore particle systems. If you want to get anywhere in this nature of code life, you’re likely to find yourself developing systems of many things–balls bouncing, birds flocking, ecosystems evolving, all sorts of things in plural.

-

Just about every chapter after this one is going to deal with a list of objects. Yes, I’ve dipped my toe in the array waters in some of the first vector and forces examples. But now it‘s time to go where no array has gone before.

-

First, I’m going to want to deal with flexible quantities of elements. Some examples will have zero things, sometimes one thing, sometimes ten things, and sometimes ten thousand things. Second, I’m going to want to take a more sophisticated object-oriented approach. Instead of writing a class to describe a single particle, I’m also going to want to write a class that describes the collection of particles—the particle system itself. The goal here is to be able to write a sketch that looks like the following:

+

This chapter is dedicated to looking at strategies for coding a particle system and managing the associated data. How do you organize your code? Where do you store information related to individual particles versus information related to the system as a whole? The examples I’ll cover will use simple dots for the particles and apply only the most basic behaviors. However, the fact that the particles I’ll generate look or behave a certain way shouldn’t limit your imagination. Just because particle systems tend to look sparkly, fly forward, or fall with gravity doesn’t mean that those are the characteristics yours should have, too. By building on this chapter’s framework and adding more creative ways to render the particles and compute their behavior, you can achieve a variety of effects.

+

In other words, the focus of this chapter is on how to keep track of a system of many elements. What those elements actually do and how they actually look is entirely up to you.

+

Why You Need Particle Systems

+

A particle system is a collection of independent objects, often represented by dots or other simple shapes. But why does this matter? Certainly, the prospect of modeling some of the phenomena listed (waterfalls!) is attractive and potentially useful. More broadly, though, as you start developing more sophisticated simulations, you’re likely to find yourself working with systems of many things—balls bouncing, birds flocking, ecosystems evolving, all sorts of things in plural. The particle system strategies discussed here will help you in all of those situations.

+

In fact, just about every chapter from this one on is going include sketches involving lists of objects, and that’s basically what a particle system is. Yes, I’ve already dipped my toe in the array waters in some of the previous chapters’ examples. But now it’s time to go where no array has gone before (in this book, anyway).

+

First, I’m going to want to accommodate flexible quantities of elements. Some examples may have zero things, sometimes one thing, sometimes ten things, and sometimes ten thousand things. Second, I’m going to want to take a more sophisticated, object-oriented approach. In addition to writing a class to describe a single particle, I’m also going to want to write a class that describes the whole collection of particles—the particle system itself. The goal here is to be able to write a sketch that looks like this:

// Ah, isn’t this main program so simple and lovely?
 let system;
 
@@ -27,14 +28,12 @@ 

4.1 Why You Need Particle Systems

background(255); system.run(); }
-

No single particle is referenced in the above code, yet the result will be full of particles flying all over the canvas. Getting used to writing sketches with multiple classes, and classes that keep lists of instances of other classes, will prove very useful as you get to later chapters in this book.

-

Finally, working with particle systems is also a good excuse to tackle two other object-oriented programming techniques: inheritance and polymorphism. With the examples you’ve seen up until now, I’ve always used an array of a single type of object, like "movers" or “oscillators.” With inheritance (and polymorphism), I’ll demonstrate a convenient way to store a single list containing objects of different types. This way, a particle system need not only be a system of a one kind of particle.

-

Though it may seem obvious to you, I’d also like to point out that my examples are modeled after conventional implementations of particle systems, and that’s where I will begin in this chapter. However, the fact that the particles in this chapter look or behave a certain way should not limit your imagination. Just because particle systems tend to look sparkly, fly forward, and fall with gravity doesn’t mean that those are the characteristics yours should have.

-

The focus here is on how to keep track of a system of many elements. What those elements do and how those elements look is up to you.

-

4.2 A Single Particle

-

Before I can get rolling on coding the system itself, I need to write the class to describe a single particle. The good news: I’ve done this already! The Mover class from Chapter 2 serves as the perfect template. A particle is an independent body that moves about the canvas. It has position, velocity, and acceleration, a constructor to initialize those variables, and functions to display() itself and update() its position.

+

No single particle is referenced in this code, and yet the result will be full of particles flying all over the canvas. This works because the details are hidden inside the ParticleSystem class, which itself holds references to lots of instances of the Particle class. Getting used to this technique of writing sketches with multiple classes, including classes that keep lists of instances of other classes, will prove very useful as you get to later chapters in this book.

+

Finally, working with particle systems is also an opportunity to tackle two other object-oriented programming techniques: inheritance and polymorphism. With the examples you’ve seen up until now, I’ve always used an array of a single type of object, like an array of movers or an array of oscillators. With inheritance and polymorphism, I’ll demonstrate a convenient way to use a single list to store objects of different types. This way, a particle system need not only be a system of a one kind of particle.

+

A Single Particle

+

Before I can get rolling on coding the particle system itself, I need to write a class to describe a single particle. The good news: I’ve done this already! The Mover class from Chapter 2 serves as the perfect template. A particle is an independent body that moves about the canvas, so just like a mover it has position, velocity, and acceleration variables; a constructor to initialize those variables; and methods to display() itself and update() its position.

class Particle {
-  //{!5} A “Particle” object is just another name for our “Mover.” It has position, velocity, and acceleration.
+  //{!5} A Particle object is just another name for a Mover. It has position, velocity, and acceleration.
   Particle(x, y) {
     this.position = createVector(x, y);
     this.acceleration = createVector();
@@ -52,34 +51,35 @@ 

4.2 A Single Particle

circle(this.position.x, this.position.y, 8); } }
-

This is about as simple as a particle can get. From here, I could take the particle in several directions. I could add the applyForce() function to affect the particle’s behavior (I’ll do precisely this in a future example). I could also add variables to describe color and shape, or load a p5.Image to draw the particle. For now, however, I’ll focus on adding just one additional detail: lifespan.

-

Some particle systems involve something called an emitter. The emitter is the source of the particles and controls the initial settings for the particles: position, velocity, and more. An emitter might emit a single burst of particles, or a continuous stream of particles, or both. The new feature here is that a particle born at the emitter does not live forever. If it were to live forever, the p5.js sketch would eventually grind to a halt as the amount of particles increases to an unwieldy number over time. As new particles are born, old particles need to be removed. This creates the illusion of an infinite stream of particles, and the performance of the sketch does not suffer. There are many different ways to decide when a particle is ready to be removed. For example, it could come into contact with another object, or it could leave the canvas. For this first Particle class, I’ll choose to add a lifespan variable that acts like a countdown timer. The timer will start at 255 and count down to 0, when the particle will be considered “dead.” The code for this in the Particle class as:

+

This is about as simple as a particle can get. From here, I could take the particle in several directions. I could add the applyForce() method to affect the particle’s behavior (I’ll do precisely this in a future example). I could also add variables to describe color and shape, or load a p5.Image to draw the particle in a more interesting way. For now, however, I’ll focus on adding just one additional detail: lifespan.

+

Some particle systems involve something called an emitter that serves as the source of the particles. The emitter controls the initial settings for the particles: position, velocity, and more. It might emit a single burst of particles or a continuous stream of particles, or some variation thereof. The new feature here is that particles born at the emitter can’t live forever. If they did, the p5.js sketch would eventually grind to a halt as the amount of particles increases to an unwieldy number over time. As new particles are born, old particles need to be removed, creating the illusion of an infinite stream of particles without hurting the performance of the sketch.

+

There are many different ways to decide when a particle is ready to be removed. For example, it could “die” when it comes into contact with another object, or when it leaves the frame of the canvas. For now, I’ll choose to give particles a lifespan variable that acts like a timer. It will start at 255 and count down to 0 as the sketch progresses, at which point the particle will be considered dead. Here’s the added code in the Particle class:

class Particle {
 
   constructor(x, y) {
     this.position = createVector(x, y);
     this.acceleration = createVector();
     this.velocity = createVector();
-    //{!1 .bold} A new variable to keep track of how long the particle has been “alive”. We start at 255 and count down for convenience.
+    //{!1 .bold} A new variable to keep track of how long the particle has been “alive.” It starts at 255 and counts down to 0.
     this.lifespan = 255;
   }
 
   update() {
     this.velocity.add(this.acceleration);
     this.position.add(this.velocity);
-    //{!1 .bold}  Lifespan decreases
+    //{!1 .bold}  Lifespan decreases.
     this.lifespan -= 2.0;
   }
 
   show() {
-    //{!2 .bold}  Since the life ranges from 255 to 0 it can be used also for alpha
+    //{!2 .bold}  Since the life ranges from 255 to 0, it can also be used also for alpha.
     stroke(0, this.lifespan);
     fill(175, this.lifespan);
     circle(this.position.x, this.position.y, 8);
   }
 }
-

The reason I chose to start the lifespan at 255 and count down to 0 is for convenience. With those values, I can assign lifespan as the alpha transparency for the circle as well. When the particle is “dead” it will also have faded away.

-

With the addition of the lifespan property, I’ll need one additional function—a function that can be queried (for a true or false answer) as to whether the particle is alive or dead. This will come in handy when writing the a class to manage the list of particles themselves. Writing this function is pretty easy. I just need to check whether the value of lifespan is less than 0. If it is, return true; otherwise, return false.

+

With lifespan ranging from 255 to 0, it can conveniently double as the alpha transparency for the circle representing the particle. This way, when the particle is dead, it will have literally faded away.

+

With the addition of the lifespan property, I’ll need one more method, one that can be queried (for a true or false answer) to determine whether the particle is alive or dead. This will come in handy when I write a separate class to manage the list of particles itself. Writing this method is pretty easy: I just need to check whether the value of lifespan is less than 0. If it is, return true; otherwise, return false.

  isDead() {
     //{!5} Is the particle still alive?
     if (this.lifespan < 0.0) {
@@ -88,14 +88,14 @@ 

4.2 A Single Particle

return false; } }
-

Or more simply, I can return the result of the boolean expression itself!

+

Even more simply, I can just return the result of the Boolean expression itself!

    isDead() {
       //{!1} Is the particle still alive?
       return (this.lifespan < 0.0);
     }
-

Before I get to the next step of making many particles, it’s worth taking a moment to make sure the particle works correctly and create a sketch with one single Particle object. Here is the full code below, with one small addition–giving the particle a random initial velocity as well as adding applyForce() to simulate gravity.

+

Before I get to the next step of making many particles, it’s worth taking a moment to confirm the particle works correctly. For that, I’ll create a sketch featuring a single Particle object at a time. Here’s the full code, with a few small additions: giving the particle a random initial velocity, as well as adding applyForce() to simulate gravity.

-

Example 4.1: A single particle

+

Example 4.1: A Single Particle

@@ -110,23 +110,22 @@

Example 4.1: A single particle

function draw() { background(255); - //{!2} Operating the single Particle + //{!2} Operating the single particle. particle.update(); particle.show(); - //{!2} Applying a gravity force + //{!2} Applying a gravity force. let gravity = createVector(0, 0.1); particle.applyForce(gravity); - //{!4} Checking the particle's state and making a new particle - if (p.isDead()) { + //{!4} Checking the particle's state and making a new particle. + if (particle.isDead()) { particle = new Particle(width / 2, 20); console.log("Particle dead!"); } } class Particle { - constructor(x,y) { this.position = createVector(x, y); //{!1 .offset-top} For demonstration purposes the Particle has a random velocity. @@ -142,43 +141,40 @@

Example 4.1: A single particle

this.acceleration.mult(0); } - applyForce(force) { - this.acceleration.add(force); - } - show() { stroke(0, this.lifespan); fill(0, this.lifespan); circle(this.position.x, this.position.y, 10); } - //{!3} Keeping the same physics model as with previous chapters + //{!3} Keeping the same physics model as in previous chapters. applyForce(force) { this.acceleration.add(force); } - //{!3} Is the Particle alive or dead? + //{!3} Is the particle alive or dead? isDead() { return (this.lifespan < 0.0); } } +

In this example, there is only one particle at a time for the sake of simplicity and testing. Each time the particle reaches the end of its lifespan, the particle variable is overwritten with a new instance of the Particle class. This effectively replaces the previous particle object. It's important to understand that the previous particle object isn't so much "deleted" as it is no longer accessible or used within the code. The sketch essentially forgets the old particle and starts anew with the freshly created one.

Exercise 4.1

-

Create a run() function in the Particle class that handles update(), show(), and applyForce(). What are the pros and cons of this approach?

+

Create a run() method in the Particle class that handles update(), show(), and applyForce(). What are the pros and cons of this approach?

Exercise 4.2

-

Add angular velocity (rotation) to the particle. Create your own non-circle particle design.

+

Add angular velocity (rotation) to the particle, and create your own non-circle particle design so the rotation is visible.

-

Now that I have a class to describe a single particle, it’s time for the next big step. How do you keep track of many particles, not knowing exactly how many particles you might have at any given time?

-

4.3 The Array

-

Thankfully, the wonderful JavaScript Array has all the functionality we need for managing a list of Particle objects. The the built-in JavaScript functions available in the JavaScript class Array allow for adding and removing particles and manipulating the arrays in all sorts of powerful ways. Although there are some cons to this approach, in order to keep the subsequent code examples more concise, I'm using a solution to Exercise 4.1 and assume a run() method that handles all of the particle's functionality. JavaScript Array Documentation.

+

An Array of Particles

+

Now that I have a class to describe a single particle, it’s time for the next big step: how can I keep track of many particles, without knowing in advance exactly how many I might have at any given time? The answer is the JavaScript array, a data structure that stores an arbitrarily long list of values. In JavaScript, an array is actually an object created from the Array class, and so it comes with many built-in methods. These methods supply all the functionality I need for maintaining a list of Particle objects, including adding particles, removing particles, or otherwise manipulating them. For a refresher on arrays, see the JavaScript Array Documentation on the MDN Web Docs.

+

As I bring arrays into the picture, I’ll use a solution to Exercise 4.1 and assume a Particle.run() method that manages all of an individual particle's functionality. While there are also some cons to this approach, it will keep the subsequent code examples more concise. To begin, I’ll use a for loop in setup() to populate an array with particles, then use another for loop in draw() to run each particle.

let total = 10;
-//{!1} Starting with an empty array
+//{!1} Start with an empty array.
 let particles = [];
 
 function setup() {
-  //{!3} This is what you’re probably used to, accessing elements on the array via an index and brackets—[].
+  //{!3} This is what you’re probably used to--accessing elements on the array via an index and brackets: [i].
   for (let i = 0; i < total; i++) {
     particles[i] = new Particle(width/2, height/2);
   }
@@ -190,20 +186,23 @@ 

4.3 The Array

particle.run(); } }
-

This last for loop demonstrates how to call functions on every element of an array by accessing each index. I initialize a variable i with value 0 and count up by 1, accessing each element of the array until I reach the end. However, this is a good time to mention the JavaScript for of loop, which is a bit more concise. The for of loop works with arrays as follows:

+

The for loop in draw() demonstrates how to call a method on every element of an array by accessing each index. I initialize a variable i to 0 and increment it by 1, accessing each element of the array until i hits particles.length and so reaches the end. As it happens, there are actually a few different ways to do this, and that’s something that I both love and hate about coding in JavaScript—there are so many different styles and options to consider. On the one hand, this makes JavaScript a highly flexible and adaptable language, but on the other hand, the abundance of choice can be overwhelming and lead to a lot of confusion when learning.

+

Let’s take a ride on the loop-de-loop rollercoaster of choices for iterating over an array:

+
    +
  • The traditional for loop, as just demonstrated. This is probably what you’re most used to, and it follows a similar syntax as other programming languages like Java and C.
  • +
  • The for...in loop. This kind of loop allows you to iterate over all the properties of an object. It's not particularly useful for arrays, so I won't cover it here.
  • +
  • The forEach() loop. This is a great one, and I encourage you to explore it! It’s an example of a higher-order function, something I’ll explain later in this chapter.
  • +
  • The for...of loop. This is the technique I’ll expand upon next. It provides a clean and concise syntax compared to the traditional for loop when working with arrays of objects.
  • +
+

Here's how the for...of loop looks:

function draw() {
   for (let particle of particles) {
     particle.run();
   }
 }
-

Let’s translate that. Say “each” instead of “let” and “in” instead of “of”:

-

“For each particle in particles, update and display that particle!”

-

I know. You cannot contain your excitement. I can’t. I know it’s not necessary, but I just have to type that again.

-
for (let particle of particles) {
-  particle.run();
-}
-

Simple, elegant, concise, lovely. Take a moment. Breathe. I have some bad news. Yes, I may love that for of loop and I will get to use it in examples. But not just yet.

-

The code I’ve written above doesn’t take advantage of the JavaScript's ability to remove elements from an array. I need to build an example that fits with the particle system scenario, where a continuous stream of particles are emitted, adding one new particle with each cycle through draw(). I’ll skip rehashing the Particle class code here, as it doesn’t need to change. What I have so far is:

+

To translate this code, say “each” instead of “let” and “in” instead of “of.” Putting it together, you get, “For each particle in particles, update and display that particle.”

+

Simple, elegant, concise, lovely. But before you get too excited about for...of loops, take a moment and breathe, because I have some bad news: they won’t work in every situation. Yes, I love for...of loops, and I’ll get to use them in some of the examples to come where I need to iterate over the items in an array, but not just yet. Ultimately, I want to create a continuous stream of particles, with one new particle added to the array each cycle through draw() and old particles removed from the array as they die. As you’ll soon see, this is where the for...of loop lets me down.

+

Creating a new particle every frame is easy: I can just call the Array class’s push() method during draw() to add a new Particle object to the end of the array. This eliminates the need to create any particles during setup().

let particles = [];
 
 function setup() {
@@ -215,43 +214,67 @@ 

4.3 The Array

//{!1 .offset-top} A new Particle object is added to the array every cycle through draw(). particles.push(new Particle(width / 2, 50)); - for (let particle of particles) { - particle.run(); + for (let i = 0; i < particles.length; i++) { + particles[i].run(); } }
-

Run the above code for a few minutes and you’ll start to see the frame rate slow down further and further until the program grinds to a halt (my tests yielded horrific performance after fifteen minutes). The issue of course is that I am adding more and more particles without removing any.

-

Fortunately, particles can be removed from the array referencing the index position of the particle to be removed. This is why I cannot use the enhanced for of loop; this loop provides no means for deleting elements while iterating. Instead, I can use the Array splice() method. (Yes, an array in JavaScript is actually an object created from the class Array with many methods!) The splice() method removes one or more elements from an array starting from a given index.

+

Run this code for a few minutes and you’ll start to see the frame rate slow down further and further until the program grinds to a halt. (My tests yielded horrific performance after 15 minutes.) The issue, of course, is that I’m adding more and more particles without removing any. To fix this, I can use the splice() method to get rid of particles as they die. It removes one or more elements from an array starting from a given index. And this is why I can’t use a for...of loop here; splice() needs a reference to the index of the particle being removed, but for...of loops don’t provide such a reference. I’m stuck using a regular for loop instead.

  for (let i = 0; i < particles.length; i++) {
+    //{!1} Improve readability by assigning array element to a variable
     let particle = particles[i];
     particle.run();
     if (particle.isDead()) {
-      //{!1} Remove one particle at index i
+      //{!1} Remove one particle at index i.
       particles.splice(i, 1);
     }
   }
-

Although the above code will run just fine (and the program will never grind to a halt), I have opened up a medium-sized can of worms. Whenever you manipulate the contents of an array while iterating through that very array, you can get into trouble. Take, for example, the following code.

+

Although this code will run just fine and never grind to a halt, I’ve opened up a medium-sized can of worms by trying to manipulate the contents of an array while iterating through that very same array. This is just asking for trouble. Take, for example, the following code:

  for (let i = 0; i < particles.length; i++) {
     let particle = particles[i];
     particle.run();
     //{!1 .offset-top} Adding a new Particle to the list while iterating?
     particles.push(new Particle(width / 2, 50));
   }
-

This is a somewhat extreme example (with flawed logic), but it proves the point. In the above case, for each particle in the list, I add a new particle to the list (and the length of the array increases). This will result in an infinite loop, as I can never increment past the size of the array!

-

While removing elements from the array during a loop doesn’t cause the sketch to crash (as it would with adding), the problem is perhaps more insidious in that it leaves no evidence. To discover the flaw I must first establish an important fact. When an element is removed from the array with splice(), all subsequent elements are shifted to the left. Figure 4.1 shows what happens when particle C (index 2) is removed. Particles A and B keep the same index, while particles D and E shift from 3 and 4 to 2 and 3, respectively.

+

This is a somewhat extreme example (with flawed logic), but it proves the point. For each particle in the list, this code adds a new particle to the list, and so the length of the array increases. This will result in an infinite loop, as I can never increment past the size of the array!

+

While removing elements from the array during a loop doesn’t cause the sketch to crash (as it would with adding), the problem is perhaps more insidious in that it leaves no evidence. To discover the flaw, I must first establish an important fact: when an element is removed from an array with splice(), all subsequent elements are shifted to the left. Figure 4.1 shows what happens when particle C (index 2) is removed. Particles A and B keep the same index, while particles D and E shift from 3 and 4 to 2 and 3, respectively.

- Figure 4.1: When an element is removed from an array, the elements shift to the left fill the empty spot. -
Figure 4.1: When an element is removed from an array, the elements shift to the left fill the empty spot.
+ Figure 4.1: When an element is removed from an array, the subsequent elements shift to the left to fill the empty spot. +
Figure 4.1: When an element is removed from an array, the subsequent elements shift to the left to fill the empty spot.
-

Let’s consider a counter i iterating over the elements of the array.

-
    -
  • when i = 0 → Check particle A → Do not delete
  • -
  • when i = 1 → Check particle B → Do not delete
  • -
  • when i = 2 → Check particle C → Delete!
  • -
  • Slide particles D and E back from slots 3 and 4 to 2 and 3
  • -
  • when i = 3 → Check particle E → Do not delete
  • -
-

Notice the problem? Particle D was never checked! When C was deleted from slot #2, D moved into slot #2, but i has already moved on to slot #3. This is not a total disaster, since particle D will get checked the next time around through draw(). Still, the expectation is that the code should iterate through every single element of the array. Skipping one is unacceptable!

-

There are two solutions to this problem. The first solution is to iterate through the array backwards. If you are sliding elements from right to left as elements are removed, it’s impossible to skip an element. Here’s how the code looks:

+

Consider what happens as counter i iterates over the elements of the array:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Value of iParticleAction
0particle ADon’t delete!
1particle BDon’t delete!
2particle CDelete! Slide particles D and E back from slots 3 and 4 to 2 and 3.
3particle EDon’t delete!
+

Notice the problem? Particle D is never checked! When C is deleted from slot 2, D moves into slot 2 in its place, but i has already moved on to slot 3. In practice, this may not be a total disaster, since particle D will get checked the next time around through draw(). Still, the expectation is that the code should iterate through every single element of the array. Skipping an element is unacceptable!

+

There are two solutions to this problem. The first solution is to iterate through the array backwards. Since elements slide from right to left as other elements are removed, it’s impossible to skip an element this way. Here’s how the code looks:

  //{!1 .bold} Looping through the list backwards
   for (let i = particles.length - 1; i >= 0; i--) {
     let particle = particles[i];
@@ -260,16 +283,16 @@ 

4.3 The Array

particles.splice(i, 1); } }
-

A second solution is to use something known as a “higher-order” function. A higher-order function is one that receives another function as an argument (or returns a function). In the case of JavaScript arrays there are many higher-order functions. A common one is sort() which takes as its argument a function that defines how to compare two elements of the array and then sorts the array according to that comparison. With the array of particles, I can make use of the higher order function filter(). filter() checks each item in the specified array and keeps only the item(s) where the given condition is true (removing those that return false).

+

A second solution is to use a higher-order function. This is a function that receives another function as an argument (or provides a function as its return value). In the case of JavaScript arrays, there are many higher-order functions. For example, a common one is sort(), which takes as its argument a function that defines how to compare two elements of the array and then sorts the array according to that comparison. With the array of particles, I can use filter(), a higher-order function that takes a function specifying some kind of condition as an argument, checks each item in an array for that condition, and keeps only the item(s) where the given condition is true (removing those items that return false).

  particles = particles.filter(function(particle) {
     //{!1} Keep particles that are not dead!
     return !particle.isDead();
   });
-

This is more commonly written using JavaScript's arrow notation. (To learn more, you can watch this higher-order functions and arrow notation video tutorial.)

+

This is more commonly written using JavaScript's arrow notation. (To learn more, check out my Coding Train video tutorial on higher-order functions and arrow notation.)

  particles = particles.filter(particle => !particle.isDead());
-

For the purposes of this book, I am going to stick with the splice() method, but I encourage you to explore writing your code with higher-order functions and arrow notation.

+

For the purposes of this book, I’m going to stick with the splice() method, but I encourage you to explore writing your code with higher-order functions and arrow notation.

-

Example 4.2: Array of particles

+

Example 4.2: An Array of Particles

@@ -295,10 +318,10 @@

Example 4.2: Array of particles

} } } -

4.4 A Particle Emitter

-

OK. Now I’ve done two things. I’ve written a class to describe an individual Particle object. I’ve conquered the array and used it to manage a list of many particles (with the ability to add and delete at will).

-

I could stop here. However, one additional step I can and should take is to write a class to describe the list of Particle objects itself, a “particle system.” Rather than “system”, however, I’m going to call this classEmitter, named for its functionality of “emitting” particles. The Emitter class will allow me to remove the bulky logic of looping through all particles from draw(), as well as open up the possibility of having multiple particle emitters.

-

If you recall the goal I set at the beginning of this chapter was to setup() and draw() without referencing any individual particles. Here it is again, only now with the naming convention of “emitter.”

+

You might be wondering why, instead of checking each particle individually, I don’t just remove the oldest particle after some period of time (determined by checking the frameCount or array length). In this example, that approach would actually work! I could even use a different array method called shift() which automatically removes the first element of an array. However, in many particle systems, other conditions or interactions may cause particles to “die” (reach the end of their lifespan) before others, even if they were created later. Checking isDead() in combination withsplice() is a nice comprehensive solution that offers flexibility in managing particles across a variety of scenarios.

+

A Particle Emitter

+

I’ve conquered the array and used it to manage a list of Particle objects, with the ability to add and delete particles at will. I could stop here and rest on my laurels, but there’s an additional step that I can and should take: writing a class describing the list of Particle objects itself. I’ll call this class, which represents the overall particle system, Emitter, after its functionality of “emitting” particles. The Emitter class will allow me to clean up the draw() function, removing the bulky logic of looping through all the particles. As an added bonus, it will also open up the possibility of having multiple particle emitters.

+

Recall that one of the goals I set at the beginning of this chapter was to write setup() and draw() without referencing any individual particles. In laying out that goal, I teased the possibility of a beautifully simple main sketch file. Here it is again, only now with the Emitter naming convention:

//{!1} Just one Particle Emitter!
 let emitter;
 
@@ -311,7 +334,7 @@ 

4.4 A Particle Emitter

background(255); emitter.run(); }
-

Let’s take the code from Example 4.2 and review a bit of object-oriented programming, looking at how each piece setup() and draw() can fit into the Emitter class.

+

To get to this point, look at each piece of setup() and draw() from Example 4.2 and think about how it can fit into the Emitter class instead. Nothing about the behavior of the code should change—the only difference is how it’s organized.

@@ -359,51 +382,52 @@

4.4 A Particle Emitter

I could also add new features to the particle system itself. For example, it might be useful for the Emitter class to keep track of an origin point where particles are born. The origin point could be initialized in the constructor.

-

Example 4.3: Single Particle Emitter

+

Example 4.3: A Single Particle Emitter

class Emitter {
-
   Emitter(x, y) {
-    //{!1 .bold} This particular ParticleSystem implementation includes an origin point where each Particle begins.
+    //{!1 .bold} This particular particle system implementation includes an origin point where each particle begins.
     this.origin = createVector(x, y);
     this.particles = [];
   }
 
   addParticle() {
-    //{!1 .bold}  The origin is passed to each Particle when it is added.
+    //{!1 .bold}  The origin is passed to each particle when it's added to the array.
     this.particles.add(new Particle(origin.x, origin.y));
-  }
+ } +} +

The example emitter is a static source of particles, which is a nice way to begin working with particle systems. However, it doesn't have to be that way! The emitter itself could have its own behaviors and experience physics, oscillate, react to user interaction, or any other kind of motion demonstrated in previous chapters. The particles could then emanate from various positions over time, creating trails and even more complex and intriguing patterns.

Exercise 4.3

-

What if the emitter moves? Can you emit particles from the mouse position or use the concepts of velocity and acceleration to move the system autonomously.

+

What if the emitter moves? Can you emit particles from the mouse position, or use the concepts of velocity and acceleration to move the system autonomously?

Exercise 4.4

-

Building off Chapter 3’s “Asteroids” example, use a particle system to emit particles from the ship’s “thrusters” whenever a thrust force is applied. The particles’ initial velocity should be related to the ship’s current direction.

+

Building off Chapter 3’s Asteroids example, use a particle system to emit particles from the ship’s “thrusters” whenever a thrust force is applied. The particles’ initial velocity should be related to the ship’s current direction.

-

4.5 A System of Emitters

-

Let’s take a moment to recap what I’ve covered so far. I described an individual Particle object. I also described a system of Particle objects, and this we call a “particle system” and organized the code into an Emitter class. I’ve defined a particle system as a collection of independent objects. But isn’t a particle system itself an object? If that’s the case (which it is), there’s no reason why I couldn’t also build a collection of many particle emitters, i.e. a system of systems.

-

This line of thinking could take you even further, and you might lock yourself in a basement for days sketching out a diagram of a system of systems of systems of systems of systems of systems. Of systems. After all, I could create a description of the world in this way. An organ is a system of cells, a human body is a system of organs, a neighborhood is a system of human bodies, a city is a system of neighborhoods, and so on and so forth. While this is an interesting road to travel down, it’s a bit beyond where I’d like to be right now. It is, however, quite useful to know how to write a sketch that keeps track of many particle systems, each of which keep track of many particles. Take the following scenario.

-

You start with a blank screen. 

+

A System of Emitters

+

So far I’ve described an individual particle and organized its code into a Particle class. I’ve also described a system of particles and organized the code into an Emitter class. This particle system is nothing more than a collection of independent Particle objects. But as an instance of the Emitter class, isn’t a particle system itself an object? If that’s the case (and it is), there’s no reason why I couldn’t also build a collection of many particle emitters: a system of systems!

+

I could take this line of thinking even further, locking myself in a basement for days and sketching out a diagram of a system of systems of systems of systems of systems of systems . . . until I get this whole system thing out of my, well, system. After all, I could describe the world in a similar way: an organ is a system of cells, a human body is a system of organs, a neighborhood is a system of human bodies, a city is a system of neighborhoods, and so on and so forth. I’m not ready to go quite that far just yet, but it would still be useful to look at how to write a sketch that keeps track of many particle systems, each of which keeps track of many particles.

+

Consider this scenario: you start with a blank screen. 

-
Click the mouse to create new emitters
+

You click the mouse and generate a particle system at the mouse’s position.

-
Click the mouse to create new emitters
+
-

Each time you click the mouse, a new particle system is created at the mouse’s position.

+

You keep clicking the mouse. Each time, another particle system springs up where you clicked.

-
Click the mouse to create new emitters
+
-

In Example 4.3, I stored a single reference to an Emitter object in the variable emitter.

+

How to do this? In Example 4.3, I stored a single reference to an Emitter object in the variable emitter.

let emitter;
 
 function setup() {
@@ -416,46 +440,47 @@ 

4.5 A System of Emitters

emitter.addParticle(); emitter.run(); }
-

For this new example, what I want to do instead is create an Array to keep track of multiple instances of emitters themselves. When the sketch begins, i.e. in setup(), the Array is empty.

+

Now I’ll call the variable emitters plural and make it an array so I can keep track of multiple Emitter objects. When the sketch begins, the array is empty.

-

Example 4.4: System of Systems

+

Example 4.4: A System of Systems

-
Click the mouse to create new emitters
+
The interactive version includes mouse interaction to create new emitters.
-
//{!1} This time, the type of thing we are putting in the array is a particle emitter itself!
-let emitter = [];
+
//{!1} This time, the type of thing we're putting in the array is a particle emitter itself!
+let emitters = [];
 
 function setup() {
   createCanvas(640, 240);
 }
-

Whenever the mouse is pressed, a new Emitter object is created and placed into the Array.

+

Whenever the mouse is pressed, a new Emitter object is created and placed into the array.

function mousePressed() {
   emitters.push(new Emitter(mouseX, mouseY));
 }
-

And in draw(), instead of referencing a single Emitter object, I can now iterate over all the emitters and call run() on each of them.

+

Then, in draw(), instead of referencing a single Emitter object, I now iterate over all the emitters and call run() on each of them.

function draw() {
   background(255);
-  //{!4} Since we aren’t deleting elements, we can use the for of loop!
+  //{!4} No emitters are removed a for...of loop can work heree!
   for (let emitter of emitters) {
     emitter.run();
     emitter.addParticle();
   }
 }
+

Notice how I am back to using a for...of loop since no elements are being removed from the emitters array.

Exercise 4.5

-

Rewrite Example 4.4 so that each particle system doesn’t live forever. When a particle system is empty (i.e. has no particles left), remove it from the array emitters.

+

Rewrite Example 4.4 so each particle system doesn’t live forever. Set a limit on how many particles an individual system can generate. Then, when a particle system is empty (has no particles left), remove it from the emitters array.

Exercise 4.6

-

Create a simulation of an object shattering into many pieces. How can you turn one large shape into many small particles? Can you create several large shapes on the screen that each shatter when clicked on?

+

Create a simulation of an object shattering into many pieces. How can you turn one large shape into many small particles? Can you create several large shapes on the screen that each shatter when clicked?

-

4.6 Inheritance and Polymorphism: An Introduction

-

You may have encountered the terms inheritance and polymorphism in your programming life before this book. After all, they are two of the three fundamental principles behind the theory of object-oriented programming (the other being encapsulation). If you’ve read other programming books, chances are it’s been covered. My beginner text, Learning Processing, has close to an entire chapter (#22) dedicated to these two topics.

-

Still, perhaps you’ve only learned about it in the abstract sense and never had a reason to really use inheritance and polymorphism. If this is true, you’ve come to the right place. Without these concepts, your ability to program a variety of particles and particle systems is extremely limited. (In the chapter 6, I’ll also demonstrate how understanding these topics will help you use physics libraries.)

-

Imagine the following. It’s a Saturday morning, you’ve just gone out for a lovely jog, had a delicious bowl of cereal, and are sitting quietly at your computer with a cup of warm chamomile tea. It’s your old friend So and So’s birthday and you’ve decided you’d like to make a greeting card with p5. How about some confetti for a birthday? Purple confetti, pink confetti, star-shaped confetti, square confetti, fast confetti, fluttery confetti, etc. All of these pieces of confetti with different appearances and different behaviors explode onto the screen at once.

-

What you’ve got is clearly a particle system—a collection of individual pieces of confetti (i.e. particles). You might be able to cleverly design a Particle class to have variables that store color, shape, behavior, and more. To create a variety of particles, you might initialize those variables with random values. But what if your particles are drastically different? This could become very messy, having all sorts of code for different ways of being a particle in the same class. Another option might be to do the following:

+

Inheritance and Polymorphism

+

Up to now, all the particles in my systems have been identical, with the same basic appearance and behaviors. Who says this has to be the case? By harnessing two fundamental principles of object-oriented programming, inheritance and polymorphism, I can create particle systems with significantly more variety and interest.

+

Perhaps you’ve encountered these two terms in your programming life before this book. For example, my beginner text, Learning Processing, has close to an entire chapter (#22) dedicated to them. Still, perhaps you’ve only learned about inheritance and polymorphism in the abstract and never had a reason to really use them. If that’s true, you’ve come to the right place. Without these techniques, your ability to program diverse particles and particle systems is extremely limited. (In Chapter 6, I’ll also demonstrate how understanding these topics will help you use physics libraries.)

+

Imagine it’s a Saturday morning. You’ve just gone out for a lovely jog, had a delicious bowl of cereal, and are sitting quietly at your computer with a cup of warm chamomile tea. It’s your old friend So and So’s birthday, and you’ve decided you’d like to make them a greeting card with p5.js. How about simulating some confetti? Purple confetti, pink confetti, star-shaped confetti, square confetti, fast confetti, fluttery confetti—all kinds of confetti, all with different appearances and different behaviors, explode onto the screen all at once.

+

What you’ve got is clearly a particle system: a collection of individual pieces (particles) of confetti. You might be able to cleverly redesign the Particle class to have variables that store color, shape, behavior, and more. To create a variety of particles, you might initialize those variables with random values. But what if some of your particles are drastically different? This could become very messy, having all sorts of code for different ways of being a particle in the same class. Another option might be to do the following:

class HappyConfetti {
 
 }
@@ -467,7 +492,7 @@ 

4.6 Inheritance and Pol class WackyConfetti { }

-

This is a nice solution: create three different classes to describe the different kinds of pieces of confetti that are part of your particle system. An Emitter constructor could then have some code to pick randomly from the three classes when filling the array. (Note that this probabilistic method is the same one I employed in the random walk examples in the Introduction.)

+

This is a nice solution: create three different classes to describe the different kinds of confetti that are part of your particle system. The Emitter constructor could then have some code to pick randomly from the three classes when filling the array. (Note that this probabilistic method is the same one I employed in the random walk examples in the Introduction.)

class Emitter {
   constructor(num) {
     this.particles = [];
@@ -484,13 +509,11 @@ 

4.6 Inheritance and Pol } } }

-

OK, I need to pause for a moment. You’ve done nothing wrong. All you wanted to do was wish your friend a happy birthday and enjoy writing some code. But while the reasoning behind the above approach is quite sound, there’s a problem.

-

Aren’t you going to be copying/pasting a lot of code between the different “confetti” classes?

-

Yes. Even though the kinds of particles are different enough to merit breaking them out into separate classes, there is still a ton of code that they will likely share. They’ll all have vectors to keep track of position, velocity, and acceleration; an update() function that implements the motion algorithm; and more.

-

This is where inheritance comes in. Inheritance allows you to write a class that inherits variables and functions from another class, all the while implementing its own custom features.

-

Let’s look at this concept in more detail and then create a particle system example that implements inheritance.

-

4.7 Inheritance Basics

-

Let’s take a different example, the world of animals: dogs, cats, monkeys, pandas, wombats, and sea nettles. I’ll start by coding a Dog class. A Dog object will have an age variable (an integer), as well as eat(), sleep(), and bark() functions.

+

Let me pause for a moment. You’ve done nothing wrong. All you wanted to do was wish your friend a happy birthday and enjoy writing some code. But while the reasoning behind this approach is quite sound, there’s a problem: aren’t you going to be copy-pasting a lot of code between the different confetti classes?

+

Yes, you probably will be. Even though the kinds of particles are different enough to merit breaking them out into separate classes, there’s still a ton of code that they’ll likely share. For example, they’ll all have vectors to keep track of position, velocity, and acceleration; an update() function that implements the motion algorithm; and more.

+

This is where inheritance comes in. Inheritance allows you to write a class that takes on (”inherits”) variables and methods from another class, while also implementing its own custom features. I’ll illustrate this concept in more detail, and then create a particle system example that implements inheritance.

+

Inheritance Basics

+

To demonstrate how inheritance works, I’ll take a different example from the world of animals: dogs, cats, monkeys, pandas, wombats, sea nettles, you name it. I’ll start by coding a Dog class. A Dog object will have an age variable (an integer), as well as eat(), sleep(), and bark() methods.

class Dog {
 
   constructor() {
@@ -509,10 +532,10 @@ 

4.7 Inheritance Basics

print("WOOF"); } }
-

Now, let’s move on to cats.

+

Now I’ll make a cat.

class Cat {
 
-  // Dogs and cats have the same variables (age) and functions (eat, sleep).
+  // Dogs and cats have many of the same variables (age) and methods (eat, sleep).
   constructor() {
     this.age = 0;
   }
@@ -535,13 +558,13 @@ 

4.7 Inheritance Basics

  • A dog is an animal and has all the properties of animals and can do all the things animals do. Also, a dog can bark.
  • A cat is an animal and has all the properties of animals and can do all the things animals do. Also, a cat can meow.
  • -

    Inheritance makes this all possible. With inheritance, classes can inherit properties (variables) and functionality (methods) from other classes. A Dog class is a child (subclass) of an Animal class. Children will automatically inherit all variables and functions from the parent (superclass), but can also include functions and variables not found in the parent. Like a phylogenetic "tree of life," inheritance follows a tree structure. Dogs inherit from mammals, which inherit from animals, and so on.

    +

    Inheritance makes this all possible, allowing Dog and Cat to be designated as children (subclasses) of the Animal class. Children automatically inherit all variables and methods from the parent (superclass), but they can also include methods and variables not found in the parent. Like a phylogenetic “tree of life,” inheritance follows a tree structure: dogs inherit from mammals, which inherit from animals, and so on.

    - Figure 4.2 -
    Figure 4.2
    + Figure 4.2: An inheritance tree +
    Figure 4.2: An inheritance tree
    -

    Here is how the syntax works with inheritance.

    -
    //{!1} The Animal class is the parent (or super) class.
    +

    Here’s how the syntax of inheritance works:

    +
    //{!1} The Animal class is the parent (or superclass).
     class Animal {
     
       constructor() {
    @@ -559,7 +582,7 @@ 

    4.7 Inheritance Basics

    } } -//{!1 .bold} The Dog class is the child (or sub) class, indicated by the code "extends Animal". +//{!1 .bold} The Dog class is the child (or subclass), indicated by the code "extends Animal". class Dog extends Animal { constructor() { //{!1 .bold} super() executes code found in the parent class. @@ -579,14 +602,10 @@

    4.7 Inheritance Basics

    print("MEOW!"); } }
    -

    This brings up two new terms:

    -
      -
    • extends – This keyword is used to specify a parent for the class being defined. Note that classes can only extend one class. However, classes can extend classes that extend other classes, i.e. Dog extends Animal, Terrier extends Dog. Everything is inherited all the way down the line.
    • -
    • super() – This calls the constructor in the parent class. In other words, whatever you do in the parent constructor, do so in the child constructor as well. Other code can be written into the constructor in addition to super(). super() can also receive arguments if there is a parent constructor defined with matching arguments.
    • -
    -

    A subclass can be expanded to include additional functions and properties beyond what is contained in the superclass. For example, let’s assume that a Dog object has a “hair color” variable in addition to age. The class would now look like this:

    +

    This code uses two new JavaScript features. First, notice the extends keyword, which specifies a parent for the class being defined. A subclass can only extend one superclass. However, classes can extend classes that extend other classes, for example Dog extends Animal, Terrier extends Dog. Everything is inherited all the way down the line from Animal to Terrier.

    +

    Second, notice the call to super() in the Dog and Cat constructors. This calls the constructor in the parent class. In other words, whatever you do in the parent constructor, do so in the child constructor as well. In this case it isn’t necessary, but super() can also receive arguments if there’s a parent constructor defined that takes matching arguments.

    +

    You can expand a subclass to include additional methods beyond those contained in the superclass. Here I’ve added the bark() method to Dog and the meow() method to Cat. You can also include additional code, besides the call to super(), in a subclass’s constructor to give that subclass extra variables. For example, let’s assume that in addition to age, a Dog object should have a haircolor variable. The class would now look like this:

    class Dog extends Animal {
    -
       constructor() {
         super();
         //{!1} A child class can introduce new variables not included in the parent.
    @@ -597,15 +616,15 @@ 

    4.7 Inheritance Basics

    print("WOOF!"); } }
    -

    Note how the parent constructor is called via super(), which sets the age to 0, but haircolor is set inside the Dog constructor itself. If a Dog object eats differently than a generic Animal object, parent functions can be overridden by rewriting the function inside the subclass.

    +

    Note how the parent constructor is first called via super(), which sets age to 0, and then haircolor is set inside the Dog constructor itself.

    +

    If a Dog object eats differently than a generic Animal object, the parent method can be overridden by creating a different definition for the method inside the subclass.

    class Dog extends Animal {
    -
       constructor() {
          super();
    -     this.haircolor = color(random(255));
    +     this.haircolor = color(210, 105, 30);
       }
     
    -  // A child can override a parent function if necessary.
    +  // A child can override a parent method if necessary.
       eat() {
         //{!1} A Dog's specific eating characteristics
         print("Woof! Woof! Slurp.")
    @@ -615,12 +634,11 @@ 

    4.7 Inheritance Basics

    print("WOOF!"); } }
    -

    But what if a dog eats the same way as a generic animal, just with some extra functionality? A subclass can both run the code from a parent class and incorporate custom code.

    +

    But what if a dog eats mostly the same way as a generic animal, just with some extra functionality? A subclass can both run the code from its parent class’s method and incorporate custom code.

    class Dog extends Animal {
    -
        constructor() {
          super();
    -     this.haircolor = color(random(255));
    +     this.haircolor = color(210, 105, 30);
        }
     
        eat() {
    @@ -634,56 +652,62 @@ 

    4.7 Inheritance Basics

    print("WOOF!"); } }
    -

    4.8 Particles with Inheritance

    -

    Now that I’ve covered the theory of inheritance and its syntax, it’s time write a working example in p5.js based on the Particle class.

    -

    Let’s review a basic Particle implementation, adapted from Example 4.1:

    +

    Similar to calling super() in the constructor, calling super.eat() inside the Dog class’s eat() method results in calling the Animal class’s eat() method. Then the subclass’s method definition can continue with any additional, custom code.

    +

    Particles with Inheritance

    +

    Now that I’ve covered the theory of inheritance and its syntax, I’m ready to write a working example of inheritance in p5.js based on the Particle class. First, take another look at a basic Particle implementation, adapted from Example 4.1:

    class Particle {
    -
    -  Particle(x, y) {
    +  constructor(x, y) {
         this.acceleration = createVector(0, 0);
         this.velocity = createVector(random(-1, 1), random(-2, 0));
         this.position = createVector(x, y);
    +    this.lifespan = 255.0;
       }
     
       run() {
    -    let gravity = createVector(0, 0.05);
    -    this.applyForce(gravity);
         this.update();
    -    this.display();
    +    this.show();
       }
     
       update() {
         this.velocity.add(this.acceleration);
         this.position.add(this.velocity);
    +    this.lifespan -= 2.0;
         this.acceleration.mult(0);
       }
    -
    +  
    +  applyForce(force) {
    +    this.acceleration.add(force);
    +  }
    +  
    +  isDead() {
    +    return (this.lifespan < 0);
    +  }
    +  
       show() {
    -    fill(0);
    +    fill(0, this.lifespan);
         circle(this.position.x, this.position.y, 8);
       }
     }
    -

    Next, I‘ll create a subclass that extends Particle (I’ll call it Confetti). It will inherit all the instance variables and methods from Particle. I‘ll also include a constructor and execute the code from the parent class with super().

    +

    The class has variables and methods that any participant in a particle system should have. Next, I’ll create a Confetti subclass that extends Particle. It will use super() to execute the code from the parent class’s constructor, and it will inherit most of the Particle class’s methods as well. However, I’ll give Confetti its own show() method, overriding that of its parent, so Confetti objects will be drawn as squares rather than circles.

    class Confetti extends Particle {
    -
       constructor(x, y) {
         super(x, y);
         // I could add variables for only Confetti here.
       }
     
    -  //{inline} There is no code here because update() is inherited from the parent.
    +  //{inline} There's no code here because methods lke update() are inherited from the parent.
     
    -  //{!6} Override the show method.
    +  //{!6} Override the show() method.
       show() {
         rectMode(CENTER);
         fill(0);
         square(this.position.x, this.position.y, 8);
       }
     }
    -

    Let’s make this a bit more sophisticated. Let’s say I want to have the Confetti particle rotate as it flies through the air. One option is to model angular velocity and acceleration as described in Chapter 3. For ease, however, I’ll implement something less formal.

    -

    I know a particle has an x position somewhere between 0 and the width of the canvas. What if I said: when the particle’s x position is 0, its rotation should be 0; when its x position is equal to the width, its rotation should be equal to 2\pi? Does this ring a bell? Whenever a value has one range that you want to map to another range, you can use the map() function (see the Introduction!)

    +

    Let’s make this a bit more sophisticated. Say I want to have each Confetti particle rotate as it flies through the air. One option is to model angular velocity and acceleration, as described in Chapter 3. For ease, however, I’ll implement something less formal.

    +

    I know a particle has an x position somewhere between 0 and the width of the canvas. What if I said: when the particle’s x position is 0, its rotation should be 0; when its x position is equal to the width, its rotation should be equal to 2\pi? Does this ring a bell? As discussed in the Introduction, whenever a value has one range that you want to map to another range, you can use the map() function.

    let angle = map(this.position.x, 0, width, 0, TWO_PI);
    -

    And just to give it a bit more spin, I can actually map the angle’s range from 0 to TWO_PI * 2. Here’s how this code fits into the show() function.

    +

    Here’s how this code fits into the show() function.

      show() {
         let angle = map(this.position.x, 0, width, 0, TWO_PI * 2);
     
    @@ -698,21 +722,20 @@ 

    4.8 Particles with Inheritance

    square(0, 0, 8); pop(); }
    +

    Just to give the confetti a bit more spin, I’ve actually mapped the angle’s range from 0 to TWO_PI * 2.

    Exercise 4.7

    -

    Instead of using map() to calculate theta, try modeling angular velocity and acceleration?

    +

    Instead of using map() to calculate angle, try modeling angular velocity and acceleration.

    Now that I have a Confetti subclass that extends the base Particle class, the next step is to also add Confetti objects to the array of particles defined in the Emitter class.

    -

    4.10 Particle Systems with Inheritance

    -

    Example 4.5: Particle system with inheritance

    +

    Example 4.5: A Particle System with Inheritance

    class Emitter {
    -
       constructor(x, y) {
         this.origin = createVector(x, y);
         //{!1 .bold} One list, for anything that is a Particle or extends Particle
    @@ -731,39 +754,39 @@ 

    Example 4.5: Particle syste run() { for (let i = this.particles.length - 1; i >= 0; i--) { - let p = this.particles[i]; - p.run(); - if (p.isDead()) { + let particle = this.particles[i]; + particle.run(); + if (particle.isDead()) { this.particles.splice(i, 1); } } } }

    +

    Can you spot how this example is also taking advantage of polymorphism? It's right there is how both Particle and Confetti objects co-mingle in the same particles array within the Emitter class. Thanks to the inheritance relationship, they can both be both considered the same type: Particle. Inheritance, along with polymorphism, enables a variety of particle types to be managed together in the one array regardless of their original class.

    Exercise 4.8

    Create a particle system with more than two “kinds” of particles. Try varying the behavior of the particles in addition to the design.

    -

    4.11 Particle Systems with Forces

    -

    So far in this chapter, I’ve focused on structuring code in an object-oriented way to manage a collection of particles. While I did keep theapplyForce() function, I took a couple shortcuts to keep things simple. Let’s now add back in a mass property changing the constructor and applyForce() function. (The rest of the class stays the same)

    +

    Particle Systems with Forces

    +

    So far in this chapter, I’ve focused on structuring code in an object-oriented way to manage a collection of particles. While I did keep theapplyForce() function in my Particle class, I took a couple shortcuts to keep the code simple. Now I’ll add a mass property back in, changing the constructor() and applyForce() methods in the process. (The rest of the class stays the same.)

    class Particle {
    -
       constructor(x, y) {
    -    //{!1} Now start with acceleration of 0,0.
         this.position = createVector(x, y);
         this.velocity = createVector(random(-1, 1),random(-2, 0));
    +    //{!1} Now start with acceleration of 0,0.
         this.acceleration = createVector(0, 0);
         this.lifespan = 255.0;
    -    //{!1} Add a mass property. Trying varying mass for different interesting results!
    +    //{!1} Add a mass property. Trying varying mass for different, interesting results!
         this.mass = 1;
       }
     
       applyForce(force) {
    -    // {!2} Dividing force by mass
    +    // {!2} Divide force by mass.
         const f = force.copy();
         f.div(this.mass);
         this.acceleration.add(f);
     }
    -

    Now that the Particle class is complete, I have a very important question to ask. Where should I call the applyForce() function? Where in the code is it appropriate to apply a force to a particle? In my view there’s no right or wrong answer; it really depends on the exact functionality and goals of a particular p5.js sketch. My “quick and dirty” solution in the previous examples was to just create a gravity force in the run() function itself of each particle.

    +

    Now that the Particle class is complete, I have a very important question to ask: where should I call the applyForce() method? Where in the code is it appropriate to apply a force to a particle? In my view, there’s no right or wrong answer; it really depends on the exact functionality and goals of a particular p5.js sketch. My quick and dirty solution in the previous examples was to just create and apply a gravity force in the run() method of each particle.

      run() {
         // {!2} Create a hard-coded vector and apply as a force
         let gravity = createVector(0, 0.05);
    @@ -771,7 +794,7 @@ 

    4.11 Particle Systems with Forces

    this.update(); this.show(); }
    -

    I’d like to consider a more generic solution that would likely apply to more broadly and allow different forces to be applied to individual particles in a system. For example. what if I were to apply a force globally every time through draw() to all particles.

    +

    I’d like to now consider a broader, more generic solution that will allow different forces to be applied to individual particles in a system. For example, what if I were to apply a force globally every time through draw() to all particles globally?

    function draw() {
       background(100);
     	
    @@ -780,27 +803,26 @@ 

    4.11 Particle Systems with Forces

    emitter.addParticle(); emitter.run(); }
    -

    Well, it seems there‘s a small problem. applyForce() is a method written inside the Particle class, but there is no reference to the individual particles themselves, only the Emitter object emitter. Since I want all particles to receive the force, however, I can pass the force to the emitter which is responsible for managing all the individual particles.

    +

    Well, it seems there’s a small problem. The applyForce() method is written inside the Particle class, but there’s no reference to the individual particles themselves, only to emitter, the Emitter object. Since I want all particles to receive the force, however, I can pass the force to the emitter and let it manage all the individual particles.

    function draw() {
       background(255);
     
       let gravity = createVector(0, 0.1);
    -  //{!1} Applying a force to the emitter
    +  //{!1} Apply a force to the emitter.
       emitter.applyForce(gravity);
       emitter.addParticle();
       emitter.run();
     }
    -

    Of course, if I call a new function on the Emitter object in draw(), I then have to define that function in the Emitter class. Let’s describe the job that function needs to perform: receive a force as a p5.Vector and apply that force to all the particles.

    -

    Now in code:

    +

    Of course, if I call an applyForce() method on the Emitter object in draw(), I then have to define that method in the Emitter class. In English, that method needs to be able to receive a force as a p5.Vector and apply that force to all the particles. Translating that into code:

      applyForce(force) {
         for (let particle of this.particles) {
           particle.applyForce(force);
         }
       }
    -

    It almost seems silly to write this function. What the code says is “apply a force to a particle system so that the system can apply that force to all of the individual particles.” Nevertheless, it’s actually quite reasonable. After all, the emitter is in charge of managing the particles, so if you want to talk to the particles, you’ve got to talk to them through their manager. (Also, here’s a chance for the enhanced loop since no particles are being deleted!)

    -

    Here is the full example (assuming the existence of the Particle class written above; no need to include it again since nothing has changed):

    +

    It almost seems silly to write this method. The code is essentially saying, “Apply a force to a particle system so that the system can apply that force to all of the individual particles.” Although this may sound a bit roundabout, this approach is actually quite reasonable. After all, the emitter is in charge of managing the particles, so if you want to talk to the particles, you’ve got to talk to them through their manager. (Also, here’s a chance to use an enhanced for...of loop, since no particles are being deleted!)

    +

    Here’s the full example, including this change. (The code assumes the existence of the Particle class written earlier; no need to show it again, since nothing has changed.)

    -

    Example 4.6: Particle system with forces

    +

    Example 4.6: A Particle System with Forces

    @@ -826,7 +848,6 @@

    Example 4.6: Particle system wit class Emitter { - constructor(x, y) { this.origin = createVector(x, y); this.particles = []; @@ -837,7 +858,7 @@

    Example 4.6: Particle system wit } applyForce(force) { - //{!3} Using a for of loop to apply the force to all particles + //{!3} Using a for...of loop to apply the force to all particles for (let particle of this.particles) { particle.applyForce(force); } @@ -854,23 +875,24 @@

    Example 4.6: Particle system wit } } }

    -

    4.12 Particle Systems with Repellers

    -

    What if I wanted to take this example one step further and add a Repeller object—the inverse of the Attractor object covered in Chapter 2 that pushes any particles away that get close? This requires a bit more sophistication because, unlike the gravity force, each force an attractor or repeller exerts on a particle must be calculated for each particle.

    +

    While this example only demonstrates a hard-coded gravity force, it's worth considering how other forces from previous chapters, such as wind or drag, could come into play. You could also experiment with varying how and when forces are applied. Instead of a force acting on particles continuously every frame, what if a force only kicked in under certain conditions or at specific moments? There's a lot of room here for creativity and interactivity in how you design your particle systems!

    +

    Particle Systems with Repellers

    +

    What if I wanted to take my code one step further and add a Repeller object—the inverse of the Attractor object covered in Chapter 2—that pushes any particles away that get too close? This requires a bit more sophistication than uniformly applying the gravity force, because the force the repeller exerts on a particular particle is unique and must be calculated separately for each particle (see Figure 4.3).

    - Figure 4.3: Gravity force—vectors are all identical  + Figure 4.3: On the left, a gravity force where vectors are all identical; on the right, an repeller force where all vectors point in different directions
    - Figure 4.4: Attractor force—vectors are all different  +
    -
    Figure 4.3: Gravity force—vectors are all identical  Figure 4.4: Attractor force—vectors are all different 
    +
    Figure 4.3: On the left, a gravity force where vectors are all identical; on the right, an repeller force where all vectors point in different directions
    -

    Let’s start by examining how I might incorporate a new Repeller object into a particle system example. I’m going to need two major additions to the code:

    +

    To incorporate a new Repeller object into a particle system sketch. I’m going to need two major additions to the code:

    1. A Repeller object (declared, initialized, and displayed).
    2. -
    3. A function that passes the Repeller object into the particle emitter so that it can apply a force to each particle object.
    4. +
    5. A method that passes the Repeller object into the particle emitter so that the repeller can apply a force to each particle object.
    let emitter;
     //{!1 .bold} New thing: we declare a Repeller object.
    @@ -890,7 +912,7 @@ 

    4.12 Particle Systems with Repeller let gravity = createVector(0, 0.1); emitter.applyForce(gravity); - //{!1 .bold} New thing: a function to apply a force from a repeller. + //{!1 .bold} New thing: a method to apply a force from a repeller. emitter.applyRepeller(repeller); emitter.run(); @@ -912,7 +934,7 @@

    4.12 Particle Systems with Repeller circle(this.position.x, this.position.y, this.r * 2); } }

    -

    The more difficult question is, how do I write the applyRepeller() function? Instead of passing a p5.Vector into a function like with applyForce(), I need to instead pass a Repeller object into applyRepeller() and ask that function to do the work of calculating the force between the repeller and all particles. Let’s look at both of these functions side by side.

    +

    The more difficult task is writing the applyRepeller() method. Instead of passing a p5.Vector object as an argument, like with applyForce(), I need to instead pass a Repeller object into applyRepeller() and ask that method to do the work of calculating the force between the repeller and each particle. Take a look at both of these methods side by side.

    @@ -938,32 +960,32 @@

    4.12 Particle Systems with Repeller

    -

    The functions are almost identical. There are only two differences. One I mentioned before—a Repeller object is the argument, not a p5.Vector. The second difference is the more important one. I must calculate a custom p5.Vector force for each and every particle and apply that force. How is that force calculated? In a function called repel(), the inverse of the attract() function from the Attractor class.

    +

    The methods are almost identical, but there are only two differences. I mentioned one of them before: the argument to applyRepeller() is a Repeller object, not a p5.Vector object. The second difference is the more important one. I must calculate a custom p5.Vector force for each and every particle and apply that force. How is that force calculated? In a Repeller class method called repel(), the inverse of the attract() method from the Attractor class.

      // All the same steps to calculate an attractive force, only pointing in the opposite direction.
       repel(particle) {
    -    // 1) Get force direction.
    +    // 1) Get the force direction.
         let force = p5.Vector.sub(this.position, particle.position);
    -    //{!2} 2) Get distance (constrain distance).
    +    //{!2} 2) Get and constrain the distance.
         let distance = force.mag();
         distance = constrain(distance, 5, 50);
    -    // 3) Calculate magnitude, use a "power" variable for G
    +    // 3) Calculate the magnitude, using a "power" variable for G.
         let strength = -1 * this.power / (distance * distance);
    -    // 4) Make a vector out of direction and magnitude.
    +    // 4) Make a vector out of the direction and magnitude.
         force.setMag(strength);
         return force;
       }

    Notice how throughout this entire process of adding a repeller to the environment, I never once considered editing the Particle class itself. A particle doesn’t actually have to know anything about the details of its environment; it simply needs to manage its position, velocity, and acceleration, as well as have the ability to receive an external force and act on it.

    -

    Now look at this example in its entirety, again leaving out the Particle class, which hasn’t changed.

    +

    I’m now ready to write this example in its entirety, again leaving out the Particle class, which hasn’t changed.

    -

    Example 4.7: ParticleSystem with repeller

    +

    Example 4.7: A Particle System with a Repeller

    -
    // One ParticleSystem
    +
    // One particle system.
     let emitter;
    -//{!1} One repeller
    +//{!1} One repeller.
     let repeller;
     
     function setup() {
    @@ -975,10 +997,10 @@ 

    Example 4.7: ParticleSystem wit function draw() { background(100); emitter.addParticle(); - // We’re applying a universal gravity. + // Apply a universal gravity. let gravity = createVector(0, 0.1); emitter.applyForce(gravity); - //{!1} Applying the repeller + //{!1} Apply the repeller. emitter.applyRepeller(repeller); emitter.run(); @@ -1032,7 +1054,6 @@

    Example 4.7: ParticleSystem wit show() { stroke(0); - strokeWeight(2); fill(127); circle(this.position.x, this.position.y, 32); } @@ -1047,65 +1068,65 @@

    Example 4.7: ParticleSystem wit return force; } }

    +

    You may have noticed the addition of the power variable in the Repeller class, which controls the strength of the repulsion force exerted. This property becomes especially interesting when you have multiple attractors and repellers, each with different power values. For example, strong attractors and weak repellers might result in particles clustering around the attractors, while more powerful repellers might reveal patterns reminiscent of paths or channels between them. These are hints of what is to come in Chapter 5 when I’ll further explore the concept of a "complex system."

    Exercise 4.9

    -

    Expand the above example to include more than one repeller (using an array).

    +

    Expand Example 4.7 to include multiple repellers and attractors!

    Exercise 4.10

    -

    Create a particle system in which each particle responds to every other particle. (Note that I’ll be going through this in detail in Chapter 6.)

    +

    Create a particle system in which each particle responds to every other particle. (I’ll explain how to do this in detail in Chapter 5.)

    -

    4.13 Image Textures and Additive Blending

    -

    Even though this book is almost exclusively focused on behaviors and algorithms rather than computer graphics and design, I don’t think I would be able to live with myself if I finished a discussion of particle systems and never once looked at an example that involves texturing each particle with an image. The way you a particle is rendered is a key piece of the puzzle in designing certain types of visual effects.

    -

    Consider a smoke simulation. Take a look at the following two images:

    +

    Image Textures and Additive Blending

    +

    Even though this book is almost exclusively focused on behaviors and algorithms rather than computer graphics and design, I don’t think I would be able to live with myself if I finished a discussion of particle systems without ever once looking at an example that involves texturing each particle with an image. After all, the way you render a particle is a key piece of the puzzle in designing certain types of visual effects. For example, compare the two smoke simulations shown in Figure 4.4.

    - Figure 4.5 White circles on the left, fuzzy images with transparency on the right.  + Figure 4.4 White circles on the left, fuzzy images with transparency on the right 
    -
    Figure 4.5 White circles on the left, fuzzy images with transparency on the right. 
    +
    Figure 4.4 White circles on the left, fuzzy images with transparency on the right 
    -

    Both of these images were generated from identical algorithms. The only difference is that a white circle is drawn in image A for each particle and a “fuzzy” blob is drawn for each in B.

    +

    Both of these images were generated from identical algorithms. The only difference is that each particle is drawn as a plain white circle in the image on the left, whereas each particle is drawn as a fuzzy blob in the image on the right. Figure 4.5 shows the two kinds of particle textures.

    - Figure 4.5 Two image textures, one all white circle, one fuzzy circle that fades out towards the edges. -
    Figure 4.5 Two image textures, one all white circle, one fuzzy circle that fades out towards the edges.
    + Figure 4.5 Two image textures: an all-white circle, or a fuzzy circle that fades out towards the edges +
    Figure 4.5 Two image textures: an all-white circle, or a fuzzy circle that fades out towards the edges
    -

    The good news here is that you get a lot of bang for very little buck. Before you write any code, however, you’ve got to make your image texture! I recommend using PNG format, as p5.js will retain the alpha channel (i.e. transparency) when drawing the image, which is needed for blending the texture as particles layer on top of each other. Once you’ve made a PNG and deposited it in your sketch’s “data” folder, you are on your way with just a few lines of code.

    +

    Using an image to texture your particles can give you a lot of bang for very little buck in terms of the realism of the visual effect. Before you write any code, however, you have to make your image texture. I recommend using the PNG format, as p5.js will retain the alpha channel (transparency) when drawing the image, which is needed for blending the texture as particles are layered on top of each other. There are numerous ways to create these textures: you can indeed make them programmatically within p5.js (I'll include an example with the book's supplementary materials), but you can also other open-source or commercial graphic editing tools.

    +

    Once you’ve made a PNG and deposited it in your sketch’s data folder, you only need a few extra lines of code.

    -

    Example 4.8: Image texture particle system

    +

    Example 4.8: An Image Texture Particle System

    -

    First, we’ll need to a variable to store the image and load it in preload().

    +

    First, declare a variable to store the image.

    let img;
    -

    Load the image in preload().

    +

    Then, load the image in preload().

    function preload() {
       //{!1} Loading the PNG
       img = loadImage("texture.png");
     }
    -

    And when it comes time to draw the particle, use the image variable instead of drawing an ellipse or rectangle.

    +

    Next, when it comes time to draw the particle, use the img variable instead of drawing a circle or rectangle.

      show() {
         imageMode(CENTER);
         //{!1} Note how tint() is the image equivalent of shape’s fill().
         tint(255, this.lifespan);
         image(img, this.position.x, this.position.y);
       }
    -

    Incidentally, this smoke example is a nice excuse to revisit the Gaussian distributions from the Introduction. To make the smoke appear a bit more realistic, instead of launching allow the particles in a purely random direction initial velocity vectors mostly around a mean value (with a lower probability of outliers) can produce an effect that appears less fountain-like and more like smoke (or fire).

    -

    Using randomGaussian() velocities can be initialized as follows:

    +

    This smoke example is also a nice excuse to revisit the Gaussian distributions from the Introduction. Instead of launching the particles in a purely random direction, which produces a fountain-like effect, the result will appear more smoke-like if the initial velocity vectors cluster mostly around a mean value, with a lower probability of outlying velocities. Using the randomGaussian() function, the particle velocities can be initialized as follows:

        let vx = randomGaussian(0, 0.3);
         let vy = randomGaussian(-1, 0.3);
    -    let vel = createVector(vx, vy);
    -

    Finally, in this example, a wind force is applied to the smoke mapped from the mouse’s horizontal position.

    + this.velocity = createVector(vx, vy);
    +

    Finally, in this example I’ve applied a wind force to the smoke, mapped from the mouse’s horizontal position.

    function draw() {
       background(0);
     
    -  //{!2} Wind force direction based on mouseX.
    +  //{!2} The wind force direction is based on mouseX.
       let dx = map(mouseX, 0, width, -0.2, 0.2);
       let wind = createVector(dx, 0);
       emitter.applyForce(wind);
    @@ -1115,43 +1136,45 @@ 

    Example 4.8: Image texture par emitter.addParticle(); } }

    +

    In addition to designing the texture itself, you should also consider its resolution. Rendering a large number of high-resolution textures can significantly impact performance, especially if the code is required to resize them dynamically. The ideal scenario for optimal speed is to size the texture precisely to the resolution you intend to draw the particles in the canvas. If you do want particles of varying sizes, then ideally you should create the texture at the maximum size of any particle.

    Exercise 4.11

    -

    Try creating your own textures for different types of effects. Can you make it look like fire, instead of smoke?

    +

    Try creating other textures for different types of effects. Can you make your particle system look like fire instead of smoke?

    Exercise 4.12

    -

    Use an array of images and assign each Particle object a different image. Even though single images are drawn by multiple particles, make sure you don’t call loadImage() any more than you need to (once for each image file!)

    +

    Use an array of images and assign each Particle object a different image. Multiple particles will be drawing the same image, so make sure you don’t call loadImage() any more than you need to. (Once for each image file is enough!)

    -

    Finally, it’s worth noting that there are many different algorithms for blending colors in computer graphics. These are often referred to as “blend modes.” By default, when you draw something on top of something else in p5, you only see the top layer—this is the default “blend” behavior. When pixels have alpha transparency (as they do in the smoke example), p5.js uses an alpha compositing algorithm that combines a percentage of the background pixels with the new foreground pixels based on those alpha values themselves.

    -

    However, it’s possible to draw using other blend modes, and a much loved blend mode for particle systems is “additive.” Additive blending in Processing with particle systems was pioneered by Robert Hodgin in his famous particle system and forces exploration, Magnetosphere, which later became the iTunes visualizer. For more see: Magnetosphere.

    -

    Additive blending is a simple blend algorithm that involves adding the pixel values of one layer to another, capping all values at 255. This results in a space-age glow effect, as the colors get brighter and brighter the more layers that add together.

    +

    Finally, it’s worth noting that there are many different algorithms for blending colors in computer graphics. These are often referred to as blend modes. Normally, when you draw something on top of something else in p5.js, you only see the top layer—this is the default “blend” behavior of not blending at all. Meanwhile, when pixels have alpha transparency values (as they do in the smoke example), p5.js automatically uses an alpha compositing algorithm that combines a percentage of the background pixels with the new foreground pixels, based on those alpha values themselves.

    +

    However, it’s possible to draw using other blend modes. For example, a much-loved technique for particle systems is additive blending. This mode was pioneered by Robert Hodgin in his famous particle system and forces exploration, Magnetosphere, which later became the iTunes visualizer.

    +

    Additive blending is a simple blend algorithm that involves adding the pixel values of one layer to another, capping all values at 255. This results in a space-age glow effect, with the colors getting brighter and brighter as more layers are added together.

    -

    Example 4.9: Additive blending

    +

    Example 4.9: Additive Blending

    -
    function setup() {
    -  createCanvas(640, 240);
    -}
    -

    Then, before you go to draw anything, you set the blend mode using blendMode():

    +

    Before you go to draw anything, set the blend mode using blendMode():

    function draw() {
    -  //{!1} Additive blending
    +  //{!1} Use additive blending.
       blendMode(ADD);
     
    -  //{!1} Also need to clear() since the background is added and does not cover what was previously drawn.
    +  //{!1} Call clear(), since the background is added and doesn't cover what was previously drawn.
       clear();
     
    -  // Also note that the “glowing” effect of additive
    -  // blending will not work with a white
    -  // (or very bright) background.
    +  // {!} The “glowing” effect of additive blending won't work with a white (or very bright) background.
       background(0);
     
       // {inline} All other particle stuff goes here.
     
     }
    +

    Additive blending and particle systems provide an opportunity to discuss renderers in computer graphics. A renderer is the part of the code that’s responsible for drawing on the screen. The p5.js library's default renderer, which you’ve so far been using without realizing it, is built on top of the standard 2D drawing and animation renderer included in modern web browsers. However, there’s an additional rendering option called WEBGL. WebGL, which stands for Web Graphics Library, is a browser-based high-performance renderer for both 2D and 3D graphics. It utilizes additional features available from your computer's graphics card. To enable it, add a third argument to createCanvas().

    +
    function setup() {
    +  // Enabling the WEBGL renderer
    +  createCanvas(640, 240, WEBGL);
    +}
    +

    Typically, the WebGL renderer is only necessary if you’re drawing 3D shapes in your p5.js sketch. However, even for 2D sketches, the WebGL renderer can be useful in some cases—depending on your computer's hardware and the specific details of your sketch, it can significantly improve drawing performance. A particle system (especially one with additive blending enabled) is exactly one of these scenarios where many more particles can be drawn without slowing down the sketch in WEBGL mode. However,WEBGL mode also changes how some functions behave, and it may alter the quality of the rendering itself. Additionally, older devices or browsers may not support the renderer, in which case your sketch won’t work for as wide an audience.

    Exercise 4.13

    Use tint() in combination with additive blending to create a rainbow effect.

    diff --git a/content/05_steering.html b/content/05_steering.html index cf1fc7f3..c8097a18 100644 --- a/content/05_steering.html +++ b/content/05_steering.html @@ -5,7 +5,7 @@

    Chapter 5. Autonomous Agents

    — Valentino Braitenberg

    Let’s think for a moment. Why are you here? The nature of code, right? What have I been demonstrating so far? Inanimate objects. Lifeless shapes sitting in canvas that flop around when affected by forces in their environment. What if you could breathe life into those shapes? What if those shapes could live by their own rules? Can shapes have hopes and dreams and fears? This is the domain on this chapter—autonomous agents.

    -

    5.1 Forces from Within

    +

    Forces from Within

    The term autonomous agent generally refers to an entity that makes its own choices about how to act in its environment without any influence from a leader or global plan. For the context here, “acting” will mean moving. This addition is a significant conceptual leap. Instead of a box sitting on a boundary waiting to be pushed by another falling box, I would like to now design a box that has the ability and “desire” to leap out of the way of that other falling box, if it so chooses. While the concept of forces that come from within is a major shift in design thinking, the code base will barely change, as these desires and actions are simply that—forces.

    Here are three key components of autonomous agents to keep in mind as I build the examples.

      @@ -14,7 +14,7 @@

      5.1 Forces from Within

    • An autonomous agent has no leader. This third principle is something I care a little less about for the context here. After all, if you are designing a system where it makes sense to have a leader barking commands at various entities, then that’s what you’ll want to implement. Nevertheless, many of these examples will have no leader for an important reason. Towards the end of this chapter, I'll examine group behaviors and look at designing collections of autonomous agents that exhibit the properties of complex systems— intelligent and structured group dynamics that emerge not from a leader, but from the local interactions of the elements themselves.

    In the late 1980s, computer scientist Craig Reynolds developed algorithmic steering behaviors for animated characters. These behaviors allowed individual elements to navigate their digital environments in a “lifelike” manner with strategies for fleeing, wandering, arriving, pursuing, evading, and more. Used in the case of a single autonomous agent, these behaviors are fairly simple to understand and implement. In addition, by building a system of multiple characters that steer themselves according to simple, locally based rules, surprising levels of complexity emerge. The most famous example is Reynolds’s “boids” model for “flocking/swarming” behavior.

    -

    5.2 Vehicles and Steering

    +

    Vehicles and Steering

    Now that I‘ve discussed the core concepts behind autonomous agents, it‘s time to begin writing the code. There are many places where I could start. Artificial simulations of ant and termite colonies are fantastic demonstrations of systems of autonomous agents. (For more on this topic, I encourage you to read Turtles, Termites, and Traffic Jams by Mitchel Resnick.) However, I want to begin by examining agent behaviors that build on the work in the first four chapters of this book: modeling motion with vectors and forces. And so it’s time to once again rename the class that describes an entity moving about a canvas. What was once Walker became Moverwhich became Particle . In his 1999 paper “Steering Behaviors for Autonomous Characters,” Reynolds uses the word “vehicle” to describe his autonomous agents, so I will follow suit calling the class Vehicle.

     class Vehicle {
     
    @@ -36,7 +36,7 @@ 

    Why Vehicle?

  • Locomotion. For the most part, I’m going to ignore this third layer. In the case of fleeing zombies, the locomotion could be described as “left foot, right foot, left foot, right foot, as fast as you can.” In a canvas, however, a rectangle or circle or triangle’s actual movement across a window is irrelevant given that it’s all an illusion in the first place. Nevertheless, this isn’t to say that you should ignore locomotion entirely. You will find great value in thinking about the locomotive design of your vehicle and how you choose to animate it. The examples in this chapter will remain visually bare, and a good exercise would be to elaborate on the animation style —could you add spinning wheels or oscillating paddles or shuffling legs?
  • Ultimately, the most important layer for you to consider is #1—Action Selection. What are the elements of your system and what are their goals? In this chapter, I am going to cover a series of steering behaviors (i.e. actions): seek, flee, follow a path, follow a flow field, flock with your neighbors, etc. It’s important to realize, however, that the point of understanding how to write the code for these behaviors is not because you should use them in all of your projects. Rather, these are a set of building blocks, a foundation from which you can design and develop vehicles with creative goals and new and exciting behaviors. And even though the examples will be highly literal in this chapter (follow that pixel!), you should allow yourself to think more abstractly (like Braitenberg). What would it mean for your vehicle to have “love” or “fear” as its goal, its driving force? Finally (and I’ll address this later in the chapter), you won’t get very far by developing simulations with only one action. Yes, the first example will be “seek a target.” But for you to be creative—to make these steering behaviors your own—it will all come down to mixing and matching multiple actions within the same vehicle. So view these examples not as singular behaviors to be emulated, but as pieces of a larger puzzle that you will eventually assemble.

    -

    5.3 The Steering Force

    +

    The Steering Force

    I could entertain you by discussing the theoretical principles behind autonomous agents and steering as much as you like, but we won’t get anywhere without first understanding the concept of a steering force. Consider the following scenario: a vehicle with a current velocity seeks a target. And let’s think of the vehicle as a bug-like creature who desires to savor a delicious strawberry.

    Figure 5.1 A vehicle with a velocity and a target. @@ -54,8 +54,10 @@

    5.3 The Steering Force

    Assuming a p5.Vector target, I then have:

    let desired = p5.Vector.sub(target, position);

    But this there is more to the story here. What if the canvas is high-resolution and the target is thousands of pixels away? Sure, the vehicle might desire to teleport itself instantly to the target position with a massive velocity, but this won’t make for an effective animation. I’ll restate the desire as follows:

    -

    The vehicle desires to move towards the target at maximum speed.

    -

    In other words, the vector should point from position to target with a magnitude equal to maximum speed (i.e. the fastest the vehicle can go). So first, I’ll need to make sure to add a property to the Vehicle class for maximum speed itself.

    +

    The vehicle desires to move towards the target at maximum speed.

    +

    In other words, the vector should point from the vehicle's current position to the target position, with a magnitude equal to the maximum speed of the vehicle. The concept of maximum speed was introduced in Chapter 1 to ensure that the speed remained within a reasonable range. However, I did not always use it in the succeeding chapters. In Chapter 2, other forces such as friction and drag kept the speed in check, while in Chapter 3, oscillation was caused by opposing forces that keep the speed limited. In this chapter, maximum speed is a key parameter for controlling the behavior of a steering agent, so it will be included in all the examples.

    +

    While I encourage you to consider how other forces such as friction and drag could be combined with steering behaviors, I am going to focus only on steering forces for the time being. So it makes sense to include the concept of maximum speed as a limiting factor in the force calculation.

    +

    So first, I’ll need to make sure to add a property to the Vehicle class for maximum speed itself.

    class Vehicle {
     
       constructor(){
    @@ -198,7 +200,7 @@ 

    Exercise 5.2

    Exercise 5.3

    Create a sketch where a vehicle’s maximum force and maximum speed do not remain constant, but vary according to environmental factors.

    -

    5.4 Arriving Behavior

    +

    Arriving Behavior

    After working for a bit with the seeking behavior, you probably are asking yourself, “What if I want the vehicle to slow down as it approaches the target?” Before I can even begin to answer this question, I should look at the reasons behind why the seek behavior causes the vehicle to fly past the target so that it has to turn around and go back. Let’s consider the brain of a seeking vehicle. What is it thinking?

    • I want to go as fast as possible towards the target
    • @@ -282,7 +284,7 @@

      Example 5.2: Arrive steering behavi Figure 5.11: A vehicle moving towards its target faster than its desired velocity will result in a steering force pointing away from the target.
      Figure 5.11: A vehicle moving towards its target faster than its desired velocity will result in a steering force pointing away from the target.

    -

    5.5 Your Own Desires: Desired Velocity

    +

    Your Own Desires: Desired Velocity

    The first two examples I’ve covered—seek and arrive—boil down to calculating a single vector for each behavior: the desired velocity. And in fact, every single one of Reynolds’s steering behaviors follows this same pattern. In this chapter, I’m going to walk through several more of Reynolds’s behaviors—flow field, path-following, flocking. First, however, I want to emphasize again that these are examples—demonstrations of common steering behaviors that are useful in procedural animation. They are not the be-all and end-all of what you can do. As long as you can come up with a vector that describes a vehicle’s desired velocity, then you have created your own steering behavior.

    Let’s see how Reynolds defines the desired velocity for his wandering behavior.

    @@ -332,7 +334,7 @@

    Example 5.3: “Stay wit

    Exercise 5.5

    Come up with your own arbitrary scheme for calculating a desired velocity.

    -

    5.6 Flow Fields

    +

    Flow Fields

    Now back to the task at hand. Let’s examine another Craig Reynolds’s steering behavior: flow field following. What is a flow field? Think of the canvas as a grid. In each cell of the grid lives an arrow pointing in some direction—you know, a vector. As a vehicle moves around the canvas, it asks, “Hey, what arrow is beneath me? That’s my desired velocity!”

    Figure 5.14: A two-dimension grid full of unit vectors pointing in random directions. @@ -500,7 +502,7 @@

    Exercise 5.7

    Exercise 5.8

    Can you create a flow field from an image? For example, try having the vectors point from dark to light colors (or vice versa).

    -

    5.7 The Dot Product

    +

    The Dot Product

    In a moment, I’m going to work through the algorithm (along with accompanying mathematics) and code for another of Craig Reynolds’s steering behaviors: Path Following. Before I can do this, however, I would like to spend some time discussing a piece of vector math that I skipped over in Chapter 1—the dot product. I haven’t needed it yet, but it’s neccessary here and likely will prove quite useful for you beyond just this example.

    Remember all the vector math covered in Chapter 1? Add, subtract, multiply, and divide?

    @@ -582,7 +584,7 @@

    Exercise 5.9

  • If two vectors (\vec{A} and \vec{B}) are orthogonal (i.e. perpendicular), the dot product (\vec{A}\cdot\vec{B}) is equal to 0.
  • If two vectors are unit vectors, then the dot product is equal to cosine of the angle between them, i.e. \vec{A}\cdot\vec{B}=\cos(\theta) if \vec{A} and \vec{B} are of length 1.
  • -

    5.8 Path Following

    +

    Path Following

    Now that I’ve covered the fundamentals of the dot product, I’d like to return to Craig Reynolds’s path-following algorithm. Let’s quickly clarify something. The behavior here is path following, not path finding. Pathfinding refers to an algorithm that involves solving for the shortest distance between two points, often in a maze. With path following, the path already exists and the vehicle just tries to follow it.

    Figure 5.20 depicts all the components of the path following behavior. There are a lot of steps to this one beyond just a vehicle and target so take some time to review the full diagram. I’ll then slowly unpack the algorithm piece by piece.

    @@ -837,9 +839,9 @@

    5.9 Path Following with Multip
       if (normalPoint.x < a.x || normalPoint.x > b.x) {
           //{!1} Use the end point of the segment
           // as our normal point if we can’t find one.
    -      normalPoint = b.copy();
    +      normalPoint = a.copy();
        }
    -

    As a little trick, I’ll say that if it’s not within the line segment, let’s just pretend the end point of that line segment is the normal. This will ensure that the vehicle always stays on the path, even if it strays out of the bounds of the line segments themselves.

    +

    As a little trick, I’ll say that if it’s not within the line segment, let’s just pretend the start point of that line segment is the normal. This will ensure that the vehicle always stays on the path, even if it strays out of the bounds of the line segments themselves.

    Exercise 5.10 @@ -864,7 +866,7 @@

    Example 5.6: Path following

    let normalPoint = this.getNormalPoint(future, a, b); if (normalPoint.x < a.x || normalPoint.x > b.x) { - normalPoint = b.copy(); + normalPoint = a.copy(); } let distance = p5.Vector.dist(future, normalPoint); @@ -879,7 +881,7 @@

    Example 5.6: Path following

    Exercise 5.11

    Create a path that changes over time. Can the points that define the path itself have their own steering behaviors?

    -

    5.10 Complex Systems

    +

    Complex Systems

    Remember your purpose? To breathe life into the things that move around p5.js canvases? By learning to write the code for an autonomous agent and playing with these examples of individual behaviors, hopefully your soul feel a little more full. But this is no place to stop and rest on your laurels. I’m just getting started. After all, there is a deeper purpose at work here. Yes, a vehicle is a simulated being that makes decisions about how to seek and flow and follow. But what is a life led alone, without the love and support of others? The purpose here is not only to build individual behaviors for these vehicles, but to put these vehicles into systems of many vehicles and allow them to interact with each other.

    Let’s think about a tiny, crawling ant—one single ant. An ant is an autonomous agent; it can perceive its environment (using antennae to gather information about the direction and strength of chemical signals) and make decisions about how to move based on those signals. But can a single ant acting alone build a nest, gather food, defend its queen? An ant is a simple unit and can only perceive its immediate environment. A colony of ants, however, is a sophisticated complex system, a “superorganism” in which the components work together to accomplish difficult and complicated goals.

    I want to take what I’ve developed during the process of building individual agents into simulations that involve many autonomous agents operating in parallel—agents that have an ability to perceive not only their physical environment but also the actions of their fellow agents, and then act accordingly. I want to create complex systems with p5.js.

    @@ -893,10 +895,10 @@

    5.10 Complex Systems

    • Non-linearity. This aspect of complex systems is often casually referred to as “the butterfly effect,” coined by mathematician and meteorologist Edward Norton Lorenz, a pioneer in the study of chaos theory. In 1961, Lorenz was running a computer weather simulation for the second time and, perhaps to save a little time, typed in a starting value of 0.506 instead of 0.506127. The end result was completely different from the first result of the simulation. In other words, the theory is that a single butterfly flapping its wings on the other side of the world could cause a massive weather shift and ruin your weekend at the beach. It‘s called “non-linear” because there isn’t a linear relationship between a change in initial conditions and a change in outcome. A small change in initial conditions can have a massive effect on the outcome. Non-linear systems are a superset of chaotic systems. In the next chapter, you’ll see how even in a system of many zeros and ones, if you change just one bit, the result will be completely different.
    • Competition and cooperation. One of the things that often makes a complex system tick is the presence of both competition and cooperation between the elements. In the upcoming flocking system, there will be three rules—alignment, cohesion, and separation. Alignment and cohesion will ask the elements to “cooperate”—i.e. work together to stay together and move together. Separation, however, will ask the elements to “compete” for space. When the time comes, try taking out the cooperation or the competition and you’ll see how you are left without complexity. Competition and cooperation are found in living complex systems, but not in non-living complex systems like the weather.
    • -
    • Feedback. Complex systems often include a feedback loop where the output of the system is fed back into the system to influence its behavior in a positive or negative direction. Let’s say you drive to work each day because the price of gas is low. In fact, everyone drives to work. The price of gas goes up as demand begins to exceed supply. You, and everyone else, decide to take the train to work because driving is too expensive. And the price of gas declines as the demand declines. The price of gas is both the input of the system (determining whether you choose to drive or ride the train) and the output (the demand that results from your choice). I should note that economic models (like supply/demand, the stock market) are one example of a human complex system. Others include fads and trends, elections, crowds, and traffic flow.
    • +
    • Feedback. Complex systems often include a feedback loop where the output of the system is fed back into the system to influence its behavior in a positive or negative direction. Let’s say you take public transportation to work each day because it’s the most reliable and cost-effective solution. In fact, everyone starts to take public transport as traffic congestion is reduced and it becomes even more efficient and attractive. However, as the popularity increases, the system may struggle to accommodate the rising demand, leading to overcrowding, delays, and increased fares to fund infrastructure improvements. As a result, you and others start to switch back to driving, thereby increasing traffic congestion and reducing public transport's efficiency. As traffic worsens, the funds from increased fares are (hopefully) used to improve public transport infrastructure, making it more appealing once again. In this way, the cost and efficiency of public transportation is both the input of the system (determining whether you choose to use it or not) and the output (the degree of traffic congestion and subsequent cost and efficiency). I should note that economic models are just one example of a human complex system. Others include fads and trends, elections, crowds, and traffic flow.

    Complexity will serve as a theme for much of the rest of the book. In this chapter, I’ll begin by adding one more feature to the Vehicle class: an ability perceive neighboring vehicles.

    -

    5.11 Group Behaviors (or: Let’s not run into each other)

    +

    Group Behaviors (or: Let’s not run into each other)

    A group is certainly not a new concept. You’ve seen this before—in Chapter 4, where I developed a framework for managing collections of particles in a particle system Emitter class. There, a list of particles was stored in an array. I’ll start with the same technique here and store Vehicle objects in an array.

    // Declare an ArrayList of Vehicle objects.
     let vehicles;
    @@ -1067,7 +1069,7 @@ 

    Exercise 5.13

    -

    5.12 Combinations

    +

    Combinations

    The previous two exercises hint at what is perhaps the most important aspect of this chapter. After all, what is a p5.js sketch with one steering force compared to many? How could I even begin to simulate emergence in a sketch with only one rule? The most exciting and intriguing behaviors will come from mixing and matching multiple steering forces, and I’ll need a mechanism for doing so.

    You may be thinking, “This is nothing new. We do this all the time.” You would be right. In fact, this technique appeared as early as Chapter 2.

      const wind = createVector(0.001, 0);
    @@ -1127,7 +1129,7 @@ 

    Example 5.8:

    Exercise 5.14

    Redo Example 5.8 so that the behavior weights change over time. For example, what if the weights were calculated according to a sine wave or Perlin noise? Or if some vehicles are more concerned with seeking and others more concerned with separating? Can you introduce other steering behaviors as well?

    -

    5.13 Flocking

    +

    Flocking

    Flocking is a group animal behavior that is characteristic of many living creatures, such as birds, fish, and insects. In 1986, Craig Reynolds created a computer simulation of flocking behavior and documented the algorithm in his paper, “Flocks, Herds, and Schools: A Distributed Behavioral Model.” Recreating this simulation in p5.js will bring together all the concepts in this chapter.

    1. I will use the steering force formula (steer = desired - velocity) to implement the rules of flocking.
    2. @@ -1231,7 +1233,7 @@

      Exercise 5.15

      - +

      Finally, I am ready for cohesion. Here the code is virtually identical to that for alignment—only instead of calculating the average velocity of the boid’s neighbors, I want to calculate the average position of the boid’s neighbors (and use that as a target to seek).

        cohesion(boids) {
           let neighbordist = 50;
      @@ -1258,7 +1260,6 @@ 

      Exercise 5.15

      }

      It’s also worth taking the time to write a class called Flock, which will be virtually identical to the ParticleSystem class in Chapter 4 with only one tiny change: When I call run() on each Boid object (as I did to each Particle object), I’ll pass in a reference to the entire array of boids.

      Flock {
      -
         constructor() {
           this.boids = [];
         }
      @@ -1279,7 +1280,7 @@ 

      Exercise 5.15

      Example 5.9: Flocking

      -
      +
      @@ -1325,7 +1326,7 @@

      Exercise 5.18

      Exercise 5.19

      Visualize the flock in an entirely different way.

      -

      5.14 Algorithmic Efficiency (or: Why does my sketch run so slowly?)

      +

      Algorithmic Efficiency (or: Why does my sketch run so slowly?)

      I would like to hide the dark truth behind what I’ve just done, because I would like you to be happy and live a fulfilling and meaningful life. But I also would like to be able to sleep at night without worrying about you so much. So it is with a heavy heart that I must bring up this topic. yes, group behaviors are wonderful. But they can be slow, and the more elements in the group, the slower they can be. Usually, when I talk about p5.js sketches running slowly, it’s because drawing to the canvas can be slow—the more you draw, the slower your sketch runs. This is actually a case, however, where the slowness derives from the algorithm itself. Let’s discuss.

      Computer scientists classify algorithms with something called “Big O notation,” which describes the efficiency of an algorithm: how many computational cycles does e it require to complete? Let’s consider a simple analog search problem. You have a basket containing one hundred chocolate treats, only one of which is pure dark chocolate. That’s the one you want to eat. To find it, you pick the chocolates out of the basket one by one. Sure, you might be lucky and find it on the first try, but in the worst-case scenario you have to check all one hundred before you find the dark chocolate. To find one thing in one hundred, you have to check one hundred things (or to find one thing in N things, you have to check N times.) Your Big O Notation is N. This, incidentally, is the Big O Notation that describes the simple particle system. If you have N particles, you have to run and display those particles N times.

      Now, let’s think about a group behavior (such as flocking). For every Boid object, you have to check every other Boid object (for its velocity and position). Let’s say you have one hundred boids. For boid #1, you need to check one hundred boids; for boid #2, you need to check one hundred boids, and so on and so forth. For one hundred boids, you need to perform one hundred times one hundred checks, or ten thousand. No problem: computers are fast and can do things ten thousand times pretty easily. Let’s try one thousand.

      @@ -1342,27 +1343,73 @@

      5.14 Alg Figure 5.37
      Figure 5.37
      -

      This technique is known as “bin-lattice spatial subdivision” and is outlined in more detail in (surprise, surprise) Reynolds’s 2000 paper, “Interaction with Groups of Autonomous Characters”. How do you implement such an algorithm in p5.js? The sollution I’ll describe below uses multiple arrays. One array to track of all the boids, just like in the flocking example.

      +

      This technique is known as “bin-lattice spatial subdivision” and is outlined in more detail in (surprise, surprise) Reynolds’s 2000 paper, “Interaction with Groups of Autonomous Characters”. How do you implement such an algorithm in p5.js? The solution I’ll describe below uses multiple arrays. One array to track of all the boids, just like in the flocking example.

      let boids = [];

      In addition to that array, I’ll store an additional reference to each Boid object in a two-dimensional array (repurposing the make2DArray function from Example X.X: Flow Field Following). For each cell in the grid, an additional array tracks the objects in that particular cell.

      -
      let grid = make2Darray(this.cols, this.rows);
      -

      In the draw(), each Boid object then registers itself in the appropriate cell according to its position.

      -
      let column = floor(boid.x) / this.resolution;
      -let row    = floor(boid.y) / this.resolution;
      -grid[column][row] = boid;
      -

      Then when it comes time to have the boids check for neighbors, they can look at only those in their particular cell (in truth, I also need to check neighboring cells to deal with border cases).

      +
      // Each cell is 40x40 pixels
      +let resolution = 40;
      +// How many columns and rows based on the width and height?
      +let columns = floor(width  / resolution);
      +let rows    = floor(height / resolution);
      +// Create the 2D array
      +let grid = make2Darray(columns, rows);
      +

      In the draw()function, each Boid then registers itself in the appropriate cell according to its position.

      +
      function draw() {
      +  // Each frame, the grid is reset to empty arrays
      +  for (let i = 0; i < cols; i++) {
      +    for (let j = 0; j < rows; j++) {
      +      grid[i][j] = [];
      +    }
      +  }
      +
      +  // Place each boid into the appropriate cell in the grid
      +  for (let boid of flock.boids) {
      +    // Find the right column and row
      +    let column = floor(boid.position.x / resolution);
      +    let row    = floor(boid.position.y / resolution);
      +    // Constrain to limits of array
      +    column = constrain(column, 0, columns - 1);
      +    row    = constrain(row, 0, rows - 1);
      +    // Add the boid
      +    grid[column][row].push(boid);
      +  }
      +

      Then when it comes time to have the boids check for neighbors, they can look at only those in their particular cell (though in truth, I also need to check neighboring cells to deal with border cases).

      Example 5.10: Bin-lattice spatial subdivision

      +
      +
      +
      +
      -
      let column = floor(boid.x) / this.resolution;
      -let row    = floor(boid.y) / this.resolution;
      -// Instead of looking at all
      -// the boids, just this cell
      -boid.flock(grid[column][row]);
      +
        run(boids) {
      +    let column = floor(this.position.x / resolution);
      +    let row = floor(this.position.y / resolution);
      +    column = constrain(column, 0, columns - 1);
      +    row = constrain(row, 0, rows - 1);
      +    // Only these boids will be checked. See the code online for how neighboring cells are also included.
      +    let neighbors = grid[column][row]; 
      +    this.flock(neighbors);
      +    this.update();
      +    this.borders();
      +    this.render();
      +  }

      I’re only covering the basics here; for the full code, check the book’s website.

      Now, there are certainly flaws with this system. What if all the boids congregate in the corner and live in the same cell? Then don’t we have to check all 2,000 against all 2,000?

      -

      The good news is that this need for optimization is a common one and there are a wide variety of similar techniques out there. For us, it’s likely that a basic approach will be good enough (in most cases, you won’t need one at all.) I cover another, more sophisticated approach, in my [tutorial series about building a QuadTree](https://thecodingtrain.com/CodingChallenges/098.1-quadtree.html) in p5.js. A QuadTree employs the exact same methodology as bin-lattice spatial subdivison, only the cells themselves are not sized equally but rather according to the distribution density of the elements themselves. TODO: QUAD TREE DIAGRAM https://github.com/nature-of-code/noc-book-2/issues/101 The accompanying [QuadTree p5.js library](https://github.com/CodingTrain/QuadTree) also includes a flocking example. .

      -

      5.15 A Few Last Notes: Optimization Tricks

      +

      The bin-lattice spatial subdivision is best suited for when the elements are evenly distributed throughout the canvas. A data structure known as a QuadTree, however, can handle unevenly distributed systems, preventing the worst-case scenario of all the boids crowding into a single cell.

      +

      The QuadTree expands upon the spatial subdivision strategy by dynamically adapting the grid according to the distribution of the boids. Instead of a fixed grid, a QuadTree starts with a single large cell that encompasses the entire space. If too many boids are found within this cell, it splits into four smaller cells. This process can repeat for each new cell that gets too crowded, creating a flexible grid that provides finer resolution where it's needed.

      +

      Additionally, the QuadTree strategy is key to the Barnes-Hut algorithm, which I referenced briefly when building an n-body simulation in Chapter 2. This method uses a QuadTree to approximate groups of bodies into a single one when calculating gravitational forces. This drastically reduces the number of calculations needed, allowing simulations with large numbers of bodies to run more efficiently. You can learn how to build a QuadTree and apply it to a flocking system as part of Coding Challenge #98 on thecodingtrain.com.

      +
      +

      Example 5.11: QuadTree

      +
      +
      +
      +
      +
      +
      +

      Exercise 5.x

      +

      Expand the bin-lattice spatial subdivision example to use a QuadTree.

      +
      +

      A Few Last Notes: Optimization Tricks

      This is something of a momentous occasion. The end of Chapter 5 marks the end of the story of motion (in the context of this book, that is). I started with the concept of a vector, moved on to forces, designed systems of many elements, examined physics libraries, built entities with hopes and dreams and fears, and simulated emergence. The story doesn’t end here, but it does take a bit of a turn. The next two chapters won’t focus on moving bodies, but rather on systems of rules. Before you get there, I have a few quick items I’d like to mention that are important when working with the examples in Chapters 1 through 5. They also relate to optimizing your code, which fits in with the previous section.

      1) Magnitude squared (or sometimes distance squared)

      What is magnitude squared and when should you use it? Let’s revisit how the magnitude of a vector is calculated.

      @@ -1380,9 +1427,9 @@

      1) Magnitude squared

      And how is magnitude squared calculated?

      function magSq() {
         return x * x + y * y;
      -}
      +}

      Same as magnitude, but without the square root. In the case of a single vector, this will never make a significant difference on a p5.js sketch. However, if you are computing the magnitude of thousands of vectors each time through draw(), using magSq() instead of mag() could help your code run a wee bit faster.

      -

      2) Sine and cosine lookup tables

      +

      2) Sine and cosine lookup tables

      There’s a pattern here. What kinds of functions are slow to compute? Square root. Sine. Cosine. Tangent. Again, if you just need a sine or cosine value here or there in your code, you are never going to run into a problem. But what if you had something like this?

      function draw() {
         for (let i = 0; i < 10000; i++) {
      @@ -1400,8 +1447,14 @@ 

      2) Sine and cosine lookup tables

      Now, what if you need the value of sine of pi (or 180 degrees)?

      let angle = 180;
       let answer = sinvalues[angle];
      -

      A full example using this technique can be found TODO: https://github.com/nature-of-code/noc-book-2/issues/102.

      -

      3) Making gajillions of unnecessary p5.Vector objects

      +
      +

      Example 5.12: Sin/Cos look-up table

      +
      +
      +
      +
      +
      +

      3) Making gajillions of unnecessary p5.Vector objects

      I have to admit, I am perhaps the biggest culprit of this last note. In fact, in the interest of writing clear and understandable examples, I often choose to make extra p5.Vector objects when I absolutely do not need to. For the most part, this is not a problem at all. But sometimes, it can be. Let’s take a look at an example.

      function draw() {
         for (let v of vehicles) {
      diff --git a/content/examples/04_particles/noc_4_01_single_particle/particle.js b/content/examples/04_particles/noc_4_01_single_particle/particle.js
      index da07c7f8..4eae3991 100644
      --- a/content/examples/04_particles/noc_4_01_single_particle/particle.js
      +++ b/content/examples/04_particles/noc_4_01_single_particle/particle.js
      @@ -23,9 +23,6 @@ class Particle {
           this.acceleration.mult(0);
         }
       
      -  applyForce(force) {
      -    this.acceleration.add(force);
      -  }
       
         show() {
           stroke(0, this.lifespan);
      @@ -40,6 +37,6 @@ class Particle {
       
         //{!3} Is the Particle alive or dead?
         isDead() {
      -    return (this.lifespan < 0.0);
      +    return (this.lifespan < 0);
         }
       }
      \ No newline at end of file
      diff --git a/content/examples/05_steering/example_5_9_flocking/boid.js b/content/examples/05_steering/example_5_9_flocking/boid.js
      new file mode 100644
      index 00000000..67d36b01
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking/boid.js
      @@ -0,0 +1,173 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Boid class
      +// Methods for Separation, Cohesion, Alignment added
      +
      +class Boid {
      +  constructor(x, y) {
      +    this.acceleration = createVector(0, 0);
      +    this.velocity = createVector(random(-1, 1), random(-1, 1));
      +    this.position = createVector(x, y);
      +    this.r = 3.0;
      +    this.maxspeed = 3; // Maximum speed
      +    this.maxforce = 0.05; // Maximum steering force
      +  }
      +
      +  run(boids) {
      +    this.flock(boids);
      +    this.update();
      +    this.borders();
      +    this.render();
      +  }
      +
      +  applyForce(force) {
      +    // We could add mass here if we want A = F / M
      +    this.acceleration.add(force);
      +  }
      +
      +  // We accumulate a new acceleration each time based on three rules
      +  flock(boids) {
      +    let sep = this.separate(boids); // Separation
      +    let ali = this.align(boids); // Alignment
      +    let coh = this.cohesion(boids); // Cohesion
      +    // Arbitrarily weight these forces
      +    sep.mult(1.5);
      +    ali.mult(1.0);
      +    coh.mult(1.0);
      +    // Add the force vectors to acceleration
      +    this.applyForce(sep);
      +    this.applyForce(ali);
      +    this.applyForce(coh);
      +  }
      +
      +  // Method to update location
      +  update() {
      +    // Update velocity
      +    this.velocity.add(this.acceleration);
      +    // Limit speed
      +    this.velocity.limit(this.maxspeed);
      +    this.position.add(this.velocity);
      +    // Reset accelertion to 0 each cycle
      +    this.acceleration.mult(0);
      +  }
      +
      +  // A method that calculates and applies a steering force towards a target
      +  // STEER = DESIRED MINUS VELOCITY
      +  seek(target) {
      +    let desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target
      +    // Normalize desired and scale to maximum speed
      +    desired.normalize();
      +    desired.mult(this.maxspeed);
      +    // Steering = Desired minus Velocity
      +    let steer = p5.Vector.sub(desired, this.velocity);
      +    steer.limit(this.maxforce); // Limit to maximum steering force
      +    return steer;
      +  }
      +
      +  render() {
      +    // Draw a triangle rotated in the direction of velocity
      +    let theta = this.velocity.heading() + radians(90);
      +    fill(127);
      +    stroke(0);
      +    push();
      +    translate(this.position.x, this.position.y);
      +    rotate(theta);
      +    beginShape();
      +    vertex(0, -this.r * 2);
      +    vertex(-this.r, this.r * 2);
      +    vertex(this.r, this.r * 2);
      +    endShape(CLOSE);
      +    pop();
      +  }
      +
      +  // Wraparound
      +  borders() {
      +    if (this.position.x < -this.r) this.position.x = width + this.r;
      +    if (this.position.y < -this.r) this.position.y = height + this.r;
      +    if (this.position.x > width + this.r) this.position.x = -this.r;
      +    if (this.position.y > height + this.r) this.position.y = -this.r;
      +  }
      +
      +  // Separation
      +  // Method checks for nearby boids and steers away
      +  separate(boids) {
      +    let desiredseparation = 25.0;
      +    let steer = createVector(0, 0);
      +    let count = 0;
      +    // For every boid in the system, check if it's too close
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      +      if ((d > 0) && (d < desiredseparation)) {
      +        // Calculate vector pointing away from neighbor
      +        let diff = p5.Vector.sub(this.position, boids[i].position);
      +        diff.normalize();
      +        diff.div(d); // Weight by distance
      +        steer.add(diff);
      +        count++; // Keep track of how many
      +      }
      +    }
      +    // Average -- divide by how many
      +    if (count > 0) {
      +      steer.div(count);
      +    }
      +
      +    // As long as the vector is greater than 0
      +    if (steer.mag() > 0) {
      +      // Implement Reynolds: Steering = Desired - Velocity
      +      steer.normalize();
      +      steer.mult(this.maxspeed);
      +      steer.sub(this.velocity);
      +      steer.limit(this.maxforce);
      +    }
      +    return steer;
      +  }
      +
      +  // Alignment
      +  // For every nearby boid in the system, calculate the average velocity
      +  align(boids) {
      +    let neighbordist = 50;
      +    let sum = createVector(0, 0);
      +    let count = 0;
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      if ((d > 0) && (d < neighbordist)) {
      +        sum.add(boids[i].velocity);
      +        count++;
      +      }
      +    }
      +    if (count > 0) {
      +      sum.div(count);
      +      sum.normalize();
      +      sum.mult(this.maxspeed);
      +      let steer = p5.Vector.sub(sum, this.velocity);
      +      steer.limit(this.maxforce);
      +      return steer;
      +    } else {
      +      return createVector(0, 0);
      +    }
      +  }
      +
      +  // Cohesion
      +  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
      +  cohesion(boids) {
      +    let neighbordist = 50;
      +    let sum = createVector(0, 0); // Start with empty vector to accumulate all locations
      +    let count = 0;
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      if ((d > 0) && (d < neighbordist)) {
      +        sum.add(boids[i].position); // Add location
      +        count++;
      +      }
      +    }
      +    if (count > 0) {
      +      sum.div(count);
      +      return this.seek(sum); // Steer towards the location
      +    } else {
      +      return createVector(0, 0);
      +    }
      +  }
      +}
      \ No newline at end of file
      diff --git a/content/examples/05_steering/example_5_9_flocking/flock.js b/content/examples/05_steering/example_5_9_flocking/flock.js
      new file mode 100644
      index 00000000..d0f56010
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking/flock.js
      @@ -0,0 +1,24 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Flock object
      +// Does very little, simply manages the array of all the boids
      +
      +class Flock {
      +
      +  constructor() {
      +    // An array for all the boids
      +    this.boids = []; // Initialize the array
      +  }
      +
      +  run() {
      +    for (let boid of this.boids) {
      +      boid.run(this.boids); // Passing the entire list of boids to each boid individually
      +    }
      +  }
      +
      +  addBoid(b) {
      +    this.boids.push(b);
      +  }
      +}
      \ No newline at end of file
      diff --git a/content/examples/05_steering/example_5_9_flocking/index.html b/content/examples/05_steering/example_5_9_flocking/index.html
      new file mode 100644
      index 00000000..05a3e37d
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking/index.html
      @@ -0,0 +1,14 @@
      +
      +
      +  
      +    
      +    
      +    The Nature of Code Example 6.9 Flocking
      +    
      +  
      +  
      +    
      +    
      +    
      +  
      +
      diff --git a/content/examples/05_steering/example_5_9_flocking/sketch.js b/content/examples/05_steering/example_5_9_flocking/sketch.js
      new file mode 100644
      index 00000000..4d21a943
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking/sketch.js
      @@ -0,0 +1,31 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Demonstration of Craig Reynolds' "Flocking" behavior
      +// See: http://www.red3d.com/cwr/
      +// Rules: Cohesion, Separation, Alignment
      +
      +// Click mouse to add boids into the system
      +
      +let flock;
      +
      +function setup() {
      +  createCanvas(640, 240);
      +  flock = new Flock();
      +  // Add an initial set of boids into the system
      +  for (let i = 0; i < 120; i++) {
      +    let boid = new Boid(width / 2, height / 2);
      +    flock.addBoid(boid);
      +  }
      +}
      +
      +function draw() {
      +  background(255);
      +  flock.run();
      +}
      +
      +// Add a new boid into the System
      +function mouseDragged() {
      +  flock.addBoid(new Boid(mouseX, mouseY));
      +}
      diff --git a/content/examples/05_steering/example_5_9_flocking/style.css b/content/examples/05_steering/example_5_9_flocking/style.css
      new file mode 100644
      index 00000000..130b7d69
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking/style.css
      @@ -0,0 +1,7 @@
      +html, body {
      + margin: 0;
      + padding: 0;
      +}
      +canvas {
      +  display: block;
      +}
      diff --git a/content/examples/05_steering/example_5_9_flocking_with_binning/boid.js b/content/examples/05_steering/example_5_9_flocking_with_binning/boid.js
      new file mode 100644
      index 00000000..a10fd6bc
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking_with_binning/boid.js
      @@ -0,0 +1,190 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Boid class
      +// Methods for Separation, Cohesion, Alignment added
      +
      +class Boid {
      +  constructor(x, y) {
      +    this.acceleration = createVector(0, 0);
      +    this.velocity = createVector(random(-1, 1), random(-1, 1));
      +    this.position = createVector(x, y);
      +    this.r = 3.0;
      +    this.maxspeed = 3; // Maximum speed
      +    this.maxforce = 0.05; // Maximum steering force
      +  }
      +
      +  run() {
      +    let col = Math.floor(this.position.x / resolution);
      +    let row = Math.floor(this.position.y / resolution);
      +    let neighbors = [];
      +
      +    // Check cells in a 3x3 block around the current boid
      +    for (let i = -1; i <= 1; i++) {
      +      for (let j = -1; j <= 1; j++) {
      +        let newCol = col + i;
      +        let newRow = row + j;
      +        // Make sure this is a valid cell
      +        if (newCol >= 0 && newCol < cols && newRow >= 0 && newRow < rows) {
      +          // Add all boids in this cell to neighbors
      +          neighbors = neighbors.concat(grid[newCol][newRow]);
      +        }
      +      }
      +    }
      +
      +    this.flock(neighbors);
      +    this.update();
      +    this.borders();
      +    this.render();
      +  }
      +
      +  applyForce(force) {
      +    // We could add mass here if we want A = F / M
      +    this.acceleration.add(force);
      +  }
      +
      +  // We accumulate a new acceleration each time based on three rules
      +  flock(boids) {
      +    let sep = this.separate(boids); // Separation
      +    let ali = this.align(boids); // Alignment
      +    let coh = this.cohesion(boids); // Cohesion
      +    // Arbitrarily weight these forces
      +    sep.mult(1.5);
      +    ali.mult(1.0);
      +    coh.mult(1.0);
      +    // Add the force vectors to acceleration
      +    this.applyForce(sep);
      +    this.applyForce(ali);
      +    this.applyForce(coh);
      +  }
      +
      +  // Method to update location
      +  update() {
      +    // Update velocity
      +    this.velocity.add(this.acceleration);
      +    // Limit speed
      +    this.velocity.limit(this.maxspeed);
      +    this.position.add(this.velocity);
      +    // Reset accelertion to 0 each cycle
      +    this.acceleration.mult(0);
      +  }
      +
      +  // A method that calculates and applies a steering force towards a target
      +  // STEER = DESIRED MINUS VELOCITY
      +  seek(target) {
      +    let desired = p5.Vector.sub(target, this.position); // A vector pointing from the location to the target
      +    // Normalize desired and scale to maximum speed
      +    desired.normalize();
      +    desired.mult(this.maxspeed);
      +    // Steering = Desired minus Velocity
      +    let steer = p5.Vector.sub(desired, this.velocity);
      +    steer.limit(this.maxforce); // Limit to maximum steering force
      +    return steer;
      +  }
      +
      +  render() {
      +    // Draw a triangle rotated in the direction of velocity
      +    let theta = this.velocity.heading() + radians(90);
      +    fill(127);
      +    stroke(0);
      +    push();
      +    translate(this.position.x, this.position.y);
      +    rotate(theta);
      +    beginShape();
      +    vertex(0, -this.r * 2);
      +    vertex(-this.r, this.r * 2);
      +    vertex(this.r, this.r * 2);
      +    endShape(CLOSE);
      +    pop();
      +  }
      +
      +  // Wraparound
      +  borders() {
      +    if (this.position.x < -this.r) this.position.x = width + this.r;
      +    if (this.position.y < -this.r) this.position.y = height + this.r;
      +    if (this.position.x > width + this.r) this.position.x = -this.r;
      +    if (this.position.y > height + this.r) this.position.y = -this.r;
      +  }
      +
      +  // Separation
      +  // Method checks for nearby boids and steers away
      +  separate(boids) {
      +    let desiredseparation = 25.0;
      +    let steer = createVector(0, 0);
      +    let count = 0;
      +    // For every boid in the system, check if it's too close
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      // If the distance is greater than 0 and less than an arbitrary amount (0 when you are yourself)
      +      if (d > 0 && d < desiredseparation) {
      +        // Calculate vector pointing away from neighbor
      +        let diff = p5.Vector.sub(this.position, boids[i].position);
      +        diff.normalize();
      +        diff.div(d); // Weight by distance
      +        steer.add(diff);
      +        count++; // Keep track of how many
      +      }
      +    }
      +    // Average -- divide by how many
      +    if (count > 0) {
      +      steer.div(count);
      +    }
      +
      +    // As long as the vector is greater than 0
      +    if (steer.mag() > 0) {
      +      // Implement Reynolds: Steering = Desired - Velocity
      +      steer.normalize();
      +      steer.mult(this.maxspeed);
      +      steer.sub(this.velocity);
      +      steer.limit(this.maxforce);
      +    }
      +    return steer;
      +  }
      +
      +  // Alignment
      +  // For every nearby boid in the system, calculate the average velocity
      +  align(boids) {
      +    let neighbordist = 50;
      +    let sum = createVector(0, 0);
      +    let count = 0;
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      if (d > 0 && d < neighbordist) {
      +        sum.add(boids[i].velocity);
      +        count++;
      +      }
      +    }
      +    if (count > 0) {
      +      sum.div(count);
      +      sum.normalize();
      +      sum.mult(this.maxspeed);
      +      let steer = p5.Vector.sub(sum, this.velocity);
      +      steer.limit(this.maxforce);
      +      return steer;
      +    } else {
      +      return createVector(0, 0);
      +    }
      +  }
      +
      +  // Cohesion
      +  // For the average location (i.e. center) of all nearby boids, calculate steering vector towards that location
      +  cohesion(boids) {
      +    let neighbordist = 50;
      +    let sum = createVector(0, 0); // Start with empty vector to accumulate all locations
      +    let count = 0;
      +    for (let i = 0; i < boids.length; i++) {
      +      let d = p5.Vector.dist(this.position, boids[i].position);
      +      if (d > 0 && d < neighbordist) {
      +        sum.add(boids[i].position); // Add location
      +        count++;
      +      }
      +    }
      +    if (count > 0) {
      +      sum.div(count);
      +      return this.seek(sum); // Steer towards the location
      +    } else {
      +      return createVector(0, 0);
      +    }
      +  }
      +}
      diff --git a/content/examples/05_steering/example_5_9_flocking_with_binning/flock.js b/content/examples/05_steering/example_5_9_flocking_with_binning/flock.js
      new file mode 100644
      index 00000000..63f42fb9
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking_with_binning/flock.js
      @@ -0,0 +1,24 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Flock object
      +// Does very little, simply manages the array of all the boids
      +
      +class Flock {
      +
      +  constructor() {
      +    // An array for all the boids
      +    this.boids = []; // Initialize the array
      +  }
      +
      +  run() {
      +    for (let boid of this.boids) {
      +      boid.run(this.boids); // Passing the entire list of boids to each boid individually
      +    }
      +  }
      +
      +  addBoid(boid) {
      +    this.boids.push(boid);
      +  }
      +}
      \ No newline at end of file
      diff --git a/content/examples/05_steering/example_5_9_flocking_with_binning/index.html b/content/examples/05_steering/example_5_9_flocking_with_binning/index.html
      new file mode 100644
      index 00000000..05a3e37d
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking_with_binning/index.html
      @@ -0,0 +1,14 @@
      +
      +
      +  
      +    
      +    
      +    The Nature of Code Example 6.9 Flocking
      +    
      +  
      +  
      +    
      +    
      +    
      +  
      +
      diff --git a/content/examples/05_steering/example_5_9_flocking_with_binning/sketch.js b/content/examples/05_steering/example_5_9_flocking_with_binning/sketch.js
      new file mode 100644
      index 00000000..1004bb2e
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking_with_binning/sketch.js
      @@ -0,0 +1,97 @@
      +// The Nature of Code
      +// Daniel Shiffman
      +// http://natureofcode.com
      +
      +// Demonstration of Craig Reynolds' "Flocking" behavior
      +// See: http://www.red3d.com/cwr/
      +// Rules: Cohesion, Separation, Alignment
      +
      +// Click mouse to add boids into the system
      +
      +let flock;
      +
      +// bin-lattice spatial subdivision
      +let grid;
      +let cols;
      +let rows;
      +let resolution = 40; // adjust as necessary
      +
      +function make2DArray(cols, rows) {
      +  let arr = new Array(cols);
      +  for (let i = 0; i < arr.length; i++) {
      +    arr[i] = new Array(rows);
      +  }
      +  return arr;
      +}
      +
      +function setup() {
      +  createCanvas(640, 240);
      +  cols = floor(width / resolution);
      +  rows = floor(height / resolution);
      +  grid = make2DArray(cols, rows);
      +  flock = new Flock();
      +  // Add an initial set of boids into the system
      +  for (let i = 0; i < 800; i++) {
      +    let boid = new Boid(random(width), random(height));
      +    flock.addBoid(boid);
      +  }
      +}
      +
      +function draw() {
      +  background(255);
      +
      +  // Reset grid at the beginning of each frame
      +  for (let i = 0; i < cols; i++) {
      +    for (let j = 0; j < rows; j++) {
      +      grid[i][j] = [];
      +    }
      +  }
      +
      +  // Place each boid into the appropriate cell in the grid
      +  for (let boid of flock.boids) {
      +    let col = floor(boid.position.x / resolution);
      +    let row = floor(boid.position.y / resolution);
      +    col = constrain(col, 0, cols - 1);
      +    row = constrain(row, 0, rows - 1);
      +    grid[col][row].push(boid);
      +  }
      +
      +  // Draw the grid
      +  stroke(200);
      +  strokeWeight(1);
      +
      +  // Draw vertical lines
      +  for (let i = 0; i <= cols; i++) {
      +    let x = i * resolution;
      +    line(x, 0, x, height);
      +  }
      +
      +  // Draw horizontal lines
      +  for (let j = 0; j <= rows; j++) {
      +    let y = j * resolution;
      +    line(0, y, width, y);
      +  }
      +
      +  // Highlight the 3x3 neighborhood the mouse is over
      +  let mouseCol = floor(mouseX / resolution);
      +  let mouseRow = floor(mouseY / resolution);
      +  noStroke();
      +  fill(255, 50, 50, 100); // Semi-transparent red
      +  for (let i = -1; i <= 1; i++) {
      +    for (let j = -1; j <= 1; j++) {
      +      let col = mouseCol + i;
      +      let row = mouseRow + j;
      +      // Check if the cell is within the grid
      +      if (col >= 0 && col < cols && row >= 0 && row < rows) {
      +        rect(col * resolution, row * resolution, resolution, resolution);
      +      }
      +    }
      +  }
      +
      +  flock.run();
      +}
      +
      +// Add a new boid into the System
      +function mouseDragged() {
      +  flock.addBoid(new Boid(mouseX, mouseY));
      +}
      diff --git a/content/examples/05_steering/example_5_9_flocking_with_binning/style.css b/content/examples/05_steering/example_5_9_flocking_with_binning/style.css
      new file mode 100644
      index 00000000..130b7d69
      --- /dev/null
      +++ b/content/examples/05_steering/example_5_9_flocking_with_binning/style.css
      @@ -0,0 +1,7 @@
      +html, body {
      + margin: 0;
      + padding: 0;
      +}
      +canvas {
      +  display: block;
      +}
      diff --git a/content/examples/05_steering/quadtree_part_1_copy/index.html b/content/examples/05_steering/quadtree_part_1_copy/index.html
      new file mode 100644
      index 00000000..56637061
      --- /dev/null
      +++ b/content/examples/05_steering/quadtree_part_1_copy/index.html
      @@ -0,0 +1,18 @@
      +
      +
      +
      +
      +  
      +  
      +  
      +  
      +  
      +
      +
      +
      +
      +  
      +  
      +
      +
      +
      \ No newline at end of file
      diff --git a/content/examples/05_steering/quadtree_part_1_copy/quadtree.js b/content/examples/05_steering/quadtree_part_1_copy/quadtree.js
      new file mode 100644
      index 00000000..5bfc8ded
      --- /dev/null
      +++ b/content/examples/05_steering/quadtree_part_1_copy/quadtree.js
      @@ -0,0 +1,140 @@
      +// Daniel Shiffman
      +// http://codingtra.in
      +// http://patreon.com/codingtrain
      +
      +// QuadTree
      +// 1: https://www.youtube.com/watch?v=OJxEcs0w_kE
      +// 2: https://www.youtube.com/watch?v=QQx_NmCIuCY
      +
      +// For more:
      +// https://github.com/CodingTrain/QuadTree
      +
      +class Point {
      +  constructor(x, y) {
      +    this.x = x;
      +    this.y = y;
      +  }
      +}
      +
      +class Rectangle {
      +  constructor(x, y, w, h) {
      +    this.x = x;
      +    this.y = y;
      +    this.w = w;
      +    this.h = h;
      +  }
      +
      +  contains(point) {
      +    return (point.x >= this.x - this.w &&
      +      point.x < this.x + this.w &&
      +      point.y >= this.y - this.h &&
      +      point.y < this.y + this.h);
      +  }
      +
      +  intersects(range) {
      +    return !(range.x - range.w > this.x + this.w ||
      +      range.x + range.w < this.x - this.w ||
      +      range.y - range.h > this.y + this.h ||
      +      range.y + range.h < this.y - this.h);
      +  }
      +
      +
      +}
      +
      +class QuadTree {
      +  constructor(boundary, n) {
      +    this.boundary = boundary;
      +    this.capacity = n;
      +    this.points = [];
      +    this.divided = false;
      +  }
      +
      +  subdivide() {
      +    let x = this.boundary.x;
      +    let y = this.boundary.y;
      +    let w = this.boundary.w;
      +    let h = this.boundary.h;
      +    let ne = new Rectangle(x + w / 2, y - h / 2, w / 2, h / 2);
      +    this.northeast = new QuadTree(ne, this.capacity);
      +    let nw = new Rectangle(x - w / 2, y - h / 2, w / 2, h / 2);
      +    this.northwest = new QuadTree(nw, this.capacity);
      +    let se = new Rectangle(x + w / 2, y + h / 2, w / 2, h / 2);
      +    this.southeast = new QuadTree(se, this.capacity);
      +    let sw = new Rectangle(x - w / 2, y + h / 2, w / 2, h / 2);
      +    this.southwest = new QuadTree(sw, this.capacity);
      +    this.divided = true;
      +  }
      +
      +  insert(point) {
      +
      +    if (!this.boundary.contains(point)) {
      +      return false;
      +    }
      +
      +    if (this.points.length < this.capacity) {
      +      this.points.push(point);
      +      return true;
      +    } else {
      +      if (!this.divided) {
      +        this.subdivide();
      +      }
      +      if (this.northeast.insert(point)) {
      +        return true;
      +      } else if (this.northwest.insert(point)) {
      +        return true;
      +      } else if (this.southeast.insert(point)) {
      +        return true;
      +      } else if (this.southwest.insert(point)) {
      +        return true;
      +      }
      +    }
      +  }
      +
      +  query(range, found) {
      +    if (!found) {
      +      found = [];
      +    }
      +    if (!this.boundary.intersects(range)) {
      +      return;
      +    } else {
      +      for (let p of this.points) {
      +        if (range.contains(p)) {
      +          found.push(p);
      +        }
      +      }
      +      if (this.divided) {
      +        this.northwest.query(range, found);
      +        this.northeast.query(range, found);
      +        this.southwest.query(range, found);
      +        this.southeast.query(range, found);
      +      }
      +    }
      +    return found;
      +  }
      +
      +
      +  show() {
      +    stroke(0);
      +    noFill();
      +    strokeWeight(1);
      +    rectMode(CENTER);
      +    rect(this.boundary.x, this.boundary.y, this.boundary.w * 2, this.boundary.h * 2);
      +    for (let p of this.points) {
      +      strokeWeight(1);
      +      stroke(0);
      +      point(p.x, p.y);
      +    }
      +
      +    if (this.divided) {
      +      this.northeast.show();
      +      this.northwest.show();
      +      this.southeast.show();
      +      this.southwest.show();
      +    }
      +  }
      +
      +
      +
      +
      +
      +}
      diff --git a/content/examples/05_steering/quadtree_part_1_copy/sketch.js b/content/examples/05_steering/quadtree_part_1_copy/sketch.js
      new file mode 100644
      index 00000000..a673c53c
      --- /dev/null
      +++ b/content/examples/05_steering/quadtree_part_1_copy/sketch.js
      @@ -0,0 +1,46 @@
      +// Daniel Shiffman
      +// http://codingtra.in
      +// http://patreon.com/codingtrain
      +
      +// QuadTree
      +// 1: https://www.youtube.com/watch?v=OJxEcs0w_kE
      +// 2: https://www.youtube.com/watch?v=QQx_NmCIuCY
      +
      +// For more:
      +// https://github.com/CodingTrain/QuadTree
      +
      +let qtree;
      +
      +function setup() {
      +  createCanvas(640, 240);
      +  let boundary = new Rectangle(width / 2, height / 2, width, height);
      +  qtree = new QuadTree(boundary, 8);
      +  for (let i = 0; i < 2000; i++) {
      +    let x = randomGaussian(width / 2, width / 8);
      +    let y = randomGaussian(height / 2, height / 8);
      +    let p = new Point(x, y);
      +    qtree.insert(p);
      +  }
      +}
      +
      +function draw() {
      +  background(255);
      +  qtree.show();
      +
      +  rectMode(CENTER);
      +  let range = new Rectangle(mouseX, mouseY, 50, 50);
      +
      +  // This check has been introduced due to a bug discussed in https://github.com/CodingTrain/website/pull/556
      +  if (mouseX < width && mouseY < height) {
      +    strokeWeight(2);
      +    stroke(255, 50, 50);
      +    fill(255, 50, 50, 50);
      +    rect(range.x, range.y, range.w * 2, range.h * 2);
      +    let points = qtree.query(range);
      +    for (let p of points) {
      +      strokeWeight(3);
      +      stroke(50, 50, 50);
      +      point(p.x, p.y);
      +    }
      +  }
      +}
      diff --git a/content/examples/05_steering/quadtree_part_1_copy/style.css b/content/examples/05_steering/quadtree_part_1_copy/style.css
      new file mode 100644
      index 00000000..9386f1c2
      --- /dev/null
      +++ b/content/examples/05_steering/quadtree_part_1_copy/style.css
      @@ -0,0 +1,7 @@
      +html, body {
      +  margin: 0;
      +  padding: 0;
      +}
      +canvas {
      +  display: block;
      +}
      diff --git a/content/examples/05_steering/sine_cosine_lookup_table/index.html b/content/examples/05_steering/sine_cosine_lookup_table/index.html
      new file mode 100644
      index 00000000..b9d16ffa
      --- /dev/null
      +++ b/content/examples/05_steering/sine_cosine_lookup_table/index.html
      @@ -0,0 +1,13 @@
      +
      +
      +  
      +    
      +    
      +    
      +    
      +
      +  
      +  
      +    
      +  
      +
      diff --git a/content/examples/05_steering/sine_cosine_lookup_table/sketch.js b/content/examples/05_steering/sine_cosine_lookup_table/sketch.js
      new file mode 100644
      index 00000000..a6a71e04
      --- /dev/null
      +++ b/content/examples/05_steering/sine_cosine_lookup_table/sketch.js
      @@ -0,0 +1,51 @@
      +/*
      +sincoslookup taken from http://wiki.processing.org/index.php/Sin/Cos_look-up_table
      +archived version http://web.archive.org/web/20130510100827/http://wiki.processing.org/w/Sin/Cos_look-up_table
      +*/
      +
      +// declare arrays and params for storing sin/cos values 
      +let sinLUT;
      +let cosLUT;
      +// set table precision to 0.5 degrees
      +const SC_PRECISION = 0.5;
      +// caculate reciprocal for conversions
      +const SC_INV_PREC = 1 / SC_PRECISION;
      +// compute required table length
      +const SC_PERIOD = Math.floor(360 * SC_INV_PREC);
      +
      +// init sin/cos tables with values
      +// should be called from setup()
      +function initSinCos() {
      +  sinLUT = [];
      +  cosLUT = [];
      +  for (let i = 0; i < SC_PERIOD; i++) {
      +    sinLUT[i] = Math.sin(i * DEG_TO_RAD * SC_PRECISION);
      +    cosLUT[i] = Math.cos(i * DEG_TO_RAD * SC_PRECISION);
      +  }
      +}
      +
      +// circle radius used for example
      +let radius;
      +
      +function setup() {
      +  createCanvas(200, 200);
      +  initSinCos(); // important call to initialize lookup tables
      +}
      +
      +function draw() {
      +  background(255);
      +  // modulate the current radius
      +  radius = 50 + 50 * sinLUT[frameCount % SC_PERIOD];
      +
      +  // draw a circle made of points (every 5 degrees)
      +  for (let i = 0; i < 360; i += 5) {
      +    // convert degrees into array index:
      +    // the modulo operator (%) ensures periodicity 
      +    let theta = int((i * SC_INV_PREC) % SC_PERIOD);
      +    // draw the circle around mouse pos
      +    point(
      +      mouseX + radius * cosLUT[theta],
      +      mouseY + radius * sinLUT[theta]
      +    );
      +  }
      +}
      \ No newline at end of file
      diff --git a/content/examples/05_steering/sine_cosine_lookup_table/style.css b/content/examples/05_steering/sine_cosine_lookup_table/style.css
      new file mode 100644
      index 00000000..9386f1c2
      --- /dev/null
      +++ b/content/examples/05_steering/sine_cosine_lookup_table/style.css
      @@ -0,0 +1,7 @@
      +html, body {
      +  margin: 0;
      +  padding: 0;
      +}
      +canvas {
      +  display: block;
      +}