Mixcode1
Mixcode1

Reputation: 55

function .push keep replacing all elements same as last one in array

I'm trying to make trails of moving objects by using vector history array in p5js.
but after push updated vector, all elements in this.history replaced as last one.

I've searched some question here but still can't understand.

let ppp = [];

function setup() {
  createCanvas(400, 400);
  
  for (let i = 0; i < 3; i++) {
    let p = new Particle();
    ppp.push(p);
  }
}

function draw() {
  background(220);
  for (let i = 0; i < ppp.length; i++) {
    ppp[i].display();
    ppp[i].update();
  }
}

function Particle() {
  this.pv = createVector(random(width), random(height));
  this.history = [];

  let rndV = p5.Vector.random2D(); 
  this.spdV = rndV.mult(random(1, 3));
  
  this.update = function() {
    this.pv.add(this.spdV);
    this.history.push(this.pv); // replace all vector element
    
    console.log(this.history);
  }
  
  this.display = function() {
    fill(30);
    ellipse(this.pv.x, this.pv.y, 30);
    
    for (let i = 0; i < this.history.length; i++) {
      let trail = this.history[i];
      ellipse(trail.x, trail.y, 10);
    }
  }
}

or if you think my approach isn't the best, I'll be happy to hear any suggestion^^

Thanks,

Upvotes: 1

Views: 289

Answers (1)

George Profenza
George Profenza

Reputation: 51837

This can be a bit misleading in javascript:

this.history.push(this.pv);

You're pushing a reference to the same this.pv pre-allocated vector

What you are trying to do is something like:

this.history.push(this.pv.copy());

Where you are allocating memory for a completely new p5.Vector object with the x,y coordinates copied from this.pv (using the copy() method)

Demo:

let ppp = [];

function setup() {
  createCanvas(400, 400);
  
  for (let i = 0; i < 3; i++) {
    let p = new Particle();
    ppp.push(p);
  }
}

function draw() {
  background(220);
  for (let i = 0; i < ppp.length; i++) {
    ppp[i].display();
    ppp[i].update();
  }
}

function Particle() {
  this.pv = createVector(random(width), random(height));
  this.history = [];

  let rndV = p5.Vector.random2D(); 
  this.spdV = rndV.mult(random(1, 3));
  
  this.update = function() {
    this.pv.add(this.spdV);
    this.history.push(this.pv.copy()); // replace all vector element
    
    //console.log(this.history);
  }
  
  this.display = function() {
    fill(30);
    ellipse(this.pv.x, this.pv.y, 30);
    
    for (let i = 0; i < this.history.length; i++) {
      let trail = this.history[i];
      ellipse(trail.x, trail.y, 10);
    }
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Bare in mind as the sketch runs this will use more and more memory.

If simply need to render the trails and don't need the vector data for anything else you can simply render into a separate graphics layer (using createGraphics()) immediately which will save memory on the long run:

let ppp = [];
let trailsLayer;

function setup() {
  createCanvas(400, 400);
  // make a new graphics layer for trails
  trailsLayer = createGraphics(400, 400);
  trailsLayer.noStroke();
  trailsLayer.fill(0);
  
  for (let i = 0; i < 3; i++) {
    let p = new Particle();
    ppp.push(p);
  }
}

function draw() {
  background(220);
  // render the trails layer
  image(trailsLayer, 0, 0);
  
  for (let i = 0; i < ppp.length; i++) {
    ppp[i].display();
    ppp[i].update();
  }
}

function Particle() {
  this.pv = createVector(random(width), random(height));
  
  let rndV = p5.Vector.random2D(); 
  this.spdV = rndV.mult(random(1, 3));
  
  this.update = function() {
    this.pv.add(this.spdV);
    // render trails
    trailsLayer.ellipse(this.pv.x, this.pv.y, 10);
  }
  
  this.display = function() {
    fill(30);
    ellipse(this.pv.x, this.pv.y, 30);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Update to fade trails you could try something like Moving On Curves example. Notice noStroke(); is called in setup() and

fill(0, 2);
rect(0, 0, width, height);

render a faded out (alpha=2) rectangle ?

You could do something similar:

let ppp = [];
let trailsLayer;

function setup() {
  createCanvas(400, 400);
  background(255);
  // make a new graphics layer for trails
  trailsLayer = createGraphics(400, 400);
  trailsLayer.noStroke();
  // set translucent fill for fade effect
  trailsLayer.fill(255, 25);
  
  for (let i = 0; i < 3; i++) {
    let p = new Particle();
    ppp.push(p);
  }
}

function draw() {
  background(220);
  // fade out trail layer by rendering a faded rectangle each frame 
  trailsLayer.rect(0, 0, width, height);
  // render the trails layer
  image(trailsLayer, 0, 0);
  
  for (let i = 0; i < ppp.length; i++) {
    ppp[i].display();
    ppp[i].update();
  }
}

function Particle() {
  this.pv = createVector(random(width), random(height));
  
  let rndV = p5.Vector.random2D(); 
  this.spdV = rndV.mult(random(1, 3));
  
  this.update = function() {
    this.pv.add(this.spdV);
    // reset at bounds
    if(this.pv.x > width){
      this.pv.x = 0;
    }
    if(this.pv.y > height){
      this.pv.y = 0;
    }
    if(this.pv.x < 0){
      this.pv.x = width;
    }
    if(this.pv.y < 0){
      this.pv.y = height;
    }
    // render trails
    trailsLayer.push();
    trailsLayer.fill(0);
    trailsLayer.noStroke();
    trailsLayer.ellipse(this.pv.x, this.pv.y, 10);
    trailsLayer.pop();
  }
  
  this.display = function() {
    fill(30);
    ellipse(this.pv.x, this.pv.y, 30);
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

For the sake of completeness here's a version using the history vector array, but limiting that to a set size and reusing vectors allocated once (instead making new ones continuously):

let ppp = [];

function setup() {
  createCanvas(400, 400);
  noStroke();
  
  for (let i = 0; i < 3; i++) {
    let p = new Particle();
    ppp.push(p);
  }
}

function draw() {
  background(220);
  for (let i = 0; i < ppp.length; i++) {
    ppp[i].display();
    ppp[i].update();
  }
}

function Particle() {
  this.pv = createVector(random(width), random(height));
  // limit number of history vectors
  this.historySize = 24;
  this.history = new Array(this.historySize);
  // pre-allocate all vectors
  for(let i = 0 ; i < this.historySize; i++){
      this.history[i] = this.pv.copy();
  }

  let rndV = p5.Vector.random2D(); 
  this.spdV = rndV.mult(random(1, 6));
  
  this.update = function() {
    this.pv.add(this.spdV);
    this.resetBounds();
    this.updateHistory();
  };
  
  this.updateHistory = function(){
    // shift values back to front by 1 (loop from last to 2nd index)
    for(let i = this.historySize -1; i > 0; i--){
      // copy previous to current values (re-using existing vectors)
      this.history[i].set(this.history[i-1].x, this.history[i-1].y);
    }
    // finally, update the first element
    this.history[0].set(this.pv.x, this.pv.y);
  };
  
  this.resetBounds = function(){
    // reset at bounds
    if(this.pv.x > width){
      this.pv.x = 0;
    }
    if(this.pv.y > height){
      this.pv.y = 0;
    }
    if(this.pv.x < 0){
      this.pv.x = width;
    }
    if(this.pv.y < 0){
      this.pv.y = height;
    }
  };
  
  this.display = function() {
    fill(30);
    ellipse(this.pv.x, this.pv.y, 30);
    
    for (let i = 0; i < this.historySize; i++) {
      let trail = this.history[i];
      // fade trails
      let alpha = map(i, 0, this.historySize -1, 192, 0);
      fill(30, alpha);
      ellipse(trail.x, trail.y, 10);
    }
  };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>

Upvotes: 2

Related Questions