mirror of
https://github.com/nature-of-code/noc-book-2
synced 2024-11-17 07:49:05 +01:00
1617 lines
No EOL
123 KiB
HTML
1617 lines
No EOL
123 KiB
HTML
<section data-type="chapter">
|
||
<h1 id="chapter-6-physics-libraries">Chapter 6. Physics Libraries</h1>
|
||
<blockquote data-type="epigraph">
|
||
<p>“A library implies an act of faith/Which generations still in darkness hid/Sign in their night in witness of the dawn.”</p>
|
||
<p>— Victor Hugo</p>
|
||
</blockquote>
|
||
<p>Let’s review a quick summary of you’ve accomplished in the first five chapters of this book.</p>
|
||
<ol>
|
||
<li>Learned about concepts from the world of physics — What is a vector? What is a force? What is a wave?</li>
|
||
<li>Understood the math and algorithms behind such concepts.</li>
|
||
<li>Implemented the algorithms in p5.js with an object-oriented approach, culminating in building simulations of autonomous steering agents.</li>
|
||
</ol>
|
||
<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>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>
|
||
<p>Even so, anytime you add yet another JavaScript framework or library to a project, it introduces additional complexity and code. When is it worth it to have this additional overhead? If you just want to simulate a circle falling down a canvas gravity, do you really need to import an entire physics engine and learn its API? I would say, the answer is no as demonstrated in the first chapter of this book. Let’s consider another scenario. What if you want to have a hundred of those circles falling? And what if those circles aren’t circles at all, but irregularly shaped polygons? And what if you want these polygons to bounce off each other in a realistic manner when they collide?</p>
|
||
<p>You may have noticed that the first four chapters of this book, while covering motion and forces in detail, skipped over a rather important aspect of physics simulation—<em>collisions</em>. Let’s pretend for a moment that you aren’t reading a chapter about physics libraries and that I decided right now to cover how to handle collisions in a particle system. I’d have to introduce and cover two distinct algorithms that address these questions:</p>
|
||
<ol>
|
||
<li>How do I determine if two shapes are colliding (i.e. intersecting)? This is known as “collision detection.”</li>
|
||
<li>How do I determine the shapes’ velocity after the collision? This is known as “collision resolution.”</li>
|
||
</ol>
|
||
<p>If those shapes are rectangles or circles, question #1 isn’t too tough. You’ve likely encountered this before. For example, two circles are intersecting if the distance between them is less than the sum of their radii.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_1.png" alt="Figure 6.1: How to determine if two circles are colliding. Note “r” is short for “radius.”">
|
||
<figcaption>Figure 6.1: How to determine if two circles are colliding. Note “r” is short for “radius.”</figcaption>
|
||
</figure>
|
||
<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>
|
||
<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>
|
||
<img src="images/06_libraries/06_libraries_2.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<p>There, you’ll find a series of <code><script></code> tags inside the HTML tags <code><head></code> and <code></head></code>. This is how JavaScript libraries are referenced in a p5.js sketch. It’s no different than including <code>sketch.js</code> or <code>particle.js</code> in the page’s <code><body></code>. Only here, instead of keeping and editing a copy of the JavaScript code itself, a library is referenced with a url of a “CDN.” A “CDN” is a “content delivery network” or, more plainly, a server hosting files. For JS libraries that are used across hundreds of thousands of web pages with millions upon millions of users accessing these pages, they need to be pretty good at their job of serving up these libraries.</p>
|
||
<p>In the page, you’ll see the CDN for p5.js (it may be a later version by the time you are reading this!)</p>
|
||
<pre class="codesplit" data-code-language="html"><script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script></pre>
|
||
<p>To use matter.js, just add right below p5, a reference to its CDN.</p>
|
||
<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>
|
||
<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>
|
||
<li>Create all the objects in the world.</li>
|
||
</ol>
|
||
<p><strong><em>DRAW:</em></strong></p>
|
||
<ol>
|
||
<li>Calculate all the forces in the world.</li>
|
||
<li>Apply all the forces to the objects (F = M * A).</li>
|
||
<li>Update the positions of all the objects based on their acceleration.</li>
|
||
<li>Draw all of the objects.</li>
|
||
</ol>
|
||
<p>Great. Let’s rewrite this pseudocode as it will appear in the matter.js examples.</p>
|
||
<p><strong><em>SETUP:</em></strong></p>
|
||
<ol>
|
||
<li>Create all the objects in the world.</li>
|
||
</ol>
|
||
<p><strong><em>DRAW:</em></strong></p>
|
||
<ol>
|
||
<li>Draw all of the objects.</li>
|
||
</ol>
|
||
<p>This, of course, is the fantasy of a physics engine. I’ve eliminated all of those painful steps of figuring out how the objects are moving according to velocity and acceleration. Matter.js is going to take care of this for us! While there will be more details to reveal, the good news is that this does in fact accurately reflect the overall process. Let’s imagine Matter as a magic box.</p>
|
||
<p>In <code>setup()</code>, I’m going to say to Matter: “Hello there. Here are all of the things I want in my world.” In <code>draw()</code>, I’m going to politely ask Matter: “Oh, hello again. If it’s not too much trouble, I’d like to draw all of those things in my world. Could you tell me where they are?”</p>
|
||
<p>The bad news: it’s not as simple as the above explanation would lead you to believe. Making the stuff that goes in the matter.js world involves several steps related to how different kinds of shapes are built and configured. And it’s also necessary to learn to speak the language of matter.js in terms of how the various forces and other parameters of the world are configured. Here are the core concepts:</p>
|
||
<ol>
|
||
<li><strong><em>Engine</em></strong>: The engine is the entity that manages the physics simulation itself, it holds onto the “world” of the simulation as well as various properties about how the world is updated over time.</li>
|
||
<li><strong><em>Body</em></strong>: Serves as the primary element in the world. It has a position. It has a velocity. Sound familiar? The <code>Body</code> is essentially the class I’ve been building all throughout chapters 1-5. It also has geometry to define its shape. It’s important to note that “body” is a generic term used by physics engines to describe a “thing” in the world (similarly to the term “particle”) and is not related to an anthropomorphic body.</li>
|
||
<li><strong><em>Composite</em></strong>: A composite is a container that allows for the creation of complex entities (made up of multiple bodies.) The world itself is an example a Composite, and every Body created has be added to the world!</li>
|
||
<li><strong><em>Constraint</em></strong>: Acts as a connection between two bodies.</li>
|
||
</ol>
|
||
<p>In the next four sections, I am going to walk through each of the above elements in detail, building several examples along the way. But first there is one other important element to briefly discuss.</p>
|
||
<p>5. <strong><em>Vector</em></strong>: Describes a vector in a matter.js world.</p>
|
||
<p>And so here we are, arriving with trepidation at an unfortunate truth in the world of using physics libraries. Any physics simulation is going to involve the concept of a vector. This is the good part. After all, you just spent several chapters familiarizing yourself with what it means to describe motion and forces with vectors. There is nothing new to learn conceptually.</p>
|
||
<p>Now for the part that makes the single tear fall from my eye: you don’t get to use <code>p5.Vector</code>. It’s nice that p5.js has <code>createVector()</code>, but anytime you use a physics library you will likely discover that it includes its own vector implementation. This makes sense, after all; why should matter.js be expected to know about <code>p5.Vector</code>objects? In most cases, the physics engine implements its vector class in a specific way so that it is especially compatible with the rest of the library’s code. So while you won’t have to learn anything new conceptually, you do have to get used to some new naming conventions and syntax. Let’s quickly demonstrate a few of the basics in <code>Matter.Vector</code> as compared to those in <code>p5.Vector</code>.</p>
|
||
<p>How do you create a Vector?</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = createVector(1, -1);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = Matter.Vector.create(1, -1);</pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Let’s say you want to add two vectors together.</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
a.add(b);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = Matter.Vector.create(1, -1);
|
||
let b = Matter.Vector.create(3, 4);
|
||
Matter.Vector.add(a, b, a);</pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
let c = p5.Vector.add(a, b);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = Matter.Vector.create(1, -1);
|
||
let b = Matter.Vector.create(3, 4);
|
||
let c = Matter.Vector.add(a, b);</pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>How about if you want to scale the vector (multiply by a scalar value)?</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = createVector(1, -1);
|
||
v.mult(4);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = Matter.Vector.create(1, -1);
|
||
v = Matter.Vector.mult(4);</pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Magnitude and normalize?</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = createVector(3, 4);
|
||
let m = v.mag();
|
||
v.normalize();</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let v = Matter.Vector.create(3, 4);
|
||
let m = Matter.Vector.magnitude(v);
|
||
v = Matter.Vector.normalise(v);</pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<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>
|
||
<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;
|
||
|
||
// A reference to the matter physics engine
|
||
let engine;
|
||
|
||
function setup() {
|
||
createCanvas(640, 360);
|
||
// Create the Matter engine
|
||
engine = Engine.create();
|
||
}</pre>
|
||
<p>Notice how the very first line of code creates a variable called <code>Engine</code> and sets it equal to <code>Matter.Engine</code>. Here, I am deciding to point the single keyword <code>Engine</code> to the <code>Engine</code> class namespaced inside matter.js. I am making this decision because I know that I will not be using the word <code>Engine</code> for any other variables (nor does it conflict with something in p5.js) and want to be able to keep my code less verbose. I’ll be doing this with <code>Vector</code>, <code>Bodies</code>, <code>Composite</code>, and more as I continue to build the examples. (But while the linked source code will always include all the alias’s, I won’t always include them in the book text itself.)</p>
|
||
<div data-type="note">
|
||
<h3 id="----object-destructuring----object-destructuring-in-javascript-is-a-technique-to-extract-properties-from-an-object-and-assign-them-to-variables-in-the-case-of-matterjs-the-matterobject-contains-the-engine-property-an-alias-can-be-set-with-let-engine--matterengine-however-by-with-destructuring-the-enginecan-be-accessed-more-concisely-let--engine---matter-in-the-examples-in-this-chapter-i-will-create-multiple-aliases-with-this-methodology--">
|
||
Object Destructuring
|
||
Object destructuring in JavaScript is a technique to extract properties from an object and assign them to variables. In the case of matter.js, the Matterobject contains the Engine property. An alias can be set with let Engine = Matter.Engine, however, by with destructuring, the Enginecan be accessed more concisely: let { Engine } = Matter. In the examples in this chapter, I will create multiple aliases with this methodology.
|
||
</h3>
|
||
<pre class="codesplit" data-code-language="javascript">// Using Object Destructuring to extract aliases for Engine and Vector
|
||
let { Engine, Vector } = Matter;</pre>
|
||
</div>
|
||
<p>When you call <code>create()</code> on <code>Engine</code>, matter.js returns a new physics engine and world with a default gravity, a vector <span data-type="equation">(0,1)</span> pointing down, however, you can alter it by accessing the <code>gravity</code> variable itself.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> // Changing the engine's gravity to point horizontally
|
||
engine.gravity.x = 1;
|
||
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>
|
||
<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>
|
||
<pre class="codesplit" data-code-language="javascript">// Create a Matter.js Body with a rectangular shape
|
||
let box = Bodies.rectangle(x, y, w, h);</pre>
|
||
<p>Lucky us, the <code>rectangle()</code> function signature is exactly the same as p5.js’s <code>rect()</code> function! Only in this case, it’s not drawing a rectangle but rather is building the geometry for a <code>Body</code> object to store.</p>
|
||
<p>The body is now created and a reference is stored in the variable <code>box</code>. Bodies have many more properties that affect its motion. There is <em>density</em>, which ultimately determines that body’s mass. <em>Friction</em> and <em>restitution</em> (“bounciness”) affect how the body interacts when it comes into contact with other bodies. For most cases, the defaults are sufficient, but matter.js does allow you to specify these properties by passing through an additional argument in the form of a JavaScript object literal.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Specify additional properties of this matter.js Body
|
||
let options = {
|
||
friction: 0.5,
|
||
restitution: 0.8,
|
||
density: 0.002
|
||
}
|
||
let box = Matter.Bodies.rectangle(x, y, w, h, options);</pre>
|
||
<p>While the <code>options</code> variable is useful for configuring the the body, other initial conditions, such as linear or angular velocity, can be called via methods static methods of the <code>Matter.Body</code> class.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Setting arbitrary initial linear and angular velocity
|
||
const v = Vector.create(2, 0);
|
||
Body.setVelocity(box, v);
|
||
Body.setAngularVelocity(box, 0.1);</pre>
|
||
<p>Creating a body and storing it in a variable is not the last step, however. Any body must explicitly be added to the “world” in order for it to be simulated with physics. Remember, the physics world is a <code>Composite</code> object called <code>world</code> stored inside on the <code>engine</code> itself. The <code>box</code> can be added to that world with the static <code>add()</code> method</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Add the box object to the engine's world
|
||
Composite.add(engine.world, box);</pre>
|
||
<p>The above is easy to forget and a mistake that I’ve made on countless occasions. If you are ever wondering why one of your objects doesn’t appear or move along with the world’s physics, always check if you’ve actually added it to the world!</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-61">Exercise 6.1</h3>
|
||
<p>Knowing what you know about Matter.js so far, fill in the blank in the code below that demonstrates how to make a circular body.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Specify additional properties of this Matter.js Body
|
||
let options = {
|
||
friction: 0.5,
|
||
restitution: 0.8,
|
||
}
|
||
let ball = Bodies.circle(x, y, radius, options);</pre>
|
||
</div>
|
||
<h2 id="66-matterjs-render">6.6 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>
|
||
<p>The first step is to call <code>Matter.Render.create()</code> (or <code>Render.create()</code> assuming an alias). This function expects an object with the desired settings for the renderer, called <code>params</code> below.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Store the canvas in a variable
|
||
let canvas = createCanvas(640, 360);
|
||
|
||
// Configure the renderer
|
||
let params = {
|
||
canvas: canvas.elt,
|
||
engine: engine,
|
||
options = { width: width, height: height }
|
||
}
|
||
|
||
// Create the renderer
|
||
let render = Render.create(params);</pre>
|
||
<p>Notice how in the code above, I am storing a reference to the p5.js canvas in the variable <code>canvas</code>. This is necessary because I need to tell the renderer to draw into a specific canvas. Matter.js does not know about p5.js, so the canvas it is assigned is a native HTML5 canvas, stored inside the <code>elt</code> property of a p5.js canvas object. The engine is the <code>engine</code> previously created in Section 6.4. The matter.js default canvas dimensions are 800x600 so if I prefer a different size, I’ll need to configure an <code>options</code> property with <code>width</code> and <code>height</code>.</p>
|
||
<p>Creating the renderer is not enough, however, I also need to tell matter.js to run the renderer!</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Run the renderer!
|
||
Render.run(render);</pre>
|
||
<p>There is one final and critical step, however. Physics engines must be told to “step” forward in time. Since I am using the built-in renderer, I can also use the built-in “runner” which will run the engine at a default framerate of 60 frames per second. The runner is also customizable but the details are not terribly important since the goal here is to move towards using p5.js’s <code>draw()</code> loop (coming in the next section.)</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Run the engine!
|
||
Runner.run(engine);</pre>
|
||
<p>Here is all of the Matter.js code all together with an added <code>ground</code> object. Note the use of the option <code>{ isStatic: true }</code> in the creation of ground body to ensure that it remains in a fixed position. I’ll cover more details about static bodies in Section 6.x.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-61-matterjs-default-render-and-runner">Example 6.1: Matter.js Default Render and Runner</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/GXRa48IQO" data-example-path="examples/06_libraries/6_1_default_matter_js"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">// {!1} Note the use of aliases for all of the Matter.js classes needed for this sketch.
|
||
const { Engine, Bodies, Composite, Body, Vector } = Matter;
|
||
|
||
function setup() {
|
||
// Store a reference to the canvas
|
||
let canvas = createCanvas(640, 360);
|
||
|
||
// Create the physics engine
|
||
let engine = Engine.create();
|
||
|
||
// Create a renderer and assign to the p5.js canvas
|
||
const render = Matter.Render.create({
|
||
canvas: canvas.elt,
|
||
engine,
|
||
options: { width, height },
|
||
});
|
||
Matter.Render.run(render);
|
||
|
||
// Create a box with custom friction and restitution
|
||
let options = {
|
||
friction: 0.01,
|
||
restitution: 0.75,
|
||
};
|
||
let box = Bodies.rectangle(100, 100, 50, 50, options);
|
||
// Set initial velocity of box
|
||
Body.setVelocity(box, Vector.create(5, 0));
|
||
Body.setAngularVelocity(box, 0.1);
|
||
// Add box to the world
|
||
Composite.add(engine.world, box);
|
||
|
||
// Create a static body for the ground
|
||
let ground = Bodies.rectangle(width / 2, height - 5, width, 10, {
|
||
isStatic: true,
|
||
});
|
||
Composite.add(engine.world, ground);
|
||
|
||
// Create runner
|
||
let runner = Matter.Runner.create();
|
||
// Run the engine
|
||
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>
|
||
<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="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<p>This looks like any ol’ p5.js sketch. There is <code>sketch.js</code> as well as <code>box.js</code>. The <code>box.js</code> file is where I would typically write a class to describe a <code>Box</code> object, a rectangular body in the world.</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
constructor(x, y) {
|
||
//{!3} A box has an x,y position and a width.
|
||
this.x = x;
|
||
this.y = y;
|
||
this.w = 16;
|
||
}
|
||
|
||
show() {
|
||
//{!5} The box is drawn as a square().
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
square(this.x, this.y, this.w);
|
||
}
|
||
}</pre>
|
||
<p>Let’s now write <code>sketch.js</code> and create a new <code>Box</code> whenever the mouse is pressed and store all the <code>Box</code> objects in an array. (This is the same approach I took in the particle system examples from Chapter 4.)</p>
|
||
<div data-type="example">
|
||
<h3 id="example-62-a-comfortable-and-cozy-p5js-sketch-that-needs-a-little-matterjs">Example 6.2: A comfortable and cozy p5.js sketch that needs a little matter.js</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/D26YvXr_S" data-example-path="examples/06_libraries/6_2_boxes_exercise"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">//{!1} An array to store all Box objects
|
||
let boxes = [];
|
||
|
||
function setup() {
|
||
createCanvas(640, 360);
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
|
||
//{!3} When the mouse is pressed, add a new Box object.
|
||
if (mouseIsPressed) {
|
||
let box = new Box(mouseX, mouseY);
|
||
boxes.push(box);
|
||
}
|
||
|
||
//{!3} Display all the Box objects.
|
||
for (let i = 0; i < boxes.length; i++) {
|
||
boxes[i].show();
|
||
}
|
||
}</pre>
|
||
<p>Here’s a challenge. Take the above example verbatim, but instead of drawing fixed boxes on the screen, draw boxes that experience physics (with matter.js) as soon as they appear.</p>
|
||
<p>I’ll need two major steps to accomplish this goal.</p>
|
||
<h3 id="step-1-add-matterjs-to-the-p5js-sketch">Step 1: Add Matter.js to the p5.js sketch.</h3>
|
||
<p>This part is not too tough. I demonstrated all the elements needed for building a matter.js world in the previous sections. (And don’t forget, in order for this to work, make sure the library is imported in <code>index.html</code>!)</p>
|
||
<p>The first step is to add aliases for the necessary Matter classes and create an <code>engine</code> in <code>setup()</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!3} Aliases for Engine, Bodies, and Composite
|
||
let { Engine, Bodies, Composite } = Matter;
|
||
|
||
//{!1} The engine is now a global variables!
|
||
let engine;
|
||
|
||
function setup() {
|
||
//{!1} Create the engine.
|
||
engine = Engine.create();
|
||
}</pre>
|
||
<p>Then in <code>draw()</code>, I need to make sure to call one critical function: <code>Engine.update()</code>. This is instead of using the built-in matter.js <code>Runner</code>. After all, I already have a runner, the p5.js <code>draw()</code> loop! If I were to forget this function, no objects would animate. <code>Engine.update()</code> advances the physics world a step forward in time. Internally, matter.js sweeps through and looks at all of the bodies and figures out what to do with them. Just calling <code>update()</code> on its own moves the world forward with default settings; however, as with matter.js <code>Render</code> it is customizable (<a href="https://brm.io/matter-js/docs/classes/Engine.html#method_update">and documented in the matter.js reference</a>).</p>
|
||
<pre class="codesplit" data-code-language="javascript">function draw() {
|
||
//{!1} Step the engine forward in time!
|
||
Engine.update(engine);
|
||
}</pre>
|
||
<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>The original <code>Box</code> class includes variables for position and width. What I now want to say is:</p>
|
||
<p>“I hereby relinquish the command of this object’s position to matter.js. I no longer need to keep track of anything related to position, velocity, and acceleration. Instead, I only need to keep track of a <code>Body</code> and have faith that matter.js will do the rest.”</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
constructor(x, y) {
|
||
this.w = 16;
|
||
//{!1} Instead of any of the usual variables, a reference a body is stored.
|
||
this.body = Bodies.rectangle(x, y, this.w, this.w);
|
||
//{!1} Can't forget to add it to the world!
|
||
Composite.add(engine.world, this.body);
|
||
}</pre>
|
||
<p>I don’t need a position variable anymore since, as you’ll see, the body itself will keep track of its position. The body technically could keeps track of its dimensions as well, but since matter.js stores them as a list of vertices, it’s a bit more convenient to hold onto the width of the square in a variable for when it comes time to draw the box.</p>
|
||
<p>OK, almost there. Before I introduced matter.js, it was easy to draw the <code>Box</code>. The object’s 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()
|
||
show() {
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
square(this.x, this.y, this.w);
|
||
}</pre>
|
||
<p>Now that matter.js manages the object’s position, I can no longer use my own <code>x</code> or <code>y</code>variables to draw the shape. Not to fear! The <code>Box</code> object has a reference to the body associated with it. So all I need to do is politely ask the body, “Pardon me, where are you located?”</p>
|
||
<pre class="codesplit" data-code-language="javascript">let position = this.body.position;</pre>
|
||
<p>Just knowing the position of a body isn’t enough; I also need to know its angle of rotation.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let angle = this.body.angle;</pre>
|
||
<p>Once I have the position and angle, I can render the object using <code>translate()</code> and <code>rotate()</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> show() {
|
||
//{!2} I need the Body’s position and angle.
|
||
let position = this.body.position;
|
||
let angle = this.body.angle;
|
||
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
push();
|
||
//{!2} Using the position and angle to translate and rotate the square
|
||
translate(position.x, position.y);
|
||
rotate(angle);
|
||
square(0, 0, this.w);
|
||
pop();
|
||
}</pre>
|
||
<p>It’s important to note here that if you delete objects from the array (as demonstrated in Chapter 4) based on a “lifespan” or if they move outside the boundaries of the canvas, you also must also explicitly remove the body associated with your object from the matter.js world. This can be done with a <code>removeBody()</code> method.</p>
|
||
<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>
|
||
<p>In <code>draw()</code> just as in the particle system examples, you would then iterate over the array in reverse calling 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>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/oIZSHFXXk" data-example-path="examples/06_libraries/6_2_boxes_solved"><img src="examples/06_libraries/6_2_boxes_solved/screenshot.png"></div>
|
||
<figcaption>Drag the mouse to add boxes.</figcaption>
|
||
</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>
|
||
<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
|
||
let options = { isStatic: true };
|
||
let boundary = Bodies.rectangle(x, y, w, h, options);</pre>
|
||
<p>This can be incorporated into the solution to Exercise 6.2 by creating a <code>Boundary</code> class that wraps static body. Static bodies do not incorporate “material” properties like <code>restitution</code> or <code>friction</code> so make sure you are setting those in the dynamic bodies in your world. (This example is also enhanced with randomized dimensions for the boxes!)</p>
|
||
<div data-type="example">
|
||
<h3 id="example-63-falling-boxes-hitting-boundaries">Example 6.3: Falling boxes hitting boundaries</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/WSoUy03ph" data-example-path="examples/06_libraries/6_3_boxes_and_boundaries"><img src="examples/06_libraries/6_3_boxes_and_boundaries/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">class Boundary {
|
||
constructor(x, y, w, h) {
|
||
//{!4} A boundary is a simple rectangle with x, y, width, and height.
|
||
this.x = x;
|
||
this.y = y;
|
||
this.w = w;
|
||
this.h = h;
|
||
//{!1} Lock it in place by setting isStatic to true!
|
||
let options = { isStatic: true };
|
||
this.body = Bodies.rectangle(this.x, this.y, this.w, this.h, options);
|
||
Composite.add(engine.world, this.body);
|
||
}
|
||
|
||
//{!6} Since it can never move, show() can draw it
|
||
// the old-fashioned way, using the original
|
||
// variables. No need to query Matter.js.
|
||
show() {
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
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>
|
||
<p></p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_4.png" alt="Figure 6.2: A “compound” body made up of multiple shapes">
|
||
<figcaption>Figure 6.2: A “compound” body made up of multiple shapes</figcaption>
|
||
</figure>
|
||
<p>Now that I’ve demonstrated how easy it is to use a primitive shape like a rectangle or circle with matter.js, let’s imagine that you want to have a more complex form, such as the abstract character in Figure 6.2.</p>
|
||
<p>There are two strategies for making such complex forms. Beyond the four sides of a rectangle, there is a generic <code>Bodies.polygon()</code> function for any regular polygon (pentagon, hexagon, etc.). Additionally, there is <code>Bodies.trapezoid()</code> function for a quadrilateral with at least one parallel side.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// A regular hexagon (6 sided polygon)
|
||
let hexagon = Bodies.polygon(x, y, 6, radius);
|
||
|
||
// A trapezoid
|
||
let trapezoid = Bodies.trapezoid(x, y, width, height, slope);</pre>
|
||
<p>For any shape, however, <code>Bodies.fromVertices()</code>, is more general purpose since it builds the shape from an array of vectors, a series of connected vertices.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-64-polygon-shapes">Example 6.4: Polygon shapes</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/o3-Qpqu2i" data-example-path="examples/06_libraries/6_4_polygon_shapes"><img src="examples/06_libraries/6_4_polygon_shapes/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
class CustomShape {
|
||
constructor(x, y) {
|
||
//{!6} An array of 5 vectors
|
||
let vertices = [];
|
||
vertices[0] = Vector.create(-10, -10);
|
||
vertices[1] = Vector.create(20, -15);
|
||
vertices[2] = Vector.create(15, 0);
|
||
vertices[3] = Vector.create(0, 10);
|
||
vertices[4] = Vector.create(-20, 15);
|
||
|
||
//{!2} Making a body from shaped by the vertices
|
||
let options = { restitution: 1 };
|
||
this.body = Bodies.fromVertices(x, y, vertices, options);
|
||
|
||
Body.setVelocity(this.body, Vector.create(random(-5, 5), 0));
|
||
Body.setAngularVelocity(this.body, 0.1);
|
||
Composite.add(engine.world, this.body);
|
||
}</pre>
|
||
<p>When creating a custom polygon in Matter.js, you must remember two important details.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_5.png" alt="Figure 6.3: Vertices on a custom polygon oriented in clockwise order">
|
||
<figcaption>Figure 6.3: Vertices on a custom polygon oriented in clockwise order</figcaption>
|
||
</figure>
|
||
<ol>
|
||
<li><strong>Order of vertices!</strong> The vertices should be specified in clockwise order.</li>
|
||
<li><strong>Convex shapes only!</strong> A concave shape is one where the surface curves inward. Convex is the opposite (see illustration below). Note how in a convex shape every internal angle must be 180 degrees or less. Matter.js can in fact handle collisions for concave shapes, you just need to build one out of multiple convex shapes! (More about that in a moment.)</li>
|
||
</ol>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_6.png" alt="Figure 6.4: A concave shape can be drawn with multiple convex shapes. ">
|
||
<figcaption>Figure 6.4: A concave shape can be drawn with multiple convex shapes. </figcaption>
|
||
</figure>
|
||
<p>Now, when it comes time to draw the object, since the shape is built out of custom vertices the <code>beginShape()</code>, <code>endShape()</code>, and <code>vertex()</code> functions can be utilized. While the <code>CustomShape</code> class could include an array to store the pixel positions of the vertices for drawing, it’s best to query matter.js. Here, there’s no need use <code>translate()</code> or <code>rotate()</code> because the vertex positions are stored as the absolute “world” positions.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> show() {
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
// Start the shape
|
||
beginShape();
|
||
//{!3} Loop through the body vertices
|
||
for (let v of this.body.vertices) {
|
||
vertex(v.x, v.y);
|
||
}
|
||
// End the shape, closing it
|
||
endShape(CLOSE);
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-63">Exercise 6.3</h3>
|
||
<p>Using <code>Bodies.fromVertices()</code>, create your own polygon design (remember, it must be convex). Some possibilities below.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_7.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<p>A custom shape built from an array of vertices will get you pretty far. However, the convex shape requirement does limit the range of possibilities. The good news is that you can eliminate this restriction by creating a compound body made up of multiple shapes! How about creating a delicious lollipop with a thin rectangle and a circle on top?</p>
|
||
<p>Let’s start by creating two single bodies, one rectangle and one circle. Then with <code>Body.create()</code> the shapes can be joined via a <code>parts</code> array. Here’s how this looks in code:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Making the bodies
|
||
let part1 = Bodies.rectangle(x, y, w, h);
|
||
let part2 = Bodies.circle(x, y, r);
|
||
|
||
// Joining the two bodies together in an array
|
||
let body = Body.create({ parts: [part1, part2] });
|
||
|
||
// Adding the compound body to the world
|
||
Composite.add(engine.world, body);</pre>
|
||
<p>The above is a good start, but sadly, if you run it, you’ll see both shapes are created at the same <code>x</code> and <code>y</code> position.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_8.png" alt="Figure 6.5: A rectangle and a circle with the same x,y reference point.">
|
||
<figcaption>Figure 6.5: A rectangle and a circle with the same x,y reference point.</figcaption>
|
||
</figure>
|
||
<p>If I consider the center of the rectangle to be the reference point for the body, however, the center of the circle could be adjusted by an offset from the body’s center along the x-axis.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_9.png" alt="Figure 6.6: A circle placed relative to a rectangle with a horizontal offset">
|
||
<figcaption>Figure 6.6: A circle placed relative to a rectangle with a horizontal offset</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
let part1 = Bodies.rectangle(x, y, w, h);
|
||
//{!2} Subtracting an offset from the y position of the lollipop
|
||
let offset = w / 2;
|
||
let part2 = Bodies.circle(x + offset, y, r);</pre>
|
||
<p>Because there are two “parts” to the lollipop’s body, drawing is a bit trickier. There are multiple approaches I could take. For example, the vertices array could be used much like in Example 6.4. Since these parts are primitive shapes, I prefer to separately translate to each part’s position and rotate by the collective body’s angle.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-65-multiple-shapes-on-one-body">Example 6.5: Multiple shapes on one body</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/xxYF4I5bi" data-example-path="examples/06_libraries/6_5_compound_bodies"><img src="examples/06_libraries/6_5_compound_bodies/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
show() {
|
||
// The angle comes from the compound body
|
||
let angle = this.body.angle;
|
||
|
||
//{!2} Get the position for each part
|
||
let position1 = this.part1.position;
|
||
let position2 = this.part2.position;
|
||
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
|
||
// Translate and rotate the rectangle (part1)
|
||
push();
|
||
translate(position1.x, position1.y);
|
||
rotate(angle);
|
||
rectMode(CENTER);
|
||
rect(0, 0, this.w, this.h);
|
||
pop();
|
||
|
||
// Translate and rotate the circle (part2)
|
||
push();
|
||
translate(position2.x, position2.y);
|
||
rotate(angle);
|
||
circle(0, 0, this.r * 2);
|
||
pop();
|
||
}</pre>
|
||
<p>Finishing off this section, I want to stress the following: what you draw in your canvas window doesn’t magically experience perfect physics just by the mere act of creating matter.js bodies. These examples work because I carefully matched how the shapes are drawn with how the geometry of the bodies were defined. If you accidentally draw a shape differently, you won’t get an error, not from p5.js or from matter.js. However, your sketch will look odd and the physics won’t work correctly. For example, what if I accidentally use the body position (the “center of mass” between both the rectangle and circle) for the rectangle manually offset the circle when drawing the shapes?</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>
|
||
<figcaption>Figure 6.7 What happens the the shapes are drawn differently than how they were configured for matter.js.</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript"> let position = this.body.position;
|
||
let angle = this.body.angle;
|
||
translate(position.x, position.y);
|
||
rotate(angle);
|
||
rect(0, 0, this.w, this.h);
|
||
circle(0, this.h / 2, this.r * 2); </pre>
|
||
<p>While subtle here, the results end up like Figure 6.7, the collisions are off and the shapes overlap in odd ways. This is not because the physics is broken; it’s because I did not communicate properly with matter, either when adding bodies to the world or querying the world for bodies.</p>
|
||
<div data-type="exercise">
|
||
<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>
|
||
<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.">
|
||
<figcaption>Figure 6.8: A Constraint is a connection between two bodies at an anchor point for each body.</figcaption>
|
||
</figure>
|
||
<h3 id="distance-constraint">“Distance” Constraint</h3>
|
||
<p>Let’s begin with <code>Constraint</code>, a connection of fixed length between two bodies. A constraint is attached to each body at a specified anchor point (a point relative to the body’s center). Defining a constraint is similar to the methodology used to create bodies, only you need to have two bodies ready to go.</p>
|
||
<p>Let’s assume there are two <code>Particle</code> objects that each store a reference to a matter.js <code>Body</code> in a property called <code>body</code>. I’ll call them particles <code>particleA</code> and <code>particleB</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let particleA = new Particle();
|
||
let particleB = new Particle();</pre>
|
||
<p>Then <code>Constraint</code> is created with a list of options that determine its behavior:</p>
|
||
<ul>
|
||
<li><code>bodyA</code>: The first body that the constraint connects, establishing one end of the constraint.</li>
|
||
<li><code>bodyB</code>: The second body that the constraint connects, forming the other end.</li>
|
||
<li><code>pointA</code>: The position, relative to <code>bodyA</code>, where the constraint is anchored to the first body.</li>
|
||
<li><code>pointB</code>: The position, relative to <code>bodyB</code>, where the constraint is anchored to the second body.</li>
|
||
<li><code>length</code>: The resting or target length of the constraint, which it will attempt to maintain during the simulation.</li>
|
||
<li><code>stiffness</code>: A value between 0 and 1 that represents the rigidity of the constraint, with 1 being fully rigid and 0 being completely soft.</li>
|
||
</ul>
|
||
<pre class="codesplit" data-code-language="javascript">let options = {
|
||
bodyA: particleA.body,
|
||
bodyB: particleB.body,
|
||
pointA: Vector.create(0, 0),
|
||
pointB: Vector.create(0, 0),
|
||
length: 100,
|
||
stiffness: 0.5
|
||
}</pre>
|
||
<p>Technically, the only required options are <code>bodyA</code> and <code>bodyB</code>, the two bodies connected by the constraint. If you do not specify any additional options, matter.js will choose defaults for other properties. For example, it will use <code>(0, 0)</code> as the relative anchor points, set the <code>length</code> to the current distance between the bodies, and assign a default <code>stiffness</code> of <code>0.7</code>. Two other notable options not included above are <code>damping</code> and <code>angularStiffness</code>. <code>Damping</code>affects the constraint's resistance to motion, with higher values causing the constraint to lose energy more quickly. <code>AngularStiffness</code>controls the rigidity of the constraint's angular motion, with higher values resulting in less angular flexibility between the bodies.</p>
|
||
<p>Once the options are configured, the constraint be created. Once again, this assumes an additional <code>Constraint</code> alias equal to <code>Matter.Constraint</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let constraint = Constraint.create(options);
|
||
//{!1} Don't forget to add the constraint to the world!
|
||
Composite.add(engine.world, constraint);</pre>
|
||
<p>Constraints can be created anywhere in the sketch. Here’s an example of a class that represents a swinging Pendulum (mirroring example 3.x from Chapter 3).</p>
|
||
<div data-type="example">
|
||
<h3 id="example-66-matterjs-pendulum">Example 6.6: Matter.js Pendulum</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/YT6u0GqtH" data-example-path="examples/06_libraries/6_6_matter_js_pendulum"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">class Pendulum {
|
||
constructor(x, y, len) {
|
||
this.r = 12;
|
||
this.len = len;
|
||
|
||
//{!2} Create 2 bodies, one for the anchor and one for the bob.
|
||
// The anchor is static.
|
||
this.anchor = Bodies.circle(x, y, this.r, { isStatic: true });
|
||
this.bob = Bodies.circle(x + len, y, this.r, { restitution: 0.6 });
|
||
|
||
//{!6) Create a constraint connecting the anchor and bob
|
||
let options = {
|
||
bodyA: this.anchor,
|
||
bodyB: this.bob,
|
||
length: this.len,
|
||
};
|
||
this.arm = Matter.Constraint.create(options);
|
||
|
||
//{!3} Add all bodies and constraints to the world
|
||
Composite.add(engine.world, this.anchor);
|
||
Composite.add(engine.world, this.bob);
|
||
Composite.add(engine.world, this.arm);
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
|
||
//{!1} Draw a line representing the pendulum arm
|
||
line(this.anchor.position.x, this.anchor.position.y, this.bob.position.x, this.bob.position.y);
|
||
|
||
//{!6} Draw the anchor
|
||
push();
|
||
translate(this.anchor.position.x, this.anchor.position.y);
|
||
rotate(this.anchor.angle);
|
||
circle(0, 0, this.r * 2);
|
||
line(0, 0, this.r, 0);
|
||
pop();
|
||
|
||
//{!6} Draw the bob
|
||
push();
|
||
translate(this.bob.position.x, this.bob.position.y);
|
||
rotate(this.bob.angle);
|
||
circle(0, 0, this.r * 2);
|
||
line(0, 0, this.r, 0);
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<p>Example 6.6 uses a default <code>stiffness</code> of 0.7, if you try a lower value, the pendulum will appear more like a soft spring.</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/T-_eGHklR" data-example-path="examples/06_libraries/6_6_matter_js_spring"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-65">Exercise 6.5</h3>
|
||
<p>Create a simulation of a bridge by using constraints to connect a sequence of circles (or rectangles) as illustrated to the right. Assign a density of zero to lock the endpoints in place. Experiment with different values to make the bridge more or less “springy.” It should also be noted that the joints themselves have no physical geometry, so in order for your bridge not to have holes, spacing between the nodes will be important.</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/7U7yrrbNz" data-example-path="examples/06_libraries/exercise_6_5_bridge"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="revolute-constraint">“Revolute” Constraint</h3>
|
||
<figure class="half-width-right">
|
||
<img src="images/06_libraries/06_libraries_11.png" alt="Figure 6.9: A “Revolute” constraint is a connection between two bodies at a single “hinge” point.">
|
||
<figcaption>Figure 6.9: A “Revolute” constraint is a connection between two bodies at a single “hinge” point.</figcaption>
|
||
</figure>
|
||
<p>Another type of connection between bodies common to physics engine is a “revolute” joint. A revolute joint connects two bodies at a common anchor point, also known as a “hinge.” While there is no specific “revolute” constraint in matter.js, you can achieve the same effect by setting a constraint’s length to zero, allowing the bodies to rotate around a common anchor point.</p>
|
||
<p>The first step is to create the connected bodies. For a first example, I’d like to create a spinning rectangle (akin to a propellor or “windmill”) in a fixed position. For this case, I only need one body connected to a “point”. This simplifies things since I don’t have to worry about collisions between the two bodies connected at a hinge.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Create a body at a given x,y with a width and height
|
||
let body = Bodies.rectangle(x, y, w, h);
|
||
Composite.add(engine.world, body);</pre>
|
||
<p>Next the constraint is created. With a <code>length</code> of 0, it is required that the stiffness be set to <code>1</code>, otherwise the constraint may not be stable enough to keep the body connected at the anchor point.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// The constraint connects the body to a fixed x,y position with a length of 0 and stiffness of 1
|
||
let options = {
|
||
bodyA: this.body,
|
||
pointB: { x, y },
|
||
length: 0,
|
||
stiffness: 1,
|
||
};
|
||
// Create the constraint and add to the world
|
||
let constraint = Matter.Constraint.create(options);
|
||
Composite.add(engine.world, constraint);Step 4: Create the joint.</pre>
|
||
<p>Let’s take a look at all of these steps together in a class called <code>Windmill</code>. This sketch also includes a <code>Particle</code> class for dropping particles onto the windmill.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-67-spinning-windmill">Example 6.7: Spinning Windmill</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/D96JFWc3-" data-example-path="examples/06_libraries/6_7_windmill"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">class Windmill {
|
||
|
||
constructor(x, y, w, h) {
|
||
this.w = w;
|
||
this.h = h;
|
||
//{!2) The rotating body
|
||
this.body = Bodies.rectangle(x, y, w, h);
|
||
Composite.add(engine.world, this.body);
|
||
|
||
//{!7} The "revolute" constraint
|
||
let options = {
|
||
bodyA: this.body,
|
||
pointB: { x, y },
|
||
length: 0,
|
||
stiffness: 1,
|
||
};
|
||
this.constraint = Matter.Constraint.create(options);
|
||
Composite.add(engine.world, this.constraint);
|
||
}
|
||
|
||
show() {
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
push();
|
||
translate(this.body.position.x, this.body.position.y);
|
||
push();
|
||
rotate(this.body.angle);
|
||
rect(0, 0, this.w, this.h);
|
||
pop();
|
||
//{!1} Draw a stand for the windmill (not part of the physics)
|
||
line(0, 0, 0, height);
|
||
pop();
|
||
}
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-66">Exercise 6.6</h3>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_12.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<p>Use a revolute joint for the wheels of a car. [some creative prompts about the design of the vehicle/car]</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_13.png" alt=" ">
|
||
<figcaption> </figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="mouse-constraint">Mouse Constraint</h3>
|
||
<p>Before working with <code>MouseConstraint</code>, let's consider the following question: how do you set the position of a matter.js body to the mouse position? Why would you need a constraint? After all, you have access to the body's position; what's wrong with assigning the body's <code>position</code> to the mouse?</p>
|
||
<pre class="codesplit" data-code-language="javascript">body.position.x = mouseX;
|
||
body.position.y = mouseY;</pre>
|
||
<p>While this will in fact move the body, it will also have the unfortunate result of breaking the physics. Let’s imagine you built a teleportation machine that allows you to teleport from your bedroom to your kitchen (good for late-night snacking). Now, go ahead and rewrite Newton’s laws of motion to account for the possibility of teleportation. Not so easy, right? Matter.js has the same problem. If you manually assign the position of a body, it’s like saying “teleport that body” and matter.js no longer knows how to compute the physics properly. However, matter.js does allow you to tie a string around your waist and have a friend of yours to stand in the kitchen and drag you there. This is what the <code>MouseConstraint</code> does.</p>
|
||
<p>Imagine that the moment you click the mouse over a shape, it attaches to that body with a string, allowing you to drag it around until it is released. This works in a similar fashion as the “revolute” joint in that you can set the length of that string to zero, effectively moving a shape with the mouse.</p>
|
||
<p>Before you can attach the mouse, however, you need to create a matter.js <code>Mouse</code> object that listens for interactions to the p5.js canvas.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Aliases for matter.js Mouse and MouseConstraint
|
||
let { Mouse, MouseConstraint } = Matter;
|
||
// Need a reference to the p5.js canvas to listen for mouse
|
||
let canvas = createCanvas(640, 240);
|
||
// Create a Matter mouse attached to the native HTML5 canvas element
|
||
let mouse = Mouse.create(canvas.elt);</pre>
|
||
<p>Next, the <code>MouseConstraint</code> can be created with the <code>mouse</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let mouseConstraint = MouseConstraint.create(engine, { mouse });
|
||
Composite.add(engine.world, mouseConstraint);</pre>
|
||
<p>This will instantly allow you to interact with all matter.js bodies via the mouse!</p>
|
||
<p>You can also configure all the usual constraint variables by adding a <code>constraint</code> property to the options passed into <code>create()</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">mouse = Mouse.create(canvas.elt);
|
||
let options = {
|
||
mouse,
|
||
//{!1} Customize the constraint with additional properties
|
||
constraint: { stiffness: 0.7 }
|
||
};
|
||
mouseConstraint = MouseConstraint.create(engine, options);
|
||
Composite.add(engine.world, mouseConstraint);</pre>
|
||
<p>Following is an example that demonstrates a <code>MouseConstraint</code> with two <code>Box</code> objects. There are also static bodies acting as walls on the borders of the canvas.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-68-mouseconstraint-demonstration">Example 6.8: MouseConstraint demonstration</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/mTRKgn44p" data-example-path="examples/06_libraries/6_8_mouse_constraint"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2 id="610-bringing-it-all-back-home-to-forces">6.10 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
|
||
engine.gravity.x = 1;
|
||
engine.gravity.y = 0;</pre>
|
||
<p>If I want to use any of the Chapter 2 techniques with matter.js, I need look no further than the trusty <code>applyForce()</code> function. In Chapter 2 I wrote this function as part of the <code>Mover</code> class. It received a vector, divided it by mass, and accumulated it into the mover’s acceleration. With matter.js, the same function exists, but I don’t need to write it myself! It can be called with the static <code>Body.applyForce()</code>.</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
|
||
applyForce(force) {
|
||
//{!1} Calling Body's applyForce() function
|
||
Body.applyForce(this.body, this.body.position, force);
|
||
}
|
||
}</pre>
|
||
<p>Here, the function receives a force vector and passing it along to the matter.js <code>Body</code> object. The key difference is that matter.js is a more sophisticated engine than the examples from Chapter 2. The earlier examples assumed that the force was always applied at the mover’s center. Here, the exact position on the body where the force is applied is specified. In the above code, I’m just applying it to the center by asking the body for its position, but this could be adjusted.</p>
|
||
<p>Let’s say you wanted to use a gravitational attraction force. Remember the code from Chapter 2 in the <code>Attractor</code> class?</p>
|
||
<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);
|
||
|
||
let strength = (G * this.mass * mover.mass) / (distance * distance);
|
||
force.setMag(strength);
|
||
return force;
|
||
}</pre>
|
||
<p>I can rewrite the exact same function 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>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/16sblEvax" data-example-path="examples/06_libraries/6_9_matter_js_attraction"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript">class Attractor {
|
||
constructor(x, y) {
|
||
// {!3} The attractor is a static matter.js Body
|
||
this.radius = 32;
|
||
this.body = Bodies.circle(x, y, this.radius, { isStatic: true });
|
||
Composite.add(engine.world, this.body);
|
||
}
|
||
|
||
attract(mover) {
|
||
//{!2} The attract method now uses matter.js Vector functions
|
||
let force = Vector.sub(this.body.position, mover.body.position);
|
||
let distance = Vector.magnitude(force);
|
||
distance = constrain(distance, 5, 25);
|
||
|
||
//{!1} Using a small value for G to keep the system stable
|
||
let G = 0.02;
|
||
//{!1} While the mover's mass can be accessed because the attractor is a "static" body it's mass will be infinity so it is ignored here
|
||
let strength = (G * mover.body.mass) / (distance * distance);
|
||
//{!2} More matter.js Vector functions
|
||
force = Vector.normalise(force);
|
||
force = Vector.mult(force, strength);
|
||
return force;
|
||
}
|
||
}</pre>
|
||
</div>
|
||
<p>In addition to writing a custom <code>attract()</code> function for Example 6.9 there are two other key elements required for it to behave more like the example from Chapter 2.</p>
|
||
<ol>
|
||
<li>A matter.js <code>engine</code> has a default gravity pointing down, I’ve disabled it in <code>setup()</code> with a zero vector.</li>
|
||
</ol>
|
||
<pre class="codesplit" data-code-language="javascript">engine = Engine.create();
|
||
//{!1} Disabling default gravity
|
||
engine.gravity = Vector.create(0, 0);</pre>
|
||
<ol>
|
||
<li>Bodies in matter.js are created with a default “air resistance” causing them to slow down as they move. I’ve set that to zero to simulate the bodies being in the “vacuum” of space.</li>
|
||
</ol>
|
||
<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 data-type="exercise">
|
||
<h3 id="exercise-67">Exercise 6.7</h3>
|
||
<p>Incorporate <code>Body.applyForce()</code> into a new <code>spin()</code> function from Example 6.7’s <code>Windmill</code> class to simulate a motor continuously rotating the windmill.</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/cN6zF325F" data-example-path="examples/06_libraries/exercise_6_7_windmill_motor"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<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>
|
||
<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>
|
||
<p>Your first thoughts when considering an event during which two objects collide might be as follows: Well, if I know all the bodies in the system, and I know where they are all located, then I can just start comparing the positions, see which ones are intersecting, and determine that they’ve collided. That’s a nice thought, but hello??!? The whole point of using a physics engine like matter.js is that it will take care of that for us. If you are going to implement the computational geometry algorithms to test for intersection, then what you are doing is in fact re-implementing matter.js!</p>
|
||
<p>Of course, matter.js has thought of this problem before. It’s a pretty common one. Matter.js alerts you to moments of collision with an “event listener.” If you’ve worked mouse and keyboard interaction in p5.js, you are already familiar with an event listener.</p>
|
||
<p>Let’s consider the following:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// A mousePressed event you've probably written many times before.
|
||
function mousePressed() {
|
||
println("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 a a “callback”, a function that is “called back” at a later time when an event occurs. Collision events operate in a similar fashion, only instead of p5.js just knowing to look for a function called <code>mousePressed()</code> for a mouse event you have to explicitly define the name for a collision callback.</p>
|
||
<pre class="codesplit" data-code-language="javascript">Matter.Events.on(engine, 'collisionStart', handleCollisions);</pre>
|
||
<p>The above 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>So, much like <code>mousePressed()</code> is triggered when the mouse is pressed, <code>handleCollisions()</code> is triggered when two shapes collide and can be written as follows:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function handleCollisions(event) {
|
||
|
||
}</pre>
|
||
<p>Notice that the function above includes an argument named <code>event</code>. The <code>event</code> object includes all the data associated with a collision (or multiple collisions). Let’s assume a sketch with <code>Particle</code> objects that store a reference to a matter.js <code>Body</code>. Here is the process to follow:</p>
|
||
<h3 id="step-1-event-could-you-tell-me-what-two-things-collided">Step 1: Event, could you tell me what two things collided?</h3>
|
||
<p>Now, what has collided here? Matter.js detects collisions between a “pair” of bodies, these are the objects that have geometry. Any pair of colliding bodies will be in an array called <code>pairs</code> inside the <code>event</code> object. The following <code>for</code> loop iterating over all the pairs lives inside <code>handleCollisions()</code>).</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let pair of event.pairs) {
|
||
|
||
}
|
||
</pre>
|
||
<h3 id="step-2-pair-could-tell-me-which-two-bodies-are-included">Step 2: Pair, could tell me which two bodies are included?</h3>
|
||
<pre class="codesplit" data-code-language="javascript">for (let pair of event.pairs) {
|
||
let bodyA = pair.bodyA;
|
||
let bodyB = pair.bodyB;
|
||
}</pre>
|
||
<h3 id="step-3-bodies-could-you-tell-me-which-particles-you-are-associated-with">Step 3: Bodies, could you tell me which Particles you are associated with?</h3>
|
||
<p>OK, this is the harder part. After all, matter.js doesn’t know anything about my code. Sure, it is doing all sorts of stuff to keep track of the relationships between bodies and constraints, but it’s up to me to manage my own objects and their associations with matter.js elements. Luckily, matter.js provides a mechanism that allows <code>Body</code> to be attached to a custom object (in this case the <code>Particle</code>) with a <code>plugin</code> property.</p>
|
||
<p>Let’s take a look at the constructor in the <code>Particle</code> class where the body is made. Note how the body-making procedure is expanded by one line of code, noted below.</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle {
|
||
|
||
constructor(x, y, radius) {
|
||
this.radius = radius;
|
||
this.body = Bodies.circle(x, y, this.radius);
|
||
|
||
//{!1 .bold} "this" refers to this Particle object, telling the matter.js Body to store a
|
||
// reference to this Particle that can be accessed later
|
||
this.body.plugin.particle = this;
|
||
|
||
Composite.add(engine.world, this.body);
|
||
}</pre>
|
||
<p>Later, in the <code>handleCollision()</code> callback function, that <code>Particle</code> object can be access 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>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/3cREe4udP" data-example-path="examples/06_libraries/6_10_collision_events"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function handleCollisions(event) {
|
||
for (let pair of event.pairs) {
|
||
let bodyA = pair.bodyA;
|
||
let bodyB = pair.bodyB;
|
||
|
||
//{!2} Retrieving the Particle associated with the colliding Body via the plugin.
|
||
let particleA = bodyA.plugin.particle;
|
||
let particleB = bodyB.plugin.particle;
|
||
|
||
//{!4} If they are both particles, change their color!
|
||
if (particleA instanceof Particle && particleB instanceof Particle) {
|
||
particleA.change();
|
||
particleB.change();
|
||
}
|
||
}
|
||
}</pre>
|
||
<p>Now, in most cases, you cannot assume that the objects that collided are all <code>Particle</code> objects. After all, the particle might have collided with a <code>Boundary</code> object (or other kind of thing depending on what’s in your world). An object’s “type” can be checked with the <code>instance of</code> operator in JavaScript as demonstrated in the above example.</p>
|
||
<div data-type="exercise">
|
||
<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>
|
||
<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>
|
||
<p>Now that I’ve defined the derivative (differentiation), I can move on to the integral (integration) as the inverse of the derivative. In other words, the integral of an object’s velocity over time tells us the object’s new position when that time period ends. Position is the integral of velocity, and velocity is the integral of acceleration. Since the physics simulations in this book are founded on the notion of calculating acceleration based on forces, integration is needed to figure out where the object is after a certain period of time (like one cycle of the <code>draw()</code> loop!)</p>
|
||
<p>So you’ve been doing integration all along! It looks like this:</p>
|
||
<pre class="codesplit" data-code-language="javascript">velocity.add(acceleration);
|
||
location.add(velocity);</pre>
|
||
<p>The above methodology is known as Euler integration (named for the mathematician Leonhard Euler, pronounced “Oiler”) or the Euler method. It’s essentially the simplest form of integration and very easy to implement in code (see the two lines above!) However, it is not necessarily the most efficient form, nor is it close to being the most accurate. Why is Euler inaccurate? Let’s think about it this way. When you bounce on a pogo stick down the sidewalk, does the pogo stick sit in one position at time equals one second, then disappear and suddenly reappear in a new position at time equals two seconds, and do the same thing for three seconds, and four, and five? No, of course not. The pogo stick bounces continuously down the sidewalk. But what’s happening in a p5.js sketch? A circle is at one position at frame 0, another at frame 1, another at frame 2. Sure, at thirty frames per second, you see the illusion of motion. But a new position is only computed every <span data-type="equation">N</span> units of time, whereas the real world is perfectly continuous. This results in some inaccuracies, as shown in the diagram below:</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_14.png" alt="Figure 6.10: Euler approximation of a curve">
|
||
<figcaption>Figure 6.10: Euler approximation of a curve</figcaption>
|
||
</figure>
|
||
<p>The “real world” is the curve; Euler simulation is the series of line segments.</p>
|
||
<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>
|
||
<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>
|
||
</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>
|
||
<p>While <a href="http://thi.ng/umbrella">thi.ng/umbrella</a> may be a more modern and sophisticated approach, I find that toxiclibs remains a versatile tool, and I continue to use a version compatible with the latest version of Processing (4.1 as of the time of this writing) today. For this book, we should thank our lucky starts for toxiclibs.js, a JavaScript adaptation of the library, created by Kyle Phillips (”hapticdata”). I am only going to cover on a few examples related to Verlet physics, but toxiclibs.js includes a suite of other packages with functionality related to with, color, geometry, math, and more.</p>
|
||
<p>The examples I'm about to demonstrate in this chapter could also be created using matter.js, which I've spent the bulk of this chapter covering in depth. However, I've decided to move to toxiclibs for several reasons. The library holds a special place in my heart as a personal favorite, and is historically significant. I also believe that showing more than one physics library is important for providing a broader understanding of the tools and approaches available.</p>
|
||
<p>So how do you decide which library you should use? Matter.js or toxiclibs? Or something else? If you fall into one of the following two categories, your decision is a bit easier:</p>
|
||
<p><strong>1. My project involves collisions. I have circles, squares, and other strangely shaped objects that knock each other around and bounce off each other.</strong></p>
|
||
<p>In this case, you are going to want to use matter.js. toxiclibs.js does not handle “rigid body” collisions.</p>
|
||
<p><strong>2. My project involves lots of particles flying around the screen. Sometimes they attract each other. Sometimes they repel each other. And sometimes they are connected with springs.</strong></p>
|
||
<p>In this case, toxiclibs.js is likely your best choice. It is simpler to use in some ways than matter.js and particularly well suited to connected systems of particles. Toxiclibs.js is also high performance, due to the speed of the Verlet integration algorithm (not to mention the fact that it gets to ignore all of the collision geometry).</p>
|
||
<p>Here is a little chart that covers some of the features for each physics library.</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Feature</th>
|
||
<th>matter.js</th>
|
||
<th>toxiclibs VerletPhysics</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>rigid body collisions</td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
<td>No</td>
|
||
</tr>
|
||
<tr>
|
||
<td>3D physics</td>
|
||
<td>No</td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
</tr>
|
||
<tr>
|
||
<td>particle attraction and repulsion forces</td>
|
||
<td>No</td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
</tr>
|
||
<tr>
|
||
<td>spring connections (force-based)</td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
</tr>
|
||
<tr>
|
||
<td>constraints (general purpose connections)</td>
|
||
<td><strong><em>Yes</em></strong></td>
|
||
<td>No</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>All of the documentation and downloads for the library files can be found at the toxiclibs.js website: <a href="http://haptic-data.com/toxiclibsjs">haptic-data.com/toxiclibsjs</a>. For the examples in this book, I’ll be working with a hosted “CDN” version of the library referenced <code>index.html</code> in the same way I demonstrated in Section 6.2 with matter.js.</p>
|
||
<pre class="codesplit" data-code-language="html"><script src="<a href="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js">https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js</a>"></script></pre>
|
||
<p>The bulk of this chapter focused on the core elements of a matter.js sketch: world, vector, body, constraint. This will give you a head start on understanding toxiclibs.js, since it follows a similar structure.</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>matter.js</th>
|
||
<th>toxiclibs.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><code>World</code></td>
|
||
<td><code>VerletPhysics2D</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>Vector</code></td>
|
||
<td><code>Vec2D</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>Body</code></td>
|
||
<td><code>VerletParticle2D</code></td>
|
||
</tr>
|
||
<tr>
|
||
<td><code>Constraint</code></td>
|
||
<td><code>VerletSpring2D</code></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<h3 id="vectors-with-toxiclibsjs">Vectors with toxiclibs.js</h3>
|
||
<p>Here we go again. Remember all that time spent learning the ins and outs of the <code>p5.Vector</code> class? Then remember how when you got to matter.js, you have to revisit all those concepts with <code>Matter.Vector</code>? Well, it’s time to do it again. toxiclibs.js also includes its own vector classes, one for two dimensions and one for three: <code>Vec2D</code> and <code>Vec3D</code>. These are both found in the <code>toxi.geom</code> package and can be aliased in the same matter as <code>Vector</code> with matter.js.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let { Vec2D, Vec3D } = toxi.geom;</pre>
|
||
<p>Once again, toxiclibs.js vectors are the same conceptually, but have their own style and syntax new syntax. Let’s just review some of the basic vector math operations with p5.Vector now translated to <code>Vec2D</code> (I’m sticking with 2D to match the rest of this book, but I encourage you to explore 3D).</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>PVector</th>
|
||
<th>Vec2D</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
a.add(b);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = new Vec2D(1, -1);
|
||
let b = new Vec2D(3, 4);
|
||
a.addSelf(b);</pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
let c = p5.Vector.add(a, b);</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = new Vec2D(1, -1);
|
||
let b = new Vec2D(3, 4);
|
||
let c = a.add(b);</pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = createVector(1, -1);
|
||
let m = a.mag();
|
||
a.normalize();</pre>
|
||
</td>
|
||
<td>
|
||
<pre class="codesplit" data-code-language="javascript">let a = new Vec2D(1, -1);
|
||
let m = a.magnitude();
|
||
a.normalize();</pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<h3 id="building-the-toxiclibsjs-physics-world">Building the toxiclibs.js physics world</h3>
|
||
<p>The classes to describe the world and its particles and springs in toxiclibs.js are found in <code>toxi.physics2d.</code> I’m also going to use a <code>Rect</code> object (to describe a generic rectangle boundary) and <code>GravityBehavior</code> to apply a global gravity force to the world. Including <code>Vec2D</code>, I now have all the following classes.</p>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
// The necessary geometry classes for vectors and rectangles
|
||
let { Vec2D, Rect } = toxi.geom;
|
||
|
||
// Aliasing the important classes from toxi.physics2d
|
||
let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
|
||
|
||
// For the world's gravity
|
||
let { GravityBehavior } = toxi.physics2d.behaviors;</pre>
|
||
<p>The first step is to create a the world.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let physics;
|
||
|
||
function setup() {
|
||
// Creating a toxiclibs Verlet physics world
|
||
physics = new VerletPhysics2D();</pre>
|
||
<p>Once I have the <code>VerletPhysics</code> world, I can set global properties. For example, if I want a hard boundaries past which particles cannot travel, I can provide a rectangular bounds.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> physics.setWorldBounds(new Rect(0, 0, width, height));</pre>
|
||
<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)));
|
||
}</pre>
|
||
<p>Finally, in order to calculate the physics of the world and move the objects in the world, I have to call <code>update()</code>. 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()
|
||
physics.update();
|
||
}</pre>
|
||
<h2 id="614-particles-and-springs-in-toxiclibsjs">6.14 Particles and Springs in toxiclibs.js</h2>
|
||
<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) {
|
||
this.body = Bodies.circle(x, y, r);
|
||
}
|
||
}</pre>
|
||
<p>This technique is somewhat redundant since matter.js itself keeps track of the bodies in its world. However, it allows me to manage which body is which (and therefore how each body is drawn) without having to rely on iterating through the internal lists of matter.js</p>
|
||
<p>Let’s look at how you might take the same approach with the class <code>VerletParticle2D</code> in toxiclibs.js. I want to make my own <code>Particle</code> class so that I can draw the particles and include any custom properties. Following the design pattern above, I’d probably write the code as follows:</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle {
|
||
constructor(x, y, r) {
|
||
//{!1} A VerletParticle needs an initial x,y position but it has no geometry so the r is only used for drawing
|
||
this.particle = new VerletParticle2D(x, y);
|
||
this.r = r;
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
//{!1} When it comes time to draw the particle, the x,y is stored in this.particle
|
||
circle(this.particle.x, this.particle.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>Looking at the above, you’ll first notice that drawing the particle is as simple as grabbing the <code>x</code> and <code>y</code> properties and using them with <code>circle()</code>. Second, you might notice that this <code>Particle</code> class’s sole purpose is to store a reference to a <code>VerletParticle2D</code> object. This hints at something. Remember the discussion of inheritance back in Chapter 4: Particle Systems? What is a <code>Particle</code> object other than an “augmented” <code>VerletParticle2D</code>? Why bother making a Verlet particle inside a particle when I could simply <code>extend VerletParticle2D</code>?</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
//{!1} Calling super() with x,y so that the object is initialized properly
|
||
super(x, y);
|
||
//{!1} Adding a variable to track radius
|
||
this.r = r;
|
||
}
|
||
|
||
//{!1} Augmenting by adding a show() method.
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
//{!1} x and y from VerletParticle2D!
|
||
circle(this.x, this.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>Furthermore, you mind may be blown that the <code>VerletParticle2D</code> class is a subclass of <code>Vec2D</code>. So in addition to inheriting everything from <code>VerletParticle2D</code>, the <code>Particle</code> class above has actually inherited all of the<code>Vec2D</code> functions available as well!</p>
|
||
<p>I can now create particle objects.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let particle = new Particle(width/2, height/2, 8);</pre>
|
||
<p>Just creating a particle isn’t enough, however. I have to make sure to explicitly add the particle to the world with the <code>addParticle()</code> function.</p>
|
||
<pre class="codesplit" data-code-language="javascript">physics.addParticle(particle);</pre>
|
||
<p>Now, if you look at the documentation you’ll see that the <code>addParticle()</code> expects a <code>VerletParticle2D</code> object. How did it work to then pass into the function my own <code>Particle</code> object? Remember that tenet of object-oriented programming—polymorphism? Here, because the <code>Particle</code> class <strong><em>extends</em></strong> <code>VerletParticle2D</code>, I treat the particle in two different ways—as a <code>Particle</code> or as a <code>VerletParticle2D</code>. This is an incredibly powerful feature of object-oriented programming. If you build custom classes based that inherit from toxiclibs.js, you can use those objects in conjunction with all of the functions toxiclibs.js has to offer.</p>
|
||
<p>In addition to the <code>VerletParticle2D</code> class, toxiclibs.js has a set of classes that allow you to connect particles with spring forces. There are three types of springs in toxiclibs:</p>
|
||
<ul>
|
||
<li><code>VerletSpring2D</code>: This class creates a springy connection between two particles. A spring’s properties can be configured in such a way as to create a stiff stick-like connection or a highly elastic stretchy connection. A particle can also be locked so that only one end of the spring can move.</li>
|
||
<li><code>VerletConstrainedSpring2D</code>: A <code>VerletConstrainedSpring2D</code> object is a spring whose maximum distance can be limited. This can help the whole spring system achieve better stability.</li>
|
||
<li><code>VerletMinDistanceSpring2D</code>: A <code>VerletMinDistanceSpring2D</code> object is a spring that only enforces its rest length if the current distance is less than its rest length. This is handy if you want to ensure objects are at least a certain distance from each other, but don’t care if the distance is bigger than the enforced minimum.</li>
|
||
</ul>
|
||
<p>The inheritance and polymorphism technique employed in the previous section also proves to be useful when creating springs. A spring expects two particles when it is created. And again, because the <code>Particle</code> class <strong><em>extends </em></strong><code>VerletParticle2D</code>, a <code>VerletSpring2D</code> object will accept <code>Particle</code> objects passed into its constructor. Let’s take a look at some example code that assumes the existence of two particles <code>particle1</code> and <code>particle2</code> and creates a connection between them with a given rest length and strength.</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!1} What is the rest length of the spring?
|
||
let length = 80;
|
||
//{!1} How strong is the spring?
|
||
let strength = 0.01;
|
||
let spring = new VerletSpring2D(particle1, particle2, length, strength);</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>
|
||
<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) {
|
||
//{!4} First lock the particle, then set the x and y, then unlock() it.
|
||
particle1.lock();
|
||
particle1.x = mouseX;
|
||
particle1.y = mouseY;
|
||
particle1.unlock();
|
||
}</pre>
|
||
<p>And now I’m ready to put all of these elements together in a simple example that connects two particles with a spring. One particle is locked in place, and the other can be moved by dragging the mouse. Note that this example is virtually identical to Example 3.x: Springy Pendulum.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-611-simple-spring-with-toxiclibs">Example 6.11: Simple Spring with toxiclibs</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/CSzXIfoWH" data-example-path="examples/06_libraries/6_11_simple_spring_with_toxiclibs"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let { Vec2D, Rect } = toxi.geom;
|
||
let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
|
||
let { GravityBehavior } = toxi.physics2d.behaviors;
|
||
|
||
let physics;
|
||
let particle1, particle2;
|
||
|
||
function setup() {
|
||
createCanvas(640, 240);
|
||
|
||
// Creating a toxiclibs Verlet physics world
|
||
physics = new VerletPhysics2D();
|
||
physics.setWorldBounds(new Rect(0, 0, width, height));
|
||
physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
|
||
|
||
//{!1} What is the rest length of the spring?
|
||
let length = 120;
|
||
|
||
// Creating two Particles
|
||
particle1 = new Particle(width / 2, 0, 8);
|
||
particle2 = new Particle(width / 2 + length, 0, 8);
|
||
// Locking Particle 1 in place
|
||
particle1.lock();
|
||
|
||
// Creating one Spring
|
||
let spring = new VerletSpring2D(particle1, particle2, length, 0.01);
|
||
|
||
//{!3} Must add everything to the world
|
||
physics.addParticle(particle1);
|
||
physics.addParticle(particle2);
|
||
physics.addSpring(spring);
|
||
}
|
||
|
||
function draw() {
|
||
//{!1} Must update the physics
|
||
physics.update();
|
||
|
||
background(255);
|
||
|
||
//{!4} Drawing everything
|
||
stroke(0);
|
||
line(particle1.x, particle1.y, particle2.x, particle2.y);
|
||
particle1.show();
|
||
particle2.show();
|
||
|
||
//{!4} Move particle according to mouse
|
||
if (mouseIsPressed) {
|
||
particle2.lock();
|
||
particle2.x = mouseX;
|
||
particle2.y = mouseY;
|
||
particle2.unlock();
|
||
}
|
||
}
|
||
|
||
// How cute is this simple Particle class?!
|
||
class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
super(x, y);
|
||
this.r = r;
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
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>
|
||
<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">
|
||
<figcaption>Figure 6.11: Soft body simulation designs</figcaption>
|
||
</figure>
|
||
<p>Let's begin by simulating a "soft pendulum"—a bob hanging from a string, instead of a rigid arm—and use the design from Figure 6.14 as the basis. Toxiclibs.js does offer a convenient <code><strong>ParticleString2D</strong></code> class that creates a string of connected particles in a single constructor call. However, for demonstration purposes, I will create my own array using a <code><strong>for</strong></code> loop with the goal of giving you a deeper understanding of the system and enabling you to create your own custom designs beyond a single string in the future.</p>
|
||
<p>First, I’l need an array of particles (let’s use the same <code>Particle</code> class built in the previous example).</p>
|
||
<pre class="codesplit" data-code-language="javascript">let particles = [];</pre>
|
||
<p>Now, let’s say I want to have 20 particles, all spaced 10 pixels apart.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_16.png" alt="Figure 6.12: Twenty particles all spaced 10 pixels apart">
|
||
<figcaption>Figure 6.12: Twenty particles all spaced 10 pixels apart</figcaption>
|
||
</figure>
|
||
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i < total; i++) {
|
||
//{!1} Spacing them out along the x-axis
|
||
let particle = new Particle(i * length, 10, 4);
|
||
//{!1} Add the particle to the physics world.
|
||
physics.addParticle(particle);
|
||
//{!1} Add the particle to the array.
|
||
particles.push(particle);
|
||
}</pre>
|
||
<p>I can loop from <code>i</code> equals 0 all the way up to <code>total</code>, with each particle’s <code>y</code> position set to <code>i * 10</code> so that the first particle is at <span data-type="equation">(0,10)</span>, the second at <span data-type="equation">(0,20)</span>, the third at <span data-type="equation">(0,30)</span>, and so on.</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i < total; i++) {
|
||
//{!1} Spacing them out along the x-axis
|
||
let particle = new Particle(i * length, 10, 4);
|
||
//{!1} Add the particle to the physics world.
|
||
physics.addParticle(particle);
|
||
//{!1} Add the particle to the array.
|
||
particles.push(particle);
|
||
}
|
||
</pre>
|
||
<p>Even though it’s redundant, I’m going to add the particle to both the toxiclibs.js <code>physics</code> world and to the <code>particles</code> array. This will help me to manage the sketch (especially for the case where they might be more than one “string” of particles.)</p>
|
||
<p>Now for the fun part: It’s time to connect all the particles. Particle index 0 will be connected to particle 1, particle 1 to particle 2, 3 to 4, 4 to 5, and so on.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_17.png" alt="Figure 6.13: Each particle is connected to the next particle in the array">
|
||
<figcaption>Figure 6.13: Each particle is connected to the next particle in the array</figcaption>
|
||
</figure>
|
||
<p>In other words, particle <code>i</code> needs to be connected to particle <code>i+1</code> (except for when <code>i</code> represents the last element of the array zero).</p>
|
||
<pre class="codesplit" data-code-language="javascript">// The loop stops before the last element (total - 1)
|
||
for (let i = 0; i < total - 1; i++) {
|
||
// The spring connects particle i to i+1
|
||
let spring = new VerletSpring2D(particles[i], particles[i + 1], spacing, 0.01);
|
||
//{!1} The spring must also be added to the world
|
||
physics.addSpring(spring);
|
||
}</pre>
|
||
<p>Now, what if I want the string to hang from a fixed point? I can lock one of the particles—the first, the last, the middle one? Let’s go with the first.</p>
|
||
<pre class="codesplit" data-code-language="javascript">particles[0].lock();</pre>
|
||
<p>And if I want to draw all the particles as being connected with a line, along with a circle for the last particle (the “bob”), I can use <code>beginShape()</code>, <code>endShape()</code>, and <code>vertex()</code>, accessing the particle positions from the array.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-612-soft-swinging-pendulum">Example 6.12: Soft swinging pendulum</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/cIygo3QeX" data-example-path="examples/06_libraries/6_12_soft_string"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">function draw() {
|
||
physics.update();
|
||
|
||
background(255);
|
||
|
||
stroke(0);
|
||
noFill();
|
||
beginShape();
|
||
for (let particle of particles) {
|
||
//{!1} Each particle represents one vertex in the string.
|
||
vertex(particle.x, particle.y);
|
||
}
|
||
endShape();
|
||
|
||
//{!1} This draws the last particle as a circle.
|
||
particles[particles.length - 1].show();
|
||
}</pre>
|
||
<p>The full code available on the book’s website also demonstrates how to drag the “bob” particle with the mouse.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-610">Exercise 6.10</h3>
|
||
<p>Create a hanging cloth simulation using the technique above, but connect all the particles with their neighbors vertically and horizontally.</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/x3FXo0wNZ" data-example-path="examples/06_libraries/exercise_6_13_cloth_simulation"></div>
|
||
<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>
|
||
<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>
|
||
</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>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
super(x, y);
|
||
this.r = r;
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
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>
|
||
<pre class="codesplit" data-code-language="javascript">class Cluster {
|
||
// A cluster is initialized with N nodes spaced out by length
|
||
constructor(n, length) {
|
||
this.particles = [];
|
||
for (let i = 0; i < n; i++) {
|
||
//[offset-down] Here’s a funny little detail. The physics will misbehave
|
||
// if all the particles are created in exactly the same position.
|
||
let x = width / 2 + random(-1, 1);
|
||
let y = height / 2 + random(-1, 1);
|
||
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>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_19.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<table>
|
||
<tbody>
|
||
<tr>
|
||
<td>0</td>
|
||
<td>connected to</td>
|
||
<td>1</td>
|
||
</tr>
|
||
<tr>
|
||
<td>0</td>
|
||
<td>connected to</td>
|
||
<td>2</td>
|
||
</tr>
|
||
<tr>
|
||
<td>0</td>
|
||
<td>connected to</td>
|
||
<td>3</td>
|
||
</tr>
|
||
<tr>
|
||
<td>1</td>
|
||
<td>connected to</td>
|
||
<td>2</td>
|
||
</tr>
|
||
<tr>
|
||
<td>1</td>
|
||
<td>connected to</td>
|
||
<td>3</td>
|
||
</tr>
|
||
<tr>
|
||
<td>2</td>
|
||
<td>connected to</td>
|
||
<td>3</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Notice two important details about the list of connections.</p>
|
||
<ul>
|
||
<li><strong><em>No particle is connected to itself.</em></strong> 0 is not connected to 0, 1 is not connected to 1, and so on.</li>
|
||
<li><strong><em>Connections are not repeated in reverse.</em></strong> In other words, if 0 is connected to 1, I don’t need to explicitly say that 1 is connected to 0 because, well, it is by the definition of how a spring works!</li>
|
||
</ul>
|
||
<p>So how to write the code to make these connections for <span data-type="equation">N</span> particles?</p>
|
||
<p>Look at the left column of the table above. It reads: <span data-type="equation">000 11 2</span>. This tells me that I 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 < this.particles.length - 1; i++) {
|
||
// Using the variable particle_i to store the particle reference
|
||
let particle_i = this.particles[i];</pre>
|
||
<p>Now, I know we need to connect node 0 to nodes 1, 2, and 3. For node 1: 2 and 3. For node 2, only 3! So for every node <code>i</code>, I can iterate from <code>i+1</code> all the way until the end of the array. I’ll use the counter variable <code>j</code> for this purpose.</p>
|
||
<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>
|
||
<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>
|
||
<p>Assuming those connections are made in the <code>Cluster</code> constructor, all that is left to do is create cluster in <code>setup()</code> and call <code>show()</code> in <code>draw()</code>!</p>
|
||
<div data-type="example">
|
||
<h3 id="example-613-cluster">Example 6.13: Cluster</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/_tbPaFqVX" data-example-path="examples/06_libraries/6_13_force_directed_graph"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
|
||
|
||
let physics;
|
||
let cluster;
|
||
|
||
function setup() {
|
||
createCanvas(640, 240);
|
||
physics = new VerletPhysics2D();
|
||
//{!1} Create a random cluster
|
||
cluster = new Cluster(int(random(2, 20)), random(10, height / 2));
|
||
}
|
||
|
||
function draw() {
|
||
physics.update();
|
||
background(255);
|
||
//{!1} Draw cluster
|
||
cluster.show();
|
||
}</pre>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-611">Exercise 6.11</h3>
|
||
<p>Design a cluster-like structure as a skeleton for a cute, cuddly, squishy creature. Add gravity and mouse interaction.</p>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-612">Exercise 6.12</h3>
|
||
<p>Expand the force-directed graph to have more than one <code>Cluster</code> object. Use a <code>VerletMinDistanceSpring2D</code> object to connect cluster to cluster. What kind of data might you visualize with this technique?</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/WexUSx7dN" data-example-path="examples/06_libraries/exercise_6_15_force_directed_graph"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2 id="618-attraction-and-repulsion-behaviors">6.18 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>
|
||
<pre class="codesplit" data-code-language="javascript">let particle = new Particle(320, 120);</pre>
|
||
<p>For any <code>Particle</code> object, <code>AttractionBehavior</code> can be created and associated with that particle.</p>
|
||
<pre class="codesplit" data-code-language="javascript">let distance = 20;
|
||
let strength = 0.1;
|
||
let behavior = new AttractionBehavior(particle, distance, strength);</pre>
|
||
<p>Notice how the behavior is created with two arguments—<code>distance</code> and <code>strength</code>. The distance specifies the range within which the behavior will be applied. In the above scenario, only other particles within twenty pixels will experience the attraction force. The strength, of course, specifies how strong the force is.</p>
|
||
<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>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
super(x, y);
|
||
this.r = r;
|
||
//[offset-down] Every time a Particle is made, an AttractionBehavior is
|
||
// generated and added to the physics world.
|
||
// Note that when the strength
|
||
// is negative, it’s a repulsive force!
|
||
physics.addBehavior(new AttractionBehavior(this, r * 4, -1));
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
circle(this.x, this.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>I can now remake the attraction example from Chapter 2 with a single <code>Attractor</code> object that exerts an attraction behavior anywhere on the canvas (with a distance threshold of <code>width</code>).</p>
|
||
<div data-type="example">
|
||
<h3 id="example-614-attraction-and-repulsion-behaviors">Example 6.14: Attraction (and Repulsion) Behaviors</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/tjIs8XaXP" data-example-path="examples/06_libraries/6_14_attraction_behaviors"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">class Attractor extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
super(x, y);
|
||
this.r = r;
|
||
// Attracts all particles always
|
||
physics.addBehavior(new AttractionBehavior(this, width, 0.1));
|
||
// Repels particles that come within its radius
|
||
physics.addBehavior(new AttractionBehavior(this, this.r, -10));
|
||
// This is a nice improvement where the attractor adds itself to the physics
|
||
physics.addParticle(this);
|
||
}
|
||
|
||
show() {
|
||
fill(0);
|
||
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>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-613">Exercise 6.13</h3>
|
||
<p>Use <code>AttractionBehavior</code> in conjunction with spring forces.</p>
|
||
</div>
|
||
<div data-type="project">
|
||
<h3 id="the-ecosystem-project-5">The Ecosystem Project</h3>
|
||
<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 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>
|
||
</section> |