diff --git a/content/00_randomness.html b/content/00_randomness.html index c548cba..02f70d6 100644 --- a/content/00_randomness.html +++ b/content/00_randomness.html @@ -73,14 +73,14 @@
  • Code will often be broken up into snippets and interspersed with explanatory text (like this text here). Individual code snippets may therefore appear unfinished. For example, the closing } bracket for the Walker class doesn’t appear until later on.
  • -

    In addition to data, classes can be defined with functionality. In this example, a Walker object has two functions, known as methods in an OOP context. While methods are essentially functions, the distinction is that methods are defined inside of a class, and so are associated with an object or class, whereas functions aren’t. I’ll try my best to use the terms consistently in this book, but it’s common for programmers to use the terms “function” and “method” interchangeably.

    +

    In addition to data, classes can be defined with functionality. In this example, a Walker object has two functions, known as methods in an OOP context. While methods are essentially functions, the distinction is that methods are defined inside of a class, and so are associated with an object or class, whereas functions aren’t. The function keyword is a nice clue: you'll see it when defining standalone functions, but it won’t appear inside a class. I’ll try my best to use the terms consistently in this book, but it’s common for programmers to use the terms “function” and “method” interchangeably.

    The first method, show(), includes the code to draw the object (as a black dot). Once again, never forget the this. when referencing the properties (variables) of that object.

      // Objects have functions.
       show() {
         stroke(0);
         point(this.x, this.y);
       }
    -

    The next method, step(), directs the Walker object to take a step. This is where things get a bit more interesting. Remember taking steps in random directions on a floor? Now I’ll use a p5.js canvas to represent that floor. There are four possible steps. A step to the right can be simulated by incrementing x with x++; to the left by decrementing x with x--; forward by going down a pixel (y++); and backward by going up a pixel (y--). But how can the code pick from these four choices?

    +

    The next method, step(), directs the Walker object to take a step. This is where things get a bit more interesting. Remember taking steps in random directions on a floor? Now I’ll use a p5.js canvas to represent that floor. There are four possible steps. A step to the right can be simulated by incrementing x with x++; to the left by decrementing x with x--; forward by going up a pixel (y--); and backward by going down a pixel (y++). But how can the code pick from these four choices?

    Earlier I stated that you could flip two coins. In p5.js, however, when you want to randomly choose from a list of options, you can simply generate a random number with the random() function. It picks a random floating point (decimal) value within any range you want.

    let choice = floor(random(4));

    Here I declare a variable choice and assign it a random integer (whole number) with a value of 0, 1, 2, or 3 by removing the decimal places from the random floating point number using floor(). Technically speaking, the number picked by calling random(4) can never be 4.0, since the top end of the range isn’t inclusive. Rather, the highest possibility is 3.999999999 (with as many 9s as JavaScript will allow), which floor() will round down to 3.

    @@ -130,10 +130,10 @@ function draw() {
    Each time you see an Example heading in this book, it means there’s a corresponding code example available in the p5 web editor and found on the book’s website. If you’re reading this book as a PDF or in print, then you'll only see screenshots of the resulting canvas.
    -

    There are a couple adjustments I could make to the random walker. For one, this Walker object’s steps are limited to four options: up, down, left, and right. But any given pixel in the canvas can be considered to have eight possible neighbors, including diagonals (see Figure I.1). A ninth possibility to stay in the same place could also be an option.

    +

    There are a couple adjustments I could make to the random walker. For one, this Walker object’s steps are limited to four options: up, down, left, and right. But any given pixel in the canvas can be considered to have eight possible neighbors, including diagonals (see Figure 0.1). A ninth possibility to stay in the same place could also be an option.

    - Figure I.1 The steps of a random walker, with and without diagonals -
    Figure I.1 The steps of a random walker, with and without diagonals
    + Figure 0.1 The steps of a random walker, with and without diagonals +
    Figure 0.1 The steps of a random walker, with and without diagonals

    To implement a Walker object that can step to any neighboring pixel (or stay put), I could pick a number between 0 and 8 (nine possible choices). However, another way to write the code would be to pick from three possible steps along the x-axis (-1, 0, or 1) and three possible steps along the y-axis.

      step() {
    @@ -154,7 +154,7 @@ function draw() {
     

    All of these variations on the “traditional” random walk have one thing in common: at any moment in time, the probability that the Walker will take a step in a given direction is equal to the probability that the Walker will take a step in any other direction. In other words, if there are four possible steps, there is a 1 in 4 (or 25 percent) chance the Walker will take any given step. With nine possible steps, it’s a 1 in 9 chance (about 11.1 percent).

    Conveniently, this is how the random() function works. p5’s random number generator (which operates behind the scenes) produces a uniform distribution of numbers. You can test this distribution with a sketch that counts each time a random number is picked and graphs it as the height of a rectangle.

    -

    Example I.2: Random number distribution

    +

    Example 0.2: Random number distribution

    @@ -162,10 +162,12 @@ function draw() {
    //{!1} An array to keep track of how often random numbers are picked
     let randomCounts = [];
    +//{!1} Total number of slots
    +let total = 20;
     
     function setup() {
       createCanvas(640, 240);
    -  for (let i = 0; i < 20; i++) {
    +  for (let i = 0; i < total; i++) {
         randomCounts[i] = 0;
       }
     }
    @@ -178,28 +180,28 @@ function draw() {
       randomCounts[index]++;
     
       stroke(0);
    -  fill(175);
    +  fill(127);
       let w = width / randomCounts.length;
       //{!3 .offset-top} Graphing the results
       for (let x = 0; x < randomCounts.length; x++) {
         rect(x * w, height - randomCounts[x], w - 1, randomCounts[x]);
       }
    -}
    -

    Notice how each bar of the graph differs slightly in height. The sample size (the number of random numbers picked) is small, so occasional discrepancies where certain numbers are picked more often emerge. Over time, with a good random number generator, this would even out.

    +}
    +

    Notice how each bar of the graph differs slightly in height. The sample size (the number of random numbers picked) is small, so occasional discrepancies where certain numbers are picked more often emerge. Over time, with a good random number generator, this would even out.

    Pseudorandom Numbers

    The random numbers from the random() function aren’t truly random; instead, they’re known as pseudorandom because they’re the result of a mathematical function that merely simulates randomness. This function would yield a pattern over time, and thus stop seeming to be random. That time period is so long, however, that random() is random enough for the examples in this book.

    Exercise I.1

    -

    Create a random walker that has a tendency to move down and to the right. (The solution follows in the next section.)

    +

    Create a random walker that has a greater tendency to move down and to the right. (The solution follows in the next section.)

    Probability and Nonuniform Distributions

    Uniform randomness often isn’t the most thoughtful solution to a design problem—in particular, the kind of problem that involves building an organic or natural-looking simulation. With a few tricks, however, the random() function can instead produce nonuniform distributions of random numbers, where some outcomes are more likely than others. This can yield more interesting, seemingly natural results.

    -

    Think about when you first started programming with p5.js. Perhaps you wanted to draw a lot of circles on the screen, so you said to yourself, “Oh, I know! I’ll draw all these circles at random positions, with random sizes and random colors.” Seeding a system with randomness is a perfectly reasonable starting point when you’re learning the basics of computer graphics, but in this book, I’m looking to build systems modeled on what we see in nature, and pure randomness won’t always cut it. Sometimes you have to put your thumb on the scales a little bit.

    +

    Think about when you first started programming with p5.js. Perhaps you wanted to draw a lot of circles on the screen, so you said to yourself, “Oh, I know! I’ll draw all these circles at random positions, with random sizes and random colors.” Seeding a system with randomness is a perfectly reasonable starting point when you’re learning the basics of computer graphics, but in this book, I’m looking to build systems modeled on what we see in nature, and uniform randomness won’t always cut it. Sometimes you have to put your thumb on the scales a little bit.

    Creating a nonuniform distribution of random numbers will come in handy throughout the book. In Chapter 9’s genetic algorithms, for example, I’ll need a methodology for performing “selection”—which members of the population should be selected to pass their DNA to the next generation? This is akin to the Darwinian concept of “survival of the fittest.” Say you have an evolving population of monkeys. Not every monkey has an equal chance of reproducing. To simulate Darwinian natural selection, you can’t simply pick two random monkeys to be parents. The more “fit” ones should be more likely to be chosen. This could be considered the “probability of the fittest.”

    -

    Let me pause here and take a look at probability’s basic principles, so I can apply more precise words to the coding examples to come. I’ll start with single-event probability—the likelihood that a given event will occur.

    -

    If you have a system with a certain number of equally likely possible outcomes, the probability of the occurrence of a given event equals the number of outcomes that qualify as that event divided by the total number of all possible outcomes. A coin toss is a simple example: it has only two possible outcomes, heads or tails. There’s only one way to flip heads, so the probability that the coin will turn up heads is one divided by two: 1/2, or 50 percent.

    +

    Let me pause here and take a look at probability’s basic principles, so I can apply more precise words to the coding examples to come. I’ll start with single-event probability—the likelihood that a given event will occur. In probability, outcomes refer to all the possible results of a random process, and an event is the specific outcome or combination of outcomes being considered.

    +

    If you have a scenario where each outcome is just as likely as the others, the probability of given event occurring equals the number of outcomes that match that event divided by the total number of all potential outcomes. A coin toss is a simple example: it has only two possible outcomes, heads or tails. There’s only one way to flip heads, so the probability that the coin will turn up heads is one divided by two: 1/2, or 50 percent.

    Take a deck of 52 cards. The probability of drawing an ace from that deck is:

    \textrm{number of aces } / \textrm{ number of cards} = 4 / 52 = 0.077 \approx 8\%

    The probability of drawing a diamond is:

    @@ -276,7 +278,7 @@ if (num < 0.6) {

    Another common use of this technique is to control the probability of an event that you want to occur sporadically in your code. For example, let’s say you create a sketch that starts a new random walker at regular time intervals (every 100 frames). With random() you could instead assign a 1 percent chance of a new walker starting. The end result is the same (a new walker every 1 out of 100 frames on average), but the latter incorporates chance and feels more dynamic and unpredictable.

    Exercise I.3

    -

    Create a random walker with dynamic probabilities. For example, can you give it a 50 percent chance of moving in the direction of the mouse?

    +

    Create a random walker with dynamic probabilities. For example, can you give it a 50 percent chance of moving in the direction of the mouse? Remember, you can use mouseX and mouseY to get the current mouse position in p5.js!

    A Normal Distribution of Random Numbers

    Another way to create a nonuniform distribution of random numbers is to use a normal distribution, where the numbers cluster around an average value. To see why this is useful, let’s go back to that population of simulated monkeys and assume your sketch generates a thousand Monkey objects, each with a random height value between 200 and 300 (as this is a world of monkeys that have heights between 200 and 300 pixels).

    @@ -292,7 +294,7 @@ if (num < 0.6) {

    In the case of height values between 200 and 300, you probably have an intuitive sense of the mean (average) as 250. However, what if I were to say that the standard deviation is 3? Or 15? What does this mean for the numbers? The graph depicted in Figure I.2 should give you a hint. On the left is a distribution with a very low standard deviation, where the majority of the values pile up around the mean (they don’t deviate much from the standard). The version on the right has a higher standard deviation, so the values are more evenly spread out from the average (they deviate more).

    -

    In the case of height values between 200 and 300, you probably have an intuitive sense of the mean (average) as 250. However, what if I were to say that the standard deviation is 3? Or 15? What does this mean for the numbers? The graph depicted in Figure I.2 should give you a hint. The standard deviation changes over time. When the animation begins, it shows a distribution with a very low standard deviation, where the majority of the values pile up around the mean (they don’t deviate much from the standard). As the standard deviation increases, the values spread out more evenly from the average (since they’re more likely to deviate).

    +

    In the case of height values between 200 and 300, you probably have an intuitive sense of the mean (average) as 250. However, what if I were to say that the standard deviation is 3? Or 15? What does this mean for the numbers? The graph depicted in Figure I.2 should give you a hint. The standard deviation changes over time. When the animation begins, it shows a high peak. This is a distribution with a very low standard deviation, where the majority of the values pile up around the mean (they don’t deviate much from the standard). As the standard deviation increases, the values spread out more evenly from the average (since they’re more likely to deviate).

    The numbers work out as follows: Given a population, 68 percent of the members of that population will have values in the range of one standard deviation from the mean, 95 percent within two standard deviations, and 99.7 percent within three standard deviations. Given a standard deviation of 5 pixels, only 0.3 percent of the monkey heights will be less than 235 pixels (three standard deviations below the mean of 250) or greater than 265 pixels (three standard deviations above the mean of 250). Meanwhile, 68 percent of monkey heights will be between 245 and 255 pixels.

    @@ -427,8 +429,8 @@ let step = 10; let stepx = random(-step, step); let stepy = random(-step, step); -x += stepx; -y += stepy; +this.x += stepx; +this.y += stepy;

    (In Chapter 1, I’ll show how to vary the step sizes more efficiently with vectors.)

    Perlin Noise (A Smoother Approach)

    @@ -456,7 +458,7 @@ circle(x, 180, 16); let x = random(0, width); // (Tempting, but this is not correct!) let x = noise(0, width); -circle(x, 180 16); +circle(x, 180, 16);

    Conceptually, this is exactly what you want to do—calculate an x-value that ranges between 0 and the width according to Perlin noise—but this isn’t the correct implementation. While the arguments to the random() function specify a range of values between a minimum and a maximum, noise() doesn’t work this way. Instead, its output range is fixed: it always returns a value between 0 and 1. You’ll see in a moment that you can get around this easily with p5’s map() function, but first let’s examine what exactly noise() expects you to pass in as an argument.

    One-dimensional Perlin noise can be thought of as a linear sequence of values over time. For example:

    @@ -514,7 +516,7 @@ function draw() { Figure I.6: Demonstrating short and long jumps in time in Perlin noise
    Figure I.6: Demonstrating short and long jumps in time in Perlin noise
    -

    Try running the code several times, incrementing t by 0.01, 0.02, 0.05, 0.1, and 0.0001, and you’ll see different results.

    +

    In the upcoming code examples that utilize Perlin noise, pay attention to how the animation changes with varying values of t.

    Noise Ranges

    Once you have noise values that range between 0 and 1, it’s up to you to map that range to whatever size suits your purpose. The easiest way to do this is with p5’s map() function (Figure I.7). It takes five arguments. First is the value you want to map, in this case n. This is followed by the value’s current range (minimum and maximum), followed by the desired range.

    @@ -548,8 +550,8 @@ function draw() { step() { //{!2} x- and y-position mapped from noise - this.x = map(noise(tx), 0, 1, 0, width); - this.y = map(noise(ty), 0, 1, 0, height); + this.x = map(noise(this.tx), 0, 1, 0, width); + this.y = map(noise(this.ty), 0, 1, 0, height); //{!2} Move forward through “time.” this.tx += 0.01; @@ -571,15 +573,15 @@ function draw() {
    - Figure I.9: Comparing neighboring Perlin noise values in one (left) and two (right) dimensions + Figure 0.9: Comparing neighboring Perlin noise values in one (left) and two (right) dimensions
    -
    Figure I.9: Comparing neighboring Perlin noise values in one (left) and two (right) dimensions
    +
    Figure 0.9: Comparing neighboring Perlin noise values in one (left) and two (right) dimensions
    -

    Two-dimensional noise works exactly the same way conceptually. The difference, of course, is that the values aren’t just written in a linear path along one row of the graph paper, but rather fill the whole grid. A given value will be similar to all of its neighbors: above, below, to the right, to the left, and along any diagonal, as in the right half of Figure I.9.

    +

    Two-dimensional noise works exactly the same way conceptually. The difference, of course, is that the values aren’t just written in a linear path along one row of the graph paper, but rather fill the whole grid. A given value will be similar to all of its neighbors: above, below, to the right, to the left, and along any diagonal, as in the right half of Figure 0.9.

    @@ -605,6 +607,8 @@ for (let x = 0; x < width; x++) { pixels[index ] = bright; pixels[index + 1] = bright; pixels[index + 2] = bright; + //{!1} Set alpha of 255 (no transparency). + pixels[index + 3] = 255; } } updatePixels(); @@ -625,10 +629,11 @@ for (let x = 0; x < width; x++) { let bright = map(noise(xoff, yoff), 0, 1, 0, 255); // Use x and y for pixel position. let index = (x + y * width) * 4; - // Setting the red, green, and blue values + // Setting the red, green, blue, alpha values pixels[index] = bright; pixels[index + 1] = bright; pixels[index + 2] = bright; + pixels[index + 3] = 255; //{!1 .bold} Increment yoff. yoff += 0.01; } @@ -637,15 +642,15 @@ for (let x = 0; x < width; x++) { }

    I have to confess, I've done something rather confusing. I've used one-dimensional noise to set two variables (this.x and this.y) controlling the 2D motion of a walker. Then, I promptly moved on to using two-dimensional noise to set one variable (bright) controlling the brightness of each pixel in the canvas. The key difference here is that for the walker, my goal is to have two independent one-dimensional noise values; it’s just a coincidence that I’m using them to move an object through two-dimensional space. The way to accomplish this is to use two different offsets (this.tx and this.ty) to pull values from different parts of the same one-dimensional noise space. Meanwhile, in the 2D noise example, both xoff and yoff start at 0 because I'm just looking for a single value (a pixel brightness) for a given point in a two-dimensional noise space. The walker is actually navigating two separate one-dimensional noise paths, whereas the pixels are single values in a two-dimensional space.

    -

    Exercise I.8

    +

    Exercise 0.8

    Play with color, noiseDetail(), and the rate at which xoff and yoff are incremented to achieve different visual effects.

    -

    Exercise I.9

    +

    Exercise 0.9

    Add a third argument to noise that increments once per cycle through draw() to animate the two-dimensional noise.

    -

    Exercise I.10

    +

    Exercise 0.10

    Use the noise values as the elevations of a landscape.

    @@ -662,10 +667,10 @@ for (let x = 0; x < width; x++) {
    -
    Figure I.11 Tree With Perlin Noise on the left and Flow field with Perlin noise on the right
    +
    Figure 0.11 Tree With Perlin Noise on the left and Flow field with Perlin noise on the right

    Onward

    -

    I’ve talked in this chapter about how it’s easy to become over reliant on pure randomness. In many ways, it’s the most obvious answer to the kinds of questions we ask continuously: How should this object move? What color should it be? This obvious answer, however, is sometimes a lazy one.

    +

    I’ve talked in this chapter about how it’s easy to become over reliant on randomness. In many ways, it’s the most obvious answer to the kinds of questions we ask continuously: How should this object move? What color should it be? This obvious answer, however, is sometimes a lazy one.

    As I finish this chapter, it’s also worth noting that you could just as easily fall into the trap of overusing Perlin noise. How should this object move? Perlin noise! What color should it be? Perlin noise! How fast should it grow? Perlin noise!

    The point of all of this is not to say that you should or shouldn’t use randomness. Or that you should or shouldn’t use Perlin noise. The point is that the rules of your system are yours to define. The larger your programming toolbox, the more choices you’ll have as you implement those rules. If all you know is randomness, then your design thinking is limited. Sure, Perlin noise helps, but you’ll need more. A lot more. The goal of this book is to fill your toolbox, so you can make more informed choices and design more thoughtful systems.

    diff --git a/content/02_forces.html b/content/02_forces.html index 85d23ef..9d4cde2 100644 --- a/content/02_forces.html +++ b/content/02_forces.html @@ -922,7 +922,7 @@ class Body { //{inline} All the other stuff from before. - //${!7} The attract method is now part of the Body class. + //{!7} The attract method is now part of the Body class. attract(body) { let force = p5.Vector.sub(this.position, body.position); let d = constrain(force.mag(), 5, 25); diff --git a/content/04_particles.html b/content/04_particles.html index f4bda9b..2372c00 100644 --- a/content/04_particles.html +++ b/content/04_particles.html @@ -245,7 +245,7 @@ function draw() {
    - + diff --git a/content/06_libraries.html b/content/06_libraries.html index 88a835e..441f9a8 100644 --- a/content/06_libraries.html +++ b/content/06_libraries.html @@ -1,54 +1,59 @@

    Chapter 6. Physics Libraries

    -

    “A library implies an act of faith/Which generations still in darkness hid/Sign in their night in witness of the dawn.”

    +

    “A library implies an act of faith / Which generations still in darkness hid / Sign in their night in witness of the dawn.”

    — Victor Hugo

    -

    Let’s review a quick summary of you’ve accomplished in the first five chapters of this book.

    +

    Think about what you’ve accomplished so far in this book. You’ve:

      -
    1. Learned about concepts from the world of physics — What is a vector? What is a force? What is a wave?
    2. -
    3. Understood the math and algorithms behind such concepts.
    4. -
    5. Implemented the algorithms in p5.js with an object-oriented approach, culminating in building simulations of autonomous steering agents.
    6. +
    7. Learned about concepts from the world of physics. (What is a vector? What is a force? What is a wave?)
    8. +
    9. Understood the math and algorithms behind those concepts.
    10. +
    11. Implemented those algorithms in p5.js with an object-oriented approach, culminating in building simulations of autonomous steering agents.
    -

    These activities have yielded a set of motion simulation examples, allowing you to creatively define the physics of the worlds you build (whether realistic or fantastical). Of course, we’re not the first to do this. The world of computer graphics and programming is full of source code dedicated to physics simulations. Just try searching “open-source physics engine” and you could spend the rest of your day pouring over rich and complex code. This begs the question: If a code library takes care of physics simulation, why should you bother learning how to write any of the algorithms yourself?

    -

    Here is where the philosophy behind this book comes into play. While many of the libraries out there provide “out of the box” physics (and super awesome sophisticate and robus physics at that), there are significant reasons for learning the fundamentals before diving into libraries. First, without an understanding of vectors, forces, and trigonometry, you’d likely be lost just reading the documentation of a library. Second, even though a library may take care of the math behind the scenes, it won’t necessarily simplify your code. There can be a great deal of overhead in understanding how a library works and what it expects from you code-wise. Finally, as wonderful as a physics engine might be, if you look deep down into your hearts, it’s likely that you seek to create worlds and visualizations that stretch the limits of imagination. A library is great, but it provides a limited set of features. It’s important to know both when to live within those limitations in the pursuit of a creative coding project and when those limits prove to be confining.

    -

    This chapter is dedicated to examining two open-source physics libraries for JavaScript—matter.js and the toxiclibs.js. This is not to say that these are the only libraries I specifically recommend for any and all creative coding projects that merit the use of a physics engine. Both, however, integrate nicely with p5.js and will allow me to demonstrate the fundamental concepts behind physics engines and how they relate to and build upon the material from the first five chapters of this book.

    -

    There are a multitude of other physics libraries worth exploring alongside these two case studies. One that I would highly recommend is p5play, a project that was initiated by Paolo Pedercini and currently led by Quinton Ashley. p5play was specifically designed for game development and simplifies the creation of visual objects—known as “sprites”—and manages their interactions, namely “collisions” and "overlaps". As you may have guessed from the name, it’s tailored to work seamlessly with p5.js. It uses Box2D for physics simulation, which I’ll discuss in the next section.

    -

    Each physics library has its own strengths, and may offer unique advantages for specific projects. The aim of this chapter isn't to limit you to matter.js and toxiclibs.js, but to provide you with a foundation in working with physics libraries. The skills you acquire here will enable you to navigate and understand documentation, opening the door to expanding your abilities with any library you choose. Check the book’s website for ports of the examples in this chapter to other libraries.

    -

    What is Matter.js?

    -

    When I first began writing this book, matter.js did not exist! The physics engine I used to demonstrate the examples at the time was (and likely still is) the most well known of them all: Box2D. Box2D began as a set of physics tutorials written in C++ by Erin Catto for the Game Developer’s Conference in 2006. Since then it has evolved into a rich and elaborate open-source physics engine. It’s been used for countless projects, most notably highly successful games such as the award-winning Crayon Physics and the runaway hit Angry Birds.

    -

    One of the key things about Box2D is that it is a true physics engine. Box2D knows nothing about computer graphics and the world of pixels. All of Box2D’s measurements and calculations are real-world measurements (meters, kilograms, seconds)—only its “world” is a two-dimensional plane with top, bottom, left, and right edges. You tell it things like: “The gravity of the world is 9.81 newtons per kilogram, and a circle with a radius of four meters and a mass of fifty kilograms is located ten meters above the world’s bottom.” Box2D will then tell you things like: “One second later, the rectangle is at five meters from the bottom; two seconds later, it is ten meters below,” and so on. While this provides for an amazing and realistic physics engine, it also necessitates lots of complicated code in order to translate back and forth between the physics “world” (a key term in Box2D) and the world you want to draw — the “pixel” world of graphics canvas.

    -

    While this makes for an incredibly accurate and robust library (it’s also highly optimized and fast for c++ projects), it creates a tremendous burden for the coder. I will, as best I can, continue to maintain a set of Box2D compatible examples for this book (there are several JavaScript ports), but I believe the relative simplicity of working with a library that is native to JavaScript and uses pixels as the unit of measurement will make for a more intuitive and friendly bridge from my p5.js examples.

    -

    Even so, anytime you add yet another JavaScript framework or library to a project, it introduces additional complexity and code. When is it worth it to have this additional overhead? If you just want to simulate a circle falling down a canvas gravity, do you really need to import an entire physics engine and learn its API? I would say, the answer is no as demonstrated in the first chapter of this book. Let’s consider another scenario. What if you want to have a hundred of those circles falling? And what if those circles aren’t circles at all, but irregularly shaped polygons? And what if you want these polygons to bounce off each other in a realistic manner when they collide?

    -

    You may have noticed that the first four chapters of this book, while covering motion and forces in detail, skipped over a rather important aspect of physics simulation—collisions. Let’s pretend for a moment that you aren’t reading a chapter about physics libraries and that I decided right now to cover how to handle collisions in a particle system. I’d have to introduce and cover two distinct algorithms that address these questions:

    +

    These activities have yielded a set of motion simulations, allowing you to creatively define the physics of the worlds you build (whether realistic or fantastical). But of course, you and I aren’t the first or only people to do this. The world of computer graphics and programming is full of prewritten code libraries dedicated to physics simulations.

    +

    Just try searching “open-source physics engine” and you could spend the rest of your day pouring over a host of rich and complex code bases. This begs the question: If an existing code library takes care of physics simulation, why should you bother learning how to write any of the algorithms yourself? Here’s where the philosophy behind this book comes into play. While many of the libraries out there provide “out of the box” physics to experiment with (super awesome, sophisticate, and robust physics at that), there are several good reasons for learning the fundamentals from scratch before diving into such libraries.

    +

    First, without an understanding of vectors, forces, and trigonometry, it’s easy to get lost just reading the documentation of a library, let alone using it. Second, even though a library may take care of the math behind the scenes, it won’t necessarily simplify your code. There can be a great deal of overhead in understanding how a library works and what it expects from you code-wise. Finally, as wonderful as a physics engine might be, if you look deep down into your heart, it’s likely that you seek to create worlds and visualizations that stretch the limits of the imagination. A library may be great, but it only provides a limited set of features. It’s important to know both when to live within those limitations in the pursuit of a creative coding project and when those limits will prove to be confining.

    +

    This chapter is dedicated to examining two open-source physics libraries for JavaScript: Matter.js and toxiclibs.js. I don’t mean to imply that these are the only libraries you should use for any and all creative coding projects that could benefit from a physics engine (see the “Other Physics Libraries” box for some alternatives, and check the book’s website for ports of the chapter’s examples to other libraries). However, they both integrate nicely with p5.js and will allow me to demonstrate the fundamental concepts behind physics engines and how they relate to and build upon the material I’ve covered so far.

    +

    Ultimately, the aim of this chapter isn't to teach you the details of a specific physics library, but to provide you with a foundation for working with any physics library. The skills you acquire here will enable you to navigate and understand documentation, opening the door for you to expand your abilities with any library you choose.

    +

    Why Use a Physics Library?

    +

    I’ve made the case for writing your own physics simulations (as you’ve learned to do in the previous chapters), but what’s the case for using a physics library? After all, anytime you add an external framework or library to a project, it introduces complexity and extra code. Is that additional overhead really worth it? If you just want to simulate a circle falling down due to gravity, for example, do you really need to import an entire physics engine and learn its API? As the early chapters of this book hopefully demonstrated, probably not. There are lots of scenarios like this that are simple enough for you to get by writing the code yourself.

    +

    But consider another scenario. What if you want to have 100 circles falling? And what if they actually aren’t circles at all, but rather irregularly shaped polygons? And what if you want these polygons to bounce off each other in a realistic manner when they collide?

    +

    You may have noticed that while I’ve covered motion and forces in detail, I’ve so far skipped over a rather important aspect of physics simulation: collisions. Let’s pretend for a moment that you aren’t reading a chapter about physics libraries and that I’ve decided right now to explain how to handle collisions in a particle system. I’d have to cover two distinct algorithms that address two questions:

      -
    1. How do I determine if two shapes are colliding (i.e. intersecting)? This is known as “collision detection.”
    2. -
    3. How do I determine the shapes’ velocity after the collision? This is known as “collision resolution.”
    4. +
    5. How do I determine if two shapes are colliding (or intersecting)? This is known as collision detection.
    6. +
    7. How do I determine the shapes’ velocities after the collision? This is known as collision resolution.
    -

    If those shapes are rectangles or circles, question #1 isn’t too tough. You’ve likely encountered this before. For example, two circles are intersecting if the distance between them is less than the sum of their radii.

    +

    If you’re working with simple geometric shapes, question #1 isn’t too tough. In fact, perhaps you’ve encountered it before. With two circles, for instance, you know they’re intersecting if the distance between their centers is less than the sum of their radii (see Figure 6.1).

    - Figure 6.1: How to determine if two circles are colliding. Note “r” is short for “radius.” -
    Figure 6.1: How to determine if two circles are colliding. Note “r” is short for “radius.”
    + Figure 6.1: Two circles with radii r_1 and r_2 are colliding if the distance between them is less than r_1 + r_2. +
    Figure 6.1: Two circles with radii r_1 and r_2 are colliding if the distance between them is less than r_1 + r_2.
    -

    OK. Now that you know how to determine if two circles are colliding, how about calculating their velocities after the collision? This is where I’m going to stop the discussion. Why, you ask? It’s not that understanding the math behind collisions isn’t important or valuable. (In fact, I’m including additional examples on the website related to collisions without a physics library.) The reason for stopping is that life is short (let this also be a reason for you to consider going outside and frolicking instead of programming altogether). You can’t expect to master every detail of physics simulation. And while you might enjoy this discussion for circles, it’s only going to lead to wanting to work with rectangles. And strangely shaped polygons. And curved surfaces. And swinging pendulums colliding with springy springs. And and and and and.

    -

    Working with collisions in a p5.js sketch while still having time to spend with friends and family—that’s the reason for this chapter. Erin Catto spent years developing solutions to these kinds of problems with Box2D and Liam has built a beautiful JavaScript library with matter.js so there’s no need to re-invent the proverbial wheel, at least for now.

    -

    In conclusion, if you find yourself describing an idea for a p5.js sketch and the word “collisions” comes up, then it’s likely time to learn a physics engine.

    -

    Importing Matter.js Library

    -

    There are a variety of ways to incorporate a JavaScript library into a project. For this book’s demonstrations, as you already quite aware, I’m using the official p5.js web editor for developing and sharing the code examples. The easiest way to add a library besides is to edit the index.html file that is part of every p5.js sketch.

    -

    This can be accomplished by opening the file navigation on the left hand side of the editor and selecting index.html.

    +

    That’s easy enough, but how about calculating the circles’ velocities after the collision? This is where I’m going to stop the discussion. Why, you ask? It’s not that understanding the math behind collisions isn’t important or valuable. (In fact, I’m including additional examples on the website related to collisions without a physics library.) The reason for stopping is that life is short! (Let this also be a reason for you to consider going outside and frolicking for a bit before sitting down to write your next sketch.) You can’t expect to master every detail of physics simulation. And while you might enjoy learning about collision resolution for circles, it’s only going to make you want to work with rectangles next. And then with strangely shaped polygons. And then curved surfaces. And then swinging pendulums colliding with springy springs. And then, and then, and then . . .

    +

    Incorporating collisions into a p5.js sketch while still having time to spend with friends and family—that’s the reason for this chapter. People have spent years developing solutions to these kinds of problems, and beautiful JavaScript libraries like Matter.js and toxiclibs.js are the fruits of those efforts. There’s no need to reinvent the proverbial wheel, at least for now.

    +

    In conclusion, if you find yourself describing an idea for a p5.js sketch and the word “collisions” comes up, then it’s likely time to learn to use a physics engine.

    +
    +

    Other Physics Libraries

    +

    There are a multitude of other physics libraries worth exploring alongside this chapter’s two case studies, each with unique strengths that may offer advantages in certain kinds of projects. In fact, when I first began writing this book, Matter.js didn’t exist, so the physics engine I initially used to demonstrate the examples was Box2D. It was (and likely still is) the most well known physics engine of them all. It began as a set of physics tutorials written in C++ by Erin Catto for the Game Developer’s Conference in 2006. Since then Box2D has evolved into a rich and elaborate open-source physics engine. It’s been used for countless projects, most notably highly successful games such as the award-winning Crayon Physics and the runaway hit Angry Birds.

    +

    One important feature of Box2D is that it’s a true physics engine: it knows nothing about computer graphics and the world of pixels, and instead does all its measurements and calculations in real-world units like meters, kilograms, and seconds. It’s just that its “world” (a key term in Box2D) is a two-dimensional plane with top, bottom, left, and right edges. You tell it things like: “The gravity of the world is 9.81 newtons per kilogram, and a circle with a radius of 4 meters and a mass of 50 kilograms is located 10 meters above the world’s bottom.” Box2D will then tell you things like: “One second later, the rectangle is at 5 meters from the bottom; two seconds later, it’s 10 meters below,” and so on.

    +

    While this provides for an amazingly accurate and robust physics engine (one that’s highly optimized and fast for C++ projects), it also necessitates lots of complicated code in order to translate back and forth between Box2D’s physics “world” and the world you want to draw —the pixel world of graphics canvas. This creates a tremendous burden for the coder. I will, as best I can, continue to maintain a set of Box2D-compatible examples for this book (there are several JavaScript ports), but I believe the relative simplicity of working with a library like Matter.js that’s native to JavaScript and uses pixels as the unit of measurement will make for a more intuitive and friendly bridge from my p5.js examples.

    +

    Another notable library is p5play, a project initiated by Paolo Pedercini and currently led by Quinton Ashley that was specifically designed for game development. It simplifies the creation of visual objects—known as “sprites”—and manages their interactions, namely collisions and overlaps. As you may have guessed from the name, p5play is tailored to work seamlessly with p5.js. It uses Box2D under the hood for physics simulation.

    +
    +

    Importing the Matter.js Library

    +

    In a moment, I’ll turn to working with Matter.js, created by Liam [Last Name] in 2014. But before you can use an extrnal JavaScript library in a p5.js project, you need to import it into your sketch. As you’re already quite aware, I’m using the official p5.js web editor for developing and sharing this book’s code examples. The easiest way to add a library is to edit the index.html file that’s part of every new p5.js sketch created in the editor.

    +

    To do that, first expand the file navigation bar on the lefthand side of the editor and select index.html, as shown in Figure 6.X.

    - -
    + Figure 6.X: Accessing a sketch’s index.html file +
    Figure 6.X: Accessing a sketch’s index.html file
    -

    There, you’ll find a series of <script> tags inside the HTML tags <head> and </head>. This is how JavaScript libraries are referenced in a p5.js sketch. It’s no different than including sketch.js or particle.js in the page’s <body>. Only here, instead of keeping and editing a copy of the JavaScript code itself, a library is referenced with a url of a “CDN.” A “CDN” is a “content delivery network” or, more plainly, a server hosting files. For JS libraries that are used across hundreds of thousands of web pages with millions upon millions of users accessing these pages, they need to be pretty good at their job of serving up these libraries.

    -

    In the page, you’ll see the CDN for p5.js (it may be a later version by the time you are reading this!)

    -
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
    -

    To use matter.js, just add right below p5, a reference to its CDN.

    -
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
    +

    The file includes a series of <script> tags inside the HTML tags <head> and </head>. This is how JavaScript libraries are referenced in a p5.js sketch. It’s no different than including sketch.js or particle.js in the page’s <body>, only here, instead of keeping and editing a copy of the JavaScript code itself, the library is referenced with a URL of a content delivery network (CDN). This is a type of server for hosting files. For JavaScript libraries that are used across hundreds of thousands of web pages that millions upon millions of users access, CDNs need to be pretty good at their job of serving up these libraries.

    +

    You should already see a <script> tag referencing the CDN for p5.js itself (it may be a later version by the time you are reading this):

    +
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.js"></script>
    +

    To use Matter.js, just add another <script> tag referencing its CDN right below the one for p5:

    +
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js" integrity="sha512-5T245ZTH0m0RfONiFm2NF0zcYcmAuNzcGyPSQ18j8Bs5Pbfhp5HP1hosrR8XRt5M3kSRqzjNMYpm2+it/AUX/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    -

    At the time of this writing, the most recent version of matter.js is 0.18.0 and that’s what you’ll see referenced in the above snippet. As matter.js updates and new versions are released, it’s often a good idea to upgrade, but by referencing a specific version that you know works with your sketch, you don’t have to worry about new features of the library breaking your existing code.

    +

    At the time of this writing, the most recent version of Matter.js was 0.18.0, and that’s what I’ve referenced in the above snippet. As Matter.js updates and new versions are released, it’s often a good idea to upgrade, but by referencing a specific version that you know works with your sketch, you don’t have to worry about new features of the library breaking your existing code.

    Matter.js Overview

    -

    Do not despair! I really am going to get to the code very soon, and in some ways I’ll blow the previous work out of the water. But before I’m ready to do that, it’s important to walk through the overall process of using matter.js (or any physics engine) in p5.js. Let’s begin by writing a pseudo-code generalization of all of the examples in Chapters 1 through 5.

    +

    When you use Matter.js (or any physics engine) in p5.js, your code ends up looking a bit different. Here’s a pseudocode generalization of all of the examples in Chapters 1 through 5:

    SETUP:

    1. Create all the objects in the world.
    2. @@ -58,31 +63,33 @@
    3. Calculate all the forces in the world.
    4. Apply all the forces to the objects (F = M * A).
    5. Update the positions of all the objects based on their acceleration.
    6. -
    7. Draw all of the objects.
    8. +
    9. Draw all the objects.
    -

    Great. Let’s rewrite this pseudocode as it will appear in the matter.js examples.

    +

    By contrast, here’s the pseudocode for how a Matter.js example will appear:

    SETUP:

    1. Create all the objects in the world.

    DRAW:

      -
    1. Draw all of the objects.
    2. +
    3. Draw all the objects.
    -

    This, of course, is the fantasy of a physics engine. I’ve eliminated all of those painful steps of figuring out how the objects are moving according to velocity and acceleration. Matter.js is going to take care of this for us! While there will be more details to reveal, the good news is that this does in fact accurately reflect the overall process. Let’s imagine Matter as a magic box.

    -

    In setup(), I’m going to say to Matter: “Hello there. Here are all of the things I want in my world.” In draw(), I’m going to politely ask Matter: “Oh, hello again. If it’s not too much trouble, I’d like to draw all of those things in my world. Could you tell me where they are?”

    -

    The bad news: it’s not as simple as the above explanation would lead you to believe. Making the stuff that goes in the matter.js world involves several steps related to how different kinds of shapes are built and configured. And it’s also necessary to learn to speak the language of matter.js in terms of how the various forces and other parameters of the world are configured. Here are the core concepts:

    +

    This, of course, is the allure of a physics engine. I’ve eliminated all of those painful steps of figuring out how the objects are moving according to velocity and acceleration. Matter.js is going to take care of this for me!

    +

    While there will be more details to reveal, the good news is that the simplicity of this pseudocode is an accurate reflect the overall process. In this sense, Matter is a bit like a magic box. In setup(), I’m going to say to Matter: “Hello there. Here are all of the things I want in my world.” Then, in draw(), I’m going to politely ask Matter: “Oh, hello again. If it’s not too much trouble, I’d like to draw all of those things in my world. Could you please tell me where they are?”

    +

    The bad news: it’s not quite as simple as the pseudocode might lead you to believe. Actually making the stuff that goes into the Matter.js world involves several steps related to how different kinds of shapes are built and configured. It’s also necessary to learn to speak the language of Matter.js in terms of how the various forces and other parameters of the world are configured. Here are the core concepts:

      -
    1. Engine: The engine is the entity that manages the physics simulation itself, it holds onto the “world” of the simulation as well as various properties about how the world is updated over time.
    2. -
    3. Body: Serves as the primary element in the world. It has a position. It has a velocity. Sound familiar? The Body is essentially the class I’ve been building all throughout chapters 1-5. It also has geometry to define its shape. It’s important to note that “body” is a generic term used by physics engines to describe a “thing” in the world (similarly to the term “particle”) and is not related to an anthropomorphic body.
    4. -
    5. Composite: A composite is a container that allows for the creation of complex entities (made up of multiple bodies.) The world itself is an example a Composite, and every Body created has be added to the world!
    6. -
    7. Constraint: Acts as a connection between two bodies.
    8. +
    9. Engine. The entity that manages the physics simulation itself. The engine holds on to the “world” of the simulation as well as various properties about how the world is updated over time.
    10. +
    11. Body. Serves as the primary element in the world, corresponding to the physical objects being simulated. It has a position. It has a velocity. Sound familiar? It’s basically another version of the class I’ve been building all throughout Chapters 1 through 5. It also has geometry to define its shape. It’s important to note that “body” is a generic term that physics engines use to describe a “thing” in the world (similarly to the term “particle”); it isn’t related to an anthropomorphic body.
    12. +
    13. Composite. A container that allows for the creation of complex entities (made up of multiple bodies). The world itself is an example a composite, and every body created has be added to the world.
    14. +
    15. Constraint. Acts as a connection between two bodies.
    -

    In the next four sections, I am going to walk through each of the above elements in detail, building several examples along the way. But first there is one other important element to briefly discuss.

    -

    5. Vector: Describes a vector in a matter.js world.

    -

    And so here we are, arriving with trepidation at an unfortunate truth in the world of using physics libraries. Any physics simulation is going to involve the concept of a vector. This is the good part. After all, you just spent several chapters familiarizing yourself with what it means to describe motion and forces with vectors. There is nothing new to learn conceptually.

    -

    Now for the part that makes the single tear fall from my eye: you don’t get to use p5.Vector. It’s nice that p5.js has createVector(), but anytime you use a physics library you will likely discover that it includes its own vector implementation. This makes sense, after all; why should matter.js be expected to know about p5.Vectorobjects? In most cases, the physics engine implements its vector class in a specific way so that it is especially compatible with the rest of the library’s code. So while you won’t have to learn anything new conceptually, you do have to get used to some new naming conventions and syntax. Let’s quickly demonstrate a few of the basics in Matter.Vector as compared to those in p5.Vector.

    -

    How do you create a Vector?

    +

    In the coming sections, I’ll walk through each of these elements in detail, building several examples along the way. But first, there’s one other important element to briefly discuss.

    +
      +
    1. Vector. Describes a vector in a Matter.js world.
    2. +
    +

    This brings us to an important crossroads. Any physics library is going to involve the concept of a vector, and depending on how you spin it, that’s either a good or a bad thing. The good part is that you’ve just spent several chapters familiarizing yourself with what it means to describe motion and forces with vectors, so there’s nothing conceptually new for you to learn. The bad part—the part that makes a single tear fall from my eye—is that once you cross this threshold into the brave new world of physics libraries, you don’t get to use p5.Vector anymore.

    +

    It’s been great that p5.js has a built-in vector representation, but anytime you use a physics library, you’ll likely discover that it includes its own separate vector implementation, designed to be especially compatible with the rest of the library’s code. This makes sense. After all, why should Matter.js be expected to know about p5.Vectorobjects?

    +

    The upshot of all this is that while you won’t have to learn any new concepts, you do have to get used to some new naming conventions and syntax. To illustrate, I’ll show you some now-familiar p5.Vector operations alongside the equivalent Matter.Vector code. First, how do you create a vector?

    Value of ii Particle Action
    @@ -101,7 +108,7 @@
    -

    Let’s say you want to add two vectors together.

    +

    What about adding two vectors together?

    @@ -122,6 +129,17 @@ let b = Matter.Vector.create(3, 4); Matter.Vector.add(a, b, a); + +
    +

    That overwrites vector a with the result. Here’s how to put the result in a separate vector instead.

    + + + + + + + + @@ -180,15 +197,15 @@ v = Matter.Vector.normalise(v);
    p5.jsMatter.js
    let a = createVector(1, -1);
    @@ -151,8 +169,7 @@ let c = Matter.Vector.add(a, b);
    v.mult(4);
    -
    let v = Matter.Vector.create(1, -1);
    -v = Matter.Vector.mult(4);
    +
    let v = Matter.Vector.create(1, -1);
    v = Matter.Vector.mult(v, 4);
    -

    As you can see, the concepts are the same, but the function names and the arguments are different. First, everything is “name-spaced” under Matter.Vector. This is common for JavaScript libraries, p5.js is the unusual one in this regard. In p5.js to draw a circle, you call circle() rather than p5.circle(). The circle() function lives in the “global” namespace. This, in my view, is one of the things that makes p5.js special in terms of ease of use and beginner friendliness. However, it also means that for any code that you write with p5, you cannot use circle as a variable name. Name-spacing a library protects against these kinds of errors and conflicts and is why you see everything called with the Matter prefix.

    -

    In addition, unlike p5’s static and non-static versions of vector functions like add() and mult(), all vector functions in Matter are static. If you want to change a Matter.Vector while operating on it, you can add it as an optional argument: Matter.Vector.add(a, b, a): adds a and b and places the result in a. You can also set an existing variable to the newly created vector object as in v = Matter.Vector.mult(v, 2) however this still version creates a new vector in memory.

    -

    I’ll cover the basics of what you need to know for working with Matter.Vector in this chapter, but for more, full documentation can be found on the matter.js website.

    -

    Matter.js: Engine

    -

    Many physics libraries include a “world” object to manage everything. The world is typically in charge of the coordinate space, keeping a list of all the bodies in the world, controlling time, and more. In matter.js, the “world” is created inside of an Engine object, the main controller of your physics world and simulation.

    -
    // An "alias" for Matter.js Engine class
    +

    As you can see, the concepts are the same, but the specifics of the code are different. First, every method name is now preceded by Matter.Vector, which defines the namespace of the source code. This is common for JavaScript libraries; p5.js is the unusual one for not consistently using namespaces. For example, to draw a circle in p5.js, you call circle() rather than p5.circle(). The circle() function lives in the “global” namespace. This, in my view, is one of the features that makes p5.js special in terms of ease of use and beginner friendliness. However, it also means that for any code you write with p5, you can’t use circle as a variable name. Namespacing a library protects against these kinds of errors and naming conflicts, and it’s why you’ll see everything in Matter.js called with the Matter prefix.

    +

    In addition, unlike p5’s static and non-static versions of vector methods like add() and mult(), all vector methods in Matter are static. If you want to change a Matter.Vector while operating on it, you can add it as an optional argument: Matter.Vector.add(a, b, a): adds a and b and places the result in a (the third argument). You can also set an existing variable to the newly created vector object resulting from a calculation, as in v = Matter.Vector.mult(v, 2). However, this version still creates a new vector in memory rather than updating the old one.

    +

    I’ll cover more of the basics of what you need to know for working with Matter.Vector in this chapter, but for more, full documentation can be found on the Matter.js website.

    +

    Engine

    +

    Many physics libraries include a “world” object to manage everything. The world is typically in charge of the coordinate space, keeping a list of all the bodies in the simulation, controlling time, and more. In Matter.js, the “world” is created inside of an Engine object, the main controller of your physics world and simulation.

    +
    // An "alias" for the Matter.js Engine class
     let Engine = Matter.Engine;
     
    -// A reference to the matter physics engine
    +// A reference to the Matter physics engine
     let engine;
     
     function setup() {
    @@ -196,60 +213,60 @@ function setup() {
       // Create the Matter engine
       engine = Engine.create();
     }
    -

    Notice how the very first line of code creates a variable called Engine and sets it equal to Matter.Engine. Here, I am deciding to point the single keyword Engine to the Engine class namespaced inside matter.js. I am making this decision because I know that I will not be using the word Engine for any other variables (nor does it conflict with something in p5.js) and want to be able to keep my code less verbose. I’ll be doing this with Vector, Bodies, Composite, and more as I continue to build the examples. (But while the linked source code will always include all the alias’s, I won’t always include them in the book text itself.)

    +

    Notice how the very first line of code creates an Engine variable and sets it equal to Matter.Engine. Here, I’m deciding to point the single keyword Engine to the Engine class namespaced inside Matter.js in order to make my code less verbose. This works because I know I won’t be using the word Engine for any other variables, nor does it conflict with something in p5.js. I’ll be doing this with Vector, Bodies, Composite, and more as I continue to build the examples. (But while the linked source code will always include all the aliases, I won’t always show them in the book text itself.)

    -

    - Object Destructuring - Object destructuring in JavaScript is a technique to extract properties from an object and assign them to variables. In the case of matter.js, the Matterobject contains the Engine property. An alias can be set with let Engine = Matter.Engine, however, by with destructuring, the Enginecan be accessed more concisely: let { Engine } = Matter. In the examples in this chapter, I will create multiple aliases with this methodology. -

    +

    Object Destructuring

    +

    Object destructuring in JavaScript is a technique for extracting properties from an object and assigning them to variables. In the case of Matter.js, the Matter object contains the Engine property. Normally, an alias for this property can be set with let Engine = Matter.Engine, but with destructuring, the alias can be created more concisely:

    +
    let { Engine } = Matter 
    +

    This syntax really shines when you need to create aliases to multiple properties of the same object:

    // Using Object Destructuring to extract aliases for Engine and Vector
     let { Engine, Vector } = Matter;
    +

    This sets up Engine as an alias for Matter.Engine and Vector as an alias for Matter.Vector, all in one statement. I’ll use this technique throughout the chapter’s examples.

    -

    When you call create() on Engine, matter.js returns a new physics engine and world with a default gravity, a vector (0,1) pointing down, however, you can alter it by accessing the gravity variable itself.

    +

    When you call create() on Engine, Matter.js returns a new physics engine and world with a default gravity—a vector (0,1) pointing down. You can change this default by accessing the gravity variable itself:

      // Changing the engine's gravity to point horizontally
       engine.gravity.x = 1;
       engine.gravity.y = 0;
    -

    It’s worth noting that gravity doesn’t have to be fixed; you can adjust the gravity vector while your program is running. Gravity can be turned off by setting it to a (0,0) vector.

    +

    Of course, gravity doesn’t have to be fixed for the duration of the simulation; you can adjust the gravity vector while your program is running. You can also turn gravity off altogether by setting it to a (0,0) vector.

    Once the world is initialized, it’s time to actually put stuff in it—bodies!

    -

    Matter.js: Bodies

    -

    A body is the primary element in the matter.js world. It’s the equivalent to the Mover / Particle / Vehicle class I built in previous chapters—the thing that moves around the space and experiences forces. It can also be static (meaning fixed and not moving).

    -

    Matter.js bodies are created using “factory” methods found in Matter.Bodies. A “factory” method is a function that creates an object. While you probably more familiar with calling a constructor to create an object, e.g. new Particle(), you’ve seen factory methods before! For example, createVector() is a factory method for creating a p5.Vector object. Whether an object is created from a constructor or a factory method, is a matter of style and design choice by a library creator.

    -

    All of the factory methods can be found in the Bodies documentation page. Let’s start by looking at rectangle(). And remember, the code below only works because I am assuming an alias to Matter.Bodies with Bodies.

    +

    Bodies

    +

    The body is the primary element in the Matter.js world. It’s the equivalent to the Vehicle née Particle née Mover class I built in previous chapters—the thing that moves around the space and experiences forces. A body can also be static (meaning fixed and not moving).

    +

    Matter.js bodies are created using “factory” methods found in Matter.Bodies, with different methods available for creating different kinds of bodies. A factory method is a function that creates an object. While you’re probably more familiar with calling a constructor to create an object, for example with new Particle(), you’ve seen factory methods before: createVector() is a factory method for creating a p5.Vector object. Whether an object is created from a constructor or a factory method, is a matter of style and design choice by a library creator.

    +

    All of the factory methods for creating bodies can be found in the Matter.Bodies documentation page. I’ll start with the rectangle() method.

    // Create a Matter.js Body with a rectangular shape
     let box = Bodies.rectangle(x, y, w, h);
    -

    Lucky us, the rectangle() function signature is exactly the same as p5.js’s rect() function! Only in this case, it’s not drawing a rectangle but rather is building the geometry for a Body object to store.

    -

    The body is now created and a reference is stored in the variable box. Bodies have many more properties that affect its motion. There is density, which ultimately determines that body’s mass. Friction and restitution (“bounciness”) affect how the body interacts when it comes into contact with other bodies. For most cases, the defaults are sufficient, but matter.js does allow you to specify these properties by passing through an additional argument in the form of a JavaScript object literal.

    -
    // Specify additional properties of this matter.js Body
    +

    What luck! The rectangle()method signature is exactly the same as p5.js’s rect() function. In this case, however, the method isn’t drawing a rectangle but rather building the geometry for a Body object to store. (Note that calling Bodies.rectangle() only works if you first establish Bodies as an alias to Matter.Bodies.)

    +

    A body has now been created with a position and a size, and a reference to it is stored in the variable box. Bodies have many more properties that affect its motion, however. For example, there’s density, which ultimately determines that body’s mass. Friction and restitution (“bounciness”) affect how the body interacts when it comes into contact with other bodies. For most cases, the defaults are sufficient, but Matter.js does allow you to specify these properties by passing through an additional argument to the factory method in the form of a JavaScript object literal.

    +
    //{!5} Specify additional properties of this Matter.js Body
     let options = {
       friction: 0.5,    
       restitution: 0.8,
       density: 0.002  
     }
     let box = Matter.Bodies.rectangle(x, y, w, h, options);
    -

    While the options variable is useful for configuring the the body, other initial conditions, such as linear or angular velocity, can be called via methods static methods of the Matter.Body class.

    +

    While the options variable is useful for configuring the body, other initial conditions, such as linear or angular velocity, can be set via static methods of the Matter.Body class.

    // Setting arbitrary initial linear and angular velocity
     const v = Vector.create(2, 0);
     Body.setVelocity(box, v);
     Body.setAngularVelocity(box, 0.1);
    -

    Creating a body and storing it in a variable is not the last step, however. Any body must explicitly be added to the “world” in order for it to be simulated with physics. Remember, the physics world is a Composite object called world stored inside on the engine itself. The box can be added to that world with the static add() method

    +

    Creating a body and storing it in a variable isn’t enough. Any body must be explicitly added to the “world” in order for it to be simulated with physics. The physics world is a Composite object called world stored inside on the engine itself. The box can be added to that world with the static add() method

    // Add the box object to the engine's world
     Composite.add(engine.world, box);
    -

    The above is easy to forget and a mistake that I’ve made on countless occasions. If you are ever wondering why one of your objects doesn’t appear or move along with the world’s physics, always check if you’ve actually added it to the world!

    +

    This extra step is easy to forget—it’s a mistake I’ve made on countless occasions. If you’re ever wondering why one of your objects doesn’t appear or move along with the world’s physics, always check if you’ve actually added it to the world!

    Exercise 6.1

    Knowing what you know about Matter.js so far, fill in the blank in the code below that demonstrates how to make a circular body.

    -
    // Specify additional properties of this Matter.js Body
    -let options = {
    +  
    let options = {
       friction: 0.5,    
       restitution: 0.8,
     }
     let ball = Bodies.circle(x, y, radius, options);
    -

    Matter.js: Render

    -

    Once a body is added the world, matter.js will always know it’s there, check it for collisions, and move it appropriately according to other forces in the environment. It’ll do all that for you without you having to lift a finger! The question therefore is how do you know where the box is at any given moment in order to draw it?

    -

    In the next section, I’m going to cover how to query matter.js in order to render the world with p5.js. How that works is fundamental to being able to design and visualize your own creations. This is your time to shine. You can be the designer of your world, and politely ask matter.js to compute all the physics.

    -

    Matter.js, however, does include a fairly simple and straightforward Render class which is incredibly useful for quickly seeing and debugging the world you’ve designed. It does allow ways for customization of the “debug drawing” style, but I find the defaults perfectly adequate for quickly double-checking that I’ve configured a world correctly.

    -

    The first step is to call Matter.Render.create() (or Render.create() assuming an alias). This function expects an object with the desired settings for the renderer, called params below.

    +

    Render

    +

    Once a body is added the world, Matter.js will always know it’s there, check it for collisions, and update its position appropriately according to any forces in the environment. It’ll do all that for you without you having to lift a finger! But how do you actually draw the body?

    +

    In the next section, I’ll show you how to query Matter.js for the position of the various bodies in order to render the world with p5.js. How that works is fundamental to being able to control the look of your own animations. This is your time to shine: you can be the designer of your world, using your creativity and p5.js skills to visualize the bodies, while politely asking Matter.js to compute all the physics in the background.

    +

    That said, Matter.js does include a fairly simple and straightforward Render class, which is incredibly useful for quickly seeing and debugging the world you’ve designed. It provides ways to customize the “debug drawing” style, but I find the defaults perfectly adequate for quickly double-checking that I’ve configured a world correctly.

    +

    The first step is to call Matter.Render.create() (or Render.create() assuming an alias). This method expects an object with the desired settings for the renderer,which I’ll call params.

    // Store the canvas in a variable
     let canvas = createCanvas(640, 360);
     
    @@ -262,14 +279,14 @@ let params = {
     
     // Create the renderer
     let render = Render.create(params);
    -

    Notice how in the code above, I am storing a reference to the p5.js canvas in the variable canvas. This is necessary because I need to tell the renderer to draw into a specific canvas. Matter.js does not know about p5.js, so the canvas it is assigned is a native HTML5 canvas, stored inside the elt property of a p5.js canvas object. The engine is the engine previously created in Section 6.4. The matter.js default canvas dimensions are 800x600 so if I prefer a different size, I’ll need to configure an options property with width and height.

    -

    Creating the renderer is not enough, however, I also need to tell matter.js to run the renderer!

    +

    Notice how I’m storing a reference to the p5.js canvas in the canvas variable. This is necessary because I need to tell the renderer to draw into a specific canvas. Matter.js doesn’t know about p5.js, so the canvas it’s assigned is a native HTML5 canvas, stored inside the elt property of a p5.js canvas object. The engine is the engine I previously created. The Matter.js default canvas dimensions are 800 by 600, so if I prefer a different size, I need to configure an options property with width and height.

    +

    Once I have a render object, I need to tell Matter.js to run it.

    // Run the renderer!
     Render.run(render);
    -

    There is one final and critical step, however. Physics engines must be told to “step” forward in time. Since I am using the built-in renderer, I can also use the built-in “runner” which will run the engine at a default framerate of 60 frames per second. The runner is also customizable but the details are not terribly important since the goal here is to move towards using p5.js’s draw() loop (coming in the next section.)

    +

    There’s one more critical order of business: physics engines must be told to “step” forward in time. Since I’m using the built-in renderer, I can also use the built-in “runner,” which runs the engine at a default framerate of 60 frames per second. The runner is also customizable, but the details aren’t terribly important since the goal here is to move toward using p5.js’s draw() loop instead (coming in the next section).

    // Run the engine!
     Runner.run(engine);
    -

    Here is all of the Matter.js code all together with an added ground object. Note the use of the option { isStatic: true } in the creation of ground body to ensure that it remains in a fixed position. I’ll cover more details about static bodies in Section 6.x.

    +

    Here’s all of the Matter.js code all together, with an added ground object—another rectangular body. Note the use of the { isStatic: true } option in the creation of ground body to ensure that it remains in a fixed position. I’ll cover more details about static bodies later in the chapter.

    Example 6.1: Matter.js Default Render and Runner

    @@ -318,14 +335,14 @@ function setup() { // Run the engine Matter.Runner.run(runner, engine); }
    -

    Notice how there is no draw() function and all of the variables are local to setup()! Here, I am not making use of any of the capabilities of p5.js (beyond injecting a canvas onto the page). This is exactly what I want to tackle next!

    +

    There’s no draw() function here, and all of the variables are local to setup(). In fact, I’m not making use of any of the capabilities of p5.js (beyond injecting a canvas onto the page). This is exactly what I want to tackle next!

    Matter.js with p5.js

    -

    Now, as demonstrated with the Render and Runner objects, matter.js keeps a list of all bodies that exist in the world and handles drawing and animating them. (That list, incidentally, is stored in engine.world.bodies.) What I would like to show you, however, is a technique for keeping your own list(s) of bodies. Yes, this may be redundant and sacrifice a small amount of efficiency. But I more than make up for that with ease of use and customization. This methodology will allow you to code like you’re accustomed to with p5.js, keeping track of which bodies are which and drawing them appropriately. Let’s consider the following file structure:

    +

    Matter.js keeps a list of all bodies that exist in the world, and as you’ve just seen, it can handle drawing and animating them with the Render and Runner objects. (That list, incidentally, is stored in engine.world.bodies.) What I’d like to show you now, however, is a technique for keeping your own list(s) of Matter.js bodies, so you can draw them with p5. Yes, this approach may add redundancy and sacrifice a small amount of efficiency, but it more than makes up for that with ease of use and customization. With this methodology, you’ll be able to code like you’re accustomed to in p5.js, keeping track of which bodies are which and drawing them appropriately. Consider the file structure of the sketch shown in Figure 6.x.

    - -
    + Figure 6.x: The file structure of a typical p5.js sketch +
    Figure 6.x: The file structure of a typical p5.js sketch
    -

    This looks like any ol’ p5.js sketch. There is sketch.js as well as box.js. The box.js file is where I would typically write a class to describe a Box object, a rectangular body in the world.

    +

    Structurally, this looks like just another p5.js sketch. There’s a main sketch.js file, as well as box.js. This sort of extra file is where I’d typically declare a class needed for the sketch—in this case, a Box class describing a rectangular body in the world.

    class Box {
       constructor(x, y) {
         //{!3} A box has an x,y position and a width.
    @@ -343,9 +360,9 @@ function setup() {
         square(this.x, this.y, this.w);
       }
     }
    -

    Let’s now write sketch.js and create a new Box whenever the mouse is pressed and store all the Box objects in an array. (This is the same approach I took in the particle system examples from Chapter 4.)

    +

    Now I’ll write a sketch.js file that creates a new Box whenever the mouse is pressed and stores all the Box objects in an array. (This is the same approach I took in the particle system examples from Chapter 4.)

    -

    Example 6.2: A comfortable and cozy p5.js sketch that needs a little matter.js

    +

    Example 6.2: A Comfortable and Cozy p5.js Sketch That Needs a Little Matter.js

    @@ -372,11 +389,11 @@ function draw() { boxes[i].show(); } }
    -

    Here’s a challenge. Take the above example verbatim, but instead of drawing fixed boxes on the screen, draw boxes that experience physics (with matter.js) as soon as they appear.

    -

    I’ll need two major steps to accomplish this goal.

    -

    Step 1: Add Matter.js to the p5.js sketch.

    -

    This part is not too tough. I demonstrated all the elements needed for building a matter.js world in the previous sections. (And don’t forget, in order for this to work, make sure the library is imported in index.html!)

    -

    The first step is to add aliases for the necessary Matter classes and create an engine in setup().

    +

    Right now this sketch draws fixed boxes to the screen. Here’s the challenge: how can I instead draw boxes that experience physics (calculated with Matter.js) as soon as they appear, while changing the code as little as possible?

    +

    I’ll need three steps to accomplish this goal.

    +

    Step 1: Add Matter.js to the p5.js Sketch

    +

    As it stands, the sketch makes no reference to Matter.js. That clearly needs to change. Fortunately, this part isn’t too tough: I’ve already demonstrated all the elements needed to build a Matter.js world. (And don’t forget, in order for this to work, make sure the library is imported in index.html.)

    +

    First, I need to add aliases for the necessary Matter classes and create an Engine object in setup():

    //{!3} Aliases for Engine, Bodies, and Composite
     let { Engine, Bodies, Composite } = Matter;
     
    @@ -387,24 +404,26 @@ function setup() {
       //{!1} Create the engine.
       engine = Engine.create();
     }
    -

    Then in draw(), I need to make sure to call one critical function: Engine.update(). This is instead of using the built-in matter.js Runner. After all, I already have a runner, the p5.js draw() loop! If I were to forget this function, no objects would animate. Engine.update() advances the physics world a step forward in time. Internally, matter.js sweeps through and looks at all of the bodies and figures out what to do with them. Just calling update() on its own moves the world forward with default settings; however, as with matter.js Render it is customizable (and documented in the matter.js reference).

    +

    Then, in draw(), I need to make sure to call one critical Matter.js method: Engine.update().

    function draw() {
       //{!1} Step the engine forward in time!
       Engine.update(engine);
     }
    - -

    The original Box class includes variables for position and width. What I now want to say is:

    -

    “I hereby relinquish the command of this object’s position to matter.js. I no longer need to keep track of anything related to position, velocity, and acceleration. Instead, I only need to keep track of a Body and have faith that matter.js will do the rest.”

    +

    The Engine.update() method advances the physics world one step forward in time. Calling it inside the p5.js draw() loop ensures that the physics will update at every frame of the animation. This mechanism takes teh place of the built-in Matter.js Runner object I used in Example 6.1. The draw() loop is the runner now!

    +

    Internally, when Engine.update() is called, Matter.js sweeps through the world, looks at all of the bodies in it, and figures out what to do with them. Just calling Engine.update() on its own moves the world forward with default settings. However, as with Render, these settings are customizable (and documented in the Matter.js reference).

    + +

    I’ve set up my Matter.js world; now I need to link each Box object in my p5.js sketch with a body in that world. The original Box class includes variables for position and width. What I now want to say is: “I hereby relinquish command of this object’s position to Matter.js. I no longer need to keep track of anything related to position, velocity, or acceleration. Instead, I only need to keep track of the existence of a Matter.js body and have faith that the physics engine will do the rest.”

    class Box {
       constructor(x, y) {
         this.w = 16;
    -    //{!1} Instead of any of the usual variables, a reference a body is stored.
    +    //{!1} Instead of any of the usual variables, store a reference to a body.
         this.body = Bodies.rectangle(x, y, this.w, this.w);
    -    //{!1} Can't forget to add it to the world!
    +    //{!1} Don't forget to add it to the world!
         Composite.add(engine.world, this.body);
       }
    -

    I don’t need a position variable anymore since, as you’ll see, the body itself will keep track of its position. The body technically could keeps track of its dimensions as well, but since matter.js stores them as a list of vertices, it’s a bit more convenient to hold onto the width of the square in a variable for when it comes time to draw the box.

    -

    OK, almost there. Before I introduced matter.js, it was easy to draw the Box. The object’s position was stored in variables this.x and this.y.

    +

    I don’t need this.x and this.y position variables anymore. The Box constructor takes in the starting (x, y) coordinates, passes them along to Bodies.rectangle() to create a new Matter.js body, and then forgets about them. As you’ll see, the body itself will keep track of its position behind the scenes. The body could technically keep track of its dimensions as well, but since Matter.js stores them as a list of vertices, it’s a bit more convenient to hold onto the width of the square in the this.w variable for when it comes time to draw the box.

    +

    Step 3: Draw the Box Body

    +

    Almost there. Before I introduced Matter.js into the sketch, it was easy to draw the Box. The object’s position was stored in variables this.x and this.y.

      // Drawing the object using square()
       show() {
         rectMode(CENTER);
    @@ -413,11 +432,11 @@ function setup() {
         strokeWeight(2);
         square(this.x, this.y, this.w);
       }
    -

    Now that matter.js manages the object’s position, I can no longer use my own x or yvariables to draw the shape. Not to fear! The Box object has a reference to the body associated with it. So all I need to do is politely ask the body, “Pardon me, where are you located?”

    +

    Now that Matter.js manages the object’s position, I can no longer use my own x and yvariables to draw the shape. But fear not! The Box object has a reference to the Matter.js body associated with it, and that body knows its own position. All I need to do is politely ask the body, “Pardon me, where are you located?”

    let position = this.body.position;
    -

    Just knowing the position of a body isn’t enough; I also need to know its angle of rotation.

    +

    Just knowing the position of a body isn’t enough, however. The body is a square, so I also need to know its angle of rotation.

    let angle = this.body.angle;
    -

    Once I have the position and angle, I can render the object using translate() and rotate().

    +

    Once I have the position and angle, I can render the object using the native p5.js translate(), rotate(), and square() functions.

      show() {
         //{!2} I need the Body’s position and angle.
         let position = this.body.position;
    @@ -428,33 +447,32 @@ function setup() {
         stroke(0);
         strokeWeight(2);
         push();
    -    //{!2} Using the position and angle to translate and rotate the square
    +    //{!2} Use the position and angle to translate and rotate the square.
         translate(position.x, position.y);
         rotate(angle);
         square(0, 0, this.w);
         pop();
       }
    -

    It’s important to note here that if you delete objects from the array (as demonstrated in Chapter 4) based on a “lifespan” or if they move outside the boundaries of the canvas, you also must also explicitly remove the body associated with your object from the matter.js world. This can be done with a removeBody() method.

    +

    It’s important to note here that if you delete a Box objects from the boxes array—perhaps when it moves outside the boundaries of the canvas or reaches the end of its lifespan, as demonstrated in Chapter 4—you must also explicitly remove the body associated with that Box object from the Matter.js world. This can be done with a removeBody() method on the Box class.

      // This function removes a body from the Matter.js world.
       removeBody() {
         Composite.remove(engine.world, this.body);
       }
    -

    In draw() just as in the particle system examples, you would then iterate over the array in reverse calling both removeBody() and splice() to delete the object from the matter.js world and your array of boxes.

    +

    In draw(), you would then iterate over the array in reverse, just as in the particle system examples, an call both removeBody() and splice() to delete the object from the Matter.js world and your array of boxes.

    Exercise 6.2

    Drag the mouse to add boxes.
    -

    Find the example sketch called “6.2 Boxes Exercise.” Using the methodology outlined in this chapter, add the code to implement Matter.js physics. Delete bodies that have left the canvas. The result should appear as above. Feel free to be creative in how you draw the boxes!

    +

    Start with the code for 6.2 Boxes Exercise and, using the methodology outlined in this chapter, add the code to implement Matter.js physics. Delete bodies that have left the canvas. The result should appear as above. Feel free to be creative in how you draw the boxes!

    Static Matter.js Bodies

    -

    In the example just created, the Box objects appear at the mouse position and fall downwards due to the default gravity force. What if I wanted to add immovable boundaries in the world that would block the path of the Box objects?

    -

    Matter.js makes this easy with the isStatic property.

    +

    In the example I just created, the Box objects appear at the mouse position and fall downwards due to the default gravity force. What if I want to add immovable boundaries to the world that will block the path of the falling Box objects? Matter.js makes this easy with the isStatic property.

    // Creating a fixed (static) boundary body
     let options = { isStatic: true };
     let boundary = Bodies.rectangle(x, y, w, h, options);
    -

    This can be incorporated into the solution to Exercise 6.2 by creating a Boundary class that wraps static body. Static bodies do not incorporate “material” properties like restitution or friction so make sure you are setting those in the dynamic bodies in your world. (This example is also enhanced with randomized dimensions for the boxes!)

    +

    I’m still creating a body with the Bodies.rectangle() factory method, but setting the isStatic property ensures that the body will never move. I’ll incorporate this feature into the solution to Exercise 6.2 by creating a separate Boundary class that links a p5.js rectangle to a static Matter.js body. For variety, I’ll also randomize the dimensions of each falling box. (See the online code for the changes to the Box class.)

    Example 6.3: Falling boxes hitting boundaries

    @@ -486,22 +504,22 @@ let boundary = Bodies.rectangle(x, y, w, h, options); rect(this.x, this.y, this.w, this.h); } } +

    Static bodies don’t incorporate “material” properties like restitution or friction. Make sure you set those in the dynamic bodies in your world.

    Polygons and Groups of Shapes

    -

    +

    Now that I’ve demonstrated how easy it is to use a primitive shape like a rectangle or circle with Matter.js, let’s imagine that you want to have a more interesting body, such as the abstract character in Figure 6.2.

    Figure 6.2: A “compound” body made up of multiple shapes
    Figure 6.2: A “compound” body made up of multiple shapes
    -

    Now that I’ve demonstrated how easy it is to use a primitive shape like a rectangle or circle with matter.js, let’s imagine that you want to have a more complex form, such as the abstract character in Figure 6.2.

    -

    There are two strategies for making such complex forms. Beyond the four sides of a rectangle, there is a generic Bodies.polygon() function for any regular polygon (pentagon, hexagon, etc.). Additionally, there is Bodies.trapezoid() function for a quadrilateral with at least one parallel side.

    +

    There are two strategies for making such complex forms. Beyond the four sides of a rectangle, there’s a generic Bodies.polygon() method for creating any regular polygon (pentagon, hexagon, and so on). Additionally, there’s Bodies.trapezoid() for making a quadrilateral with at least one pair of parallel sides.

    // A regular hexagon (6 sided polygon)
     let hexagon = Bodies.polygon(x, y, 6, radius);
     
     // A trapezoid
     let trapezoid = Bodies.trapezoid(x, y, width, height, slope);
    -

    For any shape, however, Bodies.fromVertices(), is more general purpose since it builds the shape from an array of vectors, a series of connected vertices.

    +

    A more general-purpose option is Bodies.fromVertices(). It builds a shape from an array of vectors, treating them as a series of connected vertices. I’ll encapsulate this logic in a CustomShape class.

    -

    Example 6.4: Polygon shapes

    +

    Example 6.4: Polygon Shapes

    @@ -526,20 +544,17 @@ class CustomShape { Body.setAngularVelocity(this.body, 0.1); Composite.add(engine.world, this.body); } -

    When creating a custom polygon in Matter.js, you must remember two important details.

    +

    When creating a custom polygon in Matter.js, you must remember two important details. First, the vertices must be specified in clockwise order. For instance, Figure 6.3 shows the five vertices used to create the bodies in Example 6.4. Notice how the example added them to the vertices array in clockwise order from the top-left.

    Figure 6.3: Vertices on a custom polygon oriented in clockwise order
    Figure 6.3: Vertices on a custom polygon oriented in clockwise order
    -
      -
    1. Order of vertices! The vertices should be specified in clockwise order.
    2. -
    3. Convex shapes only! A concave shape is one where the surface curves inward. Convex is the opposite (see illustration below). Note how in a convex shape every internal angle must be 180 degrees or less. Matter.js can in fact handle collisions for concave shapes, you just need to build one out of multiple convex shapes! (More about that in a moment.)
    4. -
    +

    Second, each shape must be convex, not concave. As shown in Figure 6.4, a concave shape is one where the surface curves inward, whereas convex is the opposite. Every internal angle in a convex shape must be 180 degrees or less. Matter.js can in fact work with concave shapes, but you need to build them out of multiple convex shapes. (More about that in a moment.)

    Figure 6.4: A concave shape can be drawn with multiple convex shapes. 
    Figure 6.4: A concave shape can be drawn with multiple convex shapes. 
    -

    Now, when it comes time to draw the object, since the shape is built out of custom vertices the beginShape(), endShape(), and vertex() functions can be utilized. While the CustomShape class could include an array to store the pixel positions of the vertices for drawing, it’s best to query matter.js. Here, there’s no need use translate() or rotate() because the vertex positions are stored as the absolute “world” positions.

    +

    Since the shape is built out of custom vertices, you can use p5’s beginShape(), endShape(), and vertex() functions when it comes time to actually draw the body. The CustomShape class could include an array to store the vertices’ pixel positions, relative to (0, 0), for drawing purposes. However, it’s best to query Matter.js for the positions instead. This way there’s no need to use translate() or rotate(), since the Matter.js body stores its vertices as absolute “world” positions.

      show() {
         fill(127);
         stroke(0);
    @@ -553,16 +568,17 @@ class CustomShape {
         // End the shape, closing it
         endShape(CLOSE);    
       }
    +

    The Matter.js body stores the array of its vertex positions inside a vertices property. Notice how I can then use a for…of loop to cycle through the vertices in between beginShape() and endShape().

    Exercise 6.3

    -

    Using Bodies.fromVertices(), create your own polygon design (remember, it must be convex). Some possibilities below.

    +

    Using Bodies.fromVertices(), create your own polygon design (remember, it must be convex). Some possibilities are shown below.

     
     
    -

    A custom shape built from an array of vertices will get you pretty far. However, the convex shape requirement does limit the range of possibilities. The good news is that you can eliminate this restriction by creating a compound body made up of multiple shapes! How about creating a delicious lollipop with a thin rectangle and a circle on top?

    -

    Let’s start by creating two single bodies, one rectangle and one circle. Then with Body.create() the shapes can be joined via a parts array. Here’s how this looks in code:

    +

    A custom shape built from an array of vertices will get you pretty far. However, the convex shape requirement does limit the range of possibilities. The good news is that you can eliminate this restriction by creating a compound body made up of multiple shapes! How about creating a delicious lollipop with a thin rectangle and a circle on top?

    +

    I’ll start by creating two individual bodies, one rectangle and one circle. Then I can join them by putting them in a parts array and passing the array to Body.create().

    // Making the bodies
     let part1 = Bodies.rectangle(x, y, w, h);
     let part2 = Bodies.circle(x, y, r);
    @@ -572,31 +588,31 @@ let body = Body.create({ parts: [part1, part2] });
     
     // Adding the compound body to the world
     Composite.add(engine.world, body);
    -

    The above is a good start, but sadly, if you run it, you’ll see both shapes are created at the same x and y position.

    +

    While this does create a compound body by combining two shapes, the code isn’t quite right. If you run it, you’ll see that both shapes are centered around the same (x, y) position, as in Figure 6.5.

    - Figure 6.5: A rectangle and a circle with the same x,y reference point. -
    Figure 6.5: A rectangle and a circle with the same x,y reference point.
    + Figure 6.5: A rectangle and a circle with the same (x, y) reference point. +
    Figure 6.5: A rectangle and a circle with the same (x, y) reference point.
    -

    If I consider the center of the rectangle to be the reference point for the body, however, the center of the circle could be adjusted by an offset from the body’s center along the x-axis.

    +

    Instead, I need to offset the center of the circle horizontally from the center of the rectangle, as in Figure 6.6.

    Figure 6.6: A circle placed relative to a rectangle with a horizontal offset
    Figure 6.6: A circle placed relative to a rectangle with a horizontal offset
    +

    I’ll use half the width of the rectangle as the offset, so the circle is centered around the edge of the rectangle.

     let part1 = Bodies.rectangle(x, y, w, h);
    -//{!2} Subtracting an offset from the y position of the lollipop
    +//{!2} Adding an offset from the x position of the lollipop's "stick"
     let offset = w / 2;
     let part2 = Bodies.circle(x + offset, y, r);
    -

    Because there are two “parts” to the lollipop’s body, drawing is a bit trickier. There are multiple approaches I could take. For example, the vertices array could be used much like in Example 6.4. Since these parts are primitive shapes, I prefer to separately translate to each part’s position and rotate by the collective body’s angle.

    +

    Because there are two “parts” to the lollipop’s body, drawing it is a bit trickier. There are multiple approaches I could take. For example, I could use the body’s vertices array and draw the lollipop as a custom shape, much like in Example 6.4 (Every body stores an array of vertices, even if it wasn’t created with fromVertices()). Since each part of the lollipop is a primitive shape, however, I’d prefer to separately translate to each part’s position and rotate by the collective body’s angle.

    -

    Example 6.5: Multiple shapes on one body

    +

    Example 6.5: Multiple Shapes on One Body

    -
     
    - show() {
    +
      show() {
         // The angle comes from the compound body
         let angle = this.body.angle;
     
    @@ -604,9 +620,9 @@ let part2 = Bodies.circle(x + offset, y, r);
    let position1 = this.part1.position; let position2 = this.part2.position; - fill(127); + fill(200); stroke(0); - strokeWeight(2); + strokeWeight(1); // Translate and rotate the rectangle (part1) push(); @@ -623,56 +639,63 @@ let part2 = Bodies.circle(x + offset, y, r);
    circle(0, 0, this.r * 2); pop(); } -

    Finishing off this section, I want to stress the following: what you draw in your canvas window doesn’t magically experience perfect physics just by the mere act of creating matter.js bodies. These examples work because I carefully matched how the shapes are drawn with how the geometry of the bodies were defined. If you accidentally draw a shape differently, you won’t get an error, not from p5.js or from matter.js. However, your sketch will look odd and the physics won’t work correctly. For example, what if I accidentally use the body position (the “center of mass” between both the rectangle and circle) for the rectangle manually offset the circle when drawing the shapes?

    -
    -
    -
    Figure 6.7 What happens the the shapes are drawn differently than how they were configured for matter.js.
    -
    -
        let position = this.body.position;
    +

    Before moving on, I want to stress the following: what you draw in your canvas window doesn’t magically experience perfect physics just by the mere act of creating Matter.js bodies. The chapter’s examples have worked because I’ve been carefully matching how each p5.js shape is drawn with how the geometry of each Matter.js body is defined. If you accidentally draw a shape differently, you won’t get an error—not from p5.js or from Matter.js. However, your sketch will look odd, and the physics won’t work correctly because the world you’re seeing won’t be aligned with the world as Matter.js understands it.

    +

    To illustrate, let me return to Example 6.5. A lollipop is a compound body consisting of two parts, a rectangle (this.part1) and a circle (this.part2). I’ve been drawing each lollipop by getting the positions for the two parts separately: this.part1.position and this.part2.position. However, the overall compound body also has a position, this.body.position. It would be tempting to use that as the position for drawing the rectangle, and to figure out the circle’s position manually using an offset. After all, that’s how I conceived of the compound shape to begin with (look back at Figure 6.6).

    +
      show() {
    +    let position = this.body.position;
         let angle = this.body.angle;
    +    push();
         translate(position.x, position.y);   
         rotate(angle);
         rect(0, 0, this.w, this.h);
    -    circle(0, this.h / 2, this.r * 2); 
    -

    While subtle here, the results end up like Figure 6.7, the collisions are off and the shapes overlap in odd ways. This is not because the physics is broken; it’s because I did not communicate properly with matter, either when adding bodies to the world or querying the world for bodies.

    + circle(0, this.h / 2, this.r * 2); + pop(); + }
    +

    Figure 6.7 shows the result of this change. (In the online version, you can toggle the correct and incorrect rendering by clicking the mouse.)

    +
    +
    +
    Figure 6.7 What happens when the shapes are drawn differently than how they were configured for Matter.js
    +
    +

    At first glance, this new version may look fine, but if you look closer, the collisions are off and the shapes overlap in odd ways. This isn’t because the physics are broken; it’s because I’m not communicating properly between p5.js and Matter.js. It turns out the overall body position isn’t the center of the rectangle, but rather the “center of mass” between the rectangle and the circle. Matter.js is calculating the physics and managing collisions as before, but I’m drawing each body in the wrong place!

    Exercise 6.4

    -

    Make your own little alien being using multiple shapes attached to a single body. Remember, you aren’t limited to using the shape drawing functions in p5.js; you can use images, colors, add hair with lines, and more. Think of the matter.js shapes as skeletons for your original fantastical design!

    +

    Make your own little alien being using multiple shapes attached to a single body. Remember, you aren’t limited to using the basic shape-drawing functions in p5.js; you can use images and colors, add hair with lines, and more. Think of the Matter.js shapes as skeletons for your original fantastical design!

    -

    Feeling Attached—Matter.js Constraints

    -

    Matter.js constraints are a mechanism to connect one body to another, enabling simulations of swinging pendulums, elastic bridges, squishy characters, wheels spinning on an axle, and more. There are two kinds of matter.js constraints: MouseConstraint and Constraint.

    +

    Feeling Attached: Matter.js Constraints

    +

    A Matter.js constraint is a mechanism to connect one body to another, enabling simulations of swinging pendulums, elastic bridges, squishy characters, wheels spinning on an axle, and more. There are three kinds of constraints: distance constraints and revolute constraints, both managed through the Constraint class, and mouse constraints, managed through the MouseConstraint class.

    +

    Distance Constraints

    - Figure 6.8: A Constraint is a connection between two bodies at an anchor point for each body. -
    Figure 6.8: A Constraint is a connection between two bodies at an anchor point for each body.
    + Figure 6.8: A constraint is a connection between two bodies at an anchor point for each body. +
    Figure 6.8: A constraint is a connection between two bodies at an anchor point for each body.
    -

    “Distance” Constraint

    -

    Let’s begin with Constraint, a connection of fixed length between two bodies. A constraint is attached to each body at a specified anchor point (a point relative to the body’s center). Defining a constraint is similar to the methodology used to create bodies, only you need to have two bodies ready to go.

    -

    Let’s assume there are two Particle objects that each store a reference to a matter.js Body in a property called body. I’ll call them particles particleA and particleB.

    +

    A distance constraint is a conection of fixed length between two bodies. The constraint is attached to each body at a specified anchor, a point relative to the body’s center (see Figure 6.8). However, it’s important to note that the “fixed” length can exhibit variability, depending on the constraint’s stiffness.

    +

    Defining a constraint uses a similar methodology as creating bodies, only you need to have two bodies ready to go. Let’s assume there are two Particle objects that each store a reference to a Matter.js body in a property called body. I’ll call them particleA and particleB.

    let particleA = new Particle();
     let particleB = new Particle();
    -

    Then Constraint is created with a list of options that determine its behavior:

    +

    I want to create a constraint between these particles. For that, I need to define a series of options that determine the constraint’s behavior:

    • bodyA: The first body that the constraint connects, establishing one end of the constraint.
    • bodyB: The second body that the constraint connects, forming the other end.
    • pointA: The position, relative to bodyA, where the constraint is anchored to the first body.
    • pointB: The position, relative to bodyB, where the constraint is anchored to the second body.
    • -
    • length: The resting or target length of the constraint, which it will attempt to maintain during the simulation.
    • +
    • length: The resting or target length of the constraint. The constraint will attempt to maintain this length during the simulation.
    • stiffness: A value between 0 and 1 that represents the rigidity of the constraint, with 1 being fully rigid and 0 being completely soft.
    +

    These settings all get packaged up in an object literal.

    let options = {
       bodyA: particleA.body,
       bodyB: particleB.body,
    -	pointA: Vector.create(0, 0),
    -	pointB: Vector.create(0, 0),
    +  pointA: Vector.create(0, 0),
    +  pointB: Vector.create(0, 0),
       length: 100,
       stiffness: 0.5
     }
    -

    Technically, the only required options are bodyA and bodyB, the two bodies connected by the constraint. If you do not specify any additional options, matter.js will choose defaults for other properties. For example, it will use (0, 0) as the relative anchor points, set the length to the current distance between the bodies, and assign a default stiffness of 0.7. Two other notable options not included above are damping and angularStiffness. Dampingaffects the constraint's resistance to motion, with higher values causing the constraint to lose energy more quickly. AngularStiffnesscontrols the rigidity of the constraint's angular motion, with higher values resulting in less angular flexibility between the bodies.

    -

    Once the options are configured, the constraint be created. Once again, this assumes an additional Constraint alias equal to Matter.Constraint.

    +

    Technically, the only required options are bodyA and bodyB, the two bodies connected by the constraint. If you don’t specify any additional options, Matter.js will choose defaults for the other properties. For example, it will use (0, 0) for each relative anchor point (the body’s center), set the length to the current distance between the bodies, and assign a default stiffness of 0.7. Two other notable options I didn’t include are damping and angularStiffness. The damping option affects the constraint’s resistance to motion, with higher values causing the constraint to lose energy more quickly. The angularStiffness option controls the rigidity of the constraint’s angular motion, with higher values resulting in less angular flexibility between the bodies.

    +

    Once the options are configured, the constraint can be created. As usual, this assumes another alias: Constraint is equal to Matter.Constraint.

    let constraint = Constraint.create(options);
     //{!1} Don't forget to add the constraint to the world!
     Composite.add(engine.world, constraint);
    -

    Constraints can be created anywhere in the sketch. Here’s an example of a class that represents a swinging Pendulum (mirroring example 3.x from Chapter 3).

    +

    Just like with a body, I can add a constraint to a class to encapsulate and manage the relationship between multiple bodies. Here’s an example of a class that represents a swinging pendulum (mirroring Example 3.x from Chapter 3).

    Example 6.6: Matter.js Pendulum

    @@ -685,12 +708,12 @@ Composite.add(engine.world, constraint); this.r = 12; this.len = len; - //{!2} Create 2 bodies, one for the anchor and one for the bob. + //{!2} Create two bodies, one for the anchor and one for the bob. // The anchor is static. this.anchor = Bodies.circle(x, y, this.r, { isStatic: true }); this.bob = Bodies.circle(x + len, y, this.r, { restitution: 0.6 }); - //{!6) Create a constraint connecting the anchor and bob + //{!6} Create a constraint connecting the anchor and bob let options = { bodyA: this.anchor, bodyB: this.bob, @@ -698,7 +721,7 @@ Composite.add(engine.world, constraint); }; this.arm = Matter.Constraint.create(options); - //{!3} Add all bodies and constraints to the world + //{!3} Add all bodies and constraints to the world. Composite.add(engine.world, this.anchor); Composite.add(engine.world, this.bob); Composite.add(engine.world, this.arm); @@ -709,10 +732,10 @@ Composite.add(engine.world, constraint); stroke(0); strokeWeight(2); - //{!1} Draw a line representing the pendulum arm + //{!1} Draw a line representing the pendulum arm. line(this.anchor.position.x, this.anchor.position.y, this.bob.position.x, this.bob.position.y); - //{!6} Draw the anchor + //{!6} Draw the anchor. push(); translate(this.anchor.position.x, this.anchor.position.y); rotate(this.anchor.angle); @@ -720,7 +743,7 @@ Composite.add(engine.world, constraint); line(0, 0, this.r, 0); pop(); - //{!6} Draw the bob + //{!6} Draw the bob. push(); translate(this.bob.position.x, this.bob.position.y); rotate(this.bob.angle); @@ -729,41 +752,37 @@ Composite.add(engine.world, constraint); pop(); } } -

    Example 6.6 uses a default stiffness of 0.7, if you try a lower value, the pendulum will appear more like a soft spring.

    -
    -
    -
    -
    +

    Example 6.6 uses a default stiffness of 0.7. If you try a lower value, the pendulum will appear more like a soft spring.

    Exercise 6.5

    -

    Create a simulation of a bridge by using constraints to connect a sequence of circles (or rectangles) as illustrated to the right. Assign a density of zero to lock the endpoints in place. Experiment with different values to make the bridge more or less “springy.” It should also be noted that the joints themselves have no physical geometry, so in order for your bridge not to have holes, spacing between the nodes will be important.

    +

    Create a simulation of a bridge by using constraints to connect a sequence of circles (or rectangles) as shown below. Use the isStatic property to lock the endpoints in place. Experiment with different values to make the bridge more or less “springy.” The joints themselves have no physical geometry, so in order for your bridge not to have holes, spacing between the nodes will be important.

    -

    “Revolute” Constraint

    +

    Revolute Constraints

    - Figure 6.9: A “Revolute” constraint is a connection between two bodies at a single “hinge” point. -
    Figure 6.9: A “Revolute” constraint is a connection between two bodies at a single “hinge” point.
    + Figure 6.9: A revolute constraint is a connection between two bodies at a single anchor point or hinge. +
    Figure 6.9: A revolute constraint is a connection between two bodies at a single anchor point or hinge.
    -

    Another type of connection between bodies common to physics engine is a “revolute” joint. A revolute joint connects two bodies at a common anchor point, also known as a “hinge.” While there is no specific “revolute” constraint in matter.js, you can achieve the same effect by setting a constraint’s length to zero, allowing the bodies to rotate around a common anchor point.

    -

    The first step is to create the connected bodies. For a first example, I’d like to create a spinning rectangle (akin to a propellor or “windmill”) in a fixed position. For this case, I only need one body connected to a “point”. This simplifies things since I don’t have to worry about collisions between the two bodies connected at a hinge.

    +

    Another kind of connection between bodies common to physics engines is a revolute joint. This type of constraint connects two bodies at a common anchor point, also known as a hinge (see Figure 6.9). While there isn’t a separate revolute constraint in Matter.js, you can make one with a regular Constraint of length zero. This way the bodies can rotate around a common anchor point.

    +

    The first step is to create the connected bodies. For a first example, I’d like to create a spinning rectangle (akin to a propellor or windmill) in a fixed position. For this case, I only need one body connected to a “point.” This simplifies things, since I don’t have to worry about collisions between the two bodies connected at a hinge.

    // Create a body at a given x,y with a width and height
     let body = Bodies.rectangle(x, y, w, h);
     Composite.add(engine.world, body);
    -

    Next the constraint is created. With a length of 0, it is required that the stiffness be set to 1, otherwise the constraint may not be stable enough to keep the body connected at the anchor point.

    -
    // The constraint connects the body to a fixed x,y position with a length of 0 and stiffness of 1
    +

    Next, I can create the constraint. With a length of 0, it needs a stiffness of 1, otherwise the constraint may not be stable enough to keep the body connected at the anchor point.

    +
    // The constraint connects the body to a fixed x,y position with a length of 0 and stiffness of 1.
     let options = {
       bodyA: this.body,
       pointB: { x, y },
       length: 0,
       stiffness: 1,
     };
    -// Create the constraint and add to the world
    +// Create the constraint and add it to the world.
     let constraint = Matter.Constraint.create(options);
    -Composite.add(engine.world, constraint);Step 4: Create the joint.
    -

    Let’s take a look at all of these steps together in a class called Windmill. This sketch also includes a Particle class for dropping particles onto the windmill.

    +Composite.add(engine.world, constraint);
    +

    Putting it together, I’ll write a sketch with a class called Windmill representing a rotating body. The sketch also includes a Particle class for dropping particles onto the windmill.

    Example 6.7: Spinning Windmill

    @@ -780,7 +799,7 @@ Composite.add(engine.world, constraint);Step 4: Create the joint. this.body = Bodies.rectangle(x, y, w, h); Composite.add(engine.world, this.body); - //{!7} The "revolute" constraint + //{!8} The "revolute" constraint let options = { bodyA: this.body, pointB: { x, y }, @@ -813,30 +832,31 @@ Composite.add(engine.world, constraint);Step 4: Create the joint.
    -

    Use a revolute joint for the wheels of a car. [some creative prompts about the design of the vehicle/car]

    +

    Use a revolute joints for the wheels of a car. [some creative prompts about the design of the vehicle/car]

     
     
    -

    Mouse Constraint

    -

    Before working with MouseConstraint, let's consider the following question: how do you set the position of a matter.js body to the mouse position? Why would you need a constraint? After all, you have access to the body's position; what's wrong with assigning the body's position to the mouse?

    +

    Mouse Constraints

    +

    Before I introduce the MouseConstraint class, consider the following question: how do you set the position of a Matter.js body to the mouse position? More to the point, why would you need a constraint for this? After all, you have access to the body’s position, and you have access to the mouse’s position. What’s wrong with assigning one to the other?

    body.position.x = mouseX;
     body.position.y = mouseY;
    -

    While this will in fact move the body, it will also have the unfortunate result of breaking the physics. Let’s imagine you built a teleportation machine that allows you to teleport from your bedroom to your kitchen (good for late-night snacking). Now, go ahead and rewrite Newton’s laws of motion to account for the possibility of teleportation. Not so easy, right? Matter.js has the same problem. If you manually assign the position of a body, it’s like saying “teleport that body” and matter.js no longer knows how to compute the physics properly. However, matter.js does allow you to tie a string around your waist and have a friend of yours to stand in the kitchen and drag you there. This is what the MouseConstraint does.

    -

    Imagine that the moment you click the mouse over a shape, it attaches to that body with a string, allowing you to drag it around until it is released. This works in a similar fashion as the “revolute” joint in that you can set the length of that string to zero, effectively moving a shape with the mouse.

    -

    Before you can attach the mouse, however, you need to create a matter.js Mouse object that listens for interactions to the p5.js canvas.

    -
    // Aliases for matter.js Mouse and MouseConstraint
    +

    While this will in fact move the body, it will also have the unfortunate result of breaking the physics. Imagine you’ve built a teleportation machine that allows you to move instantly from your bedroom to your kitchen (good for late-night snacking). That’s easy enough to imagine, but now go ahead and rewrite Newton’s laws of motion to account for the possibility of teleportation. Not so easy anymore, is it?

    +

    Matter.js has the same problem. If you manually assign the position of a body, it’s like saying “teleport that body,” and Matter.js no longer knows how to compute the physics properly. However, Matter.js does allow you to tie a string around your waist and have a friend of yours to stand in the kitchen and drag you there. Replace your friend with your mouse, and that’s what a mouse constraint is.

    +

    Imagine that the moment you click the mouse over a shape, the mouse attaches to that body with a string. Now you can move the mouse around and it will drag the body around with it, until you release the mouse. This works in a similar fashion as a revolute joint in that you can set the length of that “string” to 0, effectively moving a shape with the mouse.

    +

    Before you can attach the mouse, however, you need to create a Matter.js Mouse object that listens for mouse interactions with the p5.js canvas.

    +
    // Aliases for Matter.js Mouse and MouseConstraint
     let { Mouse, MouseConstraint } = Matter;
     // Need a reference to the p5.js canvas to listen for mouse
     let canvas = createCanvas(640, 240);
     // Create a Matter mouse attached to the native HTML5 canvas element
     let mouse = Mouse.create(canvas.elt);
    -

    Next, the MouseConstraint can be created with the mouse.

    +

    Next, use the mouse object to create a MouseConstraint.

    let mouseConstraint = MouseConstraint.create(engine, { mouse });
     Composite.add(engine.world, mouseConstraint);
    -

    This will instantly allow you to interact with all matter.js bodies via the mouse!

    -

    You can also configure all the usual constraint variables by adding a constraint property to the options passed into create().

    +

    This will instantly allow you to interact with all Matter.js bodies via the mouse. There’s no need to explicitly attach the constraint to a particular body; any body you click will be constrained to the mouse.

    +

    You can also configure all the usual constraint variables by adding a constraint property to the options passed into the MouseConstraint.create() method.

    mouse = Mouse.create(canvas.elt);
     let options = {
       mouse,
    @@ -845,21 +865,21 @@ let options = {
     };
     mouseConstraint = MouseConstraint.create(engine, options);
     Composite.add(engine.world, mouseConstraint);
    -

    Following is an example that demonstrates a MouseConstraint with two Box objects. There are also static bodies acting as walls on the borders of the canvas.

    +

    Here’s an example demonstrating a MouseConstraint with two Box objects. There are also static bodies acting as walls around the borders of the canvas.

    -

    Example 6.8: MouseConstraint demonstration

    +

    Example 6.8: MouseConstraint Demonstration

    Bringing It All Back Home to Forces

    -

    In Chapter 2, I covered how to build an environment where there are multiple forces at play. An object might respond to gravitational attraction, wind, air resistance, and so on. Clearly, there are forces at work in matter.js as rectangles and circles spin and fly around the screen! But so far, I’ve only actually demonstrated how to manipulate a single global force: gravity.

    +

    In Chapter 2, I covered how to build an environment where there are multiple forces at play. An object might respond to gravitational attraction, wind, air resistance, and so on. Clearly, there are forces at work in Matter.js as rectangles and circles spin and fly around the screen! But so far, I’ve only actually demonstrated how to manipulate a single global force: gravity.

      let engine = Engine.create();
       // Changing the engine's gravity to point horizontally
       engine.gravity.x = 1;
       engine.gravity.y = 0;
    -

    If I want to use any of the Chapter 2 techniques with matter.js, I need look no further than the trusty applyForce() function. In Chapter 2 I wrote this function as part of the Mover class. It received a vector, divided it by mass, and accumulated it into the mover’s acceleration. With matter.js, the same function exists, but I don’t need to write it myself! It can be called with the static Body.applyForce().

    +

    If I want to use any of the Chapter 2 techniques with Matter.js, I need look no further than the trusty applyForce() method. In Chapter 2, I wrote this method as part of the Mover class. It received a vector, divided it by mass, and accumulated it into the mover’s acceleration. With Matter.js, the same method exists, so I no longer need to write all the details myself! I can call it with the static Body.applyForce().

    class Box {
     
       applyForce(force) {
    @@ -867,8 +887,8 @@ Composite.add(engine.world, mouseConstraint);
    Body.applyForce(this.body, this.body.position, force); } }
    -

    Here, the function receives a force vector and passing it along to the matter.js Body object. The key difference is that matter.js is a more sophisticated engine than the examples from Chapter 2. The earlier examples assumed that the force was always applied at the mover’s center. Here, the exact position on the body where the force is applied is specified. In the above code, I’m just applying it to the center by asking the body for its position, but this could be adjusted.

    -

    Let’s say you wanted to use a gravitational attraction force. Remember the code from Chapter 2 in the Attractor class?

    +

    Here, the Box class’s applyForce() method receives a force vector and simply passes it along to Matter.js’s applyForce() method to apply it to the corresponding body. The key difference with this approach is that Matter.js is a more sophisticated engine than the examples from Chapter 2. The earlier examples assumed that the force was always applied at the mover’s center. Here, the exact position on the body where the force is applied is specified. In this case, I’ve just applied it to the center as before by asking the body for its position, but this could be adjusted.

    +

    How can I bring forces into a Matter-driven sketch? Say I want to use a gravitational attraction force. Remember the code from Chapter 2 in the Attractor class?

      attract(mover) {
         let force = p5.Vector.sub(this.position, mover.position);
         let distance = force.mag();
    @@ -878,7 +898,7 @@ Composite.add(engine.world, mouseConstraint);
    force.setMag(strength); return force; } -

    I can rewrite the exact same function using Matter.Vector and incorporate it into a new Attractor class.

    +

    I can rewrite the exact same method using Matter.Vector and incorporate it into a new Attractor class.

    Example 6.9 Attraction with Matter.js

    @@ -887,39 +907,34 @@ Composite.add(engine.world, mouseConstraint);
    class Attractor {
       constructor(x, y) {
    -    // {!3} The attractor is a static matter.js Body
    +    // {!3} The attractor is a static Matter.js body.
         this.radius = 32;
         this.body = Bodies.circle(x, y, this.radius, { isStatic: true });
         Composite.add(engine.world, this.body);
       }
     
       attract(mover) {
    -    //{!2} The attract method now uses matter.js Vector functions
    +    //{!2} The attract method now uses Matter.js Vector functions.
         let force = Vector.sub(this.body.position, mover.body.position);
         let distance = Vector.magnitude(force);
         distance = constrain(distance, 5, 25);
     
    -    //{!1} Using a small value for G to keep the system stable
    +    //{!1} Using a small value for G keeps the system stable.
         let G = 0.02;
    -    //{!1} While the mover's mass can be accessed because the attractor is a "static" body it's mass will be infinity so it is ignored here
    +    //{!1} While the mover's mass can be accessed, because the attractor is a "static" body its mass will be infinity, so it is ignored here.
         let strength = (G * mover.body.mass) / (distance * distance);
    -    //{!2} More matter.js Vector functions
    +    //{!2} More Matter.js Vector functions
         force = Vector.normalise(force);
         force = Vector.mult(force, strength);
         return force;
       }
     }
    -

    In addition to writing a custom attract() function for Example 6.9 there are two other key elements required for it to behave more like the example from Chapter 2.

    -
      -
    1. A matter.js engine has a default gravity pointing down, I’ve disabled it in setup() with a zero vector.
    2. -
    +

    In addition to writing a custom attract() method for Example 6.9, there are two other key elements required for the sketch to behave more like the example from Chapter 2. First, remember that a Matter.js Engine has a default gravity pointing down. I nee to disable it in setup() with a (0, 0) vector.

    engine = Engine.create();
     //{!1} Disabling default gravity
     engine.gravity = Vector.create(0, 0);
    -
      -
    1. Bodies in matter.js are created with a default “air resistance” causing them to slow down as they move. I’ve set that to zero to simulate the bodies being in the “vacuum” of space.
    2. -
    +

    Second, bodies in Matter.js are created with a default air resistance that causes them to slow down as they move. I need to set this to 0 as well to simulate the bodies being in the “vacuum” of space.

    class Mover {
       constructor(x, y, radius) {
         this.radius = radius;
    @@ -929,7 +944,7 @@ engine.gravity = Vector.create(0, 0);
    }

    Exercise 6.7

    -

    Incorporate Body.applyForce() into a new spin() function from Example 6.7’s Windmill class to simulate a motor continuously rotating the windmill.

    +

    Incorporate Body.applyForce() into a new spin() method for Example 6.7’s Windmill class to simulate a motor continuously rotating the windmill.

    @@ -937,41 +952,42 @@ engine.gravity = Vector.create(0, 0);

    Exercise 6.8

    -

    Convert any of the steering behavior examples from Chapter 5 into matter.js. What does flocking look like with collisions?!

    +

    Convert any of the steering behavior examples from Chapter 5 to Matter.js. What does flocking look like with collisions?!

    Collision Events

    -

    Now you’ve seen a survey of what can be done with matter.js. Since this book is not called “The Nature of Matter.js,” it’s not my intention to cover every single possible feature of the matter.js library. But hopefully by looking at the basics of building bodies and constraints, when it comes time to use an aspect of matter.js that I haven’t covered, the skills you’ve gained here will make that process considerably less painful. There is one more feature of matter.js, however, that I do think is worth covering.

    -

    Let’s ask a question you’ve likely been wondering about:

    -

    What if I want something to happen when two bodies collide? I mean, don’t get me wrong—I’m thrilled that matter.js is handling all of the collisions for me. But if it takes care of everything for me, how am I supposed to know when things are happening?

    -

    Your first thoughts when considering an event during which two objects collide might be as follows: Well, if I know all the bodies in the system, and I know where they are all located, then I can just start comparing the positions, see which ones are intersecting, and determine that they’ve collided. That’s a nice thought, but hello??!? The whole point of using a physics engine like matter.js is that it will take care of that for us. If you are going to implement the computational geometry algorithms to test for intersection, then what you are doing is in fact re-implementing matter.js!

    -

    Of course, matter.js has thought of this problem before. It’s a pretty common one. Matter.js alerts you to moments of collision with an “event listener.” If you’ve worked mouse and keyboard interaction in p5.js, you are already familiar with an event listener.

    -

    Let’s consider the following:

    +

    This book isn’t called The Nature of Matter.js, so I’m not going to cover every single possible feature of the Matter.js library. At this point, I’ve gone over the basics of creating bodies and constraints, and shown you some of what the library can do. With the skills you’ve gained, hopefully the learning process will be considerably less painful when it comes time to use an aspect of Matter.js that I haven’t addressed here. Before moving on, however, there’s one more feature of the library that I think is worth covering: collision events.

    +

    Here’s a question you’ve likely been wondering about: “What if I want something extra to happen when two bodies collide? I mean, don’t get me wrong—I’m thrilled that Matter.js is handling all of the collisions behind the scenes. But if it’s taking care of the collisions for me, how am I supposed to know when they’re happening?”

    +

    Your first thoughts to answer this question might be as follows: “Well, I know all the bodies in the system, and I know where they’re all located. I can just start comparing the bodies’ positions and see which ones are intersecting. Then I can do something extra for the bodies that are determined to be colliding.”

    +

    That’s a nice thought, but hello??!? The whole point of using a physics engine like Matter.js is that it will take care of all that work for you. If you’re going to implement the computational geometry algorithms to test for intersection, then you’re basically implementing your own Matter.js!

    +

    Of course, wanting to know when bodies are colliding is a pretty common problem, so Matter.js has anticipated it. It can alert you to moments of collision with an event listener. If you’ve worked with mouse or keyboard interaction in p5.js, you already have experience with event listeners. Consider the following:

    // A mousePressed event you've probably written many times before.
     function mousePressed() {
       println("The mouse was pressed!");
     }
    -

    The global mousePressed() function in p5.js is executed whenever the mouse is pressed. This is known a a “callback”, a function that is “called back” at a later time when an event occurs. Collision events operate in a similar fashion, only instead of p5.js just knowing to look for a function called mousePressed() for a mouse event you have to explicitly define the name for a collision callback.

    +

    The global mousePressed() function in p5.js is executed whenever the mouse is pressed. This is known as a callback, a function that’s “called back” at a later time when an event occurs. Matter.js collision events operate in a similar fashion. Instead of p5.js just knowing to look for a function called mousePressed() when a mouse event occurs, however, you have to explicitly define the name for a Matter.js collision callback.

    Matter.Events.on(engine, 'collisionStart', handleCollisions);
    -

    The above code specifies that a function named handleCollisions should be executed whenever a collision between two bodies starts. There are also events for 'collisionActive' (executed over and over for the duration of an ongoing collision) and 'collisionEnd'(executed when two bodies stop colliding), but for a basic demonstration, knowing when the collision begins is more than adequate.

    -

    So, much like mousePressed() is triggered when the mouse is pressed, handleCollisions() is triggered when two shapes collide and can be written as follows:

    +

    This code specifies that a function named handleCollisions should be executed whenever a collision between two bodies starts. There are also events for 'collisionActive' (executed over and over for the duration of an ongoing collision) and 'collisionEnd'(executed when two bodies stop colliding), but for a basic demonstration, knowing when the collision begins is more than adequate.

    +

    Much like mousePressed() is triggered when the mouse is pressed, handleCollisions() (or whatever you choose to name the callback function) is triggered when two shapes collide. It can be written as follows:

    function handleCollisions(event) {
     
     }
    -

    Notice that the function above includes an argument named event. The event object includes all the data associated with a collision (or multiple collisions). Let’s assume a sketch with Particle objects that store a reference to a matter.js Body. Here is the process to follow:

    +

    Notice that the function includes an event parameter. This is an object that includes all the data associated with a collision (or multiple collisions if more than one has occurred in that time step), such as which bodies are involved. Matter.js automatically creates this object and passes it along to the handleCollisions() callback every time it’s called.

    +

    Say I have a sketch with Particle objects that each store a reference to a Matter.js body, and I want the particles to change color when they collide with each other. Here’s the process to follow to make that happen.

    Step 1: Event, could you tell me what two things collided?

    -

    Now, what has collided here? Matter.js detects collisions between a “pair” of bodies, these are the objects that have geometry. Any pair of colliding bodies will be in an array called pairs inside the event object. The following for loop iterating over all the pairs lives inside handleCollisions()).

    +

    Now, what has collided here? Matter.js detects collisions between a “pair” of bodies, these are the objects that have geometry. Any pair of colliding bodies will be in an array called pairs inside the event object. Inside handleCollisions(), I can use a for…of loop to iterate over those pairs.

    for (let pair of event.pairs) {
     
     }
     
    -

    Step 2: Pair, could tell me which two bodies are included?

    +

    Step 2: Pair, could tell me which two bodies you include?

    +

    Each pair in the pairs array is an object with references to the two bodies involved in the collision, bodyA and bodyB. I’ll extract those bodies.

    for (let pair of event.pairs) {
       let bodyA = pair.bodyA;
       let bodyB = pair.bodyB;
     }
    -

    Step 3: Bodies, could you tell me which Particles you are associated with?

    -

    OK, this is the harder part. After all, matter.js doesn’t know anything about my code. Sure, it is doing all sorts of stuff to keep track of the relationships between bodies and constraints, but it’s up to me to manage my own objects and their associations with matter.js elements. Luckily, matter.js provides a mechanism that allows Body to be attached to a custom object (in this case the Particle) with a plugin property.

    -

    Let’s take a look at the constructor in the Particle class where the body is made. Note how the body-making procedure is expanded by one line of code, noted below.

    +

    Step 3: Bodies, could you tell me which Particles you’re associated with?

    +

    Getting from the relevant Matter.js bodies to the Particle objects they’re associated with is a little harder. After all, Matter.js doesn’t know anything about my code. Sure, it’s doing all sorts of stuff to keep track of the relationships between bodies and constraints, but it’s up to me to manage my own objects and their associations with Matter.js elements. That said, Matter.js provides a mechanism that allows a Body object to be attached to a custom object (in this case, a Particle). The link is made through the Body class’s plugin property.

    +

    Take a look at the updated constructor in the Particle class where the body is made. Note how the body-making procedure has been expanded by one line of code to set the plugin property.

    class Particle {
     
       constructor(x, y, radius) {
    @@ -997,7 +1013,7 @@ function mousePressed() {
         let bodyA = pair.bodyA;
         let bodyB = pair.bodyB;
     
    -    //{!2} Retrieving the Particle associated with the colliding Body via the plugin.
    +    //{!2} Retrieving the particles associated with the colliding bodies via the plugin.
         let particleA = bodyA.plugin.particle;
         let particleB = bodyB.plugin.particle;
     
    @@ -1008,85 +1024,86 @@ function mousePressed() {
         }
       }
     }
    -

    Now, in most cases, you cannot assume that the objects that collided are all Particle objects. After all, the particle might have collided with a Boundary object (or other kind of thing depending on what’s in your world). An object’s “type” can be checked with the instance of operator in JavaScript as demonstrated in the above example.

    +

    In most cases, you can’t assume that the objects that collided are all Particle objects. After all, the particle might have collided with a Boundary object (or some other kind of thing, depending on what’s in your world). You can check an object’s “type” with JavaScript’s instanceof operator, as I’ve done in this example.

    Exercise 6.9

    Create a simulation in which Particle objects disappear when they collide with one another. Where and how should you delete the particles? Can you have them shatter into smaller particles?

    -

    A Brief Interlude—Integration Methods

    -

    Has the following ever happened to you? You’re at a fancy cocktail party regaling your friends with tall tales of software physics simulations. Someone pipes up: “Enchanting! But what integration method are you using?” “What?!” you think to yourself. “Integration?”

    -

    Maybe you’ve heard the term before. Along with “differentiation,” it’s one of the two main operations in calculus. Right, calculus. The good news is, you’ve gotten through about 90% of the material in this book related to physics simulation and I haven’t really needed to dive into calculus. But as I wrapping up the first half of this book and closing out this topic, it’s worth taking a moment to examine the calculus behind what I have been demonstrating and how it relates to the methodology in certain physics libraries (like Box2D, matter.js, and the upcoming toxiclibs).

    -

    Let’s begin by answering the question: “What does integration have to do with position, velocity, and acceleration?” Well, first let’s define differentiation, the process of finding a “derivative.” The derivative of a function is a measure of how a function changes over time. Consider position and its derivative. position is a point in space, while velocity is change in position over time. Therefore, velocity can be described as the “derivative” of position. What is acceleration? The change in velocity over time—i.e. the “derivative” of velocity.

    -

    Now that I’ve defined the derivative (differentiation), I can move on to the integral (integration) as the inverse of the derivative. In other words, the integral of an object’s velocity over time tells us the object’s new position when that time period ends. Position is the integral of velocity, and velocity is the integral of acceleration. Since the physics simulations in this book are founded on the notion of calculating acceleration based on forces, integration is needed to figure out where the object is after a certain period of time (like one cycle of the draw() loop!)

    -

    So you’ve been doing integration all along! It looks like this:

    +

    A Brief Interlude: Integration Methods

    +

    Has this ever happened to you? You’re at a fancy cocktail party, regaling your friends with tall tales of your incredible software physics simulations. Suddenly, out of the blue, someone pipes up: “Enchanting! But what integration method are you using?”

    +

    “What?!” you think to yourself. “Integration???”

    +

    Maybe you’ve heard the term before. Along with differentiation, it’s one of the two main operations in calculus. Oh right, calculus.

    +

    I’ve managed to get most of the way through the material in this book related to physics simulation without really needing to dive into calculus. As I wrap up the first half of this book, however, it’s worth taking a moment to examine the calculus behind what I’ve been demonstrating and how it relates to the methodology in certain physics libraries (like Box2D, Matter.js, and the upcoming toxiclibs). This way you’ll know what to say at the next cocktail party when someone asks you about integration.

    +

    I’ll begin with a question: “What does integration have to do with position, velocity, and acceleration?” To answer, I should first define differentiation, the process of finding a derivative. The derivative of a function is a measure of how a function changes over time. Consider position and its derivative. Position is a point in space, while velocity is the change in position over time. Therefore, velocity can be described as the derivative of position. And what’s acceleration? The change in velocity over time. Acceleration is the derivative of velocity.

    +

    Integration, the process of finidng an integral, is the inverse of differentiation. For example, the integral of an object’s velocity over time tells us the object’s new position when that time period ends. Position is the integral of velocity, and velocity is the integral of acceleration.

    +

    Since the physics simulations in this book are founded on the notion of calculating acceleration based on forces, integration is needed to figure out where the object is after a certain period of time (like one cycle of the draw() loop). I other words, you’ve been doing integration all along! It looks like this:

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

    The above methodology is known as Euler integration (named for the mathematician Leonhard Euler, pronounced “Oiler”) or the Euler method. It’s essentially the simplest form of integration and very easy to implement in code (see the two lines above!) However, it is not necessarily the most efficient form, nor is it close to being the most accurate. Why is Euler inaccurate? Let’s think about it this way. When you bounce on a pogo stick down the sidewalk, does the pogo stick sit in one position at time equals one second, then disappear and suddenly reappear in a new position at time equals two seconds, and do the same thing for three seconds, and four, and five? No, of course not. The pogo stick bounces continuously down the sidewalk. But what’s happening in a p5.js sketch? A circle is at one position at frame 0, another at frame 1, another at frame 2. Sure, at thirty frames per second, you see the illusion of motion. But a new position is only computed every N units of time, whereas the real world is perfectly continuous. This results in some inaccuracies, as shown in the diagram below:

    +

    This methodology is known as Euler integration (named for the mathematician Leonhard Euler, pronounced “Oiler”), or the Euler method. It’s essentially the simplest form of integration, and it’s very easy to implement in code—just two lines! However, it’s not necessarily the most efficient form, nor is it close to being the most accurate.

    +

    Why is Euler inaccurate? Think about it this way: when you bounce down a sidewalk on a pogo stick, does the pogo stick sit in one position at time equals 1 second, then disappear and suddenly reappear in a new position at time equals 2 seconds, and do the same thing for 3 seconds, and 4, and 5? No, of course not. The pogo stick moves continuously through time. But what’s happening in a p5.js sketch? A circle is at one position at frame 0, another at frame 1, another at frame 2, and so on. Sure, at 30 frames per second, you see the illusion of motion. But a new position is only computed every N units of time, whereas the real world is perfectly continuous. This results in some inaccuracies, as shown in Figure 6.10.

    - Figure 6.10: Euler approximation of a curve -
    Figure 6.10: Euler approximation of a curve
    + Figure 6.10: The Euler approximation of a curve +
    Figure 6.10: The Euler approximation of a curve
    -

    The “real world” is the curve; Euler simulation is the series of line segments.

    -

    One option to improve on Euler is to use smaller time steps—instead of once per frame, you could recalculate an object’s position twenty times per frame. But this isn’t practical; the sketch might then run too slowly.

    +

    The “real world” is the smooth curve; the Euler simulation is the series of straight line segments. One option to improve on Euler is to use smaller time steps—instead of once per frame, you could recalculate an object’s position 20 times per frame. But this isn’t practical; the sketch might then run too slowly.

    I still believe that Euler is the best method for learning the basics, and it’s also perfectly adequate for most of the projects you might want to make with p5.js. Anything lost in efficiency or inaccuracy is made up for in ease of use and understandability. For better accuracy, for example, the Box2D engine uses something called symplectic Euler or semi-explicit Euler, a slight modification of Euler. Other engines use an integration method called Runge-Kutta (named for German mathematicians C. Runge and M. W. Kutta) physics engines.

    -

    A very popular integration method used in physics libraries, including both matter.js and toxiclibs.js, is known as "Verlet integration." A simple way to describe Verlet integration is to think of the typical motion algorithm without explicitly storing velocity. After all, you don’t really need to store the velocity; if you always know where an object was at one point in time and where it is now, you can extrapolate its velocity. Verlet integration does precisely this, calculating velocity on the fly while the program is running, instead of maintaining a separate velocity variable. Verlet integration is particularly well suited for particle systems, especially those with spring connections between the particles. The details of how it works are handled by libraries, however, if you are interested in diving deeper into Verlet physics, I would suggest reading the seminal paper on the topic, from which just about every Verlet computer graphics simulation is derived: Jakobsen, Thomas. "Advanced character physics." Game Developer Conference (2001).

    +

    Another very popular integration method used in physics libraries, including both Matter.js and toxiclibs.js, is known as Verlet integration. A simple way to describe Verlet integration is to think of the typical motion algorithm without explicitly storing velocity. After all, you don’t really need to store the velocity; if you always know where an object was at one point in time and where it is now, you can extrapolate its velocity. Verlet integration does precisely this, calculating velocity on the fly while the program is running, instead of maintaining a separate velocity variable.

    +

    Verlet integration is particularly well suited for particle systems, especially those with spring connections between the particles. Physics libraries hide the details from you so you don’t have to worry about how it all works, but if you’re interested in diving deeper into Verlet physics, I suggest reading the seminal paper on the topic, from which just about every Verlet computer graphics simulation is derived: “Advanced Character Physics” by Thomas Jackobsen.

    Verlet Physics with toxiclibs.js

    toxiclibs is an independent, open source library collection for computational design tasks with Java & Processing developed by Karsten “toxi” Schmidt. The classes are purposefully kept fairly generic in order to maximize re-use in different contexts ranging from generative design, animation, interaction/interface design, data visualization to architecture and digital fabrication, use as teaching tool and more. — toxiclibs.org (last seen October 2021).

    -

    Around 2005, Karsten Schmidt began work on toxiclibs, a sweeping and pioneering open source library for computational design, specifically built for the Java version of Processing. Though it hasn’t been actively maintained in over 10 years, the concepts and techniques demonstrated by the library can be found in countless creative coding projects today.

    -

    Karsten Schmidt continues to contribute to the creative coding field today through his recent project, thi.ng/umbrella. This work can be considered an indirect successor to toxiclibs, but with a much greater scope, detail, and extent. If you like this book, you might specifically enjoy ou can exploring thi.ng/vectors, which provides over 800 vector algebra functions using plain vanilla JavaScript arrays.

    -

    While thi.ng/umbrella may be a more modern and sophisticated approach, I find that toxiclibs remains a versatile tool, and I continue to use a version compatible with the latest version of Processing (4.1 as of the time of this writing) today. For this book, we should thank our lucky starts for toxiclibs.js, a JavaScript adaptation of the library, created by Kyle Phillips (”hapticdata”). I am only going to cover on a few examples related to Verlet physics, but toxiclibs.js includes a suite of other packages with functionality related to with, color, geometry, math, and more.

    -

    The examples I'm about to demonstrate in this chapter could also be created using matter.js, which I've spent the bulk of this chapter covering in depth. However, I've decided to move to toxiclibs for several reasons. The library holds a special place in my heart as a personal favorite, and is historically significant. I also believe that showing more than one physics library is important for providing a broader understanding of the tools and approaches available.

    -

    So how do you decide which library you should use? Matter.js or toxiclibs? Or something else? If you fall into one of the following two categories, your decision is a bit easier:

    -

    1. My project involves collisions. I have circles, squares, and other strangely shaped objects that knock each other around and bounce off each other.

    -

    In this case, you are going to want to use matter.js. toxiclibs.js does not handle “rigid body” collisions.

    -

    2. My project involves lots of particles flying around the screen. Sometimes they attract each other. Sometimes they repel each other. And sometimes they are connected with springs.

    -

    In this case, toxiclibs.js is likely your best choice. It is simpler to use in some ways than matter.js and particularly well suited to connected systems of particles. Toxiclibs.js is also high performance, due to the speed of the Verlet integration algorithm (not to mention the fact that it gets to ignore all of the collision geometry).

    -

    Here is a little chart that covers some of the features for each physics library.

    +

    Around 2005, Karsten Schmidt began work on toxiclibs, a sweeping and pioneering open source library for computational design, specifically built for the Java version of Processing. Though it hasn’t been actively maintained in over 10 years, the concepts and techniques that the library demonstrated can be found in countless creative coding projects today.

    +

    Schmidt continues to contribute to the creative coding field today through his recent project, thi.ng/umbrella. This work can be considered an indirect successor to toxiclibs, but with a much greater scope and detail. If you like this book, you might especially enjoy exploring thi.ng/vectors, which provides over 800 vector algebra functions using plain vanilla JavaScript arrays.

    +

    While thi.ng/umbrella may be a more modern and sophisticated approach, I find that toxiclibs remains a versatile tool, and I continue to use a version compatible with the latest version of Processing (4.1 as of the time of this writing). For this book, we should thank our lucky starts for toxiclibs.js, a JavaScript adaptation of the library, created by Kyle Phillips (”hapticdata”). I’m only going to cover on a few examples related to Verlet physics, but toxiclibs.js includes a suite of other packages with functionality related to with, color, geometry, math, and more.

    +

    The examples I’m about to demonstrate could also be created using Matter.js, but I've decided to move to toxiclibs for several reasons. The library holds a special place in my heart as a personal favorite, and it’s historically significant. I also believe that showing more than one physics library is important for providing a broader understanding of the tools and approaches available.

    +

    This switch from Matter to toxiclibs raises an important question, though: how should you decide which library to use for a project? Matter.js, or toxiclibs, or something else? If you fall into one of the following two categories, your decision is a bit easier:

    +

    1. My project involves collisions. I have circles, squares, and other strangely shaped objects that knock each other around and bounce off each other. In this case, you’re going to want to use Matter.js, since toxiclibs.js doesn’t handle “rigid body” collisions.

    +

    2. My project involves lots of particles flying around the screen. Sometimes they attract each other. Sometimes they repel each other. And sometimes they’re connected with springs. In this case, toxiclibs.js is likely your best choice. It’s simpler to use in some ways than Matter.js and particularly well suited to connected systems of particles. It’s also high performance, due to the speed of the Verlet integration algorithm (not to mention the fact that it gets to ignore all of the collision geometry).

    +

    Here’s a little chart that covers some of the features for each physics library.

    - - + + - + - + - + - - + + - - + +
    Featurematter.jstoxiclibs VerletPhysicsMatter.jstoxiclibs.js
    rigid body collisionsYesYes No
    3D physics NoYesYes
    particle attraction and repulsion forces NoYesYes
    spring connections (force-based)YesYesYesYes
    constraints (general purpose connections)Yesconstraints (general-purpose connections)Yes No
    -

    All of the documentation and downloads for the library files can be found at the toxiclibs.js website: haptic-data.com/toxiclibsjs. For the examples in this book, I’ll be working with a hosted “CDN” version of the library referenced index.html in the same way I demonstrated in Section 6.2 with matter.js.

    +

    All of the documentation and downloads for the library files can be found at the toxiclibs.js website: http://haptic-data.com/toxiclibsjs. For the examples in this book, I’ll be working with a hosted CDN version of the library referenced in index.html, just as I demonstrated earlier for Matter.js. Here’s the <script> element to add:

    <script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script>
    -

    The bulk of this chapter focused on the core elements of a matter.js sketch: world, vector, body, constraint. This will give you a head start on understanding toxiclibs.js, since it follows a similar structure.

    +

    My overview of Matter.js focused on a few key features of that library: world, vector, body, constraint. This has actually given you a head start on understanding toxiclibs.js as well, since it follows a similar structure. The following table shows the corresponding toxiclibs features.

    - + @@ -1109,14 +1126,15 @@ location.add(velocity);
    matter.jsMatter.js toxiclibs.js
    -

    Vectors with toxiclibs.js

    -

    Here we go again. Remember all that time spent learning the ins and outs of the p5.Vector class? Then remember how when you got to matter.js, you have to revisit all those concepts with Matter.Vector? Well, it’s time to do it again. toxiclibs.js also includes its own vector classes, one for two dimensions and one for three: Vec2D and Vec3D. These are both found in the toxi.geom package and can be aliased in the same matter as Vector with matter.js.

    +

    I’ll discuss how some of these features translate to toxclibs.js, before putting them together to create some interesting examples.

    +

    Vectors

    +

    Here we go again. Remember all that time spent learning the ins and outs of the p5.Vector class? Then remember how you had to revisit all those concepts with Matter.js and the Matter.Vector class? Well, it’s time to do it again, because toxiclibs.js also includes its own vector classes. It has one for two dimensions and one for three: Vec2D and Vec3D. These are both found in the toxi.geom package and can be aliased in the same manner as Vector with Matter.js.

    let { Vec2D, Vec3D } = toxi.geom;
    -

    Once again, toxiclibs.js vectors are the same conceptually, but have their own style and syntax new syntax. Let’s just review some of the basic vector math operations with p5.Vector now translated to Vec2D (I’m sticking with 2D to match the rest of this book, but I encourage you to explore 3D).

    +

    Once again, toxiclibs.js vectors are conceptually the same as the p5.js vectors we know and love, but they have their own style and syntax . Here’s an overview of how some of the basic vector math operations from p5.Vector translated to Vec2D. (I’m sticking with 2D to match the rest of this book, but I encourage you to explore 3D vectors as well.)

    - + @@ -1159,8 +1177,9 @@ a.normalize();
    PVectorp5.Vector Vec2D
    -

    Building the toxiclibs.js physics world

    -

    The classes to describe the world and its particles and springs in toxiclibs.js are found in toxi.physics2d. I’m also going to use a Rect object (to describe a generic rectangle boundary) and GravityBehavior to apply a global gravity force to the world. Including Vec2D, I now have all the following classes.

    +

    Notice in particular how toxiclibs.js vectors are created by calling the Vec2D constructor with the new keyword, rather than by using a factory method like createVector().

    +

    The Physics World

    +

    The classes to describe the world and its particles and springs in toxiclibs.js are found in toxi.physics2d. I’m also going to use a Rect object (to describe a generic rectangle boundary) and GravityBehavior to apply a global gravity force to the world. Including Vec2D, I now have all the following class aliases.

     // The necessary geometry classes for vectors and rectangles
     let { Vec2D, Rect } = toxi.geom;
    @@ -1170,34 +1189,35 @@ let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
     
     // For the world's gravity
     let { GravityBehavior } = toxi.physics2d.behaviors;
    -

    The first step is to create a the world.

    +

    The first step is to create the world.

    let physics;
     
     function setup() {
       // Creating a toxiclibs Verlet physics world
       physics = new VerletPhysics2D();
    -

    Once I have the VerletPhysics world, I can set global properties. For example, if I want a hard boundaries past which particles cannot travel, I can provide a rectangular bounds.

    +

    Once I have the VerletPhysics world, I can set global properties. For example, if I want a hard boundaries beyond which particles can’t travel, I can provide a rectangular bounds.

      physics.setWorldBounds(new Rect(0, 0, width, height));
    -

    In addition, I can add gravity with theGravityBehavior object. A gravity behavior requires a vector—how strong and in what direction is the gravity?

    +

    In addition, I can add gravity with the GravityBehavior object. A gravity behavior requires a vector—how strong and in what direction is the gravity?

      physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
     }
    -

    Finally, in order to calculate the physics of the world and move the objects in the world, I have to call update(). Typically this would happen once per frame in draw().

    +

    Finally, in order to calculate the physics of the world and move the world’s objects around, I have to call the world’s update() method. Typically this would happen once per frame in draw().

    function draw() {
       //{!1} This is the same as matter.js Engine.update()
       physics.update();
     }
    -

    Particles and Springs in toxiclibs.js

    -

    In the matter.js examples, I created my own class (called, say, Particle) and included a reference to a matter.js body.

    +

    Now all that remains is to populate the world.

    +

    Particles

    +

    The toxiclibs.js equivalent of a Matter.js body—a thing that exists in the world and experiences physics—is a particle, as represented by the VerletParticle2D class. How can I integrate toxiclibs.js particles into a p5.js sketch?

    +

    In the Matter.js examples, I created my own class (called, say, Particle) and included a reference to a Matter.js body.

    class Particle {
       constructor(x, y, r) {
         this.body = Bodies.circle(x, y, r);
       }
     }
    -

    This technique is somewhat redundant since matter.js itself keeps track of the bodies in its world. However, it allows me to manage which body is which (and therefore how each body is drawn) without having to rely on iterating through the internal lists of matter.js

    -

    Let’s look at how you might take the same approach with the class VerletParticle2D in toxiclibs.js. I want to make my own Particle class so that I can draw the particles and include any custom properties. Following the design pattern above, I’d probably write the code as follows:

    +

    This technique was somewhat redundant, since Matter.js itself keeps track of the bodies in its world. However, it allowed me to manage which body is which (and therefore how each body should be drawn) without having to rely on iterating through Matter.js’s internal lists. I might take the same approach with toxiclibs.js, making my own Particle class that stores a reference to a VerletParticle2D object. This way I’ll be able to give the particles custom properties and draw them however I want. I’d probably write the code as follows:

    class Particle {
       constructor(x, y, r) {
    -    //{!1} A VerletParticle needs an initial x,y position but it has no geometry so the r is only used for drawing
    +    //{!1} A VerletParticle needs an initial x,y position, but it has no geometry, so the r is only used for drawing.
         this.particle = new VerletParticle2D(x, y);
         this.r = r;
       }
    @@ -1209,7 +1229,7 @@ function setup() {
         circle(this.particle.x, this.particle.y, this.r * 2);
       }
     }
    -

    Looking at the above, you’ll first notice that drawing the particle is as simple as grabbing the x and y properties and using them with circle(). Second, you might notice that this Particle class’s sole purpose is to store a reference to a VerletParticle2D object. This hints at something. Remember the discussion of inheritance back in Chapter 4: Particle Systems? What is a Particle object other than an “augmented” VerletParticle2D? Why bother making a Verlet particle inside a particle when I could simply extend VerletParticle2D?

    +

    Looking over this code, you might first notice that drawing the particle is as simple as grabbing the x and y properties and using them with circle(). Second, you might notice that this Particle class’s sole purpose is to store a reference to a VerletParticle2D object. This hints at something important. Think back to the discussion of inheritance in Chapter 4, and then ask yourself: what is a Particle object other than an “augmented” VerletParticle2D object? Why bother making two objects—a Particle and a VerletParticle2D—for every one particle in the world, when I could simply extend the VerletParticle2D class to include the extra code needed to draw the particle?

    class Particle extends VerletParticle2D {
       constructor(x, y, r) {
         //{!1} Calling super() with x,y so that the object is initialized properly
    @@ -1226,29 +1246,33 @@ function setup() {
         circle(this.x, this.y, this.r * 2);
       }
     }
    -

    Furthermore, you mind may be blown that the VerletParticle2D class is a subclass of Vec2D. So in addition to inheriting everything from VerletParticle2D, the Particle class above has actually inherited all of theVec2D functions available as well!

    -

    I can now create particle objects.

    +

    Furthermore, at the risk of blowing your mind, it turns out that the VerletParticle2D class is a subclass of Vec2D. This means that in addition to inheriting everything from VerletParticle2D, the Particle class has actually inherited all of theVec2D methods as well!

    +

    I can now create new particles:

    let particle = new Particle(width/2, height/2, 8);
    -

    Just creating a particle isn’t enough, however. I have to make sure to explicitly add the particle to the world with the addParticle() function.

    +

    Just creating a particle isn’t enough, however. Much like in Matter.js, I have to explicitly add the new particle to the world. In toxiclibs.js, this is ddone with the addParticle() method.

    physics.addParticle(particle);
    -

    Now, if you look at the documentation you’ll see that the addParticle() expects a VerletParticle2D object. How did it work to then pass into the function my own Particle object? Remember that tenet of object-oriented programming—polymorphism? Here, because the Particle class extends VerletParticle2D, I treat the particle in two different ways—as a Particle or as a VerletParticle2D. This is an incredibly powerful feature of object-oriented programming. If you build custom classes based that inherit from toxiclibs.js, you can use those objects in conjunction with all of the functions toxiclibs.js has to offer.

    +

    If you look at the toxiclibs.js documentation you’ll see that addParticle() expects a VerletParticle2D object. But I’ve passed it a Particle object. Does that work?

    +

    Yes! Remember one of the tenets of object-oriented programming: polymorphism. Here, because the Particle class extends VerletParticle2D, I can treat the particle in two different ways—as a Particle or as a VerletParticle2D. This is an incredibly powerful feature of object-oriented programming. If you build custom classes that inherit from toxiclibs.js classes, you can use the objects of those classes in conjunction with all of the methods toxiclibs.js has to offer.

    +

    Springs

    In addition to the VerletParticle2D class, toxiclibs.js has a set of classes that allow you to connect particles with spring forces. There are three types of springs in toxiclibs:

      -
    • VerletSpring2D: This class creates a springy connection between two particles. A spring’s properties can be configured in such a way as to create a stiff stick-like connection or a highly elastic stretchy connection. A particle can also be locked so that only one end of the spring can move.
    • -
    • VerletConstrainedSpring2D: A VerletConstrainedSpring2D object is a spring whose maximum distance can be limited. This can help the whole spring system achieve better stability.
    • -
    • VerletMinDistanceSpring2D: A VerletMinDistanceSpring2D object is a spring that only enforces its rest length if the current distance is less than its rest length. This is handy if you want to ensure objects are at least a certain distance from each other, but don’t care if the distance is bigger than the enforced minimum.
    • +
    • VerletSpring2D: A springy connection between two particles. The spring’s properties can be configured in such a way as to create a stiff, stick-like connection or a highly elastic, stretchy connection. A particle can also be locked so that only one end of the spring can move.
    • +
    • VerletConstrainedSpring2D: A spring whose maximum distance can be limited. This can help the whole spring system achieve better stability.
    • +
    • VerletMinDistanceSpring2D: A spring that only enforces its rest length if the current distance is less than its rest length. This is handy if you want to ensure objects are at least a certain distance from each other, but don’t care if the distance is bigger than the enforced minimum.
    -

    The inheritance and polymorphism technique employed in the previous section also proves to be useful when creating springs. A spring expects two particles when it is created. And again, because the Particle class extends VerletParticle2D, a VerletSpring2D object will accept Particle objects passed into its constructor. Let’s take a look at some example code that assumes the existence of two particles particle1 and particle2 and creates a connection between them with a given rest length and strength.

    +

    Inheritance and polymorphism once again prove to be useful when making springs. A spring expects two VerletParticle2D objects when it’s created, but as before, two Particle objects will do, since Particle extends VerletParticle2D.

    +

    Here’s some example code to create a spring. This snippet assumes the existence of two particles, particle1 and particle2, and creates a connection between them with a given rest length and strength.

    //{!1} What is the rest length of the spring?
     let length = 80;
     //{!1} How strong is the spring?
     let strength = 0.01;
     let spring = new VerletSpring2D(particle1, particle2, length, strength);
    -

    Just as with particles, in order for the connection to actually be part of the physics world, we need to explicitly add it.

    +

    Just as with particles, in order for the connection to actually be part of the physics world, it must be explicitly added to the world.

    physics.addSpring(spring);

    Putting It All Together: A Simple Interactive Spring

    -

    One thing I demonstrated with matter.js is that the physics simulation broke down when I overrode it and manually set the position of a body. With toxiclibs, I don’t have this problem. If I want to move the position of a particle, I can in fact set its x,y position manually. However, before doing so, it’s generally a good idea to call the lock() function.

    -

    lock() is used to fix a particle in place and is identical to setting the isStatic property to true in matter.js. Here I am going to demonstrate how to lock a particle temporarily, alter its position, and then unlock it so that it continues to move according to the physics simulation. For example, consider the scenario where I want to move a particle whenever the mouse is pressed.

    +

    I have almost everything I need to build a simple first toxiclibs example: two particles connected to form a springy pendulum. There’s one more element I want to ad, however: mouse interactivity.

    +

    With Matter.js, I explained that the physics simulation breaks down if you manually override a body’s position by setting it to the mouse. With toxiclibs, this isn’t a problem. If I want to, I can set a particle’s (x, y) position manually. However, before doing so, it’s generally a good idea to call the particle’s lock() method, which fixes the particle in place. This is identical to setting the isStatic property to true in Matter.js.

    +

    The idea is to lock the particle temporarily so it stops responding to the world’s physics, alter its position, and then unlock it (with the unlock() method) so it can start moving again from its new location. For example, consider the scenario where I want to reposition a particle whenever the mouse is pressed.

      if (mouseIsPressed) {
         //{!4} First lock the particle, then set the x and y, then unlock() it.
         particle1.lock();
    @@ -1256,7 +1280,7 @@ let spring = new VerletSpring2D(particle1, particle2, length, strength);
    particle1.y = mouseY; particle1.unlock(); } -

    And now I’m ready to put all of these elements together in a simple example that connects two particles with a spring. One particle is locked in place, and the other can be moved by dragging the mouse. Note that this example is virtually identical to Example 3.x: Springy Pendulum.

    +

    And with that, I’m ready to put all these elements together in a simple sketch with two particles connected by a spring. One particle is permanently locked in place, and the other can be moved by dragging the mouse. This example is virtually identical to Example 3.11 from Chapter 3.

    Example 6.11: Simple Spring with toxiclibs

    @@ -1331,29 +1355,25 @@ class Particle extends VerletParticle2D { circle(this.x, this.y, this.r * 2); } } -

    Connected Systems, Part I: String

    -

    The above example, two particles connected with a single spring, is the core building block for what verlet physics is particularly well suited for: soft body simulations. For example, a string can be simulated by connecting a line of particles with springs. A blanket can be simulated by connecting a grid of particles with springs. And a cute, cuddly, squishy cartoon character can be simulated with a custom layout of particles connected with springs.

    +

    Soft Body Simulations

    +

    Verlet physics is particularly well suited for a genre of computer graphics known as soft body simulation. Unlike the rigid body simulations of Matter.js, where hard-edged boxes crash into each other and retain their shapes, soft body simulations involve objects that can deform and change shape with physics. Soft bodies allow for more flexible, fluid, and organic movements. They can stretch, squish, and jiggle in response to forces and collisions, and they appear . . . well, soft.

    +

    One of the first popular examples of soft body physics was SodaConstructor, a game created in the early 2000s. Players could construct and animate custom two-dimensional creatures built out of masses and springs. Other examples over the years have included games like LocoRoco, World of Goo, and more recently, JellyCar.

    +

    The basic building blocks of soft body simulations are particles connected by springs—just like the pair particles in the last example. Figure 6.11 shows how to configure a network of particle-spring connections to make various forms.

    Figure 6.11: Soft body simulation designs
    Figure 6.11: Soft body simulation designs
    -

    Let's begin by simulating a "soft pendulum"—a bob hanging from a string, instead of a rigid arm—and use the design from Figure 6.14 as the basis. Toxiclibs.js does offer a convenient ParticleString2D class that creates a string of connected particles in a single constructor call. However, for demonstration purposes, I will create my own array using a for loop with the goal of giving you a deeper understanding of the system and enabling you to create your own custom designs beyond a single string in the future.

    -

    First, I’l need an array of particles (let’s use the same Particle class built in the previous example).

    +

    As the figure shows, a string can be simulated by connecting a line of particles with springs; a blanket can be simulated by connecting a grid of particles with springs; and a cute, cuddly, squishy cartoon character can be simulated with a custom layout of particles connected with springs. It’s not much of a leap from one to another.

    +

    A String

    +

    I’ll begin by simulating a “soft pendulum”—a bob hanging from a flexible string instead of a rigid arm. As it happens, toxiclibs.js offers a convenient ParticleString2D class that creates a “string” of particles connected by springs in a single constructor call. However, for demonstration purposes, I’ll create my own particle string using an array and a for loop. This way you’ll gain a deeper understanding of the system, enabling you to create your own custom designs beyond a single string in the future.

    +

    First, I need an array of particles (I’ll use the same Particle class built in the previous example).

    let particles = [];
    -

    Now, let’s say I want to have 20 particles, all spaced 10 pixels apart.

    +

    Now, let’s say I want to have 20 particles, all spaced 10 pixels apart, as in Figure 6.12.

    Figure 6.12: Twenty particles all spaced 10 pixels apart
    Figure 6.12: Twenty particles all spaced 10 pixels apart
    -
    for (let i = 0; i < total; i++) {
    -    //{!1} Spacing them out along the x-axis
    -    let particle = new Particle(i * length, 10, 4);
    -    //{!1} Add the particle to the physics world.
    -    physics.addParticle(particle);
    -    //{!1} Add the particle to the array.
    -    particles.push(particle);
    -  }
    -

    I can loop from i equals 0 all the way up to total, with each particle’s y position set to i * 10 so that the first particle is at (0,10), the second at (0,20), the third at (0,30), and so on.

    +

    I can loop from i equals 0 all the way up to total, creating new particles and setting each one’s y position set to i * 10. This way the first particle is at (0,10), the second at (0,20), the third at (0,30), and so on.

    for (let i = 0; i < total; i++) {
       //{!1} Spacing them out along the x-axis
       let particle = new Particle(i * length, 10, 4);
    @@ -1361,27 +1381,26 @@ class Particle extends VerletParticle2D {
       physics.addParticle(particle);
       //{!1} Add the particle to the array.
       particles.push(particle);
    -}
    -
    -

    Even though it’s redundant, I’m going to add the particle to both the toxiclibs.js physics world and to the particles array. This will help me to manage the sketch (especially for the case where they might be more than one “string” of particles.)

    -

    Now for the fun part: It’s time to connect all the particles. Particle index 0 will be connected to particle 1, particle 1 to particle 2, 3 to 4, 4 to 5, and so on.

    +} +

    Even though it’s redundant, I’m adding the particles to both the toxiclibs.js physics world and to the particles array. This will help me manage the sketch (especially for the case where there might be more than one string of particles).

    +

    Now for the fun part: it’s time to connect all the particles. Particle index 0 will be connected to particle 1, particle 1 to particle 2, 2 to 3, 3 to 4, and so on (see Figure 6.13).

    - Figure 6.13: Each particle is connected to the next particle in the array -
    Figure 6.13: Each particle is connected to the next particle in the array
    + Figure 6.13: Each particle is connected to the next particle in the array. +
    Figure 6.13: Each particle is connected to the next particle in the array.
    -

    In other words, particle i needs to be connected to particle i+1 (except for when i represents the last element of the array zero).

    -
    // The loop stops before the last element (total - 1)
    +

    In other words, particle i needs to be connected to particle i+1 (except for when i represents the last element of the array).

    +
    // The loop stops before the last element (total - 1).
     for (let i = 0; i < total - 1; i++) {
    -  // The spring connects particle i to i+1
    +  // The spring connects particle i to i+1.
       let spring = new VerletSpring2D(particles[i], particles[i + 1], spacing, 0.01);    
    -  //{!1} The spring must also be added to the world
    +  //{!1} The spring must also be added to the world.
       physics.addSpring(spring);
     }
    -

    Now, what if I want the string to hang from a fixed point? I can lock one of the particles—the first, the last, the middle one? Let’s go with the first.

    +

    Now, what if I want the string to hang from a fixed point? I can lock one of the particles—perhaps the first, the last, or the middle one. I’ll go with the first.

    particles[0].lock();
    -

    And if I want to draw all the particles as being connected with a line, along with a circle for the last particle (the “bob”), I can use beginShape(), endShape(), and vertex(), accessing the particle positions from the array.

    +

    Finally, I need to draw the particles. Instead of drawing them as circles, however, I want to treat them as points in a line. For that, I can use beginShape(), endShape(), and vertex(), accessing the individual particle positions from the array. I’ll only use the show() method to draw the last particle as a circle, creating a “bob” at the end of the string.

    -

    Example 6.12: Soft swinging pendulum

    +

    Example 6.12: Soft Swinging Pendulum

    @@ -1407,20 +1426,19 @@ for (let i = 0; i < total - 1; i++) {

    The full code available on the book’s website also demonstrates how to drag the “bob” particle with the mouse.

    Exercise 6.10

    -

    Create a hanging cloth simulation using the technique above, but connect all the particles with their neighbors vertically and horizontally.

    +

    Create a hanging cloth simulation using particles and springs. You’ll need to connect each particle with its vertical and horizontal neighbors.

    -

    Connected Systems, Part II: Soft Body Character

    -

    Now that I’ve built a simple connected system, a single string of particles, let’s expand this idea to create a squishy, cute friend in p5.js, otherwise known as a “soft body character.” In computer graphics and game design, a soft body character refers to an object that deforms and changes shape with physics. Unlike a rigid body, which maintains its shape when it moves or collides, a soft body allows for more flexible, fluid, and organic movement. Soft bodies can stretch, squish, and jiggle in response to forces and collisions. One of the first popular examples of soft body physics was SodaConstructor, a game created in the early 2000s. Players could construct and animate custom two-dimensional creatures built out of masses and springs. Other examples over the years include games like LocoRoco, World of Goo, and more recently, JellyCar.

    -

    The first step to building a soft body character is to design a “skeleton”. I’ll begin with a very simple design with only six vertices. Each vertex (drawn as a dot) represents a Particle object and each connection (drawn as a line) represents a Spring object.

    +

    A Soft Body Character

    +

    Now that I’ve built a simple connected system—a single string of particles—I’ll expand on this idea to create a squishy, cute friend in p5.js, otherwise known as a soft body character. The first step is to design a “skeleton” of connected particles. I’ll begin with a very simple design with only six vertices, as shown in Figure 6.XX. Each vertex (drawn as a dot) represents a Particle object, and each connection (drawn as a line) represents a Spring object.

    - Figure X.X Design of a soft body character, the vertices are numbered according to their positions in an array. -
    Figure X.X Design of a soft body character, the vertices are numbered according to their positions in an array.
    + Figure 6.X A skeleton for a soft body character. The vertices are numbered according to their positions in an array. +
    Figure 6.X A skeleton for a soft body character. The vertices are numbered according to their positions in an array.
    -

    Creating the particles is the easy part; it’s exactly the same as before! I’d like to make one change. Rather than having the setup() function add the particles and springs to the physics world, I’ll instead incorporate this responsibility into the Particle constructors.

    +

    Creating the particles is the easy part; it’s exactly the same as before! I’d like to make one change, though. Rather than having the setup() function add the particles and springs to the physics world, I’ll instead incorporate this responsibility into the Particle constructor itself.

    class Particle extends VerletParticle2D {
       constructor(x, y, r) {
         super(x, y);
    @@ -1435,9 +1453,9 @@ for (let i = 0; i < total - 1; i++) {
         circle(this.x, this.y, this.r * 2);
       }
     }
    -

    While it’s not strictly necessary I’d also like to incorporate a Spring class that inherits its functionality from VerletSpring2D. For this example, I want the rest length of the spring to always be equal to the distance between the skeleton’s particles in their resting (or “equilibrium”) state. Additionally, while you may want to enhance the example with a more sophisticated design, I can keep things simple by hardcoding a uniform “strength” value in the Spring constructor.

    +

    While it’s not strictly necessary, I’d also like to make a Spring class that inherits its functionality from VerletSpring2D. For this example, I want the resting length of the spring to always be equal to the distance between the skeleton’s particles in their resting (or “equilibrium”) state. Additionally, I’m keeping things simple here by hardcoding a uniform strength value of 0.01 in the Spring constructor. You may want to enhance the example with a more sophisticated design where different parts of the soft body character have different degrees of springiness.

    class Spring extends VerletSpring2D {
    -  // Constructor receives only two arguments
    +  // Constructor receives two particles as arguments
       constructor(a, b) {
         // Calculating the rest length as the distance between the particles
         let length = dist(a.x, a.y, b.x, b.y);
    @@ -1447,8 +1465,8 @@ for (let i = 0; i < total - 1; i++) {
         physics.addSpring(this);
       }
     }
    -

    Now that I have the Particle and Spring classes, the character can be assembled by adding a series of particles and springs with hardcoded values to arrays.

    -
    //{!2} Storing all the particles and springs in arrays
    +

    Now that I have the Particle and Spring classes, I can assemble the character by adding a series of particles with hardcoded starting positions to a particles array, and a series of spring connections to a springs array.

    +
    //{!2} Store all the particles and springs in arrays.
     let particles = [];
     let springs = [];
     
    @@ -1456,7 +1474,7 @@ function setup() {
       createCanvas(640, 240);
       physics = new VerletPhysics2D();
       
    -  // Creating the vertex positions of the character as particles.
    +  // Create the vertex positions of the character as particles.
       particles.push(new Particle(200, 25));
       particles.push(new Particle(400, 25));
       particles.push(new Particle(350, 125));
    @@ -1464,19 +1482,19 @@ function setup() {
       particles.push(new Particle(200, 225));
       particles.push(new Particle(250, 125));
     
    -  // Creating the vertex positions of the character as particles.
    +  // Connect the vertices with springs.
       springs.push(new Spring(particles[0], particles[1]));
       springs.push(new Spring(particles[1], particles[2]));
       springs.push(new Spring(particles[2], particles[3]));
       springs.push(new Spring(particles[3], particles[4]));
       springs.push(new Spring(particles[4], particles[5]));
     }
    -

    The beauty of this system is that you can easily expand it to create your own design by adding more particles and springs! However, there is one major issue here, if I were to apply a force (like gravity) to the body, it would instantly collapse onto itself. This is where additional “internal” springs come in and keep the character's structure stable while still allowing it to move and squish in a realistic manner.

    +

    The beauty of this system is that you can easily expand it to create your own design by adding more particles and springs! However, there’s one major issue here: I’ve only made connections around the perimeter of the character. if I were to apply a force (like gravity) to the body, it would instantly collapse onto itself. This is where additional “internal” springs come into play, as shown in Figure 6.XX. They keep the character’s structure stable while still allowing it to move and squish in a realistic manner.

    - Figure X.X: Internal springs keep the structure from collapsing, this is just one possible design. Try others! -
    Figure X.X: Internal springs keep the structure from collapsing, this is just one possible design. Try others!
    + Figure 6.X: Internal springs keep the structure from collapsing. This is just one possible design. Try others! +
    Figure 6.X: Internal springs keep the structure from collapsing. This is just one possible design. Try others!
    -

    The final example incorporates the additional springs from Figure X.X, a gravity force, as well as mouse interaction.

    +

    The final example incorporates the additional springs from Figure 6.X, a gravity force, and mouse interaction.

    Example 6.x Soft Body Character

    @@ -1539,7 +1557,7 @@ function draw() { particles[0].unlock(); } }
    -

    For the soft body character example you'll notice that I’m no longer using particle.show() and spring.show() to individually visualize the particles and springs. Instead, I’m drawing the character as a unified shape with the beginShape() and endShape() functions. This approach conveniently hides the internal springs that give the character structure but don't need to be visually represented. It also opens up possibilities for adding other design elements, like eyes or antennae, that may not be directly connected to the physics of the character—though, of course, they can be if you choose!

    +

    For the soft body character example, you’ll notice that I’m no longer using particle.show() and spring.show() to individually visualize the particles and springs. Instead, I’m drawing the character as a unified shape with the beginShape() and endShape() functions. This approach conveniently hides the internal springs that give the character structure but don't need to be visually represented. It also opens up possibilities for adding other design elements, like eyes or antennae, that may not be directly connected to the physics of the character—though, of course, they can be if you choose!

    Exercise 6.11 @@ -1550,16 +1568,17 @@ function draw() {

    -

    Connected Systems, Part III: Force-Directed Graph

    -

    Have you ever had the following thought? “I have a whole bunch of stuff I want to draw and I want all that stuff to be spaced out evenly in a nice, neat, organized manner. Otherwise I have trouble sleeping at night.”

    -

    This is not an uncommon problem in computational design. One solution is typically referred to as a “force-directed graph.” A force-directed graph is a visualization of elements—let’s call them “nodes”—in which the positions of those nodes are not manually assigned. Rather, the nodes arrange themselves according to a set of forces. While any forces can be used, a classic method involves spring forces. And so toxiclibs.js is perfect for this scenario.

    +

    A Force-Directed Graph

    +

    Have you ever had the following thought? “I have a whole bunch of stuff I want to draw, and I want all that stuff to be spaced out evenly in a nice, neat, organized manner. Otherwise, I’ll have trouble sleeping at night.”

    +

    This isn’t an uncommon problem in computational design. One solution is a force-directed graph, a visualization of elements—let’s call them “nodes”—in which the positions of those nodes aren’t manually assigned. Instead, the nodes arrange themselves according to a set of forces. While any forces can be used, a classic method involves spring forces: each node is connected to every other node with a spring, such that when the springs reach equilibrium, the nodes are evenly spaced (see Figure 6.14). Sounds like a job for toxiclibs.js.

    Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.
    Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.
    -

    Let’s walk through building a sketch to create clusters of nodes as depicted in Figure 6.14. First, I’ll need a class to describe a “node” in the system. Because the term “node” is associated with the JavaScript framework “node.js” I’ll stick with the term “particle” avoid any confusion. I can use the Particle class from the soft body example and build this new example by encapsulating a list of particles into a new class called Cluster.

    +

    To create a force-directed graph, I’ll first need a class to describe an individual node in the system. Because the term “node” is associated with the JavaScript framework Node.js, I’ll stick with the term “particle” to avoid any confusion, and I’ll continue using my Particle class from the earlier soft body examples.

    +

    Next, I’ll encapsulate a list of N particles into a new class called Cluster that represents the graph as a whole. The particles all start out near the center of the canvas.

    class Cluster {
    -  // A cluster is initialized with N nodes spaced out by length
    +  // A cluster is initialized with N nodes spaced out by length.
       constructor(n, length) {
         this.particles = [];
         for (let i = 0; i < n; i++) {      
    @@ -1570,7 +1589,8 @@ function draw() {
           this.particles.push(new Particle(x, y, 4));
         }
       }
    -

    Let’s assume there is a show() function to draw all the particles in the cluster as well as a Cluster object created in setup() and rendered it in draw(). If I ran the sketch as is, nothing would happen. Why? Because I have yet to implement that whole force-directed graph part! I need to connect every single node to every other node with a spring. This is the same idea as in the soft body character, but rather than hand-craft a skeleton here I want to write an algorithm to make all the connections. But what exactly do I mean by that? Let’s assume there are four Particle objects: 0, 1, 2 and 3. Here are the connections:

    +

    Let’s assume the Cluster class also has a show() method to draw all the particles in the cluster, and that I’ll create a new Cluster object in setup() and render it in draw(). If I ran the sketch as is, nothing would happen. Why? Because I have yet to implement the whole force-directed graph part! I need to connect every single node to every other node with a spring. This is somewhat similar to creating a soft body character, but rather than hand-craft a skeleton, I want to write an algorithm to make all the connections automatically.

    +

    What exactly do I mean by that? Say there are four Particle objects: 0, 1, 2 and 3. Here are the connections:

    @@ -1611,24 +1631,23 @@ function draw() {

    Notice two important details about the list of connections.

      -
    • No particle is connected to itself. 0 is not connected to 0, 1 is not connected to 1, and so on.
    • -
    • Connections are not repeated in reverse. In other words, if 0 is connected to 1, I don’t need to explicitly say that 1 is connected to 0 because, well, it is by the definition of how a spring works!
    • +
    • No particle is connected to itself. That is, 0 isn’t connected to 0, 1 isn’t connected to 1, and so on.
    • +
    • Connections aren’t repeated in reverse. For example, if 0 is connected to 1, I don’t need to explicitly say that 1 is also connected to 0. I already know it is by the definition of how a spring works!
    -

    So how to write the code to make these connections for N particles?

    -

    Look at the left column of the table above. It reads: 000 11 2. This tells me that I to access each particle in the list from 0 to N-1.

    +

    How to write the code to make these connections for N particles? Look at the left column of the table of connections. It reads: 000 11 2. This tells me that I need to access each particle in the, list from 0 to N-1.

        for (let i = 0; i < this.particles.length - 1; i++) {
           // Using the variable particle_i to store the particle reference
           let particle_i = this.particles[i];
    -

    Now, I know we need to connect node 0 to nodes 1, 2, and 3. For node 1: 2 and 3. For node 2, only 3! So for every node i, I can iterate from i+1 all the way until the end of the array. I’ll use the counter variable j for this purpose.

    -
          //{!1 .bold} Look how j starts at i + 1.
    +

    Now look at the right column of the table. I need to connect node 0 to nodes 1, 2, and 3. For node 1: 2 and 3. For node 2, only 3. In general, for every node i, I need to iterate from i + 1 all the way until the end of the array. I’ll use the counter variable j for this purpose.

    +
          //{!1} Look how j starts at i + 1.
           for (let j = i + 1; j < this.particles.length; j++) {
             let particle_j = this.particles[j];
    -

    For every pair of particles i and j, I can then create a spring. I’ll go back to using VerletSpring2D directly but you could also incorporate a custom Spring class.

    +

    For every pair of particles i and j, I can then create a spring. I’ll go back to using VerletSpring2D directly, but you could also incorporate a custom Spring class.

            //{!1} The spring connects particle i and j.
             physics.addSpring(new VerletSpring2D(particle_i, particle_j, length, 0.01));
           }
         }
    -

    Assuming those connections are made in the Cluster constructor, all that is left to do is create cluster in setup() and call show() in draw()!

    +

    Assuming those connections are made in the Cluster constructor, all that’s left is to create the cluster in setup() and call show() in the draw() loop!

    Example 6.13: Cluster

    @@ -1667,19 +1686,19 @@ function draw() {

    Attraction and Repulsion Behaviors

    -

    When it came time to creating an “attraction” example for matter.js, I showed how the Matter.Body class included an applyForce() function. All I then needed to do was calculate the attraction force F_g = (G \times \text{m1} \times \text{m2}) \div \text{distance}^2 as a vector and apply it to the body. Similarly, the toxiclibs.js VerletParticle class also includes a method called addForce() that can apply any calculated force to a particle.

    -

    However, toxiclibs.js also takes this idea one step further by offering built-in functionality for common forces (let’s call them “behaviors”) such as attraction! By adding anAttractionBehavior object to any given VerletParticle2D object, all other particles in the physics world will experience that attraction force.

    -

    Let’s assume I have a Particle class that extends VerletParticle2D.

    +

    When it came time to create an attraction example for Matter.js, I showed how the Matter.Body class includes an applyForce() method. All I then needed to do was calculate the attraction force F_g = (G \times m_1 \times m_2) \div d^2 as a vector and apply it to the body. Similarly, the toxiclibs.js VerletParticle2D class also includes a method called addForce() that can apply any calculated force to a particle.

    +

    However, toxiclibs.js also takes this idea one step further by offering built-in functionality for common forces (called “behaviors”) such as attraction! For example, if you add anAttractionBehavior object to a particular VerletParticle2D object, all other particles in the physics world will experience an attraction force toward that particle.

    +

    Say I create an instance of my Particle class (which extends the VerletParticle2D class).

    let particle = new Particle(320, 120);
    -

    For any Particle object, AttractionBehavior can be created and associated with that particle.

    +

    Now I can create an AttractionBehavior associated with that particle.

    let distance = 20;
     let strength = 0.1;
     let behavior = new AttractionBehavior(particle, distance, strength);
    -

    Notice how the behavior is created with two arguments—distance and strength. The distance specifies the range within which the behavior will be applied. In the above scenario, only other particles within twenty pixels will experience the attraction force. The strength, of course, specifies how strong the force is.

    +

    Notice how the behavior is created with three arguments: a particle to assign it to, a distance, and a strength. The distance specifies the range within which the behavior will be applied. In this case, only particles within 20 pixels will experience the attraction force. The strength, of course, specifies how strong the force is.

    Finally, in order for the force to be activated, the behavior needs to be added to the physics world.

    physics.addBehavior(behavior);
    -

    This means everything that lives in the physics simulation will always be attracted to that particle, as long as it is within the distance threshold.

    -

    Even though toxiclibs.js does not handle collisions, you can create a collision-like simulation by adding a repulsive behavior to each and every particle (so that every particle repels every other particle). If the force is strong and only activated in a short range (scaled to the particle radius) it behaves much like a rigid body collision. Let’s look at how to modify the Particle class to do this.

    +

    Now everything that lives in the physics simulation will always be attracted to that particle, as long as it’s within the distance threshold.

    +

    The AttractionBehavior class is a very powerful tool. For example, even though toxiclibs.js doesn’t automatically handle collisions like Matter.js does, you can create a collision-like simulation by adding an AttractionBehavior with a negative strength—a repulsive behavior—to each and every particle. If the force is strong and only activated within a short range (scaled to the particle’s radius), the result is much like a rigid body collision. Here’s how to modify the Particle class to do this.

    class Particle extends VerletParticle2D {
       constructor(x, y, r) {
         super(x, y);
    @@ -1722,18 +1741,18 @@ let behavior = new AttractionBehavior(particle, distance, strength);
    circle(this.x, this.y, this.r * 2); } }
    -

    Just as discussed in Chapter 5’s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles can run very slow due to N^2 nature of the algorithm (every particle checking every other particle). Toxiclibs.js offers a built-in spatial indexing feature (TheSpatialBins class) and physics.setIndex()that can significantly speed up these simulations. For more, check the additional examples offered on the book’s website.

    +

    Just as discussed in Chapter 5’s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles can run very slow due to N^2 nature of the algorithm (every particle checking every other particle). Toxiclibs.js offers a built-in spatial indexing feature (theSpatialBins class) and physics.setIndex()that can significantly speed up these simulations. For more, check the additional examples offered on the book’s website.

    Exercise 6.13

    Use AttractionBehavior in conjunction with spring forces.

    The Ecosystem Project

    -

    Step 5 Exercise:

    -

    Take your system of creatures from Step 4 and use a physics engine to drive their motion and behaviors. Some possibilities:

    +

    Step 6 Exercise:

    +

    Take your system of creatures from Step 5 and use a physics engine to drive their motion and behaviors. Some possibilities:

      -
    • Use matter.js to allow collisions between creatures. Consider triggering events when creatures collide.
    • -
    • Use matter.js to augment the design of your creatures. Build a skeleton with distance joints or make appendages with revolute joints.
    • +
    • Use Matter.js to allow collisions between creatures. Consider triggering an event when two creatures collide.
    • +
    • Use Matter.js to augment the design of your creatures. Build a skeleton with distance joints or make appendages with revolute joints.
    • Use toxiclibs.js to augment the design of your creature. Use a chain of toxiclibs particles for tentacles or a mesh of springs as a skeleton.
    • Use toxiclibs.js to add attraction and repulsion behaviors to your creatures.
    • Use spring (or joint) connections between objects to control their interactions. Create and delete these springs on the fly. Consider making these connections visible or invisible to the viewer.
    • diff --git a/content/09_ga.html b/content/09_ga.html index d7bb6e6..db16deb 100644 --- a/content/09_ga.html +++ b/content/09_ga.html @@ -69,8 +69,8 @@ console.log(s);
    - - + + diff --git a/content/10_nn.html b/content/10_nn.html index 850bada..c7e8a16 100644 --- a/content/10_nn.html +++ b/content/10_nn.html @@ -479,18 +479,117 @@ function draw() {

    The above diagram is known as a multi-layered perceptron, a network of many neurons. Some are input neurons and receive the inputs, some are part of what’s called a “hidden” layer (as they are connected to neither the inputs nor the outputs of the network directly), and then there are the output neurons, from which the results are read.

    Training these networks is much more complicated. With the simple perceptron, you could easily evaluate how to change the weights according to the error. But here there are so many different connections, each in a different layer of the network. How does one know how much each neuron or connection contributed to the overall error of the network?

    The solution to optimizing weights of a multi-layered network is known as backpropagation. The output of the network is generated in the same manner as a perceptron. The inputs multiplied by the weights are summed and fed forward through the network. The difference here is that they pass through additional layers of neurons before reaching the output. Training the network (i.e. adjusting the weights) also involves taking the error (desired result - guess). The error, however, must be fed backwards through the network. The final error ultimately adjusts the weights of all the connections.

    -

    Backpropagation is a bit beyond the scope of this book and involves a fancier activation function (called the sigmoid function) as well as some basic calculus. If you are interested in how backpropagation works, check the book website (and GitHub repository) for an example that solves XOR using a multi-layered feed forward network with backpropagation.

    -

    Instead, here I'll shift the focus to using neural networks in ml5.js.

    -

    Create a train a neural network with ml5.js

    -

    simple example with colors?

    -

    reference teachable machine, transfer learning and image classification?

    -

    Classification and Regression

    -

    Explain regression

    +

    Backpropagation is beyond the scope of this book and involves a fancier activation function (called the sigmoid function) as well as some basic calculus. If you are interested in continuing down this road and learning more about how backpropagation works, you can find my “toy neural network” project at github.com/CodingTrain with links to accompanying video tutorials. They go through all the steps of solving XOR using a multi-layered feed forward network with backpropagation. For this chapter, however, I’d like to get some help and phone a friend.

    +

    Machine Learning with ml5.js

    +

    That friend is ml5.js. Inspired by the philosophy of p5.js, ml5.js is a JavaScript library that aims to make machine learning accessible to a wide range of artists, creative coders, and students. It is built on top of TensorFlow.js, Google's open-source library that runs machine learning models directly in the browser without the need to install or configure complex environments. However, TensorFlow.js's low-level operations and highly technical API can be intimidating to beginners. That's where ml5.js comes in, providing a friendly entry point for those who are new to machine learning and neural networks.

    +

    Before I get to my goal of adding a "neural network" brain to a steering agent and tying ml5.js back into the story of the book, I would like to demonstrate step-by-step how to train a neural network model with "supervised learning." There are several key terms and concepts important to cover, namely “classification”, “regression”, “inputs”, and “outputs”. Examining these ideas within the context of supervised learning scenario is a great way to explore on these foundational concepts, introduce the syntax of the ml5.js library, and tie everything together.

    +

    Classification and Regression

    +

    The majority of machine learning tasks fall into one of two categories: classification and regression. Classification is probably the easier of the two to understand at the start. It involves predicting a “label” (or “category” or “class”) for a piece of data. For example, an “image classifier" might try to guess if a photo is of a cat or a dog and assign the corresponding label.

    +

    [FIGURE OF CAT OR DOG OR BIRD OR MONKEY OR ILLUSTRATIONS ASSIGNED A LABEL?]

    +

    This doesn’t happen by magic, however. The model must first be shown many examples of dog and cat illustrations with the correct labels in order to properly configure all the weights of all the connections. This is the supervised learning training process.

    +

    The simplest version of this scenario is probably the classic “Hello, World” demonstration of machine learning known as “MNIST”. MNIST, short for 'Modified National Institute of Standards and Technology,' is a dataset that was collected and processed by Yann LeCun and Corinna Cortes (AT&T Labs) and Christopher J.C. Burges (Microsoft Research). It is widely used for training and testing in the field of machine learning and consists of 70,000 handwritten digits from 0 to 9, each digit being a 28x28 pixel grayscale image.

    +

    [FIGURE FOR MNIST?]

    +

    While I won't be building a complete MNIST model for training and deployment, it serves as a canonical example of a training dataset for image classification: 70,000 images each assigned one of 10 possible labels. The key element of classification is that the output of the model involves a fixed number of discrete options. There are only 10 possible digits that the model can guess, no more and no less. After the data is used to train the model, the goal is to classify new images and assign the appropriate label.

    +

    Regression, on the other hand, is a machine learning task where the prediction is a continuous value, typically a floating point number. A regression problem can involve multiple outputs, but when beginning it’s often simpler to think of it as just one.

    +

    Consider a machine learning model that predicts the daily electricity usage of a house based on any number of factors like number of occupants, size of house, temperature outside. Here, rather than a goal of the neural network picking from a discrete set of options, it makes more sense for the neural network to guess a number. Will the house use 30.5 kilowatt-hours of energy that day? 48.7 kWh? 100.2 kWh? The output is therefore a continuous value that the model attempts to predict.

    +

    [FIGURE ILLUSTRATING REGRESSION?]

    +

    Inputs and Outputs

    +

    Once the task has been determined, the next step is to finalize the configuration of inputs and outputs of the neural network. In the case of MNIST, each image is a collection of 28x28 grayscale pixels and each pixel can be represented as a single value (ranging from 0-255). The total pixels is 28 \times 28 = 784. The grayscale value of each pixel is an input to the neural network.

    +
    + Place holder figure (just show the inputs first?, borrowed from https://ml4a.github.io/ml4a/looking_inside_neural_nets/ +
    Place holder figure (just show the inputs first?, borrowed from https://ml4a.github.io/ml4a/looking_inside_neural_nets/
    +
    +

    Since there are 10 possible digits 0-9, the output of the neural network is a prediction of one of 10 labels.

    +

    [FIGURE NOW ADDS THE OUTPUTS IN]

    +

    Let’s consider the regression scenario of predicting the electricity usage of a house. Let’s assume you have a table with the following data:

    +
    GenotypePhenotypeGenotype 100pxPhenotype 140px
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    OccupantsSize (m²)Temperature Outside (°C)Electricity Usage (kWh)
    41502425.3
    210025.516.2
    17026.512.1
    41202322.1
    29021.515.2
    51802024.4
    16018.511.7
    +

    Here in this table, the inputs to the neural network are the first three columns (occupants, size, temperature). The fourth column on the right is what the neural network is expected to guess, or the output.

    +

    [FIGURE SHOWING 3 inputs + 1 output]

    +

    Setting up the Neural Network with ml5.js

    +

    In a typical machine learning scenario, the next step after establishing the inputs and outputs is to configure the full architecture of the neural network. This involves specifying the number of hidden layers between the inputs and outputs, the number of neurons in each layer, which activation functions to use, and more! While all of this is technically possible in ml5.js, using a high-level library has the advantage of making its best guesses based on the task, inputs, and outputs to configure the network and so I can get started writing the code itself!

    +

    Just as demonstrated with Matter.js and toxiclibs.js in chapter 6, the ml5.js library can be imported into index.html.

    +
    <script src="https://unpkg.com/ml5@latest/dist/ml5.min.js"></script>
    +

    The ml5.js library is a collection of machine learning models and functions that can be accessed with the syntax ml5.functionName(). If you wanted to use a pre-trained model that detects hands, you might say ml5.handpose() or for classifying images ml5.imageClassifier(). I encourage to explore all of what ml5.js has to offer (and I will reference some of these pre-trained models in upcoming exercise ideas), however, for this chapter, I’ll be focusing on one function only in ml5.js, the function for creating a generic “neural network”: ml5.neuralNetwork().

    +

    Creating the neural network involves first making a JavaScript object with the necessary configuration properties of the network. There are many options you can use to list, but almost all of them are optional as the network will use many defaults. The default task in ml5.js is “regression” so if you wanted to create a neural network for classification you would have to write the code as follows:

    +
    let options = { task: "classification" }
    +let classifier = ml5.neuralNetwork(options);
    +

    This, however, gives ml5.js very little to go on in terms of designing the network architecture. Adding the inputs and outputs will complete the rest of the puzzle for it. In the case of MNIST, we established there were 784 inputs (grayscale pixel colors) and 10 possible output labels (digits “0” through “9”). This can be configured in ml5.js with a single integer for the number of inputs and an array of strings for the list of output labels.

    +
    let options = {
    +  inputs: 784,
    +  outputs: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"],
    +  task: "classification",
    +};
    +let digitClassifier = ml5.neuralNetwork(options);
    +

    The electricity regression scenario involved 3 input values (occupants, size, temperature) and 1 output value (usage in kWh).

    +
    let options = {
    +  inputs: 3,
    +  outputs: 1,
    +  task: "regression",
    +};
    +let energyPredictor = ml5.neuralNetwork(options);
    +

    something to help manage expectations

    +
      +
    • These examples (MNIST and the energy predictor) are simplified versions of real-world problems.
    • +
    • Real-world problems often require more complex architectures and more data preparation.
    • +
    +

    Gesture Classification

    +

    This section will go through everything explained but build a simple example of classifying the direction of a vector.

    What is NEAT “neuroevolution augmented topologies)

    flappy bird scenario (classification) vs. steering force (regression)?

    features?

    NeuroEvolution Steering

    -

    obstacle avoidance example

    +

    obstacle avoidance example?

    Other possibilities?

    diff --git a/content/examples/00_randomness/example_i_1_random_walk_traditional/sketch.js b/content/examples/00_randomness/example_i_1_random_walk_traditional/sketch.js index 92b25bc..08a14ae 100644 --- a/content/examples/00_randomness/example_i_1_random_walk_traditional/sketch.js +++ b/content/examples/00_randomness/example_i_1_random_walk_traditional/sketch.js @@ -28,16 +28,14 @@ class Walker { step() { const choice = floor(random(4)); - if (choice === 0) { + if (choice == 0) { this.x++; - } else if (choice === 1) { + } else if (choice == 1) { this.x--; - } else if (choice === 2) { + } else if (choice == 2) { this.y++; } else { this.y--; } - this.x = constrain(this.x, 0, width - 1); - this.y = constrain(this.y, 0, height - 1); } } diff --git a/content/examples/00_randomness/example_i_2_random_distribution/sketch.js b/content/examples/00_randomness/example_i_2_random_distribution/sketch.js index 8d7a9e0..629c497 100644 --- a/content/examples/00_randomness/example_i_2_random_distribution/sketch.js +++ b/content/examples/00_randomness/example_i_2_random_distribution/sketch.js @@ -4,8 +4,8 @@ // An array to keep track of how often random numbers are picked -const randomCounts = []; -const total = 20; +let randomCounts = []; +let total = 20; function setup() { createCanvas(640, 240); diff --git a/content/examples/00_randomness/example_i_6_perlin_noise_walker/sketch.js b/content/examples/00_randomness/example_i_6_perlin_noise_walker/sketch.js index 305764d..448634d 100644 --- a/content/examples/00_randomness/example_i_6_perlin_noise_walker/sketch.js +++ b/content/examples/00_randomness/example_i_6_perlin_noise_walker/sketch.js @@ -11,28 +11,30 @@ function setup() { } function draw() { - walker.walk(); - walker.display(); + walker.step(); + walker.show(); } class Walker { constructor() { - this.position = createVector(width / 2, height / 2); - // Perlin noise x and y offset - this.noff = createVector(random(1000), random(1000)); + this.tx = 0; + this.ty = 10000; } - display() { + step() { + //{!2} x- and y-position mapped from noise + this.x = map(noise(this.tx), 0, 1, 0, width); + this.y = map(noise(this.ty), 0, 1, 0, height); + + //{!2} Move forward through “time.” + this.tx += 0.01; + this.ty += 0.01; + } + + show() { strokeWeight(2); fill(127); stroke(0); - ellipse(this.position.x, this.position.y, 48, 48); - } - - walk() { - // Noise returns a value between 0 and 1 - this.position.x = map(noise(this.noff.x), 0, 1, 0, width); - this.position.y = map(noise(this.noff.y), 0, 1, 0, height); - this.noff.add(0.01, 0.01, 0); + circle(this.x, this.y, 48); } } diff --git a/content/examples/06_libraries/6_5_compound_bodies_error/lollipop.js b/content/examples/06_libraries/6_5_compound_bodies_error/lollipop.js index edef3e4..756c87e 100644 --- a/content/examples/06_libraries/6_5_compound_bodies_error/lollipop.js +++ b/content/examples/06_libraries/6_5_compound_bodies_error/lollipop.js @@ -6,17 +6,18 @@ class Lollipop { constructor(x, y) { - this.w = 4; - this.h = 24; + this.w = 24; + this.h = 4; this.r = 8; - let options = { restitution: 1 }; - this.part1 = Bodies.rectangle(x, y, this.w, this.h, options); - this.part2 = Bodies.circle(x, y - this.h / 2, this.r, options); + this.part1 = Bodies.rectangle(x, y, this.w, this.h); + this.part2 = Bodies.circle(x + this.w / 2, y, this.r); this.body = Body.create({ + restitution: 0.5, parts: [this.part1, this.part2], }); + Body.setVelocity(this.body, Vector.create(random(-5, 5), 0)); Body.setAngularVelocity(this.body, 0.1); Composite.add(engine.world, this.body); @@ -24,19 +25,48 @@ class Lollipop { // Drawing the lollipop show() { - let position = this.body.position; - let angle = this.body.angle; - rectMode(CENTER); - fill(127); - stroke(0); - strokeWeight(1); - push(); - translate(position.x, position.y); - rotate(angle); - rect(0, 0, this.w, this.h); - fill(200); - circle(0, this.h/2, this.r * 2); - pop(); + if (mouseIsPressed) { + // The angle comes from the compound body + let angle = this.body.angle; + + //{!2} Get the position for each part + let position1 = this.part1.position; + let position2 = this.part2.position; + + fill(127); + stroke(0); + strokeWeight(1); + + // Translate and rotate the rectangle (part1) + push(); + translate(position1.x, position1.y); + rotate(angle); + rectMode(CENTER); + rect(0, 0, this.w, this.h); + pop(); + + // Translate and rotate the circle (part2) + push(); + translate(position2.x, position2.y); + rotate(angle); + fill(200); + circle(0, 0, this.r * 2); + pop(); + } else { + let position = this.body.position; + let angle = this.body.angle; + rectMode(CENTER); + fill(127); + stroke(0); + strokeWeight(1); + push(); + translate(position.x, position.y); + rotate(angle); + rect(0, 0, this.w, this.h); + fill(200); + circle(this.w / 2, 0, this.r * 2); + pop(); + } } checkEdge() { diff --git a/content/images/10_nn/10_nn_13.jpg b/content/images/10_nn/10_nn_13.jpg new file mode 100644 index 0000000..4491b81 Binary files /dev/null and b/content/images/10_nn/10_nn_13.jpg differ