We are about to switch to a new forum software. Until then we have removed the registration on this forum.
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
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 mineparticleSupply
andparticleStream
), 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 inparticleSupply
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