Published
Edited
Jul 24, 2021
29 stars
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
presets = JSON.parse(preset_raw)
Insert cell
Insert cell
regl_inputs = {return {'u_alpha': point_alpha, 'u_a': a, 'u_k': k, 'u_max_r': r, "u_period": period, points: points, 'u_size': point_size, 'u_point_size_adjust': 1, 'u_random_rotation': random_rotation, 'u_random_radius': random_radius, 'u_donut_hole': donut_size, u_shear: shear, u_n_spirals: parseInt(n_spirals), u_acceleration: acceleration_factor, 'u_random_angle': random_angle}}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
md`# Log spiral function.`
Insert cell
log_spiral = glsl`

const float tau = 2. * 3.14159265359;

highp float ix_to_random(in float ix, in float seed) {
// For high numbers, taking the log avoids coincidence.
highp float seed2 = log(ix) + 1.;
vec2 co = vec2(seed2, seed);
highp float a = 12.9898;
highp float b = 78.233;
highp float c = 43758.5453;
highp float dt= dot(co.xy ,vec2(a,b));
highp float sn= mod(dt,3.14);
return fract(sin(sn) * c);
}

highp vec2 box_muller(in float ix, in float seed) {
// Box-Muller transform gives you two gaussian randoms for two uniforms.
highp float U = ix_to_random(ix, seed);
highp float V = ix_to_random(ix, seed + 17.123123);
return vec2(
sqrt(-2.*log(U))*cos(tau*V),
sqrt(-2.*log(U))*sin(tau*V)
);
}

vec2 logarithmic_spiral_jitter(

in float seed, // a random seed.
in float a, // offset
in float angle_parameter, // angle parameter
in float randomize_angle, // sd radians
in float max_r, // Maximum radius of spiral.
in float randomize_rotation_max_radians, // in standard deviations to the log-multiplier.
in float randomize_radius, // in standard deviation percentage points.
in float hole, // donut hole size.
in float speed, // webgl units per second.
in float time,// The time, in seconds, to plot at. Generally passed as a uniform or something.
in float acceleration,
in float n_spirals,
in float shear
) {

/* Returns a position in clip space. */

// Each point starts at a different place on the spiral.
vec2 two_gaussians = box_muller(seed, 55.1);

highp float calculated_angle = angle_parameter + two_gaussians.x * randomize_angle;
float k = 1. / tan(calculated_angle);
if (k > 100000.) {
k = 0.;
}

// The length of the segment to be traversed.
float arc_length = sqrt((1. + k*k)/k) * (max_r - a);
float period = arc_length / speed;

// Every point needs to start at a different place along the curve.
float stagger_time = ix_to_random(seed, 3.);

// How long does a circuit take? Add some random noise.
float time_period = period * exp(two_gaussians.y / 6.);

// Adjust u_time from the clock to our current spot.
float varying_time = time + stagger_time * time_period;

// Adjust that time by raising to a power to set the speed along the curve.
// Not sure if this is the soundest way to parametrize.

float relative_time = pow(1. - mod(varying_time, time_period)/time_period, acceleration);

// Calculate the radius at this time point.
float radius = max_r * relative_time + a;

// The angle implied by that radius.
float theta = 1./k * log(radius / a);

/* A different way to calculate radius from the theta. Not used
float max_theta = 1. / k * log(max_r / a);
float theta2 = max_theta * relative_time;
vec2 pos_theta_style = vec2(a * exp(k * theta2), theta2);
radius = pos_theta_style.x;
theta = pos_theta_style.y;
*/

// If multiple spirals, the theta needs to be rotated for which spiral we're in.
// Choose it based on a new random seed.
float which_spiral = floor(ix_to_random(seed, 13.13) * n_spirals);

float which_spiral_adjust = which_spiral / n_spirals * tau;

theta = theta + which_spiral_adjust;

// Add some gaussian jitter to the polar coordinates.
vec2 polar_jitter = box_muller(seed, 24.);

highp float radius_adjust = exp(polar_jitter.x * randomize_radius);
highp float theta_adjust = polar_jitter.y * randomize_rotation_max_radians;

vec2 shear_adjust = box_muller(seed, 59.1) * shear;

mat3 shear_mat = mat3(
1., shear_adjust.x, 0.,
shear_adjust.y, 1., 0.,
0., 0., 1.);

// into clip space.

vec3 pos_spiral = vec3(
cos(theta + theta_adjust)*(radius * radius_adjust + hole),
sin(theta + theta_adjust)*(radius * radius_adjust + hole),
0.
);

pos_spiral = pos_spiral * shear_mat;
return pos_spiral.xy;
}

`
Insert cell
Insert cell
Insert cell
Insert cell
drawPoints = {
const regl_params = {
depth: { enable: false },
stencil: { enable: false },
blend: {
enable: true,
func: {
srcRGB: 'one',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
},
},

frag: `
precision mediump float;
varying vec4 fill;

void main() {
vec2 cxy = 2.0 * gl_PointCoord - 1.0;
float r = dot(cxy, cxy);
if (r > 1.0) discard;

gl_FragColor = vec4(fill);
}
`,
vert: `
precision mediump float;

attribute vec2 position;
attribute float ix;
attribute vec3 color;

uniform float u_time;
// uniform float u_aspect_ratio;

${uniforms.value}
uniform float tick;
const float e = 1.618282;

${log_spiral.value}

varying vec4 fill;

void main() {
vec2 pos2 = vec2(position.x, position.y);
float direction = 1.;
float num_spirals = u_n_spirals;
float angle = u_k;

float overall_time = 28.;
float transition_time = mod(u_time + ix_to_random(ix, 13.12) * overall_time, overall_time)/overall_time;

vec2 spiral_pos = logarithmic_spiral_jitter(
ix, // in float ix, // a random seed.
u_a, // in float a, // offset
angle * direction, // in float angle_parameter, // angle parameter
u_random_angle, // in float randomize_angle, // sd radians
u_max_r, // in float max_r, // Maximum radius of spiral.
u_random_rotation, // in float randomize_rotation_max_radians,
u_random_radius, // in float randomize_radius, // in standard deviation percentage points.
u_donut_hole, // in float hole, // donut hole size.
u_period, // in float speed, // webgl units per second.
u_time, // in float time,// The time, in seconds, to plot at. Generally passed as a uniform or something.
u_acceleration, // in float acceleration,
num_spirals,
u_shear); // in float n_spirals

gl_PointSize = ${+point_size + 1e-7};
gl_Position = vec4(spiral_pos.xy, 1., 1.);
fill = vec4(color * u_alpha, u_alpha);
}
`,

attributes: {
// stride represents the byte length of the buffer (GL_FLOAT is 4.0 bytes)
// offset represents the byte offset used to find the target information
// Starting x,y position of each point
position: {
buffer: points,
stride: vertSize,
offset: 0
},

ix: {
buffer: points,
stride: vertSize,
offset: 8
},

// Frag color
color: {
buffer: points,
stride: vertSize,
offset: 12
}
},

uniforms: {
tick: ({ tick }) => tick,
u_time: () => (Date.now() - load_time )/ 1000 * direction,
u_aspect_ratio: aspect_ratio
},

// specify the number of points to draw
count: 10**(logNumPoints),

// specify that each vertex is a point (not part of a mesh)
primitive: "points"
}
for (let [k, v] of Object.entries(regl_inputs)) {
regl_params.uniforms[k] = regl.prop(k)
}
return regl(regl_params)
}
Insert cell
points = {
// create initial set of points
return regl.buffer(
Array(3.5e06)
.fill()
.map((d, i) => {
return [
0,//Math.random() * 2 - 1,
0,//Math.random() * 2 - 1,
i,
Math.random(), // red
Math.random(), // green
Math.random() // blue
];
})
)
}
Insert cell
formula = tex`r = ae^{k\varTheta}`
Insert cell
md`Current spiral code (can copy and paste into presets): \`${JSON.stringify({a, k, r, period, point_size, random_rotation, random_radius, donut_size, acceleration_factor, shear, n_spirals, random_angle}, undefined, 1)}\`.`
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