Published
Edited
Jul 28, 2021
1 fork
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
f = await FileAttachment("SnowFlake04.svg")
Insert cell
svgImage = {
const text = await f.text();
const document = (new DOMParser).parseFromString(text, "image/svg+xml");
const svg = d3.select(document.documentElement).remove();
const viewBox = svg.node().getAttribute("viewBox").split(" ");
const svgWidth = viewBox[2];
const svgHeight = viewBox[3];
const path = svg.selectAll("path");
const pathNode = path.node();
const d = pathNode.getAttribute('d');
const svgOut = d3.create("svg").attr("width", svgWidth).attr("height", svgHeight);
svgOut.selectAll("path").data(path).enter().append("path")
.attr("d", d=>{
return d.getAttribute('d');
})
return svgOut.node();
}
Insert cell
numElements = 1000
Insert cell
Insert cell
Insert cell
Insert cell
ctx = {
const ctx = DOM.context2d(imageWidth, imageHeight);
ctx.canvas.width = imageWidth;
ctx.canvas.height = imageHeight;
const text = await f.text();
const document = (new DOMParser).parseFromString(text, "image/svg+xml");
const svg = d3.select(document.documentElement).remove();
const path = d3.select(svgImage).selectAll("path");
path.each(d=>{
const pathData = new Path2D(d.getAttribute('d'));
ctx.fill(pathData);
})
//fill SVG path on canvas
const data = ctx.getImageData(0,0,imageWidth,imageHeight).data;
let coords = [];
//get all pixels with non-zero opacity
for (let i=0; i<data.length; i+=4) {
if (data[i+3] > 0) {
let y = Math.floor(i/(imageWidth*4));
let x = (i%(imageWidth*4))/4;
coords.push({
x,
y})
}
}
return coords;
}
Insert cell
Insert cell
Insert cell
particleCanvas = {
const alpha = exampleSDF.draw();
const ctx = DOM.context2d(exampleSDF.size, exampleSDF.size);
ctx.canvas.height = exampleSDF.size;
ctx.canvas.width = exampleSDF.size;
const u = new Uint8ClampedArray(exampleSDF.size*exampleSDF.size*4);
for (let i=0; i<alpha.length; i++) {
u[4*i+3] = alpha[i];
}
const imageData = new ImageData(u,exampleSDF.size,exampleSDF.size);
ctx.putImageData(imageData,0,0)
return ctx.canvas;
}
Insert cell
Insert cell
Insert cell
Grid = {
function Grid() {
this.width = imageWidth;
this.height = imageHeight;
this.values = new Uint8ClampedArray(this.width*this.height*4);
this.spaceWidth = this.width/10;
this.spaceHeight = this.height/10;
}
Grid.prototype.reset = function() {
this.values = new Uint8ClampedArray(this.width*this.height*4);
const num = this.width*this.height;
for (let i=0; i<num; i++) {
this.values[i*4+3] = 255;
}
}
Grid.prototype.add = function(x, y) {
if (x > this.spaceWidth || x < 0 || y > this.spaceHeight || y < 0) {
return;
}
const fracX = Math.floor(x/this.spaceWidth * this.width);
const fracY = Math.floor(y/this.spaceHeight * this.height);
const idx = fracY*this.width + fracX;
this.values[idx*4]=Math.min(255,this.values[idx*4]+255);
this.values[idx*4+1]=Math.min(255,this.values[idx*4+1]+255);
this.values[idx*4+2]=Math.min(255,this.values[idx*4+2]+255);
}
return Grid
}
Insert cell
hitGrid = {
return new Grid();
}
Insert cell
Point = {
function Point() {
//x,y,z positions
this.y = 30;
const randomPoint = ctx[Math.floor(Math.random()*ctx.length)];
this.x = randomPoint.x/10;
this.z = randomPoint.y/10;

//x,y,z velocities
this.vy = Math.random()*-0.15 - 0.05;
this.vx = (Math.random() - 0.5)/150;
this.vz = (Math.random() - 0.5)/150;
}
Point.prototype.propagate = function(delta) {
this.y = this.y + delta*this.vy*100;
this.x = this.x + delta*this.vx*100;
this.z = this.z + delta*this.vz*100;
}
Point.prototype.resetY = function() {
//point has intersected surface
if (this.y < -1) {
hitGrid.add(this.x, this.z);

this.y = 30;
const randomPoint = ctx[Math.floor(Math.random()*ctx.length)];
this.x = randomPoint.x/10;
this.z = randomPoint.y/10;
}
}
return Point;
}
Insert cell
points = {
//store data about points and their locations
let points = [];
//location of particle center
const tData = new Float32Array(numElements*18);
//texture quad coordinates for billboards relative to particle center
const vData = new Float32Array(numElements*18);
for (let i=0; i<numElements; i++) {
const point = new Point();
points.push(point);
//translation of particle
tData[i*18+0] = point.x;
tData[i*18+1] = point.y;
tData[i*18+2] = point.z;
tData[i*18+3] = point.x;
tData[i*18+4] = point.y;
tData[i*18+5] = point.z;
tData[i*18+6] = point.x;
tData[i*18+7] = point.y;
tData[i*18+8] = point.z;
tData[i*18+9] = point.x;
tData[i*18+10] = point.y;
tData[i*18+11] = point.z;
tData[i*18+12] = point.x;
tData[i*18+13] = point.y;
tData[i*18+14] = point.z;
tData[i*18+15] = point.x;
tData[i*18+16] = point.y;
tData[i*18+17] = point.z;
//billboard quad
vData[i*18+0] = -0.5;
vData[i*18+1] = -0.5;
vData[i*18+2] = 0;
vData[i*18+3] = 0.5;
vData[i*18+4] = -0.5;
vData[i*18+5] = 0;
vData[i*18+6] = -0.5;
vData[i*18+7] = 0.5;
vData[i*18+8] = 0;
vData[i*18+9] = -0.5;
vData[i*18+10] = 0.5;
vData[i*18+11] = 0;
vData[i*18+12] = 0.5;
vData[i*18+13] = -0.5;
vData[i*18+14] = 0;
vData[i*18+15] = 0.5;
vData[i*18+16] = 0.5;
vData[i*18+17] = 0;
}
return {
points: points,
tData: tData,
vData: vData
}
}
Insert cell
rendering = {
let frame;
let then;
(function idraw(now) {
now *= 0.001;
let deltaTime;
if (then) {
deltaTime = now-then;
} else {
deltaTime = 0;
}
then = now;
//update billboard center points (6 * 3)
for (let i=0; i<numElements; i++) {
points.points[i].propagate(deltaTime);
points.tData[i*18+1] = points.points[i].y;
points.tData[i*18+4] = points.points[i].y;
points.tData[i*18+7] = points.points[i].y;
points.tData[i*18] = points.points[i].x;
points.tData[i*18+3] = points.points[i].x;
points.tData[i*18+6] = points.points[i].x;
points.tData[i*18+2] = points.points[i].z;
points.tData[i*18+5] = points.points[i].z;
points.tData[i*18+8] = points.points[i].z;
points.tData[i*18+9] = points.points[i].x;
points.tData[i*18+10] = points.points[i].y;
points.tData[i*18+11] = points.points[i].z;
points.tData[i*18+12] = points.points[i].x;
points.tData[i*18+13] = points.points[i].y;
points.tData[i*18+14] = points.points[i].z;
points.tData[i*18+15] = points.points[i].x;
points.tData[i*18+16] = points.points[i].y;
points.tData[i*18+17] = points.points[i].z;
}
// Tell the attribute how to get data out of positionBuffer (ARRAY_BUFFER)
const size = 3; // 3 components per iteration
let type = canvas.gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0; // start at the beginning of the buffer

//define viewport size
canvas.gl.viewport(0, 0, canvas.width, canvas.height);

canvas.gl.clearColor(0.15, 0.15, 0.15, 1);
canvas.gl.clear(canvas.gl.COLOR_BUFFER_BIT | canvas.gl.DEPTH_BUFFER_BIT);

// Compute the matrix
const aspect = canvas.width / canvas.height;
const zNear = 1;
const zFar = 2000;
const matrix = m4.perspective(fieldOfViewRadians, aspect, zNear, zFar);

// Compute a matrix for the camera
let cameraMatrix = twgl.m4.rotateY(twgl.m4.identity(), cameraAngleRadians);
cameraMatrix = twgl.m4.rotateX(cameraMatrix, cameraAngleRadiansX);
cameraMatrix = twgl.m4.translate(cameraMatrix, [0,0,cameraZ]);
// Get the camera's position from the matrix we computed
const cameraPosition = [
cameraMatrix[12],
cameraMatrix[13],
cameraMatrix[14],
];

const up = [0, 1, 0];
// Compute the camera's matrix using look at.
cameraMatrix = twgl.m4.lookAt(cameraPosition, [imageWidth/20,0,imageHeight/20], up);

// Make a view matrix from the camera matrix
const viewMatrix = twgl.m4.inverse(cameraMatrix);
//vectors for billboard shader
const cameraRight = [viewMatrix[0], viewMatrix[4], viewMatrix[8]];
const cameraUp = [viewMatrix[1], viewMatrix[5], viewMatrix[9]];

// Compute a view projection matrix
const viewProjectionMatrix = twgl.m4.multiply(matrix, viewMatrix);

const internalFormat = canvas.gl.RGBA;
const border = 0;
const format = canvas.gl.RGBA;
type = canvas.gl.UNSIGNED_BYTE;
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, tallyTextures.particleTexture);
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, 0, internalFormat,
imageWidth, imageHeight, border,
format, type, hitGrid.values);
//first pass of gaussian smoother rendered to framebuffer
smooth(viewProjectionMatrix);
//disable rendering to framebuffer
canvas.gl.bindFramebuffer(canvas.gl.FRAMEBUFFER, null);
//draw planes, one black slightly below the color
drawPlane(viewProjectionMatrix, tallyTextures.combinedTexture, planeVertices2);
drawPlane(viewProjectionMatrix, tallyTextures.existingTexture, planeVertices);

//draw particle billboards
canvas.gl.useProgram(program);
type = canvas.gl.FLOAT;
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.trans);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, points.tData, canvas.gl.STATIC_DRAW);
canvas.gl.enableVertexAttribArray(locations.trans);
canvas.gl.vertexAttribPointer(locations.trans, 3, type, normalize, stride, offset);
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.square);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, points.vData, canvas.gl.STATIC_DRAW);
canvas.gl.enableVertexAttribArray(locations.square);
canvas.gl.vertexAttribPointer(locations.square, 3, type, normalize, stride, offset);

// Set the matrix.
canvas.gl.uniformMatrix4fv(locations.matrix, false, viewProjectionMatrix);
canvas.gl.uniform3f(locations.right, cameraRight[0], cameraRight[1], cameraRight[2]);
canvas.gl.uniform3f(locations.up, cameraUp[0], cameraUp[1], cameraUp[2]);
canvas.gl.activeTexture(canvas.gl.TEXTURE0);
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, tallyTextures.circleTexture);
canvas.gl.uniform1i(locations.circle, 0);

var primitiveType = canvas.gl.TRIANGLES;
offset = 0;
var count = 6;
canvas.gl.drawArrays(primitiveType, offset, numElements*count);
for (let i=0; i<numElements; i++) {
points.points[i].resetY();
}
//request another frame
frame=requestAnimationFrame(idraw);
})()
invalidation.then(() => cancelAnimationFrame(frame));
}
Insert cell
tallyTextures = {
//all textures the programs will need
const existingTexture = canvas.gl.createTexture();
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, existingTexture);

const level = 0;
let internalFormat = canvas.gl.RGBA;
const width = imageWidth;
const height = imageHeight;
const border = 0;
let format = canvas.gl.RGBA;
let type = canvas.gl.UNSIGNED_BYTE;
const zeros = new Uint8Array(4*imageWidth*imageHeight);
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, level, internalFormat, imageWidth, imageHeight, border,
format, type, zeros);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MIN_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MAG_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_S, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_T, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, null);
const particleTexture = canvas.gl.createTexture();
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, particleTexture);
internalFormat = canvas.gl.RGBA;
format = canvas.gl.RGBA;
type = canvas.gl.UNSIGNED_BYTE;
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, level, internalFormat,
imageWidth, imageHeight, border,
format, type, zeros);
// set the filtering so we don't need mips
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MIN_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MAG_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_S, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_T, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, null);
//black plane
const combinedTexture = canvas.gl.createTexture();
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, combinedTexture);
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, level, internalFormat,
imageWidth, imageHeight, border,
format, type, zeros);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MIN_FILTER, canvas.gl.NEAREST);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MAG_FILTER, canvas.gl.NEAREST);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_S, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_T, canvas.gl.CLAMP_TO_EDGE);
//colors for plane image
const colorbarTexture = canvas.gl.createTexture();
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, colorbarTexture);
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, 0, canvas.gl.RGBA, canvas.gl.RGBA, canvas.gl.UNSIGNED_BYTE, colorBar);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MIN_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MAG_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_S, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_T, canvas.gl.CLAMP_TO_EDGE);
//texture for particle billboards
const circleTexture = canvas.gl.createTexture();
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, circleTexture);
canvas.gl.texImage2D(canvas.gl.TEXTURE_2D, 0, canvas.gl.RGBA, canvas.gl.RGBA, canvas.gl.UNSIGNED_BYTE, particleCanvas);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MIN_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_MAG_FILTER, canvas.gl.LINEAR);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_S, canvas.gl.CLAMP_TO_EDGE);
canvas.gl.texParameteri(canvas.gl.TEXTURE_2D, canvas.gl.TEXTURE_WRAP_T, canvas.gl.CLAMP_TO_EDGE);
return {
existingTexture,
particleTexture,
combinedTexture,
colorbarTexture,
circleTexture
}
}
Insert cell
function smooth(viewProjectionMatrix) {
canvas.gl.useProgram(programSmooth);
//rendering to framebuffer
canvas.gl.bindFramebuffer(canvas.gl.FRAMEBUFFER, frameBuffers.fb1);
// attach the texture as the first color attachment
const attachmentPoint = canvas.gl.COLOR_ATTACHMENT0;
canvas.gl.framebufferTexture2D(
canvas.gl.FRAMEBUFFER, attachmentPoint, canvas.gl.TEXTURE_2D, tallyTextures.existingTexture, 0);
// Tell WebGL how to convert from clip space to pixels
canvas.gl.viewport(0, 0, imageWidth, imageHeight);
let type = canvas.gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0;
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.planePosition);
const vertices = new Float32Array([
-1, -1,
-1, 1,
1, -1,
1, -1,
-1, 1,
1, 1,
]);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, vertices, canvas.gl.STATIC_DRAW);
//rendering over entire clipspace
canvas.gl.enableVertexAttribArray(locations.smoothPlanePosLocation);
canvas.gl.vertexAttribPointer(locations.smoothPlanePosLocation, 2, type, normalize, stride, offset);
//handle texture for plane
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.texCoord);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, new Float32Array([
0, 0,
0, 1,
1, 0,
1, 0,
0,1,
1, 1,
]), canvas.gl.STATIC_DRAW);
//rendering entire texture corresponding to entire cipspace
canvas.gl.enableVertexAttribArray(locations.smoothTexCoordLocation);
canvas.gl.vertexAttribPointer(locations.smoothTexCoordLocation, 2, type, normalize, stride, offset);
//active texture is hitgrid to start
canvas.gl.activeTexture(canvas.gl.TEXTURE3);
const internalFormat = canvas.gl.RGBA;
const border = 0;
const format = canvas.gl.RGBA;
type = canvas.gl.UNSIGNED_BYTE;
//need to attach hitGrid.values to particle texture
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, tallyTextures.particleTexture);
canvas.gl.uniform1i(locations.smoothPlaneUniformLocation, 3);
// set the kernel and its weight
canvas.gl.uniform1fv(locations.smoothKernelUniformLocation, gaussianBlur);
canvas.gl.uniform1f(locations.smoothKernelWeightUniformLocation, weight);
// set the size of the image
canvas.gl.uniform2f(locations.smoothTextureSizeUniformLocation, imageWidth, imageHeight);
var primitiveType = canvas.gl.TRIANGLES;
offset = 0;
var count = 3;
canvas.gl.drawArrays(primitiveType, offset, 2*count);
}
Insert cell
planeVertices = {
const data = new Float32Array([
0, -1, 0,
0, -1, hitGrid.spaceHeight,
hitGrid.spaceWidth, -1, 0,
hitGrid.spaceWidth, -1, 0,
0, -1, hitGrid.spaceHeight,
hitGrid.spaceWidth, -1, hitGrid.spaceHeight,
])
return data;
}
Insert cell
planeVertices2 = {
const data = new Float32Array([
0, -1.01, 0,
0, -1.01, hitGrid.spaceHeight,
hitGrid.spaceWidth, -1.01, 0,
hitGrid.spaceWidth, -1.01, 0,
0, -1.01, hitGrid.spaceHeight,
hitGrid.spaceWidth, -1.01, hitGrid.spaceHeight,
])
return data;
}
Insert cell
function drawPlane(viewProjectionMatrix, texture, vertices) {
canvas.gl.useProgram(programPlane);
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.planePosition);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, vertices, canvas.gl.STATIC_DRAW);
canvas.gl.enableVertexAttribArray(locations.posPlane);
let type = canvas.gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0;
canvas.gl.vertexAttribPointer(locations.posPlane, 3, type, normalize, stride, offset);

// Set the matrix.
canvas.gl.uniformMatrix4fv(locations.matrixPlane, false, viewProjectionMatrix);
//handle texture for plane
canvas.gl.bindBuffer(canvas.gl.ARRAY_BUFFER, buffers.texCoord);
canvas.gl.bufferData(canvas.gl.ARRAY_BUFFER, new Float32Array([
0, 0,
0, 1,
1, 0,
1, 0,
0,1,
1, 1,
]), canvas.gl.STATIC_DRAW);

canvas.gl.enableVertexAttribArray(locations.texCoord);
canvas.gl.vertexAttribPointer(locations.texCoord, 2, type, normalize, stride, offset);
canvas.gl.activeTexture(canvas.gl.TEXTURE3);
const internalFormat = canvas.gl.RGBA;
const border = 0;
const format = canvas.gl.RGBA;
type = canvas.gl.UNSIGNED_BYTE;

canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, texture);
canvas.gl.uniform1i(locations.planeUniformLocation, 3);
canvas.gl.activeTexture(canvas.gl.TEXTURE4);
canvas.gl.bindTexture(canvas.gl.TEXTURE_2D, tallyTextures.colorbarTexture);
canvas.gl.uniform1i(locations.colorbarUniformLocation, 4);
// set the kernel and its weight
canvas.gl.uniform1fv(locations.kernelUniformLocation, gaussianBlur);
canvas.gl.uniform1f(locations.kernelWeightUniformLocation, weight);
// set the size of the image
canvas.gl.uniform2f(locations.textureSizeUniformLocation, imageWidth, imageHeight);
//define viewport size
canvas.gl.viewport(0, 0, canvas.width, canvas.height);
var primitiveType = canvas.gl.TRIANGLES;
offset = 0;
var count = 3;
canvas.gl.drawArrays(primitiveType, offset, 2*count);
}
Insert cell
gaussianBlur= [
0.045, 0.122, 0.045,
0.122, 0.332, 0.122,
0.045, 0.122, 0.045
]
Insert cell
function computeKernelWeight(kernel) {
var weight = kernel.reduce(function(prev, curr) {
return prev + curr;
});
return weight <= 0 ? 1 : weight;
}
Insert cell
weight = computeKernelWeight(gaussianBlur)
Insert cell
locations = {
//need to get attribute locations
const translate = canvas.gl.getAttribLocation(program, "a_trans");
const matrixLocation = canvas.gl.getUniformLocation(program, "u_matrix");
const planeMatrixLocation = canvas.gl.getUniformLocation(programPlane, "u_matrix");
const planePosLocation = canvas.gl.getAttribLocation(programPlane, "a_pos");
const texCoordLocation = canvas.gl.getAttribLocation(programPlane, "a_texcoord");
const planeUniformLocation = canvas.gl.getUniformLocation(programPlane, "u_texture");
const colorbarUniformLocation = canvas.gl.getUniformLocation(programPlane, "u_colorbar");
const textureSizeUniformLocation = canvas.gl.getUniformLocation(programPlane, "u_textureSize");
const kernelUniformLocation = canvas.gl.getUniformLocation(programPlane, "u_kernel");
const kernelWeightUniformLocation = canvas.gl.getUniformLocation(programPlane, "u_kernelWeight");
const smoothPlanePosLocation = canvas.gl.getAttribLocation(programSmooth, "a_pos");
const smoothTexCoordLocation = canvas.gl.getAttribLocation(programSmooth, "a_texcoord");
const smoothPlaneUniformLocation = canvas.gl.getUniformLocation(programSmooth, "u_texture");
const smoothTextureSizeUniformLocation = canvas.gl.getUniformLocation(programSmooth, "u_textureSize");
const smoothKernelUniformLocation = canvas.gl.getUniformLocation(programSmooth, "u_kernel");
const smoothKernelWeightUniformLocation = canvas.gl.getUniformLocation(programSmooth, "u_kernelWeight");
const squareLocation = canvas.gl.getAttribLocation(program, "a_square");
const rightLocation = canvas.gl.getUniformLocation(program, "u_right");
const upLocation = canvas.gl.getUniformLocation(program, "u_up");
const circleUniformLocation = canvas.gl.getUniformLocation(program, "u_texture");
return {
matrix: matrixLocation,
trans: translate,
matrixPlane: planeMatrixLocation,
posPlane: planePosLocation,
texCoord: texCoordLocation,
planeUniformLocation,
colorbarUniformLocation,
textureSizeUniformLocation,
kernelUniformLocation,
kernelWeightUniformLocation,
smoothPlanePosLocation,
smoothTexCoordLocation,
smoothPlaneUniformLocation,
smoothTextureSizeUniformLocation,
smoothKernelUniformLocation,
smoothKernelWeightUniformLocation,
square:squareLocation,
right:rightLocation,
up:upLocation,
circle:circleUniformLocation
}
}
Insert cell
vsSource = `
attribute vec3 a_trans;
attribute vec3 a_square;

uniform mat4 u_matrix;
uniform vec3 u_right;
uniform vec3 u_up;

varying vec2 v_texcoord;

void main() {
vec3 vertexPosition_worldspace = a_trans+ u_right * a_square.x * 0.9+ u_up * a_square.y * 0.9;

gl_Position = u_matrix * vec4(vertexPosition_worldspace, 1.0);

v_texcoord = a_square.xy + vec2(0.5,0.5);
}
`
Insert cell
fsSource = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;

void main() {
vec4 u_color = vec4(0.0,106./255.,1.0,1.0);
vec4 baseColor = u_color;
float dist = texture2D(u_texture, v_texcoord).a;

float oFactor = smoothstep(0.3, 1.0, dist);
baseColor = mix(u_color, vec4(1.0,1.0,1.0,1.0), oFactor);
baseColor.a *= smoothstep(0.0, 0.8, dist);
gl_FragColor = baseColor;

if(gl_FragColor.a < 0.5)
discard;
}
`
Insert cell
vsSourcePlane = `
attribute vec3 a_pos;
attribute vec2 a_texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
gl_Position = u_matrix*vec4(a_pos, 1.0);

v_texcoord = a_texcoord;
}
`
Insert cell
Insert cell
Insert cell
Insert cell
program = initShaderProgram(canvas.gl, vsSource, fsSource)
Insert cell
programPlane = initShaderProgram(canvas.gl, vsSourcePlane, fsSourcePlane)
Insert cell
programSmooth = initShaderProgram(canvas.gl, vsSourceSmooth, fsSourceSmooth);
Insert cell
//compiles program
function initShaderProgram(gl, vsSource, fsSource) {
//compile shaders
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
const success = gl.getProgramParameter(shaderProgram, gl.LINK_STATUS);
if (!success) {
throw new Error(`Couldn't link shader`);
gl.deleteProgram(shaderProgram);
}
return shaderProgram;
}
Insert cell
//compiles shaders
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
if (!success) {
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
throw new Error(`Couldn't compile shader`);
}
return shader;
}
Insert cell
frameBuffers = {
const fb1 = canvas.gl.createFramebuffer();
return {
fb1
}
}
Insert cell
buffers = {
//creating buffers for programs
const transBuffer = canvas.gl.createBuffer();
const planePositionBuffer = canvas.gl.createBuffer();
const texCoordBuffer = canvas.gl.createBuffer();
const square = canvas.gl.createBuffer();
return {
trans:transBuffer,
planePosition: planePositionBuffer,
texCoord: texCoordBuffer,
square:square
}
}
Insert cell
width = Math.min(600, window.innerWidth);
Insert cell
height = width
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