Search code examples
javascriptarraysvectorp5.js

Trouble with vector arrays in p5.js


I am trying to create a 2-dimensional array of vectors using p5.js I generate the 2D vector array in setup() using nested For loops, and print the vector value to the console within the same nested loops. Starting with a 2D array of 2x2 elements, I want the vectors to take their values from the array index (so, for example: vector[0][0] has value (0,0) and vector[1][1] has value (1,1).

This all works correctly in setup(), and the first line on the console is:

0 0 _class {x: 0, y: 0, z: 0, constructor: Object}

But when I access this 2D array in the draw() function and print the vectors to console, the first line is:

0 0 _class {x: 1, y: 0, z: 0, constructor: Object}

let xV = 2
let yV = 2
let vectorP = [2,2]

function setup() {
  for (var i = 0; i < xV; i++) {
   for (var j = 0; j < yV; j++) {
    vectorP[i,j] = new p5.Vector(i,j);
    console.log(i,j,vectorP[i,j]);
   }
  }
 }

function draw() {
 console.log ("draw")
  for (var i = 0; i < xV; i++) {
   for (var j = 0; j < yV; j++) {
    console.log(i,j,vectorP[i,j]) 
   }
  }
 }

I assume this is due to the way JavaScript deals with objects by reference rather than by value, but I cannot find a way to make it work. I have tried variations with createVector, push, etc. with the same result. The other possibility that occurred to me is that the next vector begins where the previous vector ended. I'm also not sure if my initial array declaration is correct. *Edit: I have tried the declaration let vectorP = new Array(2,2) which makes no difference.


Solution

  • The following syntax is the problem:

    vectorP[i,j] = new p5.Vector(i,j);
    

    In particular, i,j does not do what you think it does (indexing into a 2d array). The expression i, j evaluates to only j and i is ignored:

    const a = [10, 11, 12, 13];
    const i = 2;
    const j = 1;
    console.log(a[i, j]); // => 11

    Similarly, let vectorP = [2,2] does not declare an empty 2x2 array. It declares a 1d array with 2 values: vectorP[0] = 2; vectorP[1] = 2:

    const vectorP = [2, 2];
    console.log(vectorP); // => [2, 2]
    console.log(vectorP[0]); // => 2
    vectorP[1] = 42;
    console.log(vectorP[1]); // => 42

    If you want a true 2d array, try:

    const xV = 2;
    const yV = 2;
    const vectorP = [];
    
    function setup() {
      noLoop();
    
      for (let i = 0; i < xV; i++) {
        vectorP.push([]);
      
        for (let j = 0; j < yV; j++) {
          vectorP[i][j] = new p5.Vector(i, j);
        }
      }
    }
    
    function draw() {
      for (let i = 0; i < xV; i++) {
        for (let j = 0; j < yV; j++) {
          console.log(i, j, vectorP[i][j]);
        }
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.js"></script>

    Or, more idiomatically, use the Array constructor and map to build the arrays and for ... of or .forEach to iterate:

    const xV = 2;
    const yV = 2;
    let vectorP = [];
    
    function setup() {
      noLoop();
      vectorP = [...Array(xV)].map((_, i) => 
        [...Array(yV)].map((_, j) => new p5.Vector(i, j))
      );
    }
    
    function draw() {
      vectorP.forEach((row, i) =>
        row.forEach((cell, j) =>
          console.log(i, j, cell)
        )
      );
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.js"></script>

    If you're making many arrays, you can abstract away some of the messy syntax with:

    // ...
    const array = (len, fn) => [...Array(len)].map((_, i) => fn(i));
    
    function setup() {
      noLoop();
      vectorP = array(xV, i => array(yV, j => new p5.Vector(i, j)));
      // ...
    

    I prefer y to be rows (outer vertical axis) and x to be columns (inner horizontal axis). But I preserved your convention for this answer.

    When working with 2d arrays, it's a good idea to test with different width/height values to ensure you don't have row and column variables mixed up anywhere. In other words, try 3x2 instead of 2x2.