Public
Edited
Nov 11, 2022
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
update = (ctx, particles, fieldPerGridCell) => {
// Init
let points = [];
let noiseZ = 0;

// Reset function to prevent memory leak
const doReset = () => {
reset;
// stop listening to pose detection events by removing the event listener
poseNet.removeAllListeners("pose");
};
doReset();

// Update in every PoseNet frame
poseNet.on("pose", function (poses) {
if (poses === undefined) return;
points = [];
if (isSkelton) {
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
ctx.fill();
}

// Draw body points
poses[0].pose.keypoints
.filter((kp) => kp.score >= 0.2)
.map((kp) => {
if (isSkelton) {
ctx.beginPath();
ctx.strokeStyle = "white";
ctx.fillStyle = "white";
ctx.arc(kp.position.x, kp.position.y, 3, 0, 2 * Math.PI);
ctx.stroke();
ctx.fill();
}

if (kp.part === "leftElbow" || kp.part === "leftWrist") {
kp.positive = true;
points.push(kp);
} else if (kp.part === "rightElbow" || kp.part === "rightWrist") {
kp.positive = false;
points.push(kp);
}
});

// Draw skelton
if (isSkelton) {
const skeleton = poses[0].skeleton;
if (skeleton.length > 0) {
skeleton.map((sk) => {
ctx.beginPath();
ctx.moveTo(sk[0].position.x, sk[0].position.y);
ctx.lineTo(sk[1].position.x, sk[1].position.y);
ctx.stroke();
});
}
}

// Give force to grid
fieldPerGridCell = gridCellFactory({
cellValue: (col, row) => {
let x = col * resolution;
let y = row * resolution;

return fieldDirectionSampler(x, y, points, noiseZ);
}
});

if (isSkelton) {
for (let x = 0; x < fieldPerGridCell.length; x++) {
for (let y = 0; y < fieldPerGridCell[0].length; y++) {
const xPos = fieldPerGridCell[x][y].x;
const yPos = fieldPerGridCell[x][y].y;
ctx.beginPath();
ctx.moveTo(xPos, yPos);
ctx.lineTo(
xPos + fieldPerGridCell[x][y].vector.x * resolution * 2,
yPos + fieldPerGridCell[x][y].vector.y * resolution * 2
);
ctx.strokeStyle = "white";
ctx.stroke();
}
}
}

// Update the particles position
for (let i = 0; i < particles.length; i++) {
const particle = particles[i];
const nearestFieldPoint = getNearestFieldPoint(
particle.position.x,
particle.position.y,
fieldPerGridCell
);
particle.acceleration.add(nearestFieldPoint.vector);
particle.velocity.add(particle.acceleration);
particle.acceleration.mult(0);

// gridLayer(fieldG, flattenedArr);
// // Give force to the particles that are within a certain range from the body points
// points.map((p) => {
// const d = particle.position.dist(p.position);

// const angle =
// n.simplex3(p.position.x / 20, p.position.y / 20, noiseZ) *
// Math.PI *
// 2 +
// Math.PI / 2;
// const length = Math.abs(
// n.simplex3(
// p.position.x / 40 + 40000,
// p.position.y / 40 + 40000,
// noiseZ
// )
// );
// const acc = new Vector(0, length);
// acc.setAngle(angle);

// if (d <= range) {
// particle.acceleration.add(acc);
// particle.velocity.add(particle.acceleration);
// particle.acceleration.mult(0);
// }
// });
}

noiseZ += 0.002;
});
}
Insert cell
drawParticle = (ctx, particles) => {
particles.map((particle) => {
// Move the particles
particle.position.add(particle.velocity);
if (particle.velocity.magnitude() > maxVelocity) {
particle.velocity = particle.velocity.limit(maxVelocity);
}
particle.velocity = particle.velocity.mult(0.999);

// Bouncing off the walls
if (particle.position.x < 0) {
particle.position.x = width;
} else if (particle.position.x > width) {
particle.position.x = 0;
}
if (particle.position.y < 0) {
particle.position.y = height;
} else if (particle.position.y > height) {
particle.position.y = 0;
}

// Draw particles
isSkelton ? (ctx.globalAlpha = 1) : (ctx.globalAlpha = 0.03);

const colorScheme = d3.interpolatePlasma(
(particle.position.y / height) * 2
);
ctx.beginPath();
ctx.fillRect(particle.position.x, particle.position.y, 2, 2);
ctx.fillStyle = colorScheme;
});
}
Insert cell
Insert cell
gridCellFactory = function (params = {}) {
let x0 = width * -0.5;
let x1 = width * 1.5;
let y0 = height * -0.5;
let y1 = height * 1.5;

let numOfColumns = Math.ceil((y1 - y0) / resolution);
let numOfRows = Math.ceil((x1 - x0) / resolution);

let cells = [];

d3.range(numOfRows).forEach((row, i) => {
cells.push([]);
d3.range(numOfColumns).forEach((col) => {
cells[i].push({
x: col * resolution,
y: row * resolution,
vector: params.cellValue ? params.cellValue(col, row) : 0,
theta: params.cellValue
? Math.atan2(
params.cellValue(col, row).x,
params.cellValue(col, row).y
)
: 0
});
});
});

return cells;
}
Insert cell
vectorForGivenPositionAndPole = (x, y, px, py, positive) => {
let dx = x - px;
let dy = y - py;

if (positive) {
return Math.atan2(dy, dx);
} else {
return (Math.atan2(dy, dx) + Math.PI) % (2 * Math.PI);
}
}
Insert cell
fieldDirectionSampler = (x, y, poles, noiseZ) => {
// Setting up the noise
let noise = n.simplex3(x / 20, y / 20, noiseZ) * 2 * Math.PI;
// perlin.gen((x / width) * perlinScale, (y / height) * perlinScale) *
// 2 *
// Math.PI;
let noiseStr = perlinNoiseStr;

// Noise at given point
let perlinBackground = {
sumx: Math.cos(noise) * noiseStr,
sumy: Math.sin(noise) * noiseStr
};

// Stack the influence of the poles on top
let sumfield = poles.reduce((memo, pole) => {
let dx = x - pole.position.x;
let dy = y - pole.position.y;
let r = Math.hypot(dx, dy);
let magnitude = magneticStr / Math.pow(r, magneticRange);
let theta = vectorForGivenPositionAndPole(
x,
y,
pole.position.x,
pole.position.y,
pole.positive
);

// Influence of each pole at the given point
let fieldx = magnitude * Math.cos(theta);
let fieldy = magnitude * Math.sin(theta);

return {
sumx: memo.sumx + fieldx,
sumy: memo.sumy + fieldy
};
}, perlinBackground); // { sumx: 0, sumy: 0 })

let vector = new Vector(sumfield.sumx, sumfield.sumy);
// the Math.PI / 2 at the end "turns" the field 90 degrees so we get the whirlpool directions
vector.rotate(Math.PI / 2);

return vector;
}
Insert cell
getNearestFieldPoint = (x, y, grid) => {
let col = Math.max(0, Math.min(Math.round(x / resolution), grid.length - 1));
let row = Math.max(0, Math.min(Math.round(y / resolution), grid.length - 1));

return grid[row][col];
}
Insert cell
cellRenderer = [
(enter) => {
let g = enter
.append("g")
.attr(
"transform",
(d, i) =>
`translate(${d.x}, ${d.y}) rotate(${(d.theta / Math.PI) * 180})`
);

g.append("line")
.attr("x0", 0)
.attr("y0", 0)
.attr("x1", 3)
.attr("y0", 0)
.attr("stroke", "black")
.attr("stroke-width", 1);

g.append("polygon").attr("fill", "black").attr("points", "6,0 3,2 3,-2");

g.append("circle").attr("fill", "black").attr("r", "1");

return g;
},
(update) => {
return update.attr(
"transform",
(d, i) => `translate(${d.x}, ${d.y}) rotate(${(d.theta / Math.PI) * 180})`
);
}
]
Insert cell
gridLayer = (g, grid) => {
g.selectAll("g")
.data(grid)
.join(...cellRenderer);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more