A tidy physics engine for building and visualizing orbital simulations.
Note:
orbitris a work in progress. The physics engine is functional, but the built-in plotting functions (plot_orbits()andplot_orbits_3d()) are intentionally minimal — they exist to get you a quick look at your simulation, not to produce publication-quality figures. Sincesimulate_system()returns a standard tidy tibble, you have the full power ofggplot2,plotly, and any other visualization library at your disposal. See Custom Visualization for examples.
orbitr is a lightweight N-body gravitational physics engine built for the R ecosystem. Simulate planetary orbits, binary star systems, or chaotic three-body problems in a few lines of pipe-friendly code. Under the hood it ships a compiled C++ acceleration engine via Rcpp and falls back gracefully to a fully vectorized pure-R implementation.
library(orbitr)
sim <- create_system() |>
add_body("Sun", mass = mass_sun) |>
add_body("Earth", mass = mass_earth, x = distance_earth_sun, vy = speed_earth) |>
simulate_system(time_step = 86400, duration = 86400 * 365)
sim## # A tibble: 1,462 × 9
## id mass x y z vx vy vz time
## <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 Sun 1.99e30 0 0 0 0 0 0 0
## 2 Earth 5.97e24 149600000000 0 0 0 29780 0 0
## ...
mass_sun, mass_earth, distance_earth_sun, and speed_earth are built-in constants — real-world values in SI units (kg, meters, m/s) so you don’t have to look anything up. orbitr ships constants for the Sun, all eight planets, and the Moon. See Physical Constants for the full list.
simulate_system() returns a tidy tibble — one row per body per time step — ready for dplyr, ggplot2, plotly, or any other tool in the R ecosystem.
sim |> plot_orbits()
You’ll notice only Earth’s orbit is visible — the Sun is missing. That’s a limitation of plot_orbits(): it draws trajectories using geom_path(), and the Sun barely moves during the simulation so its path is too small to see at this scale. The Sun does move — Newton’s third law means Earth pulls on the Sun just as the Sun pulls on Earth, causing it to trace a tiny loop around the system’s barycenter. It’s just invisible at this zoom level because the Sun is ~330,000 times more massive than the Earth. This stellar wobble is real, though — it’s exactly the method astronomers use to detect exoplanets.
By default, plot_orbits() returns a standard ggplot object for planar (2D) simulations, and a plotly HTML widget for simulations with any 3D motion. (You can also force 3D rendering on planar data with three_d = TRUE.) Because the 2D case returns a regular ggplot, you can layer additional geoms, scales, themes, and labels onto it with + like any other ggplot. One quick fix for the missing Sun is to drop a marker at the origin:
sim |>
plot_orbits() +
ggplot2::geom_point(
data = data.frame(x = 0, y = 0),
ggplot2::aes(x = x, y = y),
color = "gold",
size = 6
) +
ggplot2::labs(title = "Earth-Sun Orbit")
This works because the Sun sits essentially at the origin throughout the simulation — the barycenter wobble is well inside the Sun itself. For systems where the central body actually moves a noticeable amount, you’d want to pull its position from the simulation tibble instead of hardcoding (0, 0).
Watching the Orbit in Motion
Static plots are nice, but you can also play the simulation forward as an animation with animate_system(). By default each body leaves a fading wake of recent positions behind it:
animate_system(sim, fps = 20, duration = 6)
animate_system() is the animated counterpart to plot_system() — it samples the simulation tibble down to roughly fps * duration evenly spaced frames and renders them as a GIF using gganimate. Like the static plotters, it auto-dispatches to a 3D version (animate_system_3d(), an interactive plotly widget with a play button) the moment any body has non-zero Z motion. The 2D path requires the gganimate and gifski packages — install them with install.packages(c("gganimate", "gifski")).
For better 2D plots where you control point markers, axis ranges, and labels, use ggplot2 directly on the simulation tibble (see Custom Visualization). For interactive 3D views where you can zoom in and find the Sun, see 3D Plotting.
Installation
# install.packages("devtools")
devtools::install_github("DRosenman/orbitr")For 3D interactive plotting, you’ll also want:
install.packages("plotly")Quick Start
The workflow is simple: create a system, add bodies, simulate, and plot.
create_system() |>
add_body("Earth", mass = mass_earth) |>
add_body("Moon", mass = mass_moon, x = distance_earth_moon, vy = speed_moon) |>
simulate_system(time_step = 3600, duration = 86400 * 28) |>
plot_orbits()-
create_system()initializes an empty simulation with standard gravitational constant G. -
add_body()places a body with a given mass, position, and velocity. Built-in constants likemass_earthanddistance_earth_moonsave you from looking anything up. -
simulate_system()runs the N-body integration. The default Velocity Verlet integrator conserves energy for stable long-term orbits. -
plot_orbits()produces a quick 2D trajectory plot — or an interactive 3D plotly visualization if any body has Z-axis motion. For more control, useggplot2orplotlydirectly on the simulation tibble.
The output is a standard tidy tibble, so you can plug it straight into ggplot2, plotly, dplyr, or whatever you normally use.
Learn More
- Quick Start Guide — Full getting-started walkthrough
- Examples — Earth-Moon, Sun-Earth-Moon, Kepler-16, and more
- Physical Constants — All built-in masses, distances, and speeds
- 3D Plotting — Interactive 3D visualization with plotly
- Custom Visualization — Build your own plots with ggplot2 and plotly
- The Physics — Gravitational equations, integrators, and the C++ engine
-
Reference Frames — Shift your perspective with
shift_reference_frame() - Unstable Orbits — Why most random configurations are chaotic