Public
Edited
Oct 26, 2023
16 forks
Importers
256 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

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