mirror of
https://github.com/nature-of-code/noc-book-2
synced 2024-11-17 07:49:05 +01:00
1760 lines
No EOL
148 KiB
HTML
1760 lines
No EOL
148 KiB
HTML
<section data-type="chapter">
|
||
<h1 id="chapter-6-physics-libraries">Chapter 6. Physics Libraries</h1>
|
||
<div class="chapter-opening-quote">
|
||
<blockquote data-type="epigraph">
|
||
<p>A library implies an act of faith</p>
|
||
<p>Which generations still in darkness hid</p>
|
||
<p>Sign in their night, in witness of the dawn.</p>
|
||
<div class="chapter-opening-quote-source">
|
||
<p>—Victor Hugo</p>
|
||
</div>
|
||
</blockquote>
|
||
</div>
|
||
<div class="chapter-opening-figure">
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_1.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
<h3 id="living-root-bridges-photo-by-arshiya-urveeja-bose">Living root bridges (photo by Arshiya Urveeja Bose)</h3>
|
||
<p>In the Indian state of Meghalaya, the Khasi and Jaiñtia peoples live in areas that experience some of the highest rainfall in the world. During the monsoon season, floods often make traveling between villages impossible. As a result, the ancient tradition of constructing living root bridges emerged. These bridges, like the double living root bridge in East Khasi shown here, are created by guiding and growing tree roots through bamboo, palm trunks, or steel scaffolding. They grow and become stronger as the roots interact with the environment, forming adaptive, springlike connections.</p>
|
||
</div>
|
||
<p></p>
|
||
<p>Think about what you’ve accomplished so far in this book. You’ve done the following:</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 those concepts</li>
|
||
<li>Implemented those 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 simulations, allowing you to creatively define the physics of the worlds you build (whether realistic or fantastical). But, of course, you and I aren’t the first or only people to do this. The world of computer graphics and programming is full of prewritten code libraries dedicated to physics simulations.</p>
|
||
<p>Just try searching <em>open source physics engine</em> and you could spend the rest of your day poring over a host of rich and complex codebases. This begs the question: If an existing code library takes care of physics simulation, why should you bother learning how to write any of the algorithms yourself? Here’s where the philosophy behind this book comes into play. While many libraries provide out-of-the-box physics to experiment with (super-awesome, sophisticated, and robust physics at that), there are several good reasons for learning the fundamentals from scratch before diving into such libraries.</p>
|
||
<p>First, without an understanding of vectors, forces, and trigonometry, it’s easy to get lost just reading the documentation of a library, let alone using it. Second, even though a library may take care of the math behind the scenes, it won’t necessarily simplify your code. A great deal of overhead may be required in understanding how a library works and what it expects from you code-wise. Finally, as wonderful as a physics engine might be, if you look deep down into your heart, you’ll likely see that you seek to create worlds and visualizations that stretch the limits of the imagination. A library may be great, but it provides only a limited set of features. It’s important to know when to live within those limitations in the pursuit of a creative coding project and when those limits will 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 <a href="http://haptic-data.com/toxiclibsjs">Toxiclibs.js</a>. I don’t mean to imply that these are the only libraries you should use for any and all creative coding projects that could benefit from a physics engine (see <a href="#other-physics-libraries">“Other Physics Libraries”</a> for alternatives, and check the book’s website for ports of the chapter’s examples to other libraries). However, both libraries integrate nicely with p5.js and will allow me to demonstrate the fundamental concepts behind physics engines and how they relate to and build upon the material I’ve covered so far.</p>
|
||
<p>Ultimately, the aim of this chapter isn’t to teach you the details of a specific physics library, but to provide you with a foundation for working with <em>any</em> physics library. The skills you acquire here will enable you to navigate and understand documentation, opening the door for you to expand your abilities with any library you choose.</p>
|
||
<h2 id="why-use-a-physics-library">Why Use a Physics Library?</h2>
|
||
<p>I’ve made the case for writing your own physics simulations (as you’ve learned to do in the previous chapters), but why use a physics library? After all, adding any external framework or library to a project introduces complexity and extra code. Is that additional overhead worth it? If you just want to simulate a circle falling down because of gravity, for example, do you really need to import an entire physics engine and learn its API? As the early chapters of this book hopefully demonstrated, probably not. Lots of scenarios like this are simple enough for you to get by writing the code yourself.</p>
|
||
<p>But consider another scenario. What if you want to have 100 circles falling? And what if they aren’t circles at all, but rather irregularly shaped polygons? And what if you want these polygons to bounce off one another in a realistic manner when they collide?</p>
|
||
<p>You may have noticed that while I’ve covered motion and forces in detail, I’ve so far skipped over a rather important aspect of physics simulation: <strong>collisions</strong>. Let’s pretend for a moment that you aren’t reading a chapter about physics libraries and that I’ve decided right now to explain how to handle collisions in a particle system. I’d have to cover two distinct algorithms that address these questions:</p>
|
||
<ol>
|
||
<li>How do I determine if two shapes are colliding (or intersecting)? This is known as <strong>collision detection</strong>.</li>
|
||
<li>How do I determine the shapes’ velocities after the collision? This is known as <strong>collision resolution</strong>.</li>
|
||
</ol>
|
||
<p>If you’re working with simple geometric shapes, question 1 isn’t too tough. In fact, perhaps you’ve encountered it before. With two circles, for instance, you know they’re intersecting if the distance between their centers is less than the sum of their radii (see Figure 6.1).</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_2.png" alt="Figure 6.1: Two circles with radii r_1 and r_2 are colliding if the distance between them is less than r_1 + r_2.">
|
||
<figcaption>Figure 6.1: Two circles with radii <span data-type="equation">r_1</span> and <span data-type="equation">r_2</span> are colliding if the distance between them is less than <span data-type="equation">r_1 + r_2</span>.</figcaption>
|
||
</figure>
|
||
<p>That’s easy enough, but how about calculating the circles’ velocities after the collision? This is where I’m going to stop the discussion. Why, you ask? It’s not that understanding the math behind collisions isn’t important or valuable. (In fact, I’m including additional examples on the website related to collisions without a physics library.) The reason for stopping is that life is short! (Let this also be a reason for you to consider going outside and frolicking for a bit before sitting down to write your next sketch.) You can’t expect to master every detail of physics simulation. And while you might enjoy learning about collision resolution for circles, it’s only going to make you want to work with rectangles next. And then with strangely shaped polygons. And then curved surfaces. And then swinging pendulums colliding with springy springs. And then, and then, and then . . .</p>
|
||
<p>Incorporating complex features like collisions into a p5.js sketch while still having time to spend with friends and family—that’s the reason for this chapter. People have spent years developing solutions to these kinds of problems, and beautiful JavaScript libraries like Matter.js and Toxiclibs.js are the fruits of those efforts. You don’t need to reinvent 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 <em>collisions</em> comes up, then it’s likely time to learn to use a physics engine.</p>
|
||
<div class="allow-break">
|
||
<div data-type="note">
|
||
<h3 id="other-physics-libraries">Other Physics Libraries</h3>
|
||
<p>A multitude of other physics libraries are worth exploring alongside this chapter’s two case studies, each with unique strengths that may offer advantages in certain kinds of projects. In fact, when I first began writing this book, Matter.js didn’t exist, so the physics engine I initially used to demonstrate the examples was Box2D. It was (and likely still is) the most well-known physics engine of them all.</p>
|
||
<p><a href="https://box2d.org/">Box2D</a> began as a set of physics tutorials written in C++ by Erin Catto for the Game Developers Conference in 2006. Since then, Box2D has evolved into a rich and elaborate open source physics engine. It’s been used for countless projects, most notably highly successful games such as the award-winning <em>Crayon Physics</em> and the runaway hit <em>Angry Birds</em>.</p>
|
||
<p>One important feature of Box2D is that it’s a true physics engine: it knows nothing about computer graphics and the world of pixels, and instead does all its measurements and calculations in real-world units like meters, kilograms, and seconds. It’s just that its “world” (a key term in Box2D) is a 2D plane with top, bottom, left, and right edges. You tell it things like “The gravity of the world is 9.81 newtons per kilogram, and a circle with a radius of 4 meters and a mass of 50 kilograms is located 10 meters above the world’s bottom.” Box2D will then tell you things like “One second later, the rectangle is at 5 meters from the bottom; two seconds later, it’s 10 meters below,” and so on.</p>
|
||
<p>While this provides for an amazingly accurate and robust physics engine (one that’s highly optimized and fast for C++ projects), it also necessitates lots of complicated code to translate back and forth between Box2D’s physics world and the world you want to draw—the pixel world of the graphics canvas. This creates a tremendous burden for the coder. I will, as best I can, continue to maintain a set of Box2D-compatible examples for this book (there are several JavaScript ports), but I believe the relative simplicity of working with a library like Matter.js that 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>Another notable library is <a href="https://p5play.org/">p5play</a>, a project initiated by Paolo Pedercini and currently led by Quinton Ashley that was specifically designed for game development. It simplifies the creation of visual objects—known as sprites—and manages their interactions (namely, collisions and overlaps). As you may have guessed from the name, p5play is tailored to work seamlessly with p5.js. It uses Box2D under the hood for physics simulation.</p>
|
||
</div>
|
||
</div>
|
||
<h2 id="importing-the-matterjs-library">Importing the Matter.js Library</h2>
|
||
<p>In a moment, I’ll turn to working with Matter.js, created by Liam Brummitt in 2014. But before you can use an external JavaScript library in a p5.js project, you need to import it into your sketch. As you’re already quite aware, I’m using the official p5.js web editor for developing and sharing this book’s code examples. The easiest way to add a library is to edit the <em>index.html</em> file that’s part of every new p5.js sketch created in the editor.</p>
|
||
<p>To do that, first expand the file navigation bar on the left-hand side of the editor and select <em>index.html</em>, as shown in Figure 6.2.</p>
|
||
<p>The file includes 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 from 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, the library is referenced with a URL of a <strong>content delivery network (CDN)</strong>. This is a type of server for hosting files. For JavaScript libraries that are used across hundreds of thousands of web pages accessed by millions upon millions of users, CDNs need to be pretty good at their job of serving up these libraries.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_3.png" alt="Figure 6.2: Accessing a sketch’s index.html file">
|
||
<figcaption>Figure 6.2: Accessing a sketch’s <em>index.html</em> file</figcaption>
|
||
</figure>
|
||
<p>You should already see a <code><script></code> tag referencing 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.9.0/p5.js"></script></pre>
|
||
<p>To use Matter.js, add another <code><script></code> tag referencing its CDN right below the one for p5:</p>
|
||
<pre class="codesplit" data-code-language="html"><script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.9.0/p5.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.19.0/matter.min.js"></script></pre>
|
||
<p>At the time of this writing, the most recent version of Matter.js is <code>0.19.0</code>, and that’s what I’ve referenced in this 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="matterjs-overview">Matter.js Overview</h2>
|
||
<p>When you use Matter.js (or any physics engine) in p5.js, your code ends up looking a bit different. Here’s a pseudocode generalization of all the examples in <a href="/vectors#">Chapters 1</a> through <a href="/autonomous-agents#">5</a>:</p>
|
||
<p><code><strong>setup()</strong></code></p>
|
||
<ol>
|
||
<li>Create all the objects in the world.</li>
|
||
</ol>
|
||
<p><code><strong>draw()</strong></code></p>
|
||
<ol>
|
||
<li>Calculate all the forces in the world.</li>
|
||
<li>Apply all the forces to the objects (<span data-type="equation">F = M \times A</span>).</li>
|
||
<li>Update the positions of all the objects based on their acceleration.</li>
|
||
<li>Draw all the objects.</li>
|
||
</ol>
|
||
<p>By contrast, here’s the pseudocode for a Matter.js example:</p>
|
||
<p><code><strong>setup()</strong></code></p>
|
||
<ol>
|
||
<li>Create all the objects in the world.</li>
|
||
</ol>
|
||
<p><code><strong>draw()</strong></code></p>
|
||
<ol>
|
||
<li>Draw all the objects.</li>
|
||
</ol>
|
||
<p>This, of course, is the allure of a physics engine. I’ve eliminated all those painful steps of figuring out how the objects are moving according to velocity and acceleration. Matter.js is going to take care of this for me!</p>
|
||
<p>While there will be more details to reveal, the good news is that the simplicity of this pseudocode is an accurate reflection of the overall process. In this sense, Matter.js is a bit like a magic box. In <code>setup()</code>, I’m going to say to Matter, “Hello there. Here are all of the things I want in my world.” Then, 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 please tell me where they are?”</p>
|
||
<p>The bad news: the process is not quite as simple as the pseudocode might lead you to believe. Actually making the stuff that goes into the Matter.js world requires several steps related to building and configuring different kinds of shapes.</p>
|
||
<div class="avoid-break">
|
||
<p>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>
|
||
<ul>
|
||
<li><strong>Engine:</strong> The entity that manages the physics simulation itself. The engine holds on to the world of the simulation as well as various properties indicating how the world is updated over time.</li>
|
||
</ul>
|
||
</div>
|
||
<ul>
|
||
<li><strong>Bodies:</strong> The primary elements in the world, corresponding to the physical objects being simulated. A body has a position and a velocity. Sound familiar? It’s basically another version of the class I’ve been building throughout <a href="/vectors#">Chapters 1</a> through <a href="/autonomous-agents#">5</a>. It also has geometry to define its shape. It’s important to note that <em>body</em> is a generic term that physics engines use to describe a <em>thing</em> in the world (similarly to the term <em>particle</em>); it isn’t related to an anthropomorphic body.</li>
|
||
<li><strong>Composite:</strong> A container that allows for the creation of complex entities (made up of multiple bodies). The world itself is an example of a composite, and every body created has to be added to the world.</li>
|
||
<li><strong>Constraints:</strong> Act as connections between bodies.</li>
|
||
</ul>
|
||
<p>In the coming sections, I’ll walk through each of these elements in detail, building several examples along the way. But first, there’s one other important element to briefly discuss:</p>
|
||
<ul>
|
||
<li><strong>Vector:</strong> Describes an entity with magnitude and direction using x- and y-components, defining positions, velocities, and forces in a Matter.js world.</li>
|
||
</ul>
|
||
<p>This brings us to an important crossroads. Any physics library is fundamentally built around vectors, and depending on how you spin it, that’s either a good thing or a bad thing. The good part is that you’ve just spent several chapters familiarizing yourself with what it means to describe motion and forces with vectors, so there’s nothing conceptually new for you to learn. The bad part—the part that makes a single tear fall from my eye—is that once you cross this threshold into the brave new world of physics libraries, you don’t get to use <code>p5.Vector</code> anymore.</p>
|
||
<p>It’s been great that p5.js has a built-in vector representation, but anytime you use a physics library, you’ll likely discover that it includes its own separate vector implementation, designed to be especially compatible with the rest of the library’s code. This makes sense. After all, why should Matter.js be expected to know about <code>p5.Vector</code>objects?</p>
|
||
<p>The upshot of all this is that while you won’t have to learn any new concepts, you do have to get used to new naming conventions and syntax. To illustrate, I’ll show you some now-familiar <code>p5.Vector</code> operations alongside the equivalent <code>Matter.Vector</code> code. First, how do you create a vector?</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let v = createVector(1, -1);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let v = Matter.Vector.create(1, -1);</code></pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>What about adding two vectors together?</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
a.add(b);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let a = Matter.Vector.create(1, -1);
|
||
let b = Matter.Vector.create(3, 4);
|
||
Matter.Vector.add(a, b, a);</code></pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>That overwrites vector <code>a</code> with the result. Here’s how to put the result in a separate vector instead:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.js</th>
|
||
<th>Matter.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
let c = p5.Vector.add(a, b);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let a = Matter.Vector.create(1, -1);
|
||
let b = Matter.Vector.create(3, 4);
|
||
let c = Matter.Vector.add(a, b);</code></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><code>let v = createVector(1, -1);
|
||
v.mult(4);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let v = Matter.Vector.create(1, -1);
|
||
v = Matter.Vector.mult(v, 4);</code></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><code>let v = createVector(3, 4);
|
||
let m = v.mag();
|
||
v.normalize();</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let v = Matter.Vector.create(3, 4);
|
||
let m = Matter.Vector.magnitude(v);
|
||
v = Matter.Vector.normalise(v);</code></pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>As you can see, the concepts are the same, but the specifics of the code are different. First, every method name is now preceded by <code>Matter.Vector</code>, which defines the <strong>namespace</strong> of the source code. This is common for JavaScript libraries; p5.js is unusual for not consistently using namespaces. For example, to draw a circle in p5.js, 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 features that makes p5.js special in terms of ease of use and beginner friendliness. However, it also means that for any code you write with p5.js, you can’t use <code>circle</code> as a variable name. Namespacing a library protects against these kinds of errors and naming conflicts, and it’s why you’ll see everything in Matter.js called with the <code>Matter</code> prefix.</p>
|
||
<p>In addition, unlike p5.js’s static and nonstatic versions of vector methods like <code>add()</code> and <code>mult()</code>, all vector methods in Matter.js 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> (the third argument). You can also set an existing variable to the newly created vector object resulting from a calculation, as in <code>v = Matter.Vector.mult(v, 2)</code>. However, this version still creates a new vector in memory rather than updating the old one.</p>
|
||
<p>I’ll cover more of the basics for working with <code>Matter.Vector</code> in this chapter, but for details, you can find the <a href="https://brm.io/matter-js">full documentation on the Matter.js website</a>.</p>
|
||
<h3 id="engine">Engine</h3>
|
||
<p>Many physics libraries include a <em>world</em> object to manage everything. The world is typically in charge of the coordinate space, keeping a list of all the bodies in the simulation, controlling time, and more. In Matter.js, the world is created inside 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 the Matter.js <code>Engine</code> class
|
||
let Engine = Matter.Engine;
|
||
|
||
//{!1} A reference to the Matter.js physics engine
|
||
let engine;
|
||
|
||
function setup() {
|
||
createCanvas(640, 360);
|
||
// Create the Matter.js engine.
|
||
engine = Engine.create();
|
||
}</pre>
|
||
<p>Notice that the very first line of code creates an <code>Engine</code> variable and sets it equal to <code>Matter.Engine</code>. Here, I’m deciding to point the single keyword <code>Engine</code> to the <code>Engine</code> class namespaced inside Matter.js in order to make my code less verbose. This works because I know I won’t be using the word <code>Engine</code> for any other variables, nor does it conflict with something in p5.js. 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 aliases, I won’t always show them in the book text.)</p>
|
||
<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 (0, 1) pointing down. You can change this default by accessing the <code>gravity</code> variable:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> // Change the engine’s gravity to point horizontally.
|
||
engine.gravity.x = 1;
|
||
engine.gravity.y = 0;</pre>
|
||
<p>Of course, gravity doesn’t have to be fixed for the duration of the simulation; you can adjust the gravity vector while your program is running. You can also turn gravity off altogether by setting it to a (0, 0) vector.</p>
|
||
<div data-type="note">
|
||
<h3 id="object-destructuring">Object Destructuring</h3>
|
||
<p><strong>Object destructuring</strong> in JavaScript is a technique for extracting properties from an object and assigning them to variables. In the case of Matter.js, the <code>Matter</code> object contains the <code>Engine</code> property. Normally, an alias for this property can be set with <code>let Engine = Matter.Engine</code>, but with destructuring, the alias can be created more concisely:</p>
|
||
<pre class="codesplit" data-code-language="javascript">const { Engine } = Matter;</pre>
|
||
<p>Hold on. Did you catch that I snuck in a <code>const</code> here? I know I said back in <a href="/random#">Chapter 0</a> that I would use only <code>let</code> for variable declarations throughout this book. However, working with an external library is a really good time to dip your toe in the <code>const</code> waters. In JavaScript, <code>const</code> is used for declaring variables whose values should never be reassigned after initialization. In this case, I want to protect myself from accidentally overwriting the <code>Engine</code> variable later in the code, which would likely break everything!</p>
|
||
<p>With that out of the way, let’s look at how the destructuring syntax really shines when you need to create aliases to multiple properties of the same object:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Use object destructuring to extract aliases for <code>Engine</code> and <code>Vector</code>.
|
||
const { Engine, Vector } = Matter;</pre>
|
||
<p>This sets up <code>Engine</code> as an alias for <code>Matter.Engine</code>, and <code>Vector</code> as an alias for <code>Matter.Vector</code>, all in one statement. I’ll use this technique throughout the chapter’s examples.</p>
|
||
</div>
|
||
<p>Once the world is initialized, it’s time to put stuff in it—bodies!</p>
|
||
<h3 id="bodies">Bodies</h3>
|
||
<p>The <strong>body</strong> is the primary element in the Matter.js world. It’s the equivalent of the <code>Vehicle</code> (née <code>Particle</code>, née <code>Mover</code>) class I built in previous chapters—the thing that moves around the space and experiences forces. A body can also be static (fixed and not moving).</p>
|
||
<p>Matter.js bodies are created using factory methods found in <code>Matter.Bodies</code>, with different methods available for creating different kinds of bodies. A <strong>factory method</strong> is a function that creates an object. While you’re probably more familiar with calling a constructor to create an object—for example, with <code>new Particle()</code>—you’ve seen factory methods before: <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 the factory methods for creating bodies can be found in the <code>Matter.Bodies</code> <a href="https://brm.io/matter-js/docs/classes/Bodies.html">documentation page</a>. I’ll start with the <code>rectangle()</code> method:</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>What luck! The <code>rectangle()</code> method signature is exactly the same as p5.js’s <code>rect()</code> function. In this case, however, the method isn’t <em>drawing</em> a rectangle but rather building the geometry for a <code>Body</code> object to store. (Note that calling <code>Bodies.rectangle()</code> works only if you first establish <code>Bodies</code> as an alias to <code>Matter.Bodies</code>.)</p>
|
||
<p>A body has now been created with a position and a size, and a reference to it is stored in the variable <code>box</code>. Bodies have many more properties that affect their motion, however. For example, density ultimately determines that body’s mass. Friction and restitution (bounciness) affect how the body interacts when it comes into contact with other bodies. For most cases, the defaults are sufficient, but Matter.js does allow you to specify these properties by passing through an additional argument to the factory method in the form of a JavaScript <strong>object literal</strong>, a collection of key-value pairs separated by commas and enclosed in curly brackets:</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!5} Specify the properties of this body in an object literal.
|
||
let options = {
|
||
friction: 0.5,
|
||
restitution: 0.8,
|
||
density: 0.002
|
||
};
|
||
let box = Matter.Bodies.rectangle(x, y, w, h, options);</pre>
|
||
<p>Each key in the object literal (for example, <code>friction</code>) serves as a unique identifier, and its value (<code>0.5</code>) is the data associated with that key. You can think of an object literal as a simple dictionary or lookup table—in this case, holding the desired settings for a new Matter.js body. Note, however, that while the <code>options</code> argument is useful for configuring the body, other initial conditions, such as linear or angular velocity, can be set via static methods of the <code>Matter.Body</code> class:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Set an 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 isn’t enough. Any body must be explicitly added to the world in order for it to be simulated with physics. The physics world is a <code>Composite</code> object called <code>world</code> stored inside 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>This extra step is easy to forget—it’s a mistake I’ve made on countless occasions. If you’re ever wondering why one of your objects doesn’t appear or move along with the world’s physics, always check that 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 following code that demonstrates how to make a circular body:</p>
|
||
<pre class="codesplit" data-code-language="javascript">let options = {
|
||
friction: 0.5,
|
||
restitution: 0.8,
|
||
};
|
||
let ball = <span class="blank">Bodies</span>.<span class="blank">circle</span>(<span class="blank">x</span>, <span class="blank">y</span>, <span class="blank">radius</span>, options);</pre>
|
||
</div>
|
||
<h3 id="render">Render</h3>
|
||
<p>Once a body is added to the world, Matter.js will always know it’s there, check it for collisions, and update its position appropriately, according to any forces in the environment. It’ll do all that without you having to lift a finger! But how do you draw the body?</p>
|
||
<p>In the next section, I’ll show you how to query Matter.js for the position of the various bodies in order to render the world with p5.js. The way that works is fundamental to being able to control the look of your own animations. This is your time to shine: you can be the designer of your world, using your creativity and p5.js skills to visualize the bodies, while politely asking Matter.js to compute all the physics in the background.</p>
|
||
<p>That said, Matter.js 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 provides ways to customize the <em>debug drawing</em> 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 method expects an object with the desired settings for the renderer, which I’ll call <code>params</code>.</p>
|
||
<div class="avoid-break">
|
||
<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>
|
||
</div>
|
||
<p>Notice that I’m storing a reference to the p5.js canvas in the <code>canvas</code> variable. This is necessary because I need to tell the renderer to draw in a specific canvas. Matter.js doesn’t know about p5.js, so the canvas it’s assigned is a native HTML5 canvas, stored inside the <code>elt</code> property of a p5.js canvas object. The engine is the <code>engine</code> I previously created. The Matter.js default canvas dimensions are 800×600, so if I prefer a different size, I need to configure an <code>options</code> property with <code>width</code> and <code>height</code>.</p>
|
||
<p>Once I have a <code>render</code> object, I need to tell Matter.js to run it:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Run the renderer!
|
||
Render.run(render);</pre>
|
||
<p>One more critical order of business remains: physics engines must be told to step forward in time. Since I’m using the built-in renderer, I can also use the built-in runner, which runs the engine at a default frame rate of 60 frames per second. The runner is also customizable, but the details aren’t terribly important since the goal here is to move toward using p5.js’s <code>draw()</code> loop instead (coming in the next section):</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Run the engine!
|
||
Runner.run(engine);</pre>
|
||
<p>Here’s the Matter.js code all together, with an added <code>ground</code> object—another rectangular body. Note the use of the <code>{ isStatic: true }</code> option in the creation of the ground body to ensure that it remains in a fixed position. I’ll cover more details about static bodies in <a href="#static-matterjs-bodies">“Static Matter.js Bodies”</a>.</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"><img src="examples/06_libraries/6_1_default_matter_js/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">// {!1} Note the use of aliases for all the Matter.js classes needed for this sketch.
|
||
const { Engine, Bodies, Composite, Body, Vector, Render } = 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 it to the p5.js canvas.
|
||
let render = Matter.Render.create({
|
||
canvas: canvas.elt, engine,
|
||
options: { width: width, height: height },
|
||
});
|
||
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 the initial velocity of the box.
|
||
Body.setVelocity(box, Vector.create(5, 0));
|
||
Body.setAngularVelocity(box, 0.1);
|
||
// Add the 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 the runner.
|
||
let runner = Matter.Runner.create();
|
||
// Run the engine.
|
||
Matter.Runner.run(runner, engine);
|
||
}</pre>
|
||
<p>There’s no <code>draw()</code> function here, and all the variables are local to <code>setup()</code>. In fact, I’m not using any p5.js capabilities (beyond injecting a canvas onto the page). This is exactly what I want to tackle next!</p>
|
||
<h2 id="matterjs-with-p5js">Matter.js with p5.js</h2>
|
||
<p>Matter.js keeps a list of all bodies that exist in the world, and as you’ve just seen, it can handle drawing and animating them with the <code>Render</code> and <code>Runner</code> objects. (That list, incidentally, is stored in <code>engine.world.bodies</code>.) What I’d like to show you now, however, is a technique for keeping your own list(s) of Matter.js bodies, so you can draw them with p5.js.</p>
|
||
<p>Yes, this approach may add redundancy and sacrifice a small amount of efficiency, but it more than makes up for that with ease of use and customization. With this methodology, you’ll be able to code as you’re accustomed to in p5.js, keeping track of which bodies are which and drawing them appropriately. Consider the file structure of the sketch shown in Figure 6.3.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_4.png" alt="Figure 6.3: The file structure of a typical p5.js sketch">
|
||
<figcaption>Figure 6.3: The file structure of a typical p5.js sketch</figcaption>
|
||
</figure>
|
||
<p>Structurally, this looks like just another p5.js sketch. There’s a main <em>sketch.js</em> file, as well as <em>box.js</em>. This sort of extra file is where I’d typically declare a class needed for the sketch—in this case, a <code>Box</code> class describing a rectangular body in the world:</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
constructor(x, y) {
|
||
//{!3} A box has an <code>(x, y)</code> position and a width.
|
||
this.x = x;
|
||
this.y = y;
|
||
this.w = 16;
|
||
}
|
||
|
||
show() {
|
||
//{!5} The box is drawn as a <code>square()</code>.
|
||
rectMode(CENTER);
|
||
fill(127);
|
||
stroke(0);
|
||
strokeWeight(2);
|
||
square(this.x, this.y, this.w);
|
||
}
|
||
}</pre>
|
||
<p>Now I’ll write a <em>sketch.js</em> file that creates a new <code>Box</code> whenever the mouse is clicked and stores all the <code>Box</code> objects in an array. (This is the same approach I took in the particle system examples from <a href="/particles#">Chapter 4</a>.)</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"><img src="examples/06_libraries/6_2_boxes_exercise/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">//{!1} An array to store all <code>Box</code> objects
|
||
let boxes = [];
|
||
|
||
function setup() {
|
||
createCanvas(640, 360);
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
|
||
//{!3} When the mouse is clicked, add a new <code>Box</code> object.
|
||
if (mouseIsPressed) {
|
||
let box = new Box(mouseX, mouseY);
|
||
boxes.push(box);
|
||
}
|
||
|
||
//{!3} Display all the <code>Box</code> objects.
|
||
for (let box of boxes) {
|
||
box.show();
|
||
}
|
||
}</pre>
|
||
<p>Right now, this sketch draws fixed boxes to the screen. Here’s the challenge: How can I instead draw boxes that experience physics (calculated with Matter.js) as soon as they appear, while changing the code as little as possible?</p>
|
||
<p>I’ll need three 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>As it stands, the sketch makes no reference to Matter.js. That clearly needs to change. Fortunately, this part isn’t too tough: I’ve already demonstrated all the elements needed to build a Matter.js world. (And don’t forget, in order for this to work, make sure the library is imported in <em>index.html.</em>)</p>
|
||
<p>First, I need to add aliases for the necessary Matter.js classes and create an <code>Engine</code> object in <code>setup()</code>:</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!3} Aliases for <code>Engine</code>, <code>Bodies</code>, and <code>Composite</code>
|
||
let { Engine, Bodies, Composite } = Matter;
|
||
|
||
//{!1} The engine is now a global variable!
|
||
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 Matter.js method, <code>Engine.update()</code>:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function draw() {
|
||
//{!1} Step the engine forward in time!
|
||
Engine.update(engine);
|
||
}</pre>
|
||
<p>The <code>Engine.update()</code> method advances the physics world one step forward in time. Calling it inside the p5.js <code>draw()</code> loop ensures that the physics will update at every frame of the animation. This mechanism takes the place of the built-in Matter.js <code>Runner</code> object I used in Example 6.1. The <code>draw()</code> loop is the runner now!</p>
|
||
<p>Internally, when <code>Engine.update()</code> is called, Matter.js sweeps through the world, looks at all the bodies in it, and figures out what to do with them. Just calling <code>Engine.update()</code> on its own moves the world forward with default settings. However, as with <code>Render</code>, these settings are customizable and documented in the <a href="https://brm.io/matter-js/docs/classes/Engine.html#method_update">Matter.js documentation</a>.</p>
|
||
<h3 id="step-2-link-every-box-object-with-a-matterjs-body">Step 2: Link Every Box Object with a Matter.js Body</h3>
|
||
<p>I’ve set up my Matter.js world; now I need to link each <code>Box</code> object in my p5.js sketch with a body in that world. The original <code>Box</code> class includes variables for position and width. What I now want to say is “I hereby relinquish command of this object’s position to Matter.js. I no longer need to keep track of anything related to position, velocity, or acceleration. Instead, I need to keep track of only the existence of a Matter.js body and have faith that the physics engine will do the rest.”</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
constructor(x, y) {
|
||
this.w = 16;
|
||
//{!1} Instead of any of the usual variables, store a reference to a body.
|
||
this.body = Bodies.rectangle(x, y, this.w, this.w);
|
||
//{!1} Don’t forget to add it to the world!
|
||
Composite.add(engine.world, this.body);
|
||
}</pre>
|
||
</div>
|
||
<p>I don’t need <code>this.x</code> and <code>this.y</code> position variables anymore. The <code>Box</code> constructor takes in the starting x- and y-coordinates, passes them along to <code>Bodies.rectangle()</code> to create a new Matter.js body, and then forgets about them. As you’ll see, the body itself will keep track of its position behind the scenes. The body could technically keep track of its dimensions as well, but since Matter.js stores them as a list of vertices, it’s a bit more convenient to hold onto the width of the square in the <code>this.w</code> variable for when it comes time to draw the box.</p>
|
||
<h3 id="step-3-draw-the-box-body">Step 3: Draw the Box Body</h3>
|
||
<p>Almost there. Before I introduced Matter.js into the sketch, drawing <code>Box</code> was easy. The object’s position was stored in the variables <code>this.x</code> and <code>this.y</code>:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> // Draw the object by using <code>square()</code>.
|
||
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> and <code>y</code>variables to draw the shape. But fear not! The <code>Box</code> object has a reference to the Matter.js body associated with it, and that body knows its own position. All I need to do is politely ask the body, “Pardon me, where are you located?”</p>
|
||
<pre class="codesplit" data-code-language="javascript">let position = this.body.position;</pre>
|
||
<p>Just knowing the position of a body isn’t enough, however. The body is a square, so I also need to know its angle of rotation:</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 by using the native p5.js <code>translate()</code>, <code>rotate()</code>, and <code>square()</code> functions:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> show() {
|
||
//{!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} Use 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 a <code>Box</code> object from the <code>boxes</code> array—perhaps when it moves outside the boundaries of the canvas or reaches the end of its life span, as demonstrated in <a href="/particles#">Chapter 4</a>—you must also explicitly remove the body associated with that <code>Box</code> object from the Matter.js world. This can be done with a <code>removeBody()</code> method on the <code>Box</code> class:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> // This function removes a body from the Matter.js world.
|
||
removeBody() {
|
||
Composite.remove(engine.world, this.body);
|
||
}</pre>
|
||
<p>In <code>draw()</code>, you would then iterate over the array in reverse, just as in the particle system examples, and call both <code>removeBody()</code> and <code>splice()</code> to delete the object from the Matter.js world and your array of boxes.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-62">Exercise 6.2</h3>
|
||
<p>Start with the code for Example 6.2 and, using the methodology outlined in this chapter, add the code to implement Matter.js physics. Delete bodies that have left the canvas. The result should appear as in this image. Feel free to be creative in the way you draw the boxes!</p>
|
||
<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></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2 id="static-matterjs-bodies">Static Matter.js Bodies</h2>
|
||
<p>In the example I just created, the <code>Box</code> objects appear at the mouse position and fall downward because of the default gravity force. What if I want to add immovable boundaries to the world that will block the path of the falling <code>Box</code> objects? Matter.js makes this easy with the <code>isStatic</code> property.</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Create a fixed (static) boundary body.
|
||
let options = { isStatic: true };
|
||
let boundary = Bodies.rectangle(x, y, w, h, options);</pre>
|
||
<p>I’m still creating a body with the <code>Bodies.rectangle()</code> factory method, but setting the <code>isStatic</code> property ensures that the body will never move. I’ll incorporate this feature into the solution to Exercise 6.2 by creating a separate <code>Boundary</code> class that links a p5.js rectangle to a static Matter.js body. For variety, I’ll also randomize the dimensions of each falling box. (See the online code for the changes to the <code>Box</code> class.)</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 the body in place by setting <code>isStatic</code> to <code>true</code>!
|
||
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 the boundary can never move, <code>show()</code> 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>
|
||
<p>Static bodies don’t incorporate material properties like <code>restitution</code> or <code>friction</code>. Make sure you set those in the dynamic bodies in your world.</p>
|
||
<h2 id="polygons-and-groups-of-shapes">Polygons and Groups of Shapes</h2>
|
||
<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 create a more interesting body, such as the abstract character in Figure 6.4.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_5.png" alt="Figure 6.4: A compound body made up of multiple shapes">
|
||
<figcaption>Figure 6.4: A compound body made up of multiple shapes</figcaption>
|
||
</figure>
|
||
<p>Two strategies can be used to make such complex forms. The generic <code>Bodies.polygon()</code> method can create any regular polygon (pentagon, hexagon, and so on). Additionally, <code>Bodies.trapezoid()</code> makes a quadrilateral with at least one pair of parallel sides:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// A regular hexagon (six-sided polygon)
|
||
let hexagon = Bodies.polygon(x, y, 6, radius);
|
||
// A trapezoid
|
||
let trapezoid = Bodies.trapezoid(x, y, width, height, slope);</pre>
|
||
<p>A more general-purpose option is <code>Bodies.fromVertices()</code>. It builds a shape from an array of vectors, treating them as a series of connected vertices. I’ll encapsulate this logic in a <code>CustomShape</code> class.</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>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript">class CustomShape {
|
||
constructor(x, y) {
|
||
//{!6} An array of five 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} Make a body 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>
|
||
</div>
|
||
<p>When creating a custom polygon in Matter.js, you must remember two important details. First, the vertices must be specified in clockwise order. For instance, Figure 6.5 shows the five vertices used to create the bodies in Example 6.4. Notice that the example added them to the <code>vertices</code> array in clockwise order from the top left.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_6.png" alt="Figure 6.5: Vertices on a custom polygon oriented in clockwise order">
|
||
<figcaption>Figure 6.5: Vertices on a custom polygon oriented in clockwise order</figcaption>
|
||
</figure>
|
||
<p>Second, each shape must be convex, not concave. As shown in Figure 6.6, a <strong>concave</strong> shape has a surface that curves inward, whereas <strong>convex</strong> is the opposite. Every internal angle in a convex shape must be 180 degrees or less. Matter.js can work with concave shapes, but you need to build them out of multiple convex shapes (more about that in a moment).</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_7.png" alt="Figure 6.6: A concave shape can be drawn with multiple convex shapes. ">
|
||
<figcaption>Figure 6.6: A concave shape can be drawn with multiple convex shapes. </figcaption>
|
||
</figure>
|
||
<p>Since the shape is built out of custom vertices, you can use p5.js’s <code>beginShape()</code>, <code>endShape()</code>, and <code>vertex()</code> functions when it comes time to actually draw the body. The <code>CustomShape</code> class <em>could</em> include an array to store the vertices’ pixel positions, relative to (0, 0), for drawing purposes. However, it’s best to query Matter.js for the positions instead. This way, there’s no need to use <code>translate()</code> or <code>rotate()</code>, since the Matter.js body stores its vertices as absolute world positions:</p>
|
||
<div class="snip-above">
|
||
<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>
|
||
<p>The Matter.js body stores the array of its vertex positions inside a <code>vertices</code> property. Notice that I can then use a <code>for...of</code> loop to cycle through the vertices between <code>beginShape()</code> and <code>endShape()</code>.</p>
|
||
<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 are shown here.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_8.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 <strong>compound body</strong> made up of multiple shapes! How about creating a delicious lollipop with a thin rectangle and a circle on top?</p>
|
||
<p>I’ll start by creating two individual bodies, one rectangle and one circle. Then I can join them by putting them in a <code>parts</code> array and passing the array to <code>Body.create()</code>:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Make the bodies.
|
||
let part1 = Bodies.rectangle(x, y, w, h);
|
||
let part2 = Bodies.circle(x, y, r);
|
||
|
||
// Join the two bodies together in an array.
|
||
let body = Body.create({ parts: [part1, part2] });
|
||
|
||
// Add the compound body to the world.
|
||
Composite.add(engine.world, body);</pre>
|
||
<p>While this does create a compound body by combining two shapes, the code isn’t quite right. If you run it, you’ll see that both shapes are centered on the same (<em>x</em>, <em>y</em>) position, as in Figure 6.7.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_9.png" alt="Figure 6.7: A rectangle and a circle with the same (x, y) reference point">
|
||
<figcaption>Figure 6.7: A rectangle and a circle with the same (<em>x</em>, <em>y</em>) reference point</figcaption>
|
||
</figure>
|
||
<p>Instead, I need to offset the center of the circle horizontally from the center of the rectangle, as in Figure 6.8.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_10.png" alt="Figure 6.8: A circle placed relative to a rectangle with a horizontal offset">
|
||
<figcaption>Figure 6.8: A circle placed relative to a rectangle with a horizontal offset</figcaption>
|
||
</figure>
|
||
<p>I’ll use half the width of the rectangle as the offset, so the circle is centered on the edge of the rectangle:</p>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
let part1 = Bodies.rectangle(x, y, w, h);
|
||
//{!2} Add an offset from the x-position of the lollipop’s stick.
|
||
let offset = w / 2;
|
||
let part2 = Bodies.circle(x + offset, y, r);</pre>
|
||
<p>Because the lollipop’s body has two parts, drawing it is a bit trickier. I could take multiple approaches. For example, I could use the body’s <code>vertices</code> array and draw the lollipop as a custom shape, much like Example 6.4. (Every body stores an array of vertices, even if it wasn’t created with the <code>fromVertices()</code> method.) Since each part of the lollipop is a primitive shape, however, I’d prefer to separately translate to each part’s position and rotate by the collective body’s angle.</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(200);
|
||
stroke(0);
|
||
// Translate and rotate the rectangle (<code>part1</code>).
|
||
push();
|
||
translate(position1.x, position1.y);
|
||
rotate(angle);
|
||
rectMode(CENTER);
|
||
rect(0, 0, this.w, this.h);
|
||
pop();
|
||
// Translate and rotate the circle (<code>part2</code>).
|
||
push();
|
||
translate(position2.x, position2.y);
|
||
rotate(angle);
|
||
circle(0, 0, this.r * 2);
|
||
pop();
|
||
}</pre>
|
||
<p>Before moving on, I want to stress that what you draw in your canvas window doesn’t magically experience perfect physics just by the mere act of creating Matter.js bodies. The chapter’s examples have worked because I’ve been carefully matching the way I’ve drawn each p5.js body with the way I’ve defined the geometry of each Matter.js body. If you accidentally draw a shape differently, you won’t get an error—not from p5.js or from Matter.js. However, your sketch will look odd, and the physics won’t work correctly because the world you’re seeing won’t be aligned with the world as Matter.js understands it.</p>
|
||
<p>To illustrate, let me return to Example 6.5. A lollipop is a compound body consisting of two parts, a rectangle (<code>this.part1</code>) and a circle (<code>this.part2</code>). I’ve been drawing each lollipop by getting the positions for the two parts separately: <code>this.part1.position</code> and <code>this.part2.position</code>. However, the overall compound body also has a position, <code>this.body.position</code>. It would be tempting to use that as the position for drawing the rectangle, and to figure out the circle’s position manually using an offset. After all, that’s how I conceived of the compound shape to begin with (look back at Figure 6.8):</p>
|
||
<pre class="codesplit" data-code-language="javascript"> show() {
|
||
//{!1} Getting the body position rather than the parts.
|
||
let position = this.body.position;
|
||
let angle = this.body.angle;
|
||
push();
|
||
translate(position.x, position.y);
|
||
rotate(angle);
|
||
rect(0, 0, this.w, this.h);
|
||
circle(0, this.h / 2, this.r * 2);
|
||
pop();
|
||
}</pre>
|
||
<p>Figure 6.9 shows the result of this change.</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/HWeBLcNuu" data-example-path="examples/06_libraries/6_5_compound_bodies_error"><img src="examples/06_libraries/6_5_compound_bodies_error/screenshot.png"></div>
|
||
<figcaption>Figure 6.9: What happens when the shapes are drawn differently from their Matter.js configurations</figcaption>
|
||
</figure>
|
||
<p>At first glance, this new version may look fine, but if you look closer, the collisions are off and the shapes overlap in odd ways. This isn’t because the physics is broken; it’s because I’m not communicating properly between p5.js and Matter.js. It turns out the overall body position isn’t the center of the rectangle, but rather the center of mass between the rectangle and the circle. Matter.js is calculating the physics and managing collisions as before, but I’m drawing each body in the wrong place! (In the online version, you can toggle the correct and incorrect renderings by clicking the mouse.)</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-64">Exercise 6.4</h3>
|
||
<p>Make your own little alien being by using multiple shapes attached to a single body. Remember, you aren’t limited to using the basic shape-drawing functions in p5.js; you can use images and colors, add hair with lines, and more. Think of the Matter.js shapes as skeletons for your original fantastical design!</p>
|
||
</div>
|
||
<h2 id="matterjs-constraints">Matter.js Constraints</h2>
|
||
<p>A Matter.js <strong>constraint</strong> is a mechanism to connect one body to another, enabling simulations of swinging pendulums, elastic bridges, squishy characters, wheels spinning on an axle, and more. Constraints have three types: distance constraints and revolute constraints, both managed through the <code>Constraint</code> class, and mouse constraints, managed through the <code>MouseConstraint</code> class.</p>
|
||
<h3 id="distance-constraints">Distance Constraints</h3>
|
||
<div class="half-width-right">
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_11.png" alt="Figure 6.10: A constraint is a connection between two bodies at an anchor point for each body.">
|
||
<figcaption>Figure 6.10: A constraint is a connection between two bodies at an anchor point for each body.</figcaption>
|
||
</figure>
|
||
</div>
|
||
<p>A <strong>distance constraint</strong> is a connection of fixed length between two bodies, similar to a spring force connecting two shapes in <a href="/oscillation#">Chapter 3</a>. The constraint is attached to each body at a specified <strong>anchor</strong>, a point relative to the body’s center (see Figure 6.10). Depending on the constraint’s stiffness property, the “fixed” length can exhibit variability, much as a spring can be more or less rigid.</p>
|
||
<p>Defining a constraint uses a similar methodology as creating bodies, only you need to have two bodies ready to go. Let’s assume that two <code>Particle</code> objects each store a reference to a Matter.js body in a property called <code>body</code>. I’ll call them <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>I want to create a constraint between these particles. For that, I need to define a series of options that determine the constraint’s behavior:</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. The constraint will attempt to maintain this length during the simulation.</li>
|
||
<li><code>stiffness</code>: A value from 0 to 1 that represents the rigidity of the constraint, with 1 being fully rigid and 0 being completely soft.</li>
|
||
</ul>
|
||
<p>These settings get packaged up in an object literal:</p>
|
||
<div class="avoid-break">
|
||
<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>
|
||
</div>
|
||
<p>Technically, the only required options are <code>bodyA</code> and <code>bodyB</code>, the two bodies connected by the constraint. If you don’t specify any additional options, Matter.js will choose defaults for the other properties. For example, it will use <code>(0, 0)</code> for each relative anchor point (the body’s center), 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 I didn’t include are <code>damping</code> and <code>angularStiffness</code>. The <code>damping</code> option affects the constraint’s resistance to motion, with higher values causing the constraint to lose energy more quickly. The <code>angularStiffness</code> option 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 can be created. As usual, this assumes another alias—<code>Constraint</code> is 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>I can include a constraint to a class to encapsulate and manage the relationships among multiple bodies. Here’s an example of a class that represents a swinging pendulum (mirroring Example 3.11 from <a href="/oscillation#">Chapter 3</a>).</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"><img src="examples/06_libraries/6_6_matter_js_pendulum/screenshot.png"></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 two bodies, one for the anchor and one for the bob.
|
||
// The anchor is static.
|
||
this.anchor = Bodies.circle(x, y, this.r, { isStatic: true });
|
||
this.bob = Bodies.circle(x + len, y, this.r, { restitution: 0.6 });
|
||
//{!6} Create a constraint connecting the anchor and the 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);
|
||
//{!2} 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 <code>0.7</code>. If you try a lower value, the pendulum will appear more like a soft spring.</p>
|
||
<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 shown in the following image. Use the <code>isStatic</code> property to lock the endpoints in place. Experiment with different values to make the bridge more or less springy. The joints 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"><img src="examples/06_libraries/exercise_6_5_bridge/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="revolute-constraints">Revolute Constraints</h3>
|
||
<div class="half-width-right">
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_12.png" alt="Figure 6.11: A revolute constraint is a connection between two bodies at a single anchor point, or hinge.">
|
||
<figcaption>Figure 6.11: A revolute constraint is a connection between two bodies at a single anchor point, or hinge.</figcaption>
|
||
</figure>
|
||
</div>
|
||
<p>Another kind of connection between bodies common to physics engines is a <strong>revolute joint</strong>. This type of constraint connects two bodies at a common anchor point, also known as a <strong>hinge</strong> (see Figure 6.11). While Matter.js doesn’t have a separate revolute constraint, you can make one with a regular <code>Constraint</code> of length 0. This way, the bodies can 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 propeller or windmill) in a fixed position. For this case, I need only one body connected to a point. This simplifies the code 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 position with width and height.
|
||
let body = Bodies.rectangle(x, y, w, h);
|
||
Composite.add(engine.world, body);</pre>
|
||
<p>Next, I can create the constraint. With a <code>length</code> of <code>0</code>, it needs a <code>stiffness</code> of <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 (<em>x</em>, <em>y</em>) position with a length of 0 and stiffness of 1.
|
||
let options = {
|
||
bodyA: this.body,
|
||
pointB: { x: x, y: y },
|
||
length: 0,
|
||
stiffness: 1,
|
||
};
|
||
// Create the constraint and add it to the world.
|
||
let constraint = Matter.Constraint.create(options);
|
||
Composite.add(engine.world, constraint);</pre>
|
||
<p>Putting the code together, I’ll write a sketch with a class called <code>Windmill</code> representing a rotating body. The 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"><img src="examples/06_libraries/6_7_windmill/screenshot.png"></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);
|
||
//{!8} The revolute constraint
|
||
let options = {
|
||
bodyA: this.body,
|
||
pointB: { x: x, y: 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>
|
||
<p>Notice the line in this example representing the windmill stand. It isn’t part of the Matter.js physics world, and I never created a body for it. This illustrates an important point about working with a physics engine alongside p5.js: you can add elements to the canvas that contribute to the visual design without affecting the physics, as long as you don’t need those elements to participate in the simulation itself.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-66">Exercise 6.6</h3>
|
||
<p>Create a vehicle that has revolute joints for its wheels. Consider the size and positioning of the wheels. How does changing the <code>stiffness</code> property affect their movement?</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_13.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="mouse-constraints">Mouse Constraints</h3>
|
||
<p>Before I introduce the <code>MouseConstraint</code> class, consider the following question: How do you set the position of a Matter.js body to the mouse position? More to the point, why would you need a constraint for this? After all, you have access to the body’s position, and you have access to the mouse’s position. What’s wrong with assigning one to the other?</p>
|
||
<pre class="codesplit" data-code-language="javascript">body.position.x = mouseX;
|
||
body.position.y = mouseY;</pre>
|
||
<p>While this code will move the body, it will also have the unfortunate result of breaking the physics. Imagine you’ve built a teleportation machine that allows you to move instantly from your bedroom to your kitchen (good for late-night snacking). That’s easy enough to imagine, but now go ahead and rewrite Newton’s laws of motion to account for the possibility of teleportation. Not so easy anymore, is it?</p>
|
||
<p>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 <em>does</em> allow you to tie a string around your waist and have a friend of yours stand in the kitchen and drag you there. Replace your friend with your mouse, and that’s what a mouse constraint is.</p>
|
||
<p>Imagine that the moment you click the mouse over a shape, the mouse attaches to that body with a string. Now you can move the mouse around, and it will drag the body around with it until you release the mouse. This works in a similar fashion as a revolute joint in that you can set the length of that “string” to 0, effectively moving a shape with the mouse.</p>
|
||
<p>Before you can attach the mouse, however, you need to create a Matter.js <code>Mouse</code> object that listens for mouse interactions with the p5.js canvas:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// Aliases for Matter.js <code>Mouse</code> and <code>MouseConstraint</code>
|
||
let { Mouse, MouseConstraint } = Matter;
|
||
// Need a reference to the p5.js canvas to listen for the mouse
|
||
let canvas = createCanvas(640, 240);
|
||
// Create a <code>Matter</code> mouse attached to the native HTML5 <code>canvas</code> element.
|
||
let mouse = Mouse.create(canvas.elt);</pre>
|
||
<p>Next, use the <code>mouse</code> object to create a <code>MouseConstraint</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. You don’t need to explicitly attach the constraint to a particular body; any body you click will be constrained to 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 the <code>MouseConstraint.create()</code> method:</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>Here’s an example demonstrating a <code>MouseConstraint</code> with two <code>Box</code> objects. Static bodies act as walls around 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"><img src="examples/06_libraries/6_8_mouse_constraint/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<p>In this example, you’ll see that the <code>stiffness</code> property of the constraint is set to <code>0.7</code>, giving a bit of elasticity to the imaginary mouse string. Other properties such as <code>angularStiffness</code> and <code>damping</code> can also influence the mouse’s interaction. What happens if you adjust these values?</p>
|
||
<h2 id="adding-more-forces">Adding More Forces</h2>
|
||
<p>In <a href="/forces#">Chapter 2</a>, I covered how to build an environment with multiple forces at play. An object might respond to gravitational attraction, wind, air resistance, and so on. Clearly, forces are at work in Matter.js as rectangles and circles spin and fly around the screen! But so far, I’ve demonstrated how to manipulate only a single global force: gravity.</p>
|
||
<pre class="codesplit" data-code-language="javascript"> let engine = Engine.create();
|
||
// Change 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 <a href="/forces#">Chapter 2</a> techniques with Matter.js, I need look no further than the trusty <code>applyForce()</code> method. In <a href="/forces#">Chapter 2</a>, I wrote this method 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 method exists, so I no longer need to write all the details myself! I can call it with the static <code>Body.applyForce()</code>. Here’s what that looks like in what’s now the <code>Box</code> class:</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Box {
|
||
applyForce(force) {
|
||
//{!1} Call <code>Body</code>’s <code>applyForce()</code>.
|
||
Body.applyForce(this.body, this.body.position, force);
|
||
}
|
||
}</pre>
|
||
<p>Here, the <code>Box</code> class’s <code>applyForce()</code> method receives a force vector and simply passes it along to Matter.js’s <code>applyForce()</code> method to apply it to the corresponding body. The key difference with this approach is that Matter.js is a more sophisticated engine than the examples from <a href="/forces#">Chapter 2</a>. The earlier examples assumed that the force was always applied at the mover’s center. Here, I’ve specified the exact position on the body where the force is applied. In this case, I’ve just applied it to the center as before by asking the body for its position, but this could be adjusted—for example, a force pushing at the edge of a box, causing it to spin across the canvas, much like dice tumbling when thrown.</p>
|
||
<p>How can I bring forces into a Matter.js-driven sketch? Say I want to use a gravitational attraction force. Remember the code from Example 2.6 in the <code>Attractor</code> class?</p>
|
||
<pre class="codesplit" data-code-language="javascript"> attract(mover) {
|
||
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 method by 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"><img src="examples/06_libraries/6_9_matter_js_attraction/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<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 <code>attract()</code> 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} Use a small value for <code>G</code> to keep the system stable.
|
||
let G = 0.02;
|
||
//{!1} The mover’s mass is included here, but the attractor’s mass is left out since, as a static body, it is equivalent to infinity.
|
||
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>
|
||
<p>In addition to writing a custom <code>attract()</code> method for Example 6.9, two other key elements are required for the sketch to behave more like the example from <a href="/forces#">Chapter 2</a>. First, remember that a Matter.js <code>Engine</code> has a default gravity pointing down. I need to disable it in <code>setup()</code> with a <code>(0, 0)</code> vector:</p>
|
||
<pre class="codesplit" data-code-language="javascript">engine = Engine.create();
|
||
//{!1} Disable the default gravity.
|
||
engine.gravity = Vector.create(0, 0);</pre>
|
||
<p>Second, bodies in Matter.js are created with a default air resistance that causes them to slow down as they move. I need to set this to <code>0</code> as well to simulate the bodies being in the vacuum of space:</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript">class Mover {
|
||
constructor(x, y, radius) {
|
||
this.radius = radius;
|
||
//{!1} Disable the default air resistance.
|
||
let options = { frictionAir: 0 };
|
||
this.body = Bodies.circle(x, y, this.radius, options);
|
||
}</pre>
|
||
</div>
|
||
<p>This is also a good time to revisit the concept of mass. Although I’m accessing the <code>mass</code> property of the body associated with the mover in the <code>attract()</code> method, I never explicitly set it. In Matter.js, the mass of a body is automatically calculated based on its size (area) and density. Larger bodies will therefore have a greater mass. To increase the mass relative to the size, you can try setting a <code>density</code> property in the <code>options</code> object (the default is <code>0.001</code>). For static bodies, such as the attractor, the mass is considered infinite. This is how the attractor stays locked in position despite the movers continuously knocking into it.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-67">Exercise 6.7</h3>
|
||
<p>Incorporate <code>Body.applyForce()</code> into a new <code>spin()</code> method for 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"><img src="examples/06_libraries/exercise_6_7_windmill_motor/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-68">Exercise 6.8</h3>
|
||
<p>Convert any of the steering behavior examples from <a href="/autonomous-agents#">Chapter 5</a> to Matter.js. What does flocking look like with collisions?</p>
|
||
</div>
|
||
<h2 id="collision-events">Collision Events</h2>
|
||
<p>This book isn’t called <em>The Nature of Matter.js</em>, so I’m not going to cover every possible feature of the Matter.js library. At this point, I’ve gone over the basics of creating bodies and constraints, and shown you some of what the library can do. With the skills you’ve gained, hopefully the learning process will be considerably less painful when it comes time to use an aspect of Matter.js that I haven’t addressed here. Before moving on, however, one more feature of the library is worth covering: collision events.</p>
|
||
<p>Here’s a question you’ve likely been wondering about: “What if I want something extra to happen when two bodies collide? I mean, don’t get me wrong—I’m thrilled that Matter.js is handling all the collisions behind the scenes. But if it’s taking care of the collisions for me, how am I supposed to know when they’re happening?”</p>
|
||
<p>Your first thoughts to answer this question might be as follows: “Well, I know all the bodies in the system, and I know where they’re all located. I can just start comparing the bodies’ positions and see which ones are intersecting. Then I can do something extra for the bodies that are determined to be colliding.”</p>
|
||
<p>That’s a nice thought, but hello? The whole point of using a physics engine like Matter.js is that it will take care of all that work for you. If you’re going to implement the computational geometry algorithms to test for intersection, you’re basically implementing your own Matter.js!</p>
|
||
<p>Of course, wanting to know when bodies are colliding is a pretty common problem, so Matter.js has anticipated it. It can alert you to moments of collision with an <strong>event listener</strong>. If you’ve worked with mouse or keyboard interaction in p5.js, you already have experience with event listeners. Consider the following:</p>
|
||
<pre class="codesplit" data-code-language="javascript">// A <code>mousePressed</code> event you’ve probably written many times before
|
||
function mousePressed() {
|
||
print("The mouse was pressed!");
|
||
}</pre>
|
||
<p>The global <code>mousePressed()</code> function in p5.js is executed whenever the mouse is clicked. This is known as a <strong>callback</strong>, a function that’s called back at a later time when an event occurs. Matter.js collision events operate in a similar fashion. Instead of p5.js just knowing to look for a function called <code>mousePressed()</code> when a mouse event occurs, however, you have to explicitly define the name for a Matter.js collision callback:</p>
|
||
<pre class="codesplit" data-code-language="javascript">Matter.Events.on(engine, 'collisionStart', handleCollisions);</pre>
|
||
<p>This code specifies that a function named <code>handleCollisions</code> should be executed whenever a collision between two bodies starts. Matter.js also has 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>Just as <code>mousePressed()</code> is triggered when the mouse is clicked, <code>handleCollisions()</code> (or whatever you choose to name the callback function) is triggered when two shapes collide. It can be written as follows.</p>
|
||
<div class="avoid-break">
|
||
<pre class="codesplit" data-code-language="javascript">function handleCollisions(event) {
|
||
|
||
}</pre>
|
||
</div>
|
||
<p>Notice that the function includes an <code>event</code> parameter. This is an object that includes all the data associated with a collision (or multiple collisions if more than one has occurred in that time step), such as which bodies are involved. Matter.js automatically creates this object and passes it along to the <code>handleCollisions()</code> callback every time a collision occurs.</p>
|
||
<p>Say I have a sketch of <code>Particle</code> objects. Each stores a reference to a Matter.js body, and I want the particles to change color when they collide. Here’s the process to follow to make that happen.</p>
|
||
<p><strong>Step 1: Event, could you tell me which two things collided?</strong></p>
|
||
<p>Now, what has collided here? Matter.js detects collisions between a pair of bodies. Any pair of colliding bodies will be in an array called <code>pairs</code> inside the <code>event</code> object. Inside <code>handleCollisions()</code>, I can use a <code>for...of</code> loop to iterate over those pairs:</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let pair of event.pairs) {
|
||
|
||
}</pre>
|
||
<p><strong>Step 2: Pair, could you tell me which two bodies you include?</strong></p>
|
||
<p>Each pair in the <code>pairs</code> array is an object with references to the two bodies involved in the collision, <code>bodyA</code> and <code>bodyB</code>. I’ll extract those bodies:</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let pair of event.pairs) {
|
||
let bodyA = pair.bodyA;
|
||
let bodyB = pair.bodyB;
|
||
}</pre>
|
||
<p><strong>Step 3: Bodies, could you tell me which particles you’re associated with?</strong></p>
|
||
<p>Getting from the relevant Matter.js bodies to the <code>Particle</code> objects they’re associated with is a little harder. After all, Matter.js doesn’t know anything about my code. Sure, it’s doing all sorts of stuff to keep track of the relationships between bodies and constraints, but it’s up to me to manage my own objects and their associations with Matter.js elements. That said, every Matter.js body is instantiated with an empty object—<code>{ }</code>—called <code>plugin</code>, ready to store any custom data about that body. I can link the body to a custom object (in this case, a <code>Particle</code>) by storing a reference to that object in the <code>plugin</code> property.</p>
|
||
<p>Take a look at the updated constructor in the <code>Particle</code> class where the body is made. Note that the body-making procedure has been expanded by one line of code to add a <code>particle</code> property inside <code>plugin</code>. It’s important to make sure you’re adding a new property to the existing <code>plugin</code> object (in this case, <code>plugin.particle = this</code>) rather than overwriting the <code>plugin</code> object (for example, with <code>plugin = this</code>). The latter could interfere with other features or customizations.</p>
|
||
<div class="snip-below avoid-break">
|
||
<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} <code>this</code> refers to this <code>Particle</code> object, telling the Matter.js <code>Body</code> to store a
|
||
// reference to this particle that can be accessed later.
|
||
this.body.plugin.particle = this;
|
||
Composite.add(engine.world, this.body);
|
||
}</pre>
|
||
</div>
|
||
<p>Later, in the <code>handleCollision()</code> callback function, that <code>Particle</code> object can be accessed from the <code>Body</code> 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"><img src="examples/06_libraries/6_10_collision_events/screenshot.png"></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} Retrieve the particles associated with the colliding bodies via the <code>plugin</code>.
|
||
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>In most cases, you can’t 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 (another kind of thing, depending on what’s in your world). You can check an object’s type with JavaScript’s <code>instanceof</code> operator, as I’ve done in this 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="a-brief-interlude-integration-methods">A Brief Interlude: Integration Methods</h2>
|
||
<p>Has this ever happened to you? You’re at a fancy cocktail party, regaling your friends with tall tales of your incredible software physics simulations. Suddenly, out of the blue, someone pipes up: “Enchanting! But what integration method are you using?”</p>
|
||
<p><em>What?!</em> you think to yourself. <em>Integration?</em></p>
|
||
<p>Maybe you’ve heard the term before. Along with differentiation, it’s one of the two main operations in calculus. Oh right, calculus.</p>
|
||
<p>I’ve managed to get most of the way through this material related to physics simulation without really needing to dive into calculus. As I wrap up the first half of this book, however, it’s worth taking a moment to examine the calculus behind what I’ve been demonstrating and how it relates to the methodology in certain physics libraries (like Box2D, Matter.js, and the upcoming Toxiclibs.js). This way, you’ll know what to say at the next cocktail party when someone asks you about integration.</p>
|
||
<p>I’ll begin with a question: “What does integration have to do with position, velocity, and acceleration?” To answer, I should first define <strong>differentiation</strong>, the process of finding a derivative. The <strong>derivative</strong> of a function is a measure of how a function changes over time. Consider position and its derivative. Position is a point in space, while velocity is the change in position over time. Therefore, velocity can be described as the derivative of position. And what’s acceleration? The change in velocity over time. Acceleration is the derivative of velocity.</p>
|
||
<p><strong>Integration</strong>, the process of finding an integral, is the inverse of differentiation. For example, the <strong>integral</strong> 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.</p>
|
||
<p>Since the physics simulations in this book are founded on the notion of calculating acceleration based on forces, integration is needed to figure out the object’s location after a certain period of time (like one cycle of the <code>draw()</code> loop). In other words, you’ve been doing integration all along! It looks like the following.</p>
|
||
<pre class="codesplit" data-code-language="javascript">velocity.add(acceleration);
|
||
position.add(velocity);</pre>
|
||
<p>This methodology is known as <strong>Euler integration</strong>, or the Euler method (named for the mathematician Leonhard Euler, pronounced <em>Oiler</em>). It’s essentially the simplest form of integration and is very easy to implement in code—just two lines! However, while it’s computationally simple, it’s by no means the most accurate or stable choice for certain types of simulations.</p>
|
||
<p>Why is Euler inaccurate? Think about it this way: when you bounce down a sidewalk on a pogo stick, does the pogo stick sit in one position at time equals 1 second, then disappear and suddenly reappear in a new position at time equals 2 seconds, and do the same thing for 3 seconds, and 4, and 5? No, of course not. The pogo stick moves continuously through time.</p>
|
||
<p>But what’s happening in a p5.js sketch? A circle is at one position at frame 0, another at frame 1, another at frame 2, and so on. Sure, at 30 frames per second, you see the <em>illusion</em> of motion. But a new position is computed only every <em>N</em> units of time, whereas the real world is perfectly continuous. This results in some inaccuracies, as shown in Figure 6.12.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_14.png" alt="Figure 6.12: The Euler approximation of a curve">
|
||
<figcaption>Figure 6.12: The Euler approximation of a curve</figcaption>
|
||
</figure>
|
||
<p>The “real world” is the smooth curve; the Euler simulation is the series of straight-line segments. One option to improve on Euler is to use smaller time steps—instead of once per frame, you could recalculate an object’s position 20 times per frame. But this isn’t practical; the sketch might then run too slowly.</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 symplectic Euler, or semi-explicit Euler, a slight modification of Euler. Other engines use an integration method called Runge-Kutta (named for German mathematicians Carl Runge and Martin Kutta).</p>
|
||
<p>Another popular integration method used in physics libraries, including both Matter.js and Toxiclibs.js, is <strong>Verlet integration</strong>. 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.</p>
|
||
<p>Verlet integration is particularly well suited for particle systems, especially those with spring connections between the particles. Physics libraries hide the details from you so you don’t have to worry about how it all works, but if you’re interested in diving deeper into Verlet physics, I suggest reading the seminal paper on the topic, from which just about every Verlet computer graphics simulation is derived: <a href="https://www.cs.cmu.edu/afs/cs/academic/class/15462-s13/www/lec_slides/Jakobsen.pdf">“Advanced Character Physics” by Thomas Jakobsen</a>.</p>
|
||
<h2 id="verlet-physics-with-toxiclibsjs">Verlet Physics with Toxiclibs.js</h2>
|
||
<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 more than 10 years, the concepts and techniques that the library demonstrated can be found in countless creative coding projects today. Its website described it as follows:</p>
|
||
<blockquote data-type="epigraph">
|
||
<p><em>Toxiclibs is an independent, open source library collection for computational design tasks with Java and Processing developed by Karsten “toxi” Schmidt. The classes are purposefully kept fairly generic in order to maximize reuse 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></p>
|
||
</blockquote>
|
||
<p>Schmidt continues to contribute to the creative coding field through his recent project, <a href="https://thi.ng/umbrella">thi.ng umbrella</a>. This work can be considered an indirect successor to Toxiclibs, but with a much greater scope and detail. If you like this book, you might especially enjoy exploring <a href="https://thi.ng/vectors">thi.ng vectors</a>, which provides more than 800 vector algebra functions using plain-vanilla JavaScript arrays.</p>
|
||
<p>While thi.ng/umbrella may be a more modern and sophisticated approach, Toxiclibs remains a versatile tool, and I continue to use a version compatible with the latest version of Processing (4.3 as of the time of this writing). For this book, we should thank our lucky stars for Toxiclibs.js, a JavaScript adaptation of the library, created by Kyle Phillips (hapticdata). I’m going to cover only a few examples related to Verlet physics, but Toxiclibs.js includes a suite of other packages with functionality related to color, geometry, math, and more.</p>
|
||
<p>The examples I’m about to demonstrate could also be created using Matter.js, but I’ve decided to move to Toxiclibs.js for several reasons. The library holds a special place in my heart as a personal favorite, and it’s historically significant. I also believe that showing more than one physics library is important for providing a broader understanding of the tools and approaches available.</p>
|
||
<p>This switch from Matter.js to Toxiclibs.js raises an important question, though: How should you decide which library to use for a project? Matter.js, or Toxiclibs.js, or something else? If you fall into one of the following two categories, your decision is a bit easier:</p>
|
||
<ul>
|
||
<li><strong>My project involves collisions. I have circles, squares, and other strangely shaped objects that knock each other around and bounce off each other. </strong>In this case, you’re going to want to use Matter.js, since Toxiclibs.js doesn’t handle rigid-body collisions.</li>
|
||
<li><strong>My project involves lots of particles flying around the screen. Sometimes they attract each other. Sometimes they repel each other. And sometimes they’re connected with springs. </strong>In this case, Toxiclibs.js is likely your best choice. It’s simpler to use in some ways than Matter.js and particularly well suited to connected systems of particles. It’s also high performance, because it gets to ignore all of the collision geometry.</li>
|
||
</ul>
|
||
<p>Here’s 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.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>Rigid-body collisions</td>
|
||
<td>Yes</td>
|
||
<td>No</td>
|
||
</tr>
|
||
<tr>
|
||
<td>3D physics</td>
|
||
<td>No</td>
|
||
<td>Yes</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Particle attraction and repulsion forces</td>
|
||
<td>No</td>
|
||
<td>Yes</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Spring connections (force based)</td>
|
||
<td>Yes</td>
|
||
<td>Yes</td>
|
||
</tr>
|
||
<tr>
|
||
<td>Constraints (general-purpose connections)</td>
|
||
<td>Yes</td>
|
||
<td>No</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>All the documentation and downloads for the library files can be found at the <a href="http://haptic-data.com/toxiclibsjs">Toxiclibs.js website</a>. For the examples in this book, I’ll be working with a hosted CDN version of the library referenced in <em>index.html</em>, just as I demonstrated earlier for Matter.js. Here’s the <code><script></code> element to add:</p>
|
||
<pre class="codesplit" data-code-language="html"><script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script></pre>
|
||
<p>My overview of Matter.js focused on a few key features of that library: world, vector, body, constraint. This has given you a head start on understanding Toxiclibs.js as well, since it follows a similar structure. The following table shows the corresponding Toxiclibs.js features:</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Matter.js</th>
|
||
<th>Toxiclibs.js</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre><code>World</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>VerletPhysics2D</code></pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre><code>Vector</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>Vec2D</code></pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre><code>Body</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>VerletParticle2D</code></pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre><code>Constraint</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>VerletSpring2D</code></pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>I’ll discuss how some of these features translate to Toxiclibs.js before putting them together to create some interesting examples.</p>
|
||
<h3 id="vectors">Vectors</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 you had to revisit all those concepts with Matter.js and the <code>Matter.Vector</code> class? Well, it’s time to do it again, because Toxiclibs.js also includes its own vector classes. It has one for two dimensions and one for three: <code>Vec2D</code> and <code>Vec3D</code>. Both are found in the <code>toxi.geom</code> package and can be aliased in the same manner 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 conceptually the same as the p5.js vectors we know and love, but they have their own style and syntax. Here’s an overview of how some of the basic vector math operations from <code>p5.Vector</code> translate to <code>Vec2D</code> (I’m sticking with 2D to match the rest of this book, but I encourage you to explore 3D vectors as well).</p>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>p5.Vector</th>
|
||
<th>Vec2D</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
a.add(b);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let a = new Vec2D(1, -1);
|
||
let b = new Vec2D(3, 4);
|
||
a.addSelf(b);</code></pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let a = createVector(1, -1);
|
||
let b = createVector(3, 4);
|
||
let c = p5.Vector.add(a, b);</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let a = new Vec2D(1, -1);
|
||
let b = new Vec2D(3, 4);
|
||
let c = a.add(b);</code></pre>
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td>
|
||
<pre><code>let a = createVector(1, -1);
|
||
let m = a.mag();
|
||
a.normalize();</code></pre>
|
||
</td>
|
||
<td>
|
||
<pre><code>let a = new Vec2D(1, -1);
|
||
let m = a.magnitude();
|
||
a.normalize();</code></pre>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>Notice in particular that Toxiclibs.js vectors are created by calling the <code>Vec2D</code> constructor with the <code>new</code> keyword, rather than by using a factory method like <code>Matter.Vector()</code> or <code>createVector()</code>.</p>
|
||
<h3 id="the-physics-world">The 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 class aliases:</p>
|
||
<pre class="codesplit" data-code-language="javascript">
|
||
// The necessary geometry classes: vectors, rectangles
|
||
let { Vec2D, Rect } = toxi.geom;
|
||
// Alias the important classes from <code>toxi.physics2d</code>.
|
||
let { VerletPhysics2D, VerletParticle2D, VerletSpring2D } = toxi.physics2d;
|
||
// For the world’s gravity
|
||
let { GravityBehavior } = toxi.physics2d.behaviors;</pre>
|
||
<p>The first step is to create the world:</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript">let physics;
|
||
|
||
function setup() {
|
||
// Create a Toxiclibs world.
|
||
physics = new VerletPhysics2D();</pre>
|
||
</div>
|
||
<p>Once I have the <code>VerletPhysics</code> world, I can set global properties. For example, if I want hard boundaries beyond which particles can’t travel, I can provide rectangular bounds:</p>
|
||
<div class="snip-above snip-below">
|
||
<pre class="codesplit" data-code-language="javascript"> physics.setWorldBounds(new Rect(0, 0, width, height));</pre>
|
||
</div>
|
||
<p>In addition, I can add gravity with the <code>GravityBehavior</code> object. A gravity behavior requires a vector—how strong and in what direction is the gravity?</p>
|
||
<div class="snip-above">
|
||
<pre class="codesplit" data-code-language="javascript"> physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
|
||
}</pre>
|
||
</div>
|
||
<p>Finally, to calculate the physics of the world and move the world’s objects around, I have to call the world’s <code>update()</code> method. Typically, this would happen once per frame in <code>draw()</code>:</p>
|
||
<pre class="codesplit" data-code-language="javascript">function draw() {
|
||
//{!1} This is the same as the Matter.js <code>Engine.update()</code>.
|
||
physics.update();
|
||
}</pre>
|
||
<p>Now all that remains is to populate the world.</p>
|
||
<h3 id="particles">Particles</h3>
|
||
<p>The Toxiclibs.js equivalent of a Matter.js body—a thing that exists in the world and experiences physics—is a <strong>particle</strong>, as represented by the <code>VerletParticle2D</code> class. However, unlike Matter.js bodies, Toxiclibs.js particles don’t store geometry. They’re just points in space.</p>
|
||
<p>How should I integrate Toxiclibs.js particles into a p5.js sketch? In the Matter.js examples, I created my own class (called <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 was somewhat redundant, since Matter.js keeps track of the bodies in its world. However, it allowed me to manage which body is which (and therefore how each body should be drawn) without having to rely on iterating through Matter.js’s internal lists. I might take the same approach with Toxiclibs.js, making my own <code>Particle</code> class that stores a reference to a <code>VerletParticle2D</code> object. This way, I’ll be able to give the particles custom properties and draw them however I want. I’d probably write the code as follows:</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle {
|
||
constructor(x, y, r) {
|
||
//{!1} A <code>VerletParticle</code> needs an initial <code>(x, y)</code> position, but it has no geometry, so the <code>r</code> is used only 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 <code>(x, y)</code> is stored in <code>this.particle</code>.
|
||
circle(this.particle.x, this.particle.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>Looking over this code, you might 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 doesn’t do much beyond storing a reference to a <code>VerletParticle2D</code> object. This hints at something important. Think back to the discussion of inheritance in <a href="/particles#">Chapter 4</a>, and then ask yourself: What is a <code>Particle</code> object other than an augmented <code>VerletParticle2D</code> object? Why bother making two objects—a <code>Particle</code> and a <code>VerletParticle2D</code>—for every one particle in the world, when I could simply extend the <code>VerletParticle2D</code> class to include the extra code needed to draw the particle?</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
//{!1} Call <code>super()</code> with <code>(x, y)</code> so the object is initialized properly.
|
||
super(x, y);
|
||
//{!1} Add a variable to track the radius.
|
||
this.r = r;
|
||
}
|
||
|
||
//{!1} Augment by adding a <code>show()</code> method.
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
//{!1} <code>x</code> and <code>y</code> from <code>VerletParticle2D</code>!
|
||
circle(this.x, this.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>Furthermore, at the risk of blowing your mind, it turns out that the <code>VerletParticle2D</code> class is a subclass of <code>Vec2D</code>. This means that in addition to inheriting everything from <code>VerletParticle2D</code>, the <code>Particle</code> class has inherited all the<code>Vec2D</code> methods as well!</p>
|
||
<p>I can now create new particles:</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. Just as in Matter.js, I have to explicitly add the new particle to the world. In Toxiclibs.js, this is done with the <code>addParticle()</code> method:</p>
|
||
<pre class="codesplit" data-code-language="javascript">physics.addParticle(particle);</pre>
|
||
<p>If you look at the Toxiclibs.js documentation, you’ll see that <code>addParticle()</code> expects a <code>VerletParticle2D</code> object. But I’ve passed it a <code>Particle</code> object. Does that work?</p>
|
||
<p>Yes! Remember one of the tenets of OOP: polymorphism. Here, because the <code>Particle</code> class extends <code>VerletParticle2D</code>, I can treat the particle in two ways: as a <code>Particle</code> or as a <code>VerletParticle2D</code>. This is an incredibly powerful feature of OOP. If you build custom classes that inherit from Toxiclibs.js classes, you can use the objects of those classes in conjunction with all the methods Toxiclibs.js has to offer.</p>
|
||
<h3 id="springs">Springs</h3>
|
||
<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. Toxiclibs.js has three types of springs:</p>
|
||
<ul>
|
||
<li><code>VerletSpring2D</code>: A springy connection between two particles. The spring’s properties can be configured in such a way as to create a stiff, stick-like connection or a highly elastic, stretchy connection. A particle can also be locked so that only one end of the spring can move.</li>
|
||
<li><code>VerletConstrainedSpring2D</code>: A spring whose maximum distance can be limited. This can help the whole spring system achieve better stability.</li>
|
||
<li><code>VerletMinDistanceSpring2D</code>: A spring that enforces its rest length only if the current distance is less than its rest length. This is handy if you want to ensure that objects are at least a certain distance from each other, but you don’t care if the distance is bigger than the enforced minimum.</li>
|
||
</ul>
|
||
<p>Inheritance and polymorphism once again prove to be useful when making springs. A spring expects two <code>VerletParticle2D</code> objects when it’s created, but as before, two <code>Particle</code> objects will do, since <code>Particle</code> extends<strong><em> </em></strong><code>VerletParticle2D</code>.</p>
|
||
<p>Here’s some example code to create a spring. This snippet 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? (The higher the value, the more rigid 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 be part of the physics world, it must be explicitly added to the world:</p>
|
||
<pre class="codesplit" data-code-language="javascript">physics.addSpring(spring);</pre>
|
||
<p>I have almost everything I need to build a simple first Toxiclibs.js example: two particles connected to form a springy pendulum. I want to add one more element, however: mouse interactivity.</p>
|
||
<p>With Matter.js, I explained that the physics simulation breaks down if you manually override a body’s position by setting it to the mouse. With Toxiclibs.js, this isn’t a problem. If I want to, I can set a particle’s (<em>x</em>, <em>y</em>) position manually. However, before doing so, it’s generally a good idea to call the particle’s <code>lock()</code> method, which fixes the particle in place. This is identical to setting the <code>isStatic</code> property to <code>true</code> in Matter.js.</p>
|
||
<p>The idea is to lock the particle temporarily so it stops responding to the world’s physics, alter its position, and then unlock it (with the <code>unlock()</code> method) so it can start moving again from its new location. For example, say I want to reposition a particle whenever the mouse is clicked:</p>
|
||
<pre class="codesplit" data-code-language="javascript"> if (mouseIsPressed) {
|
||
//{!4} First lock the particle, then set the <code>x</code> and <code>y</code>, then <code>unlock()</code> it.
|
||
particle1.lock();
|
||
particle1.x = mouseX;
|
||
particle1.y = mouseY;
|
||
particle1.unlock();
|
||
}</pre>
|
||
<p>And with that, I’m ready to put all these elements together in a simple sketch with two particles connected by a spring. One particle is permanently locked in place, and the other can be moved by dragging the mouse. This example is virtually identical to Example 3.11 from <a href="/oscillation#">Chapter 3</a>.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-611-simple-spring-with-toxiclibsjs">Example 6.11: Simple Spring with Toxiclibs.js</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"><img src="examples/06_libraries/6_11_simple_spring_with_toxiclibs/screenshot.png"></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);
|
||
// Create a Toxiclibs.js 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;
|
||
// Create two particles.
|
||
particle1 = new Particle(width / 2, 0, 8);
|
||
particle2 = new Particle(width / 2 + length, 0, 8);
|
||
// Lock particle 1 in place.
|
||
particle1.lock();
|
||
// Create 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} Draw everything.
|
||
stroke(0);
|
||
line(particle1.x, particle1.y, particle2.x, particle2.y);
|
||
particle1.show();
|
||
particle2.show();
|
||
//{!6} Move the particle according to the mouse.
|
||
if (mouseIsPressed) {
|
||
particle2.lock();
|
||
particle2.x = mouseX;
|
||
particle2.y = mouseY;
|
||
particle2.unlock();
|
||
}
|
||
}
|
||
|
||
//{!11} How cute is this simple <code>Particle</code> 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>
|
||
<p>In this example, I’m continuing to visually represent the spring connecting the particles with a line. Keep in mind, however, that the behavior of the spring still exists, whether you choose to visually represent it or not. This can open up creative possibilities. For instance, you could decide to make the spring invisible or depict it in a completely different way, perhaps as a series of dots or a shape of your own invention.</p>
|
||
<h2 id="soft-body-simulations">Soft-Body Simulations</h2>
|
||
<p>Verlet physics is particularly well suited for a genre of computer graphics known as soft-body simulation. Unlike the <strong>rigid-body</strong> simulations of Matter.js, in which hard-edged boxes crash into one another and retain their shapes, <strong>soft-body</strong> simulations involve objects that can deform and change shape with physics. Soft bodies allow for more flexible, fluid, and organic movements. They can stretch, squish, and jiggle in response to forces and collisions, and they appear . . . well, soft.</p>
|
||
<p>One of the first popular examples of soft-body physics was <em>SodaConstructor</em>, a game created in the early 2000s. Players could construct and animate custom 2D creatures built out of masses and springs. Other examples over the years have included games like <em>LocoRoco</em>, <em>World of Goo</em>, and more recently, <em>JellyCar</em>.</p>
|
||
<p>The basic building blocks of soft-body simulations are particles connected by springs—just like the pair particles in Example 6.11. Figure 6.13 shows how to configure a network of particle-spring connections to make various forms.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_15.png" alt="Figure 6.13: Soft-body simulation designs">
|
||
<figcaption>Figure 6.13: Soft-body simulation designs</figcaption>
|
||
</figure>
|
||
<p>As the figure shows, a string can be simulated by connecting a line of particles with springs; a blanket can be simulated by connecting a grid of particles with springs; and a cute, cuddly, squishy cartoon character can be simulated with a custom layout of particles connected with springs. It’s not much of a leap from one to another.</p>
|
||
<h3 id="a-string">A String</h3>
|
||
<p>I’ll begin by simulating a <em>soft pendulum</em>—a bob hanging from a flexible string instead of a rigid arm. As it happens, Toxiclibs.js offers a convenient <code>ParticleString2D</code> class that creates a string of particles connected by springs in a single constructor call. However, for demonstration purposes, I’ll create my own particle string by using an array and a <code>for</code> loop. This way, you’ll gain a deeper understanding of the system, enabling you to create your own custom designs beyond a single string in the future.</p>
|
||
<p>First, I need an array of particles. I’ll use the same <code>Particle</code> class built in Example 6.11:</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, as in Figure 6.14.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_16.png" alt="Figure 6.14: Twenty particles all spaced 10 pixels apart">
|
||
<figcaption>Figure 6.14: Twenty particles all spaced 10 pixels apart</figcaption>
|
||
</figure>
|
||
<p>I can loop from <code>i</code> equals <code>0</code> all the way up to <code>total</code>, creating new particles and setting each one’s <code>y</code> position to <code>i * 10</code>. The first particle is at (0, 10), the second at (0, 20), the third at (0, 30), and so on:</p>
|
||
<pre class="codesplit" data-code-language="javascript">for (let i = 0; i < total; i++) {
|
||
//{!1} Space 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 adding the particles to both the Toxiclibs.js <code>physics</code> world and the <code>particles</code> array. This will help me manage the sketch (especially when I might have 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, 2 to 3, 3 to 4, and so on (see Figure 6.15).</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_17.png" alt="Figure 6.15: Each particle is connected to the next particle in the array.">
|
||
<figcaption>Figure 6.15: 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):</p>
|
||
<pre class="codesplit" data-code-language="javascript">// The loop stops before the last element (<code>total – 1</code>).
|
||
for (let i = 0; i < total - 1; i++) {
|
||
// The spring connects particle <code>i</code> to <code>i + 1</code>.
|
||
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—perhaps the first, the last, or the middle one. I’ll go with the first:</p>
|
||
<pre class="codesplit" data-code-language="javascript">particles[0].lock();</pre>
|
||
<p>Finally, I need to draw the particles. Instead of drawing them as circles, however, I want to treat them as points in a line. For that, I can use <code>beginShape()</code>, <code>endShape()</code>, and <code>vertex()</code>, accessing the individual particle positions from the array. I’ll use the <code>show()</code> method to draw only the last particle as a circle, creating a bob at the end of the string.</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"><img src="examples/06_libraries/6_12_soft_string/screenshot.png"></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 particles and springs. You’ll need to connect each particle with its vertical and horizontal neighbors.</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"><img src="examples/06_libraries/exercise_6_13_cloth_simulation/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="a-soft-body-character">A Soft-Body Character</h3>
|
||
<p>Now that I’ve built a simple connected system—a single string of particles—I’ll expand on this idea to create a squishy, cute friend in p5.js, otherwise known as a <strong>soft-body character</strong>. The first step is to design a skeleton of connected particles. I’ll begin with a very simple design with only six vertices, as shown in Figure 6.16. Each vertex (drawn as a dot) represents a <code>Particle</code> object, and each connection (drawn as a line) represents a <code>Spring</code> object.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_18.png" alt="Figure 6.16: A skeleton for a soft-body character. The vertices are numbered according to their positions in an array.">
|
||
<figcaption>Figure 6.16: A skeleton for a soft-body character. The vertices are numbered according to their positions in an array.</figcaption>
|
||
</figure>
|
||
<p>Creating the particles is the easy part; it’s exactly the same as before! I’d like to make one change, though. Rather than having the <code>setup()</code> function add the particles and springs to the physics world, I’ll incorporate this responsibility into the <code>Particle</code> constructor:</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Particle extends VerletParticle2D {
|
||
constructor(x, y, r) {
|
||
super(x, y);
|
||
this.r = r;
|
||
//{!1} Add the object to the global physics world. Inside a class, the object is referenced with <code>this</code>.
|
||
physics.addParticle(this);
|
||
}
|
||
|
||
show() {
|
||
fill(127);
|
||
stroke(0);
|
||
circle(this.x, this.y, this.r * 2);
|
||
}
|
||
}</pre>
|
||
<p>While it’s not strictly necessary, I’d also like to make a <code>Spring</code> class that inherits its functionality from <code>VerletSpring2D</code>. For this example, I want the resting length of the spring to always be equal to the distance between the skeleton’s particles when they’re first created. Additionally, I’m keeping the implementation simple here by hardcoding a uniform strength value of <code>0.01</code> in the <code>Spring</code> constructor. You may want to enhance the example with a more sophisticated design that sets varying degrees of springiness to the different parts of the soft-body character.</p>
|
||
<pre class="codesplit" data-code-language="javascript">class Spring extends VerletSpring2D {
|
||
// The constructor receives two particles as arguments.
|
||
constructor(a, b) {
|
||
// Calculate the rest length as the distance between the particles.
|
||
let length = dist(a.x, a.y, b.x, b.y);
|
||
// Hardcode the spring strength.
|
||
super(a, b, length, 0.01);
|
||
//{!1} Another enhancement to have the object add itself to the physics world!
|
||
physics.addSpring(this);
|
||
}
|
||
}</pre>
|
||
<p>Now that I have the <code>Particle</code> and <code>Spring</code> classes, I can assemble the character by adding a series of particles with hardcoded starting positions to a <code>particles</code> array, and a series of spring connections to a <code>springs</code> array.</p>
|
||
<pre class="codesplit" data-code-language="javascript">//{!2} Store all the particles and springs in arrays.
|
||
let particles = [];
|
||
let springs = [];
|
||
|
||
function setup() {
|
||
createCanvas(640, 240);
|
||
physics = new VerletPhysics2D();
|
||
// Create the vertex positions of the character as particles.
|
||
particles.push(new Particle(200, 25));
|
||
particles.push(new Particle(400, 25));
|
||
particles.push(new Particle(350, 125));
|
||
particles.push(new Particle(400, 225));
|
||
particles.push(new Particle(200, 225));
|
||
particles.push(new Particle(250, 125));
|
||
// Connect the vertices with springs.
|
||
springs.push(new Spring(particles[0], particles[1]));
|
||
springs.push(new Spring(particles[1], particles[2]));
|
||
springs.push(new Spring(particles[2], particles[3]));
|
||
springs.push(new Spring(particles[3], particles[4]));
|
||
springs.push(new Spring(particles[4], particles[5]));
|
||
springs.push(new Spring(particles[5], particles[0]));
|
||
}</pre>
|
||
<p>The beauty of this system is that you can easily expand it to create your own design by adding more particles and springs! However, there’s one major issue here: I’ve made connections only around the perimeter of the character. If I were to apply a force (like gravity) to the body, it would instantly collapse onto itself. This is where additional internal springs come into play, as shown in Figure 6.17. They keep the character’s structure stable while still allowing it to move and squish in a realistic manner.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_19.png" alt="Figure 6.17: Internal springs keep the structure from collapsing. This is just one possible design. Try others! ">
|
||
<figcaption>Figure 6.17: Internal springs keep the structure from collapsing. This is just one possible design. Try others!</figcaption>
|
||
</figure>
|
||
<p>The final example incorporates the additional springs from Figure 6.17, a gravity force, and mouse interaction.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-613-soft-body-character">Example 6.13: Soft-Body Character</h3>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/-1beeiwUK" data-example-path="examples/06_libraries/soft_body_character_copy"><img src="examples/06_libraries/soft_body_character_copy/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<pre class="codesplit" data-code-language="javascript">let physics;
|
||
let particles = [];
|
||
let springs = [];
|
||
|
||
function setup() {
|
||
createCanvas(640, 240);
|
||
physics = new VerletPhysics2D();
|
||
physics.setWorldBounds(new Rect(0, 0, width, height));
|
||
physics.addBehavior(new GravityBehavior(new Vec2D(0, 0.5)));
|
||
// Particles at vertices of character.
|
||
particles.push(new Particle(200, 25));
|
||
particles.push(new Particle(400, 25));
|
||
particles.push(new Particle(350, 125));
|
||
particles.push(new Particle(400, 225));
|
||
particles.push(new Particle(200, 225));
|
||
particles.push(new Particle(250, 125));
|
||
// Springs connecting vertices of character.
|
||
springs.push(new Spring(particles[0], particles[1]));
|
||
springs.push(new Spring(particles[1], particles[2]));
|
||
springs.push(new Spring(particles[2], particles[3]));
|
||
springs.push(new Spring(particles[3], particles[4]));
|
||
springs.push(new Spring(particles[4], particles[5]));
|
||
springs.push(new Spring(particles[5], particles[0]));
|
||
//{!3} Three internal springs!
|
||
springs.push(new Spring(particles[5], particles[2]));
|
||
springs.push(new Spring(particles[0], particles[3]));
|
||
springs.push(new Spring(particles[1], particles[4]));
|
||
}
|
||
|
||
function draw() {
|
||
background(255);
|
||
physics.update();
|
||
//{!7} Draw the character as one shape.
|
||
fill(127);
|
||
stroke(0);
|
||
beginShape();
|
||
for (let particle of particles) {
|
||
vertex(particle.x, particle.y);
|
||
}
|
||
endShape(CLOSE);
|
||
//{!6} Mouse interaction
|
||
if (mouseIsPressed) {
|
||
particles[0].lock();
|
||
particles[0].x = mouseX;
|
||
particles[0].y = mouseY;
|
||
particles[0].unlock();
|
||
}
|
||
}</pre>
|
||
<p>For the soft-body character example, you’ll notice that I’m no longer drawing all the elements of the physics simulation on the canvas! The <code>show()</code> method of the particles isn’t called, and the internal springs that give the character its structure are not rendered with lines. In fact, the springs themselves are never referenced after <code>setup()</code>, since the character’s shape is constructed from its particle positions. As such, the springs array isn’t strictly needed in this example, although I do find it useful to have, considering it may be necessary for enhancing the sketch in the future.</p>
|
||
<p>Considering the drawing as its own problem, distinct from the character’s skeletal structure, also opens up possibilities for adding other design elements such as eyes or antennae. These creative enhancements don’t need to be directly connected to the physics of the character, although they can be if you choose to do so!</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-611">Exercise 6.11</h3>
|
||
<p>Design your own soft-body character with additional vertices and connections. What other design elements can you add? What other forces and interactions can you incorporate?</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/hQw1Ih97c" data-example-path="examples/06_libraries/exercise_6_11_soft_body_character_enhanced"><img src="examples/06_libraries/exercise_6_11_soft_body_character_enhanced/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h3 id="a-force-directed-graph">A Force-Directed Graph</h3>
|
||
<p>Have you ever had the following thought? “I have a whole bunch of stuff I want to draw, and I want all that stuff to be spaced out evenly in a nice, neat, organized manner. Otherwise, I’ll have trouble sleeping at night.”</p>
|
||
<p>This isn’t an uncommon problem in computational design. One solution is a <strong>force-directed graph</strong>, a visualization of elements—let’s call them <em>nodes</em>—whose positions aren’t manually assigned. Instead, the nodes arrange themselves according to a set of forces. While any forces can be used, a classic approach uses spring forces: each node is connected to every other node with a spring, such that when the springs reach equilibrium, the nodes are evenly spaced (see Figure 6.18). Sounds like a job for Toxiclibs.js!</p>
|
||
<figure>
|
||
<div data-type="embed" data-p5-editor="https://editor.p5js.org/natureofcode/sketches/0RqAtTXwI" data-example-path="examples/06_libraries/figure_6_18"><img src="examples/06_libraries/figure_6_18/screenshot.png"></div>
|
||
<figcaption>Figure 6.18: In this force-directed graph, clusters of particles are connected by spring forces.</figcaption>
|
||
</figure>
|
||
<p>To create a force-directed graph, I’ll first need a class to describe an individual node in the system. Because the term <em>node</em> is associated with the JavaScript framework Node.js, I’ll stick with the term <em>particle</em> to avoid any confusion, and I’ll continue using my <code>Particle</code> class from the earlier soft-body examples.</p>
|
||
<p>Next, I’ll encapsulate a list of <em>N</em> particles into a new class called <code>Cluster</code> that represents the graph as a whole. The particles all start out near the center of the canvas:</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript">class Cluster {
|
||
// A cluster is initialized with <em>N</em> nodes.
|
||
constructor(n, length) {
|
||
this.particles = [];
|
||
for (let i = 0; i < n; i++) {
|
||
// 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>
|
||
</div>
|
||
<p>Let’s assume that the <code>Cluster</code> class also has a <code>show()</code> method to draw all the particles in the cluster and that I’ll create a new <code>Cluster</code> object in <code>setup()</code> and render it in <code>draw()</code>. If I ran the sketch as is, nothing would happen. Why? Because I have yet to implement the whole force-directed graph part! I need to connect every single node to every other node with a spring. This is somewhat similar to creating a soft-body character, but rather than handcraft a skeleton, I want to write an algorithm to make all the connections automatically.</p>
|
||
<p>What exactly do I mean by that? Say I have five <code>Particle</code> objects: 0, 1, 2, 3, and 4. Figure 6.19 illustrates the connections.</p>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_20.png" alt="Figure 6.19: A network graph showing each of the five nodes connected to every other node">
|
||
<figcaption>Figure 6.19: A network graph showing each of the five nodes connected to every other node</figcaption>
|
||
</figure>
|
||
<p>Notice two important details about the list of connections:</p>
|
||
<ul>
|
||
<li><strong>No particle is connected to itself.</strong> That is, 0 isn’t connected to 0, 1 isn’t connected to 1, and so on.</li>
|
||
<li><strong>Connections aren’t repeated in reverse.</strong> For example, if 0 is connected to 1, I don’t need to explicitly say that 1 is also connected to 0. I already know this, based on the definition of how a spring works!</li>
|
||
</ul>
|
||
<p>How do I write the code to make these connections for <em>N</em> particles? Look at the four columns illustrated in Figure 6.19. They iterate all the connections starting from particles 0 up to 3. This tells me that I need to access each particle in the list from 0 to <em>N</em> – 1:</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript"> for (let i = 0; i < this.particles.length - 1; i++) {
|
||
// Use the variable <code>particle_i</code> to store the particle reference.
|
||
let particle_i = this.particles[i];</pre>
|
||
</div>
|
||
<p>Now look at the connections listed in Figure 6.19. I need to connect node 0 to nodes 1, 2, and 3. For node 1, I connect 2 and 3. For node 2, only 3. In general, for every node <code>i</code>, I need to iterate from <code>i + 1</code> all the way until the end of the array. I’ll use the counter variable <code>j</code> for this purpose:</p>
|
||
<div class="snip-below">
|
||
<pre class="codesplit" data-code-language="javascript"> //{!1} Look at how <code>j</code> starts at <code>i + 1</code>.
|
||
for (let j = i + 1; j < this.particles.length; j++) {
|
||
let particle_j = this.particles[j];</pre>
|
||
</div>
|
||
<p>For every pair of particles <code>i</code> and <code>j</code>, I can then create a spring. I’ll go back to using <code>VerletSpring2D</code> directly, but you could also incorporate a custom <code>Spring</code> class:</p>
|
||
<div class="snip-above">
|
||
<pre class="codesplit" data-code-language="javascript"> //{!1} The spring connects particles <code>i</code> and <code>j</code>.
|
||
physics.addSpring(new VerletSpring2D(particle_i, particle_j, length, 0.01));
|
||
}
|
||
}</pre>
|
||
</div>
|
||
<p>Assuming those connections are made in the <code>Cluster</code> constructor, all that’s left is to create the cluster in <code>setup()</code> and call <code>show()</code> in the <code>draw()</code> loop!</p>
|
||
<div data-type="example">
|
||
<h3 id="example-614-cluster">Example 6.14: Cluster</h3>
|
||
<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"><img src="examples/06_libraries/6_13_force_directed_graph/screenshot.png"></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(floor(random(2, 20)), random(10, height / 2));
|
||
}
|
||
|
||
function draw() {
|
||
physics.update();
|
||
background(255);
|
||
//{!1} Draw the cluster.
|
||
cluster.show();
|
||
}</pre>
|
||
<p>This example illustrates a force-directed graph but does not involve any actual data! Here, the number of nodes in each cluster and the equilibrium length between the nodes are assigned randomly, and the spring strength has a constant value of <code>0.01</code>. In a real-world application, these values could be determined based on your specific data, hopefully resulting in a meaningful visualization of the relationships within the data.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-612">Exercise 6.12</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-613">Exercise 6.13</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_13_force_directed_graph"><img src="examples/06_libraries/exercise_6_13_force_directed_graph/screenshot.png"></div>
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
<h2 id="attraction-and-repulsion-behaviors">Attraction and Repulsion Behaviors</h2>
|
||
<p>When it came time to create an attraction example for Matter.js, I showed how the <code>Matter.Body</code> class includes an <code>applyForce()</code> method. All I then needed to do was calculate the attraction force <span data-type="equation">F_g = (G \times m_1 \times m_2) \div d^2</span> as a vector and apply it to the body. Similarly, the Toxiclibs.js <code>VerletParticle2D</code> class also includes a method called <code>addForce()</code> that can apply any calculated force to a particle.</p>
|
||
<p>However, Toxiclibs.js takes this idea one step further by offering built-in functionality for common forces (called behaviors) such as attraction! For example, if you add an <code>AttractionBehavior</code> object to a particular <code>VerletParticle2D</code> object, all other particles in the physics world will experience an attraction force toward that particle.</p>
|
||
<p>Say I create an instance of my <code>Particle</code> class (which extends the <code>VerletParticle2D</code> class):</p>
|
||
<pre class="codesplit" data-code-language="javascript">let particle = new Particle(320, 120);</pre>
|
||
<p>Now I can create an <code>AttractionBehavior</code> 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 that the behavior is created with three arguments: a particle to assign it to, a distance, and a strength. The distance specifies the range within which the behavior will be applied. In this case, only particles within 20 pixels will experience the attraction force. The strength, of course, specifies how strong the force is.</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>Now everything that lives in the physics simulation will always be attracted to that particle, as long as it’s within the distance threshold.</p>
|
||
<p>The <code>AttractionBehavior</code> class is a very powerful tool. For example, even though Toxiclibs.js doesn’t automatically handle collisions like Matter.js does, you can create a collision-like simulation by adding an <code>AttractionBehavior</code> with a negative strength—a repulsive behavior—to each and every particle. If the force is strong and activated only within a short range (scaled to the particle’s radius), the result is much like a rigid-body collision. Here’s how to modify the <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;
|
||
// Every time a <code>Particle</code> is made, an <code>AttractionBehavior</code> 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 <a href="/forces#">Chapter 2</a> with a single <code>Attractor</code> object that exerts an attraction behavior anywhere on the canvas. Even though the attractor is centered, I’m using a distance threshold of the full <code>width</code> to account for any movement of the attractor, and for particles located outside the canvas boundaries.</p>
|
||
<div data-type="example">
|
||
<h3 id="example-615-attraction-and-repulsion-behaviors">Example 6.15: 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"><img src="examples/06_libraries/6_14_attraction_behaviors/screenshot.png"></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;
|
||
// Attract all particles always.
|
||
physics.addBehavior(new AttractionBehavior(this, width, 0.1));
|
||
// Repel particles that come within its radius.
|
||
physics.addBehavior(new AttractionBehavior(this, this.r, -10));
|
||
// 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 <a href="/autonomous-agents#spatial-subdivisions">“Spatial Subdivisions”</a>, Toxiclibs.js projects with large numbers of particles interacting with one another can run very slowly because of the <span data-type="equation">N^2</span> nature of the algorithm (every particle checking every other particle). To speed up the simulation, you could use the manual <code>addForce()</code> method in conjunction with a binning algorithm. Keep in mind, this would also require you to manually calculate the attraction force, as the built-in <code>AttractionBehavior</code> would no longer apply.</p>
|
||
<div data-type="exercise">
|
||
<h3 id="exercise-614">Exercise 6.14</h3>
|
||
<p>Use <code>AttractionBehavior</code> in conjunction with spring forces.</p>
|
||
</div>
|
||
<div data-type="project">
|
||
<h3 id="the-ecosystem-project-7">The Ecosystem Project</h3>
|
||
<p>Take your system of creatures from Chapter 5 and use a physics engine to drive their motion and behaviors. Here are some possibilities:</p>
|
||
<ul>
|
||
<li>Use Matter.js to allow collisions between creatures. Consider triggering an event when two creatures collide.</li>
|
||
<li>Use Matter.js to augment the design of your creatures. Build a skeleton with distance joints or make appendages with revolute joints.</li>
|
||
<li>Use Toxiclibs.js to augment the design of your creature. Use a chain of Toxiclibs.js particles for tentacles or a mesh of springs as a skeleton.</li>
|
||
<li>Use Toxiclibs.js to add attraction and repulsion behaviors to your creatures.</li>
|
||
<li>Use spring (or joint) connections between objects to control their interactions. Create and delete these springs on the fly. Consider making these connections visible or invisible to the viewer.</li>
|
||
</ul>
|
||
<figure>
|
||
<img src="images/06_libraries/06_libraries_21.png" alt="">
|
||
<figcaption></figcaption>
|
||
</figure>
|
||
</div>
|
||
</section> |