10
\$\begingroup\$

After about half an hour of fooling around with Processing.js/JavaScript, I got this fairly decent particle system set up:

// main particle constructor class
var Particle = function(position) {
 this.velocity = new PVector(random(-0.1, 0.1), random(-0.1, 0.1));
 this.acceleration = new PVector(random(-0.01, 0.01), random(-0.01, 0.01));
 this.lifeTime = 300;
 this.position = position.get();
};
// update the particle
Particle.prototype.update = function() {
 this.velocity.add(this.acceleration);
 this.position.add(this.velocity);
 this.lifeTime--;
};
// render the particle
Particle.prototype.render = function() {
 strokeWeight(2);
 stroke(161, 153, 153, this.lifeTime);
 fill(140, 135, 140, this.lifeTime);
 ellipse(this.position.x, this.position.y, 15, 15);
};
// check the bounds of a particle
Particle.prototype.checkBounds = function() {
 if(this.position.x >= 385) {
 this.acceleration = new PVector(random(-0.02, -0.01), random(-0.01, 0.01));
 }
 if(this.position.x <= 15) {
 this.acceleration = new PVector(random(0.01, 0.02), random(-0.01, 0.01));
 }
 if(this.position.y >= 385) {
 this.acceleration = new PVector(random(-0.01, 0.01), random(-0.02, -0.01));
 }
 if(this.position.y <= 15) {
 this.acceleration = new PVector(random(-0.01, 0.01), random(0.01, 0.02));
 }
};
// check if the particle is dead
Particle.prototype.particleDead = function() {
 if(this.lifeTime > 0) {
 return false;
 } else {
 return true;
 }
};
// array containing particles
var particles = [];
// main draw loop
var draw = function() {
 // draw the background and push a new particle
 background(11, 100, 209);
 particles.push(new Particle(new PVector(mouseX, mouseY)));
 // iterate through particles and run their methods
 for(var p = particles.length-1; p >= 0; p--) {
 var particle = particles[p];
 particle.checkBounds();
 particle.update();
 particle.render();
 if(particle.particleDead()) {
 particles.splice(p, 1);
 }
 }
};

Now, I think that this could definitely be improved, as the Particle.prototype.checkBounds method seems to not work all the time. Anyways, what can I improve here?

asked Oct 23, 2014 at 23:44
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Updated my answer with an approach to fix for your checkBounds. \$\endgroup\$ Commented Oct 24, 2014 at 21:06
  • 1
    \$\begingroup\$ checkBounds does not implement hard boundaries: it just applies a force to the right if the particle is close to the left edge. \$\endgroup\$ Commented Oct 25, 2014 at 2:58
  • \$\begingroup\$ It would be nice if you could include a link to a working demo on jsfiddle or plnkr. I don't know if it is possible with processing.js. \$\endgroup\$ Commented Oct 25, 2014 at 2:59
  • \$\begingroup\$ @toto2 khanacademy.org/computer-programming/basic-particle-system/… \$\endgroup\$ Commented Oct 25, 2014 at 22:05

1 Answer 1

3
\$\begingroup\$

Interesting question,

there are few things that could be improved:

  • Read up on the revealing module pattern, it would be excellent to

    • Reduce your usage of this
    • Make your class look like 1 piece of code

    Something like this:

    // main particle constructor class
    function Particle(position) {
     var velocity = new PVector(random(-0.1, 0.1), random(-0.1, 0.1)),
     acceleration = new PVector(random(-0.01, 0.01), random(-0.01, 0.01)),
     lifeTime = 300,
     position = position.get();
     function update() {
     velocity.add(acceleration);
     position.add(velocity);
     lifeTime--;
     }
     function render() {
     strokeWeight(2);
     stroke(161, 153, 153, lifeTime);
     fill(140, 135, 140, lifeTime);
     ellipse(position.x,position.y, 15, 15);
     };
     return {
     update : update,
     render: render;
     } 
    };
    

    I obviously did not rewrite the whole thing, but you should catch my drift.

  • You keep using random(-0.1, 0.1), I would create a helper function for this
  • fill(140, 135, 140, lifeTime); <- I would put a comment as to what color this should be

  • Do not use an if statement to determine true or false like you do :

    // check if the particle is dead
    Particle.prototype.particleDead = function() {
     if(this.lifeTime > 0) {
     return false;
     } else {
     return true;
     }
    }; 
    

    do this instead:

    // check if the particle is dead
    Particle.prototype.particleDead = function() {
     return this.lifeTime > 0
    }; 
    

    If you have time, read up on falsy values, you could also simply do return !this.lifetime

  • I am still playing with the code, I managed to fix the right hand side bounce with this:

    if(this.position.x >= 385) {
     this.acceleration.x = random(-0.02, -0.01);
     this.velocity.x = random(-0.1, -0.01);
    }
    

    In essence you have to solve both the acceleration and the velocity.

I have to wonder, how do you access random? I can only access random as a function under the instance of Processing.

It is too bad that the snippets will now allow for localStorage access, so I can only give you a link to jsbin with my refactored version and the source code here in case jsbin ever dies:

var canvas = document.getElementById('partikals'),
 pjs = new Processing(canvas),
 width = 400,
 height = 400,
 padding = 15;
function Particle2(position) {
 var velocity = new PVector(pjs.random(-0.1, 0.1), pjs.random(-0.1, 0.1)),
 acceleration = new PVector(pjs.random(-0.01, 0.01), pjs.random(-0.01, 0.01)),
 lifeTime = 300;
 position = position.get();
 function update() {
 velocity.add(acceleration);
 position.add(velocity);
 lifeTime--;
 }
 function render() {
 pjs.strokeWeight(2);
 //Each particle is grey
 pjs.stroke(161, 153, 153, lifeTime);
 pjs.fill(140, 135, 140, lifeTime);
 pjs.ellipse(position.x, position.y, 15, 15);
 }
 function isDead() {
 return !lifeTime;
 }
 //I wonder if just *= -1 would work
 function bounce() {
 if (position.x > width - padding) {
 acceleration.x = -Math.abs(acceleration.x);
 velocity.x = -Math.abs(velocity.x);
 }
 if (position.x < padding) {
 acceleration.x = Math.abs(acceleration.x);
 velocity.x = Math.abs(velocity.x);
 }
 if (position.y > height - padding) {
 acceleration.y = -Math.abs(acceleration.y);
 velocity.y = -Math.abs(velocity.y);
 }
 if (position.y < padding) {
 acceleration.y = Math.abs(acceleration.y);
 velocity.y = Math.abs(velocity.y);
 }
 }
 return {
 update: update,
 render: render,
 isDead: isDead,
 bounce: bounce
 };
}
// array containing particles
var particles = [];
pjs.setup = function() {
 pjs.size(width, height);
 pjs.frameRate(24);
};
pjs.draw = function() {
 //Clear by drawing the background and push a new particle
 pjs.background(11, 100, 209);
 particles.push(new Particle2(new PVector(pjs.mouseX, pjs.mouseY)));
 // iterate through particles and run their methods
 for (var p = particles.length - 1; p >= 0; p--) {
 var particle = particles[p];
 particle.bounce();
 particle.update();
 particle.render();
 if (particle.isDead()) {
 particles.splice(p, 1);
 }
 }
};
pjs.setup();
Ethan Bierlein
15.9k4 gold badges59 silver badges146 bronze badges
answered Oct 24, 2014 at 17:32
\$\endgroup\$
4
  • \$\begingroup\$ I use Khanacademy to program, they must have some special features to make things easier. \$\endgroup\$ Commented Oct 24, 2014 at 22:10
  • 1
    \$\begingroup\$ +1 for some good points, but I have a few concerns: A revealing JavaScript module is an IIFE - not a constructor function, just as described in your link. In my opinion, if OP wants to use the revealing module pattern, it should look something like this: jsfiddle.net/jw9cj6pj Your current update and render functions will be redefined for each new Particle that is instantiated, which is bad from a performance point of view. \$\endgroup\$ Commented Oct 24, 2014 at 22:59
  • \$\begingroup\$ @Johan There is no performance problem, but there is a memory cost. Given that in this code, you can have at most 200 instances, I think this is still the right approach. \$\endgroup\$ Commented Oct 25, 2014 at 0:32
  • 1
    \$\begingroup\$ There is a fine line between over-engineering and maintainability, but since using the prototype adds little to no overhead, I think despite the current(!) limit you should not waste the memory. Changing the design is much harder later on. \$\endgroup\$ Commented Oct 25, 2014 at 11:37

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.