Main content
Computer programming
Course: Computer programming > Unit 5
Lesson 8: Particle Systems- Intro to particle systems
- A single particle
- Challenge: Falling leaves
- A particle system
- Challenge: Fish bubbles
- Systems of particle systems
- Challenge: Fire starter
- Particle types
- Challenge: Magical cauldron
- Particle systems with forces
- Challenge: River rocks
- Project: Creature Colonies
© 2023 Khan AcademyTerms of usePrivacy PolicyCookie Notice
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):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?
- what does
Confetti.prototype.constructor = Confetti;
do ?
i tried to remove it and it still works.(41 votes)- This makes sure that the constructor is still Confetti, but it is not required to that.(16 votes)
- 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)- Simply modify the Particle constructor to include a parameter for acceleration, then pass in whatever random numbers you like.(3 votes)
- What does
Particle.call(this, position);
do, literally?(6 votes)- It literally calls the
Particle
function substitutingthis
as the value for thethis
that Particle typically sees. So, instead of modifying its normalthis
,Particle
modifies thethis
that it was invoked with.
Clear as mud?(6 votes)
- 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);
};(3 votes) - 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)- This is probably late but you simply need to give the random function a minimum size and a maximum size.(2 votes)
- 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)- 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 themRMatrix2D
I have reused in more than a dozen programs.(3 votes)
- 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)- if it doesn't work the first time, then spam it(1 vote)
- 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)
- 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)
- We use following statements to base functionality of one object to another.
Confetti.prototype = Object.create(Particle.prototype);
Could somebody explain & deconstruct the above statements? In the next challenge I just usedConfetti.prototype.constructor = Confetti;
& it worked fine without use of 2nd statement. Why so?Stars.prototype = Object.create(Particle.prototype);
(1 vote)- It should work just fine without the the second statement... that is, until you try using the Javascript
instanceof
operator.(2 votes)
- How did you get the rectangles to rotate by themselves on their own "axle"(1 vote)
- The "axle" is the "point of rotation". Many people try to rotate objects without taking the point of rotation under consideration. Disappointment unsues.
https://www.khanacademy.org/computer-programming/rotate-this/6375407836200960(2 votes)