Contents

This post explores embedding a Rust-compiled WebAssembly module directly into a Jekyll page. The computation — evaluating a damped wave interference pattern on a 100×100 grid — runs entirely in Rust/WASM. Three.js renders the 3D surface; Plotly renders a 2D cross-section. Both update live as you adjust the parameters.

The demo

Two damped radial waves emanate from configurable source positions and interfere:

\[z(x,y) = e^{-d\,r_1}\cos(k\,r_1) + e^{-d\,r_2}\cos(k\,r_2 + \varphi)\]

where $r_1$ is the distance from the origin and $r_2$ is the distance from the second source. Use the GUI panel (top-right) to adjust parameters. Orbit with left-click drag, zoom with scroll, pan with right-click drag.

Left-drag: orbit  ·  Scroll: zoom  ·  Right-drag: pan  ·  Blue = trough  ·  Red = crest  ·  Yellow line = cross-section


How it works

The Rust library exposes three functions via wasm-bindgen:

#[wasm_bindgen]
pub fn compute_surface(
    n: usize, range: f32,
    k: f32, damping: f32, phase: f32,
    src2_x: f32, src2_y: f32,
) -> Vec<f32> { ... }

#[wasm_bindgen]
pub fn compute_cross_section(
    n: usize, range: f32,
    k: f32, damping: f32, phase: f32,
    src2_x: f32, src2_y: f32, y_slice: f32,
) -> Vec<f32> { ... }

#[wasm_bindgen]
pub fn linspace(n: usize, range: f32) -> Vec<f32> { ... }

wasm-bindgen converts Vec<f32> returns to a JavaScript Float32Array — directly usable as a Three.js BufferAttribute without any additional copying or conversion. The N=100 grid means 10,000 evaluations of exp + cos per parameter change, all in compiled native code.

The JS side only handles rendering: it updates vertex positions and colors in the existing BufferGeometry and calls computeVertexNormals(), then passes the cross-section array to Plotly.


Building from source

# Install prerequisites (once)
rustup target add wasm32-unknown-unknown
cargo install wasm-pack

# Build and copy assets
cd rust/
bash build.sh

The build script runs wasm-pack build --target web, which produces an ES module (wave_surface.js) and the WASM binary (wave_surface_bg.wasm), then copies both to assets/wasm/. Jekyll serves them as static files; the post imports them with <script type="module">.

The --target web flag is the key choice for a no-bundler setup: it generates an ES module with a top-level init() that fetches and instantiates the .wasm file by URL, compatible with any static file server.