This question already has answers here:
Array.prototype.fill() with object passes reference and not new instance
(7 answers)
Closed 5 months ago.
I am trying to convert a 2D grid of (x, y) points to a sphere in space. I loop through all of the points in the grid and set the value to an object {x: someX, y: someY, z: someZ}. But for some reason, the value I get inside the loop is different from the value outside. Do I have a typo or is this something about JS I don't understand yet? Here is the exact section causing issues:
for (let y = 0; y < GRID_WIDTH; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
points[y][x] = grid2Sphere(x, y); //returns {x: someX, y: someY, z: someZ}. This works.
console.log(points[y][x]); // This logs the correct values
}
console.log(points[y]); // This is always equal to the row where y = 0. It's as if everything that happened in the inner loop did not get set.
}
For those interested, here is the entire file. I am using p5.js:
let R;
let points;
const GRID_WIDTH = 20;
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
stroke("#000");
R = 100;
angleMode(RADIANS);
points = new Array(GRID_WIDTH).fill(new Array(GRID_WIDTH).fill({}));
// Calculate and draw each point.
for (let y = 0; y < GRID_WIDTH; y++) {
for (let x = 0; x < GRID_WIDTH; x++) {
points[y][x] = Object.assign({}, grid2Sphere(x, y));
console.log(points[y][x]);
}
console.log(points[y]);
}
drawPoints(points);
}
function draw() {
noLoop();
}
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_};
}
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();
}
Related
I want to be able to implement more rows or columns to the grid depending on the format. (For example if i want to use the sketch on a horizontal 16x9 screen, there would have to be more rows than columns) Right now it only works if the format/canvas is square.
As soon as I change the number of tiles or the size of the canvas, the elements jump around.
Here is my sketch:
let colors = [
"#F7F7F7",
"#141414",
"#FB2576",
"#F48668",
"#67339E",
"#00A6A6",
"#78FFD6"
];
function drawSubdividedCircle(x, y, size, segments, layers) {
segments = random (1,13);
layers = random (1,13);
const r = 360 / segments;
for (let i = 0; i < segments; i++) {
for (let j = 0; j < layers; j++) {
fill(random(colors));
const s = map(j, 0, layers, size, 0);
arc(
x + size / 2,
y + size / 2,
s,
s,
radians(r * i),
radians(r * (i + 1)));
}
}
}
function setup() {
createCanvas(500, 500);
frameRate(2);
}
function draw() {
noStroke();
let tilesX = 5;
let tilesY = 5;
let tileW = width / tilesX;
let tileH = height / tilesY;
const tileSize = width / tilesX;
for (let x = 0; x < tilesX; x++) {
for (let y = 0; y < tilesY; y++) {
rect(x*tileW, y*tileH, tileW, tileH);
fill(random(colors));
push();
for (let x = 0; x < tilesX; x++) {
for (let y = 0; y < tilesY; y++) {
let r = random(1);
if (r < 0.5) {
ellipseMode(CORNER);
ellipse(x*tileW, y*tileH, tileW, tileH);
fill(random(colors));
} else {
ellipseMode(CENTER);
drawSubdividedCircle(x * tileSize, y * tileSize, tileSize);
fill(random(colors));
pop();
}
}
}
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
I changed your code a little.. Removed two redundant for-loops, it is much faster now.
When you want to make your program responsive, you have to make a choice. Do you want the circles to scale when you change the resolution or maybe do you want the amount of rows/columns to change when the resolution changes? There are several possibilities, I made it now to keep the amount of tiles, and use the smaller value of the 'tileW' and 'tileH' to remain the circle shape.
let colors = [
"#F7F7F7",
"#141414",
"#FB2576",
"#F48668",
"#67339E",
"#00A6A6",
"#78FFD6"
];
var tilesX = 5;
var tilesY = 5;
var tileW;
var tileH;
var tileSize;
function drawSubdividedCircle(x, y, size, segments, layers)
{
segments = random (1,13);
layers = random (1,13);
const r = 360 / segments;
for (let i = 0; i < segments; i++)
{
for (let j = 0; j < layers; j++)
{
fill(random(colors));
const s = map(j, 0, layers, size, 0);
arc(
x + size / 2,
y + size / 2,
s,
s,
radians(r * i),
radians(r * (i + 1)));
}
}
}
function setup()
{
createCanvas(550, 500);
ellipseMode(CENTER);
//frameRate(2);
tileW = width / tilesX;
tileH = height / tilesY;
tileSize = min(tileW, tileH);
}
function draw()
{
background(0);
noStroke();
for (let x = 0; x < tilesX; x++)
{
for (let y = 0; y < tilesY; y++)
{
rect(x * tileSize, y * tileSize, tileSize, tileSize);
fill(random(colors));
let r = random(1);
if (r < 0.5)
{
ellipse((x - 0.5) * tileSize, (y - 0.5) * tileSize, tileSize, tileSize);
fill(random(colors));
}
else
{
drawSubdividedCircle(x * tileSize, y * tileSize, tileSize);
fill(random(colors));
}
}
}
}
I have created a circle with subdivisions (function CircleSubDivs) in p5.js and now want to generate a grid filled with those. Ideally the ellipse would also be disproportionate if the width and height of a tile is not the same, or if the amount of tiles were to be controlled by the mouse position the ellipse would move flexibly.
This was my inspiration
This is my code so far:
// let colors = [
// "#F48668 ",
// "#5D2E8C",
// "#F7F7F7"
// ];
function CircleSubDivs(x, y, size) {
let amount = 13;
let r = 360 / amount;
for (let j = 0; j < 10; j++) {
for (let i = 0; i < amount; i++) {
fill(random(255));
let s = map(j, 0, 8, width, 100);
arc(width / 2, height / 2, s, s, radians(r * i), radians(r * (i + 1)));
}
}
}
function setup() {
createCanvas(500, 500);
frameRate(1);
}
function draw() {
background("#0F0F0F");
noStroke();
let tilesX = 3;
let tilesY = 2;
let tileW = width / tilesX;
let tileH = height / tilesY;
for (let x = 0; x < tilesX; x++) {
for (let y = 0; y < tilesY; y++) {
CircleSubDivs(x * tileW, y * tileH, tileW, tileH);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
As you can see I have created a grid and tried to store my function for the ellipse with divisions in there, but it just shows me one single ellipse when I run it. I would really appreciate some help :)
Your parameters to your function are unused, so the function does the same thing every time.
You might try something like:
function drawSubdividedCircle(x, y, size, segments=13, layers=10) {
const r = 360 / segments;
for (let i = 0; i < segments; i++) {
for (let j = 0; j < layers; j++) {
fill(random(255));
const s = map(j, 0, layers, size, 0);
arc(
x + size / 2,
y + size / 2,
s,
s,
radians(r * i),
radians(r * (i + 1))
);
}
}
}
function setup() {
createCanvas(500, 500);
frameRate(1);
}
function draw() {
background("#0F0F0F");
noStroke();
const tilesX = 3;
const tilesY = 2;
const tileSize = width / tilesX;
for (let x = 0; x < tilesX; x++) {
for (let y = 0; y < tilesY; y++) {
drawSubdividedCircle(x * tileSize, y * tileSize, tileSize);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.js"></script>
I'm trying make render 3d rotating cube but cube has weird shape, I can't find the bug/glitch.
I'm following this tutorial on youtube.
I think I made something wrong on my code but everything seems good to me and I checked values on chrome debugging mode.
But when I following the tutorial I make some personal change but i'm sure this changes doesn't effect code working and make optimization on working.
Thanks.
const canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d');
const W = 600, H = 600;
const MODEL_MAX_X = 2, MODEL_MIN_X = -2, MODEL_MAX_Y = 2, MODEL_MIN_Y = -2, STEP = 0.5;
var points = [], triangles = [];
for (let x = -1; x <= 1; x += STEP)
for (let y = -1; y <= 1; y += STEP)
for (let z = -1; z <= 1; z += STEP)
points.push([x, y, z]);
for (let dimension = 0; dimension <= 2; ++dimension)
for (let side = -1; side <= 1; side += 2) {
var sidePoints = points.filter(point => point[dimension] == side).slice(0,3);
triangles.push([...sidePoints]);
}
function persvectiveProjection([x, y, z]) {
return [x / (z + 4), y / (z + 4)];
}
function project(point) {
const [x, y] = persvectiveProjection(point);
return [
W * (x - MODEL_MIN_X) / (MODEL_MAX_X - MODEL_MIN_X),
H * (1 - y - MODEL_MIN_Y) / (MODEL_MAX_Y - MODEL_MIN_Y)
];
}
ctx.lineWidth = 4;
ctx.strokeStyle = '#000';
function renderPoint(point) {
const [x, y] = project(point);
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + 1, y + 1);
ctx.stroke();
}
function renderTriangle (triangle) {
const projectedTriangle = triangle.map(project);
const [a, b, c] = projectedTriangle;
ctx.beginPath();
ctx.moveTo(a[0], a[1]);
ctx.lineTo(b[0], b[1]);
ctx.lineTo(c[0], c[1]);
ctx.lineTo(a[0], a[1]);
ctx.stroke();
}
function rotateY(point, theta) {
const [x, y, z] = point;
return [
Math.cos(theta) * x - Math.sin(theta) * z,
y,
Math.sin(theta) * x + Math.cos(theta) * z
]
}
function rotateX(point, theta) {
const [x, y, z] = point;
return [
x,
Math.cos(theta) * y - Math.sin(theta) * z,
Math.sin(theta) * y + Math.cos(theta) * z
]
}
var theta = 0;
var dtheta = 0.01;
function render() {
ctx.clearRect(0, 0, W, H);
theta += dtheta;
triangles.forEach(triangle => {
var rotatedTriangle = triangle.map(point => rotateX(rotateY(point, theta), 0.43 * theta));
renderTriangle(rotatedTriangle);
})
requestAnimationFrame(render);
}
render();
I'm drawing each point of a vector as a vertex in P5.js.
I would like to change the color of each vertex in a loop separately like a rainbow. The thing is that it changes the color of all the vertex and I don't know why.
var x = 0.01
var y = 0
var z = 0
var sigma = 10
var prandtl = 28
var rayleigh = 8 / 3
var dt = 0.01
var positionPoint = []
var colorChange = 0;
function setup() {
createCanvas(800, 800, WEBGL)
colorMode(HSB)
}
function draw() {
background("grey")
formula()
noFill()
scale(5)
strokeWeight(3)
beginShape()
rotateZ(frameCount * 0.01);
rotateY(frameCount * 0.01);
for (var i = 0; i < positionPoint.length; i++) {
stroke(colorChange, 255, 255)
vertex(positionPoint[i].x, positionPoint[i].y, positionPoint[i].z)
colorChange += 0.001
if (colorChange > 255) {
colorChange = 0
}
}
endShape()
}
const formula = () => {
var dx = (sigma * (y - x)) * dt
var dy = (x * (prandtl - z) - y) * dt
var dz = (x * y - rayleigh * z) * dt
x = x + dx
y = y + dy
z = z + dz
positionPoint.push(createVector(x, y, z))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
Shapes drawn from corner points can only be colored uniformly. You need to draw separate line segments.
vertex(positionPoint[i].x, positionPoint[i].y, positionPoint[i].z)
line(positionPoint[i].x, positionPoint[i].y, positionPoint[i].z,
positionPoint[i+1].x, positionPoint[i+1].y, positionPoint[i+1].z)
Color channels are in range [0, 255]. Set colorChange = 0 before the loop and inclrement it by 1 in the loop:
colorChange = 0;
for (var i = 0; i < positionPoint.length-1; i++) {
// [...]
colorChange += 1
if (colorChange > 255) {
colorChange = 0
}
}
var x = 0.01
var y = 0
var z = 0
var sigma = 10
var prandtl = 28
var rayleigh = 8 / 3
var dt = 0.01
var positionPoint = []
var colorChange = 0;
function setup() {
createCanvas(800, 800, WEBGL)
colorMode(HSB)
}
function draw() {
background("grey")
formula()
noFill()
scale(5)
strokeWeight(3)
//beginShape()
rotateZ(frameCount * 0.01);
rotateY(frameCount * 0.01);
colorChange = 0;
for (var i = 0; i < positionPoint.length-1; i++) {
stroke(colorChange, 255, 255)
//vertex(positionPoint[i].x, positionPoint[i].y, positionPoint[i].z)
line(positionPoint[i].x, positionPoint[i].y, positionPoint[i].z, positionPoint[i+1].x, positionPoint[i+1].y, positionPoint[i+1].z)
colorChange += 1
if (colorChange > 255) {
colorChange = 0
}
}
//endShape()
}
const formula = () => {
var dx = (sigma * (y - x)) * dt
var dy = (x * (prandtl - z) - y) * dt
var dz = (x * y - rayleigh * z) * dt
x = x + dx
y = y + dy
z = z + dz
positionPoint.push(createVector(x, y, z))
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.min.js"></script>
There is something I need to build, but my math ability is not up to par. What I am looking to build is something like this demo, but I need it to be a hybrid of a circle and polygon instead of a line, so to speak. The black line should be dynamic and randomly generated that basically acts as a border on the page.
Currently, I am dissecting this answer with the aim of hopefully being able to transpose it into this, but I am having massive doubts that I will be able to figure this out.
Any idea how to do this or can anybody explain the mathematics?
Below are my notes about the code from the answer I linked above.
var
cw = cvs.width = window.innerWidth,
ch = cvs.height = window.innerHeight,
cx = cw / 2,
cy = ch / 2,
xs = Array(),
ys = Array(),
npts = 20,
amplitude = 87, // can be val from 1 to 100
frequency = -2, // can be val from -10 to 1 in steps of 0.1
ctx.lineWidth = 4
// creates array of coordinates that
// divides page into regular portions
// creates array of weights
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i
ys[i] = 2.0*(Math.random()-0.5)*amplitude
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (let x = 0; x < cw; x++) {
y = 0.0
wsum = 0.0
for (let i = -5; i <= 5; i++) {
xx = x; // 0 / 1 / 2 / to value of screen width
// creates sequential sets from [-5 to 5] to [15 to 25]
ii = Math.round(x/xs[1]) + i
// `xx` is a sliding range with the total value equal to client width
// keeps `ii` within range of 0 to 20
if (ii < 0) {
xx += cw
ii += npts
}
if (ii >= npts){
xx -= cw
ii -= npts
}
// selects eleven sequential array items
// which are portions of the screen width and height
// to create staggered inclines in increments of those portions
w = Math.abs(xs[ii] - xx)
// creates irregular arcs
// based on the inclining values
w = Math.pow(w, frequency)
// also creates irregular arcs therefrom
y += w*ys[ii];
// creates sets of inclining values
wsum += w;
}
// provides a relative position or weight
// for each y-coordinate in the total path
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
This is my answer. Please read the comments in the code. I hope this is what you need.
// initiate the canvas
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = 600),
cx = cw / 2;
let ch = (canvas.height = 400),
cy = ch / 2;
ctx.fillStyle = "white"
// define the corners of an rectangle
let corners = [[100, 100], [500, 100], [500, 300], [100, 300]];
let amplitud = 20;// oscilation amplitude
let speed = 0.01;// the speed of the oscilation
let points = []; // an array of points to draw the curve
class Point {
constructor(x, y, hv) {
// the point is oscilating around this point (cx,cy)
this.cx = x;
this.cy = y;
// the current angle of oscilation
this.a = Math.random() * 2 * Math.PI;
this.hv = hv;// a variable to know if the oscilation is horizontal or vertical
this.update();
}
// a function to update the value of the angle
update() {
this.a += speed;
if (this.hv == 0) {
this.x = this.cx;
this.y = this.cy + amplitud * Math.cos(this.a);
} else {
this.x = this.cx + amplitud * Math.cos(this.a);
this.y = this.cy;
}
}
}
// a function to divide a line that goes from a to b in n segments
// I'm using the resulting points to create a new point object and push this new point into the points array
function divide(n, a, b) {
for (var i = 0; i <= n; i++) {
let p = {
x: (b[0] - a[0]) * i / n + a[0],
y: (b[1] - a[1]) * i / n + a[1],
hv: b[1] - a[1]
};
points.push(new Point(p.x, p.y, p.hv));
}
}
divide(10, corners[0], corners[1]);points.pop();
divide(5, corners[1], corners[2]);points.pop();
divide(10, corners[2], corners[3]);points.pop();
divide(5, corners[3], corners[0]);points.pop();
// this is a function that takes an array of points and draw a curved line through those points
function drawCurves() {
//find the first midpoint and move to it
let p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
let mp = {};
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, mp.x, mp.y);
}
//curve through the last point, back to the first midpoint
ctx.quadraticCurveTo(
points[points.length - 1].x,
points[points.length - 1].y,
p.x,
p.y
);
ctx.stroke();
ctx.fill();
}
function Draw() {
window.requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
points.map(p => {
p.update();
});
drawCurves();
}
Draw();
canvas{border:1px solid; background:#6ab150}
<canvas></canvas>