Skip to content

Repository files navigation

Universe Simulator

CI License: MIT C++20 OpenGL 3.3 Tests

An interactive Newtonian N-body universe simulator in C++20 and OpenGL 3.3: real astronomical data, four selectable integrators, live conserved-quantity diagnostics, runtime body spawning, and a GPU-deformed "spacetime" grid that is clearly labelled as the visual analogy it is.

Built warning-free on four toolchains -- MinGW g++ 16.2, MSVC 19.44, gcc 13.3 and clang 18.1. CI builds every push on Linux, Windows and macOS, and runs the OpenGL self-test headlessly on Linux through Mesa's software rasteriser, so continuous integration exercises the real GL path rather than only compiling.

The inner solar system orbiting inside its gravitational well

Every orbit here is produced by integrating a = Σ G m / r². Nothing is animated on a path.

The solar system with the control panels open

What it does

  • Simulates gravitational attraction between arbitrary bodies with F = G m1 m2 / r^2, in double precision, in SI units.
  • Ships fourteen scene presets built from real masses and semi-major axes, from a bouncing-ball kinematics lab to a compact object tearing through the inner solar system.
  • Flies around in 3D, or locks the camera to any body and orbits it.
  • Spawns new bodies at runtime, from the UI or the command line, which immediately participate in the pairwise sum.
  • Reports kinetic, potential and total energy, momentum, angular momentum and relative energy drift, live.
  • Shows osculating orbital elements for the selected body: semi-major axis, eccentricity, periapsis, apoapsis, period, inclination.
  • Pauses, single-steps, resets, and accelerates time from 0.01x to 1e9x without ever enlarging the physics timestep.
  • Draws a warped grid that responds to mass, as a visualisation -- see docs/PHYSICS.md.
  • Renders through an HDR pipeline with bloom, ACES tone mapping and a procedural starfield, so stars glow rather than being flat discs.
  • Solves gravity by exact O(N^2) summation or a Barnes-Hut octree, with a 2000-body asteroid belt preset to run the tree against.
  • Demonstrates real celestial mechanics: Trojan asteroids librating at the L4 and L5 Lagrange points, with a control showing an arbitrary angle does not hold.
  • Forecasts where the selected body will go by integrating a copy of the whole system, so the prediction includes perturbation from every other body rather than being a Kepler ellipse.
  • Has an optional post-Newtonian correction that reproduces Mercury's perihelion precession to 42.77 arcsec/century against a predicted 43.

Engineering highlights

The parts that were genuinely hard, and what they cost:

  • Floating-origin rendering. Physics runs in double; Earth's orbital radius is 1.5e11 m and float would quantise its position into 16 km steps. Every position is made camera-relative in double and only then narrowed to float, so nothing downstream ever sees an absolute astronomical coordinate.
  • Scale compression that preserves ordering. The Sun is 109 Earth radii, so any constant exaggeration large enough to make Earth visible draws the Sun wider than Earth's entire orbit. Drawn radius is gain * (r / metresPerUnit) ^ 0.35, with the gain solved per scene from a named reference body. Gravity never reads a drawn radius.
  • Symplectic integration, and a test that proves it. Velocity Verlet by default. The energy test asserts boundedness rather than smallness: peak drift over orbits 1-25 is compared against orbits 26-50, which is the property that actually separates a symplectic method from an accurate one. The inner solar system holds to better than 1e-5 relative energy drift over a simulated decade, and Earth completes exactly one orbit per Julian year.
  • Time acceleration that does not corrupt the physics. Up to 1e9x by taking more fixed steps, never a larger one, with a step budget that degrades into slow motion instead of freezing and reports when it is hit.
  • Honest failure analysis. A compact object thrown through a star moves the total energy by a factor of thousands. Rather than hide it, a test refines the timestep across 600 s, 150 s and 37.5 s and requires the drift to fall monotonically, which distinguishes unresolved-encounter truncation error from a masked singularity.
  • Testability enforced by layering. sim/ has no OpenGL, GLFW or ImGui dependency and is a separate static library, so all 160 unit tests run headless. The picking and scale maths were deliberately moved into sim/ so they could be tested without a GL context.
  • Barnes-Hut octree, measured rather than claimed. --benchmark times it against exact summation and prints speed and accuracy, because a solver that is faster and wrong is not faster. Measured cost per doubling of N: direct x4.0 (quadratic), tree x2.3 (N log N), crossing over near 4000 bodies and reaching 4.8x at 16384 with the force error flat at ~1.2%. The tree also breaks Newton's third law, so a test asserts its momentum drift is worse than direct summation rather than pretending otherwise.
  • Reproduces the classic test of general relativity. An optional 1PN correction advances Mercury's perihelion by a measured 42.77 arcsec/century against the closed-form 43.00. Getting there required noticing that velocity Verlet precesses a Kepler orbit on its own -- about -37 arcsec/century at a 600 s step, the same order as the effect and in the opposite direction. The measurement differences a Newtonian run against a relativistic one to cancel it, and a separate test proves that drift is truncation error by showing it falls exactly 4x per halving of the step (-149.98 / -37.49 / -9.37 arcsec/century at 1200 / 600 / 300 s).
  • Trajectory forecasting that is not a conic section. The predicted path is produced by stepping a copy of the live system with the same integrator, so it accounts for every other body. A test forecasts a quarter of Earth's year, advances the real system by the same amount and requires the endpoints to agree to 1e-6 AU; another predicts Mercury over forty years with and without Jupiter present and requires the answers to differ, which a closed-form ellipse could not do.
  • HDR render pipeline. Multisampled RGBA16F target so a star's core can exceed 1.0 and survive to the bright pass, then bloom, ACES tone mapping and a procedural starfield. At 8 bits the overflow clips to white and the halo disappears entirely.

Download

Windows x64 portable build -- 1 MB, no installer and no runtime redistributable. Unzip and run universe-sim.exe. Needs an OpenGL 3.3 capable GPU.

Or build from source, which takes about a minute; see below.

Demo clips

Rendered with tools/make_demo.sh, which is deterministic: simulated time advances a fixed amount per output frame, so the same command reproduces the same footage.

Clip What it shows
solar-system.mp4 Sun through Neptune, camera sweeping a full circle
three-body.mp4 Three equal masses on a Lagrange triangle, drifting into chaos
spawned-star.mp4 A star inserted at runtime tearing the inner system apart
precession.mp4 Relativistic perihelion advance, exaggerated into a rosette
trojans.mp4 Asteroids librating around Jupiter's L4 and L5 points

Three stars, three overlapping gravitational wells Binary stars sharing a single merged well Two thousand mutually attracting asteroids solved with Barnes-Hut Earth's predicted path for the coming year

Relativistic perihelion precession tracing a rosette Trojan asteroid swarms at Jupiter's L4 and L5 points

Left: relativistic perihelion precession, exaggerated 300000x so the rosette is visible in seconds. At strength 1 this is Mercury's real 43 arcseconds per century, measured by the test suite as 42.77. Right: Trojan swarms librating 60 degrees ahead of and behind Jupiter at L4 and L5, held there by nothing but the stability of the equilibrium.

Left: 2000 asteroids, each attracting every other, solved with the Barnes-Hut octree. Right: a one-year forecast, produced by integrating a copy of the system rather than by drawing an ellipse.

Build

Requires CMake 3.24+, a C++20 compiler and an OpenGL 3.3 capable GPU. GLFW, GLM and Dear ImGui are fetched automatically at configure time; GLAD is vendored.

cmake -S . -B build -G Ninja
cmake --build build

Linux additionally needs the X11 and Wayland development packages. GLFW builds both backends by default, so the Wayland codegen tooling is required even if you only ever run under X11:

sudo apt install -y ninja-build   libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev   libgl1-mesa-dev libwayland-dev wayland-protocols libxkbcommon-dev

Windows builds with either MSVC or MinGW. MSVC requires /Zc:preprocessor, which the build sets automatically: the legacy preprocessor mangles raw string literals when a macro stringizes them, which the test assertions do.

Run the tests:

build/bin/simcore-tests.exe          # or: ctest --test-dir build
ctest --test-dir build            # 160 unit tests + the application self-test
ctest --test-dir build -E selftest  # skip the test that needs a GPU

160 unit tests with no third-party test framework, plus a self-test that drives every UI-reachable state transition against a real GL context (scene loading, spawning, deleting, focusing, the reset buttons, every integrator and stabilisation mode, and emptying the scene entirely) and checks 5000+ invariants afterwards.

Run

build/bin/universe-sim.exe                        # solar system, panels open
build/bin/universe-sim.exe --scene three-body
build/bin/universe-sim.exe --list-scenes
build/bin/universe-sim.exe --help

Controls

Key Action
Hold right mouse Look around
W A S D Move
Space / Ctrl Up / down
Shift / Alt Faster / slower
Scroll Fly speed, or orbit distance
C Toggle orbit / free-flight camera
F Focus the selected body
Left click Select a body
Tab Cycle selection
P Pause / resume
. Single step
[ ] Halve / double time scale
R Reset the scene
N Spawn a body ahead of the camera
G T V B Toggle grid / trails / velocity vectors / Schwarzschild radius
F5 / F12 Reload shaders / screenshot
Esc Release the mouse, then quit

Scenes

Key Scene
bounce Kinematics lab: uniform 9.81 m/s^2, restitution, friction
attract Three masses released from rest
two-body Earth around the Sun
earth-moon Sun, Earth and Moon at Earth-Moon scale
inner Inner solar system
solar-system Sun through Neptune
binary Two solar-mass stars plus a circumbinary planet
three-body Three stars in a chaotic configuration
intruder A 0.8 solar-mass star falling into the inner system
compact A 12 solar-mass compact object with a probe ring
compact-vs-sun A 30 solar-mass object crossing the solar system
belt Sun, Jupiter and 2000 mutually attracting asteroids (Barnes-Hut)
precession Relativistic perihelion advance, exaggerated into a rosette
trojans Jupiter's L4/L5 Trojan swarms librating about the Lagrange points

Inner solar system Three-body chaos Binary stars Three masses falling together

Configuration

Every preset is also a JSON file in configs/. Files there are loaded at startup and replace the compiled-in preset with the same key, so masses, radii, orbital radii and initial velocities can be edited without rebuilding.

build/bin/universe-sim.exe --export-configs configs   # regenerate from code
build/bin/universe-sim.exe --no-configs               # ignore configs/

Numbers are written with 17 significant digits, so a save/load round trip reproduces the same trajectory exactly -- there is a test that asserts bitwise equality of the state after 4000 steps, not just approximate agreement.

Unit system

SI throughout: metres, kilograms, seconds. The UI formats values for reading (6371000 m becomes 6371.000 km, 1.496e11 m becomes 1.0000 AU, masses in solar or Earth masses, times in days and years) but every stored quantity is SI.

Scale management

Three independent scales, which must never be confused:

Name Meaning Read by
physical radius the body's true radius in metres collisions and merging
metresPerUnit metres per world unit position transform only
body size gain/exponent drawn-radius exaggeration model matrix only

Gravity uses mass and position only. No code in sim/ reads a drawn radius.

Body radii are exaggerated by a power law, not a multiplier:

drawnRadius = gain * (trueRadius / metresPerUnit) ^ exponent

A constant multiplier cannot work here. The Sun is 109 Earth radii, so any factor large enough to make the Earth visible draws the Sun wider than the Earth's entire orbit -- which is exactly what the first attempt did. An exponent below 1 compresses that ratio while preserving the ordering. True scale in the Rendering panel turns the exaggeration off, at which point the planets vanish; that is worth looking at once.

Architecture

sim/      pure C++20 + glm. No OpenGL, GLFW or ImGui.
  ^
  | one-way
engine/   window, input, timing, camera, assets, screenshots
render/   shaders, meshes, renderers
ui/       ImGui panels

sim/ is a separate static library and the tests link against only that. If a physics change cannot be tested without opening a window, the layering has been broken. Details in docs/ARCHITECTURE.md.

Physics, honestly

The full statement is in docs/PHYSICS.md. The essentials:

  • The dynamics are Newtonian by default. A post-Newtonian correction is available but off unless you enable it, and it is the two-body Schwarzschild term applied pairwise rather than the full EIH N-body Lagrangian.
  • The warped grid is a visualisation. It is displaced by a softened Newtonian potential and is a strictly one-way read of simulation state. Nothing in the physics reads it back. It is the rubber-sheet analogy, not general relativity, and not a picture of curved spacetime.
  • Compact objects are not black holes. They are Newtonian point masses with a small radius. Their Schwarzschild radius r_s = 2GM/c^2 is displayed as a reference figure; no event horizon, lensing or time dilation is modelled.
  • Softening is a documented modification, not a hidden fudge, and it does not make a close encounter accurate -- only finite. The error there is timestep resolution, which the tests demonstrate by refining the step.

Limitations

The model

  • Orbits start as circles at the semi-major axis; real eccentricities and phases are not reproduced.
  • All presets are coplanar, so the grid can lie in the orbital plane.
  • Bodies are point masses for gravity: no oblateness, tides, rotation or axial tilt.
  • Barnes-Hut trades exactness for speed: it approximates distant groups, so momentum is conserved only to the opening-angle error. Direct summation stays the default everywhere except the asteroid belt, and long runs should use it. Neither solver is GPU-accelerated.
  • Lighting is not inverse-square, for readability across four orders of magnitude of scene scale.
  • The merge model discards the kinetic energy of relative motion and does not conserve spin angular momentum, because rotation is not modelled.

The interface

  • At wide zoom a small body can sit inside a larger body's drawn sphere and become unclickable -- the Moon inside the Earth in the inner-system view, for instance. Select it from the Bodies list or cycle with Tab instead. The self-test reports how many bodies this affects rather than hiding it.
  • The background starfield is visible through the spacetime sheet, including "below" it. That is correct for a transparent visualisation plane rather than a floor, but it reads oddly at very shallow camera angles.
  • Mouse and keyboard interaction is not driven by any automated test. The logic behind every control is covered by the self-test, but the click that reaches it is not.

Verification

Rendering milestones are verified by capturing frames offscreen and inspecting them, not by assuming a draw call worked:

build/bin/universe-sim.exe --scene inner --no-ui --warmup 24000000 \
    --screenshot out.png --frame 4
tools/capture_scenes.sh docs/images     # every preset at once

Runtime spawning is verified through the same code path the UI uses:

build/bin/universe-sim.exe --scene inner --spawn "Sun" --spawn-distance 14 \
    --warmup 20000000 --settle 60000000 --no-ui --screenshot spawned.png

That prints each body's orbital radius before and after. With no spawn the planets move by under 0.3% over the same interval; with a star inserted at runtime, Mercury's distance from the origin grows by 803% and Earth's by 531% -- from nothing but the extra term in the pairwise sum.

Progress and per-milestone results, including the bugs found along the way, are in docs/PROGRESS.md.

Licence

MIT, see LICENSE. Third-party components and their licences are listed in THIRD-PARTY.md; GLFW, GLM and Dear ImGui are fetched at configure time rather than redistributed here.

About

Universe simulator in C++20 and OpenGL: Newtonian N-body gravity with real solar-system data, a spacetime-curvature visualisation, and live energy and momentum diagnostics.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages