Published
Edited
Mar 24, 2021
Importers
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Draw = class Draw {
constructor(specs, options) {
if (options.regl) {}
if (options.viewport) {
this.canvas = html`<canvas width=${options.viewport.width} height=${options.viewport.height}>`;
} else {
this.canvas = options.canvas;
}
this.regl = options.regl || createRegl({
canvas: this.canvas
});
if (options.camera) {
this.camera = createCamera(this.regl, options.camera);
}
specs = Array.isArray(specs) ? specs : [specs];
this._drawFns = specs.map(spec => this.regl(spec(this.regl)));
this.canvas.value = this;
}
draw(props, clearColor = [0, 0, 0, 1]) {
this.camera(state => {
this.regl.clear({
color: clearColor
});
for (const fn of this._drawFns) {
fn(props);
}
})
}
}
Insert cell
Insert cell
createCamera = {
// https://github.com/regl-project/regl-camera/blob/master/regl-camera.js
const {identity, perspective, lookAt} = mat4;
var isBrowser = typeof window !== 'undefined'

function createCamera (regl, props_) {
var props = props_ || {}

// Preserve backward-compatibilty while renaming preventDefault -> noScroll
if (typeof props.noScroll === 'undefined') {
props.noScroll = props.preventDefault;
}

var cameraState = window.cameraState = {
view: identity(new Float32Array(16)),
projection: identity(new Float32Array(16)),
center: new Float32Array(props.center || 3),
theta: props.theta || 0,
phi: props.phi || 0,
distance: Math.log(props.distance || 10.0),
eye: new Float32Array(3),
up: new Float32Array(props.up || [0, 1, 0]),
fovy: props.fovy || Math.PI / 4.0,
near: typeof props.near !== 'undefined' ? props.near : 0.01,
far: typeof props.far !== 'undefined' ? props.far : 1000.0,
noScroll: typeof props.noScroll !== 'undefined' ? props.noScroll : false,
flipY: !!props.flipY,
dtheta: 0,
dphi: 0,
rotationSpeed: typeof props.rotationSpeed !== 'undefined' ? props.rotationSpeed : 1,
zoomSpeed: typeof props.zoomSpeed !== 'undefined' ? props.zoomSpeed : 1,
renderOnDirty: typeof props.renderOnDirty !== undefined ? !!props.renderOnDirty : false
}

var element = props.element
var damping = typeof props.damping !== 'undefined' ? props.damping : 0.9

var right = new Float32Array([1, 0, 0])
var front = new Float32Array([0, 0, 1])

var minDistance = Math.log('minDistance' in props ? props.minDistance : 0.1)
var maxDistance = Math.log('maxDistance' in props ? props.maxDistance : 1000)

var ddistance = 0

var prevX = 0
var prevY = 0

if (isBrowser && props.mouse !== false) {
var source = element || regl._gl.canvas

function getWidth () {
return element ? element.offsetWidth : window.innerWidth
}

function getHeight () {
return element ? element.offsetHeight : window.innerHeight
}

mouseChange(source, function (buttons, x, y) {
if (buttons & 1) {
var dx = (x - prevX) / getWidth()
var dy = (y - prevY) / getHeight()

cameraState.dtheta += cameraState.rotationSpeed * 4.0 * dx
cameraState.dphi += cameraState.rotationSpeed * 4.0 * dy
cameraState.dirty = true;
}
prevX = x
prevY = y
})

mouseWheel(source, function (dx, dy) {
ddistance += dy / getHeight() * cameraState.zoomSpeed
cameraState.dirty = true;
}, props.noScroll)
}

function damp (x) {
var xd = x * damping
if (Math.abs(xd) < 0.1) {
return 0
}
cameraState.dirty = true;
return xd
}

function clamp (x, lo, hi) {
return Math.min(Math.max(x, lo), hi)
}

function updateCamera (props) {
Object.keys(props).forEach(function (prop) {
cameraState[prop] = props[prop]
})

var center = cameraState.center
var eye = cameraState.eye
var up = cameraState.up
var dtheta = cameraState.dtheta
var dphi = cameraState.dphi

const previous = {
theta: cameraState.theta,
phi: cameraState.phi,
distance: cameraState.distance
};
cameraState.theta += dtheta
cameraState.phi = clamp(
cameraState.phi + dphi,
-Math.PI / 2.0,
Math.PI / 2.0)
cameraState.distance = clamp(
cameraState.distance + ddistance,
minDistance,
maxDistance)
cameraState.dtheta = damp(dtheta)
cameraState.dphi = damp(dphi)
ddistance = damp(ddistance)

var theta = cameraState.theta
var phi = cameraState.phi
var r = Math.exp(cameraState.distance)

var vf = r * Math.sin(theta) * Math.cos(phi)
var vr = r * Math.cos(theta) * Math.cos(phi)
var vu = r * Math.sin(phi)

for (var i = 0; i < 3; ++i) {
eye[i] = center[i] + vf * front[i] + vr * right[i] + vu * up[i]
}
if (cameraState.dirty && props_.log) {
console.log('camera', cameraState)
}

lookAt(cameraState.view, eye, center, up)
}

cameraState.dirty = true;

var injectContext = regl({
context: Object.assign({}, cameraState, {
dirty: function () {
return cameraState.dirty;
},
projection: function (context) {
perspective(cameraState.projection,
cameraState.fovy,
context.viewportWidth / context.viewportHeight,
cameraState.near,
cameraState.far)
if (cameraState.flipY) { cameraState.projection[5] *= -1 }
return cameraState.projection
}
}),
uniforms: Object.keys(cameraState).reduce(function (uniforms, name) {
uniforms[name] = regl.context(name)
return uniforms
}, {})
})

function setupCamera (props, block) {
if (typeof setupCamera.dirty !== 'undefined') {
cameraState.dirty = setupCamera.dirty || cameraState.dirty
setupCamera.dirty = undefined;
}

if (props && block) {
cameraState.dirty = true;
}

if (cameraState.renderOnDirty && !cameraState.dirty) return;

if (!block) {
block = props
props = {}
}

updateCamera(props)
injectContext(block)
cameraState.dirty = false;
}

Object.keys(cameraState).forEach(function (name) {
setupCamera[name] = cameraState[name]
})

return setupCamera
}

return createCamera;
}
Insert cell
mouseChange = require(await FileAttachment("mouse-change-bundle.js").url())
Insert cell
mouseWheel = require(await FileAttachment("mouse-wheel-bundle.js").url())
Insert cell
Insert cell
compilerTest = {
const compile = GlslCompiler({uniform: name => `uniforms.${name}`, attribute: name => `attributes.${name}`})
return compile(`
precision mediump float;
attribute vec2 uv;
attribute vec4 color;
varying vec4 fColor;
uniform vec2 uScreenSize;

void main (void) {
fColor = color;
vec2 position = vec2(uv.x, -uv.y) * 1.0;
position.x *= uScreenSize.y / uScreenSize.x;
gl_Position = vec4(position, 0, 1);
}
`)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function stripPragma(s) {
return s.split('\n').map(line => line.replace(/^#pragma.*$/, '')).join('\n')
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
showMatrix([1,1.123123,2,3], {name: 'm', round: 3})
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