I think you're on the right track, the confusion stems from here actually:
points = new Array(GRID_WIDTH).fill(new Array(GRID_WIDTH).fill({}));
to break it down:
new Array(GRID_WIDTH).fill({})
creates an array of empty objects
new Array(GRID_WIDTH).fill(new Array(GRID_WIDTH).fill({}));
makes a new array that is filled with references (not copies) of the first array (e.g. if the first array of {},{},...
was called myData
, the nested array would contain: myData,myData,...
20 times)
You could assign/allocate a new array on each y loop:
// Calculate and draw each point.
for (let y = 0; y < GRID_WIDTH; y++) {
// ensure this is a new array (and not the same reference)
points[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
points[y][x] = grid2Sphere(x, y);
console.log(x, y, points[y][x]);
}
console.log(y, points[y]);
}
Here's a modified version of your code:
let R;
let points;
const GRID_WIDTH = 20;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
background(255);
stroke("#000");
R = 100;
points = new Array(GRID_WIDTH).fill(new Array(GRID_WIDTH).fill({}));
// Calculate and draw each point.
for (let y = 0; y < GRID_WIDTH; y++) {
// ensure this is a new array (and not the same reference)
points[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
points[y][x] = grid2Sphere(x, y);
console.log(x, y, points[y][x]);
}
console.log(y, points[y]);
}
// move camera for visual debugging purposes only
camera(300, -100, 0, 0, 0, 0, 0, 1, 0);
drawPoints(points);
}
function drawPoints(pArray) {
for (let y = 0; y < GRID_WIDTH; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
let p = pArray[y][x];
point(p.x, p.y, p.z);
}
}
}
function grid2Sphere(x, y) {
var radX = map(x, 0, GRID_WIDTH - 1, -1 * PI * R, PI * R);
var radY = map(y, 0, GRID_WIDTH - 1, -1 * PI / 2 * R, PI / 2 * R);
var lon = radX / R;
var lat = 2 * atan(exp(radY / R)) - PI / 2;
var x_ = R * cos(lat) * cos(lon);
var y_ = R * cos(lat) * sin(lon);
var z_ = R * sin(lat);
return {x: x_, y: y_, z: z_};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
You could simplify further by just using an empty array that gets populated later (without worrying about filling it ahead of time):
let R = 100;
let points;
const GRID_WIDTH = 20;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
background(255);
stroke("#000");
points = [];
// Calculate and draw each point.
for (let y = 0; y < GRID_WIDTH; y++) {
points[y] = [];
for (let x = 0; x < GRID_WIDTH; x++) {
points[y][x] = grid2Sphere(x, y);
console.log(x, y, points[y][x]);
}
console.log(y, points[y]);
}
}
function draw(){
background(255);
orbitControl();
drawPoints(points);
}
function drawPoints(pArray) {
beginShape(POINTS);
for (let y = 0; y < GRID_WIDTH; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
let p = pArray[y][x];
vertex(p.x, p.y, p.z);
}
}
endShape();
}
function grid2Sphere(x, y) {
var radX = map(x, 0, GRID_WIDTH - 1, -PI * R, PI * R);
var radY = map(y, 0, GRID_WIDTH - 1, -HALF_PI * R, HALF_PI * R);
var lon = radX / R;
var lat = 2 * atan(exp(radY / R)) - PI / 2;
return {
x: R * cos(lat) * cos(lon),
y: R * cos(lat) * sin(lon),
z: R * sin(lat)
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
You can simplify even further by using a flat (1D) array (which you can easily set the size of if you need to):
let R = 100;
const GRID_WIDTH = 20;
const NUM_POINTS = GRID_WIDTH * GRID_WIDTH;
let points = new Array(NUM_POINTS);
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
for(let i = 0; i < NUM_POINTS; i++){
let x = i % GRID_WIDTH;
let y = i / GRID_WIDTH; // this is a float (ok for now, but with image indices floor() it)
points[i] = p5.Vector.fromAngles(map(x, 0, GRID_WIDTH - 1, 0, PI),
map(y, 0, GRID_WIDTH - 1, 0, TWO_PI),
R);
}
}
function draw(){
background(255);
orbitControl();
drawPoints(points);
}
function drawPoints(pArray) {
beginShape(POINTS);
pArray.forEach(p => vertex(p.x, p.y, p.z));
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
Here, I'm also using beginShape() / endShape() / vertex()
which should be more efficient than point()
(though for your static image won't make a difference) and using p5.Vector which on top of providing x,y,z
properties has a bunch of nice linear algebra utilities (e.g. computing angles between vectors, perpendiculars to vectors, etc.) as well as p5.Vector.fromAngles() which does the spherical to cartesian coordinate conversion for you.
An even shorter version of the above (though less readible):
let R = 100;
const GRID_WIDTH = 20;
let points = new Array(GRID_WIDTH * GRID_WIDTH).fill();
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
points.forEach((p,i) => points[i] = p5.Vector.fromAngles((i % GRID_WIDTH) / GRID_WIDTH * PI,
(i / GRID_WIDTH) / GRID_WIDTH * TWO_PI, R));
}
function draw(){
background(255);
orbitControl();
drawPoints(points);
}
function drawPoints(pArray) {
beginShape(POINTS);
pArray.forEach(p => vertex(p.x, p.y, p.z));
endShape();
}