Public
Edited
Mar 9, 2023
3 stars
Insert cell
Insert cell
width = 512
Insert cell
vertexShader = `
attribute vec2 a_position;
uniform vec2 u_resolution;

void main() {
gl_Position = vec4((a_position / u_resolution) * 2.0 - 1.0, 0.0, 1.0);
gl_PointSize = 2.0;
}
`
Insert cell
fragShader = `
precision mediump float;

void main() {
gl_FragColor = vec4(vec3(0.0), 0.45);
}
`
Insert cell
function createParticles(length) {
const inkSize = 10;
return _.times(length, () => {
// position
let angle = _.random(0, 2 * Math.PI);
// let radius = Math.min(_.random(inkSize), 0.5 * inkSize);
let radius = _.random(inkSize);
const position = vec2.fromValues(
radius * Math.cos(angle) + width / 2,
radius * Math.sin(angle) + width * 0.95
);
// velocity
angle = _.random(0, 2 * Math.PI);
radius = _.random(0.5 * inkSize);
const velocity = vec2.fromValues(
// radius * Math.cos(angle),
// radius * Math.sin(angle)
0,
0
);

return {
position,
// initial velocity
velocity,
acceleration: vec2.create(),
life: _.random(1000, 3000)
};
});
}
Insert cell
{
const canvas = DOM.canvas(width, width);
const gl = canvas.getContext("webgl");

// create program
const program = initShaderProgram(gl, vertexShader, fragShader);

// attribute location
const positionAttribLoc = gl.getAttribLocation(program, "a_position");
const resolutionUniformLoc = gl.getUniformLocation(program, "u_resolution");

gl.viewport(0, 0, width, width);
gl.useProgram(program);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
gl.enable(gl.BLEND);
gl.disable(gl.DEPTH_TEST);

gl.uniform2f(resolutionUniformLoc, width, width);

let particles = createParticles(1000);

const start = Date.now();
while (true) {
const elapsed = Date.now() - start;
const positions = [];

// filter out "dead" particles, add new ones
particles = _.chain(particles)
.union(createParticles(300))
.filter((d) => d.life)
.value();
console.log(particles.length);
const gravity = vec2.fromValues(0, -5);
_.each(particles, (d, i) => {
const { position, velocity, acceleration } = d;
// disperse
const [x, y] = position;

// if (y < width * 0.75) {
const radius = 5;
const angle = d3.scaleLinear(
[0, 1],
[Math.PI * -3, 3 * Math.PI]
)(noise(x / 50, y / 50, elapsed / 10000));
vec2.add(
velocity,
velocity,
vec2.fromValues(radius * Math.cos(angle), radius * Math.sin(angle))
);

// add downforce
vec2.add(velocity, velocity, gravity);
// } else {
// // add downforce
// vec2.add(velocity, velocity, vec2.fromValues(0, -5));
// }

// update position
vec2.add(position, position, velocity);
// reset velocity
vec2.scale(velocity, velocity, 0.5);

positions.push(position[0]);
positions.push(position[1]);

// decrement life counter
d.life -= 1;
});
const positionBuffer = initPositionBuffer(gl, positions);
setPositionAttribute(gl, positionBuffer, positionAttribLoc);

gl.drawArrays(gl.POINTS, 0, particles.length);

yield Promises.delay(60, canvas);
}
}
Insert cell
Insert cell
Insert cell
import {
initShaderProgram,
initPositionBuffer,
setPositionAttribute
} from "@sxywu/00-webgl-setup"
Insert cell
import { vec2, vec3 } from "@sxywu/00-3-nature-of-code-utilities"
Insert cell
noise = require("p5").then((p5) => p5.prototype.noise)
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