If you're seeing this message, it means we're having trouble loading external resources on our website.

If you're behind a web filter, please make sure that the domains *.kastatic.org and *.kasandbox.org are unblocked.

Course: Computer programming - JavaScript and the web>Unit 5

Lesson 8: Particle Systems

A single particle

Before we can create an entire `ParticleSystem`, we have to create an object that will describe a single particle. The good news: we've done this already. Our `Mover` object from the Forces section serves as the perfect template. For us, a particle is an independent body that moves about the screen. It has `location`, `velocity`, and `acceleration`, a constructor to initialize those variables, and functions to `display()` itself and `update()` its location.
``````// A simple Particle object
var Particle = function(position) {
this.acceleration = new PVector();
this.velocity = new PVector();
this.position = position.get();
};

Particle.prototype.update = function(){
};

Particle.prototype.display = function() {
stroke(0, 0, 0);
fill(175, 175, 175);
ellipse(this.position.x, this.position.y, 8, 8);
};``````
This is about as simple as a particle can get. From here, we could take our particle in several directions. We could add an `applyForce()` method to affect the particle’s behavior (we’ll do precisely this in a future example). We could add variables to describe color and shape, or use `image()` to draw the particle. For now, however, let’s focus on adding just one additional detail: lifespan.
Typical particle systems involve something called an emitter. The emitter is the source of the particles and controls the initial settings for the particles, location, velocity, etc. An emitter might emit a single burst of particles, or a continuous stream of particles, or both. The point is that for a typical implementation such as this, a particle is born at the emitter but does not live forever. If it were to live forever, our program would eventually grind to a halt as the number of particles increased to an unwieldy number over time. As new particles are born, we need old particles to die. This creates the illusion of an infinite stream of particles, and the performance of our program does not suffer.
There are many different ways to decide when a particle dies. For example, it could come into contact with another object, or it could simply leave the screen. For our first `Particle` object, however, we’re simply going to add a `timeToLive` property. It will act as a timer, counting down from 255 to 0, at which point we'll consider the particle to be "dead." And so we expand the `Particle` object as follows:
``````// A simple Particle object
var Particle = function(position) {
this.acceleration = new PVector();
this.velocity = new PVector();
this.position = position.get();
this.timeToLive = 255;
};

Particle.prototype.update = function(){
this.timeToLive -= 2;
};

Particle.prototype.display = function() {
stroke(255, 255, 255, this.timeToLive);
fill(127, 127, 127, this.timeToLive);
ellipse(this.position.x, this.position.y, 8, 8);
};``````
The reason we chose to start the `timeToLive` at 255 and count down to 0 is for convenience. With those values, we can use `timeToLive` as the alpha transparency for the ellipse as well. When the particle is “dead” it will also have faded away onscreen.
With the addition of the `timeToLive` property, we’ll also need one additional method—a function that can be queried (for a true or false answer) as to whether the particle is alive or dead. This will come in handy when we are writing the `ParticleSystem` object, whose task will be to manage the list of particles themselves. Writing this function is pretty easy; it just needs to return true if the value of `timeToLive` is less than 0.
``````Particle.prototype.isDead = function() {
return this.timeToLive < 0;
};``````
Before we get to the next step of making many particles, it’s worth taking a moment to make sure our particle works correctly and create a sketch with one single `Particle` object. Here is the full code below, with two small additions. We add a convenience method called `run()` that simply calls both `update()` and `display()` for us. In addition, we give the particle a random initial velocity as well as a downward acceleration (to simulate gravity).
Now that we have an object to describe a single particle, we’re ready for the next big step. How do we keep track of many particles, when we can’t ensure exactly how many particles we might have at any given time?

Want to join the conversation?

• Why cant the `Particle.prototype.isDead` function just return a boolean value, like this,
``Particle.prototype.isDead = function() {    return this.timeToLive <= 0;}``

instead of having a series of uneeded `if` statements?
• From the author:I prefer your version as well, I'll change the code to that. Thanks for asking!
• Just curious, what does this.position = position.get(); do?
• `PVector.get()` creates a copy of the vector object.
Then that copy is assigned to the position of the particle.

So the particle now has a position with the same values, but it can be changed independently.
If you didn't use get() to make a copy, then you would not be able to change one position without also changing the other. Too bad KA doesn't teach why.
• step 3 im getting "Make sure you also change the leaf's angular velocity and acceleration when it hits the ground! "
now ive applied it to the draw , update, display, im not sure where im going wrong?
` if(this.position.y < height){ this.velocity.set(0,0); this.acceleration.set(0,0); }`
• Does `this.position.y < height` mean that the leaf have not still hit the ground yet?

By the way, according to the hint, you should change angular velocity and angular acceleration of the leaf. It should stop moving or rotating when hitting the ground.
• For step two on the challenge: I've tried using
``if (leaves[i].position.y >= height) {    leaves[i].position.y = height;    leaves[i].velocity.y = 0;    leaves[i].acceleration = new PVector();}``

in a couple of variations: using `height - 40`; using `>` instead of `>=`; placing in the `draw` function at the top, at the bottom, in its own loop; placing in `Particle.prototype.update` using `this`; etc. These all work: the leaves pile up at the bottom, but I'm not getting credit by the autograder. Any tips?
• The grader wants you to use the `.set()` method.
• The autoGrader doesn't accept my changes in step one:
``var ds =[];mouseClicked=function(){    ds.push( new Particle(new PVector(mouseX,mouseY)));};draw = function() {    background(194, 231, 255);    tree.display();    for(var i=0;i<ds.length;i++){        ds[i].run();    }};``

• It wants you to declare a variable in place of leaves[i]. Basically, set a variable in the "for" loop equal to leaves[i]. Then, use the variable where the leaves[i] should have been. The grader is very picky with this challenge.
• what is the difference between these three:
var v1 = new PVector(0,1);
var v2 = v1; ...............................(1)
var v2 = v1.get(); .......................(2) &
var v2;
v2.set(v1); ...............................(3)
• 1.
``var v1 = new PVector(12, 5);var v2 = new PVector(12, 5);println(v2 === v1);``
We see that `v1` and `v2` are separate objects as witnessed by the equality operator `===`, where as
``var v1 = new PVector(12, 5);var v2 = v1;println(v2 === v1);``
show us that `v1` and `v2` refer to the same object. In the latter case, if you modify `v2` then `v1` will also show that modification.

2.
``var v1 = new PVector(3, 4);var v2 = v1.get();``
The `get` method returns a `new` PVector. So `v1` and `v2` are separate objects that have the same property values, like in the first case of example one.

3.
``var v1 = new PVector(8, 15);var v2;v2.set(v1);``
will fail since `v2` is undefined and as such has no `set` method. Try
``var v1 = new PVector(8, 15);var v2 = new PVector();v2.set(v1);``
to make the separate objects `v1` and `v2` have the same property values as in the first case of example one.
• I need help, in the next challenge, on step one. I am writing the next `code`:
``mouseClicked = function (){    leaves.push(new Particle(new PVector(mouseX, mouseY)));};draw = function() {    background(194, 231, 255);    tree.display();    for (var i=0;i<leaves.length;i++){        leaves[i].run();        }   };``

It is working, leaves are falling, but the "beaver" don't pass me into the next part of the challenge. What is wrong, please someone can help me?
• create a variable that is equal to leaves[i] and call it instead
• Why, all of the sudden, you have to include "new PVector" as part of the position argument. Did I miss something along the way?
``var Particle = function(x,y) {    this.acceleration = new PVector(0, 0.05);    this.velocity = new PVector(random(0, 1), random(0, 0));    this.position =  new PVector(x,y);};mouseClicked = function(){    var particle = (new Particle(mouseX,mouseY)); leaves.push(particle);};``
• That works just fine as well too. (Perhaps if you wanted to specify initial velocity for the particle as well, using `new Particle(x, y, vx, vy)` is less desirable compared to `new Particle(new PVector(x, y), new PVector(vx, vy))`, because the latter groups together it's components together more clearly)
• i need help on the next challenge... i looked in the comments and tried to use what they said, but am still not getting it. here is my code:

var Particle = function(position) {
this.acceleration = new PVector(0, 0.05);
this.velocity = new PVector(random(0, 1), random(0, 0));
this.position = position;
};

Particle.prototype.run = function() {
this.update();
this.display();
};

Particle.prototype.update = function(){
};

Particle.prototype.display = function() {
image(getImage("avatars/leaf-green"), this.position.x, this.position.y, 40, 40);
};

var Tree = function(position, options) {
this.position = position.get();
this.branchingFactor = 3;
this.angleBetweenBranches = 32;
this.scaleFactor = 0.7;
this.numLevels = 4;
this.baseBranchLength = 120;
};

Tree.prototype.display = function() {
var self = this;

var forward = function(distance) {
line(0, 0, 0, -distance);
translate(0, -distance);
};

var back = function(distance) {
forward(-distance);
};

var right = function(angle) {
rotate(angle * PI / 180);
};

var left = function(angle) {
right(-angle);
};

var drawTree = function(depth, length) {
if (depth === 0) {
image(getImage("avatars/leaf-green"), -10, -30, 40, 40);
return;
}
var totalAngle = self.angleBetweenBranches * (self.branchingFactor - 1);

strokeWeight(depth*5);
forward(length);
right(totalAngle / 2.0);
for (var i = 0; i < self.branchingFactor; i += 1) {
drawTree(depth - 1, length * self.scaleFactor);
left(self.angleBetweenBranches);
}
right(totalAngle / 2.0 + self.angleBetweenBranches);
back(length);
};

pushMatrix();
translate(this.position.x, this.position.y);
stroke(122, 112, 85);
drawTree(this.numLevels, this.baseBranchLength);
popMatrix();
};

var leaves = [];
var tree = new Tree(new PVector(width/2, 400));
mouseClicked = function(){
ds.push( new Particle(new PVector(mouseX,mouseY)));
};

draw = function() {
background(194, 231, 255);
tree.display();
for(var i = 0; i < leaves.length; i++) {
var fall = leaves[i];

};
``if (leaf.position.y > height - 30)``
``angleMode = "radians";var Particle = function(position) {    this.acceleration = new PVector(0, 0.05);    this.velocity = new PVector(random(0, 1), random(0, 0));    this.position = position;        this.angle = 0;    this.aVelocity = 0;    this.aAcceleration = 0.0005;};Particle.prototype.run = function() {    this.update();    this.display();};Particle.prototype.update = function(){    this.velocity.add(this.acceleration);    this.position.add(this.velocity);        this.aVelocity += this.aAcceleration;    this.angle += this.aVelocity;};Particle.prototype.display = function() {    pushMatrix();    rotate(this.angle);    image(getImage("avatars/leaf-green"), this.position.x, this.position.y, 40, 40);    popMatrix();};var leaves = [];var tree = new Tree(new PVector(width/2, 400));mouseClicked = function() {    var particle = new Particle(new PVector(mouseX, mouseY));    leaves.push(particle);};draw = function() {    background(194, 231, 255);    tree.display();    for (var i = 0; i < leaves.length; i++) {        var leaf = leaves[i];        leaf.run();        if (leaf.position.y >= height - 30) {            leaf.acceleration.set(0, 0);            leaf.velocity.set(0, 0);            resetMatrix();            leaf.aAcceleration = 0;            leaf.aVelocity = 0;        }    }};``