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?

\$\endgroup\$
  • 1
    \$\begingroup\$ Updated my answer with an approach to fix for your checkBounds. \$\endgroup\$ – konijn Oct 24 '14 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\$ – toto2 Oct 25 '14 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\$ – toto2 Oct 25 '14 at 2:59
  • \$\begingroup\$ @toto2 khanacademy.org/computer-programming/basic-particle-system/… \$\endgroup\$ – Ethan Bierlein Oct 25 '14 at 22:05
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();
\$\endgroup\$
  • \$\begingroup\$ I use Khanacademy to program, they must have some special features to make things easier. \$\endgroup\$ – Ethan Bierlein Oct 24 '14 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\$ – Johan Oct 24 '14 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\$ – konijn Oct 25 '14 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\$ – Ingo Bürk Oct 25 '14 at 11:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.