Published
Edited
Jul 6, 2019
40 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
N;
regl.poll();
configureComputePasses({dt, dx, N}, () => {
initializeSolutionPass({
output: y[0],
wavenumber,
envelopeWidth,
});
});
while (true) {
regl.poll();
configureComputePasses({dt, dx, N}, () => {
performFFTPasses(forwardFFTPasses({input: y[0], output: y[0]}));
for (let i = 0; i < 10; i++) {
timeIntegrationPass([
{input: y[0], output: y[1]},
{input: y[1], output: y[0]}
]);
}

performFFTPasses(inverseFFTPasses({input: y[0], output: y[0]}));
})

drawToScreen({envelope});
yield regl.container
}
}
Insert cell
Insert cell
Insert cell
x = regl.buffer(new Array(N).fill(0).map((d, i) => (i / N) * 2.0 - 1.0))
Insert cell
Insert cell
y = new Array(3).fill(0).map(() => regl.framebuffer({
color: regl.texture({
width: N,
height: 1,

// (r,b,g,a) => (real0, imag0, real1, imag1)
format: 'rgba',
type: preferHalfFloat ? 'half float' : 'float',

// We have discrete solution components; we need no interpolation.
min: 'nearest',
mag: 'nearest',
})
}))
Insert cell
Insert cell
CFL = 0.95
Insert cell
dx = 1.0 / N
Insert cell
dt = Math.min(1e-6, CFL * Math.pow(dx * 0.5, 2))
Insert cell
Insert cell
initializeSolutionPass = regl({
frag: `
precision highp float;
varying vec2 uv;
uniform vec2 uResolution;
uniform float uDx, uK, uEnvelopeWidth;
void main () {
float x = ((gl_FragCoord.x - 0.5) * uResolution.x - 0.5) / uResolution.x * uDx;

gl_FragColor = vec4(
vec2(
cos(uK * x),
sin(uK * x)
) * exp(-0.5 * pow(x / uEnvelopeWidth, 2.0)),
vec2(0)
);
}
`,
uniforms: {
uSrc: regl.prop('input'),
uK: regl.prop('wavenumber'),
uEnvelopeWidth: regl.prop('envelopeWidth'),
},
framebuffer: regl.prop('output'),
})
Insert cell
Insert cell
timeIntegrationPass = regl({
frag: `
precision highp float;
varying vec2 uv;
uniform sampler2D uSrc;
uniform vec2 uResolution;
uniform float uDt, uDx;

const float TWOPI = 3.14159265358979 * 2.0;
const float hbar = 1.0;
const float m = 1.0;

vec2 dydt (vec2 y, float k2) {
return k2 * (hbar * 0.5 / m) * vec2(y.y, -y.x);
}

float wavenumber (float resolution, float dx) {
float x = (gl_FragCoord.x - 0.5) * resolution;
return ((x < 0.5) ? x : x - 1.0) * (TWOPI / dx);
}
void main () {
float kx = wavenumber(uResolution.x, uDx);
float kx2 = kx * kx;

// Get the current state
vec2 y0 = texture2D(uSrc, uv).xy;

// Perform RK-4 time integration
vec2 k1 = uDt * dydt(y0, kx2);
vec2 k2 = uDt * dydt(y0 + k1 * 0.5, kx2);
vec2 k3 = uDt * dydt(y0 + k2 * 0.5, kx2);
vec2 k4 = uDt * dydt(y0 + k3, kx2);
vec2 y1 = y0 + (k1 + k4 + 2.0 * (k2 + k3)) / 6.0;

// Return the stepped solution
gl_FragColor = vec4(y1, vec2(0));
}
`,
uniforms: {
uSrc: regl.prop('input'),
},
framebuffer: regl.prop('output'),
})
Insert cell
Insert cell
configureComputePasses = regl({
vert: `
precision highp float;
attribute vec2 xy;
varying vec2 uv;
void main () {
uv = xy * 0.5 + 0.5;
gl_Position = vec4(xy, 0, 1);
}`,
attributes: {
xy: [-4, -4, 4, -4, 0, 4],
},
uniforms: {
uDt: regl.prop('dt'),
uDx: regl.prop('dx'),
uResolution: (ctx, props) => [1 / props.N, 1],
},
depth: {enable: false},
count: 3
})
Insert cell
Insert cell
performFFTPasses = regl({
frag: `
precision highp float;

uniform sampler2D uSrc;
uniform vec2 uResolution;
uniform float uSubtransformSize, uNormalization;
uniform bool uHorizontal, uForward;

const float TWOPI = 6.283185307179586;

vec4 fft (
sampler2D src,
vec2 resolution,
float subtransformSize,
bool horizontal,
bool forward,
float normalization
) {
vec2 evenPos, oddPos, twiddle, outputA, outputB;
vec4 even, odd;
float index, evenIndex, twiddleArgument;

index = (horizontal ? gl_FragCoord.x : gl_FragCoord.y) - 0.5;

evenIndex = floor(index / subtransformSize) *
(subtransformSize * 0.5) +
mod(index, subtransformSize * 0.5) +
0.5;

if (horizontal) {
evenPos = vec2(evenIndex, gl_FragCoord.y);
oddPos = vec2(evenIndex, gl_FragCoord.y);
} else {
evenPos = vec2(gl_FragCoord.x, evenIndex);
oddPos = vec2(gl_FragCoord.x, evenIndex);
}

evenPos *= resolution;
oddPos *= resolution;

if (horizontal) {
oddPos.x += 0.5;
} else {
oddPos.y += 0.5;
}

even = texture2D(src, evenPos);
odd = texture2D(src, oddPos);

twiddleArgument = (forward ? TWOPI : -TWOPI) * (index / subtransformSize);
twiddle = vec2(cos(twiddleArgument), sin(twiddleArgument));

return (even.rgba + vec4(
twiddle.x * odd.xz - twiddle.y * odd.yw,
twiddle.y * odd.xz + twiddle.x * odd.yw
).xzyw) * normalization;
}

void main () {
gl_FragColor = fft(uSrc, uResolution, uSubtransformSize, uHorizontal, uForward, uNormalization);
}`,
uniforms: {
uSrc: regl.prop('input'),
uResolution: regl.prop('resolution'),
uSubtransformSize: regl.prop('subtransformSize'),
uHorizontal: regl.prop('horizontal'),
uForward: regl.prop('forward'),
uNormalization: regl.prop('normalization'),
},
framebuffer: regl.prop('output'),
})
Insert cell
Insert cell
inverseFFTPasses = {
let inversePasses = planFFT({
width: N,
height: 1,
input: y[0],
ping: y[1],
pong: y[2],
output: y[0],
forward: false
})
return function (opts) {
inversePasses[0].input = opts.input;
inversePasses[inversePasses.length - 1].output = opts.output;
return inversePasses;
}
}
Insert cell
forwardFFTPasses = {
let forwardPasses = planFFT({
width: N,
height: 1,
input: y[0],
ping: y[1],
pong: y[2],
output: y[0],
forward: true
})
return function (opts) {
forwardPasses[0].input = opts.input;
forwardPasses[forwardPasses.length - 1].output = opts.output;
return forwardPasses;
}
}
Insert cell
Insert cell
function planFFT (opts) {
function isPowerOfTwo(n) {
return n !== 0 && (n & (n - 1)) === 0
}
function checkPOT (label, value) {
if (!isPowerOfTwo(value)) {
throw new Error(label + ' must be a power of two. got ' + label + ' = ' + value);
}
}
var i, ping, pong, uniforms, tmp, width, height;

opts = opts || {};
opts.forward = opts.forward === undefined ? true : opts.forward;
opts.splitNormalization = opts.splitNormalization === undefined ? true : opts.splitNormalization;

function swap () {
tmp = ping;
ping = pong;
pong = tmp;
}

if (opts.size !== undefined) {
width = height = opts.size;
checkPOT('size', width);
} else if (opts.width !== undefined && opts.height !== undefined) {
width = opts.width;
height = opts.height;
checkPOT('width', width);
checkPOT('height', width);
} else {
throw new Error('either size or both width and height must provided.');
}

// Swap to avoid collisions with the input:
ping = opts.ping;
if (opts.input === opts.pong) {
ping = opts.pong;
}
pong = ping === opts.ping ? opts.pong : opts.ping;

var passes = [];
var xIterations = Math.round(Math.log(width) / Math.log(2));
var yIterations = Math.round(Math.log(height) / Math.log(2));
var iterations = xIterations + yIterations;

// Swap to avoid collisions with output:
if (opts.output === ((iterations % 2 === 0) ? pong : ping)) {
swap();
}

// If we've avoiding collision with output creates an input collision,
// then you'll just have to rework your framebuffers and try again.
if (opts.input === pong) {
throw new Error([
'not enough framebuffers to compute without copying data. You may perform',
'the computation with only two framebuffers, but the output must equal',
'the input when an even number of iterations are required.'
].join(' '));
}

for (i = 0; i < iterations; i++) {
uniforms = {
input: ping,
output: pong,
horizontal: i < xIterations,
forward: !!opts.forward,
resolution: [1.0 / width, 1.0 / height]
};

if (i === 0) {
uniforms.input = opts.input;
} else if (i === iterations - 1) {
uniforms.output = opts.output;
}

if (i === 0) {
if (!!opts.splitNormalization) {
uniforms.normalization = 1.0 / Math.sqrt(width * height);
} else if (!opts.forward) {
uniforms.normalization = 1.0 / width / height;
} else {
uniforms.normalization = 1;
}
} else {
uniforms.normalization = 1;
}

uniforms.subtransformSize = Math.pow(2, (uniforms.horizontal ? i : (i - xIterations)) + 1);

passes.push(uniforms);

swap();
}

return passes;
}
Insert cell
Insert cell
function drawToScreen (opts) {
regl.clear({color: [1, 1, 1, 1]});
configureViewport(() => {
// Draw the axes
drawLineSegments({
positions: axisLinesBuffers.positions,
colors: axisLinesBuffers.colors,
count: axisLines.positions.length / 2,
lineWidth: 1
});
// Draw lines for the real and imaginary components of the solution
drawLinesFromTexture([{
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [211 / 255, 45 / 255, 161 / 255, opts.envelope ? 0.5 : 0.5],
mask: [1, 0, 0, 0],
lineWidth: 1.5
}, {
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [45 / 255, 161 / 255, 211 / 255, opts.envelope ? 0.5 : 0.5],
mask: [0, 1, 0, 0],
lineWidth: 1.5
}]);
//if (!envelope) {
// Draw points for the real and imaginary components of the solution
drawPointsFromTexture([{
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [211 / 255, 45 / 255, 161 / 255, opts.envelope ? 0.3 : 1],
mask: [1, 0, 0, 0],
pointSize: 4,
}, {
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [45 / 255, 161 / 255, 211 / 255, opts.envelope ? 0.3 : 1],
mask: [0, 1, 0, 0],
pointSize: 4,
}]);
//}
// Draw lines for the real and imaginary components of the solution
if (opts.envelope) {
drawEnvelopeFromTexture([{
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [0.3, 0.1, 0.3, 0.7],
sign: 1,
lineWidth: 2
}, {
x: x,
positionsData: y[0],
lookupTable: textureLookupTableBuffer,
count: N,
color: [0.3, 0.1, 0.3, 0.7],
sign: -1,
lineWidth: 2
}]);
}
});
}
Insert cell
Insert cell
textureLookupTable = createTextureLookupTable(N, 1)
Insert cell
textureLookupTableBuffer = regl.buffer(textureLookupTable)
Insert cell
function createTextureLookupTable (width, height, stride) {
stride = stride || 2;
let n = width * height * stride;

let out = new Float32Array(n);

for (let i = 0, iStride = 0; iStride < n; i++, iStride += stride) {
out[iStride] = ((i % width) + 0.5) / width;
out[iStride + 1] = (((i / width) | 0) + 0.5) / height;
}

return out;
}
Insert cell
Insert cell
drawPointsFromTexture = regl({
vert: `
precision highp float;
uniform mat4 uProjectionView;
uniform vec4 uMask;
uniform float uPointSize;
attribute vec2 aPoint;
attribute float aX;
uniform sampler2D uSrc;

void main () {
float y = dot(texture2D(uSrc, aPoint.xy), uMask);
vec2 xy = vec2(aX, y);
gl_Position = uProjectionView * vec4(xy, 0, 1);
gl_PointSize = uPointSize;
}`,
frag: `
precision highp float;
uniform vec4 uColor;
uniform float uPointSize;
void main () {
float r = length(gl_PointCoord.xy * 2.0 - 1.0) * uPointSize;
float alpha = smoothstep(uPointSize, uPointSize - 2.0, r);
gl_FragColor = vec4(uColor.rgb, uColor.a * alpha);
}`,
attributes: {
aPoint: regl.prop('lookupTable'),
aX: regl.prop('x'),
},
uniforms: {
uSrc: regl.prop('positionsData'),
uMask: regl.prop('mask'),
uColor: regl.prop('color'),
uPointSize: (ctx, props) => ctx.pixelRatio * props.pointSize,
},
depth: {enable: false},
primitive: 'points',
count: regl.prop('count'),
});
Insert cell
Insert cell
drawLinesFromTexture = regl({
vert: `
precision highp float;
uniform mat4 uProjectionView;
uniform vec4 uMask;
uniform float uLineWidth, uAspect;
attribute vec2 aLookup, aNextLookup;
attribute float aX, aNextX;
attribute vec2 aLinePosition;
uniform sampler2D uSrc;

vec2 lineNormal (vec4 p, vec4 n, float aspect) {
return normalize((p.yx / p.w - n.yx / n.w) * vec2(1, aspect)) * vec2(-1.0 / aspect, 1);
}

void main () {
float y = dot(texture2D(uSrc, aLookup), uMask);
float yNext = dot(texture2D(uSrc, aNextLookup), uMask);

vec2 xy = vec2(aX, y);
vec2 xyNext = vec2(aNextX, yNext);

vec4 p = uProjectionView * vec4(xy, 0, 1);
vec4 n = uProjectionView * vec4(xyNext, 0, 1);

// Select one or the other positions
gl_Position = mix(p, n, aLinePosition.y);

// Apply the screen-space offset
gl_Position.xy += lineNormal(p, n, uAspect) * aLinePosition.x * uLineWidth * gl_Position.w;
}`,
frag: `
precision mediump float;
uniform vec4 uColor;
void main () {
gl_FragColor = uColor;
}`,
attributes: {
aLookup: {buffer: regl.prop('lookupTable'), divisor: 1, stride: 8},
aNextLookup: {buffer: regl.prop('lookupTable'), divisor: 1, offset: 8, stride: 8},
aX: {buffer: regl.prop('x'), divisor: 1},
aNextX: {buffer: regl.prop('x'), offset: 4, divisor: 1},
aLinePosition: [-1, 0, 1, 0, -1, 1, 1, 1],
},
uniforms: {
uSrc: regl.prop('positionsData'),
uLineWidth: (ctx, props) => props.lineWidth / ctx.framebufferHeight * ctx.pixelRatio,
uMask: regl.prop('mask'),
uColor: regl.prop('color'),
},
primitive: 'triangle strip',
instances: (ctx, props) => props.count - 1,
count: 4,
});
Insert cell
drawEnvelopeFromTexture = regl({
vert: `
precision highp float;
uniform mat4 uProjectionView;
uniform float uSign;
uniform float uLineWidth, uAspect;
attribute vec2 aLookup, aNextLookup;
attribute float aX, aNextX;
attribute vec2 aLinePosition;
uniform sampler2D uSrc;

vec2 lineNormal (vec4 p, vec4 n, float aspect) {
return normalize((p.yx / p.w - n.yx / n.w) * vec2(1, aspect)) * vec2(-1.0 / aspect, 1);
}

void main () {
float y = length(texture2D(uSrc, aLookup).xy) * uSign;
float yNext = length(texture2D(uSrc, aNextLookup).xy) * uSign;

vec2 xy = vec2(aX, y);
vec2 xyNext = vec2(aNextX, yNext);

vec4 p = uProjectionView * vec4(xy, 0, 1);
vec4 n = uProjectionView * vec4(xyNext, 0, 1);

// Select one or the other positions
gl_Position = mix(p, n, aLinePosition.y);

// Apply the screen-space offset
gl_Position.xy += lineNormal(p, n, uAspect) * aLinePosition.x * uLineWidth * gl_Position.w;
}`,
frag: `
precision mediump float;
uniform vec4 uColor;
void main () {
gl_FragColor = uColor;
}`,
attributes: {
aLookup: {buffer: regl.prop('lookupTable'), divisor: 1, stride: 8},
aNextLookup: {buffer: regl.prop('lookupTable'), divisor: 1, offset: 8, stride: 8},
aX: {buffer: regl.prop('x'), divisor: 1},
aNextX: {buffer: regl.prop('x'), offset: 4, divisor: 1},
aLinePosition: [-1, 0, 1, 0, -1, 1, 1, 1],
},
uniforms: {
uSrc: regl.prop('positionsData'),
uLineWidth: (ctx, props) => props.lineWidth / ctx.framebufferHeight * ctx.pixelRatio,
uSign: regl.prop('sign'),
uColor: regl.prop('color'),
},
primitive: 'triangle strip',
instances: (ctx, props) => props.count - 1,
count: 4,
});
Insert cell
Insert cell
axisLines = {
let dark = [0.5, 0.5, 0.5, 1];
let light = [0.92, 0.92, 0.92, 1];
let positions = [];
let colors = [];
let labelPosition = [];
let labelText = [];
let labelAlign = [];
for (let i = -10; i <= 10; i += (width > 600 ? 1 : 2)) {
let x = i * 0.1;
labelPosition.push([x, -1]);
labelText.push(x.toFixed(1).replace(/([^\.])0$/,'$1'));
labelAlign.push('center');
if (~[-10, 0, 10].indexOf(i)) continue;
positions.push([x, -1], [x, 1]);
colors.push(light, light);
positions.push([x, -1], [x, -0.975]);
colors.push(dark, dark);
}
for (let i = -4; i <= 4; i += (width > 600 ? 1 : 2)) {
let y = i * 0.25;
labelPosition.push([-1, y]);
labelText.push(y.toFixed(2).replace(/([^\.])0$/,'$1'));
labelAlign.push('end')
if (~[-4, 0, 4].indexOf(i)) continue;
positions.push([-1, y], [1, y]);
colors.push(light, light);
positions.push([-1, y], [-0.99, y]);
colors.push(dark, dark);
}
positions.push([-1, 0], [1, 0],
[0, -1], [0, 1],
[-1, -1], [-1, 1],
[-1, 1], [1, 1],
[1, 1], [1, -1],
[1, -1], [-1, -1]);
colors.push(dark, dark, dark, dark, dark, dark, dark, dark, dark, dark, dark, dark);
return {positions, colors, labelPosition, labelText, labelAlign};
}
Insert cell
axisLinesBuffers = {
return {
positions: regl.buffer(axisLines.positions),
colors: regl.buffer(axisLines.colors)
};
}
Insert cell
Insert cell
{
let canvas = regl.labelCanvas;
let ctx = regl.labelCanvas.getContext('2d');
let pixelRatio = window.devicePixelRatio;
let w = canvas.width;
let h = canvas.height;
ctx.clearRect(0, 0, w, h);
ctx.fillColor = '#000';
let font = `sans-serif`
let fontSizePx = 10 * pixelRatio;
ctx.font = `${fontSizePx}px ${font}`;
ctx.textAlign = "center";
let labelPosition = axisLines.labelPosition;
let labelText = axisLines.labelText;
for (let i = 0; i < labelPosition.length; i++) {
let position = labelPosition[i];
let text = labelText[i];
let align = axisLines.labelAlign[i];
ctx.textAlign = align;
let xShift = align === 'center' ? 0 : -4 * pixelRatio;
let yShift = align === 'center' ? 2 * pixelRatio + fontSizePx : fontSizePx * 0.4;
let xy = vec3transformMat4([], [position[0], position[1], 0], projectionView);
let ii = w * (0.5 + 0.5 * xy[0]);
let jj = h * (0.5 - 0.5 * xy[1]);
ctx.fillText(text, ii + xShift, jj + yShift);
}
}
Insert cell
function vec3transformMat4(out, a, m) {
var x = a[0], y = a[1], z = a[2],
w = m[3] * x + m[7] * y + m[11] * z + m[15]
w = w || 1.0
out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w
out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w
out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w
return out
}
Insert cell
Insert cell
drawLineSegments = regl({
vert: `
precision highp float;
uniform mat4 uProjectionView;
uniform float uLineWidth, uAspect;
attribute vec2 aPosition, aNextPosition;
attribute vec4 aColor, aNextColor;
attribute vec2 aLinePosition;
varying vec4 vColor;

vec2 lineNormal (vec4 p, vec4 n, float aspect) {
return normalize((p.yx / p.w - n.yx / n.w) * vec2(1, aspect)) * vec2(-1.0 / aspect, 1);
}

void main () {
vec4 p = uProjectionView * vec4(aPosition, 0, 1);
vec4 n = uProjectionView * vec4(aNextPosition, 0, 1);

// Select one or the other positions
gl_Position = mix(p, n, aLinePosition.y);

vColor = mix(aColor, aNextColor, aLinePosition.y);

// Apply the screen-space offset
gl_Position.xy += lineNormal(p, n, uAspect) * aLinePosition.x * uLineWidth * gl_Position.w;
}`,
frag: `
precision mediump float;
varying vec4 vColor;
void main () {
gl_FragColor = vColor;
}`,
attributes: {
aPosition: {buffer: regl.prop('positions'), divisor: 1, offset: 0, stride: 16},
aNextPosition: {buffer: regl.prop('positions'), divisor: 1, offset: 8, stride: 16},
aColor: {buffer: regl.prop('colors'), divisor: 1, offset: 0, stride: 32},
aNextColor: {buffer: regl.prop('colors'), divisor: 1, offset: 16, stride: 32},
aLinePosition: [-1, 0, 1, 0, -1, 1, 1, 1],
},
uniforms: {
uLineWidth: (ctx, props) => props.lineWidth / ctx.framebufferHeight * ctx.pixelRatio,
uColor: regl.prop('color'),
},
primitive: 'triangle strip',
instances: (ctx, props) => props.count,
count: 4,
});
Insert cell
Insert cell
projectionView = [
(width - 35) / width, 0, 0, 0,
0, (height - 35) / height, 0, 0,
0, 0, 1, 0,
35 * 0.5 / width, 35 / height / 2, 0, 1
]
Insert cell
configureViewport = regl({
uniforms: {
uProjectionView: projectionView,
uAspect: ctx => ctx.framebufferWidth / ctx.framebufferHeight,
},
blend: {
enable: true,
func: {
srcRGB: 'src alpha',
srcAlpha: 1,
dstRGB: 'one minus src alpha',
dstAlpha: 1,
},
equation: {
rgb: 'add',
alpha: 'add',
}
},
depth: {enable: false},
})
Insert cell
Insert cell
createREGL = require("regl")
Insert cell
regl = createREGLInstance()
Insert cell
height = Math.max(250, Math.floor(width * 0.5));
Insert cell
function createREGLInstance () {
const container = document.createElement('div');
container.style.position = "relative";
const w = width;
const h = height;
const pixelRatio = window.devicePixelRatio;
container.style.width = w + 'px';
container.style.height = h + 'px';
const reglCanvas = document.createElement('canvas');
reglCanvas.style.position = "absolute";
reglCanvas.style.top = 0
reglCanvas.style.left = 0;
reglCanvas.style.width = '100%';
reglCanvas.style.height = '100%';
reglCanvas.style.zIndex = 1;
reglCanvas.width = Math.floor(w * pixelRatio);
reglCanvas.height = Math.floor(h * pixelRatio);
const labelCanvas = document.createElement('canvas');
labelCanvas.style.position = "absolute";
labelCanvas.style.top = 0
labelCanvas.style.left = 0;
labelCanvas.style.width = '100%';
labelCanvas.style.height = '100%';
labelCanvas.style.zIndex = 2;
labelCanvas.width = Math.floor(w * pixelRatio);
labelCanvas.height = Math.floor(h * pixelRatio);
container.appendChild(reglCanvas);
container.appendChild(labelCanvas);
const regl = createREGL({
canvas: reglCanvas,
attributes: {
antialias: true,
},
extensions: [
'OES_texture_float',
'ANGLE_instanced_arrays'
],
optionalExtensions: [
'OES_texture_half_float',
]
});
regl.container = container;
regl.reglCanvas = reglCanvas;
regl.labelCanvas = labelCanvas;
return regl;
}
Insert cell
function isTouchDevice() {
return (
!!(typeof window !== 'undefined' &&
('ontouchstart' in window ||
(window.DocumentTouch &&
typeof document !== 'undefined' &&
document instanceof window.DocumentTouch))) ||
!!(typeof navigator !== 'undefined' &&
(navigator.maxTouchPoints || navigator.msMaxTouchPoints))
);
}
Insert cell
preferHalfFloat = regl.hasExtension('OES_texture_half_float') && isTouchDevice()
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