Notion - Update docs

This commit is contained in:
shiffman 2023-06-26 19:41:29 +00:00 committed by GitHub
parent d57b29785f
commit de81bbaa68
38 changed files with 1375 additions and 682 deletions

View file

@ -52,7 +52,7 @@ function draw() {
}
}</pre>
<p>This is about as simple as a particle can get. From here, I could take the particle in several directions. I could add the <code>applyForce()</code> method to affect the particles behavior (Ill do precisely this in a future example). I could also add variables to describe color and shape, or load a <code>p5.Image</code> to draw the particle in a more interesting way. For now, however, Ill focus on adding just one additional detail: <strong><em>lifespan</em></strong>.</p>
<p>Some particle systems involve something called an <strong><em>emitter</em></strong> that serves as the source of the particles. The emitter controls the initial settings for the particles: position, velocity, and more. It might emit a single burst of particles or a continuous stream of particles, or some variation thereof. The new feature here is that particles born at the emitter cant live forever. If they did, the p5.js sketch would eventually grind to a halt as the amount of particles increases to an unwieldy number over time. As new particles are born, old particles need to be removed, creating the illusion of an infinite stream of particles without hurting the performance of the sketch.</p>
<p>Some particle systems involve something called an <strong><em>emitter</em></strong> that serves as the source of the particles. The emitter controls the initial settings for the particles: position, velocity, and more. It might emit a single burst of particles, a continuous stream of particles, or some variation thereof. The new feature here is that particles born at the emitter cant live forever. If they did, the p5.js sketch would eventually grind to a halt as the particles add up to an unwieldy number over time. As new particles are born, old particles need to be removed, creating the illusion of an infinite stream of particles without hurting the performance of the sketch.</p>
<p>There are many different ways to decide when a particle is ready to be removed. For example, it could “die” when it comes into contact with another object, or when it leaves the frame of the canvas. For now, Ill choose to give particles a <code>lifespan</code> variable that acts like a timer. It will start at 255 and count down to 0 as the sketch progresses, at which point the particle will be considered dead. Heres the added code in the <code>Particle</code> class:</p>
<pre class="codesplit" data-code-language="javascript">class Particle {
@ -157,7 +157,7 @@ class Particle {
return (this.lifespan &#x3C; 0.0);
}
}</pre>
<p>In this example, there is only one particle at a time for the sake of simplicity and testing. Each time the particle reaches the end of its lifespan, the <code>particle</code> variable is overwritten with a new instance of the <code>Particle</code> class. This effectively replaces the previous particle object. It's important to understand that the previous particle object isn't so much "deleted" as it is no longer accessible or used within the code. The sketch essentially forgets the old particle and starts anew with the freshly created one.</p>
<p>In this example, theres only one particle at a time for the sake of simplicity and testing. Each time the particle reaches the end of its lifespan, the <code>particle</code> variable is overwritten with a new instance of the <code>Particle</code> class. This effectively replaces the previous particle object. It's important to understand that the previous <code>Particle</code> object isn't so much “deleted” as it is no longer accessible or used within the code. The sketch essentially forgets the old particle and starts anew with the freshly created one.</p>
<div data-type="exercise">
<h3 id="exercise-41">Exercise 4.1</h3>
<p>Create a <code>run()</code> method in the <code>Particle</code> class that handles <code>update()</code>, <code>show()</code>, and <code>applyForce()</code>. What are the pros and cons of this approach?</p>
@ -167,8 +167,8 @@ class Particle {
<p>Add angular velocity (rotation) to the particle, and create your own non-circle particle design so the rotation is visible.</p>
</div>
<h2 id="an-array-of-particles">An Array of Particles</h2>
<p>Now that I have a class to describe a single particle, its time for the next big step: how can I keep track of many particles, without knowing in advance exactly how many I might have at any given time? The answer is the JavaScript array, a data structure that stores an arbitrarily long list of values. In JavaScript, an array is actually an object created from the <code>Array</code> class, and so it comes with many built-in methods. These methods supply all the functionality I need for maintaining a list of <code>Particle</code> objects, including adding particles, removing particles, or otherwise manipulating them. For a refresher on arrays, see the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array">JavaScript Array Documentation</a> on the MDN Web Docs.</p>
<p>As I bring arrays into the picture, Ill use a solution to Exercise 4.1 and assume a <code>Particle.run()</code> method that manages all of an individual particle's functionality. While there are also some cons to this approach, it will keep the subsequent code examples more concise. To begin, Ill use a <code>for</code> loop in <code>setup()</code> to populate an array with particles, then use another <code>for</code> loop in <code>draw()</code> to run each particle.</p>
<p>Now that I have a class to describe a single particle, its time for the next big step: how can I keep track of many particles, without knowing in advance exactly how many I might have at any given time? The answer is the JavaScript array, a data structure that stores an arbitrarily long list of values. In JavaScript, an array is actually an object created from the <code>Array</code> class, and so it comes with many built-in methods. These methods supply all the functionality I need for maintaining a list of <code>Particle</code> objects, including adding particles, removing particles, or otherwise manipulating them. For a refresher on arrays, see the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array">JavaScript array documentation</a> on the MDN Web Docs.</p>
<p>As I bring arrays into the picture, Ill use a solution to Exercise 4.1 and assume a <code>Particle.run()</code> method that manages all of an individual particles functionality. While there are also some cons to this approach, it will keep the subsequent code examples more concise. To begin, Ill use a <code>for</code> loop in <code>setup()</code> to populate an array with particles, then use another <code>for</code> loop in <code>draw()</code> to run each particle.</p>
<pre class="codesplit" data-code-language="javascript">let total = 10;
//{!1} Start with an empty array.
let particles = [];
@ -186,7 +186,7 @@ function draw() {
particle.run();
}
}</pre>
<p>The <code>for</code> loop in <code>draw()</code> demonstrates how to call a method on every element of an array by accessing each index. I initialize a variable <code>i</code> to <code>0</code> and increment it by <code>1</code>, accessing each element of the array until <code>i</code> hits <code>particles.length</code> and so reaches the end. As it happens, there are actually a few different ways to do this, and thats something that I both love and hate about coding in JavaScript—there are so many different styles and options to consider. On the one hand, this makes JavaScript a highly flexible and adaptable language, but on the other hand, the abundance of choice can be overwhelming and lead to a lot of confusion when learning.</p>
<p>The <code>for</code> loop in <code>draw()</code> demonstrates how to call a method on every element of an array by accessing each index. I initialize a variable <code>i</code> to <code>0</code> and increment it by <code>1</code>, accessing each element of the array until <code>i</code> hits <code>particles.length</code> and so reaches the end. As it happens, there are actually a few other ways to do the same thing. This is something that I both love and hate about coding in JavaScript—there are so many different styles and options to consider. On the one hand, this makes JavaScript a highly flexible and adaptable language, but on the other hand, the abundance of choices can be overwhelming and lead to a lot of confusion when learning.</p>
<p>Lets take a ride on the loop-de-loop rollercoaster of choices for iterating over an array:</p>
<ul>
<li>The traditional <code>for</code> loop, as just demonstrated. This is probably what youre most used to, and it follows a similar syntax as other programming languages like Java and C.</li>
@ -200,7 +200,7 @@ function draw() {
particle.run();
}
}</pre>
<p>To translate this code, say “each” instead of “let” and “in” instead of “of.” Putting it together, you get, “For each particle in particles, update and display that particle.”</p>
<p>To translate this code, say “each” instead of “let,” and “in” instead of “of.” Putting it together, you get, “For each particle in particles, update and display that particle.”</p>
<p>Simple, elegant, concise, lovely. But before you get too excited about <code>for...of</code> loops, take a moment and breathe, because I have some bad news: they wont work in every situation. Yes, I love <code>for...of</code> loops, and Ill get to use them in some of the examples to come where I need to iterate over the items in an array, but not just yet. Ultimately, I want to create a continuous stream of particles, with one new particle added to the array each cycle through <code>draw()</code> and old particles removed from the array as they die. As youll soon see, this is where the <code>for...of</code> loop lets me down.</p>
<p>Creating a new particle every frame is easy: I can just call the <code>Array</code> classs <code>push()</code> method during <code>draw()</code> to add a new <code>Particle</code> object to the end of the array. This eliminates the need to create any particles during <code>setup()</code>.</p>
<pre class="codesplit" data-code-language="javascript">let particles = [];
@ -235,13 +235,13 @@ function draw() {
//{!1 .offset-top} Adding a new Particle to the list while iterating?
particles.push(new Particle(width / 2, 50));
}</pre>
<p>This is a somewhat extreme example (with flawed logic), but it proves the point. For each particle in the list, this code adds a new particle to the list, and so the <code>length</code> of the array increases. This will result in an infinite loop, as I can never increment past the size of the array!</p>
<p>This is a somewhat extreme scenario (with flawed logic), but it proves the point. For each particle in the list, this code adds a new particle to the list, and so the <code>length</code> of the array increases. This will result in an infinite loop, as I can never increment past the size of the array!</p>
<p>While removing elements from the array during a loop doesnt cause the sketch to crash (as it would with adding), the problem is perhaps more insidious in that it leaves no evidence. To discover the flaw, I must first establish an important fact: when an element is removed from an array with <code>splice()</code>, all subsequent elements are shifted to the left. Figure 4.1 shows what happens when particle C (index 2) is removed. Particles A and B keep the same index, while particles D and E shift from 3 and 4 to 2 and 3, respectively.</p>
<figure>
<img src="images/04_particles/04_particles_1.png" alt="Figure 4.1: When an element is removed from an array, the subsequent elements shift to the left to fill the empty spot.">
<figcaption>Figure 4.1: When an element is removed from an array, the subsequent elements shift to the left to fill the empty spot.</figcaption>
</figure>
<p>Consider what happens as counter <code>i</code> iterates over the elements of the array:</p>
<p>Consider what happens as counter <code>i</code> iterates over the elements of this array.</p>
<table>
<thead>
<tr>
@ -264,7 +264,7 @@ function draw() {
<tr>
<td>2</td>
<td>particle C</td>
<td>Delete! Slide particles D and E back from slots 3 and 4 to 2 and 3.</td>
<td>Delete! Slide particles D and E over from slots 3 and 4 to 2 and 3.</td>
</tr>
<tr>
<td>3</td>
@ -274,7 +274,7 @@ function draw() {
</tbody>
</table>
<p>Notice the problem? Particle D is never checked! When C is deleted from slot 2, D moves into slot 2 in its place, but <code>i</code> has already moved on to slot 3. In practice, this may not be a total disaster, since particle D will get checked the next time around through <code>draw()</code>. Still, the expectation is that the code should iterate through every single element of the array. Skipping an element is unacceptable!</p>
<p>There are two solutions to this problem. The first solution is to iterate through the array backwards. Since elements slide from right to left as other elements are removed, its impossible to skip an element this way. Heres how the code looks:</p>
<p>There are two solutions to this problem. The first is to iterate through the array backwards. Since elements slide from right to left as other elements are removed, its impossible to skip an element this way. Heres how the code looks:</p>
<pre class="codesplit" data-code-language="javascript"> //{!1 .bold} Looping through the list backwards
for (let i = particles.length - 1; i >= 0; i--) {
let particle = particles[i];
@ -318,7 +318,7 @@ function draw() {
}
}
}</pre>
<p>You might be wondering why, instead of checking each particle individually, I dont just remove the oldest particle after some period of time (determined by checking the frameCount or array length). In this example, that approach would actually work! I could even use a different array method called <code>shift()</code> which automatically removes the first element of an array. However, in many particle systems, other conditions or interactions may cause particles to “die” (reach the end of their lifespan) before others, even if they were created later. Checking <code>isDead()</code> in combination with<code>splice()</code> is a nice comprehensive solution that offers flexibility in managing particles across a variety of scenarios.</p>
<p>You might be wondering why, instead of checking each particle individually, I dont just remove the oldest particle after some period of time (determined by checking the <code>frameCount</code> or array length). In this example, where the particles die in the same order in which theyre born, that approach would actually work. I could even use a different array method called <code>shift()</code>, which automatically removes the first element of an array. However, in many particle systems, other conditions or interactions may cause “younger” particles to die sooner than “older” particles. Checking <code>isDead()</code> in combination with<code>splice()</code> is a nice, comprehensive solution that offers flexibility in managing particles across a variety of scenarios.</p>
<h2 id="a-particle-emitter">A Particle Emitter</h2>
<p>Ive conquered the array and used it to manage a list of <code>Particle</code> objects, with the ability to add and delete particles at will. I could stop here and rest on my laurels, but theres an additional step that I can and should take: writing a class describing the list of <code>Particle</code> objects itself. Ill call this class, which represents the overall particle system, <code>Emitter</code>, after its functionality of “emitting” particles. The <code>Emitter</code> class will allow me to clean up the <code>draw()</code> function, removing the bulky logic of looping through all the particles. As an added bonus, it will also open up the possibility of having multiple particle emitters.</p>
<p>Recall that one of the goals I set at the beginning of this chapter was to write <code>setup()</code> and <code>draw()</code> without referencing any individual particles. In laying out that goal, I teased the possibility of a beautifully simple main sketch file. Here it is again, only now with the <code>Emitter</code> naming convention:</p>
@ -400,7 +400,7 @@ this.particles.splice(i, 1);
this.particles.add(new Particle(origin.x, origin.y));
}
}</pre>
<p>The example emitter is a static source of particles, which is a nice way to begin working with particle systems. However, it doesn't have to be that way! The emitter itself could have its own behaviors and experience physics, oscillate, react to user interaction, or any other kind of motion demonstrated in previous chapters. The particles could then emanate from various positions over time, creating trails and even more complex and intriguing patterns.</p>
<p>The example emitter is a static source of particles, which is a nice way to begin working with particle systems. However, thats not how it has to be. The emitter itself could have its own behaviors: experiencing physics, oscillating, reacting to user input, or exhibiting any other kind of motion demonstrated in previous chapters. The particles could then emanate from various positions over time, creating trails or other, more complex and intriguing patterns.</p>
<div data-type="exercise">
<h3 id="exercise-43">Exercise 4.3</h3>
<p>What if the emitter moves? Can you emit particles from the mouse position, or use the concepts of velocity and acceleration to move the system autonomously?</p>
@ -445,7 +445,7 @@ function draw() {
<h3 id="example-44-a-system-of-systems">Example 4.4: A System of Systems</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/s_Y3-Mmo7" data-example-path="examples/04_particles/4_4_multiple_emitters"></div>
<figcaption>The interactive version includes mouse interaction to create new emitters.</figcaption>
<figcaption>Clicking the mouse adds a new emitter.</figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">//{!1} This time, the type of thing we're putting in the array is a particle emitter itself!
@ -461,13 +461,13 @@ function setup() {
<p>Then, in <code>draw()</code>, instead of referencing a single <code>Emitter</code> object, I now iterate over all the emitters and call <code>run()</code> on each of them.</p>
<pre class="codesplit" data-code-language="javascript">function draw() {
background(255);
//{!4} No emitters are removed a for...of loop can work heree!
//{!4} No emitters are removed, so a for...of loop can work here!
for (let emitter of emitters) {
emitter.run();
emitter.addParticle();
}
}</pre>
<p>Notice how I am back to using a <code>for...of</code> loop since no elements are being removed from the <code>emitters</code> array.</p>
<p>Notice how Im back to using a <code>for...of</code> loop since no elements are being removed from the <code>emitters</code> array.</p>
<div data-type="exercise">
<h3 id="exercise-45">Exercise 4.5</h3>
<p>Rewrite Example 4.4 so each particle system doesnt live forever. Set a limit on how many particles an individual system can generate. Then, when a particle system is empty (has no particles left), remove it from the <code>emitters</code> array.</p>
@ -511,7 +511,14 @@ class WackyConfetti {
}</pre>
<p>Let me pause for a moment. Youve done nothing wrong. All you wanted to do was wish your friend a happy birthday and enjoy writing some code. But while the reasoning behind this approach is quite sound, theres a problem: arent you going to be copy-pasting a lot of code between the different confetti classes?</p>
<p>Yes, you probably will be. Even though the kinds of particles are different enough to merit breaking them out into separate classes, theres still a ton of code that theyll likely share. For example, theyll all have vectors to keep track of position, velocity, and acceleration; an <code>update()</code> function that implements the motion algorithm; and more.</p>
<p>This is where <strong><em>inheritance</em></strong> comes in. Inheritance allows you to write a class that takes on (”inherits”) variables and methods from another class, while also implementing its own custom features. Ill illustrate this concept in more detail, and then create a particle system example that implements inheritance.</p>
<p>This is where <strong><em>inheritance</em></strong> comes in. Inheritance allows you to write a class that takes on (”inherits”) variables and methods from another class, while also implementing its own custom features. You might also be wondering whether adding all those different types of confetti to a single <code>particles</code> array actually works? After all, I dont typically mix different kinds of objects in one array as it could get quite confusing. How will the code know which particle is which kind of confetti? Wouldnt separate arrays be easier to manage?</p>
<pre class="codesplit" data-code-language="javascript"> constructor() {
this.happyParticles = [];
this.funParticles = [];
this.wackyParticles = [];
}</pre>
<p>However, this is now terribly inconvenient, as a single array is far more practical for managing all the particles in the system. The ability to mix objects of different types in one array is an inherent feature of JavaScript itself and the concept of <strong><em>polymorphism </em></strong>allows for them to be operated on as if they are the same type. I can populate an array with different kinds of particles, and each particle will still maintain its unique behaviors and characteristics as defined in its respective class.</p>
<p>In this section, I'll illustrate these concepts in more detail, and then create a particle system that implements inheritance and polymorphism.</p>
<h3 id="inheritance-basics">Inheritance Basics</h3>
<p>To demonstrate how inheritance works, Ill take a different example from the world of animals: dogs, cats, monkeys, pandas, wombats, sea nettles, you name it. Ill start by coding a <code>Dog</code> class. A <code>Dog</code> object will have an <code>age</code> variable (an integer), as well as <code>eat()</code>, <code>sleep()</code>, and <code>bark()</code> methods.</p>
<pre class="codesplit" data-code-language="javascript">class Dog {
@ -653,8 +660,64 @@ class Cat extends Animal {
}
}</pre>
<p>Similar to calling <code>super()</code> in the constructor, calling <code>super.eat()</code> inside the <code>Dog</code> classs <code>eat()</code> method results in calling the <code>Animal</code> classs <code>eat()</code> method. Then the subclasss method definition can continue with any additional, custom code.</p>
<h3 id="particles-with-inheritance">Particles with Inheritance</h3>
<p>Now that Ive covered the theory of inheritance and its syntax, Im ready to write a working example of inheritance in p5.js based on the <code>Particle</code> class. First, take another look at a basic <code>Particle</code> implementation, adapted from <a href="#example-41-a-single-particle">Example 4.1</a>:</p>
<h3 id="polymorphism-basics">Polymorphism Basics</h3>
<p>Now that you are acquainted with inheritance is , try to imagine how you would code a diverse animal kingdom with dogs, cats, turtles, and kiwis all frolicking about.</p>
<pre class="codesplit" data-code-language="javascript">//{!4} Separate arrays for each animal
let dogs = [];
let cats = [];
let turtles = [];
let kiwis = [];
for (let i = 0; i &#x3C; 10; i++) {
dogs.push(new Dog());
}
for (let i = 0; i &#x3C; 15; i++) {
cats.push(new Cat());
}
for (let i = 0; i &#x3C; 6; i++) {
turtles.push(new Turtle());
}
for (let i = 0; i &#x3C; 98; i++) {
kiwis.push(new Kiwi());
}</pre>
<p>As the day begins, the animals are all pretty hungry and are looking to eat. So its off to looping time (enhanced looping time!)…</p>
<pre class="codesplit" data-code-language="javascript">// Separate loops for each animal
for (let dog of dogs) {
dog.eat();
}
for (let cat of cats) {
cat.eat();
}
for (let turtle of turtles) {
turtle.eat();
}
for (kiwi of kiwis) {
kiw.eat();
}</pre>
<p>This works well, but as the world expands to include many more animal species, Im going to be stuck writing a lot of individual loops. Is this really necessary? After all, the creatures are all animals, and they all like to eat. Why not just have one array objects and fill it with all different <em>kinds</em> of animals?</p>
<pre class="codesplit" data-code-language="javascript">// Just one array for all the animals!
let kingdom = [];
for (let i = 0; i &#x3C; 10; i++) {
kingdom.push(new Dog());
}
for (let i = 0; i &#x3C; 15; i++) {
kingdom.push(new Cat());
}
for (let i = 0; i &#x3C; 6; i++) {
kingdom.push(new Turtle());
}
for (let i = 0; i &#x3C; 98; i++) {
kingdom.push(new Kiwi());
}
// One loop for all the animals!
for (let animal of kingdom) {
animal.eat();
}</pre>
<p>This is <strong>polymorphism</strong> (from the Greek <em>polymorphos </em>meaning “many forms”) in action. Although all the animals are grouped together in an array and processed in a single <code>for</code> loop, JavaScript can identify their true types and invoke the appropriate <code>eat()</code> method. It's that simple!</p>
<h3 id="particles-with-inheritance-and-polymorphism">Particles with Inheritance and Polymorphism</h3>
<p>Now that Ive covered the theory of these new concepts and their syntax, Im ready to write a working example of inheritance and polymorphism in p5.js based on the <code>Particle</code> class. First, take another look at a basic <code>Particle</code> implementation, adapted from <a href="#example-41-a-single-particle">Example 4.1</a>:</p>
<pre class="codesplit" data-code-language="javascript">class Particle {
constructor(x, y) {
this.acceleration = createVector(0, 0);
@ -729,7 +792,7 @@ class Cat extends Animal {
</div>
<p>Now that I have a <code>Confetti</code> subclass that extends the base <code>Particle</code> class, the next step is to also add <code>Confetti</code> objects to the array of particles defined in the <code>Emitter</code> class.</p>
<div data-type="example">
<h3 id="example-45-a-particle-system-with-inheritance">Example 4.5: A Particle System with Inheritance</h3>
<h3 id="example-45-a-particle-system-with-inheritance-and-polymorphism">Example 4.5: A Particle System with Inheritance and Polymorphism</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/2ZlNJp2EW" data-example-path="examples/04_particles/noc_4_05_particle_system_inheritance_polymorphism"></div>
<figcaption></figcaption>
@ -762,7 +825,7 @@ class Cat extends Animal {
}
}
}</pre>
<p>Can you spot how this example is also taking advantage of polymorphism? It's right there is how both <code>Particle</code> and <code>Confetti</code> objects co-mingle in the same <code>particles</code> array within the <code>Emitter</code> class. Thanks to the inheritance relationship, they can both be both considered the same type: <code>Particle</code>. Inheritance, along with polymorphism, enables a variety of particle types to be managed together in the one array regardless of their original class.</p>
<p>Can you spot how this example is also taking advantage of polymorphism? Its what allows both <code>Particle</code> and <code>Confetti</code> objects to commingle in the same <code>particles</code> array within the <code>Emitter</code> class. Thanks to the inheritance relationship, they can both be considered to be of the same type, <code>Particle</code>, and its safe to iterate through the array and call methods like <code>run()</code> and <code>isDead()</code> on each object. Inheritance, along with polymorphism, enables a variety of particle types to be managed together in the one array, regardless of their original class.</p>
<div data-type="exercise">
<h3 id="exercise-48">Exercise 4.8</h3>
<p>Create a particle system with more than two “kinds” of particles. Try varying the behavior of the particles in addition to the design.</p>
@ -875,7 +938,7 @@ class Emitter {
}
}
}</pre>
<p>While this example only demonstrates a hard-coded gravity force, it's worth considering how other forces from previous chapters, such as wind or drag, could come into play. You could also experiment with varying how and when forces are applied. Instead of a force acting on particles continuously every frame, what if a force only kicked in under certain conditions or at specific moments? There's a lot of room here for creativity and interactivity in how you design your particle systems!</p>
<p>While this example only demonstrates a hard-coded gravity force, it's worth considering how other forces from previous chapters, such as wind or drag, could come into play. You could also experiment with varying how and when forces are applied. Instead of a force acting on particles continuously every frame, what if a force only kicked in under certain conditions or at specific moments? Theres a lot of room here for creativity and interactivity in how you design your particle systems!</p>
<h2 id="particle-systems-with-repellers">Particle Systems with Repellers</h2>
<p>What if I wanted to take my code one step further and add a <code>Repeller</code> object—the inverse of the <code>Attractor</code> object covered in <a href="/force#gravitational-attraction">Chapter 2</a>—that pushes any particles away that get too close? This requires a bit more sophistication than uniformly applying the gravity force, because the force the repeller exerts on a particular particle is unique and must be calculated separately for each particle (see Figure 4.3).</p>
<figure>
@ -1068,7 +1131,7 @@ class Repeller {
return force;
}
}</pre>
<p>You may have noticed the addition of the <code>power</code> variable in the <code>Repeller</code> class, which controls the strength of the repulsion force exerted. This property becomes especially interesting when you have multiple attractors and repellers, each with different power values. For example, strong attractors and weak repellers might result in particles clustering around the attractors, while more powerful repellers might reveal patterns reminiscent of paths or channels between them. These are hints of what is to come in Chapter 5 when Ill further explore the concept of a "complex system."</p>
<p>Notice the addition of the <code>power</code> variable in the <code>Repeller</code> class, which controls the strength of the repulsion force exerted. This property becomes especially interesting when you have multiple attractors and repellers, each with different power values. For example, strong attractors and weak repellers might result in particles clustering around the attractors, while more powerful repellers might reveal patterns reminiscent of paths or channels between them. These are hints of whats to come in Chapter 5, where Ill further explore the concept of a “complex system.”</p>
<div data-type="exercise">
<h3 id="exercise-49">Exercise 4.9</h3>
<p>Expand Example 4.7 to include multiple repellers and attractors!</p>
@ -1095,7 +1158,7 @@ class Repeller {
<img src="images/04_particles/04_particles_7.png" alt="Figure 4.5 Two image textures: an all-white circle, or a fuzzy circle that fades out towards the edges">
<figcaption>Figure 4.5 Two image textures: an all-white circle, or a fuzzy circle that fades out towards the edges</figcaption>
</figure>
<p>Using an image to texture your particles can give you a lot of bang for very little buck in terms of the realism of the visual effect. Before you write any code, however, you have to make your image texture. I recommend using the PNG format, as p5.js will retain the alpha channel (transparency) when drawing the image, which is needed for blending the texture as particles are layered on top of each other. There are numerous ways to create these textures: you can indeed make them programmatically within p5.js (I'll include an example with the book's supplementary materials), but you can also other open-source or commercial graphic editing tools.</p>
<p>Using an image to texture your particles can give you a lot of bang for very little buck in terms of the realism of the visual effect. Before you write any code, however, you have to make your image texture. I recommend using the PNG format, as p5.js will retain the alpha channel (transparency) when drawing the image, which is needed for blending the texture as particles are layered on top of each other. There are numerous ways to create these textures: you can indeed make them programmatically within p5.js (Ill include an example with the books supplementary materials), but you can also use other open-source or commercial graphics editing tools.</p>
<p>Once youve made a PNG and deposited it in your sketchs <em>data</em> folder, you only need a few extra lines of code.</p>
<div data-type="example">
<h3 id="example-48-an-image-texture-particle-system">Example 4.8: An Image Texture Particle System</h3>
@ -1136,7 +1199,7 @@ class Repeller {
emitter.addParticle();
}
}</pre>
<p>In addition to designing the texture itself, you should also consider its resolution. Rendering a large number of high-resolution textures can significantly impact performance, especially if the code is required to resize them dynamically. The ideal scenario for optimal speed is to size the texture precisely to the resolution you intend to draw the particles in the canvas. If you do want particles of varying sizes, then ideally you should create the texture at the maximum size of any particle.</p>
<p>In addition to designing the texture itself, you should also consider its resolution. Rendering a large number of high-resolution textures can significantly impact performance, especially if the code has to resize them dynamically. The ideal scenario for optimal speed is to size the texture precisely to the resolution you intend to draw the particles in the canvas. If you want particles of varying sizes, then ideally you should create the texture at the maximum size of any particle.</p>
<div data-type="exercise">
<h3 id="exercise-411">Exercise 4.11</h3>
<p>Try creating other textures for different types of effects. Can you make your particle system look like fire instead of smoke?</p>

View file

@ -13,7 +13,8 @@
<p>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, were 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?</p>
<p>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, youd 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 wont 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, its 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. Its 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.</p>
<p>This chapter is dedicated to examining two open-source physics libraries for JavaScript—<a href="https://brm.io/matter-js/">matter.js</a> 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.</p>
<h2 id="61-what-is-matterjs">6.1 What is Matter.js?</h2>
<p>There are a multitude of other physics libraries worth exploring alongside these two case studies. Two that I would highly recommend are <a href="https://p5play.org/">p5.play</a> (created by Paolo Pedercini and developed by Quinton Ashley) and <a href="http://wellcaffeinated.net/PhysicsJS/">PhysicsJS</a> (by Jasper Palfree). p5.play was specifically designed for game development and simplifies the creation of visual objects—known as “sprites”—and manages their interactions, or “collisions”. As noted in the name, its tailored to work seamlessly with p5.js. PhysicsJS also has a comprehensive set of features including collision detection and resolution, gravity, and friction among others. Each of these libraries 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 books website for ports of the examples in this chapter to other libraries.</p>
<h2 id="what-is-matterjs">What is Matter.js?</h2>
<p>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 Developers Conference in 2006. Since then it has evolved into a rich and elaborate open-source physics engine. Its been used for countless projects, most notably highly successful games such as the award-winning Crayon Physics and the runaway hit Angry Birds.</p>
<p>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 Box2Ds 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 worlds 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.</p>
<p>While this makes for an incredibly accurate and robust library (its 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.</p>
@ -31,7 +32,7 @@
<p>OK. Now that you know how to determine if two circles are colliding, how about calculating their velocities after the collision? This is where Im going to stop the discussion. Why, you ask? Its not that understanding the math behind collisions isnt important or valuable. (In fact, Im 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 cant expect to master every detail of physics simulation. And while you might enjoy this discussion for circles, its 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.</p>
<p>Working with collisions in a p5.js sketch while still having time to spend with friends and family—thats 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 theres no need to re-invent the proverbial wheel, at least for now.</p>
<p>In conclusion, if you find yourself describing an idea for a p5.js sketch and the word “collisions” comes up, then its likely time to learn a physics engine.</p>
<h2 id="62-working-with-matterjs-with-p5js">6.2 Working with Matter.js with p5.js</h2>
<h2 id="importing-matterjs-library">Importing Matter.js Library</h2>
<p>There are a variety of ways to incorporate a JavaScript library into a project. For this books demonstrations, as you already quite aware, Im 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 <code>index.html</code> file that is part of every p5.js sketch.</p>
<p>This can be accomplished by opening the file navigation on the left hand side of the editor and selecting <code>index.html</code>.</p>
<figure>
@ -45,7 +46,7 @@
<pre class="codesplit" data-code-language="html">&#x3C;script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js">&#x3C;/script>
&#x3C;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">&#x3C;/script></pre>
<p>At the time of this writing, the most recent version of matter.js is <code>0.18.0</code> and thats what youll see referenced in the above snippet. As matter.js updates and new versions are released, its often a good idea to upgrade, but by referencing a specific version that you know works with your sketch, you dont have to worry about new features of the library breaking your existing code.</p>
<h2 id="63-matterjs-overview">6.3 Matter.js Overview</h2>
<h2 id="matterjs-overview">Matter.js Overview</h2>
<p>Do not despair! I really am going to get to the code very soon, and in some ways Ill blow the previous work out of the water. But before Im ready to do that, its important to walk through the overall process of using matter.js (or any physics engine) in p5.js. Lets begin by writing a pseudo-code generalization of all of the examples in Chapters 1 through 5.</p>
<p><strong><em>SETUP:</em></strong></p>
<ol>
@ -181,7 +182,7 @@ v = Matter.Vector.normalise(v);</pre>
<p>As you can see, the concepts are the same, but the function names and the arguments are different. First, everything is “name-spaced” under <code>Matter.Vector</code>. This is common for JavaScript libraries, p5.js is the unusual one in this regard. In p5.js to draw a circle, you call <code>circle()</code> rather than <code>p5.circle()</code>. The <code>circle()</code> 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 <code>circle</code> 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 <code>Matter</code> prefix.</p>
<p>In addition, unlike p5s static and non-static versions of vector functions like <code>add()</code> and <code>mult()</code>, all vector functions in <code>Matter</code> are static. If you want to change a <code>Matter.Vector</code> while operating on it, you can add it as an optional argument: <code>Matter.Vector.add(a, b, a)</code>: adds <code>a</code> and <code>b</code> and places the result in <code>a.</code> You can also set an existing variable to the newly created vector object as in <code>v = Matter.Vector.mult(v, 2)</code> however this still version creates a new vector in memory.</p>
<p>Ill cover the basics of what you need to know for working with <code>Matter.Vector</code> in this chapter, but for more, <a href="https://brm.io/matter-js/docs/classes/Vector.html">full documentation can be found on the matter.js website</a>.</p>
<h2 id="64-matterjs-engine">6.4 Matter.js: Engine</h2>
<h2 id="matterjs-engine">Matter.js: Engine</h2>
<p>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 <code>Engine</code> object, the main controller of your physics world and simulation.</p>
<pre class="codesplit" data-code-language="javascript">// An "alias" for Matter.js Engine class
let Engine = Matter.Engine;
@ -209,7 +210,7 @@ let { Engine, Vector } = Matter;</pre>
engine.gravity.y = 0;</pre>
<p>Its worth noting that gravity doesnt 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 <span data-type="equation">(0,0)</span> vector.</p>
<p>Once the world is initialized, its time to actually put stuff in it—bodies!</p>
<h2 id="65-matterjs-bodies">6.5 Matter.js: Bodies</h2>
<h2 id="matterjs-bodies">Matter.js: Bodies</h2>
<p>A body is the primary element in the matter.js world. Its the equivalent to the <code>Mover</code> / <code>Particle</code> / <code>Vehicle</code> 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).</p>
<p>Matter.js bodies are created using “factory” methods found in <code>Matter.Bodies</code>. 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. <code>new Particle()</code>, youve seen factory methods before! For example, <code>createVector()</code> is a factory method for creating a <code>p5.Vector</code> 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.</p>
<p>All of the factory methods can be found <a href="https://brm.io/matter-js/docs/classes/Bodies.html">in the </a><a href="https://brm.io/matter-js/docs/classes/Bodies.html"><code>Bodies</code></a><a href="https://brm.io/matter-js/docs/classes/Bodies.html"> documentation page</a>. Lets start by looking at <code>rectangle()</code>. And remember, the code below only works because I am assuming an alias to <code>Matter.Bodies</code> with <code>Bodies</code>.</p>
@ -243,7 +244,7 @@ let options = {
}
let ball = Bodies.circle(x, y, radius, options);</pre>
</div>
<h2 id="66-matterjs-render">6.6 Matter.js: Render</h2>
<h2 id="matterjs-render">Matter.js: Render</h2>
<p>Once a body is added the world, matter.js will always know its there, check it for collisions, and move it appropriately according to other forces in the environment. Itll 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?</p>
<p>In the next section, Im 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.</p>
<p>Matter.js, however, does include a fairly simple and straightforward <code>Render</code> class which is incredibly useful for quickly seeing and debugging the world youve designed. It does allow ways for customization of the “debug drawing” style, but I find the defaults perfectly adequate for quickly double-checking that Ive configured a world correctly.</p>
@ -317,7 +318,7 @@ function setup() {
Matter.Runner.run(runner, engine);
}</pre>
<p>Notice how there is no <code>draw()</code> function and all of the variables are local to <code>setup()</code>! 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!</p>
<h2 id="67-matterjs-and-p5js">6.7 Matter.js and p5.js</h2>
<h2 id="matterjs-with-p5js">Matter.js with p5.js</h2>
<p>Now, as demonstrated with the <code>Render</code> and <code>Runner</code> 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 <code>engine.world.bodies</code>.) 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 youre accustomed to with p5.js, keeping track of which bodies are which and drawing them appropriately. Lets consider the following file structure:</p>
<figure>
<img src="images/06_libraries/06_libraries_3.png" alt="">
@ -446,7 +447,7 @@ function setup() {
</figure>
<p>Find the example sketch called “<a href="https://editor.p5js.org/natureofcode/sketches/D26YvXr_S">6.2 Boxes Exercise</a>.” 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!</p>
</div>
<h2 id="68-static-matterjs-bodies">6.8 Static Matter.js Bodies</h2>
<h2 id="static-matterjs-bodies">Static Matter.js Bodies</h2>
<p>In the example just created, the <code>Box</code> 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 <code>Box</code> objects?</p>
<p>Matter.js makes this easy with the <code>isStatic</code> property.</p>
<pre class="codesplit" data-code-language="javascript">// Creating a fixed (static) boundary body
@ -484,7 +485,7 @@ let boundary = Bodies.rectangle(x, y, w, h, options);</pre>
rect(this.x, this.y, this.w, this.h);
}
}</pre>
<h2 id="69-polygons-and-groups-of-shapes">6.9 Polygons and Groups of Shapes</h2>
<h2 id="polygons-and-groups-of-shapes">Polygons and Groups of Shapes</h2>
<p></p>
<figure>
<img src="images/06_libraries/06_libraries_4.png" alt="Figure 6.2: A “compound” body made up of multiple shapes">
@ -637,7 +638,7 @@ let part2 = Bodies.circle(x + offset, y, r);</pre>
<h3 id="exercise-64">Exercise 6.4</h3>
<p>Make your own little alien being using multiple shapes attached to a single body. Remember, you arent 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!</p>
</div>
<h2 id="69-feeling-attachedmatterjs-constraints">6.9 Feeling Attached—Matter.js Constraints</h2>
<h2 id="feeling-attachedmatterjs-constraints">Feeling Attached—Matter.js Constraints</h2>
<p>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: <code>MouseConstraint</code> and <code>Constraint</code>.</p>
<figure class="half-width-right">
<img src="images/06_libraries/06_libraries_10.png" alt="Figure 6.8: A Constraint is a connection between two bodies at an anchor point for each body.">
@ -851,7 +852,7 @@ Composite.add(engine.world, mouseConstraint);</pre>
<figcaption></figcaption>
</figure>
</div>
<h2 id="610-bringing-it-all-back-home-to-forces">6.10 Bringing It All Back Home to Forces</h2>
<h2 id="bringing-it-all-back-home-to-forces">Bringing It All Back Home to Forces</h2>
<p>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, Ive only actually demonstrated how to manipulate a single global force: gravity.</p>
<pre class="codesplit" data-code-language="javascript"> let engine = Engine.create();
// Changing the engine's gravity to point horizontally
@ -937,7 +938,7 @@ engine.gravity = Vector.create(0, 0);</pre>
<h3 id="exercise-68">Exercise 6.8</h3>
<p>Covert any of the steering behavior examples from Chapter 5 into matter.js. What does flocking look like with collisions?!</p>
</div>
<h2 id="611-collision-events">6.11 Collision Events</h2>
<h2 id="collision-events">Collision Events</h2>
<p>Now youve seen a survey of what can be done with matter.js. Since this book is not called “The Nature of Matter.js,” its 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 havent covered, the skills youve 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.</p>
<p>Lets ask a question youve likely been wondering about:</p>
<p><em>What if I want something to happen when two bodies collide? I mean, dont get me wrong—Im 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?</em></p>
@ -1011,7 +1012,7 @@ function mousePressed() {
<h3 id="exercise-69">Exercise 6.9</h3>
<p>Create a simulation in which <code>Particle</code> objects disappear when they collide with one another. Where and how should you delete the particles? Can you have them shatter into smaller particles?</p>
</div>
<h2 id="612-a-brief-interludeintegration-methods">6.12 A Brief Interlude—Integration Methods</h2>
<h2 id="a-brief-interludeintegration-methods">A Brief Interlude—Integration Methods</h2>
<p>Has the following ever happened to you? Youre 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?”</p>
<p>Maybe youve heard the term before. Along with “differentiation,” its one of the two main operations in calculus. Right, calculus. The good news is, youve gotten through about 90% of the material in this book related to physics simulation and I havent really needed to dive into calculus. But as I wrapping up the first half of this book and closing out this topic, its 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).</p>
<p>Lets begin by answering the question: “What does integration have to do with position, velocity, and acceleration?” Well, first lets define <strong><em>differentiation</em></strong>, 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.</p>
@ -1028,9 +1029,9 @@ location.add(velocity);</pre>
<p>One option to improve on Euler is to use smaller time steps—instead of once per frame, you could recalculate an objects position twenty times per frame. But this isnt practical; the sketch might then run too slowly.</p>
<p>I still believe that Euler is the best method for learning the basics, and its 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.</p>
<p>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 dont 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: <a href="http://www.cs.cmu.edu/afs/cs/academic/class/15462-s13/www/lec_slides/Jakobsen.pdf">Jakobsen, Thomas. "Advanced character physics." Game Developer Conference (2001)</a>.</p>
<h2 id="613-verlet-physics-with-toxiclibsjs">6.13 Verlet Physics with toxiclibs.js</h2>
<h2 id="verlet-physics-with-toxiclibsjs">Verlet Physics with toxiclibs.js</h2>
<blockquote data-type="epigraph">
<p><em>toxiclibs is an independent, open source library collection for computational design tasks with Java &#x26; 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. — </em><a href="http://toxiclibs.org"><em>toxiclibs.org</em></a><em> (last seen October 2021).</em></p>
<p><em>toxiclibs is an independent, open source library collection for computational design tasks with Java &#x26; 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. — </em><a href="http://toxiclibs.org/"><em>toxiclibs.org</em></a><em> (last seen October 2021).</em></p>
</blockquote>
<p>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 hasnt been actively maintained in over 10 years, the concepts and techniques demonstrated by the library can be found in countless creative coding projects today.</p>
<p>Karsten Schmidt continues to contribute to the creative coding field today through his recent project, <a href="https://thi.ng/umbrella"><strong>thi.ng/umbrella</strong></a><strong>.</strong> 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 <a href="https://thi.ng/vectors"><strong>thi.ng/vectors</strong></a>, which provides over 800 vector algebra functions using plain vanilla JavaScript arrays.</p>
@ -1184,7 +1185,7 @@ function setup() {
//{!1} This is the same as matter.js Engine.update()
physics.update();
}</pre>
<h2 id="614-particles-and-springs-in-toxiclibsjs">6.14 Particles and Springs in toxiclibs.js</h2>
<h3 id="particles-and-springs-in-toxiclibsjs">Particles and Springs in toxiclibs.js</h3>
<p>In the matter.js examples, I created my own class (called, say, <code>Particle</code>) and included a reference to a matter.js body.</p>
<pre class="codesplit" data-code-language="javascript">class Particle {
constructor(x, y, r) {
@ -1244,7 +1245,7 @@ let strength = 0.01;
let spring = new VerletSpring2D(particle1, particle2, length, strength);</pre>
<p>Just as with particles, in order for the connection to actually be part of the physics world, we need to explicitly add it.</p>
<pre class="codesplit" data-code-language="javascript">physics.addSpring(spring);</pre>
<h2 id="615-putting-it-all-together-a-simple-interactive-spring">6.15 Putting It All Together: A Simple Interactive Spring</h2>
<h2 id="putting-it-all-together-a-simple-interactive-spring">Putting It All Together: A Simple Interactive Spring</h2>
<p>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 dont have this problem. If I want to move the position of a particle, I can in fact set its <span data-type="equation">x,y</span> position manually. However, before doing so, its generally a good idea to call the <code>lock()</code> function.</p>
<p><code>lock()</code> is used to fix a particle in place and is identical to setting the <code>isStatic</code> property to <code>true</code> 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.</p>
<pre class="codesplit" data-code-language="javascript"> if (mouseIsPressed) {
@ -1329,7 +1330,7 @@ class Particle extends VerletParticle2D {
circle(this.x, this.y, this.r * 2);
}
}</pre>
<h2 id="616-connected-systems-part-i-string">6.16 Connected Systems, Part I: String</h2>
<h2 id="connected-systems-part-i-string">Connected Systems, Part I: String</h2>
<p>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.</p>
<figure>
<img src="images/06_libraries/06_libraries_15.png" alt="Figure 6.11: Soft body simulation designs">
@ -1411,19 +1412,20 @@ for (let i = 0; i &#x3C; total - 1; i++) {
<figcaption></figcaption>
</figure>
</div>
<h2 id="617-connected-systems-part-ii-force-directed-graph">6.17 Connected Systems, Part II: Force-Directed Graph</h2>
<p>Have you ever had the following thought?</p>
<p>“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.”</p>
<p>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—lets 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.</p>
<h2 id="connected-systems-part-ii-soft-body-character">Connected Systems, Part II: Soft Body Character</h2>
<p>Now that Ive built a simple connected system, a single string of particles, lets 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.</p>
<p>The first step to building a soft body character is to design a “skeleton”. Ill begin with a very simple design with only six vertices. Each vertex (drawn as a dot) represents a <code>Particle</code> object and each connection (drawn as a line) represents a <code>Spring</code> object.</p>
<figure>
<img src="images/06_libraries/06_libraries_18.png" alt="Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.">
<figcaption>Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.</figcaption>
<img src="images/06_libraries/06_libraries_18.png" alt="Figure X.X Design of a soft body character, the vertices are numbered according to their positions in an array.">
<figcaption>Figure X.X Design of a soft body character, the vertices are numbered according to their positions in an array.</figcaption>
</figure>
<p>Lets walk through building a sketch to create clusters of nodes as depicted in Figure 6.14. First, well need a class to describe a “node” in the system. Because “Node” is associated with the JavaScript framework “node.js” Ill stick with the term <code>Particle</code>to avoid any confusion. This is the easy part; its exactly the same as before!</p>
<p>Creating the particles is the easy part; its exactly the same as before! Id like to make one change. Rather than having the <code>setup()</code> function add the particles and springs to the physics world, Ill instead incorporate this responsibility into the <code>Particle</code> constructors.</p>
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
constructor(x, y, r) {
super(x, y);
this.r = r;
//{!1} Adding the object to the global physics world. Inside a class, the object itself is referenced with "this".
physics.addParticle(this);
}
show() {
@ -1432,7 +1434,129 @@ for (let i = 0; i &#x3C; total - 1; i++) {
circle(this.x, this.y, this.r * 2);
}
}</pre>
<p>Next I can write a class called <code>Cluster</code>, which will describe a list of particles.</p>
<p>While its not strictly necessary Id also like to incorporate a <code>Spring</code> class that inherits its functionality from <code>VerletSpring2D</code>. For this example, I want the rest length of the spring to always be equal to the distance between the skeletons 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 <code>Spring</code> constructor.</p>
<pre class="codesplit" data-code-language="javascript">class Spring extends VerletSpring2D {
// Constructor receives only two 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);
// Hardcoding the spring strength
super(a, b, length, 0.01);
//{!1} Another enhancement to to have the object add itself to the physics world!
physics.addSpring(this);
}
}</pre>
<p>Now that I have the <code>Particle</code> and <code>Spring classes</code>, the character can be assembled by adding a series of particles and springs with hardcoded values to arrays.</p>
<pre class="codesplit" data-code-language="javascript">//{!2} Storing all the particles and springs in arrays
let particles = [];
let springs = [];
function setup() {
createCanvas(640, 240);
physics = new VerletPhysics2D();
// Creating 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));
particles.push(new Particle(400, 225));
particles.push(new Particle(200, 225));
particles.push(new Particle(250, 125));
// Creating the vertex positions of the character as particles.
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]));
}</pre>
<p>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.</p>
<figure>
<img src="images/06_libraries/06_libraries_19.png" alt="Figure X.X: Internal springs keep the structure from collapsing, this is just one possible design. Try others! ">
<figcaption>Figure X.X: Internal springs keep the structure from collapsing, this is just one possible design. Try others!</figcaption>
</figure>
<p>The final example incorporates the additional springs from Figure X.X, a gravity force, as well as mouse interaction.</p>
<div data-type="example">
<h3 id="example-6x-soft-body-character">Example 6.x Soft Body Character</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/-1beeiwUK" data-example-path="examples/06_libraries/soft_body_character_copy"></div>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">let physics;
let particles = [];
let springs = [];
function setup() {
createCanvas(640, 240);
physics = new VerletPhysics2D();
let bounds = new Rect(0, 0, width, height);
physics.setWorldBounds(bounds);
physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
particles.push(new Particle(200, 25));
particles.push(new Particle(400, 25));
particles.push(new Particle(350, 125));
particles.push(new Particle(400, 225));
particles.push(new Particle(200, 225));
particles.push(new Particle(250, 125));
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]));
springs.push(new Spring(particles[5], particles[0]));
//{!3} Three internal springs!
springs.push(new Spring(particles[5], particles[2]));
springs.push(new Spring(particles[0], particles[3]));
springs.push(new Spring(particles[1], particles[4]));
}
function draw() {
background(255);
physics.update();
//{!7} Drawing the character as one shape
fill(127);
stroke(0);
beginShape();
for (let particle of particles) {
vertex(particle.x, particle.y);
}
endShape(CLOSE);
//{!6} Mouse interaction
if (mouseIsPressed) {
particles[0].lock();
particles[0].x = mouseX;
particles[0].y = mouseY;
particles[0].unlock();
}
}</pre>
<p>For the soft body character example you'll notice that Im no longer using <code>particle.show()</code> and <code>spring.show()</code> to individually visualize the particles and springs. Instead, Im drawing the character as a unified shape with the <code>beginShape()</code> and <code>endShape()</code> 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!</p>
<div data-type="exercise">
<h3 id="----exercise-611----design-your-own-soft-body-character-with-additional-vertices-and-connections-what-other-design-elements-can-you-add-what-other-forces-and-interactions-can-you-incorporate--">
Exercise 6.11
Design your own soft body character with additional vertices and connections. What other design elements can you add? What other forces and interactions can you incorporate?
</h3>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/hQw1Ih97c" data-example-path="examples/06_libraries/soft_body_character_enhanced_copy"></div>
<figcaption></figcaption>
</figure>
</div>
<h2 id="connected-systems-part-iii-force-directed-graph">Connected Systems, Part III: Force-Directed Graph</h2>
<p>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.”</p>
<p>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—lets 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.</p>
<figure>
<img src="images/06_libraries/06_libraries_20.png" alt="Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.">
<figcaption>Figure 6.14: An example of a “force-directed graph”: clusters of particles connected by spring forces.</figcaption>
</figure>
<p>Lets walk through building a sketch to create clusters of nodes as depicted in Figure 6.14. First, Ill need a class to describe a “node” in the system. Because the term “node” is associated with the JavaScript framework “node.js” Ill stick with the term “particle” avoid any confusion. I can use the <code>Particle</code> class from the soft body example and build this new example by encapsulating a list of particles into a new class called <code>Cluster</code>.</p>
<pre class="codesplit" data-code-language="javascript">class Cluster {
// A cluster is initialized with N nodes spaced out by length
constructor(n, length) {
@ -1445,9 +1569,9 @@ for (let i = 0; i &#x3C; total - 1; i++) {
this.particles.push(new Particle(x, y, 4));
}
}</pre>
<p>Lets assume there is a <code>show()</code> function to draw all the particles in the cluster as well as a <code>Cluster</code> object created in <code>setup()</code> and rendered it in <code>draw()</code>. 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. But what exactly do I mean by that? Lets assume there are four <code>Particle</code> objects: 0, 1, 2 and 3. Here are our connections:</p>
<p>Lets assume there is a <code>show()</code> function to draw all the particles in the cluster as well as a <code>Cluster</code> object created in <code>setup()</code> and rendered it in <code>draw()</code>. 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? Lets assume there are four <code>Particle</code> objects: 0, 1, 2 and 3. Here are the connections:</p>
<figure>
<img src="images/06_libraries/06_libraries_19.png" alt="">
<img src="images/06_libraries/06_libraries_21.png" alt="">
<figcaption></figcaption>
</figure>
<table>
@ -1498,7 +1622,7 @@ for (let i = 0; i &#x3C; total - 1; i++) {
<pre class="codesplit" data-code-language="javascript"> //{!1 .bold} Look how j starts at i + 1.
for (let j = i + 1; j &#x3C; this.particles.length; j++) {
let particle_j = this.particles[j];</pre>
<p>For every pair of particles <code>i</code> and <code>j</code>, I can then create a spring.</p>
<p>For every pair of particles <code>i</code> and <code>j</code>, I can then create a spring. Ill go back to using <code>VerletSpring2D</code> directly but you could also incorporate a custom <code>Spring</code> class.</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} The spring connects particle i and j.
physics.addSpring(new VerletSpring2D(particle_i, particle_j, length, 0.01));
}
@ -1520,7 +1644,7 @@ function setup() {
createCanvas(640, 240);
physics = new VerletPhysics2D();
//{!1} Create a random cluster
cluster = new Cluster(int(random(2, 20)), random(10, height / 2));
cluster = new Cluster(floor(random(2, 20)), random(10, height / 2));
}
function draw() {
@ -1541,7 +1665,7 @@ function draw() {
<figcaption></figcaption>
</figure>
</div>
<h2 id="618-attraction-and-repulsion-behaviors">6.18 Attraction and Repulsion Behaviors</h2>
<h2 id="attraction-and-repulsion-behaviors">Attraction and Repulsion Behaviors</h2>
<p>When it came time to creating an “attraction” example for matter.js, I showed how the <code>Matter.Body</code> class included an <code>applyForce()</code> function. All I then needed to do was calculate the attraction force <span data-type="equation">F_g = (G \times \text{m1} \times \text{m2}) \div \text{distance}^2</span> as a vector and apply it to the body. Similarly, the toxiclibs.js <code>VerletParticle</code> class also includes a method called <code>addForce()</code> that can apply any calculated force to a particle.</p>
<p>However, toxiclibs.js also takes this idea one step further by offering built-in functionality for common forces (lets call them “behaviors”) such as attraction! By adding an<code>AttractionBehavior</code> object to any given <code>VerletParticle2D</code> object, all other particles in the physics world will experience that attraction force.</p>
<p>Lets assume I have a <code>Particle</code> class that <code>extends VerletParticle2D</code>.</p>
@ -1554,7 +1678,7 @@ let behavior = new AttractionBehavior(particle, distance, strength);</pre>
<p>Finally, in order for the force to be activated, the behavior needs to be added to the physics world.</p>
<pre class="codesplit" data-code-language="javascript">physics.addBehavior(behavior);</pre>
<p>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.</p>
<p>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. Lets look at how we might modify our <code>Particle</code> class to do this.</p>
<p>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. Lets look at how to modify the <code>Particle</code> class to do this.</p>
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
constructor(x, y, r) {
super(x, y);
@ -1597,7 +1721,7 @@ let behavior = new AttractionBehavior(particle, distance, strength);</pre>
circle(this.x, this.y, this.r * 2);
}
}</pre>
<p>Just as discussed in Chapter 5s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles can run very slow due to <span data-type="equation">N^2</span> nature of the algorithm (every particle checking every other particle). Toxiclibs.js offers a built-in spatial indexing feature (The<code><strong>SpatialBins</strong></code> class) and <code><strong>physics.setIndex()</strong></code>that can significantly speed up these simulations. For more, check the additional examples offered on the books website.</p>
<p>Just as discussed in Chapter 5s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles can run very slow due to <span data-type="equation">N^2</span> nature of the algorithm (every particle checking every other particle). Toxiclibs.js offers a built-in spatial indexing feature (The<code>SpatialBins</code> class) and <code>physics.setIndex()</code>that can significantly speed up these simulations. For more, check the additional examples offered on the books website.</p>
<div data-type="exercise">
<h3 id="exercise-613">Exercise 6.13</h3>
<p>Use <code>AttractionBehavior</code> in conjunction with spring forces.</p>
@ -1607,10 +1731,10 @@ let behavior = new AttractionBehavior(particle, distance, strength);</pre>
<p>Step 5 Exercise:</p>
<p>Take your system of creatures from Step 4 and use a physics engine to drive their motion and behaviors. Some possibilities:</p>
<ul>
<li>Use Box2D to allow collisions between creatures. Consider triggering events when creatures collide.</li>
<li>Use Box2D to augment the design of your creatures. Build a skeleton with distance joints or make appendages with revolute joints.</li>
<li>Use toxiclibs to augment the design of your creature. Use a chain of toxiclibs particles for tentacles or a mesh of springs as a skeleton.</li>
<li>Use toxiclibs to add attraction and repulsion behaviors to your creatures.</li>
<li>Use matter.js to allow collisions between creatures. Consider triggering events when creatures collide.</li>
<li>Use matter.js to augment the design of your creatures. Build a skeleton with distance joints or make appendages with revolute joints.</li>
<li>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.</li>
<li>Use toxiclibs.js to add attraction and repulsion behaviors to your creatures.</li>
<li>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.</li>
</ul>
</div>

View file

@ -247,10 +247,11 @@ cells = newcells;</pre>
<pre class="codesplit" data-code-language="javascript">// Rule 222
let ruleset = [1, 1, 0, 1, 1, 1, 1, 0];</pre>
<p>And the neighborhood being tested is “111”. The resulting state is equal to ruleset index 0, based on how I first wrote the <code>rules()</code> function .</p>
<pre class="codesplit" data-code-language="javascript"> if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[0];</pre>
<p>The binary number “111” converted to a decimal number is 7. But I dont want <code>ruleset[7]</code>; I want <code>ruleset[0]</code>. For this to work, ruleset needs to be written with the bits in reverse order:</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Rule 222 in “reverse” order
let ruleset = [0, 1, 1, 1, 1, 0, 1, 1];</pre>
<pre class="codesplit" data-code-language="javascript"> if (a === 1 &#x26;&#x26; b === 1 &#x26;&#x26; c === 1) return ruleset[0];</pre>
<p>The binary number “111” converted to a decimal number is 7. But I dont want <code>ruleset[7]</code>; I want <code>ruleset[0]</code>. For this to work, I need to invert the index from which I am reading:</p>
<pre class="codesplit" data-code-language="javascript"> // Invert the index so that 0 becomes 7, 1 becomes 6, and so on...
return ruleset[7 - index];
</pre>
<p>I now have everything needed to compute the generations for a Wolfram elementary CA. Lets take a moment to organize the code all together.</p>
<pre class="codesplit" data-code-language="javascript">//{!1} Array for the cells
let cells = [];
@ -282,7 +283,7 @@ function draw() {
function rules(a, b, c) {
let s = "" + a + b + c;
let index = parseInt(s, 2);
return ruleset[index];
return ruleset[7 - index];
}</pre>
<h2 id="74-drawing-an-elementary-ca">7.4 Drawing an Elementary CA</h2>
<p>Whats missing? Presumably, the point here is to draw the cells. As you saw earlier, the standard technique for doing this is to stack the generations one on top of each other and draw a rectangle that is black (for state 1) or white (for state 0).</p>
@ -363,7 +364,7 @@ function draw() {
function rules(a, b, c) {
let s = "" + a + b + c;
let index = parseInt(s, 2);
return ruleset[index];
return ruleset[7 - index];
}</pre>
<div data-type="exercise">
<h3 id="exercise-71">Exercise 7.1</h3>

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,15 @@
// Daniel Shiffman
// http://natureofcode.com
const { Engine, Bodies, Composite, Constraint, Vector, Mouse, MouseConstraint } = Matter;
const {
Engine,
Bodies,
Composite,
Constraint,
Vector,
Mouse,
MouseConstraint,
} = Matter;
// A reference to the matter physics engine
let engine;

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<script src="sketch.js"></script>
<script src="particle.js"></script>
<script src="spring.js"></script>
</body>
</html>

View file

@ -0,0 +1,13 @@
class Particle extends VerletParticle2D {
constructor(x, y) {
super(x, y);
this.r = 4;
physics.addParticle(this);
}
show() {
fill(0);
stroke(0);
circle(this.x, this.y, this.r * 2);
}
}

View file

@ -0,0 +1,61 @@
// Coding Train / Daniel Shiffman
const { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
const { GravityBehavior } = toxi.physics2d.behaviors;
const { Vec2D, Rect } = toxi.geom;
let physics;
let particles = [];
let springs = [];
function setup() {
createCanvas(640, 240);
physics = new VerletPhysics2D();
let bounds = new Rect(0, 0, width, height);
physics.setWorldBounds(bounds);
physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
particles.push(new Particle(200, 25));
particles.push(new Particle(400, 25));
particles.push(new Particle(350, 125));
particles.push(new Particle(400, 225));
particles.push(new Particle(200, 225));
particles.push(new Particle(250, 125));
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]));
springs.push(new Spring(particles[5], particles[0]));
springs.push(new Spring(particles[5], particles[2]));
springs.push(new Spring(particles[0], particles[3]));
springs.push(new Spring(particles[1], particles[4]));
}
function draw() {
background(255);
physics.update();
fill(127);
stroke(0);
strokeWeight(2);
beginShape();
for (let particle of particles) {
vertex(particle.x, particle.y);
}
endShape(CLOSE);
if (mouseIsPressed) {
particles[0].lock();
particles[0].x = mouseX;
particles[0].y = mouseY;
particles[0].unlock();
}
}

View file

@ -0,0 +1,12 @@
class Spring extends VerletSpring2D {
constructor(a, b, strength) {
let length = dist(a.x, a.y, b.x, b.y);
super(a, b, length, 0.01);
physics.addSpring(this);
}
show() {
stroke(0);
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}

View file

@ -0,0 +1,4 @@
html, body {
margin: 0;
padding: 0;
}

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<script src="sketch.js"></script>
<script src="particle.js"></script>
<script src="spring.js"></script>
</body>
</html>

View file

@ -0,0 +1,16 @@
class Particle extends VerletParticle2D {
constructor(x, y) {
super(x, y);
this.r = 2;
physics.addParticle(this);
}
show() {
fill(252, 238, 33);
strokeWeight(1);
circle(this.x, this.y, this.r * 12);
strokeWeight(this.r * 4);
point(this.x, this.y);
}
}

View file

@ -0,0 +1,141 @@
// Coding Train / Daniel Shiffman
const { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
const { GravityBehavior } = toxi.physics2d.behaviors;
const { Vec2D, Rect } = toxi.geom;
let physics;
let particles = [];
let eyes = [];
let springs = [];
let showSprings = false;
function keyPressed() {
if (key == ' ') {
showSprings = !showSprings;
}
}
function setup() {
createCanvas(640, 240);
physics = new VerletPhysics2D();
let bounds = new Rect(0, 0, width, height);
physics.setWorldBounds(bounds);
particles.push(new Particle(200, 25));
particles.push(new Particle(250, 25));
particles.push(new Particle(300, 25));
particles.push(new Particle(350, 25));
particles.push(new Particle(400, 25));
particles.push(new Particle(350, 125));
particles.push(new Particle(400, 225));
particles.push(new Particle(350, 225));
particles.push(new Particle(300, 225));
particles.push(new Particle(250, 225));
particles.push(new Particle(200, 225));
particles.push(new Particle(250, 125));
eyes.push(new Particle(275, 75));
eyes.push(new Particle(325, 75));
eyes.push(new Particle(250, -25));
eyes.push(new Particle(350, -25));
for (let i = 0; i < particles.length; i++) {
for (let j = i + 1; j < particles.length; j++) {
if (i !== j) {
let a = particles[i];
let b = particles[j];
// let b = particles[(i + 1) % particles.length];
springs.push(new Spring(a, b));
}
}
}
for (let particle of particles) {
springs.push(new Spring(particle, eyes[0]));
springs.push(new Spring(particle, eyes[1]));
}
springs.push(new Spring(eyes[2], particles[1]));
springs.push(new Spring(eyes[3], particles[3]));
springs.push(new Spring(eyes[2], particles[3]));
springs.push(new Spring(eyes[3], particles[1]));
springs.push(new Spring(eyes[2], particles[0]));
springs.push(new Spring(eyes[3], particles[4]));
springs.push(new Spring(eyes[3], particles[2]));
springs.push(new Spring(eyes[2], particles[2]));
springs.push(new Spring(eyes[2], eyes[3]));
springs.push(new Spring(eyes[0], eyes[3]));
springs.push(new Spring(eyes[0], eyes[2]));
springs.push(new Spring(eyes[1], eyes[2]));
springs.push(new Spring(eyes[1], eyes[3]));
}
function draw() {
background(255);
physics.update();
stroke(112, 50, 126);
if (showSprings) stroke(112, 50, 126, 100);
strokeWeight(4);
line(particles[1].x, particles[1].y, eyes[2].x, eyes[2].y);
line(particles[3].x, particles[3].y, eyes[3].x, eyes[3].y);
strokeWeight(16);
point(eyes[2].x, eyes[2].y);
point(eyes[3].x, eyes[3].y);
fill(45, 197, 244);
if (showSprings) fill(45, 197, 244, 100);
strokeWeight(2);
beginShape();
for (let particle of particles) {
vertex(particle.x, particle.y);
}
endShape(CLOSE);
// fill(127, 127);
// stroke(0);
// strokeWeight(2);
// beginShape();
// for (let particle of particles) {
// vertex(particle.x, particle.y);
// }
// endShape(CLOSE);
// for (let particle of particles) {
// particle.show();
// }
eyes[0].show();
eyes[1].show();
// for (let eye of eyes) {
// eye.show();
// }
if (showSprings) {
for (let spring of springs) {
spring.show();
}
}
if (mouseIsPressed) {
particles[0].lock();
particles[0].x = mouseX;
particles[0].y = mouseY;
particles[0].unlock();
}
}

View file

@ -0,0 +1,13 @@
class Spring extends VerletSpring2D {
constructor(a, b, strength) {
let length = dist(a.x, a.y, b.x, b.y);
super(a, b, length, 0.001);
physics.addSpring(this);
}
show() {
strokeWeight(1);
stroke(0, 127);
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}

View file

@ -0,0 +1,4 @@
html, body {
margin: 0;
padding: 0;
}

View file

@ -20,22 +20,22 @@ function randomCharacter() {
// Constructor (makes a random DNA)
class DNA {
constructor(num) {
// The genetic sequence
//{.code-wide} Create DNA randomly.
constructor(length) {
this.genes = [];
// Fitness
//{!1} Adding a variable to track fitness.
this.fitness = 0;
for (let i = 0; i < num; i++) {
this.genes[i] = randomCharacter(); // Pick from range of chars
for (let i = 0; i < length; i++) {
this.genes[i] = randomCharacter();
}
}
// Converts character array to a String
//{!3 .code-wide} Converts array to String—PHENOTYPE.
getPhrase() {
return this.genes.join("");
}
// Fitness function (returns floating point % of "correct" characters)
//{.code-wide} Calculate fitness.
calculateFitness(target) {
let score = 0;
for (let i = 0; i < this.genes.length; i++) {
@ -46,7 +46,7 @@ class DNA {
this.fitness = score / target.length;
}
// Crossover
//{.code-wide} Crossover
crossover(partner) {
// The child is a new instance of DNA.
// (Note that the genes are generated randomly in DNA constructor,
@ -68,6 +68,7 @@ class DNA {
return child;
}
//{.code-wide} Mutation
mutate(mutationRate) {
//{!1} Looking at each gene in the array
for (let i = 0; i < this.genes.length; i++) {

View file

@ -35,40 +35,51 @@ let populationSize = 150;
// Population array
let population = [];
// Mating pool array
let matingPool = [];
// Target phrase
let target = "to be or not to be";
function setup() {
createCanvas(640, 360);
createCanvas(640, 240);
//{!3} Step 1: Initialize Population
for (let i = 0; i < populationSize; i++) {
population[i] = new DNA(target.length);
}
}
function draw() {
// Step 2: Selection
//{!3} Step 2a: Calculate fitness.
for (let i = 0; i < population.length; i++) {
population[i].calculateFitness(target);
population[i].calculateFitness(target);
}
let matingPool = []; // ArrayList which we will use for our "mating pool"
// Step 2b: Build mating pool.
let matingPool = [];
for (let i = 0; i < population.length; i++) {
let nnnn = floor(population[i].fitness * 100); // Arbitrary multiplier, we can also use monte carlo method
for (let j = 0; j < nnnn; j++) {
// and pick two random numbers
//{!4} Add each member n times according to its fitness score.
let n = floor(population[i].fitness * 100);
for (let j = 0; j < n; j++) {
matingPool.push(population[i]);
}
}
// Step 3: Reproduction
for (let i = 0; i < population.length; i++) {
let a = floor(random(matingPool.length));
let b = floor(random(matingPool.length));
let partnerA = matingPool[a];
let partnerB = matingPool[b];
let aIndex = floor(random(matingPool.length));
let bIndex = floor(random(matingPool.length));
let partnerA = matingPool[aIndex];
let partnerB = matingPool[bIndex];
// Step 3a: Crossover
let child = partnerA.crossover(partnerB);
// Step 3b: Mutation
child.mutate(mutationRate);
//{!1} Note that we are overwriting the population with the new
// children. When draw() loops, we will perform all the same
// steps with the new population of children.
population[i] = child;
}
@ -79,5 +90,5 @@ function draw() {
background(255);
textFont("Courier");
textSize(12);
text(everything, 0, 0, width, height);
text(everything, 12, 0, width, height);
}

View file

@ -13,7 +13,7 @@ class DNA {
this.genes = [];
// The maximum strength of the forces
this.maxforce = 0.1;
for (let i = 0; i < lifetime; i++) {
for (let i = 0; i < lifeSpan; i++) {
let angle = random(TWO_PI);
this.genes[i] = p5.Vector.fromAngle(angle);
this.genes[i].mult(random(0, this.maxforce));
@ -46,4 +46,4 @@ class DNA {
}
}
}
}
}

View file

@ -8,90 +8,79 @@
// Initialize the population
class Population {
constructor(m, num) {
this.mutationRate = m; // Mutation rate
constructor(mutation, length) {
this.mutationRate = mutation; // Mutation rate
this.population = []; // Array to hold the current population
this.matingPool = []; // ArrayList which we will use for our "mating pool"
this.generations = 0; // Number of generations
//make a new set of creatures
for (var i = 0; i < num; i++) {
var location = createVector(width / 2, height + 20);
this.population[i] = new Rocket(location, new DNA());
for (let i = 0; i < length; i++) {
let x = width / 2;
let y = height + 20;
this.population[i] = new Rocket(x, y, new DNA());
}
}
live() {
// Run every rocket
for (var i = 0; i < this.population.length; i++) {
for (let i = 0; i < this.population.length; i++) {
this.population[i].run();
}
}
// Calculate fitness for each creature
fitness() {
for (var i = 0; i < this.population.length; i++) {
this.population[i].calcFitness();
for (let i = 0; i < this.population.length; i++) {
this.population[i].calculateFitness();
}
}
// Generate a mating pool
selection() {
// Clear the ArrayList
this.matingPool = [];
// Calculate total fitness of whole population
var maxFitness = this.getMaxFitness();
// Calculate fitness for each member of the population (scaled to value between 0 and 1)
// Based on fitness, each member will get added to the mating pool a certain number of times
// A higher fitness = more entries to mating pool = more likely to be picked as a parent
// A lower fitness = fewer entries to mating pool = less likely to be picked as a parent
for (var i = 0; i < this.population.length; i++) {
var fitnessNormal = map(this.population[i].getFitness(), 0, maxFitness, 0, 1);
var n = floor(fitnessNormal * 100); // Arbitrary multiplier
for (var j = 0; j < n; j++) {
this.matingPool.push(this.population[i]);
}
// Sum all of the fitness values
let totalFitness = 0;
for (let i = 0; i < this.population.length; i++) {
totalFitness += this.population[i].fitness;
}
// Divide by the total to normalize the fitness values
for (let i = 0; i < this.population.length; i++) {
this.population[i].fitness /= totalFitness;
}
}
// Making the next generation
reproduction() {
// Refill the population with children from the mating pool
for (var i = 0; i < this.population.length; i++) {
// Create a new the population with children from the mating pool
let nextPopulation = [];
for (let i = 0; i < this.population.length; i++) {
// Sping the wheel of fortune to pick two parents
var m = floor(random(this.matingPool.length));
var d = floor(random(this.matingPool.length));
// Pick two parents
var mom = this.matingPool[m];
var dad = this.matingPool[d];
// Get their genes
var momgenes = mom.getDNA();
var dadgenes = dad.getDNA();
// Mate their genes
var child = momgenes.crossover(dadgenes);
let parentA = this.weightedSelection();
let parentB = this.weightedSelection();
let child = parentA.crossover(parentB);
// Mutate their genes
child.mutate(this.mutationRate);
// Fill the new population with the new child
var location = createVector(width / 2, height + 20);
this.population[i] = new Rocket(location, child);
let x = width / 2;
let y = height + 20;
nextPopulation[i] = new Rocket(x, y, child);
}
// Replace the old population
this.population = nextPopulation;
this.generations++;
}
getGenerations() {
return this.generations;
}
// Find highest fitness for the population
getMaxFitness() {
var record = 0;
for (var i = 0; i < this.population.length; i++) {
if (this.population[i].getFitness() > record) {
record = this.population[i].getFitness();
}
weightedSelection() {
// Start with the first element
let index = 0;
// Pick a starting point
let start = random(1);
// At the finish line?
while (start > 0) {
// Move a distance according to fitness
start = start - this.population[index].fitness;
// Next element
index++;
}
return record;
// Undo moving to the next element since the finish has been reached
index--;
return this.population[index].dna;
}
}
}

View file

@ -9,11 +9,11 @@
//constructor
class Rocket {
constructor(position, dna) {
constructor(x, y, dna) {
// All of our physics stuff
this.acceleration = createVector();
this.velocity = createVector();
this.position = position.copy();
this.position = createVector(x, y);
// Size
this.r = 4;
// Fitness and DNA
@ -27,9 +27,9 @@ class Rocket {
// Fitness function
// fitness = one divided by distance squared
calcFitness() {
let d = dist(this.position.x, this.position.y, target.x, target.y);
this.fitness = pow(1 / d, 2);
calculateFitness() {
let distance = p5.Vector.dist(this.position, target);
this.fitness = 1 / (distance * distance);
}
// Run in relation to all the obstacles
@ -46,14 +46,14 @@ class Rocket {
// Did I make it to the target?
checkTarget() {
let d = dist(this.position.x, this.position.y, target.x, target.y);
if (d < 12) {
let distance = p5.Vector.dist(this.position, target);
if (distance < 12) {
this.hitTarget = true;
}
}
applyForce(f) {
this.acceleration.add(f);
applyForce(force) {
this.acceleration.add(force);
}
update() {
@ -87,12 +87,4 @@ class Rocket {
pop();
}
getFitness() {
return this.fitness;
}
getDNA() {
return this.dna;
}
}

View file

@ -14,33 +14,21 @@
// This example is inspired by Jer Thorp's Smart Rockets
// http://www.blprnt.com/smartrockets/
let lifetime; // How long should each generation live
let lifeSpan = 250; // How long should each generation live
let lifeCounter = 0; // Timer for cycle of generation
let population; // Population
let lifeCounter; // Timer for cycle of generation
let target; // Target position
let info;
function setup() {
createCanvas(640, 240);
// The number of cycles we will allow a generation to live
lifetime = height;
// Initialize variables
lifeCounter = 0;
target = createVector(width / 2, 24);
// Create a population with a mutation rate, and population max
let mutationRate = 0.01;
population = new Population(mutationRate, 50);
info = createP("");
info.position(10, 380);
}
function draw() {
@ -50,11 +38,10 @@ function draw() {
fill(127);
stroke(0);
strokeWeight(2);
ellipse(target.x, target.y, 24, 24);
circle(target.x, target.y, 24);
// If the generation hasn't ended yet
if (lifeCounter < lifetime) {
if (lifeCounter < lifeSpan) {
population.live();
lifeCounter++;
// Otherwise a new generation
@ -67,8 +54,15 @@ function draw() {
// Display some info
fill(0);
info.html("Generation #: " + population.getGenerations() + "<br>" + "Cycles left: " + (lifetime - lifeCounter));
noStroke();
text(
"Generation #: " +
population.generations +
"\nCycles left: " +
(lifeSpan - lifeCounter),
10,
20
);
}
// Move the target if the mouse is pressed
@ -76,4 +70,4 @@ function draw() {
function mousePressed() {
target.x = mouseX;
target.y = mouseY;
}
}

View file

@ -17,7 +17,7 @@ class DNA {
} else {
this.genes = [];
// Constructor (makes a DNA of random PVectors)
for (let i = 0; i < lifetime; i++) {
for (let i = 0; i < lifeSpan; i++) {
this.genes[i] = p5.Vector.random2D();
this.genes[i].mult(random(0, this.maxforce));
}
@ -46,7 +46,7 @@ class DNA {
mutate(m) {
for (let i = 0; i < this.genes.length; i++) {
if (random(1) < m) {
this.genes[i] == p5.Vector.random2D();
this.genes[i] = p5.Vector.random2D();
this.genes[i].mult(random(0, this.maxforce));
}
}

View file

@ -25,15 +25,11 @@ class Obstacle {
}
contains(spot) {
if (
return (
spot.x > this.position.x &&
spot.x < this.position.x + this.w &&
spot.y > this.position.y &&
spot.y < this.position.y + this.h
) {
return true;
} else {
return false;
}
);
}
}

View file

@ -6,28 +6,24 @@
// A class to describe a population of "creatures"
// Initialize the population
class Population {
constructor(m, num) {
this.mutationRate = m; // Mutation rate
this.population = new Array(num); // Array to hold the current population
this.matingPool = []; // ArrayList which we will use for our "mating pool"
constructor(mutation, length) {
this.mutationRate = mutation; // Mutation rate
this.population = new Array(length); // Array to hold the current population
this.generations = 0; // Number of generations
//make a new set of creatures
// Make a new set of creatures
for (let i = 0; i < this.population.length; i++) {
let position = createVector(width / 2, height + 20);
this.population[i] = new Rocket(position, new DNA(), this.population.length);
this.population[i] = new Rocket(320, 220, new DNA());
}
}
live(os) {
live(obstacles) {
// For every creature
for (let i = 0; i < this.population.length; i++) {
// If it finishes, mark it down as done!
this.population[i].checkTarget();
this.population[i].run(os);
this.population[i].run(obstacles);
}
}
@ -40,70 +36,56 @@ class Population {
}
// Calculate fitness for each creature
calcFitness() {
calculateFitness() {
for (let i = 0; i < this.population.length; i++) {
this.population[i].calcFitness();
this.population[i].calculateFitness();
}
}
// Generate a mating pool
selection() {
// Clear the ArrayList
this.matingPool = [];
// Calculate total fitness of whole population
let maxFitness = this.getMaxFitness();
// Calculate fitness for each member of the population (scaled to value between 0 and 1)
// Based on fitness, each member will get added to the mating pool a certain number of times
// A higher fitness = more entries to mating pool = more likely to be picked as a parent
// A lower fitness = fewer entries to mating pool = less likely to be picked as a parent
// Sum all of the fitness values
let totalFitness = 0;
for (let i = 0; i < this.population.length; i++) {
let fitnessNormal = map(this.population[i].getFitness(), 0, maxFitness, 0, 1);
let n = int(fitnessNormal * 100); // Arbitrary multiplier
for (let j = 0; j < n; j++) {
this.matingPool.push(this.population[i]);
}
totalFitness += this.population[i].fitness;
}
// Divide by the total to normalize the fitness values
for (let i = 0; i < this.population.length; i++) {
this.population[i].fitness /= totalFitness;
}
}
// Making the next generation
reproduction() {
// Refill the population with children from the mating pool
let nextPopulation = [];
// Create the next population with children from the mating pool
for (let i = 0; i < this.population.length; i++) {
// Sping the wheel of fortune to pick two parents
let m = int(random(this.matingPool.length));
let d = int(random(this.matingPool.length));
// Pick two parents
let mom = this.matingPool[m];
let dad = this.matingPool[d];
// Get their genes
let momgenes = mom.getDNA();
let dadgenes = dad.getDNA();
// Mate their genes
let child = momgenes.crossover(dadgenes);
let parentA = this.weightedSelection();
let parentB = this.weightedSelection();
let child = parentA.crossover(parentB);
// Mutate their genes
child.mutate(this.mutationRate);
// Fill the new population with the new child
let position = createVector(width / 2, height + 20);
this.population[i] = new Rocket(position, child, this.population.length);
nextPopulation[i] = new Rocket(320, 220, child);
}
// Replace the old population
this.population = nextPopulation;
this.generations++;
}
getGenerations() {
return this.generations;
}
// Find highest fitness for the population
getMaxFitness() {
let record = 0;
for (let i = 0; i < this.population.length; i++) {
if (this.population[i].getFitness() > record) {
record = this.population[i].getFitness();
}
weightedSelection() {
// Start with the first element
let index = 0;
// Pick a starting point
let start = random(1);
// At the finish line?
while (start > 0) {
// Move a distance according to fitness
start = start - this.population[index].fitness;
// Next element
index++;
}
return record;
// Undo moving to the next element since the finish has been reached
index--;
return this.population[index].dna;
}
}
}

View file

@ -6,15 +6,15 @@
// the only difference is that it has DNA & fitness
class Rocket {
constructor(pos, dna, totalRockets) {
constructor(x, y, dna) {
// All of our physics stuff
this.acceleration = createVector();
this.velocity = createVector();
this.position = pos.copy();
this.position = createVector(x, y);
this.r = 4;
this.dna = dna;
this.finishTime = 0; // We're going to count how long it takes to reach target
this.recordDist = 10000; // Some high number that will be beat instantly
this.finishCounter = 0; // We're going to count how long it takes to reach target
this.recordDistance = Infinity; // Some high number that will be beat instantly
this.fitness = 0;
this.geneCounter = 0;
@ -27,64 +27,64 @@ class Rocket {
// finish = what order did i finish (first, second, etc. . .)
// f(distance,finish) = (1.0f / finish^1.5) * (1.0f / distance^6);
// a lower finish is rewarded (exponentially) and/or shorter distance to target (exponetially)
calcFitness() {
if (this.recordDist < 1) this.recordDist = 1;
calculateFitness() {
// Reward finishing faster and getting close
this.fitness = 1 / (this.finishTime * this.recordDist);
this.fitness = 1 / (this.finishCounter * this.recordDistance);
// Make the function exponential
// Let's try to to the 4th power!
this.fitness = pow(this.fitness, 4);
if (this.hitObstacle) this.fitness *= 0.1; // lose 90% of fitness hitting an obstacle
if (this.hitTarget) this.fitness *= 2; // twice the fitness for finishing!
//{!3} lose 90% of fitness hitting an obstacle
if (this.hitObstacle) {
this.fitness *= 0.1;
}
//{!3} Double the fitness for finishing!
if (this.hitTarget) {
this.fitness *= 2;
}
}
// Run in relation to all the obstacles
// If I'm stuck, don't bother updating or checking for intersection
run(os) {
run(obstacles) {
// Stop the rocket if it's hit an obstacle or the target
if (!this.hitObstacle && !this.hitTarget) {
this.applyForce(this.dna.genes[this.geneCounter]);
this.geneCounter = (this.geneCounter + 1) % this.dna.genes.length;
this.update();
// If I hit an edge or an obstacle
this.obstacles(os);
}
// Draw me!
if (!this.hitObstacle) {
this.show();
// Check if rocket hits an obstacle
this.checkObstacles(obstacles);
}
this.show();
}
// Did I make it to the target?
checkTarget() {
let d = dist(
this.position.x,
this.position.y,
target.position.x,
target.position.y
);
if (d < this.recordDist) this.recordDist = d;
let distance = p5.Vector.dist(this.position, target.position);
//{!3} Check if the distance is closer than the “record” distance. If it is, set a new record.
if (distance < this.recordDistance) {
this.recordDistance = distance;
}
// If the object reaches the target, set a boolean flag to true.
if (target.contains(this.position) && !this.hitTarget) {
this.hitTarget = true;
// Otherwise, increase the finish counter
} else if (!this.hitTarget) {
this.finishTime++;
this.finishCounter++;
}
}
// Did I hit an obstacle?
obstacles(os) {
for (let i = 0; i < os.length; i++) {
let obs = os[i];
if (obs.contains(this.position)) {
// This new function lives in the Rocket class and checks if a rocket has
// hit an obstacle.
checkObstacles(obstacles) {
for (let obstacle of obstacles) {
if (obstacle.contains(this.position)) {
this.hitObstacle = true;
}
}
}
applyForce(f) {
this.acceleration.add(f);
applyForce(force) {
this.acceleration.add(force);
}
update() {
@ -116,18 +116,12 @@ class Rocket {
vertex(this.r, this.r * 2);
endShape();
fill(0);
noStroke();
rotate(-theta);
//text(nf(this.fitness,2,1), 5, 5);
// text(nf(this.fitness, 1, 5), 15, 5);
pop();
}
getFitness() {
return this.fitness;
}
getDNA() {
return this.dna;
}
stopped() {
return this.hitObstacle;
}
}

View file

@ -14,11 +14,11 @@
// This example is inspired by Jer Thorp's Smart Rockets
// http://www.blprnt.com/smartrockets/
let lifetime; // How long should each generation live
let lifeSpan = 250; // How long should each generation live
let population; // Population
let lifecycle; // Timer for cycle of generation
let lifeCounter = 0; // Timer for cycle of generation
let recordtime; // Fastest time to target
let target; // Target position
@ -29,41 +29,37 @@ let obstacles = []; //an array list to keep track of all the obstacles!
function setup() {
createCanvas(640, 240);
// The number of cycles we will allow a generation to live
lifetime = 300;
// Initialize variables
lifecycle = 0;
recordtime = lifetime;
recordTime = lifeSpan;
target = new Obstacle(width / 2 - 12, 24, 24, 24);
// Create a population with a mutation rate, and population max
let mutationRate = 0.01;
population = new Population(mutationRate, 50);
population = new Population(0.01, 150);
// Create the obstacle course
obstacles = [];
obstacles.push(new Obstacle(width / 2 - 100, height / 2, 200, 10));
obstacles.push(new Obstacle(width / 2 - 75, height / 2, 150, 10));
}
function draw() {
background(255);
// Draw the start and target positions
target.show();
// If the generation hasn't ended yet
if (lifecycle < lifetime) {
if (lifeCounter < lifeSpan) {
population.live(obstacles);
if (population.targetReached() && lifecycle < recordtime) {
recordtime = lifecycle;
if (population.targetReached() && lifeCounter < recordTime) {
recordTime = lifeCounter;
} else {
lifeCounter++;
}
lifecycle++;
// Otherwise a new generation
} else {
lifecycle = 0;
population.calcFitness();
lifeCounter = 0;
population.calculateFitness();
population.selection();
population.reproduction();
}
@ -76,9 +72,9 @@ function draw() {
// Display some info
fill(0);
noStroke();
text("Generation #: " + population.getGenerations(), 10, 18);
text("Cycles left: " + (lifetime - lifecycle), 10, 36);
text("Record cycles: " + recordtime, 10, 54);
text("Generation #: " + population.generations, 10, 18);
text("Cycles left: " + (lifeSpan - lifeCounter), 10, 36);
text("Record cycles: " + recordTime, 10, 54);
}
// Move the target if the mouse is pressed
@ -86,5 +82,5 @@ function draw() {
function mousePressed() {
target.position.x = mouseX;
target.position.y = mouseY;
recordtime = lifetime;
recordTime = lifeSpan;
}

View file

@ -5,41 +5,46 @@
// Interactive Selection
// http://www.genarts.com/karl/papers/siggraph91.html
//Constructor (makes a random DNA)
// Constructor (makes a random DNA)
class DNA {
constructor(newgenes) {
// DNA is random floating point values between 0 and 1 (!!)
// The genetic sequence
let len = 20; // Arbitrary length
let length = 20; // Arbitrary length
if (newgenes) {
this.genes = newgenes;
} else {
this.genes = new Array(len);
this.genes = new Array(length);
for (let i = 0; i < this.genes.length; i++) {
this.genes[i] = random(0, 1);
}
}
}
// Crossover
// Creates new DNA sequence from two (this &
crossover(partner) {
let child = new Array(this.genes.length);
let crossover = floor(random(this.genes.length));
let child = new DNA(this.genes.length);
//{!1} Picking a random “midpoint” in the genes array
let midpoint = floor(random(this.genes.length));
for (let i = 0; i < this.genes.length; i++) {
if (i > crossover) child[i] = this.genes[i];
else child[i] = partner.genes[i];
// Before the midpoint genes from this DNA
if (i < midpoint) {
child.genes[i] = this.genes[i];
// After the midpoint from the partner DNA
} else {
child.genes[i] = partner.genes[i];
}
}
let newgenes = new DNA(child);
return newgenes;
return child;
}
// Based on a mutation probability, picks a new random character in array spots
mutate(m) {
mutate(mutationRate) {
for (let i = 0; i < this.genes.length; i++) {
if (random(1) < m) {
this.genes[i] = random(0, 1);
//{!1} Check a random number against mutation rate
if (random(1) < mutationRate) {
this.genes[i] = random(1);
}
}
}
}
}

View file

@ -0,0 +1,93 @@
// The Nature of Code
// Daniel Shiffman
// http://natureofcode.com
// Interactive Selection
// http://www.genarts.com/karl/papers/siggraph91.html
// The class for a "face", contains DNA sequence, fitness value, position on screen
// Fitness Function f(t) = t (where t is "time" mouse rolls over face)
// Create a new flower
class Flower {
constructor(dna, x, y) {
this.rolloverOn = false; // Are we rolling over this flower?
this.dna = dna; // Flower's DNA
this.x = x; // Position on screen
this.y = y;
let w = 70; // Size of square enclosing flower
let h = 140; // Size of square enclosing flower
this.fitness = 1; // How good is this flower?
this.boundingBox = new Rectangle(
this.x - w / 2,
this.y - h / 2,
this.w,
this.h
);
}
// Display the flower
show() {
let genes = this.dna.genes;
let c = color(genes[0], genes[1], genes[2]); // petal color
let size = map(genes[3], 0, 1, 0, this.wh / 4); // petal size
let count = floor(map(genes[4], 0, 1, 0, 10)); // petal count
let centerColor = color(genes[5], genes[6], genes[7]); // center color
let centerSize = map(genes[8], 0, 1, 0, this.wh / 8); // center size
let stemColor = color(genes[9], genes[10], genes[11]); // stem color
let stemLength = map(genes[12], 0, 1, 0, (this.wh * 3) / 4); // stem length
push();
translate(this.x, this.y);
noStroke();
// Draw the petals
fill(c);
for (let i = 0; i < count; i++) {
let angle = map(i, 0, count, 0, TWO_PI);
let x = size * cos(angle);
let y = size * sin(angle);
ellipse(x, y, size, size);
}
// Draw the center
fill(centerColor);
ellipse(0, 0, centerSize, centerSize);
// Draw the stem
fill(stemColor);
rect(0, centerSize / 2 + stemLength / 2, 5, stemLength);
// Draw the bounding box
stroke(0.25);
if (this.rolloverOn) fill(0, 0.25);
else noFill();
rectMode(CENTER);
rect(0, 0, this.wh, this.wh);
pop();
// Display fitness value
textAlign(CENTER);
if (this.rolloverOn) fill(0);
else fill(0.25);
text("" + floor(this.fitness), this.x, this.y + 55);
}
getFitness() {
return this.fitness;
}
getDNA() {
return this.dna;
}
// Increment fitness if mouse is rolling over flower
rollover(mx, my) {
if (this.r.contains(mx, my)) {
this.rolloverOn = true;
this.fitness += 0.25;
} else {
this.rolloverOn = false;
}
}
}

View file

@ -1,15 +1,15 @@
<!doctype html>
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.6.0/p5.min.js"></script>
<meta charset="utf-8" />
<title>Nature of Code Example 9.4: Faces</title>
<link rel="stylesheet" type="text/css" href="style.css">
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<script src="dna.js"></script>
<script src="population.js"></script>
<script src="face.js"></script>
<script src="flower.js"></script>
<script src="rectangle.js"></script>
<script src="sketch.js"></script>
</body>

View file

@ -10,20 +10,20 @@
// Create the population
class Population {
constructor(m, num) {
this.mutationRate = m; // Mutation rate
constructor(mutationRate, size) {
this.mutationRate = mutationRate; // Mutation rate
this.population = []; // array to hold the current population
this.matingPool = [];
this.generations = 0; // Number of generations
for (let i = 0; i < num; i++) {
this.population[i] = new Face(new DNA(), 40 + i * 80, 60);
for (let i = 0; i < size; i++) {
this.population[i] = new Flower(new DNA(), 40 + i * 80, 60);
}
}
// Display all faces
display() {
show() {
for (let i = 0; i < this.population.length; i++) {
this.population[i].display();
this.population[i].show();
}
}
@ -34,25 +34,44 @@ class Population {
}
}
// Generate a mating pool
selection() {
// Clear the ArrayList
this.matingPool = [];
// Calculate total fitness of whole population
let maxFitness = this.getMaxFitness();
// Calculate fitness for each member of the population (scaled to value between 0 and 1)
// Based on fitness, each member will get added to the mating pool a certain number of times
// A higher fitness = more entries to mating pool = more likely to be picked as a parent
// A lower fitness = fewer entries to mating pool = less likely to be picked as a parent
// Sum all of the fitness values
let totalFitness = 0;
for (let i = 0; i < this.population.length; i++) {
let fitnessNormal = map(this.population[i].getFitness(), 0, maxFitness, 0, 1);
let n = floor(fitnessNormal * 100); // Arbitrary multiplier
totalFitness += this.population[i].fitness;
}
// Divide by the total to normalize the fitness values
for (let i = 0; i < this.population.length; i++) {
this.population[i].fitness /= totalFitness;
}
}
for (let j = 0; j < n; j++) {
this.matingPool.push(this.population[i]);
}
weightedSelection() {
// Start with the first element
let index = 0;
// Pick a starting point
let start = random(1);
// At the finish line?
while (start > 0) {
// Move a distance according to fitness
start = start - population[index].fitness;
// Next element
index++;
}
// Undo moving to the next element since the finish has been reached
index--;
return this.population[index];
}
selection() {
// Sum all of the fitness values
let totalFitness = 0;
for (let i = 0; i < this.population.length; i++) {
totalFitness += this.population[i].fitness;
}
// Divide by the total to normalize the fitness values
for (let i = 0; i < this.population.length; i++) {
this.population[i].fitness /= totalFitness;
}
}
@ -74,23 +93,8 @@ class Population {
// Mutate their genes
child.mutate(this.mutationRate);
// Fill the new population with the new child
this.population[i] = new Face(child, 40 + i * 80, 60);
this.population[i] = new Flower(child, 40 + i * 80, 60);
}
this.generations++;
}
getGenerations() {
return this.generations;
}
// Find highest fitness for the population
getMaxFitness() {
let record = 0;
for (let i = 0; i < this.population.length; i++) {
if (this.population[i].getFitness() > record) {
record = this.population[i].getFitness();
}
}
return record;
}
}
}

View file

@ -2,8 +2,7 @@
// Daniel Shiffman
// http://natureofcode.com
// Re-implementing java.awt.Rectangle
// so JS mode works
// Re-implementing java.awt.Rectangle (from Processing) for p5.js
class Rectangle {
constructor(x, y, w, h) {
@ -14,6 +13,11 @@ class Rectangle {
}
contains(px, py) {
return (px > this.x && px < this.x + this.width && py > this.y && py < this.y + this.height);
return (
px > this.x &&
px < this.x + this.width &&
py > this.y &&
py < this.y + this.height
);
}
}
}

View file

@ -10,29 +10,28 @@ let population;
function setup() {
createCanvas(640, 240);
colorMode(RGB, 1.0, 1.0, 1.0, 1.0);
let popmax = 8;
let populationSize = 8;
let mutationRate = 0.05; // A pretty high mutation rate here, our population is rather small we need to enforce variety
// Create a population with a target phrase, mutation rate, and population max
population = new Population(mutationRate, popmax);
population = new Population(mutationRate, populationSize);
// A simple button class
button = createButton("evolve new generation");
button.mousePressed(nextGen);
button.mousePressed(nextGeneration);
button.position(10, 200);
textFont("Courier");
textSize(14);
}
function draw() {
background(1);
// Display the faces
population.display();
population.show();
population.rollover(mouseX, mouseY);
textFont("Courier");
textAlign(LEFT);
text("Generation " + population.getGenerations(), 12, height - 48);
text("Generation " + population.generations, 12, height - 48);
}
// If the button is clicked, evolve next generation
function nextGen() {
function nextGeneration() {
population.selection();
population.reproduction();
}

View file

@ -117,8 +117,7 @@ class Population {
allPhrases() {
let everything = "";
let displayLimit = min(this.population.length, 50);
let displayLimit = min(this.population.length, 51);
for (let i = 0; i < displayLimit; i++) {
everything += this.population[i].getPhrase();

View file

@ -28,7 +28,6 @@
//
// # Rinse and repeat
let target;
let popmax;
let mutationRate;
@ -72,13 +71,15 @@ function draw() {
text("Best phrase:", 10, 32);
textSize(24);
text(answer, 10, 64);
let statstext = "total generations: " + population.getGenerations() + "\n";
statstext += "average fitness: " + nf(population.getAverageFitness(), 0, 2) + "\n";
let statstext =
"total generations: " + population.getGenerations() + "\n";
statstext +=
"average fitness: " + nf(population.getAverageFitness(), 0, 2) + "\n";
statstext += "total population: " + popmax + "\n";
statstext += "mutation rate: " + floor(mutationRate * 100) + "%";
textSize(12);
text(statstext, 10, 96);
textSize(8);
text(population.allPhrases(), width/2, 24)
}
text(population.allPhrases(), width / 2, 24);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 262 KiB

After

Width:  |  Height:  |  Size: 224 KiB