I built an interactive 3D three-body problem simulator in the browser

I built an interactive 3D three-body problem simulator in the browser

Explore the famous unsolvable problem in real time

Amrutha Gujjar
Amrutha Gujjar March 14, 2026

Live demo: https://structuredlabs.github.io/threebodyproblem/

<iframe   src="https://structuredlabs.github.io/threebodyproblem/"   width="100%"   height="400"   title="Embedded content" ></iframe>

I've always been fascinated by the three-body problem -- not the novel (though that's great too), but the original mathematical one. Three masses, Newtonian gravity, no closed-form solution. The system is fully deterministic, yet practically unpredictable. I wanted to build something that makes that paradox feel real, so I made an interactive 3D simulator that runs entirely client-side.


Why this is hard (and interesting)

Most people's intuition about gravity comes from two-body systems -- planets orbiting stars, moons orbiting planets. Those are solved. Kepler did it in the 1600s. Add one more body, and everything breaks. Poincare proved in 1890 that no general closed-form solution exists. The system is chaotic in the mathematical sense: trajectories that start arbitrarily close diverge exponentially.

What makes this more than a curiosity is that it's the canonical example of deterministic chaos. Same equations, same initial conditions, same outcome every time -- but change the 8th decimal place of a starting position and within a few orbits you get a completely different result. The simulation lets you see this directly.


The physics

The integrator is RK4 (4th-order Runge-Kutta), not Euler. This matters more than you'd think. Euler integration accumulates energy error linearly -- your orbits will spiral inward or outward over time. RK4 gives you (O(h^4)) local error, which keeps energy drift under 0.01% for well-behaved orbits.

Two things I found necessary in practice:

Adaptive timestepping. When two bodies approach closely, the force gradient becomes steep and a fixed timestep blows up. Before each frame, I compute the minimum pairwise separation. Below (r = 0.1), the timestep gets subdivided proportionally (down to 5% of nominal), with multiple sub-steps per frame. This was the difference between "simulation occasionally explodes" and "simulation runs indefinitely."

Gravitational softening. The denominator uses (r^3 + \epsilon^3) instead of (r^3). Without this, two bodies passing near-zero separation produce effectively infinite acceleration and everything flies apart in one frame. (\epsilon = 10^{-6}) is small enough to not visibly distort orbits.

The simulation tracks total energy (kinetic + potential) and displays the drift percentage as a live diagnostic. It's a useful sanity check -- if drift is climbing, something is wrong with your integrator or your timestep is too coarse.

The engine is general N-body ((O(N^2)) pairwise), so it handles 3, 4, or more bodies. No Barnes-Hut or FMM needed at this scale.


The rendering

Three.js with a few layers that make it look better than "colored spheres on a black background":

  • Each body has three concentric spheres (solid core, inner glow, outer halo) plus a point light, so they cast colored illumination on each other during close passes
  • Emissive intensity scales with velocity -- faster bodies glow brighter, which turns out to be a surprisingly intuitive visual cue
  • Orbital trails are 800-point line geometries with custom GLSL shaders for per-vertex alpha fade. Additive blending makes overlapping trails luminous rather than muddy
  • An UnrealBloomPass over the whole scene gives everything a soft radiative look
  • 2,500-point procedural starfield on a spherical shell for depth

Body radius scales as (m^{1/3}), which is the physically correct relationship for constant density.


Presets

Four starting configurations, each chosen to demonstrate something different:

  • Figure-8 -- the Chenciner-Montgomery periodic orbit (2000). Three equal masses chasing each other along a figure-eight. Requires precise initial conditions; perturbation kills it, which is the fun part.
  • Helix -- three bodies with z-offsets producing a spiraling 3D orbit. Shows that the simulation isn't just 2D projected into 3D.
  • 4-body -- four masses at cardinal points with tangential velocities. Much more chaotic than three.
  • Random 3D -- randomized positions/velocities, center-of-mass corrected to zero. Every click is different. Some configurations are surprisingly long-lived.

What I learned building it

Speed control should scale step count, not timestep. My first approach was to multiply dt by the speed factor. This destroys accuracy at high speeds. Instead, the speed slider scales the number of RK4 steps per frame. 3x speed = 15 steps instead of 5, same dt. More expensive but correct.

Additive blending solves a lot of visual problems. Trails, glows, starfield -- everything looks better with additive blending and depthWrite: false. You lose proper occlusion, but for a space simulation that's actually what you want. Light should add, not occlude.

The UI should get out of the way. The controls are translucent overlays with pointer-events: none on the container divs. The 3D canvas receives all mouse events for orbiting/zooming unless you specifically click a button. This took a few iterations to get right.


Stack

Intentionally minimal:

  • Three.js (v0.183) for rendering
  • esbuild for bundling ES modules into a single IIFE
  • Static HTML, no framework
  • Deployed on GitHub Pages with a .nojekyll file

Total: one HTML file, one JS file, one bundler. The bundled output is ~560KB (mostly Three.js).


Try it

Demo: https://structuredlabs.github.io/threebodyproblem/

Orbit with mouse, scroll to zoom. Try the Figure-8 preset, then drag one mass slider slightly. Watch how quickly the periodic orbit dissolves into chaos. That's the three-body problem in a nutshell.

You can export any configuration as JSON and import it later, so if you find an interesting orbit, save it.


Things I might add

  • Click-to-spawn new bodies (the engine supports it, just need raycasting)
  • Symplectic integrator option (Leapfrog/Yoshida) for exact energy conservation
  • Poincare sections for visualizing the phase space
  • WebGPU compute for scaling to larger N

Sign Up