Performance optimization for p5.js of particles system?

The idea of this program is to implement data visualization in form of "Tron" trails (like in the movie), with additional random color change functionality. Eventually it will visualize the way devices move in the building. I used particle system to implement the trail. The problem is that the sketch doesn't run at a constant FPS. I know that functions randomMovement() and changeColor() look a bit loaded, but profiling indicated that the addTrail() function takes the most of the time and provides the greatest load on the program. Right now the program uses some randomly generated coordinates to align and move devices, but eventually the real data will be involved and number of devices will increase up to 1000 and at the moment program is overwhelmed with only 100 of them. So I was wondering if there is a way to optimize this trail system. I am relatively new to Js and am hoping that someone could run through my code to see if it could be optimized. Any help will be greatly appreciated.

let x1 = [];
let y1 = [];
let Devices = [];

function setup() {

    createCanvas(displayWidth, displayHeight);
    getDummyDevices(100);
}

function draw() {

    background(100);
    for(let i = Devices.length - 1; i > 0; i--){
        Devices[i].goTo(x1[i], y1[i]);
        Devices[i].show();
    }

    showFps();
}

function showFps(){
    textSize(32);
    fill(70,250,5);
    text("FPS: " + frameRate().toFixed(2), 10, height - 10);
}

function getDummyDevices(number){

    for(let i = 0; i <= number; i++){

        let x = random(0, width);
        let y = random(0, height);

        let temp = new Device(x,y);
        Devices.push(temp);

        let x1p = random(0, width);
        let y1p = random(0, height);
        x1.push(x1p);
        y1.push(y1p);
    }

}


class Device {

    constructor(x0,y0) {
        this.x = x0;
        this.y = y0;

        this.diameter = 25;                           // diameter of the device
        this.speed = 2;                               // movement speed of the device

        if(random(-1,1) >= 0) {
            this.trailColor = color(255,0,0);         // trailColor: color variable
            this.trailColorString = "red";            // trailColorString: string indicator of color
        }
        else {
            this.trailColor = color(0,0,255); 
            this.trailColorString = "blue";
        }
        this.changeColorPeriod = random(100,200);     // How often device should change color
        this.lastChangedColorTime = 0;                // keeps track of last change of color
        this.colorChangeSpeed = 20;                   // speed of color change of the device (lower -> slower)

        this.deviceColor = color(8, 20, 33);          // color of the device

        this.particles = [];                          // stores particles representing trail of the device
        this.particleFrequency = 7;                   // per how many frames new particle appears
        this.particleCounter = 0;

        this.changeDirectionPeriod = random(50, 150); // How often device should change direction
        this.lastChangedDirectionTime = 0;            // keeps track of last change of direction
        this.currentDirection = random(-1, 1);        // signs direction of device
    }

    show() {
        fill(this.deviceColor);
        ellipse(this.x, this.y, this.diameter);
    }

    goTo(x1, y1) {

        this.randomMovement(x1, y1);
        this.addTrail();
        this.changeColor();
    }

    // simple 'random' movement to destination point, random means device randomly follows x or y first
    randomMovement(x1, y1){

        this.lastChangedDirectionTime += 1;

        if(this.lastChangedDirectionTime >= this.changeDirectionPeriod)
        {
            this.currentDirection *= -1;
            this.lastChangedDirectionTime = 0;
        }

        if(this.currentDirection < 0){

            // simple movement (first x, then y)
            if(!this.inDistanceOf("x", x1)){
                if(this.x < x1) this.x += this.speed;
                else if(this.x > x1) this.x -= this.speed;
            }
            else if(!this.inDistanceOf("y", y1)){
                if(this.y < y1) this.y += this.speed;
                else if(this.y > y1) this.y -= this.speed;
            }
        } else {

            // first y, then x
            if(!this.inDistanceOf("y", y1)){
                if(this.y < y1) this.y += this.speed;
                else if(this.y > y1) this.y -= this.speed;
            }
            else if(!this.inDistanceOf("x", x1)){
                if(this.x < x1) this.x += this.speed;
                else if(this.x > x1) this.x -= this.speed;
            }
        }
    }

    addTrail(){

        if(this.particleCounter >= this.particleFrequency){

            let p = new Particle(this.x, this.y, this.speed, this.trailColor);
            this.particles.push(p);
            this.particleCounter = 0;
        }
        this.particleCounter += 1;

        for(let i = this.particles.length - 1; i >= 0; i--){

            this.particles[i].show();

            if(this.particles[i].finished()){
                this.particles.splice(i, 1);
            }
        }
    }

    // function to periodically change color of the trail
    changeColor(){

        this.lastChangedColorTime += 1;

        if(this.lastChangedColorTime >= this.changeColorPeriod)
        {
            if(this.trailColorString == "red") this.trailColorString = "blue";
            else this.trailColorString = "red";
            this.lastChangedColorTime = 0;
        }

        let r = red(this.trailColor);
        let b = blue(this.trailColor);

        if(this.trailColorString == "blue")
        {
            if(r > 0){
                r -= this.colorChangeSpeed;
                if(r < 122) b += this.colorChangeSpeed;
            }
            else if(r >= 0 && b < 255)
            {
                b += this.colorChangeSpeed;
            }
        }
        else {
            if(b > 0){
                b -= this.colorChangeSpeed;
                if(b < 122) r += this.colorChangeSpeed;
            }
            else if(b >= 0 && r < 255)
            {
                r += this.colorChangeSpeed;
            }
        }

        this.trailColor = color(r, 0, b);
    }

    // due to the fact that destination point is not an integer, 
    // I use this function to determine if device is close enough to the destination and can stop moving
    inDistanceOf(axis, xy){
        let distance = 3;
        let toReturn = false;

        let check0, check1;

        if(axis == "x"){
            check0 = this.x - xy;
            check1 = xy - this.xy;

            if((check0 < distance) && ( check0 > -distance)) toReturn = true;

            if((check1 < distance) && ( check1 > -distance)) toReturn = true;
        }

        if(axis == "y"){
            check0 = this.y - xy;
            check1 = xy - this.y;

            if((check0 < distance) && ( check0 > -distance)) toReturn = true;

            if((check1 < distance) && ( check1 > -distance)) toReturn = true;
        }
        return toReturn;
    }

}


class Particle {

    constructor(x0, y0, speed, color) {
        this.x = x0;
        this.y = y0;
        this.diameter = 20;
        this.alpha = 255;
        this.speed = speed;

        this.r = red(color);
        this.b = blue(color);
    }

    show() {

        noStroke();
        fill(this.r, 0, this.b, this.alpha);
        ellipse(this.x, this.y, this.diameter);

        this.update();
    }

    // steadily decreases transparency of particles to make view of moving trail
    update() {
        if(this.alpha > 200){
            this.alpha -= 1.2*this.speed;
        }
        else if(this.alpha > 150){
            this.alpha -= 1*this.speed;
        }
        else if(this.alpha > 100){
            this.alpha -= 0.8*this.speed;
        }
        else if(this.alpha > 50){
            this.alpha -= 0.5*this.speed;
        }
        else{
            this.alpha -= 0.3*this.speed;
        }
    }

    finished() {
        return this.alpha <= 0;
    }

}

Answers

  • edited December 2017

    I'm not an expert, but I've been doing a lot of work with particle systems. Upon a quick look, one piece of advice I can give is to recycle particles where possible. It appears that you create new particles, add them to the particles array, remove and destroy them when they're done, and create fresh particles to replace them when needed. Instead, you could instantiate a reasonable amount of particles at the beginning of your program and keep them in another array (I always call mine particleSupply and particleStream), then just move them back and forth as necessary and set the attributes when a particle enters the live array. You can also include a line to check for available particles in particleSupply and make a few more if there are none available. I haven't taken any measurements of how much this helps performance; it was just suggested to me when I was learning about particle systems in the first place.

    Sounds like a really cool idea. Best of luck with it

Sign In or Register to comment.