Public
Edited
Oct 26, 2023
16 forks
Importers
257 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tileImg = fetchImage(`https://a.tiles.mapbox.com/v4/mapbox.terrain-rgb/${location}.png?access_token=${mapboxToken}`)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
errors = {
const errors = new Float32Array(gridSize * gridSize);

const numSmallestTriangles = tileSize * tileSize;
const numTriangles = numSmallestTriangles * 2 - 2; // 2 + 4 + 8 + ... 2^k = 2 * 2^k - 2
const lastLevelIndex = numTriangles - numSmallestTriangles;
// iterate over all possible triangles, starting from the smallest level
for (let i = numTriangles - 1; i >= 0; i--) {
// get triangle coordinates from its index in an implicit binary tree
let id = i + 2;
let ax = 0, ay = 0, bx = 0, by = 0, cx = 0, cy = 0;
if (id & 1) {
bx = by = cx = tileSize; // bottom-left triangle
} else {
ax = ay = cy = tileSize; // top-right triangle
}
while ((id >>= 1) > 1) {
const mx = (ax + bx) >> 1;
const my = (ay + by) >> 1;

if (id & 1) { // left half
bx = ax; by = ay;
ax = cx; ay = cy;
} else { // right half
ax = bx; ay = by;
bx = cx; by = cy;
}
cx = mx; cy = my;
}

// calculate error in the middle of the long edge of the triangle
const interpolatedHeight = (terrain[ay * gridSize + ax] + terrain[by * gridSize + bx]) / 2;
const middleIndex = ((ay + by) >> 1) * gridSize + ((ax + bx) >> 1);
const middleError = Math.abs(interpolatedHeight - terrain[middleIndex]);
if (i >= lastLevelIndex) { // smallest triangles
errors[middleIndex] = middleError;

} else { // bigger triangles; accumulate error with children
const leftChildError = errors[((ay + cy) >> 1) * gridSize + ((ax + cx) >> 1)];
const rightChildError = errors[((by + cy) >> 1) * gridSize + ((bx + cx) >> 1)];
errors[middleIndex] = Math.max(errors[middleIndex], middleError, leftChildError, rightChildError);
}
}
return errors;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
updatedGeometry = {
let i = 0;
const indices = geometry.index.array;
function processTriangle(ax, ay, bx, by, cx, cy) {
// middle of the long edge
const mx = (ax + bx) >> 1;
const my = (ay + by) >> 1;

if (Math.abs(ax - cx) + Math.abs(ay - cy) > 1 && errors[my * gridSize + mx] > maxError) {
// triangle doesn't approximate the surface well enough; split it into two
processTriangle(cx, cy, ax, ay, mx, my);
processTriangle(bx, by, cx, cy, mx, my);

} else {
// add a triangle to the final mesh
indices[i++] = ay * gridSize + ax;
indices[i++] = by * gridSize + bx;
indices[i++] = cy * gridSize + cx;
}
}

processTriangle(0, 0, tileSize, tileSize, tileSize, 0);
processTriangle(tileSize, tileSize, 0, 0, 0, tileSize);
geometry.index.needsUpdate = true;
geometry.setDrawRange(0, i);
renderer.render(scene, camera);
return geometry;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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