Extend the bouncing ball with vectors example into 3D. Can you get a sphere to bounce around a box?
-
1.4 More Vector Math
+
More Vector Math
Addition was really just the first step. There are many mathematical operations commonly used with vectors. Here’s a comprehensive table of the operations available as functions in the p5.Vector class.
This chapter isn’t suggesting that every good p5.js simulation needs to involve gravitational attraction. Rather, you should be thinking creatively about how to design your own rules to drive the behavior of objects, using my approach to simulating gravitational attraction as a model. For example, what happens if you design an attractive force that gets weaker as the objects get closer, and stronger as the objects get farther apart? Or what if you design your attractor to attract faraway objects, but repel close ones?
-
2.10 The n-Body Problem
+
The n-Body Problem
I started exploring gravitational attraction with a simple scenario, one object attracts another object, then moved on to the slightly more complex one object attracts many objects. A logical next step is to explore what happens when many objects attract many objects!
To begin, while it’s so far been helpful to have separate Mover and Attractor classes, this distinction is actually a bit misleading. After all, according to Newton's third law, all forces occur in pairs: if an attractor attracts a mover, then that mover should also attract the attractor. Instead of two different classes here, what I really want is a single type of thing—called, for example, a Body—with every body attracting every other body.
The scenario being described here is commonly referred to as the n-body problem. It involves solving for the motion of a group of objects that interact via gravitational forces. The two-body problem is a famously “solved” problem, meaning the motions can be precisely computed with mathematical equations when only two bodies are involved. However, adding one more body turns the two-body problem into a three-body problem, and suddenly no formal solution exists.
Before running to see the example online, take a look at this constrainLength method and see if you can fill in the blanks.
constrainLength(bob, minlen, maxlen) {
//{!1} Vector pointing from Bob to Anchor
- let direction = p5.Vector.sub(bob.position, this.anchor);
+ let direction = p5.Vector.sub(bob.position, this.anchor);
let length = direction.mag();
//{!1} Is it too short?
if (length < minlen) {
- direction.setMag(minlen);
+ direction.setMag(minlen);
//{!1} Keep position within constraint.
- bob.position = p5.Vector.add(this.anchor, direction);
+ bob.position = p5.Vector.add(this.anchor, direction);
bob.velocity.mult(0);
//{!1} Is it too long?
- } else if (length > maxlen) {
- direction.setMag(maxlen);
+ } else if (length > maxlen) {
+ direction.setMag(maxlen);
//{!1} Keep position within constraint.
- bob.position = p5.Vector.add(this.anchor, direction);
+ bob.position = p5.Vector.add(this.anchor, direction);
bob.velocity.mult(0);
}
}
let x = i * width / cols;
-let y = j * height / rows;
-flowfield[i][j] = createVector(width/2 - x, height/2 - y);
-flowfield[i][j].rotate(PI / 2);
+
let x = i * width / cols;
+let y = j * height / rows;
+flowfield[i][j] = createVector(width/2 - x, height/2 - y);
+flowfield[i][j].rotate(PI / 2);
Now that I have a two-dimensional array storing the flow field vectors, I need a way for the vehicle to look up its desired velocity. For that, I simply divide the vehicle’s x and y position by the resolution of the grid. This gives me the indices of the desired vector in the 2D array. For example, if the resolution is 10 and the vehicle is at (100, 50), I’ll want to look up column 10 and row 5.
The bad news: it’s not quite as simple as the pseudocode might lead you to believe. Actually making the stuff that goes into the Matter.js world involves several steps related to how different kinds of shapes are built and configured. 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:
Engine. The entity that manages the physics simulation itself. The engine holds on to the “world” of the simulation as well as various properties about how the world is updated over time.
-
Body. Serves as the primary element in the world, corresponding to the physical objects being simulated. It has a position. It has a velocity. Sound familiar? It’s basically another version of the class I’ve been building all throughout Chapters 1 through 5. It also has geometry to define its shape. It’s important to note that “body” is a generic term that physics engines use to describe a “thing” in the world (similarly to the term “particle”); it isn’t related to an anthropomorphic body.
+
Bodies. Serve as the primary elements in the world, corresponding to the physical objects being simulated. A body has a position, and it has a velocity. Sound familiar? It’s basically another version of the class I’ve been building all throughout Chapters 1 through 5. It also has geometry to define its shape. It’s important to note that “body” is a generic term that physics engines use to describe a “thing” in the world (similarly to the term “particle”); it isn’t related to an anthropomorphic body.
Composite. A container that allows for the creation of complex entities (made up of multiple bodies). The world itself is an example a composite, and every body created has be added to the world.
-
Constraint. Acts as a connection between two bodies.
+
Constraints. Act as connections between bodies.
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.
Once a body is added 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 for you without you having to lift a finger! But how do you actually draw the body?
@@ -465,7 +465,7 @@
Exercise 6.2
Drag the mouse to add boxes.
-
Start with the code for 6.2 Boxes Exercise 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 above. Feel free to be creative in how you draw the boxes!
+
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 above. Feel free to be creative in how you draw the boxes!
Static Matter.js Bodies
In the example I just created, the Box objects appear at the mouse position and fall downwards due to 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.
@@ -568,7 +568,7 @@
Example 6.4: Polygon Shapes
// End the shape, closing it
endShape(CLOSE);
}
-
The Matter.js body stores the array of its vertex positions inside a vertices property. Notice how I can then use a for…of loop to cycle through the vertices in between beginShape() and endShape().
+
The Matter.js body stores the array of its vertex positions inside a vertices property. Notice how I can then use a for...of loop to cycle through the vertices in between beginShape() and endShape().
Exercise 6.3
Using Bodies.fromVertices(), create your own polygon design (remember, it must be convex). Some possibilities are shown below.
@@ -604,7 +604,7 @@
Exercise 6.3
//{!2} Adding an offset from the x position of the lollipop's "stick"
let offset = w / 2;
let part2 = Bodies.circle(x + offset, y, r);
-
Because there are two “parts” to the lollipop’s body, drawing it is a bit trickier. There are multiple approaches I could take. For example, I could use the body’s vertices array and draw the lollipop as a custom shape, much like in Example 6.4 (Every body stores an array of vertices, even if it wasn’t created with fromVertices()). 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.
+
Because there are two “parts” to the lollipop’s body, drawing it is a bit trickier. There are multiple approaches I could take. For example, I could use the body’s vertices array and draw the lollipop as a custom shape, much like in Example 6.4. (Every body stores an array of vertices, even if it wasn’t created with the fromVertices() 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.
Example 6.5: Multiple Shapes on One Body
-
A distance constraint is a conection of fixed length between two bodies. The constraint is attached to each body at a specified anchor, a point relative to the body’s center (see Figure 6.8). However, it’s important to note that the “fixed” length can exhibit variability, depending on the constraint’s stiffness.
+
A distance constraint is a connection of fixed length between two bodies. The constraint is attached to each body at a specified anchor, a point relative to the body’s center (see Figure 6.8). Depending on the constraint’s stiffness, the “fixed” length of the constraint can exhibit variability.
Defining a constraint uses a similar methodology as creating bodies, only you need to have two bodies ready to go. Let’s assume there are two Particle objects that each store a reference to a Matter.js body in a property called body. I’ll call them particleA and particleB.
let particleA = new Particle();
let particleB = new Particle();
@@ -695,7 +695,7 @@
Distance Constraints
let constraint = Constraint.create(options);
//{!1} Don't forget to add the constraint to the world!
Composite.add(engine.world, constraint);
-
Just like with a body, I can add a constraint to a class to encapsulate and manage the relationship between multiple bodies. Here’s an example of a class that represents a swinging pendulum (mirroring Example 3.x from Chapter 3).
+
I can include a constraint to a class to encapsulate and manage the relationship between multiple bodies. Here’s an example of a class that represents a swinging pendulum (mirroring Example 3.x from Chapter 3).
Example 6.6: Matter.js Pendulum
-
Notice in particular how toxiclibs.js vectors are created by calling the Vec2D constructor with the new keyword, rather than by using a factory method like Matter.Vector()and createVector().
+
Notice in particular how toxiclibs.js vectors are created by calling the Vec2D constructor with the new keyword, rather than by using a factory method like Matter.Vector() or createVector().
The Physics World
The classes to describe the world and its particles and springs in toxiclibs.js are found in toxi.physics2d. I’m also going to use a Rect object (to describe a generic rectangle boundary) and GravityBehavior to apply a global gravity force to the world. Including Vec2D, I now have all the following class aliases.
@@ -1210,7 +1210,7 @@
The Physics World
}
Now all that remains is to populate the world.
Particles
-
The toxiclibs.js equivalent of a Matter.js body—a thing that exists in the world and experiences physics—is a particle, as represented by the VerletParticle2D class. However, unlike Matter.js bodies, toxiclibs particles do not store geometry.
+
The toxiclibs.js equivalent of a Matter.js body—a thing that exists in the world and experiences physics—is a particle, as represented by the VerletParticle2D class. However, unlike Matter.js bodies, toxiclibs particles don’t store geometry.
How should I integrate toxiclibs.js particles into a p5.js sketch? In the Matter.js examples, I created my own class (called, say, Particle) and included a reference to a Matter.js body.
class Particle {
constructor(x, y, r) {
@@ -1272,7 +1272,7 @@
Springs
let spring = new VerletSpring2D(particle1, particle2, length, strength);
Just as with particles, in order for the connection to actually be part of the physics world, it must be explicitly added to the world.
physics.addSpring(spring);
-
I have almost everything I need to build a simple first toxiclibs example: two particles connected to form a springy pendulum. There’s one more element I want to ad, however: mouse interactivity.
+
I have almost everything I need to build a simple first toxiclibs example: two particles connected to form a springy pendulum. There’s one more element I want to add, however: mouse interactivity.
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, this isn’t a problem. If I want to, I can set a particle’s (x, y) position manually. However, before doing so, it’s generally a good idea to call the particle’s lock() method, which fixes the particle in place. This is identical to setting the isStatic property to true in Matter.js.
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 unlock() method) so it can start moving again from its new location. For example, consider the scenario where I want to reposition a particle whenever the mouse is pressed.
if (mouseIsPressed) {
@@ -1561,7 +1561,7 @@
Example 6.x Soft Body Character
particles[0].unlock();
}
}
-
For the soft body character example, you’ll notice that I’m no longer drawing all the elements on the canvas! The show() function of the particles is not 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 setup()since the character's shape is constructed from its particle positions. I do think, however, it's useful to keep the array as part of the example, considering it may be necessary for enhancing the sketch in the future.
+
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 show() 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 setup(), 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 fin it useful to have, considering it may be necessary for enhancing the sketch in the future.
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!
@@ -1678,7 +1678,7 @@
Example 6.13: Cluster
//{!1} Draw cluster
cluster.show();
}
-
This example illustrates the concept of a force-directed graph, but it 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 0.01. 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 itself.
+
This example illustrates the concept of a force-directed graph, but it 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 0.01. 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 itself.
Exercise 6.11
Design a cluster-like structure as a skeleton for a cute, cuddly, squishy creature. Add gravity and mouse interaction.
I can now remake the attraction example from Chapter 2 with a single Attractor 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 width to account for any movement of the attractor, or particles being outside the canvas boundaries.
+
I can now remake the attraction example from Chapter 2 with a single Attractor 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 width to account for any movement of the attractor, and for particles located outside the canvas boundaries.
Example 6.14: Attraction (and Repulsion) Behaviors
Just as discussed in Chapter 5’s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles interacting with each other can run very slow due to N^2 nature of the algorithm (every particle checking every other particle). To speed up the simulation, you could potentially use the manual addForce()method in conjunction with an implemented binning algorithm. Keep in mind, this would also require you to manually calculate the attraction force, as the built-in AttractionBehavior would no longer apply.
+
Just as discussed in Chapter 5’s section on spatial subdivision and “binning”, toxiclibs.js projects with large numbers of particles interacting with each other can run very slow due to the N^2 nature of the algorithm (every particle checking every other particle). To speed up the simulation, you could potentially use the manual addForce()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 AttractionBehavior would no longer apply.
Exercise 6.13
Use AttractionBehavior in conjunction with spring forces.
// [TBD] Global variables needed for the GA
+
// Mutation rate
let mutationRate = 0.01;
// Population Size
@@ -577,15 +577,15 @@
Example 9.1: Genetic
function setup() {
createCanvas(640, 360);
- //{!3} Step 1: Initialize Population
- for (let i = 0; i < populationSize; i++) {
+ //{!3} Step 1: Initialize Population
+ for (let i = 0; i < populationSize; i++) {
population[i] = new DNA(target.length);
}
}
function draw() {
- // Step 2: Selection
+ // Step 2: Selection
//{!3} Step 2a: Calculate fitness.
for (let i = 0; i < population.length; i++) {
population[i].calculateFitness(target);
@@ -602,7 +602,7 @@
Example 9.1: Genetic
}
}
- // Step 3: Reproduction
+ // Step 3: Reproduction
for (let i = 0; i < population.length; i++) {
let aIndex = floor(random(matingPool.length));
let bIndex = floor(random(matingPool.length));
@@ -1294,7 +1294,7 @@
Exercise 9.12
-
9.12 Interactive Selection
+
Interactive Selection
In addition to Evolving Virtual Creatures, Sims is also well known for his museum installation Galapagos. Originally installed in the Intercommunication Center in Tokyo in 1997, the installation consists of twelve monitors displaying computer-generated images. These images evolve over time, following the genetic algorithm steps of selection and reproduction. The innovation here is not the use of the genetic algorithm itself, but rather the strategy behind the fitness function. In front of each monitor is a sensor on the floor that can detect the presence of a visitor viewing the screen. The fitness of an image is tied to the length of time that viewers look at the image. This is known as interactive selection, a genetic algorithm with fitness values assigned by people.
This strategy is far from being confined to art installations and is quite prevalent in the digital age of user-generated ratings and reviews. Could you imagine evolving the perfect song based on your Spotify ratings? Or the ideal book according to Goodreads reviews?