# Orbital Mechanics Simulation - Technical Reference ## Overview 3D orbital mechanics simulation using 2-body gravitational model with sphere of influence (SOI) transitions. Built with C-style C++ and raylib. ## Technical Constraints - C-style C++ only: structs and functions, no classes or templates - RK4 (Runge-Kutta 4th order) integration for physics - Simple rotations (quaternions deferred) ## Coordinate Frame Strategy The simulation uses a hybrid coordinate frame system optimized for numerical precision in nested orbital systems. **Global Frame:** - Origin at root body (index 0, parent_index = -1) - All positions/velocities are in meters relative to this origin - Used for SOI calculations, rendering, and interplanetary trajectories **Local Frame:** - Position/velocity relative to gravitational parent - Used for most bodies in stable nested orbits (moons around planets) - RK4 integration operates in local frame to preserve floating-point precision **Coordinate Frame Selection:** - Bodies using local frame: Children with stable orbital relationships (e.g., moons orbiting planets) - Bodies using global frame: Direct children of root body (planets) and bodies on interplanetary trajectories - Dual coordinate storage maintained for seamless frame transitions during SOI crossings **Benefits:** - Eliminates large offsets in floating-point calculations (moon at 3.8×10⁸ m instead of 1.5×10¹¹ m) - Isolates moon orbits from planetary perturbations - Maintains full floating-point precision for small orbital changes - Improved Earth-Moon orbital stability (20% drift → stable) **Key Functions:** - `initialize_local_coordinates()` - initializes local frame positions/velocities from global coordinates - `compute_global_coordinates()` - computes global positions/velocities from local frames - `compute_spacecraft_globals()` - calculates global coordinates for all spacecraft **Implementation Details:** - Dual coordinate storage: both local and global coordinates maintained - Parent bodies treated as origin in child's reference frame during integration - RK4 integration operates on local coordinates - Global coordinates computed after each physics step for rendering and SOI checks ## Core Data Structures ### Vec3 (physics.h) ```cpp struct Vec3 { double x, y, z; }; ``` ### CelestialBody (simulation.h) ```cpp struct CelestialBody { char name[64]; double mass; // kg double radius; // meters Vec3 local_position; // position relative to parent (meters) Vec3 local_velocity; // velocity relative to parent (m/s) Vec3 position; // global position (meters from origin) Vec3 velocity; // global velocity (m/s) double soi_radius; // sphere of influence radius (meters) int parent_index; // index of gravitational parent (-1 for root) float color[3]; // RGB for rendering double eccentricity; // orbital eccentricity (0 = circular) double semi_major_axis; // meters }; ``` ### SimulationState (simulation.h) ```cpp struct SimulationState { CelestialBody* bodies; int body_count; int max_bodies; Spacecraft* spacecraft; int craft_count; int max_craft; Maneuver* maneuvers; int maneuver_count; int max_maneuvers; double time; // simulation time (seconds) double dt; // time step (seconds) char config_name[256]; }; ``` ### OrbitalElements (simulation.h) ```cpp struct OrbitalElements { double time_days; double semi_major_axis_au; double eccentricity; double specific_energy; double distance_to_sun_au; double distance_to_ref_body_au; double velocity_magnitude; }; ``` ### Spacecraft (spacecraft.h) ```cpp struct Spacecraft { char name[64]; double mass; Vec3 local_position; Vec3 local_velocity; Vec3 position; Vec3 velocity; int parent_index; }; ``` ### Maneuver (maneuver.h) ```cpp enum BurnDirection { BURN_PROGRADE, BURN_RETROGRADE, BURN_NORMAL, BURN_ANTINORMAL, BURN_RADIAL_IN, BURN_RADIAL_OUT, BURN_CUSTOM }; enum TriggerType { TRIGGER_TIME, TRIGGER_TRUE_ANOMALY }; struct Maneuver { char name[64]; int craft_index; BurnDirection direction; double delta_v; TriggerType trigger_type; double trigger_value; bool executed; double executed_time; }; ``` ### OrbitalMetrics (test_utilities.h) ```cpp struct OrbitalMetrics { double kinetic_energy; double potential_energy; double total_energy; double orbital_radius; double velocity_magnitude; double angular_position; }; ``` ### OrbitTracker (test_utilities.h) ```cpp struct OrbitTracker { double initial_angle; double previous_angle; int quadrant_transitions; bool orbit_completed; double time_at_completion; int body_index; double min_time_days; }; ``` ## Module Overview ### Physics (physics.cpp/h) Vector math and gravity calculations. RK4 (Runge-Kutta 4th order) integration with `rk4_step()`. Physics module is independent of simulation structures and accepts direct parameters for improved modularity. **Key functions:** - `rk4_step(Vec3* position, Vec3* velocity, double dt, double body_mass, double parent_mass)` - RK4 integration using position/velocity pointers - `evaluate_acceleration(Vec3 relative_pos, double body_mass, double parent_mass)` - computes gravitational acceleration from parent ### Simulation (simulation.cpp/h) Simulation state management and updates. SOI detection using Hill sphere: `r_soi = a * (m/M)^(2/5)`. **Key functions:** - `find_dominant_body()` - determines which body has gravitational dominance - `update_soi()` - calculates sphere of influence radius using Hill sphere - `update_simulation()` - runs one physics step: finds dominant parent, calculates gravity, applies RK4 integration - `initialize_local_coordinates()` - converts global to local coordinates on load - `compute_global_coordinates()` - converts local to global coordinates after update - Dynamic parent switching when bodies cross SOI boundaries (with hysteresis) **Update Order:** - Root bodies updated first (in their own frame = global) - Global coordinates computed for roots - Child bodies updated in parent's local frame - Global coordinates computed for all children **Additional functions:** - `create_simulation()` / `destroy_simulation()` - memory management for simulation state - `add_body_to_simulation()` / `add_spacecraft()` - dynamic addition of bodies and spacecraft - `update_bodies_physics()` / `update_spacecraft_physics()` - separated physics updates - `execute_pending_maneuvers()` - processes scheduled burn maneuvers - `compute_spacecraft_globals()` - calculates global coordinates for all spacecraft - `initialize_bodies()` - combined initialization for velocities, SOI radii, and local coordinates ### Maneuver (maneuver.cpp/h) Burn execution system for spacecraft orbital maneuvers. Supports multiple burn directions and trigger types. **Enums:** - `BurnDirection`: PROGRADE, RETROGRADE, NORMAL, ANTINORMAL, RADIAL_IN, RADIAL_OUT, CUSTOM - `TriggerType`: TIME, TRUE_ANOMALY **Key functions:** - Direction calculation: `calculate_prograde_dir()`, `calculate_retrograde_dir()`, `calculate_normal_dir()`, `calculate_antinormal_dir()`, `calculate_radial_in_dir()`, `calculate_radial_out_dir()`, `get_burn_direction_vector()` - Burn application: `apply_impulsive_burn()`, `apply_custom_burn()` - Execution: `check_maneuver_trigger()`, `execute_maneuver()` - Utility: `calculate_orbital_velocity()` **Implementation:** - All burn direction vectors calculated in local frame relative to current orbital state - Impulsive burns apply instantaneous velocity changes - Trigger types allow time-based or orbital-position-based burn execution - Maneuvers track execution state and timestamp ### Config Loader (config_loader.cpp/h) TOML-based config parser using tomlc17 library. Auto-calculates circular orbit velocities and SOI radii. **Key functions:** - `load_system_config()` - main entry point, loads bodies/spacecraft/maneuvers from config file - `parse_toml_body()` - parses individual body entries - `parse_toml_spacecraft()` - parses spacecraft entries - `parse_toml_maneuver()` - parses maneuver entries - `load_spacecraft_from_toml()` - loads spacecraft array from config - `load_maneuvers_from_toml()` - loads maneuvers array from config - `initialize_bodies()` - combined initialization: velocities, SOI radii, and local coordinates **Config format details:** - TOML arrays: `[[bodies]]`, `[[spacecraft]]`, `[[maneuvers]]` - Comments start with `#` - `parent_index = -1` indicates root body (star) - Supports nested orbits (planets with moons) - All numeric values accept integers or floats **Config format (TOML) - Bodies:** ```toml [[bodies]] name = "Sun" mass = 1.989e30 radius = 6.96e8 position = { x = 0.0, y = 0.0, z = 0.0 } parent_index = -1 color = { r = 1.0, g = 1.0, b = 0.0 } eccentricity = 0.0 semi_major_axis = 0.0 [[bodies]] name = "Earth" mass = 5.972e24 radius = 6.371e6 position = { x = 1.496e11, y = 0.0, z = 0.0 } parent_index = 0 color = { r = 0.0, g = 0.5, b = 1.0 } eccentricity = 0.0 semi_major_axis = 1.496e11 ``` **Config format (TOML) - Spacecraft:** ```toml [[spacecraft]] name = "LEO_Satellite" mass = 1000.0 position = { x = 0.0, y = 6.771e6, z = 0.0 } velocity = { x = 7660.0, y = 0.0, z = 0.0 } parent_index = 1 ``` - `position` and `velocity` are in local frame relative to parent - `velocity` is optional (defaults to zero if omitted) **Config format (TOML) - Maneuvers:** ```toml [[maneuvers]] name = "orbit_raise" spacecraft_name = "LEO_Satellite" trigger_type = "time" trigger_value = 3600.0 direction = "prograde" delta_v = 500.0 ``` - `trigger_type`: "time" (seconds) or "true_anomaly" (radians) - `direction`: "prograde", "retrograde", "normal", "antinormal", "radial_in", "radial_out" - `delta_v`: velocity change magnitude in m/s **Validation rules:** - Body count must not exceed `max_bodies` - Spacecraft count must not exceed `max_craft` - Maneuver count must not exceed `max_maneuvers` - Body `parent_index` must be < body index or -1 (root) - Parent-child distance must exceed combined radii - Spacecraft `parent_index` must reference valid body - Maneuver `spacecraft_name` must reference existing spacecraft - Maneuver names must be unique within config ### Renderer (renderer.cpp/h) Raylib 3D visualization system. See **[docs/rendering.md](rendering.md)** for complete documentation including: - Camera controls and follow system - Object rendering (bodies, spacecraft, maneuvers) - Orbit path rendering (elliptical, parabolic, hyperbolic) - UI panels (info, body list, maneuver list) - Coordinate transformation and scaling ### Test Utilities (test_utilities.cpp/h) Test helper functions for orbital mechanics validation. **Key functions:** - `calculate_kinetic_energy()` - computes kinetic energy of a body - `calculate_potential_energy_pair()` - computes gravitational potential energy between two bodies - `calculate_system_total_energy()` - sums total energy of entire system - `calculate_orbital_metrics()` - returns comprehensive orbital state metrics - `create_orbit_tracker()` - initializes orbit completion tracking - `update_orbit_tracker()` - tracks orbital progress and detects completion - `compare_double()` / `compare_vec3()` - floating-point comparison with tolerance ### Main Program (main.cpp) GUI-only application with interactive 3D visualization. - Initializes simulation with MAX_BODIES=100, TIME_STEP=60 seconds - Runs 100 physics steps per frame (adjustable with speed multiplier) - Game loop: input handling → camera update → physics update (if not paused) → rendering - Supports speed multiplier (2x/0.5x per keypress, min 0.125x) - Default config: `tests/configs/solar_system.toml` **Controls:** - Arrow keys: Rotate and zoom camera - Space: Pause/Resume - +/-: Speed up/slow down simulation - I: Toggle info display - ESC: Quit ## Build System ### Makefile Targets - `make` - Build raylib (first time) and compile sources to `orbit_sim` - `make rebuild` - Clean and rebuild - `make clean` - Remove build artifacts - `make clean-all` - Clean everything including raylib - `make run` - Build and run the simulation - `make test` - Run full automated test suite - `make test-build` - Build test executable ### Dependencies - g++ (C++14) - raylib (built automatically from `ext/raylib/src`) - tomlc17 (included in `ext/tomlc17/src`) - Catch2 (for testing) - libX11, libGL, libpthread (system libraries) ### Test Infrastructure - **Framework**: Catch2 for unit testing - use `./orbit_test -s '[CONFIG_NAME]'` to show extra [INFO] messages on passing tests if needed - **Test Configs**: `tests/configs/` contains test scenarios - `solar_system.toml` - Full solar system with moons - `earth_circular.toml`, `mars_circular.toml` - Simple orbital tests - `parabolic_comet.toml` - Parabolic orbit (e=1.0) - `hyperbolic_comet.toml` - Hyperbolic orbit (e>1.0) - `soi_transition.toml` - SOI crossing test (3-body system) - **Test Files**: - `test_energy.cpp` - Energy conservation validation - `test_moon_orbits.cpp` - Moon orbital stability tests - `test_orbital_period.cpp` - Orbital period verification - `test_parabolic_orbit.cpp` - Parabolic orbit tests - `test_hyperbolic_orbit.cpp` - Hyperbolic orbit tests - `test_soi_transition.cpp` - SOI transition validation ## Orbit Types ### Elliptical Orbits (0 ≤ e < 1) - Standard planetary and moon orbits - Eccentricity e = 0 (circular) to e < 1 (elliptical) - Total energy is negative (bound to parent) - Velocity follows vis-viva equation: `v² = GM(2/r - 1/a)` ### Parabolic Orbits (e = 1) - Escape trajectories with exactly escape velocity - Total energy is zero (marginally unbound) - Escape velocity: `v² = 2GM/r` - Rendered using true anomaly range: -π*0.95 to π*0.95 - Used for comets on escape trajectories ### Hyperbolic Orbits (e > 1) - Fast escape trajectories exceeding escape velocity - Total energy is positive (unbound) - Asymptotic velocity: `v∞ = √(2GM/|a|)` where a < 0 - Shows open curve with asymptotic behavior - Used for high-speed comets and interstellar objects ## Data Flow ### Initialization Sequence 1. Configuration file → `load_system_config()` → populates `SimulationState`: - Loads bodies array from `[[bodies]]` - Loads spacecraft array from `[[spacecraft]]` - Loads maneuvers array from `[[maneuvers]]` 2. `initialize_bodies()` → combined initialization: - Calculates circular orbit velocities for all bodies (using vis-viva equation) - Computes sphere of influence radius for each body (Hill sphere) - Sets local coordinates (position/velocity) for all bodies - Initializes spacecraft global coordinates ### Main Simulation Loop 1. `update_simulation()` → for each body: - `find_dominant_body()` → determine gravitational parent based on SOI - `evaluate_acceleration()` → compute gravitational force from parent - `rk4_step()` → update position/velocity using Runge-Kutta 4th order 2. `render_simulation()` → for each body: - `scale_position()` → convert to render coordinates using logarithmic scaling - `scale_radius()` → convert to render size using exponential scaling - `render_body()` → draw sphere with color ### SOI Transition Mechanics - Bodies dynamically switch gravitational parents when crossing SOI boundaries - Uses 0.5x distance hysteresis to prevent oscillation between parents - `find_dominant_body()` checks all bodies and selects most dominant influence ## Technical Notes ### Code Style and Architecture - C-style C++: structs and functions only, no classes or templates - All headers use include guards - Memory management uses malloc/free - Layer separation: Physics, Simulation, Configuration, Rendering layers - Physics module is independent of simulation structures (parameter-based signatures) ### Physics Considerations - Timestep: 60 seconds for solar system scale - Circular orbit velocity: `v = sqrt(G * M / r)` - Physics steps per frame: 100 (default) with speed multiplier adjustment - Simulation time per frame: 60s * 100 = 6000 seconds at 1x speed - SOI (Sphere of Influence) uses Hill sphere approximation: `r_soi = a * (m/M)^(2/5)` - SOI transitions use 0.5x distance hysteresis to prevent oscillation - Parabolic orbits use escape velocity: `v² = 2GM/r` - Hyperbolic orbits have positive total energy and asymptotic velocity