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

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