Published
Edited
Mar 22, 2021
Importers
16 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
rotation_logic = glsl `
mat2 rotations(float a) {
if (a < 0.5) {
return mat2( 1., 0.,
0., 1.);
}
if (a <= 1.5) {
return mat2( 1. , 0.,
0., -1.);
}
if (a <= 2.5) {
return mat2( -1., 0.,
0., 1.);
}
return mat2( 1., 0.,
0., 1.);
}
`
Insert cell
Insert cell
H_Position = glsl`
vec2 H_position(in float a, mat2 rot) {
// input is in 0., 1., 2., 3.

vec2 p;

// Check both the rotations at once. This will be 2 or zero.
if (a < 0.5) { p = vec2(-.5, 1.);}
else if (a < 1.5) {p = vec2(-.5, -1.);}
else if (a < 2.5) {p = vec2(0.5, -1.);}
else {p = vec2(1.5, -1.);}
return p * rot;
}
`

Insert cell
md`## implementation

As with Hilbert, those two functions make it possible to calculate any finite number of points.

The direction at which you enter the L (long or short) varies depending on rotation, so that is coded into the loop below.

`
Insert cell
H_Curve = glsl`

float modI(float a, float b) {
float m=a-floor((a+0.5)/b)*b;
return floor(m+0.5);
}

${rotation_logic.value}
${H_Position.value}
// Two necessary functions.

vec2 H_Curve(float raw_ix) {


// Start near the origin with an identity transform;
vec2 xy = vec2(0., 0.);

// Numbers greater than zero are
bool negative = raw_ix < -.5;
float sign = 1.;
float abs_ix = abs(raw_ix);

if (negative) {
sign = -1.;
abs_ix -= 1.; // Waste a cycle and do zero from both directions.
// Could cause some problems?
}

mat2 rotation = mat2(sign, 0.,
0., sign );
float MAX_ITER = 14.;

// Is the point even or odd? Used to position the up or down direction.
float jag = modI(abs_ix, 2.);
float ix = floor(abs_ix/ 2.0 + 0.1);
// Where we are in the L shape (0, 1, 2, 3);
float a;

for (float i = 16.; i > -.5; i -= 1.) {
float rank_in_layer = floor((ix + .01) / (pow(4., i)));
a = modI(rank_in_layer, 4.);

// This will change a in place.
if (rotation[0][0] * rotation[1][1] * sign < 0.) {
a = 3. - a;
}
vec2 p = H_position(a, rotation);
rotation = rotation * rotations(a);
xy += (p) * pow(2., i - 1.);

}

/* Position the last element */
// No clue what's going on here. The rotation
// functions look like they're a proxy for some kind of logic
// gate or something, but I got it nearly by trial and error.

float last_direction = rotation[1][1];
float is_rotated = rotation[1][1] * rotation[0][0];
if (is_rotated > 0.) {
last_direction *= -1.;
}
if (jag < .5) {last_direction *= -1.;}
xy += vec2(0., .25 * last_direction * sign);

// Some magic involving 2 ** 15 and 2**16 to reshift
// points somewhere close to the origin.
// If not using 16 as the highest point in the loop, this will have to change, too.
float y_shift = 0.;
float x_shift = 0.;
if (negative) {
xy += vec2(pow(2., 16.) + pow(2., 15.) + x_shift, -pow(2., 16.) + y_shift);
} else {
xy += vec2(pow(2., 15.) + x_shift, -pow(2., 16.));
}

return xy;
}
`
Insert cell
Insert cell
vertex_shader = glsl`
precision highp float;

attribute float ix;

uniform float u_time;
uniform float u_max_ix;
uniform float tick;
uniform sampler2D u_color_buffer;
uniform float u_separation;
varying vec4 fill;

${H_Curve.value}

void main() {
vec2 p_null = vec2(0., 0.);
gl_PointSize = 5.;
float relative_pos = ix - floor(u_max_ix/2.);
if (relative_pos < 0.) {relative_pos;}
float scale = 1. / sqrt(u_max_ix) * 2.5;
vec2 pos = (H_Curve(relative_pos) - p_null) * scale + vec2(-.99, .5);
gl_Position = vec4(pos.x, pos.y, 0., 1.);
float prop_through = ix / u_max_ix;
float offset = -fract(u_time / 5.);
fill = texture2D(u_color_buffer, vec2(fract(offset + ix/u_max_ix), 0.5));
// float n_colors = 8.;
// float m = 8.;
// fill = texture2D(u_color_buffer, vec2(fract(modI(floor((ix)/m), n_colors) / n_colors), 0.5));
// fill = texture2D(u_color_buffer, vec2(1000. * floor(ix/m), .5));

}
`
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
fbo = regl.framebuffer({width: Math.floor(width * .75), height: Math.floor(width * .75)})

Insert cell
z.points
Insert cell
draw_tick = {
const tick = regl.frame(({time}) => {
// clear contents of the drawing buffer
regl.clear({
color: [0, 0, 0, 0],
depth: 1
})
drawPoints({points: z.points, rounded_points: Math.round(z.points), primitive: z.primitive})
/*
fbo.use( () => {
regl.clear({
color: [0, 0, 0, 0],
depth: 1
})
drawPoints({points: z.points, rounded_points: Math.round(z.points), primitive: z.primitive})
})
drawPoints({points: z.points, rounded_points: Math.round(z.points), primitive: z.primitive})

copy_from_fbo(fbo)
})
*/
})
invalidation.then(() => tick.cancel())
}
Insert cell
copy_from_fbo = function(fbo) {
regl({
profile: true,
blend: {
enable: true,
func: {
srcRGB: 'one',
srcAlpha: 'one',
dstRGB: 'one minus src alpha',
dstAlpha: 'one minus src alpha',
}
},

frag: `
precision mediump float;
varying vec2 uv;
uniform sampler2D tex;
uniform float wRcp, hRcp;
void main() {
vec4 here = texture2D(tex, uv);
vec4 oneup = texture2D(tex, uv + vec2(wRcp, 0.));
vec4 twoup = texture2D(tex, uv + vec2(0., hRcp));
gl_FragColor = max(here, max(oneup, twoup));
}
`,
vert: `
precision mediump float;
attribute vec2 position;
varying vec2 uv;
void main() {
uv = 0.5 * (position + 1.0);
gl_Position = vec4(position, 0, 1);
}
`,
attributes: {
position: () => fill_buffer
},
depth: { enable: false },
count: 3,
uniforms: {
tex: fbo,
wRcp: ({viewportWidth}) => 1.0 / viewportWidth,
hRcp: ({viewportHeight}) => 1.0 / viewportHeight
},
})()
}
Insert cell
fill_buffer = regl.buffer(
{data: [ -4, -4, 4, -4, 0, 4 ]})
Insert cell
p = {
while (true) {
const s = d3.scaleLinear().domain([-1, 1]).range([4, 18])
if (animate.length) {
z.points = 2 ** (s(Math.sin(now/10000)))
} else {
z.points = (Math.pow(2, n_points))
}
z.primitive = primitive
yield z.points
}
}

Insert cell
fragment_shader = glsl`
precision mediump float;
varying vec4 fill;

void main() {
gl_FragColor = fill;
}
`
Insert cell
z = ({
rounded_points: 16,
primitive: "line"
})
Insert cell
z
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: fragment_shader.value,
vert: vertex_shader.value,
attributes: {
ix: {
buffer: points,
stride: 4,
offset: 0 * 4
},
},
uniforms: {
tick: ({ tick }) => tick,
u_max_ix: regl.prop("points"),
u_boust: 1, // Left older from previous version
u_time: regl.context("time"),
u_aspect_ratio: 1,
u_color_buffer: colorscheme,
u_separation: 0,
},
// specify the number of points to draw from the buffer.
count: (_, props) => props["rounded_points"],
primitive: regl.prop("primitive")
}

return regl(regl_params)
}
Insert cell
points = regl.buffer(vals)
Insert cell
vals = {
const vals = new Float32Array(2**24)
for (let i = 0; i < vals.length; i++) {
vals[i] = i
}
return vals
}
Insert cell
colorscheme = regl.texture(canvas)
Insert cell
wrapREGL = require('regl')
Insert cell
import { glsl } from "@stwind/glsl-chunk-tag";
Insert cell
d3 = require("d3@v6")
Insert cell
import {Checkbox, Radio, Range} from "@observablehq/inputs"

Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more