Profile

Building the New Website (Portfolio landing page) with an Orbital Descent SAR Mesh

Published on 2026-05-27

Introduction

I was dealing with steady traffic (800+ monthly visitors) on the Sellmore site, i needed a way to update my page from a basic static site, to something that showcases my skills.

The initial site could just be ported over and then i just had to add some dynamic scripting with javascript and three.js,

The logic was largely assisted by AI, translating the ArcGIS mesh into a wireframe mesh.

First I needed a way to keep the 3D descent sequence from tanking the client's CPU. Instead of loading massive .obj files that chew through resources, I decided to treat the DOM like a constrained embedded environment.

By procedurally generating a Synthetic Aperture Radar (SAR) wireframe of Brisbane, I could map the browser's scroll input directly to camera altitude kinematics, creating a fluid descent simulation without the overhead.

My The initial thought was to use the ArcGIS mapping feature but that would be too client-heavy

Prerequisites

  • Vanilla JavaScript & Three.js: The core engine.
  • Linear Interpolation: The logic of mapping physical limits (like the physical throw of a flight yoke) to a 0.0 to 1.0 float.
  • Contiguous Memory Allocation: The computer science concept behind Instanced Rendering.

Step 1: Memory Allocation (Geometry and Material)

To avoid massive CPU overhead, I went with THREE.InstancedMesh. In a C++ or RTOS environment, this is conceptually similar to allocating a single block of memory for a geometry, then instructing the GPU to draw that identical memory block hundreds of times using different offset pointers. Here is the initial blueprint allocation:

const buildingGeo = new THREE.BoxGeometry(1, 1, 1);
const cityMaterial = new THREE.MeshBasicMaterial({ color: 0x06b6d4, wireframe: true });

Line-by-Line Breakdown: * const buildingGeo: We declare an immutable variable to hold our shape data in memory. * = new THREE.BoxGeometry(1, 1, 1);: We call the Three.js constructor to allocate a single 1 \times 1 \times 1 unit cube geometry. This is our base footprint. * const cityMaterial: We declare our material variable. * = new THREE.MeshBasicMaterial(...): We allocate the visual properties for the mesh. * { color: 0x06b6d4, wireframe: true }: We pass an object setting the hex colour (cyan) and forcing the engine to render it as an aviation-style SAR wireframe.

Step 2: Instancing and the Calculation Register

Next, we need to set up the instanced memory block and a temporary object to handle our matrix math.

const instancedCity = new THREE.InstancedMesh(buildingGeo, cityMaterial, 600);
const dummy = new THREE.Object3D();

Line-by-Line Breakdown: * const instancedCity = new THREE.InstancedMesh(buildingGeo, cityMaterial, 600);: Instead of creating 600 individual objects, we allocate one contiguous block of memory designed to hold 600 instances of our buildingGeo and cityMaterial. Highly memory efficient. * const dummy = new THREE.Object3D();: We create a single, invisible 3D object. This acts as our temporary calculation register. We use it to compute the spatial math for a single building, then copy that math into our instanced array.

Step 3: Populating the Navigation Grid

Now we loop through our memory block to simulate the layout of the CBD grid.

for (let i = 0; i < 600; i++) {
    dummy.position.set(Math.random() * 100, 5, Math.random() * 100);
    dummy.updateMatrix();
    instancedCity.setMatrixAt(i, dummy.matrix);
}
scene.add(instancedCity);

Line-by-Line Breakdown: * for (let i = 0; i < 600; i++) {: A standard loop iterating 600 times. * dummy.position.set(...): We alter our temporary register object. We scatter its X and Z coordinates randomly across a 100-unit grid, locking the Y (vertical height) at 5. * dummy.updateMatrix();: We instruct the engine to calculate the transformation matrix (the combined position, rotation, and scale data) for this specific coordinate. * instancedCity.setMatrixAt(i, dummy.matrix);: We extract the calculated matrix from our dummy register and write it directly into the i index of our contiguous instancedCity memory block. * } scene.add(instancedCity);: We close the loop and push the massive, single-draw-call block into the active rendering scene.

Step 4: Sensor Polling (The Altimeter)

Finally, we need to tie the user's scroll input to the camera's altitude, simulating a descent glide slope.

window.addEventListener('scroll', () => {
    let progress = window.scrollY / (document.body.scrollHeight - window.innerHeight);
    camera.position.y = 120 - (progress * 110); 
});

Line-by-Line Breakdown: * window.addEventListener('scroll', () => {: We set up an event listener. In an RTOS context, this is like an interrupt service routine polling a pitot-static system or Air Data Computer whenever a state change occurs. * let progress = ...: We establish our glide slope variable. * window.scrollY / (document.body.scrollHeight - window.innerHeight);: We divide the current vertical scroll position by the total available scroll area. This normalises our input into a predictable 0.0 to 1.0 state. * camera.position.y = 120 - (progress * 110);: We map the abstract progress variable to our physical flight property (Altitude). At Top of Descent (where progress is 0), we cruise at 120 units. As we scroll, we multiply our progress by 110 and subtract it, bringing our final altitude down to a hard deck of 10. * });: We close the listener function.

Conclusion

By treating the browser like a constrained hardware environment and applying basic avionics logic to our inputs, I was able to build a complex 3D targeting simulation that runs perfectly smoothly, regardless of traffic volume or client hardware limits.

Comments

← Back to Home