Published
Edited
Jun 1, 2021
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// A simple plane of size 2x2 parallel to the xz plane
plane = ({
pos: [
[-1 + displace, 0, 1],
[1 + displace, 0, 1],
[1 + displace, 0, -1],
[-1 + displace, 0, -1]
],
normal: [[0, 1, 0], [0, 1, 0], [0, 1, 0], [0, 1, 0]],
faces: [[0, 1, 2], [2, 3, 0]]
})
Insert cell
// A bunny model that fits a 2x2x2 box centered at the origin
bunny = {
let mesh = parseObj(await FileAttachment("bunny.obj").text())

// Compute a modeling matrix that centers and scales the object to
// a unit radius
let {center,size} = bbox(mesh.pos);
let {mat4,vec3} = glmatrix;
let s = 2/Math.max(...size);
let model = mat4.mul([],
mat4.fromScaling([],vec3.fromValues(s,s,s)),
mat4.fromTranslation([],center.map(x=>-x)));
mesh.pos.forEach(v => vec3.transformMat4(v,v,model))
return mesh
}
Insert cell
//
// Utility that returns the bounding box of an array of points
//
bbox = function(pos) {
let vec3 = glmatrix.vec3;
let sum = vec3.fromValues(0,0,0);
let min = vec3.fromValues(...pos[0]);
let max = vec3.fromValues(...pos[0]);
for (let p of pos) {
vec3.add(sum,sum,p);
p.forEach((x,i) => {
min[i] = Math.min(x,min[i]);
max[i] = Math.max(x,max[i]);
})
}
return {center: vec3.scale(sum,sum,1/pos.length),
size: vec3.sub(max,max,min)}
}
Insert cell
trackballControls = function (canvas, refresh) {
//
// Simple trackball controls. Attaches listeners to the canvas
// and calls refresh (mat4) anytime the user drags the mouse.
//
let mouse = null;
let {vec3,mat4} = glmatrix;
canvas.oncontextmenu = (e) => {
e.preventDefault();
e.stopPropagation()
}
canvas.onmousedown = (e) => {
mouse = vec3.fromValues(e.offsetX,e.offsetY,0)
}
canvas.onmouseup = (e) => {
mouse = null
}
canvas.onmousemove = (e) => {
if (mouse) {
let newMouse = vec3.fromValues(e.offsetX,e.offsetY,0);
if (e.buttons & 1) {
let axis = vec3.fromValues(newMouse[1]-mouse[1], newMouse[0]-mouse[0],0);
let angle = vec3.len(axis)*Math.PI/180*0.5; // 1/2 degree
if (angle == 0) return;
vec3.normalize(axis,axis);
let rot = mat4.fromRotation([], angle, axis);
refresh (rot)
}
else {
let s = newMouse[1] > mouse[1] ? 0.98 : (newMouse[1] < mouse[1] ? 1.02 : 1);
let scale = mat4.fromScaling([], vec3.fromValues(s,s,s));
refresh (scale);
}
mouse = newMouse
}
}
}
Insert cell
//
// Returns a function that can be called to render object 'obj' onto a canvas
// having Regl context 'regl'
//
drawMesh = function (regl, obj) {
let draw = regl({
frag: `
precision mediump float;
varying vec3 vtxNormal;
uniform vec3 lightDir;
uniform vec3 lightColor;
uniform vec3 objColor;
uniform float ambient;
uniform float emission;
uniform float diffuse;
uniform float specular;
uniform float shininess;
uniform float alpha;
void main () {
vec3 normal = normalize(vtxNormal);
float diffuseComp = max(0.0,diffuse * dot(normal,lightDir));
vec3 ref = 2.0*dot(lightDir,normal)*normal - lightDir;
float specularComp = specular*pow(max(0.0,dot(ref,vec3(0.0,0.0,1.0))),shininess);
gl_FragColor = vec4(emission*objColor +
(ambient+diffuseComp)*lightColor*objColor +
specularComp*lightColor, alpha);
}`,

vert: `
attribute vec3 position;
attribute vec3 normal;
varying vec3 vtxNormal;
uniform mat4 modelview;
uniform mat4 projection;
void main () {
vec4 worldpos = modelview*vec4(position, 1.0);
gl_Position = projection*worldpos;
vtxNormal = (modelview*vec4(normal,0.0)).xyz;
}`,

// These are the vertex attributes that will be passed
// on to the vertex shader
attributes: {
position: obj.pos,
normal: obj.normal
},
// Enable alpha blending
blend: {
enable: true,
func: {
src: 'src alpha',
dst: 'one minus src alpha'
}
},


// These are the uniforms, i.e., global shader variables that are set
// from inside the host (CPU) program
uniforms: {
objColor: regl.prop('objColor'),
lightColor: regl.prop('lightColor'),
lightDir: regl.prop('lightDir'),
ambient: regl.prop('ambient'),
emission: regl.prop('emission'),
diffuse: regl.prop('diffuse'),
specular: regl.prop('specular'),
shininess : regl.prop ('shininess'),
modelview : regl.prop('modelview'),
projection : regl.prop ('projection'),
alpha: regl.prop('alpha'),
},

// Number of triangles
elements: obj.faces
});
let defaults = {objColor:[1,1,1],
lightColor:[1,1,1],
lightDir:glmatrix.vec3.normalize([],[1,1,10]),
emission:0.0,
ambient:0.3,
diffuse:0.6,
specular: 0.1,
shininess: 60,
alpha: 1,
modelview: glmatrix.mat4.create(),
projection: glmatrix.mat4.perspective ([],
40 * Math.PI/180,
1.333, 0.01, 100)};
return function (uniforms = {}) {
uniforms = {... defaults, ...uniforms}
draw (uniforms)
}
}
Insert cell
Insert cell
import { parseObj } from "@esperanc/phong-illumination-model"
Insert cell
import {radio} from "@jashkenas/inputs"
Insert cell
glmatrix = require('https://bundle.run/gl-matrix@3.3.0')
Insert cell
createRegl = require('regl@1.4.2/dist/regl.js')
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