Published
Edited
Mar 22, 2021
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
constants = ({
TAU: Math.PI * 2,
})
Insert cell
Insert cell
uniforms = ({
R: { type: 'slider', min: 0, max: 1, value: 0.15, step: 0.001 },
r: { type: 'slider', min: 0, max: 0.5, value: 0.03, step: 0.0001 },
a: { type: 'slider', min: 0, max: 0.5, step: 0.0001 },
frame: { type: 'frame' }
// frame: { type: 'slider', min: 0, max: 1000, value: 0, step: 1 }
})
Insert cell
Insert cell
N = 5;
Insert cell
expressions = {
const out = [];
const min = -1;
const max = 1;
const z = '-1.0';
for (let i = 0; i < N; i++) {
for (let j = 0; j < N; j++) {
const x = Number(min + i/(N - 1) * (max - min)).toFixed(5);
const y = Number(min + j/(N - 1) * (max - min)).toFixed(5);
const s1 = 60 * (3 + Math.floor(Math.random() * 5)) * (Math.random() > 0.5 ? -1 : 1)
const s2 = 60 * (3 + Math.floor(Math.random() * 5)) * (Math.random() > 0.5 ? -1 : 1)
out.push({
color: [1, 0, 1],
locals: {
// For convenience and performance, these get turned into local variables like:
// float NAME = EXPR;
// before the actual expression is computed
F: Math.floor(Math.random() * 30 + 1).toFixed(1),
t: `frame / ${s1.toFixed(1)}`,
r_wave: 'r * (1.0 + a*sin(F * TAU * (u + t)))',
A: 'R + r_wave * cos(TAU * v)',
x,
y,
z
},
expr: `(A * sin(TAU * u) + x, r_wave * sin(TAU * v) + y, A * cos(TAU * u) + z)`
}, {
// disabled: true,
color: [.3, 0, 1],
locals: {
F: Math.floor(Math.random() * 30 + 1).toFixed(1),
t: `frame / ${s2.toFixed(1)}`,
r_wave: 'r * (1.0 + a*sin(F * TAU * (u + t)))',
A: 'R + r_wave * cos(TAU * v)',
x,
y,
z
},
expr: `(r_wave * sin(TAU * v) + x, A * cos(TAU * u) + y, A * sin(TAU * u) - R + z)`
})
}
}
return out;
}

/*
[{
color: [1, 0, 1],
locals: {
// For convenience and performance, these get turned into local variables like:
// float NAME = EXPR;
// before the actual expression is computed
F: '5.0',
t: 'frame / 180.0',
r_wave: 'r * (1.0 + a*sin(F * TAU * (u + t)))',
A: 'R + r_wave * cos(TAU * v)',
},
expr: `(A * sin(TAU * u), r_wave * sin(TAU * v), A * cos(TAU * u))`
}, {
// disabled: true,
color: [.3, 0, 1],
locals: {
F: '10.0',
t: 'frame / 300.0',
r_wave: 'r * (1.0 + a*sin(F * TAU * (u + t)))',
A: 'R + r_wave * cos(TAU * v)'
},
expr: `(r_wave * sin(TAU * v), A * cos(TAU * u), A * sin(TAU * u) - R)`
}]
*/
Insert cell
Insert cell
Insert cell
Insert cell
// Convert plain geometry arrays into typed array buffers so that regl doesn't have to do this separately
// each time it uses the same underyling geometry.
buffers = ({
positions: regl.buffer(makeTypedArray(geometry.positions)),
faces: regl.elements(geometry.faces),
normalPositions: regl.buffer(makeTypedArray(geometry.normalVisualization.positions)),
normalOffsets: regl.buffer(makeTypedArray(geometry.normalVisualization.offsets)),
normalFaces: regl.elements(geometry.normalVisualization.faces)
})
Insert cell
Insert cell
uniformInfo = {
const uniformDeclarations = [];
const uniformLocals = [];
const uniformPropGetters = {};
for (const k in uniforms) {
const uniformName = `u_slider_${k}`;
uniformPropGetters[uniformName] = (_, props) => {
return props[k];
}
uniformLocals.push(`float ${k} = ${uniformName};`);
uniformDeclarations.push(`uniform float ${uniformName};`);
}
return {
uniformDeclarations,
uniformPropGetters,
uniformLocals
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function vertexShader(compiledExpression) {
return `
precision mediump float;

attribute vec2 a_position;

uniform mat4 projection, view;
uniform mat4 u_viewInverseTranspose;

// A "varying" is a value that we want to export to the fragment shader.
varying vec3 v_color;
varying vec3 v_normal;

${compiledExpression.glsl}

void main() {
vec3 pos = evaluate(a_position);
// calculate normal
vec3 normal = evaluateNormal(a_position, pos);

// Multiply our transformation matrices to set the vertex's position in screen space.
gl_Position = projection * view * vec4(pos, 1.0);

// Export the v_color varying to just be the color that we assigned for this vertex.
v_color = vec3(${compiledExpression.color.map(n => n.toFixed(3))});
v_normal = (mat3(u_viewInverseTranspose) * normal).xyz;
}
`}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
mutable loop = {
console.log('Rebuilding')
const specs = [];
for (const expression of compiled) {
specs.push(surfaceSpec(expression));
if (controls.showNormals) {
specs.push(normalsSpec(expression))
}
}
const camera = {
center: [0, 0, 0],
up: [0, 1, 0],
distance: 2,
theta: + Math.PI / 2,
phi: 0,
noScroll: true,
rotationSpeed: 1,
zoomSpeed: 3,
element: animationCanvas,
log: true
};
return new Draw(specs, {camera, regl, canvas: animationCanvas});
}
Insert cell
drawing = {
let frame = 1;
const MIN_FRAME_TIME = 1000 / 60;
const frameTimes = [];

const getProps = () => ({
...(mutable sliders.values),
...getFrameUniforms(frame),
controls: (mutable dynamicControls).values
});

try {
let last = Date.now();
while (true) {
const props = getProps();
loop.draw(props);
const now = Date.now();
const time = now - last;
last = now;
frameTimes.push(time);
if (frameTimes.length > 100) frameTimes.shift();
if (mutable playing) {
frame += 1;
}
yield { status: mutable playing ? 'running' : 'stopped', frame, fps: meanFPS(frameTimes), props};
}
} catch (e) {
yield { status: 'error', error: e, props: getProps() };
// throw e;
}
}
Insert cell
Insert cell
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