---
title: "Reference Frames"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{Reference Frames}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>",
  fig.width = 7,
  fig.height = 5
)
```

```{r}
library(orbitr)
```

## The Problem of Scale

Every N-body simulation has to pick an origin — some point that sits at (0, 0, 0). By default, `orbitr` uses the coordinate system you set up: whatever body you placed at the origin stays there (at least initially), and everything else is measured relative to that point. This is your **reference frame**.

The trouble is that the "natural" reference frame for building a simulation isn't always the best one for understanding the results. Consider the Sun-Earth-Moon system. The obvious way to set it up is heliocentric (Sun at the origin):

```{r}
sim <- create_system() |>
  add_sun() |>
  add_body("Earth", mass = mass_earth, x = distance_earth_sun, vy = speed_earth) |>
  add_body("Moon",  mass = mass_moon,
           x = distance_earth_sun + distance_earth_moon,
           vy = speed_earth + speed_moon) |>
  simulate_system(time_step = seconds_per_hour, duration = seconds_per_year)

sim |> plot_orbits()
```

At this scale the Earth-Moon distance (~384,000 km) is a rounding error compared to the Earth-Sun distance (~150 million km). The Moon's trajectory overlaps Earth's completely, and the Sun barely moves (its tiny wobble around the barycenter is invisible at this zoom level), so `plot_orbits()` only shows what looks like a single circular track. The physics is fine; the perspective is wrong.

## Shifting the Reference Frame

`shift_reference_frame()` fixes this by applying a Galilean coordinate transformation. At every time step, it subtracts the position and velocity of a chosen body from all other bodies:

$$
\vec{r}'_i(t) = \vec{r}_i(t) - \vec{r}_{\text{center}}(t) \qquad \vec{v}'_i(t) = \vec{v}_i(t) - \vec{v}_{\text{center}}(t)
$$

The chosen body ends up fixed at the origin, and every other body's trajectory shows its motion *relative to that body*. No physics changes — same forces, same accelerations — you're just moving the camera.

```{r}
sim |>
  shift_reference_frame("Earth") |>
  plot_orbits()
```

Now Earth sits at the center, the Moon traces its familiar near-circular orbit, and the Sun sweeps a wide arc in the background. This is the geocentric view — the same system, seen from a different place.

## How It Works

The function signature is:

```r
shift_reference_frame(sim_data, center_id, keep_center = TRUE)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `sim_data` | `tibble` | — | Output from `simulate_system()` |
| `center_id` | `character` | — | ID of the body to place at (0, 0, 0) |
| `keep_center` | `logical` | `TRUE` | Keep the center body in the output? |

The transformation operates on all six phase-space coordinates (`x`, `y`, `z`, `vx`, `vy`, `vz`) simultaneously. At each time step, the function captures the center body's exact state and subtracts it from every body in the system. The result is a tibble with the same structure as the input — it slots right back into the pipe.

## Keeping vs. Removing the Center Body

By default, `keep_center = TRUE` leaves the center body in the output. Its coordinates will all be zero at every time step, so it appears as a stationary dot at the origin. This is useful when you want to see where the "camera" is.

Set `keep_center = FALSE` to drop the center body entirely. This is the better choice when you're feeding the shifted data into a custom visualization or analysis pipeline and don't need a point stuck at zero:

```{r}
library(ggplot2)

sim |>
  shift_reference_frame("Earth", keep_center = FALSE) |>
  dplyr::filter(id == "Moon") |>
  ggplot(aes(x = x, y = y, color = time / seconds_per_day)) +
  geom_path(linewidth = 1.2) +
  scale_color_viridis_c(name = "Day") +
  coord_equal() +
  labs(title = "Lunar Orbit (Earth-Centered)", x = "X (m)", y = "Y (m)") +
  theme_minimal()
```

## Multiple Perspectives on the Same Data

Since `shift_reference_frame()` doesn't modify the underlying physics — it just translates coordinates — you can call it multiple times on the same simulation to explore different viewpoints. There's no need to re-run `simulate_system()`.

```{r}
# Same simulation, three different perspectives

# 1. From the Sun (original frame, but explicit)
sim |>
  shift_reference_frame("Sun") |>
  plot_orbits()
```

```{r}
# 2. From the Earth
sim |>
  shift_reference_frame("Earth") |>
  plot_orbits()
```

```{r}
# 3. From the Moon
sim |>
  shift_reference_frame("Moon") |>
  plot_orbits()
```

The Moon-centered (selenocentric) view is particularly interesting: from the Moon's perspective, the Earth appears to orbit *it* in a small circle, while the Sun sweeps a much larger arc. This is of course just a matter of perspective — the physics doesn't care which body you call the center.

## Analyzing Relative Velocities

The velocity transformation is just as useful as the position transformation. After shifting to Earth's frame, the velocity columns (`vx`, `vy`, `vz`) give each body's velocity *relative to Earth*. You can use this to study how the Moon's orbital speed varies over time:

```{r}
library(ggplot2)

sim |>
  shift_reference_frame("Earth", keep_center = FALSE) |>
  dplyr::filter(id == "Moon") |>
  dplyr::mutate(speed = sqrt(vx^2 + vy^2 + vz^2)) |>
  ggplot(aes(x = time / seconds_per_day, y = speed)) +
  geom_line(color = "#2563eb", linewidth = 0.8) +
  labs(title = "Moon's Orbital Speed Relative to Earth",
       x = "Time (days)", y = "Speed (m/s)") +
  theme_minimal()
```

The oscillation reflects the Moon's slightly elliptical orbit — it speeds up at perigee (closest approach) and slows down at apogee (farthest point), exactly as Kepler's second law predicts.

## A Binary Star System from the Planet's View

Reference frame shifts aren't limited to the most massive body. You can center on any body in the system. Here's Kepler-16b — a real circumbinary planet — looking back at its two parent stars:

```{r}
AU <- distance_earth_sun

m_A <- 0.68 * mass_sun
m_B <- 0.20 * mass_sun
m_planet <- 0.333 * mass_jupiter

a_bin <- 0.22 * AU
r_A <- a_bin * m_B / (m_A + m_B)
r_B <- a_bin * m_A / (m_A + m_B)
v_A <- sqrt(gravitational_constant * m_B^2 / ((m_A + m_B) * a_bin))
v_B <- sqrt(gravitational_constant * m_A^2 / ((m_A + m_B) * a_bin))

r_planet <- 0.7048 * AU
v_planet <- sqrt(gravitational_constant * (m_A + m_B) / r_planet)

kepler16 <- create_system() |>
  add_body("Star A", mass = m_A, x = r_A, vy = v_A) |>
  add_body("Star B", mass = m_B, x = -r_B, vy = -v_B) |>
  add_body("Kepler-16b", mass = m_planet, x = r_planet, vy = v_planet) |>
  simulate_system(time_step = seconds_per_hour, duration = seconds_per_day * 228.8 * 3)
```

From the default (barycentric) frame, you see the planet's wide orbit and the stars' tight inner dance:

```{r}
kepler16 |> plot_orbits()
```

Now shift to the planet's perspective:

```{r}
kepler16 |>
  shift_reference_frame("Kepler-16b", keep_center = FALSE) |>
  plot_orbits()
```

From Kepler-16b, both stars trace looping spirograph-like patterns — a combination of the binary's mutual orbit and the planet's own revolution around the pair. This is what a double sunset looks like when you map it over time: two stars that dance around each other while slowly circling the sky.

## Practical Tips

**Don't re-simulate.** `shift_reference_frame()` is a pure coordinate transformation on the output tibble. It's fast and doesn't require re-running the physics. Store the simulation result once and shift it as many times as you need.

**Use `keep_center = FALSE` for custom plots.** When piping into `ggplot2` or `plotly`, the center body sitting at (0, 0) with zero velocity can clutter your visualization or skew axis ranges. Dropping it keeps your plots clean.

**Chain with `dplyr`.** The output is a standard tibble, so you can filter, mutate, and summarize after shifting. For instance, compute the distance between two bodies over time, or track how relative velocity evolves:

```{r}
# Distance between Earth and Moon over time
sim |>
  shift_reference_frame("Earth", keep_center = FALSE) |>
  dplyr::filter(id == "Moon") |>
  dplyr::mutate(distance_km = sqrt(x^2 + y^2 + z^2) / 1000) |>
  ggplot(aes(x = time / seconds_per_day, y = distance_km)) +
  geom_line(color = "#dc2626", linewidth = 0.8) +
  labs(title = "Earth-Moon Distance Over One Year",
       x = "Time (days)", y = "Distance (km)") +
  theme_minimal()
```

**The frame doesn't affect the physics.** Shifting the reference frame is a post-processing step. The gravitational forces, accelerations, and numerical integration all happened in the original coordinate system. You're just choosing where to stand when you look at the results.
