diff --git a/content/06_libraries.html b/content/06_libraries.html index 4102a61..63e85b9 100644 --- a/content/06_libraries.html +++ b/content/06_libraries.html @@ -79,7 +79,7 @@
draw()
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!
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 setup()
, I’m going to say to Matter, “Hello there. Here are all of the things I want in my world.” Then, in draw()
, 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?”
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. 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:
+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.
+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:
+Each key in the object literal (for example, friction
) serves as a unique identifier, and its value (0.5
) 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 options
argument is useful for configuring the body, other initial conditions, such as linear or angular velocity, can be set via static methods of the Matter.Body
class:
// Set an arbitrary initial linear and angular velocity. @@ -279,7 +284,7 @@ Composite.add(engine.world, box);
let options = { friction: 0.5, restitution: 0.8, -} +}; let ball = Bodies.circle(x, y, radius, options);
The first step is to call Matter.Render.create()
(or Render.create()
, assuming an alias). This method expects an object with the desired settings for the renderer, which I’ll call params
:
// 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);
Notice that I’m storing a reference to the p5.js canvas in the canvas
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 elt
property of a p5.js canvas object. The engine is the engine
I previously created. The Matter.js default canvas dimensions are 800×600, so if I prefer a different size, I need to configure an options
property with width
and height
.
In the example I just created, the Box
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 Box
objects? Matter.js makes this easy with the isStatic
property:
In the example I just created, the Box
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 Box
objects? Matter.js makes this easy with the isStatic
property.
// Create a fixed (static) boundary body. let options = { isStatic: true }; let boundary = Bodies.rectangle(x, y, w, h, options);@@ -549,8 +550,7 @@ let trapezoid = Bodies.trapezoid(x, y, width, height, slope);
-class CustomShape { ++};class CustomShape { constructor(x, y) { //{!6} An array of five vectors let vertices = []; @@ -719,7 +719,7 @@ let particleB = new Particle();pointB: Vector.create(0, 0), length: 100, stiffness: 0.5 -}
Technically, the only required options are bodyA
and bodyB
, 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 (0, 0)
for each relative anchor point (the body’s center), set the length
to the current distance between the bodies, and assign a default stiffness
of 0.7
. Two other notable options I didn’t include are damping
and angularStiffness
. The damping
option affects the constraint’s resistance to motion, with higher values causing the constraint to lose energy more quickly. The angularStiffness
option controls the rigidity of the constraint’s angular motion, with higher values resulting in less angular flexibility between the bodies.
Once the options are configured, the constraint can be created. As usual, this assumes another alias—Constraint
is equal to Matter.Constraint
:
let constraint = Constraint.create(options); @@ -762,8 +762,9 @@ Composite.add(engine.world, constraint);stroke(0); strokeWeight(2); - //{!1} Draw a line representing the pendulum arm. - line(this.anchor.position.x, this.anchor.position.y, this.bob.position.x, this.bob.position.y); + //{!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(); @@ -799,12 +800,12 @@ Composite.add(engine.world, constraint);
Another kind of connection between bodies common to physics engines is a revolute joint. This type of constraint connects two bodies at a common anchor point, also known as a hinge (see Figure 6.11). While Matter.js doesn’t have a separate revolute constraint, you can make one with a regular Constraint
of length 0. This way, the bodies can rotate around a common anchor point.
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:
+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.
// Create a body at a given position with width and height. let body = Bodies.rectangle(x, y, w, h); Composite.add(engine.world, body);
Next, I can create the constraint. With a length
of 0
, it needs a stiffness
of 1
; otherwise, the constraint may not be stable enough to keep the body connected at the anchor point:
// The constraint connects the body to a fixed (x, y) position with a length of 0 and stiffness of 1. +// The constraint connects the body to a fixed (x, y) position with a length of 0 and stiffness of 1. let options = { bodyA: this.body, pointB: { x: x, y: y }, @@ -1083,7 +1084,7 @@ position.add(velocity);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.
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).
Another popular integration method used in physics libraries, including both Matter.js and Toxiclibs.js, is Verlet integration. A simple way to describe Verlet integration is to think of the typical motion algorithm without explicitly storing velocity. After all, you don’t really need to store the velocity; if you always know where an object was at one point in time and where it is now, you can extrapolate its velocity. Verlet integration does precisely this, calculating velocity on the fly while the program is running, instead of maintaining a separate velocity variable.
-Verlet integration is particularly well suited for particle systems, especially those with spring connections between the particles. 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: “Advanced Character Physics” by Thomas Jakobsen.
+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: “Advanced Character Physics” by Thomas Jakobsen.
Verlet Physics with Toxiclibs.js
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:
@@ -1135,7 +1136,7 @@ position.add(velocity);
All the documentation and downloads for the library files can be found at the Toxiclibs.js website. For the examples in this book, I’ll be working with a hosted CDN version of the library referenced in index.html, just as I demonstrated earlier for Matter.js. Here’s the <script>
element to add:
<script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script>+
<script src="https://cdn.jsdelivr.net/gh/hapticdata/toxiclibsjs@0.3.2/build/toxiclibs.js"></script>
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: