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.

Main content

Particle types

Now we're going to use more advanced object-oriented programming techniques like inheritance, so you may want to review Object inheritance from the Intro to JS course and come back. Don't worry, we'll wait!
Feeling good about how inheritance works? Good, because we're going to use inheritance to make different types of Particle sub-objects, which share much of the same functionality but also differ in key ways.
Let's review a simplified Particle implementation:
var Particle = function(position) {
  this.acceleration = new PVector(0, 0.05);
  this.velocity = new PVector(random(-1, 1), random(-1, 0));
  this.position = position.get();
};

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

Particle.prototype.update = function(){
  this.velocity.add(this.acceleration);
  this.position.add(this.velocity);
};

Particle.prototype.display = function() {
  fill(127, 127, 127);
  ellipse(this.position.x, this.position.y, 12, 12);
};
Next, we create a new object type based on Particle, which we'll call Confetti. We'll start off with a constructor function that accepts the same number of arguments, and simply calls the Particle constructor, passing them along:
var Confetti = function(position) {
  Particle.call(this, position);
};
Now, in order to make sure that our Confetti objects share the same methods as Particle objects, we need to specify that their prototype should be based on the Particle prototype:
Confetti.prototype = Object.create(Particle.prototype);
Confetti.prototype.constructor = Confetti;
At this point, we have Confetti objects that act exactly the same way as Particle objects. The point of inheritance isn't to make duplicates, it's to make new objects that share a lot of functionality but also differ in some way. So, how is a Confetti object different? Well, just based on the name, it seems like it should look different. Our Particle objects are ellipses, but confetti is usually little bits of square paper, so at the very least, we should change the display method to show them as rectangles instead:
Confetti.prototype.display = function(){
  rectMode(CENTER);
  fill(0, 0, 255, this.timeToLive);
  stroke(0, 0, 0, this.timeToLive);
  strokeWeight(2);
  rect(0, 0, 12, 12);
};
Here's a program with one Particle object instance and one Confetti object instance. Notice they behave similarly but differ in their appearance:

Adding rotation

Let’s make this a bit more sophisticated. Let’s say we want to have the Confetti particle rotate as it flies through the air. We could, of course, model angular velocity and acceleration as we did in the Oscillations section. Instead, we’ll try a quick and dirty solution.
We know a particle has an x location somewhere between 0 and the width of the window. What if we said: when the particle’s x location is 0, its rotation should be 0; when its x location is equal to the width, its rotation should be equal to TWO_PI? Sound familiar? Whenever we have a value with one range that we want to map to another range, we can use the ProcessingJS map() function to easily compute the new value.
var theta = map(this.position.x, 0, width, 0, TWO_PI);
And just to give it a bit more spin, we can actually map the angle’s range from 0 to TWO_PI*2. Let’s look at how this code fits into the display() method.
Confetti.prototype.display = function(){
  rectMode(CENTER);
  fill(0, 0, 255);
  stroke(0, 0, 0);
  strokeWeight(2);
  pushMatrix();
  translate(this.position.x, this.position.y);
  var theta = map(this.position.x, 0, width, 0, TWO_PI * 2);
  rotate(theta);
  rect(0, 0, 12, 12);
  popMatrix();
};
Here's how that looks - restart it a few time to see the effect of the rotation:
We could also base the theta on the y position, which has a bit of a different effect. Why is that? Well, the particle has a non-zero constant acceleration in the y direction, which means that the y velocity is a linear function of time, and that the y position is actually a parabolic function of time. You can see what that means in the graphs below (which were generated based on the above program):
Three graphs of position (a line arcing down), velocity (a straight line from the top to bottom), and acceleration (a straight line).
That means that if we base the confetti rotation on the y position, the rotation will also be parabolic. This won't be too physically accurate since the actual rotation of confetti falling through the air is pretty complicated, but try it yourself and see how realistic it looks! Can you think of other functions which might look even more realistic?

A diverse ParticleSystem

Now, what we really want is to be able to create many Particle objects and many Confetti objects. That's what we made the ParticleSystem object for, so perhaps we can just extend it to also keep track of Confetti objects? Here's one way we could do that, copying what we did for the Particle objects:
var ParticleSystem = function(position) {
  this.origin = position;
  this.particles = [];
  this.confettis = [];
};

ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    this.confettis.push(new Confetti(this.origin));
};

ParticleSystem.prototype.run = function(){
  for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
  }
for (var i = this.confettis.length-1; i >= 0; i--) {
    var p = this.confettis[i]; p.run();
  }
};
Notice that we have two separate arrays, one for particles and one for confetti. Every time we do something to the particles array, we have to do it to the confetti array! That's annoying, because it means we have to write twice as much code, and if we change something, we have to change it in two places. We could actually avoid this duplication, because we're allowed to store objects of different types in arrays in JavaScript, and because our objects have the same interface - we're calling the run() method, and both types of objects define that interface. So, we'll go back to just storing a single array, we'll randomly decide what type of particle object to add, and we'll go back to iterating through the single array. This is a much simpler change - all we end up modifying is the addParticle method:
var ParticleSystem = function(position) {
  this.origin = position;
  this.particles = [];
};

ParticleSystem.prototype.addParticle = function() {
  var r = random(1);
  if (r < 0.5) {
    this.particles.push(new Particle(this.origin));
  } else {
    this.particles.push(new Confetti(this.origin));
  }
};

ParticleSystem.prototype.run = function(){
  for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
      this.particles.splice(i, 1);
    }
  }
};
All together now!

Want to join the conversation?

  • purple pi purple style avatar for user Armel Gandour
    what does Confetti.prototype.constructor = Confetti; do ?
    i tried to remove it and it still works.
    (41 votes)
    Default Khan Academy avatar avatar for user
  • male robot hal style avatar for user Ron Jensen
    So confetti has a much greater surface area than a particle, and a greater drag coefficient. Therefore, I would expect it would fall a little slower. Since confetti inherits the particle constructor where acceleration is set, how do I use a different random() statement to generate a different acceleration?

    This is a broadly focused question asked on a specific item, but applies in many situations, I would think.
    (11 votes)
    Default Khan Academy avatar avatar for user
  • leaf red style avatar for user Blaze
    What does Particle.call(this, position); do, literally?
    (6 votes)
    Default Khan Academy avatar avatar for user
    • old spice man green style avatar for user Bob Lyon
      It literally calls the Particle function substituting this as the value for the this that Particle typically sees. So, instead of modifying its normal this, Particle modifies the this that it was invoked with.

      Clear as mud?
      (6 votes)
  • blobby green style avatar for user bigmac5066
    I'm stuck on step one of Magical Cauldron. Can someone tell me what the mistake is in my code?


    angleMode = "radians";

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

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

    Particle.prototype.update = function(){
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.timeToLive -= 2;
    };

    Particle.prototype.display = function() {
    stroke(0, 0, 0, this.timeToLive);
    strokeWeight(2);
    fill(255, 0, 0, this.timeToLive);
    ellipse(this.position.x, this.position.y, 12, 12);
    };

    Particle.prototype.isDead = function(){
    if (this.timeToLive < 0) {
    return true;
    } else {
    return false;
    }
    };

    var Smoke = function(position){
    this.position = position.get();
    };
    Smoke.prototype.constructor = Smoke;

    Smoke.prototype = Object.create(Particle.prototype);

    Smoke.prototype.display = function(){
    var size = random(10, 40);
    noStroke();
    fill(217, 204, 204);
    ellipse(this.position.x, this.position.y, size, size);
    };

    var ParticleSystem = function(position) {
    this.origin = position.get();
    this.particles = [];
    };

    ParticleSystem.prototype.addParticle = function() {
    this.particles.push(new Particle(this.origin));
    };

    ParticleSystem.prototype.run = function(){
    for (var i = this.particles.length-1; i >= 0; i--) {
    var p = this.particles[i];
    p.run();
    if (p.isDead()) {
    this.particles.splice(i, 1);
    }
    }
    };

    var particleSystem = new ParticleSystem(new
    PVector(width/2, 280));

    draw = function() {
    background(72, 7, 105);
    particleSystem.addParticle();
    particleSystem.run();

    // The magical cauldron
    fill(36, 36, 36);
    var cauldronX1 = 150;
    var cauldronX2 = 250;
    var cauldronY = 285;
    bezier(cauldronX1, cauldronY,
    cauldronX1-100, cauldronY+145,
    cauldronX2+100, cauldronY+145,
    cauldronX2, cauldronY);
    };
    (4 votes)
    Default Khan Academy avatar avatar for user
  • blobby green style avatar for user PocVocem
    I can't satisfy grader's requirements at step 4. I have a random generated size variable, i pass this variable as width and height to imageFunctions. There are no errors, but grader do not accept this.

    var Star = function(position){
    Particle.call(this, position);
    this.size = random(10)+10;
    };

    Star.prototype = Object.create(Particle.prototype);

    Star.prototype.display = function() {
    image(getImage("cute/Star"),this.position.x, this.position.y, this.size, this.size);
    };
    (3 votes)
    Default Khan Academy avatar avatar for user
  • duskpin sapling style avatar for user Weather
    Wouldn't it be more efficient to have a "datablock" variable for each particle type?
    That way, you wouldn't need to store the gravity value and other constants separately in each particle. Something like this, keeping Particle.acceleration in case you want more forces:
    var ParticleDB = {
    gravity: new PVector(0, 0.05)
    };
    var Particle = function(position) {
    this.datablock = ParticleDB;
    this.acceleration = new PVector(0, 0);
    this.velocity = new PVector(random(-1, 1), random(-1, 0));
    this.position = position.get();
    };
    Particle.prototype.update = function() {
    this.acceleration.add(this.datablock.gravity);
    this.velocity.add(this.acceleration);
    this.position.add(this.velocity);
    this.acceleration.set(0, 0);
    };
    var Confetti = function(position) {
    Particle.call(this, position);
    datablock = ParticleDB;
    };

    Just hard-coding the gravity into Particle.update() or using a regular global variable (var gravity = new PVector(0, 0.05);) would be better for this example where both particle types fall at the same speed and don't have unique physics, but wouldn't this be better than what they did in the example program?

    And maybe you could go farther by only having the base Particle code, nothing like Confetti, and put the code for individual particle types in the datablocks too:
    var ParticleDB = {
    gravity: new PVector(0, 0.05),
    draw: function() {...}
    };
    var ConfettiDB = {
    gravity: ParticleDB.gravity, // want speed to be the same
    draw: function() {...}
    };


    The question is, which of these three ways is best?
    (2 votes)
    Default Khan Academy avatar avatar for user
    • old spice man green style avatar for user Bob Lyon
      The third suggestion is no better than the tried & true method of simply using Particle.prototype.display to draw confetti.

      Yes, some kind of "datablock" for holding constants and other internal and/or "static" data is a good idea. Simply using global variables is not a great idea due to global-name-space-pollution concerns and the inability to readily copy code here and use it elsewhere.

      A fourth approach is something I picked up by grabbing code from the web - a notion of a library. Its main attribute is that a library introduces one name to the global name space via invoking one function. Should you want/need that library in another program, a copy & paste of that one function guarantees that everything necessary is copied.

      This program, https://www.khanacademy.org/computer-programming/menger-sponge/5000305485152256 has three examples of libraries - Node, Face, Cube.
      This program https://www.khanacademy.org/computer-programming/bs-or-screens-cubed/3424538152 has quite a few libraries. One of them RMatrix2D I have reused in more than a dozen programs.
      (3 votes)
  • winston baby style avatar for user Jerry Wang
    In the next challenge, why does Oh noes guy always say : "p.run is not a function". I am so confused
    Thanks a lot in advance
    (2 votes)
    Default Khan Academy avatar avatar for user
  • piceratops seed style avatar for user Andrew  Horne
    On Step 1 of Magical Cauldron, I did exactly as the lesson showed, but it's not letting me pass. What else do I need to do?
    (1 vote)
    Default Khan Academy avatar avatar for user
    • spunky sam blue style avatar for user wowsk
      When you are calling the Particle function in your Smoke function, you need to pass something(s) into it. What do you think that is? Look at what you are passing into the Smoke function. If you do not understand please tell me.
      (2 votes)
  • aqualine seed style avatar for user K B
    We use following statements to base functionality of one object to another.
    Confetti.prototype = Object.create(Particle.prototype);
    Confetti.prototype.constructor = Confetti;
    Could somebody explain & deconstruct the above statements? In the next challenge I just used
    Stars.prototype = Object.create(Particle.prototype);
    & it worked fine without use of 2nd statement. Why so?
    (1 vote)
    Default Khan Academy avatar avatar for user
  • duskpin tree style avatar for user &amp;amp;
    How did you get the rectangles to rotate by themselves on their own "axle"
    (1 vote)
    Default Khan Academy avatar avatar for user