Notion - Update docs
|
@ -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 particle’s behavior (I’ll do precisely this in a future example). I could also add variables to describe color and shape, or load a <code>p5.Image</code> to draw the particle in a more interesting way. For now, however, I’ll 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 can’t live forever. If they did, the p5.js sketch would eventually grind to a halt as the amount of particles increases to an unwieldy number over time. As new particles are born, old particles need to be removed, creating the illusion of an infinite stream of particles without hurting the performance of the sketch.</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 can’t 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, I’ll 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. Here’s 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 < 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, there’s 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, it’s time for the next big step: how can I keep track of many particles, without knowing in advance exactly how many I might have at any given time? The answer is the JavaScript array, a data structure that stores an arbitrarily long list of values. In JavaScript, an array is actually an object created from the <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, I’ll 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, I’ll 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, it’s time for the next big step: how can I keep track of many particles, without knowing in advance exactly how many I might have at any given time? The answer is the JavaScript array, a data structure that stores an arbitrarily long list of values. In JavaScript, an array is actually an object created from the <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, I’ll 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, I’ll 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 that’s something that I both love and hate about coding in JavaScript—there are so many different styles and options to consider. On the one hand, this makes JavaScript a highly flexible and adaptable language, but on the other hand, the abundance of choice can be overwhelming and lead to a lot of confusion when learning.</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>Let’s 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 you’re 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 won’t work in every situation. Yes, I love <code>for...of</code> loops, and I’ll get to use them in some of the examples to come where I need to iterate over the items in an array, but not just yet. Ultimately, I want to create a continuous stream of particles, with one new particle added to the array each cycle through <code>draw()</code> and old particles removed from the array as they die. As you’ll 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> class’s <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 doesn’t cause the sketch to crash (as it would with adding), the problem is perhaps more insidious in that it leaves no evidence. To discover the flaw, I must first establish an important fact: when an element is removed from an array with <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, it’s impossible to skip an element this way. Here’s 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, it’s impossible to skip an element this way. Here’s 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 don’t just remove the oldest particle after some period of time (determined by checking the frameCount or array length). In this example, that approach would actually work! I could even use a different array method called <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 don’t 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 they’re 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>I’ve 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 there’s an additional step that I can and should take: writing a class describing the list of <code>Particle</code> objects itself. I’ll 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, that’s 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 I’m 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 doesn’t live forever. Set a limit on how many particles an individual system can generate. Then, when a particle system is empty (has no particles left), remove it from the <code>emitters</code> array.</p>
|
||||
|
@ -511,7 +511,14 @@ class WackyConfetti {
|
|||
}</pre>
|
||||
<p>Let me pause for a moment. You’ve done nothing wrong. All you wanted to do was wish your friend a happy birthday and enjoy writing some code. But while the reasoning behind this approach is quite sound, there’s a problem: aren’t you going to be copy-pasting a lot of code between the different confetti classes?</p>
|
||||
<p>Yes, you probably will be. Even though the kinds of particles are different enough to merit breaking them out into separate classes, there’s still a ton of code that they’ll likely share. For example, they’ll all have vectors to keep track of position, velocity, and acceleration; an <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. I’ll 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 don’t 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? Wouldn’t 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, I’ll take a different example from the world of animals: dogs, cats, monkeys, pandas, wombats, sea nettles, you name it. I’ll start by coding a <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> class’s <code>eat()</code> method results in calling the <code>Animal</code> class’s <code>eat()</code> method. Then the subclass’s method definition can continue with any additional, custom code.</p>
|
||||
<h3 id="particles-with-inheritance">Particles with Inheritance</h3>
|
||||
<p>Now that I’ve covered the theory of inheritance and its syntax, I’m ready to write a working example of inheritance in p5.js based on the <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 < 10; i++) {
|
||||
dogs.push(new Dog());
|
||||
}
|
||||
for (let i = 0; i < 15; i++) {
|
||||
cats.push(new Cat());
|
||||
}
|
||||
for (let i = 0; i < 6; i++) {
|
||||
turtles.push(new Turtle());
|
||||
}
|
||||
for (let i = 0; i < 98; i++) {
|
||||
kiwis.push(new Kiwi());
|
||||
}</pre>
|
||||
<p>As the day begins, the animals are all pretty hungry and are looking to eat. So it’s 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, I’m 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 < 10; i++) {
|
||||
kingdom.push(new Dog());
|
||||
}
|
||||
for (let i = 0; i < 15; i++) {
|
||||
kingdom.push(new Cat());
|
||||
}
|
||||
for (let i = 0; i < 6; i++) {
|
||||
kingdom.push(new Turtle());
|
||||
}
|
||||
for (let i = 0; i < 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 I’ve covered the theory of these new concepts and their syntax, I’m 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? It’s 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 it’s 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? There’s 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 I’ll 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 what’s to come in Chapter 5, where I’ll 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 (I’ll include an example with the book’s supplementary materials), but you can also use other open-source or commercial graphics editing tools.</p>
|
||||
<p>Once you’ve made a PNG and deposited it in your sketch’s <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>
|
||||
|
|
|
@ -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, we’re not the first to do this. The world of computer graphics and programming is full of source code dedicated to physics simulations. Just try searching “open-source physics engine” and you could spend the rest of your day pouring over rich and complex code. This begs the question: If a code library takes care of physics simulation, why should you bother learning how to write any of the algorithms yourself?</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, you’d likely be lost just reading the documentation of a library. Second, even though a library may take care of the math behind the scenes, it won’t necessarily simplify your code. There can be a great deal of overhead in understanding how a library works and what it expects from you code-wise. Finally, as wonderful as a physics engine might be, if you look deep down into your hearts, it’s likely that you seek to create worlds and visualizations that stretch the limits of imagination. A library is great, but it provides a limited set of features. It’s important to know both when to live within those limitations in the pursuit of a creative coding project and when those limits prove to be confining.</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, it’s 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 book’s 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 Developer’s Conference in 2006. Since then it has evolved into a rich and elaborate open-source physics engine. It’s been used for countless projects, most notably highly successful games such as the award-winning Crayon Physics and the runaway hit Angry Birds.</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 Box2D’s measurements and calculations are real-world measurements (meters, kilograms, seconds)—only its “world” is a two-dimensional plane with top, bottom, left, and right edges. You tell it things like: “The gravity of the world is 9.81 newtons per kilogram, and a circle with a radius of four meters and a mass of fifty kilograms is located ten meters above the world’s bottom.” Box2D will then tell you things like: “One second later, the rectangle is at five meters from the bottom; two seconds later, it is ten meters below,” and so on. While this provides for an amazing and realistic physics engine, it also necessitates lots of complicated code in order to translate back and forth between the physics “world” (a key term in Box2D) and the world you want to draw — the “pixel” world of graphics canvas.</p>
|
||||
<p>While this makes for an incredibly accurate and robust library (it’s also highly optimized and fast for c++ projects), it creates a tremendous burden for the coder. I will, as best I can, continue to maintain a set of Box2D compatible examples for this book (there are several JavaScript ports), but I believe the relative simplicity of working with a library that is native to JavaScript and uses pixels as the unit of measurement will make for a more intuitive and friendly bridge from my p5.js examples.</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 I’m going to stop the discussion. Why, you ask? It’s not that understanding the math behind collisions isn’t important or valuable. (In fact, I’m including additional examples on the website related to collisions without a physics library.) The reason for stopping is that life is short (let this also be a reason for you to consider going outside and frolicking instead of programming altogether). You can’t expect to master every detail of physics simulation. And while you might enjoy this discussion for circles, it’s only going to lead to wanting to work with rectangles. And strangely shaped polygons. And curved surfaces. And swinging pendulums colliding with springy springs. And and and and and.</p>
|
||||
<p>Working with collisions in a p5.js sketch while still having time to spend with friends and family—that’s the reason for this chapter. Erin Catto spent years developing solutions to these kinds of problems with Box2D and Liam has built a beautiful JavaScript library with matter.js so there’s no need to re-invent the proverbial wheel, at least for now.</p>
|
||||
<p>In conclusion, if you find yourself describing an idea for a p5.js sketch and the word “collisions” comes up, then it’s likely time to learn a physics engine.</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 book’s demonstrations, as you already quite aware, I’m using the official p5.js web editor for developing and sharing the code examples. The easiest way to add a library besides is to edit the <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"><script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js" integrity="sha512-5T245ZTH0m0RfONiFm2NF0zcYcmAuNzcGyPSQ18j8Bs5Pbfhp5HP1hosrR8XRt5M3kSRqzjNMYpm2+it/AUX/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script></pre>
|
||||
<p>At the time of this writing, the most recent version of matter.js is <code>0.18.0</code> and that’s what you’ll see referenced in the above snippet. As matter.js updates and new versions are released, it’s often a good idea to upgrade, but by referencing a specific version that you know works with your sketch, you don’t have to worry about new features of the library breaking your existing code.</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 I’ll blow the previous work out of the water. But before I’m ready to do that, it’s important to walk through the overall process of using matter.js (or any physics engine) in p5.js. Let’s begin by writing a pseudo-code generalization of all of the examples in Chapters 1 through 5.</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 p5’s 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>I’ll 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>It’s worth noting that gravity doesn’t have to be fixed; you can adjust the gravity vector while your program is running. Gravity can be turned off by setting it to a <span data-type="equation">(0,0)</span> vector.</p>
|
||||
<p>Once the world is initialized, it’s 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. It’s 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>, you’ve 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>. Let’s 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 it’s there, check it for collisions, and move it appropriately according to other forces in the environment. It’ll do all that for you without you having to lift a finger! The question therefore is how do you know where the box is at any given moment in order to draw it?</p>
|
||||
<p>In the next section, I’m going to cover how to query matter.js in order to render the world with p5.js. How that works is fundamental to being able to design and visualize your own creations. This is your time to shine. You can be the designer of your world, and politely ask matter.js to compute all the physics.</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 you’ve designed. It does allow ways for customization of the “debug drawing” style, but I find the defaults perfectly adequate for quickly double-checking that I’ve configured a world correctly.</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 you’re accustomed to with p5.js, keeping track of which bodies are which and drawing them appropriately. Let’s consider the following file structure:</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 aren’t limited to using the shape drawing functions in p5.js; you can use images, colors, add hair with lines, and more. Think of the matter.js shapes as skeletons for your original fantastical design!</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, I’ve 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 you’ve seen a survey of what can be done with matter.js. Since this book is not called “The Nature of Matter.js,” it’s not my intention to cover every single possible feature of the matter.js library. But hopefully by looking at the basics of building bodies and constraints, when it comes time to use an aspect of matter.js that I haven’t covered, the skills you’ve gained here will make that process considerably less painful. There is one more feature of matter.js, however, that I do think is worth covering.</p>
|
||||
<p>Let’s ask a question you’ve likely been wondering about:</p>
|
||||
<p><em>What if I want something to happen when two bodies collide? I mean, don’t get me wrong—I’m thrilled that matter.js is handling all of the collisions for me. But if it takes care of everything for me, how am I supposed to know when things are happening?</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? You’re at a fancy cocktail party regaling your friends with tall tales of software physics simulations. Someone pipes up: “Enchanting! But what integration method are you using?” “What?!” you think to yourself. “Integration?”</p>
|
||||
<p>Maybe you’ve heard the term before. Along with “differentiation,” it’s one of the two main operations in calculus. Right, calculus. The good news is, you’ve gotten through about 90% of the material in this book related to physics simulation and I haven’t really needed to dive into calculus. But as I wrapping up the first half of this book and closing out this topic, it’s worth taking a moment to examine the calculus behind what I have been demonstrating and how it relates to the methodology in certain physics libraries (like Box2D, matter.js, and the upcoming toxiclibs).</p>
|
||||
<p>Let’s begin by answering the question: “What does integration have to do with position, velocity, and acceleration?” Well, first let’s 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 object’s position twenty times per frame. But this isn’t practical; the sketch might then run too slowly.</p>
|
||||
<p>I still believe that Euler is the best method for learning the basics, and it’s also perfectly adequate for most of the projects you might want to make with p5.js. Anything lost in efficiency or inaccuracy is made up for in ease of use and understandability. For better accuracy, for example, the Box2D engine uses something called symplectic Euler or semi-explicit Euler, a slight modification of Euler. Other engines use an integration method called Runge-Kutta (named for German mathematicians C. Runge and M. W. Kutta) physics engines.</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 don’t really need to store the velocity; if you always know where an object was at one point in time and where it is now, you can extrapolate its velocity. Verlet integration does precisely this, calculating velocity on the fly while the program is running, instead of maintaining a separate velocity variable. Verlet integration is particularly well suited for particle systems, especially those with spring connections between the particles. The details of how it works are handled by libraries, however, if you are interested in diving deeper into Verlet physics, I would suggest reading the seminal paper on the topic, from which just about every Verlet computer graphics simulation is derived: <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 & 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 & 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 hasn’t been actively maintained in over 10 years, the concepts and techniques demonstrated by the library can be found in countless creative coding projects today.</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 don’t 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, it’s 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 < 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—let’s call them “nodes”—in which the positions of those nodes are not manually assigned. Rather, the nodes arrange themselves according to a set of forces. While any forces can be used, a classic method involves spring forces. And so toxiclibs.js is perfect for this scenario.</p>
|
||||
<h2 id="connected-systems-part-ii-soft-body-character">Connected Systems, Part II: Soft Body Character</h2>
|
||||
<p>Now that I’ve built a simple connected system, a single string of particles, let’s expand this idea to create a squishy, cute friend in p5.js, otherwise known as a “soft body character.” In computer graphics and game design, a soft body character refers to an object that deforms and changes shape with physics. Unlike a rigid body, which maintains its shape when it moves or collides, a soft body allows for more flexible, fluid, and organic movement. Soft bodies can stretch, squish, and jiggle in response to forces and collisions. One of the first popular examples of soft body physics was SodaConstructor, a game created in the early 2000s. Players could construct and animate custom two-dimensional creatures built out of masses and springs. Other examples over the years include games like LocoRoco, World of Goo, and more recently, JellyCar.</p>
|
||||
<p>The first step to building a soft body character is to design a “skeleton”. I’ll begin with a very simple design with only six vertices. Each vertex (drawn as a dot) represents a <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>Let’s walk through building a sketch to create clusters of nodes as depicted in Figure 6.14. First, we’ll need a class to describe a “node” in the system. Because “Node” is associated with the JavaScript framework “node.js” I’ll stick with the term <code>Particle</code>to avoid any confusion. This is the easy part; it’s exactly the same as before!</p>
|
||||
<p>Creating the particles is the easy part; it’s exactly the same as before! I’d like to make one change. Rather than having the <code>setup()</code> function add the particles and springs to the physics world, I’ll 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 < 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 it’s not strictly necessary I’d 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 skeleton’s particles in their resting (or “equilibrium”) state. Additionally, while you may want to enhance the example with a more sophisticated design, I can keep things simple by hardcoding a uniform “strength” value in the <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 I’m no longer using <code>particle.show()</code> and <code>spring.show()</code> to individually visualize the particles and springs. Instead, I’m 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—let’s call them “nodes”—in which the positions of those nodes are not manually assigned. Rather, the nodes arrange themselves according to a set of forces. While any forces can be used, a classic method involves spring forces. And so toxiclibs.js is perfect for this scenario.</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>Let’s walk through building a sketch to create clusters of nodes as depicted in Figure 6.14. First, I’ll need a class to describe a “node” in the system. Because the term “node” is associated with the JavaScript framework “node.js” I’ll stick with the term “particle” avoid any confusion. I can use the <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 < total - 1; i++) {
|
|||
this.particles.push(new Particle(x, y, 4));
|
||||
}
|
||||
}</pre>
|
||||
<p>Let’s 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? Let’s assume there are four <code>Particle</code> objects: 0, 1, 2 and 3. Here are our connections:</p>
|
||||
<p>Let’s 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? Let’s 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 < 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 < 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. I’ll 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 (let’s 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>Let’s 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. Let’s 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. Let’s 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 5’s 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 book’s website.</p>
|
||||
<p>Just as discussed in Chapter 5’s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles can run very slow due to <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 book’s 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>
|
||||
|
|
|
@ -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 && b === 1 && c === 1) return ruleset[0];</pre>
|
||||
<p>The binary number “111” converted to a decimal number is 7. But I don’t 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 && b === 1 && c === 1) return ruleset[0];</pre>
|
||||
<p>The binary number “111” converted to a decimal number is 7. But I don’t 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. Let’s 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>What’s 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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
|
@ -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++) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
93
content/examples/09_ga/9_4_interactive_selection/flower.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 262 KiB After Width: | Height: | Size: 224 KiB |