Public
Edited
Sep 11, 2022
1 fork
15 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
canvas = {
const aspectRatio = 0.8;
const c = DOM.canvas(width * (devicePixelRatio || 1), width * aspectRatio * (devicePixelRatio || 1));
c.style.width = `${width}px`;
c.style.height = `${width * aspectRatio}px`;
c.addEventListener("mousedown", () => { c.classList.add('dragging') });
c.addEventListener("mouseup", () => { c.classList.remove('dragging') });
return c;
}
Insert cell
Insert cell
{
let tick = 0;

const camera = reglCamera(regl, {
element: canvas,
center: [0, -0.05, 0],
theta: Math.PI / 2,
phi: Math.PI / 8,
distance: 1.0,
damping: 0.5,
renderOnDirty: false,
rotationSpeed: 0.5,
zoomSpeed: 0.5,
noScroll: true,
});
// Continuously updates
regl.frame(() => {
camera(function () {
// Clears the canvas first
regl.clear({ color: [0.8, 0.9, 0.96, 1] });

drawBlock({ tick });

// Increments tick
if (!canvas.classList.contains("dragging")) tick++;
});
});

// resets regl on invalidation
invalidation.then(() => {
regl.destroy();
mutable regl = createRegl({canvas, extensions: ['OES_element_index_uint']});
});
}
Insert cell
drawBlock = regl({
frag: `
precision mediump float;
varying vec2 vertex;
uniform sampler2D tex;
void main () {
gl_FragColor = texture2D(tex, vertex);
}`,

vert: `
precision mediump float;
attribute vec2 uv;
attribute vec3 position;
varying vec2 vertex;
uniform float tick;
uniform vec3 axis;
uniform mat4 projection, view;
const float PI = 3.14159;

vec3 quatRotate(vec3 v){
float turn = -0.03 * tick * PI / 180.0;
vec4 q = vec4(axis * sin(turn), cos(turn));
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
}

void main () {
vertex = uv.xy;
gl_Position = projection * view * vec4(quatRotate(position), 1);
}`,

attributes: {
position: blockMesh.vertices,
uv: blockMesh.uvs,
},

uniforms: {
tick: regl.prop("tick"),
axis: [0.0, 1.0, 0.0],
tex: satelliteTex,
},

elements: blockMesh.faces
})
Insert cell
blockMesh = {
const blockWidth = 1;
const blockHeight = 0.03;
const terrainHeight = 0.036;
const rasterWidth = demData.length;

const xy = blockWidth / 2;
const z = blockHeight / 2;

const demRangeScale = d3.scaleLinear().domain(d3.extent(demData.flat())).range([0, terrainHeight]);

// Create the vertices for the top of the mesh
const dataVertices = demData
// calculate the vertices that we have data for
.map((row, i) => row.map((height, j) => ({
vertex: [
-xy + j * blockWidth / rasterWidth,
z + demRangeScale(height),
-xy + i * blockWidth / rasterWidth,
],
uv: [j / rasterWidth, i / rasterWidth]
})));

const vertices = [];
const faces = [];
const uvs = [];
let currentVertexCount = 0;
const vertexIndexes = [];

// compile vertices, faces, and uvs for the top
for (let rowIndex = 0; rowIndex < rasterWidth - 1; rowIndex++) {
for (let colIndex = 0; colIndex < rasterWidth - 1; colIndex++) {
const bottomRight = dataVertices[rowIndex + 1][colIndex + 1];
vertices.push(bottomRight.vertex);
uvs.push(bottomRight.uv);
const bottomRightIndex = currentVertexCount++;
vertexIndexes[`${rowIndex + 1}|${colIndex + 1}`] = bottomRightIndex;
let topLeftIndex = vertexIndexes[`${rowIndex}|${colIndex}`];
if (!topLeftIndex) {
const topLeft = dataVertices[rowIndex][colIndex];
vertices.push(topLeft.vertex);
uvs.push(topLeft.uv);
topLeftIndex = currentVertexCount++;
vertexIndexes[`${rowIndex}|${colIndex}`] = topLeftIndex;
}
let topRightIndex = vertexIndexes[`${rowIndex}|${colIndex + 1}`];
if (!topRightIndex) {
const topRight = dataVertices[rowIndex][colIndex + 1];
vertices.push(topRight.vertex);
uvs.push(topRight.uv);
topRightIndex = currentVertexCount++;
vertexIndexes[`${rowIndex}|${colIndex + 1}`] = topRightIndex;
}

let bottomLeftIndex = vertexIndexes[`${rowIndex + 1}|${colIndex}`];
if (!bottomLeftIndex) {
const bottomLeft = dataVertices[rowIndex + 1][colIndex];
vertices.push(bottomLeft.vertex);
uvs.push(bottomLeft.uv);
bottomLeftIndex = currentVertexCount++;
vertexIndexes[`${rowIndex + 1}|${colIndex}`] = bottomLeftIndex;
}

faces.push([topLeftIndex, topRightIndex, bottomLeftIndex]);
faces.push([bottomLeftIndex, topRightIndex, bottomRightIndex]);
}
}

// Create and add vertices, uvs, and faces for the bottom
[[-xy, -z, -xy], [+xy, -z, -xy], [+xy, -z, +xy], [-xy, -z, +xy]].forEach(v => {
vertices.push(v);
uvs.push([1, 0]);
});
faces.push(
[vertices.length - 1, vertices.length - 2, vertices.length - 3],
[vertices.length - 3, vertices.length - 1, vertices.length - 4]
);

// Create and add the vertices, uvs, and faces for the sides
// Head spinning repetitive code hell
for (let i = 0; i < rasterWidth - 1; i++) {
currentVertexCount = vertices.length;
const s4 = [[0, i], [0, i + 1]];
const h4 = s4.map(d => demData[d[0]][d[1]]);
const s3 = [[rasterWidth - i - 1, 0], [rasterWidth - i - 2, 0]];
const h3 = s3.map(d => demData[d[0]][d[1]]);
const s2 = [[rasterWidth - 1, rasterWidth - i - 1], [rasterWidth - 1, rasterWidth - i - 2]];
const h2 = s2.map(d => demData[d[0]][d[1]]);
const s1 = [[i, rasterWidth - 1], [i + 1, rasterWidth - 1]];
const h1 = s1.map(d => demData[d[0]][d[1]]);

vertices.push(
[-xy + s1[0][1] / rasterWidth * blockWidth, -z, -xy + s1[0][0] / rasterWidth * blockWidth],
[-xy + s1[1][1] / rasterWidth * blockWidth, -z, -xy + s1[1][0] / rasterWidth * blockWidth],
[-xy + s1[0][1] / rasterWidth * blockWidth, z + demRangeScale(h1[0]), -xy + s1[0][0] / rasterWidth * blockWidth],
[-xy + s1[1][1] / rasterWidth * blockWidth, z + demRangeScale(h1[1]), -xy + s1[1][0] / rasterWidth * blockWidth],
[-xy + s2[0][1] / rasterWidth * blockWidth, -z, -xy + s2[0][0] / rasterWidth * blockWidth],
[-xy + s2[1][1] / rasterWidth * blockWidth, -z, -xy + s2[1][0] / rasterWidth * blockWidth],
[-xy + s2[0][1] / rasterWidth * blockWidth, z + demRangeScale(h2[0]), -xy + s2[0][0] / rasterWidth * blockWidth],
[-xy + s2[1][1] / rasterWidth * blockWidth, z + demRangeScale(h2[1]), -xy + s2[1][0] / rasterWidth * blockWidth],
[-xy + s3[0][1] / rasterWidth * blockWidth, -z, -xy + s3[0][0] / rasterWidth * blockWidth],
[-xy + s3[1][1] / rasterWidth * blockWidth, -z, -xy + s3[1][0] / rasterWidth * blockWidth],
[-xy + s3[0][1] / rasterWidth * blockWidth, z + demRangeScale(h3[0]), -xy + s3[0][0] / rasterWidth * blockWidth],
[-xy + s3[1][1] / rasterWidth * blockWidth, z + demRangeScale(h3[1]), -xy + s3[1][0] / rasterWidth * blockWidth],
[-xy + s4[0][1] / rasterWidth * blockWidth, -z, -xy + s4[0][0] / rasterWidth * blockWidth],
[-xy + s4[1][1] / rasterWidth * blockWidth, -z, -xy + s4[1][0] / rasterWidth * blockWidth],
[-xy + s4[0][1] / rasterWidth * blockWidth, z + demRangeScale(h4[0]), -xy + s4[0][0] / rasterWidth * blockWidth],
[-xy + s4[1][1] / rasterWidth * blockWidth, z + demRangeScale(h4[1]), -xy + s4[1][0] / rasterWidth * blockWidth],
);

faces.push(
[currentVertexCount, currentVertexCount + 1, currentVertexCount + 2],
[currentVertexCount + 1, currentVertexCount + 2, currentVertexCount + 3],
[currentVertexCount + 4, currentVertexCount + 5, currentVertexCount + 6],
[currentVertexCount + 5, currentVertexCount + 6, currentVertexCount + 7],
[currentVertexCount + 8, currentVertexCount + 9, currentVertexCount + 10],
[currentVertexCount + 9, currentVertexCount + 10, currentVertexCount + 11],
[currentVertexCount + 12, currentVertexCount + 13, currentVertexCount + 14],
[currentVertexCount + 13, currentVertexCount + 14, currentVertexCount + 15],
);

for (let j = 0; j < 16; j++) uvs.push([1, 0]);
}
return { vertices, faces, uvs };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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