DEV Community

pier andrea cavaletto
pier andrea cavaletto

Posted on

BRAIN STORM

TL;DR — I spent the last months building BRAIN STORM, an interactive 3D atlas of the human brain that lets you continuously drill down from the whole cortex to a single water molecule and a Bohr-model atom with quantum orbitals — all in a browser tab, at 60 fps. Real MRI data (MNI152), procedural rendering for everything below the cellular scale, and open source (MIT).

💻 GitHub: https://github.com/PierAndreaCavaletto/Brain-Storm
DEMO: https://x.com/Pierandreadev

Why

Most brain visualisations you find online are either static PNGs from a paper or expensive desktop software you have to install. I wanted something a curious high-schooler and a working neuroscientist could both open in one click — and once inside, actually travel between scales, not just look at a picture.

So the goal became: one continuous camera path from the whole cortex (~150 mm) to a single hydrogen orbital (~1 Å) — that's ~9 orders of magnitude of zoom, seamless.

The scale journey

Every layer is anatomically real, not decorative:

Layer What you see Where it comes from
L1 · Macrocosm Full brain mesh + Yeo-7 network ROI hubs + connectome MNI152 T1w, marching-cubes to .glb
L1 · Dura Mater Falx cerebri, tentorium, superior sagittal sinus, middle meningeal artery, fibroblast field, capillaries, sensory nerves Procedural from anatomical reference
L2 · Arachnoid Trabecular network, CSF flow with cardiac pulsatility, Pacchioni granulations Procedural
L3 · Pia Mater Pial vessels, penetrating capillaries, astrocyte end-feet Procedural
Molecular Collagen triple helix (Gly-X-Y knobs, D-banding), water molecule (104.5° H–O–H), hydrated Na⁺/K⁺ octahedral shell Procedural
Atomic Bohr model with proton/neutron nucleus, K/L/M electron shells cycling through C/N/O/Ca Procedural
Subatomic 1s spherical + 2p dumbbell + 3d donut/cloverleaf orbitals sampled from real hydrogen-like wavefunctions Rejection sampling in JavaScript

The tech stack

  • Next.js 15 — App Router, single page, no SSR for the canvas
  • React Three Fiber + drei — declarative Three.js scene graph
  • @react-three/postprocessing — Bloom + Vignette for the neon aesthetic
  • Zustand — cross-component state (which layer, which drill-down, camera targets)
  • Tailwind — UI chrome
  • troika-three-text (via drei) — SDF 3D labels that always face camera

Everything runs client-side. Total bundle ~2 MB gzipped + ~20 MB of mesh assets.

The bit I'm most proud of: sampling quantum orbitals

At the deepest zoom you can pick "Un elettrone in orbita" and see actual hydrogen-like probability clouds, not stylised planetary orbits. Each orbital shape (1s, 2px, 2py, 2pz, 3d_z², 3d_xy) comes from rejection sampling on the wavefunction's angular part:


typescript
function sample3d_z2(rng: () => number, a0: number): [number, number, number] {
  // Radial: gamma(shape=5, scale=a₀/3) → r⁴ exp(−2r/3a₀)
  const r = -(a0 / 3) * (
    Math.log(Math.max(1e-8, rng())) +
    Math.log(Math.max(1e-8, rng())) +
    Math.log(Math.max(1e-8, rng())) +
    Math.log(Math.max(1e-8, rng())) +
    Math.log(Math.max(1e-8, rng()))
  );
  // Angular: (3cos²θ − 1)² — the donut shape
  for (let tries = 0; tries < 40; tries++) {
    const theta = Math.acos(2 * rng() - 1);
    const phi = 2 * Math.PI * rng();
    const ct = Math.cos(theta);
    const factor = (3 * ct * ct - 1);
    const density = factor * factor / 4;
    if (rng() < density) {
      return [
        r * Math.sin(theta) * Math.cos(phi),
        r * Math.sin(theta) * Math.sin(phi),
        r * ct,
      ];
    }
  }
  return [0, 0, r];
}
I sample ~2700 points per site, colour them per orbital type, and render as THREE.Points with a radial-gradient sprite for the additive glow. What you see isn't "art"; it's the real |ψ|² of a hydrogen electron.

The performance journey (from 4 fps to 60 fps)
The naïve implementation had every layer always mounted. When you clicked "L2 · Aracnoide" the previous layer's 1250+ tube meshes were still being drawn in the depth buffer. FPS floor was ~4.

Three fixes that mattered most:

Conditionally mount — heavy layer components (DuraMicroscopy, ArachnoidView, PiaView) unmount entirely when you're not focused on them. Sounds obvious, was not.
InstancedMesh everywhere — 1560 individual meningothelial-cell meshes became one InstancedMesh. Same for 40 mast cells, 26 000 fibroblasts, 31 000 atoms. Cost went from ~1600 draw calls to ~10.
Split visibility per entity — a single microT opacity meant "hide fibroblasts" also hid capillaries. Splitting into fibT/vesselT/nerveT lets each drill-down mute the crowd without hiding the subject.
Ended up at 40-60 fps on a mid-range GPU with the full scene loaded.

Bug I only found while recording the demo reel
While filming the video I noticed one shell label rendered "undefinede-" in the Bohr view. Traced to a useMemo in SingleAtomView that copied r, label, color from each shell but dropped the electrons field — the JSX below then read s.electrons and got undefined. Two-line fix, but a great reminder that TypeScript's compile-time checks stop at object shape — they can't stop you from forgetting to copy a field. The nasty ones are always the field-drop bugs.

What's next
The project is v0.3-alpha and I'm still shipping. Roadmap:

L3 Pia hero drill-downs (single artery, vein, capillary, astrocyte)
L4 Glia limitans (aquaporin-4, glymphatic clearance)
L5 Cerebral cortex (6 laminae, pyramidal + GABAergic neurons)
L6 Synapse (vesicle exocytosis, neurotransmitter binding)
Try it
If you're a neuroscientist, dev, teacher, or just curious: try it, break it, tell me what's wrong.

💻 GitHub: https://github.com/PierAndreaCavaletto/Brain-Storm
🎥 60-second reel: https://x.com/Pierandreadev
Feedback and scientific corrections are the fuel. Especially interested if you're a neuroanatomy prof and something's wrong — open an issue and I'll fix it.

Built with Next.js, React Three Fiber, MNI152, and too much coffee. MIT licensed. If you enjoyed this article, a ⭐ on the repo helps a lot.
Enter fullscreen mode Exit fullscreen mode

Top comments (0)