Notion - Update docs

This commit is contained in:
shiffman 2023-10-30 21:05:56 +00:00 committed by GitHub
parent aad92077b5
commit 9f4548b9b6
15 changed files with 216 additions and 84 deletions

View file

@ -810,7 +810,8 @@ position.add(velocity);</pre>
<p>Ill use the rest of this chapter to show you how to implement these algorithms.</p>
<h3 id="algorithm-1-constant-acceleration">Algorithm #1: Constant Acceleration</h3>
<p>Acceleration Algorithm #1, a constant acceleration, isnt particularly interesting, but its the simplest and thus an excellent starting point to incorporate acceleration into the code. The first thing to do is add another variable to the <code>Mover</code> class:</p>
<pre class="codesplit" data-code-language="javascript">class Mover {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Mover {
constructor() {
// Initialize a stationary mover at the center of the canvas.
this.position = createVector(width / 2, height / 2);
@ -818,12 +819,16 @@ position.add(velocity);</pre>
// A new vector for acceleration
<strong>this.acceleration = createVector(0, 0);</strong>
}</pre>
</div>
<p></p>
<p>Next, incorporate acceleration into the <code>update()</code> function:</p>
<pre class="codesplit" data-code-language="javascript"> update() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> update() {
// The motion algorithm is now two lines of code!
<strong>this.velocity.add(this.acceleration);
this.position.add(this.velocity);</strong>
}</pre>
</div>
<p>Im almost finished. The only missing piece is to get that mover moving! In the constructor, the initial velocity is set to zero, rather than a random vector as previously done. Therefore, when the sketch starts, the object is at rest. To get it moving instead of changing the velocity directly, Ill update it through the objects acceleration. According to Algorithm #1, the acceleration should be constant, so Ill choose a value now.</p>
<pre class="codesplit" data-code-language="javascript"> this.acceleration = createVector(-0.001, 0.01);</pre>
<p>This means that for every frame of the animation, the objects velocity should increase by -0.001 pixels in the x direction and 0.01 pixels in the y direction. Maybe youre thinking, “Gosh, those values seem awfully small!” Indeed, they are quite tiny, but thats by design. Acceleration values accumulate over time in the velocity, about 30 times per second depending on the sketchs frame rate. To keep the magnitude of the velocity vector from growing too quickly and spiraling out of control, the acceleration values should remain quite small.</p>

View file

@ -45,11 +45,13 @@
<p>The formula to convert from degrees to radians is:</p>
<div data-type="equation">\text{radians} = {2\pi \times \text{degrees} \over 360}</div>
<p>Thankfully, if you prefer to think of angles in degrees, you can call <code>angleMode(DEGREES)</code>, or you can use the convenience function <code>radians()</code> to convert values from degrees to radians. The constants<code>PI</code> , <code>TWO_PI</code> , and <code>HALF_PI</code>are also available (equivalent to 180, 360, and 90 degrees, respectively). For example, here are two ways in p5.js to rotate a shape by 60 degrees:</p>
<pre class="codesplit" data-code-language="javascript">let angle = 60;
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">let angle = 60;
rotate(radians(angle));
angleMode(DEGREES);
rotate(angle);</pre>
</div>
<p>While degrees can be useful, for the purposes of this book, Ill be working with radians because theyre the standard unit of measurement across many programming languages and graphics environments. If theyre new to you, this is a good opportunity to practice! Additionally, if you arent familiar with how rotation is implemented in p5.js, I recommend watching <a href="https://thecodingtrain.com/tracks/transformations-in-p5">my Coding Train video series on transformations in p5.js</a>.</p>
<div data-type="exercise">
<h3 id="exercise-31">Exercise 3.1</h3>
@ -69,13 +71,15 @@ rotate(angle);</pre>
<div data-type="equation">\text{angle} = \text{angle} + \text{angular velocity}</div>
<p>In fact, these angular motion formulas are simpler than their linear motion equivalents since the angle here is a <em>scalar</em> quantity (a single number), not a vector! This is because on two-dimensional space, where theres one axis of rotation; in 3D space, the angle would become a vector. (Note that in most contexts these formulas would include a multiplication by the change in time, referred to as “delta-time.” Im assuming a delta-time of 1 that corresponds to one frame of animation in p5.js.)</p>
<p>Using the answer from Exercise 3.1, lets say you wanted to rotate a baton in p5.js by some angle. Originally, the code might have read:</p>
<pre class="codesplit" data-code-language="javascript">translate(width / 2, height / 2);
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">translate(width / 2, height / 2);
rotate(angle);
line(-60, 0, 60, 0);
circle(60, 0, 16);
circle(-60, 0, 16, 16);
angle = angle + 0.1;</pre>
</div>
<p>Adding in the principles of angular motion, I can instead write the following example (the solution to Exercise 3.1).</p>
<div data-type="example">
<h3 id="example-31-angular-motion-using-rotate">Example 3.1: Angular Motion Using rotate()</h3>
@ -131,10 +135,10 @@ function draw() {
this.angleVelocity = 0;
this.angleAcceleration = 0;
}
}</pre>
<p>Then, in <code>update()</code>, the movers position and angle are updated according to the algorithm I just demonstrated.</p>
<pre class="codesplit" data-code-language="javascript">update() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">update() {
// Regular old-fashioned motion
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
@ -145,8 +149,10 @@ function draw() {
this.acceleration.mult(0);
}</pre>
</div>
<p>Of course, for any of this to matter, I also need to rotate the object when drawing it in the <code>show()</code> method. (Ill add a line from the center to the edge of the circle so that rotation is visible. You could also draw the object as a shape other than a circle.)</p>
<pre class="codesplit" data-code-language="javascript">show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">show() {
stroke(0);
fill(175, 200);
@ -161,6 +167,7 @@ function draw() {
// pop() restores the previous state after rotation is complete.
pop();
}</pre>
</div>
<p>At this point, if you were to actually go ahead and create a <code>Mover</code> object, you wouldnt see it behave any differently. This is because the angular acceleration is initialized to zero (<code>this.angleAcceleration = 0;</code>) . For the object to rotate, it needs a non-zero acceleration! Certainly, one option is to hard-code a number in the constructor:</p>
<pre class="codesplit" data-code-language="javascript"> this.angleAcceleration = 0.01;</pre>
<p>You can produce a more interesting result, however, by dynamically assigning an angular acceleration in the <code>update()</code> method according to forces in the environment. This could be my cue to start researching the physics of angular acceleration based on the concepts of <a href="http://en.wikipedia.org/wiki/Torque">torque</a> and <a href="http://en.wikipedia.org/wiki/Moment_of_inertia">moment of inertia</a>, but at this stage, that level of simulation would be a bit of a rabbit hole. (Ill cover more about modeling angular acceleration with a pendulum later in this chapter, as well as look at how third-party physics libraries realistically model rotational motion in Chapter 6.) Instead, a quick and dirty solution that yields creative results will suffice. A reasonable approach is to calculate angular acceleration as a function of the objects “linear acceleration,” its rate of change of velocity along a path vector, as opposed to its rotation. Heres an example:</p>
@ -175,7 +182,8 @@ function draw() {
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> update() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> update() {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
@ -188,6 +196,7 @@ function draw() {
this.acceleration.mult(0);
}</pre>
</div>
<p>Notice that Ive used multiple strategies to keep the object from spinning out of control. First, I divide <code>acceleration.x</code> by <code>10</code> before assigning it to <code>angleAcceleration</code>. Then, for good measure, I also use <code>constrain()</code> to confine <code>angleVelocity</code> to the range <code>(-0.1, 0.1)</code>.</p>
<div data-type="exercise">
<h3 id="exercise-33">Exercise 3.3</h3>
@ -256,7 +265,8 @@ function draw() {
</tbody>
</table>
<p>Now that I have the formula, lets see where it should go in the <code>Mover</code> classs <code>show()</code> method to make the mover (now drawn as a rectangle) point in its direction of motion. Note that in p5.js, the function for inverse tangent is <code>atan()</code>.</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
// Solve for the angle using atan().
let angle = atan(this.velocity.y / this.velocity.x);
@ -270,6 +280,7 @@ function draw() {
rect(0, 0, 30, 10);
pop();
}</pre>
</div>
<div class="half-width-right">
<figure>
<img src="images/03_oscillation/03_oscillation_8.png" alt="Figure 3.7: The vectors \vec{v}_1 and \vec{v}_2 with components (4,-3) and (-4,3) point in opposite directions.">
@ -288,7 +299,8 @@ function draw() {
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
// Using atan2() to account for all possible directions
let angle = atan2(this.velocity.y, this.velocity.x);
@ -302,6 +314,7 @@ function draw() {
rect(0, 0, 30, 10);
pop();
}</pre>
</div>
<p>To simplify this even further, the <code>p5.Vector</code> class itself provides a method called <code>heading()</code>, which takes care of calling <code>atan2()</code> and returns the 2D direction angle, in radians, for any <code>p5.Vector</code>.</p>
<pre class="codesplit" data-code-language="javascript"> // The easiest way to do this!
let angle = this.velocity.heading();</pre>
@ -365,12 +378,14 @@ function draw() {
theta += 0.02;
}</pre>
<p>Polar to Cartesian conversion is common enough that p5.js includes a handy function to take care of it for you. Its included as a static method of the <code>p5.Vector</code> class called <code>fromAngle()</code>. It takes an angle in radians and creates a unit vector in Cartesian space that points in the direction specified by the angle. Heres how that would look in Example 3.4.</p>
<pre class="codesplit" data-code-language="javascript"> // Create a unit vector pointing in the direction of an angle.
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> // Create a unit vector pointing in the direction of an angle.
let position = p5.Vector.fromAngle(theta);
// To complete polar to Cartesian conversion, scale the vector by r.
position.mult(r);
// Draw the circle using the x,y components of the vector.
circle(position.x, position.y, 48);</pre>
</div>
<p>Are you amazed yet? Ive demonstrated some pretty great uses of tangent (for finding the angle of a vector) and sine and cosine (for converting from polar to Cartesian coordinates). I could stop right here and be satisfied. But Im not going to. This is only the beginning. As Ill show you next, what sine and cosine can do for you goes beyond mathematical formulas and right triangles.</p>
<div data-type="exercise">
<h3 id="exercise-35">Exercise 3.5</h3>
@ -945,7 +960,8 @@ function draw() {
<li>angular acceleration</li>
</ul>
<p>The <code>Pendulum</code> class needs all these properties, too.</p>
<pre class="codesplit" data-code-language="javascript">class Pendulum {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Pendulum {
constructor() {
// Length of arm
this.r = ????;
@ -956,8 +972,10 @@ function draw() {
// Angular acceleration
this.angleAcceleration = ????;
} </pre>
</div>
<p>Next, I need to write an <code>update()</code> method to update the pendulums angle according to the formula.</p>
<pre class="codesplit" data-code-language="javascript"> update() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> update() {
// An arbitrary constant
let gravity = 0.4;
// Calculate acceleration according to the formula.
@ -967,6 +985,7 @@ function draw() {
// Increment angle.
this.angle += this.angleVelocity;
}</pre>
</div>
<p>Note that the acceleration calculation now includes a multiplication by 1. When the pendulum is to the right of its resting position, the angle is positive, and so the sine of the angle is also positive. However, gravity should “pull” the bob back toward the resting position. Conversely, when the pendulum is to the left of its resting position, the angle is negative, and so its sine is negative, too. In this case, the pulling force should be positive. Multiplying by 1 is necessary in both scenarios.</p>
<div class="half-width-right">
<figure>

View file

@ -230,7 +230,8 @@ function draw() {
}
}</pre>
<p>Run this code for a few minutes and youll start to see the frame rate slow down further and further until the program grinds to a halt. (My tests yielded horrific performance after 15 minutes.) The issue, of course, is that Im adding more and more particles without removing any. To fix this, I can use the <code>splice()</code> method to get rid of particles as they die. It removes one or more elements from an array starting from a given index. And this is why I cant use a <code>for...of</code> loop here; <code>splice()</code> needs a reference to the index of the particle being removed, but <code>for...of</code> loops dont provide such a reference. Im stuck using a regular <code>for</code> loop instead.</p>
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; particles.length; i++) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; particles.length; i++) {
//{!1} Improve readability by assigning array element to a variable
let particle = particles[i];
particle.run();
@ -239,13 +240,16 @@ function draw() {
particles.splice(i, 1);
}
}</pre>
</div>
<p>Although this code will run just fine and never grind to a halt, Ive opened up a medium-sized can of worms by trying to manipulate the contents of an array while iterating through that very same array. This is just asking for trouble. Take, for example, the following code:</p>
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; particles.length; i++) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; particles.length; i++) {
let particle = particles[i];
particle.run();
// Adding a new Particle to the list while iterating?
particles.push(new Particle(width / 2, 20));
}</pre>
</div>
<p>This is a somewhat extreme scenario (with flawed logic), but it proves the point. For each particle in the list, this code adds a new particle to the list, and so the <code>length</code> of the array increases. This will result in an infinite loop, as I can never increment past the size of the array!</p>
<p>While removing elements from the array during a loop doesnt cause the sketch to crash (as it would with adding), the problem is perhaps more insidious in that it leaves no evidence. To discover the flaw, I must first establish an important fact: when an element is removed from an array with <code>splice()</code>, all subsequent elements are shifted to the left. Figure 4.1 shows what happens when particle C (index 2) is removed. Particles A and B keep the same index, while particles D and E shift from 3 and 4 to 2 and 3, respectively.</p>
<figure>
@ -286,7 +290,8 @@ function draw() {
</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 is to iterate through the array backwards. Since elements slide from right to left as other elements are removed, its impossible to skip an element this way. Heres how the code looks:</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} Looping through the list backwards
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> //{!1} Looping through the list backwards
<strong>for (let i = particles.length - 1; i >= 0; i--) {</strong>
let particle = particles[i];
particle.run();
@ -294,13 +299,18 @@ function draw() {
particles.splice(i, 1);
}
}</pre>
</div>
<p>A second solution is to use a <strong>higher-order function</strong>. This is a function that receives another function as an argument (or provides a function as its return value). In the case of JavaScript arrays, there are many higher-order functions. For example, a common one is <code>sort()</code>, which takes as its argument a function that defines how to compare two elements of the array and then sorts the array according to that comparison. With the array of particles, I can use <code>filter()</code>, a higher-order function that takes a function specifying some kind of condition as an argument, checks each item in an array for that condition, and returns only the item(s) where the given condition is true (excluding those items that return false).</p>
<pre class="codesplit" data-code-language="javascript"> particles = particles.filter(function(particle) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> particles = particles.filter(function(particle) {
// Keep particles that are not dead!
return !particle.isDead();
});</pre>
</div>
<p>This is more commonly written using JavaScripts arrow notation. (To learn more, check out my Coding Train video tutorial on <a href="https://thecodingtrain.com/tracks/topics-in-native-javascript/js/higher-order-functions">higher-order functions and arrow notation</a>.)</p>
<pre class="codesplit" data-code-language="javascript"> particles = particles.filter(particle => !particle.isDead());</pre>
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> particles = particles.filter(particle => !particle.isDead());</pre>
</div>
<p>For the purposes of this book, Im going to stick with the <code>splice()</code> method, but I encourage you to explore writing your code with higher-order functions and arrow notation.</p>
<div data-type="example">
<h3 id="example-42-an-array-of-particles">Example 4.2: An Array of Particles</h3>
@ -537,11 +547,13 @@ class WackyConfetti {
<p>Let me pause for a moment. Youve done nothing wrong. All you wanted to do was wish your friend a happy birthday and enjoy writing some code. But while the reasoning behind this approach is quite sound, theres a problem: arent you going to be copy-pasting a lot of code between the different confetti classes?</p>
<p>Yes, you probably will be. Even though the kinds of particles are different enough to merit breaking them out into separate classes, theres still a ton of code that theyll likely share. For example, theyll all have vectors to keep track of position, velocity, and acceleration; an <code>update()</code> function that implements the motion algorithm; and more.</p>
<p>This is where <strong>inheritance</strong> comes in. Inheritance allows you to write a class that takes on (”inherits”) variables and methods from another class, while also implementing its own custom features. You might also be wondering whether adding all those different types of confetti to a single <code>particles</code> array actually works. After all, I dont typically mix different kinds of objects in one array, as it could get quite confusing. How will the code in the <code>Emitter</code> class know which particle is which kind of confetti? Wouldnt separate arrays be easier to manage?</p>
<pre class="codesplit" data-code-language="javascript"> constructor() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> constructor() {
this.happyParticles = [];
this.funParticles = [];
this.wackyParticles = [];
}</pre>
</div>
<p>In fact, separating the particles into different arrays is quite inconvenient; a single array for all the particles in the system would be far more practical. Fortunately, the ability to mix objects of different types in one array is an inherent feature of JavaScript, and the concept of <strong>polymorphism</strong><strong><em> </em></strong>allows the mixed objects to be operated on as if they were of 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, Ill illustrate inheritance and polymorphism in more detail, and then Ill create a particle system that incorporates these concepts.</p>
<h3 id="inheritance-basics">Inheritance Basics</h3>
@ -796,7 +808,8 @@ for (let animal of kingdom) {
<p>I know a particle has an <span data-type="equation">x</span> position somewhere between 0 and the width of the canvas. What if I said: when the particles <span data-type="equation">x</span> position is 0, its rotation should be 0; when its <span data-type="equation">x</span> position is equal to the width, its rotation should be equal to <span data-type="equation">4\pi</span>? Does this ring a bell? As discussed in <a href="/random#a-smoother-approach-with-perlin-noise">Chapter 0</a>, whenever a value has one range that you want to map to another range, you can use the <code>map()</code> function.</p>
<pre class="codesplit" data-code-language="javascript">let angle = map(this.position.x, 0, width, 0, TWO_PI * 2);</pre>
<p>Heres how this code fits into the <code>show()</code> method.</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
let angle = map(this.position.x, 0, width, 0, TWO_PI * 2);
rectMode(CENTER);
@ -810,6 +823,7 @@ for (let animal of kingdom) {
square(0, 0, 12);
pop();
}</pre>
</div>
<p>The choice of <span data-type="equation">4\pi</span> might seem arbitrary, but its intentional—two full rotations adds a significant degree of spin to the particle compared to just one.</p>
<div data-type="exercise">
<h3 id="exercise-47">Exercise 4.7</h3>
@ -857,7 +871,8 @@ for (let animal of kingdom) {
</div>
<h2 id="particle-systems-with-forces">Particle Systems with Forces</h2>
<p>So far in this chapter, Ive focused on structuring code in an object-oriented way to manage a collection of particles. While I did keep the<code>applyForce()</code> function in my <code>Particle</code> class, I took a couple shortcuts to keep the code simple. Now Ill add a <code>mass</code> property back in, changing the <code>constructor()</code> and <code>applyForce()</code> methods in the process. (The rest of the class stays the same.)</p>
<pre class="codesplit" data-code-language="javascript">class Particle {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Particle {
constructor(x, y) {
this.position = createVector(x, y);
this.velocity = createVector(random(-1, 1),random(-2, 0));
@ -873,15 +888,18 @@ for (let animal of kingdom) {
let f = force.copy();
f.div(this.mass);
this.acceleration.add(f);
}</pre>
}</pre>
</div>
<p>Now that the <code>Particle</code> class is complete, I have a very important question to ask: where should I call the <code>applyForce()</code> method? Where in the code is it appropriate to apply a force to a particle? In my view, theres no right or wrong answer; it really depends on the exact functionality and goals of a particular p5.js sketch. My quick and dirty solution in the previous examples was to just create and apply a <code>gravity</code> force in the <code>run()</code> method of each particle.</p>
<pre class="codesplit" data-code-language="javascript"> run() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> run() {
//{!2} Create a hard-coded vector and apply it as a force.
let gravity = createVector(0, 0.05);
this.applyForce(gravity);
this.update();
this.show();
}</pre>
</div>
<p>Id like to now consider a broader, more generic solution that will allow different forces to be applied to individual particles in a system. For example, what if I were to apply a force globally every time through <code>draw()</code> to all particles globally?</p>
<pre class="codesplit" data-code-language="javascript">function draw() {
background(255);
@ -902,11 +920,13 @@ for (let animal of kingdom) {
emitter.run();
}</pre>
<p>Of course, if I call an <code>applyForce()</code> method on the <code>Emitter</code> object in <code>draw()</code>, I then have to define that method in the <code>Emitter</code> class. In English, that method needs to be able to receive a force as a <code>p5.Vector</code> and apply that force to all the particles. Translating that into code:</p>
<pre class="codesplit" data-code-language="javascript"> applyForce(force) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> applyForce(force) {
for (let particle of this.particles) {
particle.applyForce(force);
}
}</pre>
</div>
<p>It almost seems silly to write this method. The code is essentially saying, “Apply a force to a particle system so that the system can apply that force to all of the individual particles.” Although this may sound a bit roundabout, this approach is actually quite reasonable. After all, the emitter is in charge of managing the particles, so if you want to talk to the particles, youve got to talk to them through their manager. (Also, heres a chance to use an enhanced <code>for...of</code> loop, since no particles are being deleted!)</p>
<p>Heres the full example, including this change. (The code assumes the existence of the <code>Particle</code> class written earlier; no need to show it again, since nothing has changed.)</p>
<div data-type="example">
@ -1043,7 +1063,8 @@ function draw() {
</tbody>
</table>
<p>The methods are almost identical, but there are only two differences. I mentioned one of them before: the argument to <code>applyRepeller()</code> is a <code>Repeller</code> object, not a <code>p5.Vector</code> object. The second difference is the more important one. I must calculate a custom <code>p5.Vector</code> force for each and every particle and apply that force. How is that force calculated? In a <code>Repeller</code> class method called <code>repel()</code>, the inverse of the <code>attract()</code> method from the <code>Attractor</code> class.</p>
<pre class="codesplit" data-code-language="javascript"> // All the same steps to calculate an attractive force, only pointing in the opposite direction.
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> // All the same steps to calculate an attractive force, only pointing in the opposite direction.
repel(particle) {
// 1) Get the force direction.
let force = p5.Vector.sub(this.position, particle.position);
@ -1056,6 +1077,7 @@ function draw() {
force.setMag(strength);
return force;
}</pre>
</div>
<p>Notice how throughout this entire process of adding a repeller to the environment, I never once considered editing the <code>Particle</code> class itself. A particle doesnt actually have to know anything about the details of its environment; it simply needs to manage its position, velocity, and acceleration, as well as have the ability to receive an external force and act on it.</p>
<p>Im now ready to write this example in its entirety, again leaving out the <code>Particle</code> class, which hasnt changed.</p>
<div data-type="example">
@ -1194,12 +1216,14 @@ class Repeller {
img = loadImage("texture.png");
}</pre>
<p>Next, when it comes time to draw the particle, use the <code>img</code> variable instead of drawing a circle or rectangle.</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
imageMode(CENTER);
//{!1} Note how tint() is the image equivalent of shapes fill().
tint(255, this.lifespan);
image(img, this.position.x, this.position.y);
}</pre>
</div>
<p>This smoke example is also a nice excuse to revisit the Gaussian distributions from <a href="/random#a-normal-distribution-of-random-numbers">Chapter 0</a>. Instead of launching the particles in a purely random direction, which produces a fountain-like effect, the result will appear more smoke-like if the initial velocity vectors cluster mostly around a mean value, with a lower probability of outlying velocities. Using the <code>randomGaussian()</code> function, the particle velocities can be initialized as follows:</p>
<pre class="codesplit" data-code-language="javascript"> let vx = randomGaussian(0, 0.3);
let vy = randomGaussian(-1, 0.3);

View file

@ -76,7 +76,8 @@
</figure>
<p>The concept of maximum speed was introduced in Chapter 1 to ensure that a movers speed remained within a reasonable range. However, I didnt always use it in the subsequent chapters. In Chapter 2, other forces such as friction and drag kept the speed in check, while in Chapter 3, oscillation was caused by opposing forces that kept the speed limited. In this chapter, maximum speed is a key parameter for controlling the behavior of a steering agent, so Ill include it in all the examples.</p>
<p>While I encourage you to consider how other forces such as friction and drag could be combined with steering behaviors, Im going to focus only on steering forces for the time being. As such, I can include the concept of maximum speed as a limiting factor in the force calculation. First, I need to add a property to the <code>Vehicle</code> class setting the maximum speed.</p>
<pre class="codesplit" data-code-language="javascript">class Vehicle {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Vehicle {
constructor() {
this.position = createVector();
@ -86,11 +87,13 @@
this.maxspeed = ????;
}
</pre>
</div>
<p>Then, in the desired velocity calculation, Ill scale according to maximum speed.</p>
<pre class="codesplit" data-code-language="javascript">let desired = p5.Vector.sub(target, this.position);
desired.setMag(this.maxspeed);</pre>
<p>Putting this all together, I can now write a method called <code>seek()</code> that receives a <code>p5.Vector</code> target and calculates a steering force toward that target.</p>
<pre class="codesplit" data-code-language="javascript"> seek(target) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> seek(target) {
//{!1} Calculating the desired velocity to target at max speed
let desired = p5.Vector.sub(target,this.position);
desired.setMag(this.maxspeed);
@ -101,6 +104,7 @@ desired.setMag(this.maxspeed);</pre>
// to the objects acceleration
this.applyForce(steer);
}</pre>
</div>
<p>Notice how I finish the method by passing the steering force into <code>applyForce()</code>. This assumes that the code is built on top of the foundation I developed in <a href="/force#">Chapter 2</a>.</p>
<p>To see why Reynoldss steering formula works so well, take a look at Figure 5.4. It shows what the steering force looks like relative to the vehicle and target positions.</p>
<figure>
@ -109,7 +113,8 @@ desired.setMag(this.maxspeed);</pre>
</figure>
<p>This force looks quite different from gravitational attraction. Remember one of the principles of autonomous agents: an autonomous agent has a <em>limited</em> ability to perceive its environment, including its own state. Heres that ability, subtly but powerfully embedded into Reynoldss steering formula. In the case of gravitational attraction, the force pulling an object toward another is the same regardless of how that object is moving. But here, the vehicle is actively aware of its own velocity, and its steering force compensates accordingly. This adds a lifelike quality to the simulation, as the way in which the vehicle moves toward the target depends on its own understanding of its current motion.</p>
<p>In all of this excitement, Ive missed one last step. What sort of vehicle is this? Is it a super sleek race car with amazing handling? Or a large city bus that needs a lot of advance notice to turn? A graceful panda, or a lumbering elephant? The example code, as it stands, has no feature to account for this variation in steering ability. For that, I need to limit the magnitude of the steering force. Ill call this limit the “maximum force” (or <code>maxforce</code> for short).</p>
<pre class="codesplit" data-code-language="javascript">class Vehicle {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Vehicle {
constructor() {
this.position = createVector();
@ -121,8 +126,10 @@ desired.setMag(this.maxspeed);</pre>
this.maxforce = ????;
}
</pre>
</div>
<p>Now I just need to impose that limit before applying the steering force.</p>
<pre class="codesplit" data-code-language="javascript"> seek(target) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> seek(target) {
let desired = p5.Vector.sub(target, this.position);
desired.setMag(this.maxspeed);
let steer = p5.Vector.sub(desired, this.velocity);
@ -132,6 +139,7 @@ desired.setMag(this.maxspeed);</pre>
this.applyForce(steer);
}</pre>
</div>
<p>Limiting the steering force brings up an important point: the goal isnt to get the vehicle to the target as fast as possible. If it were, I would just say “set position equal to target” and the vehicle would instantly teleport to there! Instead, as Reynolds puts it, the goal is to move the vehicle in a “lifelike and improvisational manner.”</p>
<p>Im trying to make it appear as if the vehicle is steering its way to the target, and so its up to me to play with the forces and variables of the system to simulate a given behavior. For example, a large maximum steering force would result in a very different path than a small one (see Figure 5.5). One isnt inherently better or worse than the other; it depends on the desired effect. (And of course, these values need not be fixed and could change based on other conditions. Perhaps a vehicle has an <em>energy</em> property: the higher the energy, the better it can steer.)</p>
<figure>
@ -271,7 +279,8 @@ desired.setMag(this.maxspeed);</pre>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> arrive(target) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> arrive(target) {
let desired = p5.Vector.sub(target, this.position);
//{!1} The distance is the magnitude of
@ -293,6 +302,7 @@ desired.setMag(this.maxspeed);</pre>
steer.limit(this.maxforce);
this.applyForce(steer);
}</pre>
</div>
<p>The arrive behavior is a great demonstration of an autonomous agents perception of the environment—including its own state. This model differs from the inanimate forces of Chapter 2: a celestial body attracted to another body doesnt know it is experiencing gravity, whereas a cheetah chasing its prey knows its chasing. The key is in how the forces are calculated. For instance, in the gravitational attraction sketch (Example 2.6), the force always pointed directly from the object to the target—the exact direction of the desired velocity. Here, by contrast, the vehicle perceives its distance to the target and adjusts its desired speed accordingly, slowing down as it gets closer. The force on the vehicle itself is therefore based not just on the desired velocity, but on the desired velocity <em>relative to its current velocity</em>. The vehicle accounts for its own state as part of its assessment of the environment.</p>
<p>Put another way, the magic of Reynoldss “desired minus velocity” equation is that it essentially makes the steering force a manifestation of the current velocitys <strong><em>error</em></strong>: “Im supposed to be going this fast in this direction, but Im actually going this fast in another direction. My error is the difference between where I want to go and where Im currently going.” Sometimes, this can lead to seemingly unexpected results, as in Figure 5.10.</p>
<figure>
@ -336,7 +346,8 @@ desired.setMag(this.maxspeed);</pre>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> // The method receives an “offset” from the edges
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> // The method receives an “offset” from the edges
boundaries(offset) {
// Start with a null desired velocity.
let desired = null;
@ -364,6 +375,7 @@ desired.setMag(this.maxspeed);</pre>
this.applyForce(steer);
}
}</pre>
</div>
<p>In this <code>boundaries()</code> method, you might be wondering why I set the the <code>desired</code> velocity to <code>null</code> at the outset. Why not just set <code>desired</code> to a vector of 0? Remember, the steering force equals the desired velocity minus the current velocity! If the vehicle desires to move at 0 velocity, the resulting force would slow the vehicle down to a stop. By initializing <code>desired</code> to <code>null</code> and checking that its non-null before applying the steering force, the vehicle wont be affected at all when its comfortably away from the edges of the canvas.</p>
<div data-type="exercise">
<h3 id="exercise-55">Exercise 5.5</h3>
@ -378,7 +390,6 @@ desired.setMag(this.maxspeed);</pre>
<p>Reynoldss own flow field example involves the vehicle looking ahead to its future position and following the vector at that spot. For simplicitys sake, however, Ill instead have the vehicle follow the vector at its current position.</p>
<p>Before I can write the additional code for the <code>Vehicle</code> class to follow a flow field, I first need a class that describes the flow field itself, Since a flow field is essentially a grid of vectors, a two-dimensional array is a convenient data structure to represent it, as I can reference each element with two indices, the cells column and row in the grid. (If you arent familiar with 2D arrays, I suggest reviewing my video tutorial on <a href="https://thecodingtrain.com/tracks/p5-tips-and-tricks/more-p5/2d-arrays">2D Arrays in JavaScript</a> available at thecodingtrain.com.)</p>
<pre class="codesplit" data-code-language="javascript">class FlowField {
constructor() {
// Resolution of grid relative to canvas width and height in pixels.
this.resolution = ????;
@ -392,7 +403,8 @@ desired.setMag(this.maxspeed);</pre>
}
}</pre>
<p>How should I fill in the missing values? Lets say I have a canvas thats 200 pixels wide by 200 pixels high. In theory, I could make a flow field that has a vector for every single pixel, meaning 40,000 vectors total (<span data-type="equation">200 \times 200</span>). This isnt a terribly unreasonable number, but in this context, a vector per pixel is overkill. I can easily get by with, say, one every ten pixels (<span data-type="equation">20 \times 20 = 400</span>). My <code>resolution</code> variable sets the size of each cell in pixels. Then I can calculate the number of columns and rows based on the size of the canvas divided by the resolution.</p>
<pre class="codesplit" data-code-language="javascript"> constructor() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> constructor() {
this.resolution = 10;
// Total columns equals width divided by resolution.
this.cols = floor(width / this.resolution);
@ -404,6 +416,7 @@ desired.setMag(this.maxspeed);</pre>
this.field[i] = new Array(this.rows);
}
}</pre>
</div>
<p>Now that Ive set up the data structure for the flow field, its time to compute the flow fields vectors themselves. How do I do that? However I want! Perhaps Id like every vector in the flow field pointing to the right (Figure 5.14).</p>
<figure>
<img src="images/05_steering/05_steering_15.png" alt="Figure 5.14: A flow field with all vectors pointing to the right">
@ -520,7 +533,8 @@ let row = floor(this.position.y / this.resolution);</pre>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">class Vehicle {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Vehicle {
follow(flow) {
// What is the vector at that spot in the flow field?
@ -532,6 +546,7 @@ let row = floor(this.position.y / this.resolution);</pre>
steer.limit(this.maxforce);
this.applyForce(steer);
}</pre>
</div>
<p>Notice how <code>lookup()</code> is a method of the <code>FlowField</code> class, rather than of <code>Vehicle</code>. While you certainly could place <code>lookup()</code> within the <code>Vehicle</code> class instead, from my perspective, placing it in <code>FlowField</code> aligns best with the principle of encapsulation in object-oriented programming. The “look up” task, which involves retrieving a vector based on a position from the flow field, is inherently tied to the data of the <code>FlowField</code> object itself.</p>
<p>You may also notice some familiar elements from the particle system chapter, such as the use of an array of vehicles. Although the vehicles here operate independently, this is a great first step toward thinking about the group behaviors that Ill introduced later this chapter.</p>
<div data-type="exercise">
@ -764,7 +779,8 @@ if (distance > path.radius) {
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> follow(path) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> follow(path) {
//{!3} Step 1: Predict the vehicles future position.
let future = this.velocity.copy();
future.setMag(25);
@ -786,6 +802,7 @@ if (distance > path.radius) {
this.seek(target);
}
}</pre>
</div>
<div class="half-width-right">
<figure>
<img src="images/05_steering/05_steering_30.png" alt="Figure 5.28: The elements of the getNormalPoint() function: position, a, and b.">
@ -868,17 +885,21 @@ if (distance > path.radius) {
}
}</pre>
<p>Now that the <code>Path</code> class has been updated, its the vehicles turn to learn how to accommodate multiple line segments. All it did before was find the normal for one line. Using a loop, it can find the normals for all the segments.</p>
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; path.points.length - 1; i++) {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i &#x3C; path.points.length - 1; i++) {
let a = path.points[i];
let b = path.points[i + 1];
//{!1 .offset-top} Find the normal for each line segment.
let normalPoint = getNormalPoint(future, a, b);</pre>
</div>
<p>The next step is to test if the normal point is actually between points <code>a</code> and <code>b</code>. Since I know the path goes from left to right in this example, I can test if the <code>x</code> component of <code>normalPoint</code> is outside the <code>x</code> components of <code>a</code> and <code>b</code>.</p>
<pre class="codesplit" data-code-language="javascript"> if (normalPoint.x &#x3C; a.x || normalPoint.x > b.x) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> if (normalPoint.x &#x3C; a.x || normalPoint.x > b.x) {
//{!1} Use the end point of the segment
// as our normal point if we cant find one.
normalPoint = b.copy();
}</pre>
</div>
<p>If its not within the line segment, Ill just pretend the end point of that line segment is the normal. (You might also try the beginning point, depending on the particulars of your path.) This will ensure that the vehicle always stays on the path, even if it strays out of the bounds of the line segments themselves.</p>
<div data-type="exercise">
<h3 id="exercise-510">Exercise 5.10</h3>
@ -1077,7 +1098,8 @@ function draw() {
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> separate(vehicles) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> separate(vehicles) {
//{!1 .bold} Note how the desired separation is based
// on the Vehicles size.
let desiredSeparation = this.r * 2;
@ -1104,6 +1126,7 @@ function draw() {
this.applyForce(steer);
}
}</pre>
</div>
<p>The <code>separate()</code> method includes two extra improvements. First, the desired separation now depends on the size of the vehicle, as opposed to an arbitrary constant. This way, the separation behavior adapts dynamically to the individual characteristics of the vehicles. Second, the magnitude of the vector pointing away from a neighboring vehicle is set to be inversely proportional to the distance. This means that the closer the neighbor, the more the vehicle wants to flee, and vice versa.</p>
<div data-type="exercise">
<h3 id="exercise-512">Exercise 5.12</h3>
@ -1131,20 +1154,25 @@ function draw() {
</ul>
<p>Imagine the vehicles represent a school of fish. Although they want to avoid colliding with each other, their primary concern is seeking out a food source (the mouse). Being able to adjust the weights of the two steering forces is crucial to achieving this effect.</p>
<p>To begin, Ill add a method called <code>applyBehaviors()</code> to the <code>Vehicle</code> class to manage all of the behaviors.</p>
<pre class="codesplit" data-code-language="javascript">applyBehaviors(vehicles) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">applyBehaviors(vehicles) {
this.separate(vehicles);
this.seek(createVector(mouseX, mouseY));
}</pre>
</div>
<p>Here a single method takes care of calling the other methods that apply the forces—<code>separate()</code> and <code>seek()</code>. I could start mucking around within those methods to adjust the strength of the forces theyre calculating, but it might be easier to instead ask those methods to simply calculate and return the forces. Then I can adjust the forces strength and apply them to the vehicles acceleration within <code>applyBehaviors()</code>.</p>
<pre class="codesplit" data-code-language="javascript"> applyBehaviors(vehicles) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> applyBehaviors(vehicles) {
let separate = this.separate(vehicles);
let seek = this.seek(createVector(mouseX, mouseY));
//{!2} Apply the forces here since seek() and separate() no longer do so.
this.applyForce(separate);
this.applyForce(seek);
}</pre>
</div>
<p>Heres how this new approach changes the <code>seek()</code> method:</p>
<pre class="codesplit" data-code-language="javascript"> seek(target) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> seek(target) {
let desired = p5.Vector.sub(target, this.position);
desired.setMag(this.maxspeed);
let steer = p5.Vector.sub(desired, this.velocity);
@ -1155,6 +1183,7 @@ function draw() {
//{!1} Instead of applying the force, return the vector.
return steer;
}</pre>
</div>
<p>This is a subtle change, but incredibly important: it allows the strength of these forces to be weighted all in one place.</p>
<div data-type="example">
<h3 id="example-58-combining-steering-behaviors-seek-and-separate">Example 5.8: Combining Steering Behaviors (Seek and Separate)</h3>
@ -1163,7 +1192,8 @@ function draw() {
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">applyBehaviors(vehicles) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">applyBehaviors(vehicles) {
let separate = this.separate(vehicles);
let seek = this.seek(createVector(mouseX, mouseY));
//{!2 .bold} These values can be whatever you want them to be!
@ -1174,6 +1204,7 @@ function draw() {
this.applyForce(separate);
this.applyForce(seek);
}</pre>
</div>
<p>In this code, I use <code>mult()</code> to adjust the forces. By multiplying each force vector by a factor, its magnitude is scaled accordingly. These factors, in this case 1.5 for <code>separate</code> and 0.5 for <code>seek</code>, represent the weight assigned to each force. However, the weights dont have to be constants. Think about how they might vary dynamically based on conditions within the environment or properties of the vehicle. For example, what if the <code>seek</code> weight increases when the vehicle detects food nearby (imagine the vehicle as a creature with a <code>hunger</code> property) or the <code>separate</code> weight becomes larger if the vehicle enters a crowded area. This flexibility in adjusting the weights allows for more sophisticated and nuanced behaviors to emerge.</p>
<div data-type="exercise">
<h3 id="exercise-514">Exercise 5.14</h3>
@ -1546,7 +1577,8 @@ function draw() {
}</pre>
<p>Now I never make a new <code>p5.Vector</code> object once the sketch starts; I just use the same one over the whole length of the sketch!</p>
<p>Throughout the books examples, youll find lots of opportunities to reduce the number of temporary objects. (I told you, Im a major offender.) For example, heres a snippet from this chapters <code>seek()</code> method:</p>
<pre class="codesplit" data-code-language="javascript"> let desired = p5.Vector.sub(target, this.position);
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> let desired = p5.Vector.sub(target, this.position);
desired.normalize();
desired.mult(this.maxspeed);
@ -1554,8 +1586,10 @@ function draw() {
let steer = p5.Vector.sub(desired,this.velocity);
steer.limit(this.maxforce);
return steer;</pre>
</div>
<p>See how Ive made two vector objects? First, I calculate the desired velocity vector, then the steering force. To be more efficient, I could rewrite this to create only one vector.</p>
<pre class="codesplit" data-code-language="javascript"> let desired = p5.Vector.sub(target, this.position);
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> let desired = p5.Vector.sub(target, this.position);
desired.normalize();
desired.mult(this.maxspeed);
@ -1563,6 +1597,7 @@ function draw() {
desired.sub(this.velocity);
desired.limit(this.maxforce);
return desired;</pre>
</div>
<p>I dont actually need a second vector called <code>steer</code>. I can just reuse the <code>desired</code> vector object and turn it into the steering force by subtracting <code>velocity</code>. I didnt do this in my example because it makes the code more confusing to read. But in some cases, changes like this may improve efficiency.</p>
<div data-type="exercise">
<h3 id="exercise-521">Exercise 5.21</h3>

View file

@ -426,7 +426,8 @@ function setup() {
<p>Internally, when <code>Engine.update()</code> is called, Matter.js sweeps through the world, looks at all of the bodies in it, and figures out what to do with them. Just calling <code>Engine.update()</code> on its own moves the world forward with default settings. However, as with <code>Render</code>, these settings are customizable (and documented in the <a href="https://brm.io/matter-js/docs/classes/Engine.html#method_update">Matter.js reference</a>).</p>
<h3 id="step-2-link-every-box-object-with-a-matterjs-body">Step 2: Link Every Box Object with a Matter.js Body</h3>
<p>Ive set up my Matter.js world; now I need to link each <code>Box</code> object in my p5.js sketch with a body in that world. The original <code>Box</code> class includes variables for position and width. What I now want to say is: “I hereby relinquish command of this objects position to Matter.js. I no longer need to keep track of anything related to position, velocity, or acceleration. Instead, I only need to keep track of the existence of a Matter.js body and have faith that the physics engine will do the rest.”</p>
<pre class="codesplit" data-code-language="javascript">class Box {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Box {
constructor(x, y) {
this.w = 16;
//{!1} Instead of any of the usual variables, store a reference to a body.
@ -434,10 +435,12 @@ function setup() {
//{!1} Dont forget to add it to the world!
Composite.add(engine.world, this.body);
}</pre>
</div>
<p>I dont need <code>this.x</code> and <code>this.y</code> position variables anymore. The <code>Box</code> constructor takes in the starting <span data-type="equation">(x, y)</span> coordinates, passes them along to <code>Bodies.rectangle()</code> to create a new Matter.js body, and then forgets about them. As youll see, the body itself will keep track of its position behind the scenes. The body could technically keep track of its dimensions as well, but since Matter.js stores them as a list of vertices, its a bit more convenient to hold onto the width of the square in the <code>this.w</code> variable for when it comes time to draw the box.</p>
<h3 id="step-3-draw-the-box-body">Step 3: Draw the Box Body</h3>
<p>Almost there. Before I introduced Matter.js into the sketch, it was easy to draw the <code>Box</code>. The objects position was stored in variables <code>this.x</code> and <code>this.y</code>.</p>
<pre class="codesplit" data-code-language="javascript"> // Drawing the object using square()
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> // Drawing the object using square()
show() {
rectMode(CENTER);
fill(127);
@ -445,12 +448,18 @@ function setup() {
strokeWeight(2);
square(this.x, this.y, this.w);
}</pre>
</div>
<p>Now that Matter.js manages the objects position, I can no longer use my own <code>x</code> and <code>y</code>variables to draw the shape. But fear not! The <code>Box</code> object has a reference to the Matter.js body associated with it, and that body knows its own position. All I need to do is politely ask the body, “Pardon me, where are you located?”</p>
<pre class="codesplit" data-code-language="javascript">let position = this.body.position;</pre>
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">let position = this.body.position;</pre>
</div>
<p>Just knowing the position of a body isnt enough, however. The body is a square, so I also need to know its angle of rotation.</p>
<pre class="codesplit" data-code-language="javascript">let angle = this.body.angle;</pre>
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">let angle = this.body.angle;</pre>
</div>
<p>Once I have the position and angle, I can render the object using the native p5.js <code>translate()</code>, <code>rotate()</code>, and <code>square()</code> functions.</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
//{!2} I need the Bodys position and angle.
let position = this.body.position;
let angle = this.body.angle;
@ -466,11 +475,14 @@ function setup() {
square(0, 0, this.w);
pop();
}</pre>
</div>
<p>Its important to note here that if you delete a <code>Box</code> objects from the <code>boxes</code> array—perhaps when it moves outside the boundaries of the canvas or reaches the end of its lifespan, as demonstrated in Chapter 4—you must also explicitly remove the body associated with that <code>Box</code> object from the Matter.js world. This can be done with a <code>removeBody()</code> method on the <code>Box</code> class.</p>
<pre class="codesplit" data-code-language="javascript"> // This function removes a body from the Matter.js world.
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> // This function removes a body from the Matter.js world.
removeBody() {
Composite.remove(engine.world, this.body);
}</pre>
</div>
<p>In <code>draw()</code>, you would then iterate over the array in reverse, just as in the particle system examples, an call both <code>removeBody()</code> and <code>splice()</code> to delete the object from the Matter.js world and your array of boxes.</p>
<div data-type="exercise">
<h3 id="exercise-62">Exercise 6.2</h3>
@ -538,7 +550,8 @@ let trapezoid = Bodies.trapezoid(x, y, width, height, slope);</pre>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript">
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">
class CustomShape {
constructor(x, y) {
//{!6} An array of 5 vectors
@ -557,6 +570,7 @@ class CustomShape {
Body.setAngularVelocity(this.body, 0.1);
Composite.add(engine.world, this.body);
}</pre>
</div>
<p>When creating a custom polygon in Matter.js, you must remember two important details. First, the vertices must be specified in clockwise order. For instance, Figure 6.5 shows the five vertices used to create the bodies in Example 6.4. Notice how the example added them to the <code>vertices</code> array in clockwise order from the top-left.</p>
<figure>
<img src="images/06_libraries/06_libraries_6.png" alt="Figure 6.5: Vertices on a custom polygon oriented in clockwise order">
@ -568,7 +582,8 @@ class CustomShape {
<figcaption>Figure 6.6: A concave shape can be drawn with multiple convex shapes. </figcaption>
</figure>
<p>Since the shape is built out of custom vertices, you can use p5s <code>beginShape()</code>, <code>endShape()</code>, and <code>vertex()</code> functions when it comes time to actually draw the body. The <code>CustomShape</code> class <em>could</em> include an array to store the vertices pixel positions, relative to <span data-type="equation">(0, 0)</span>, for drawing purposes. However, its best to query Matter.js for the positions instead. This way theres no need to use <code>translate()</code> or <code>rotate()</code>, since the Matter.js body stores its vertices as absolute “world” positions.</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
fill(127);
stroke(0);
strokeWeight(2);
@ -581,6 +596,7 @@ class CustomShape {
// End the shape, closing it
endShape(CLOSE);
}</pre>
</div>
<p>The Matter.js body stores the array of its vertex positions inside a <code>vertices</code> property. Notice how I can then use a <code>for...of</code> loop to cycle through the vertices in between <code>beginShape()</code> and <code>endShape()</code>.</p>
<div data-type="exercise">
<h3 id="exercise-63">Exercise 6.3</h3>
@ -625,7 +641,8 @@ let part2 = Bodies.circle(x + offset, y, r);</pre>
<figcaption></figcaption>
</figure>
</div>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
// The angle comes from the compound body
let angle = this.body.angle;
@ -652,9 +669,11 @@ let part2 = Bodies.circle(x + offset, y, r);</pre>
circle(0, 0, this.r * 2);
pop();
}</pre>
</div>
<p>Before moving on, I want to stress the following: what you draw in your canvas window doesnt magically experience perfect physics just by the mere act of creating Matter.js bodies. The chapters examples have worked because Ive been carefully matching how each p5.js shape is drawn with how the geometry of each Matter.js body is defined. If you accidentally draw a shape differently, you wont get an error—not from p5.js or from Matter.js. However, your sketch will look odd, and the physics wont work correctly because the world youre seeing wont be aligned with the world as Matter.js understands it.</p>
<p>To illustrate, let me return to Example 6.5. A lollipop is a compound body consisting of two parts, a rectangle (<code>this.part1</code>) and a circle (<code>this.part2</code>). Ive been drawing each lollipop by getting the positions for the two parts separately: <code>this.part1.position</code> and <code>this.part2.position</code>. However, the overall compound body also has a position, <code>this.body.position</code>. It would be tempting to use that as the position for drawing the rectangle, and to figure out the circles position manually using an offset. After all, thats how I conceived of the compound shape to begin with (look back at Figure 6.8).</p>
<pre class="codesplit" data-code-language="javascript"> show() {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> show() {
let position = this.body.position;
let angle = this.body.angle;
push();
@ -664,6 +683,7 @@ let part2 = Bodies.circle(x + offset, y, r);</pre>
circle(0, this.h / 2, this.r * 2);
pop();
}</pre>
</div>
<p>Figure 6.9 shows the result of this change.</p>
<figure>
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/HWeBLcNuu" data-example-path="examples/06_libraries/6_5_compound_bodies_error"><img src="examples/06_libraries/6_5_compound_bodies_error/screenshot.png"></div>
@ -904,7 +924,8 @@ Composite.add(engine.world, mouseConstraint);</pre>
}</pre>
<p>Here, the <code>Box</code> classs <code>applyForce()</code> method receives a force vector and simply passes it along to Matter.jss <code>applyForce()</code> method to apply it to the corresponding body. The key difference with this approach is that Matter.js is a more sophisticated engine than the examples from Chapter 2. The earlier examples assumed that the force was always applied at the movers center. Here, the exact position on the body where the force is applied is specified. In this case, Ive just applied it to the center as before by asking the body for its position, but this could be adjusted. For example, imagine a scenario where a force pushes at the edge of a box, causing it to spin across the canvas, much like dice tumbling when thrown.</p>
<p>How can I bring forces into a Matter-driven sketch? Say I want to use a gravitational attraction force. Remember the code from Example 2.6 in the <code>Attractor</code> class?</p>
<pre class="codesplit" data-code-language="javascript"> attract(mover) {
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> attract(mover) {
let force = p5.Vector.sub(this.position, mover.position);
let distance = force.mag();
distance = constrain(distance, 5, 25);
@ -913,6 +934,7 @@ Composite.add(engine.world, mouseConstraint);</pre>
force.setMag(strength);
return force;
}</pre>
</div>
<p>I can rewrite the exact same method using <code>Matter.Vector</code> and incorporate it into a new <code>Attractor</code> class.</p>
<div data-type="example">
<h3 id="example-69-attraction-with-matterjs">Example 6.9 Attraction with Matter.js</h3>
@ -950,13 +972,15 @@ Composite.add(engine.world, mouseConstraint);</pre>
//{!1} Disabling default gravity
engine.gravity = Vector.create(0, 0);</pre>
<p>Second, bodies in Matter.js are created with a default air resistance that causes them to slow down as they move. I need to set this to <code>0</code> as well to simulate the bodies being in the “vacuum” of space.</p>
<pre class="codesplit" data-code-language="javascript">class Mover {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Mover {
constructor(x, y, radius) {
this.radius = radius;
//{!1} Disabling default air resistance
let options = { frictionAir: 0 };
this.body = Bodies.circle(x, y, this.radius, options);
}</pre>
</div>
<p>This is also a good time to revisit to the concept of mass. Although Im accessing the <code>mass</code> property of the body associated with the mover in the <code>attract()</code> method, I never explicitly set it. In Matter.js, the mass of a body is automatically calculated based on its size (area) and density. Larger bodies will therefore have a greater mass. To increase the mass relative to the size, you can try setting a <code>density</code> property in the <code>options</code> object (the default is <code>0.001</code>). For static bodies, such as the attractor, the mass is considered infinite. This is how the attractor stays locked in position despite the movers continuously knocking into it.</p>
<div data-type="exercise">
<h3 id="exercise-67">Exercise 6.7</h3>
@ -981,7 +1005,9 @@ function mousePressed() {
print("The mouse was pressed!");
}</pre>
<p>The global <code>mousePressed()</code> function in p5.js is executed whenever the mouse is pressed. This is known as a <strong>callback</strong>, a function thats “called back” at a later time when an event occurs. Matter.js collision events operate in a similar fashion. Instead of p5.js just knowing to look for a function called <code>mousePressed()</code> when a mouse event occurs, however, you have to explicitly define the name for a Matter.js collision callback.</p>
<pre class="codesplit" data-code-language="javascript">Matter.Events.on(engine, 'collisionStart', handleCollisions);</pre>
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript">Matter.Events.on(engine, 'collisionStart', handleCollisions);</pre>
</div>
<p>This code specifies that a function named <code>handleCollisions</code> should be executed whenever a collision between two bodies starts. There are also events for <code>'collisionActive'</code> (executed over and over for the duration of an ongoing collision) and <code>'collisionEnd'</code>(executed when two bodies stop colliding), but for a basic demonstration, knowing when the collision begins is more than adequate.</p>
<p>Much like <code>mousePressed()</code> is triggered when the mouse is pressed, <code>handleCollisions()</code> (or whatever you choose to name the callback function) is triggered when two shapes collide. It can be written as follows:</p>
<pre class="codesplit" data-code-language="javascript">function handleCollisions(event) {
@ -1003,7 +1029,8 @@ function mousePressed() {
<p><strong>Step 3: Bodies, could you tell me which particles youre associated with?</strong></p>
<p>Getting from the relevant Matter.js bodies to the <code>Particle</code> objects theyre associated with is a little harder. After all, Matter.js doesnt know anything about my code. Sure, its doing all sorts of stuff to keep track of the relationships between bodies and constraints, but its up to me to manage my own objects and their associations with Matter.js elements. That said, every Matter.js body is instantiated with an empty object— <code>{ }</code>— called <code>plugin</code>, ready to store any custom data about that body. I can link the body to a custom object (in this case, a <code>Particle</code>) by storing a reference to that object in the <code>plugin</code> property.</p>
<p>Take a look at the updated constructor in the <code>Particle</code> class where the body is made. Note how the body-making procedure has been expanded by one line of code to add a <code>particle</code> property inside <code>plugin</code>. Its important to make sure youre adding a new property to the existing <code>plugin</code> object (in this case, <code>plugin.particle = this</code>) rather than overwriting the <code>plugin</code>object itself (for example, with <code>plugin = this</code>). That latter could interfere with other features or customizations.</p>
<pre class="codesplit" data-code-language="javascript">class Particle {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Particle {
constructor(x, y, radius) {
this.radius = radius;
@ -1015,6 +1042,7 @@ function mousePressed() {
Composite.add(engine.world, this.body);
}</pre>
</div>
<p>Later, in the <code>handleCollision()</code> callback function, that <code>Particle</code> object can be accessed from the <code>Body</code> itself via the <code>plugin</code>.</p>
<div data-type="example">
<h3 id="example-610-collision-events">Example 6.10: Collision Events</h3>
@ -1221,16 +1249,22 @@ let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
// For the worlds gravity
let { GravityBehavior } = toxi.physics2d.behaviors;</pre>
<p>The first step is to create the world.</p>
<pre class="codesplit" data-code-language="javascript">let physics;
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">let physics;
function setup() {
// Creating a toxiclibs Verlet physics world
physics = new VerletPhysics2D();</pre>
</div>
<p>Once I have the <code>VerletPhysics</code> world, I can set global properties. For example, if I want a hard boundaries beyond which particles cant travel, I can provide a rectangular bounds.</p>
<pre class="codesplit" data-code-language="javascript"> physics.setWorldBounds(new Rect(0, 0, width, height));</pre>
<div class="snip-above snip-below">
<pre class="codesplit" data-code-language="javascript"> physics.setWorldBounds(new Rect(0, 0, width, height));</pre>
</div>
<p>In addition, I can add gravity with the <code>GravityBehavior</code> object. A gravity behavior requires a vector—how strong and in what direction is the gravity?</p>
<pre class="codesplit" data-code-language="javascript"> physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
<div class="snip-above">
<pre class="codesplit" data-code-language="javascript"> physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
}</pre>
</div>
<p>Finally, in order to calculate the physics of the world and move the worlds objects around, I have to call the worlds <code>update()</code> method. Typically this would happen once per frame in <code>draw()</code>.</p>
<pre class="codesplit" data-code-language="javascript">function draw() {
//{!1} This is the same as matter.js Engine.update()
@ -1608,7 +1642,8 @@ function draw() {
</figure>
<p>To create a force-directed graph, Ill first need a class to describe an individual node in the system. Because the term “node” is associated with the JavaScript framework Node.js, Ill stick with the term “particle” to avoid any confusion, and Ill continue using my <code>Particle</code> class from the earlier soft body examples.</p>
<p>Next, Ill encapsulate a list of <span data-type="equation">N</span> particles into a new class called <code>Cluster</code> that represents the graph as a whole. The particles all start out near the center of the canvas.</p>
<pre class="codesplit" data-code-language="javascript">class Cluster {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Cluster {
// A cluster is initialized with N nodes.
constructor(n, length) {
this.particles = [];
@ -1620,6 +1655,7 @@ function draw() {
this.particles.push(new Particle(x, y, 4));
}
}</pre>
</div>
<p>Lets assume the <code>Cluster</code> class also has a <code>show()</code> method to draw all the particles in the cluster, and that Ill create a new <code>Cluster</code> object in <code>setup()</code> and render it in <code>draw()</code>. If I ran the sketch as is, nothing would happen. Why? Because I have yet to implement the whole force-directed graph part! I need to connect every single node to every other node with a spring. This is somewhat similar to creating a soft body character, but rather than hand-craft a skeleton, I want to write an algorithm to make all the connections automatically.</p>
<p>What exactly do I mean by that? Say there are five <code>Particle</code> objects: 0, 1, 2, 3, and 4. Figure 6.19 illustrates the connections.</p>
<figure>
@ -1686,18 +1722,24 @@ function draw() {
<li><strong>Connections arent repeated in reverse.</strong> For example, if 0 is connected to 1, I dont need to explicitly say that 1 is also connected to 0. I already know it is by the definition of how a spring works!</li>
</ul>
<p>How to write the code to make these connections for <span data-type="equation">N</span> particles? Look at the left column of the table of connections. It reads: <span data-type="equation">000 11 2</span>. This tells me that I need to access each particle in the, list from <span data-type="equation">0</span> to <span data-type="equation">N-1</span>.</p>
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; this.particles.length - 1; i++) {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i &#x3C; this.particles.length - 1; i++) {
// Using the variable particle_i to store the particle reference
let particle_i = this.particles[i];</pre>
</div>
<p>Now look at the right column of the table. I need to connect node 0 to nodes 1, 2, and 3. For node 1: 2 and 3. For node 2, only 3. In general, for every node <code>i</code>, I need to iterate from <code>i + 1</code> all the way until the end of the array. Ill use the counter variable <code>j</code> for this purpose.</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} Look how j starts at i + 1.
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript"> //{!1} Look how j starts at i + 1.
for (let j = i + 1; j &#x3C; this.particles.length; j++) {
let particle_j = this.particles[j];</pre>
</div>
<p>For every pair of particles <code>i</code> and <code>j</code>, I can then create a spring. Ill go back to using <code>VerletSpring2D</code> directly, but you could also incorporate a custom <code>Spring</code> class.</p>
<pre class="codesplit" data-code-language="javascript"> //{!1} The spring connects particle i and j.
<div class="snip-above">
<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));
}
}</pre>
</div>
<p>Assuming those connections are made in the <code>Cluster</code> constructor, all thats left is to create the cluster in <code>setup()</code> and call <code>show()</code> in the <code>draw()</code> loop!</p>
<div data-type="example">
<h3 id="example-614-cluster">Example 6.14: Cluster</h3>

View file

@ -633,7 +633,8 @@ board = next;</pre>
<pre class="codesplit" data-code-language="javascript"> board[i][j] = new Cell(floor(random(2)));
</pre>
<p>Here, <code>Cell</code> is a new class that Ill write. What are the properties of a <code>Cell</code> object? In the Game of Life example, I might choose to create a cell that stores its position and size along with its state.</p>
<pre class="codesplit" data-code-language="javascript">class Cell {
<div class="snip-below">
<pre class="codesplit" data-code-language="javascript">class Cell {
constructor(state, x, y, w) {
// What is the cells state?
this.state = state;
@ -641,13 +642,14 @@ board = next;</pre>
// position and size
this.x = x;
this.y = y;
this.w = w;
...</pre>
this.w = w;</pre>
</div>
<p>In the non-OOP version, I used separate 2D arrays to keep track of the states for the current and next generation. By making a cell an object, however, each cell could keep track of both states by introducing a variable for the “previous” state.</p>
<pre class="codesplit" data-code-language="javascript">...
// What was its previous state?
<div class="snip-above">
<pre class="codesplit" data-code-language="javascript"> // What was its previous state?
this.previous = this.state;
}</pre>
</div>
<p>Suddenly, with these additional properties, how the cell is visualized can incorporate more information about what the state is doing. For example, what if each cell were colored based on whether its state has changed from one frame to another?</p>
<div data-type="example">
<h3 id="example-73-object-oriented-game-of-life">Example 7.3: Object-Oriented Game of Life</h3>
@ -673,7 +675,8 @@ board = next;</pre>
square(this.x, this.y, this.w);
}</pre>
<p>Not much else about the code (at least for my purposes here) has to change. The neighbors can still be counted the same way; the difference is that the neighbors <code>previous</code> states are counted and the cells new <code>state</code> property is updated. It also might be beneficial to encapsulate this logic into a <code>calculateState()</code> method that takes the <code>board</code> as an argument. Ill leave that as an exercise for the reader.</p>
<pre class="codesplit" data-code-language="javascript">
<div class="snip-above">
<pre class="codesplit" data-code-language="javascript">
for (let x = 1; x &#x3C; columns - 1; x++) {
for (let y = 1; y &#x3C; rows - 1; y++) {
let neighbors = 0;
@ -696,6 +699,7 @@ board = next;</pre>
}
}
}</pre>
</div>
<p>By transforming the cells into objects, numerous possibilities emerge for enhancing the cells properties and behaviors. For example, what if each cell had a <code>lifespan</code> property that increments with each cycle and influences its color or shape over time? Or imagine if a cell had a <code>terrain</code> property that could be <code>land</code>, <code>water</code>, <code>mountain</code>, or <code>forest</code>. How could a two-dimensional CA integrate into a tile-based strategy game or other context?</p>
<h2 id="variations-on-traditional-ca">Variations on Traditional CA</h2>
<p>Now that Ive covered the basic concepts, algorithms, and programming strategies behind the most famous 1D and 2D cellular automata, its time to think about how you might take this foundation of code and build on it, developing creative applications of CAs in your own work. In this section, Ill talk through some ideas for expanding the features of a CA. Example answers to these exercises can be found on the book website.</p>

View file

@ -210,9 +210,7 @@ function drawCircles(x, y, radius) {
line(x, y + 20, x + length / 3, y + 20);
//{!1 .bold} From 2/3rd to end
line(x + (2 * length) / 3, y + 20, x + length, y + 20);
}
</pre>
}</pre>
<p>Figure 8.10 shows the result.</p>
<figure>
<img src="images/08_fractals/08_fractals_12.png" alt="Figure 8.10: Two generations of lines drawn with the Cantor set rules.">

View file

@ -41,7 +41,7 @@
</ol>
<p>These features are illustrated in Figure 11.2.</p>
<figure>
<img src="images/11_nn_ga/11_nn_ga_3.jpg" alt="Figure 11.2: The Flappy Bird input features for a neural network">
<img src="images/11_nn_ga/11_nn_ga_3.png" alt="Figure 11.2: The Flappy Bird input features for a neural network">
<figcaption>Figure 11.2: The <em>Flappy Bird</em> input features for a neural network</figcaption>
</figure>
<p>The neural network will have five inputs, one for each feature, but what about the outputs? Is this a classification problem or a regression problem? This may seem like an odd question to ask in the context of a game like <em>Flappy Bird</em>, but its actually quite important and relates to how the game is controlled. Tapping the screen, pressing a button, or using keyboard controls are all examples of classification. After all, theres only a discrete set of choices: tap or not; press W, A, S, or D on the keyboard. On the other hand, using an analog controller like a joystick leans toward regression. A joystick can be tilted in varying degrees in any direction, translating to continuous output values for both its horizontal and vertical axes.</p>
@ -471,7 +471,8 @@ for (let i = 0; i &#x3C; lifeSpan; i++) {
// Use another output for the magnitude.
let magnitude = outputs[1].value * this.maxforce;
// Create and apply the force.
let force = p5.Vector.fromAngle(angle).setMag(magnitude);
let force = p5.Vector.fromAngle(angle)
force.setMag(magnitude);
this.applyForce(force);
}</pre>
<p>The neural network brain outputs two values: one for the angle of the vector and one for the magnitude. You might think to instead use these outputs for the vectors <span data-type="equation">x</span> and <span data-type="equation">y</span> components. The default output range for an ml5.js neural network is between 0 and 1, however, and I want the forces to be capable of pointing in both positive and negative directions. Mapping the first output to an angle by multiplying it by <code>TWO_PI</code> offers the full range.</p>
@ -652,10 +653,10 @@ function draw() {
<p>A common approach to simulating how a real-world creature (or robot) would have a limited awareness of its surroundings is to attach <strong>sensors</strong> to an agent. Think back to that mouse in the maze from the beginning of the chapter (hopefully its been thriving on the cheese its been getting as a reward), and now imagine it has to navigate the maze in the dark. Its whiskers might act as proximity sensors to detect walls and turns. The mouse whiskers cant “see” the entire maze, only the immediate surroundings. Another example of sensors is a bat using echolocation to navigate, or a car on a winding road that can only see whats projected in front of its headlights.</p>
<p>Id like to build on this idea of the whiskers (or more formally the <em>vibrissae</em>) found in mice, cats, and other mammals. In the real world, animals use their vibrissae to navigate and detect nearby objects, especially in dark or obscured environments (see Figure 11.4). How can I attach whisker-like sensors to my neuroevolutionary seeking creatures?</p>
<figure>
<img src="images/11_nn_ga/11_nn_ga_5.jpg" alt="Figure 11.4: Clawdius the Cat sensing his environment with his vibrissae">
<img src="images/11_nn_ga/11_nn_ga_5.png" alt="Figure 11.4: Clawdius the Cat sensing his environment with his vibrissae">
<figcaption>Figure 11.4: Clawdius the Cat sensing his environment with his vibrissae</figcaption>
</figure>
<p>Ill keep the generic class name <code>Creature</code> but think of them now as the circular “bloops” from Chapter 9, enhanced with whisker-like sensors that emanate from their center in all directions.</p>
<p>Ill keep the generic class name <code>Creature</code> but think of them now as the amoeba-like “bloops” from Chapter 9, enhanced with whisker-like sensors that emanate from their center in all directions.</p>
<pre class="codesplit" data-code-language="javascript">class Creature {
constructor(x, y) {
// The creature has a position and radius.
@ -664,13 +665,14 @@ function draw() {
// The creature has an array of sensors.
this.sensors = [];
// The creature has 9 sensors.
let totalSensors = 9;
// The creature has 8 sensors.
let totalSensors = 8;
for (let i = 0; i &#x3C; totalSensors; i++) {
// First, calculate a direction for the sensor .
let angle = map(i, 0, totalSensors, 0, TWO_PI);
// Create a vector a little bit longer than the radius as the sensor.
this.sensors[i] = p5.Vector.fromAngle(angle).mult(this.r * 1.5);
this.sensors[i] = p5.Vector.fromAngle(angle);
this.sensors[i].setMag(this.r * 1.5);
}
}
}</pre>
@ -692,9 +694,11 @@ function draw() {
<p>How can I determine if a creatures sensor is touching the food? One approach could be to use a technique called <strong>raycasting</strong>. This method is commonly employed in computer graphics to project straight lines (often representing beams of light) from an origin point in a scene to determine what objects they intersect with. Raycasting is useful for visibility and collision checks, exactly what Im doing here!</p>
<p>While raycasting would provide a robust solution, it requires more mathematics than Id like to delve into here. For those interested, an explanation and implementation are available in Coding Challenge #145 on <a href="http://thecodingtrain.com/">thecodingtrain.com</a>. For this example, Ill opt for a more straightforward approach and check whether the endpoint of a sensor lies inside the food circle (see Figure 11.5).</p>
<figure>
<img src="images/11_nn_ga/11_nn_ga_6.jpg" alt="Figure 11.5: The endpoint of a sensor is inside or outside of the food based on its distance to the center of the food.">
<img src="images/11_nn_ga/11_nn_ga_6.png" alt="Figure 11.5: The endpoint of a sensor is inside or outside of the food based on its distance to the center of the food.">
<figcaption>Figure 11.5: The endpoint of a sensor is inside or outside of the food based on its distance to the center of the food.</figcaption>
</figure>
<p></p>
<p></p>
<p>As I want the sensor to store a value for its sensing along with the sensing algorithm itself, it makes sense to encapsulate these elements into a <code>Sensor</code> class.</p>
<pre class="codesplit" data-code-language="javascript">class Sensor {
constructor(v) {
@ -804,7 +808,8 @@ class Creature {
let outputs = this.brain.predictSync(inputs);
let angle = outputs[0].value * TWO_PI;
let magnitude = outputs[1].value;
let force = p5.Vector.fromAngle(angle).setMag(magnitude);
let force = p5.Vector.fromAngle(angle)
force.setMag(magnitude);
this.applyForce(force);
}</pre>
<p>The logical next step might be incorporate all the usual parts of the genetic algorithm, writing a fitness function (how much food did each creature eat?) and performing selection after a fixed generational time period. But this is a great opportunity to revisit the principles of a “continuous” ecosystem and aim for a more sophisticated environment and set of potential behaviors for the creatures themselves. Instead of a fixed lifespan cycle for each generation, Ill bring back Chapter 9s concept of a <code>health</code> score for each creature. For every cycle through <code>draw()</code> that a creature lives, its health deteriorates a little bit.</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB