-- NORMAL -- ~/projects/pointcloud-viewer.md project 04 / 06

pointcloud-viewer

A LiDAR cloud read by Rust compiled to WebAssembly, then laid on the map by three.js: it all happens in the browser.

Stack
Rust · WASM · React · three.js
Year
2026
Repository
github.com/Popoch39/pointcloud-viewer ↗
A

Anatomy

7 parts · only one counts
Fig. 1 One turn of the head, 628 points
  1. 01.las / .lazL78–87 ↗

    Pick the file

    A .las or .laz goes off to be converted; a .bin already converted opens straight away.

    openFile()

    78const openFile = useCallback(

    79 (file: File) => {

    80 if (/\.(bin|pcbk)$/i.test(file.name)) {

  2. 02Web WorkerL13–37 ↗

    Work in the background

    Converts off to one side so the page never freezes, then hands back the result uncopied.

    self.onmessage

    13let wasmReady: Promise<unknown> | null = null

    15self.onmessage = async (event: MessageEvent<BakeRequest>) => {

    16 const post = (message: BakeWorkerMessage, transfer: Transferable[] = []) =>

  3. 03reader.points()L48–132 ↗

    Convert the points

    Rust rereads each point, re-centring it on the cloud’s corner to stay precise in 32 bits.

    bake_to_pcbk()

    48pub fn bake_to_pcbk(

    49 las_bytes: Vec<u8>,

    50 mut on_progress: impl FnMut(u64, u64),

  4. 04TypedArrayL41–91 ↗

    Read the in-house format

    A 72-byte header, then 18 bytes per point, read in place without decoding a thing.

    parseBakedPointCloud()

    41export function parseBakedPointCloud(buffer: ArrayBuffer): BakedPointCloud {

    42 if (buffer.byteLength < HEADER_SIZE) {

    43 throw new Error('PCBK buffer is smaller than its header')

  5. Put the cloud on the map

    Only the cloud’s corner goes from Lambert-93 to GPS; every point hangs off it.

    computeAnchor()

    22export function computeAnchor(cloud: BakedPointCloud, epsg: number): Anchor {

    23 const { worldOffset, bboxMin, bboxMax } = cloud

    24 const lngLat = toWgs84(epsg, worldOffset.x, worldOffset.y)

  6. 06custom layerL25–121 ↗

    Draw the points

    three.js draws the cloud over the base map, in a single upload to the GPU.

    PointCloudLayer

    25export class PointCloudLayer implements CustomLayerInterface {

    26 readonly id = 'pointcloud'

    27 readonly type = 'custom' as const

  7. Change the colours

    RGB, intensity, class or altitude: a single value changes, the points stay put.

    createPointsMaterial()

    127export function createPointsMaterial(

    128 cloud: BakedPointCloud,

    129 mode: ColorMode,

100,000 pts · 1.80 MB · EPSG:2154
Note view

Aim at the ground: the head turns towards the pointer · click the wheel: colourTouch the ground: the head turns as far as the point · tap the wheel: colour

Fig. 1 — The scanner rereads sample.laz, the repository’s test file: 100,000 points in a spiral around a station in Paris’s 6th arrondissement, in Lambert-93. Each turn of the head reads 628 points. The mirror, that is the Rust bake compiled to WebAssembly in a Web Worker, re-centres every point on the corner marker, the cloud’s minimum, so that it stays precise in f32. The disc then receives the PCBK: a 72-byte header at the hub, then 18 bytes per point, in four sectors, one per block. Once the bake is done, three.js lays the whole cloud at once on the IGN base map, and the wheel changes its colours without touching the geometry. Each drawn point stands for 40, and the instrument is not to the scale of the ground.

Parts list

  1. 01 LAS/LAZ card The chosen file. A .las or .laz goes off to be baked in a Worker; a .bin already baked opens on the spot. Code: src/hooks/use-bake.ts, lines 78–87 ↗
  2. 02 Sealed housing The Web Worker: it reads the file’s bytes, initialises WebAssembly only once, reports progress and hands back the PCBK by transfer, without a copy. Code: src/workers/bake-worker.ts, lines 13–37 ↗
  3. 03 Mirror drum The Rust bake compiled to WebAssembly: it walks through the points of the LAS/LAZ, re-centres them on the minimum of each axis so they stay precise in f32, then writes the 72-byte header and the planar blocks. Code: webassembly/src/lib.rs, lines 48–132 ↗
  4. 04 PCBK disc The in-house format: checks the magic and the version, then lays TypedArray views over the buffer, decoding nothing. Code: src/lib/baked-pointcloud.ts, lines 41–91 ↗
  5. 05 Corner marker The anchor: only the World Offset is reprojected (proj4 to WGS84, then Mercator), and the whole cloud is placed around it. Code: src/lib/map-anchor.ts, lines 22–46 ↗
  6. 06 Point cloud The three.js layer on MapLibre’s WebGL context: positions sent as they are, matrices composed in doubles on every frame. Code: src/lib/pointcloud-layer.ts, lines 25–121 ↗
  7. 07 Filter wheel The colour mode (RGB, intensity, classification, altitude): one uniform changes, the geometry stays. Code: src/lib/pointcloud-shaders.ts, lines 127–156 ↗