Particle Motion
Learning Objectives
- Modify an object definition to give particles more properties and update the particle simulation to account for these new properties.
- Modify the program so that the particles shrink in size over time based on their age.
- Create a new particle simulator that includes a repeller, a point that repels particles with some force.
Participation
In this lab/recitation, you will write some p5.js programs that use the techniques you’ve learned in class so far. The goal here is not just to get the programs done as quickly as possible, but also to help your peers if they get stuck and to discuss alternate ways to solve the same problems. You will be put into breakout rooms in Zoom to work on your code and then discuss your answers with each other, or to help each other if you get stuck. Then we will return to discuss the results together as a group.
For each problem, you will start with a copy of the uncompressed template-p5only.zip in a folder named lab-09. Rename the folder as andrewID-09-A, andrewID-09-B, etc. as appropriate.
A. Color Particles
Modify the “Mouse Particles” particle system (the last example from class yesterday) so that particles have a variety of colors and sizes.
- Since each particle has two new properties, size and color, modify the makeParticle function so that each particle has two more properties. Set the size to a random value between 5 and 15 and the color to a random color but not black or white). Note that each particle should have a random size and color but this should not change for each particle throughout the program run. In other words, your objects shouldn’t flash different colors or change sizes as they’re moving.
- Which other function(s) must be modified? Make the appropriate modifications and test your program.
For convenience, here’s a copy of the code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
var gravity = 0.3; // downward acceleration var spring = 0.7; // how much velocity is retained after bounce var drag = 0.0001; // drag causes particles to slow down var np = 100; // how many particles function particleStep() { this.age++; this.x += this.dx; this.y += this.dy; if (this.x > width) { // bounce off right wall this.x = width - (this.x - width); this.dx = -this.dx * spring; } else if (this.x < 0) { // bounce off left wall this.x = -this.x; this.dx = -this.dx * spring; } if (this.y > height) { // bounce off bottom this.y = height - (this.y - height); this.dy = -this.dy * spring; } else if (this.y < 0) { // bounce off top this.y = -this.y; this.dy = -this.dy * spring; } this.dy = this.dy + gravity; // force of gravity // drag is proportional to velocity squared // which is the sum of the squares of dx and dy var vs = Math.pow(this.dx, 2) + Math.pow(this.dy, 2); // d is the ratio of old velocty to new velocity var d = vs * drag; // d goes up with velocity squared but can never be // so high that the velocity reverses, so limit d to 1 d = min(d, 1); // scale dx and dy to include drag effect this.dx *= (1 - d); this.dy *= (1 - d); } function particleDraw() { point(this.x, this.y); } // create a "Particle" object with position and velocity function makeParticle(px, py, pdx, pdy) { p = {x: px, y: py, dx: pdx, dy: pdy, age: 0, stepFunction: particleStep, drawFunction: particleDraw } return p; } var particles = []; function setup() { createCanvas(400, 400); for (var i = 0; i < np; i++) { // make a particle var p = makeParticle(200, 200, random(-50, 50), random(-50, 50)); // push the particle onto particles array particles.push(p); } frameRate(10); } // draw all particles in the particles array // function draw() { background(230); stroke(0); strokeWeight(10); if (mouseIsPressed) { var newp = makeParticle(mouseX, mouseY, random(-10, 10), random(-10, 0)); particles.push(newp); } // newParticles will hold all the particles that we want to // retain for the next call to draw() -- we will retain particles // if the age is < 200 (frames). Initially, newParticle is empty // because we have not found any "young" particles yet. newParticles = []; for (var i = 0; i < particles.length; i++) { // for each particle var p = particles[i]; p.stepFunction(); p.drawFunction(); // since we are "looking" at every particle in order to // draw it, let's use the opportunity to see if particle[i] // is younger than 200 frames. If so, we'll push it onto the // end of newParticles. if (p.age < 200) { newParticles.push(p); } } // now, newParticles has EVERY particle with an age < 200 frames. // these are the particles we want to draw next time, so assign // particles to this new array. The old value of particles, i.e. // the entire array, is simply "lost" -- Javascript will reclaim // and reuse the memory since that array is no longer needed. particles = newParticles; } |
Here is one frame of a possible solution:
B. Shrinking Particles
Code the program from the previous problem into a new project. In this exercise, you will decrease the size of the particle as it gets older. After the particle ages every 40 steps, shrink the size of the particle by 20%. Think: where in the code would you make this update?
Test your program to make sure it works correctly. (You may want to reduce the number of particles so you can see what’s happening more easily.)
C. Repelling Particles
Let (rpx, rpy) represent a location that repels particles. The value rpc represents a fixed, constant factor that you should adjust to get the right “look”: if rpc is too small, particles will not be noticeably affected by the force. If is extremely large, particles will all be immediately pushed away, possibly “running” to the corners to “get away.”
- Copy the original mouse particles program into a new project. You don’t need the colors and sizes this time. In this new project, comment out (or remove) the code in particleStep that deals with drag and gravity. You don’t need them for this problem.
- Add global variables for rpx, rpy, and rpc. Set rpx = 200, rpy = 100, and try rpc = 1000 for now. You can change it later.
- In the particleStep function, after you compute the location of the particle after a possible bounce, compute the effect of the repeller on the particle. To implement repulsion, you want to accelerate away from (rpx, rpy) by the repulsive force. Let’s say the distance from the particle to the repeller is dp (use the dist function to compute dp) and the repelling force is:
f = rpc / (Math.pow(dp, 2))
Then the direction of the force is:
dirx = (x – rpx) / dp
diry = (y – rpy) / dp
Add f * dirx to the x coordinate of the particle, and add f * diry to the y coordinate of the particle. - In the draw function, draw a small red square at the location (rpx, rpy) to give the impression that particles are being repelled by the object.
- Between runs of your program, modify rpc until you see the particles being repelled from the square.
This challenge is inspired by “Example 4.7: ParticleSystem with repeller” from Chapter 4 of Daniel Shiffman, The Nature of Code.