20 KiB
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 coordinatescompute_global_coordinates()- computes global positions/velocities from local framescompute_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)
struct Vec3 {
double x, y, z;
};
CelestialBody (simulation.h)
struct CelestialBody {
char name[64];
double mass; // kg
double radius; // meters
int parent_index; // index of gravitational parent (-1 for root body like Sun)
float color[3]; // RGB color for rendering
// Orbital elements from config (Keplerian elements)
OrbitalElements orbit;
// Global frame (from origin)
Vec3 global_position; // meters from origin
Vec3 global_velocity; // m/s
// Local frame (relative to parent)
Vec3 local_position; // meters from parent
Vec3 local_velocity; // m/s relative to parent
double soi_radius; // sphere of influence radius (meters)
};
SimulationState (simulation.h)
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 (orbital_mechanics.h)
struct OrbitalElements {
union {
double semi_major_axis; // for elliptical (e<1) and hyperbolic (e>1)
double semi_latus_rectum; // for parabolic (e≈1)
};
double eccentricity;
double true_anomaly;
double inclination;
double longitude_of_ascending_node;
double argument_of_periapsis;
};
Note: 3D orientation using inclination, longitude_of_ascending_node, and argument_of_periapsis is defined but not yet applied to orbital calculations (deferred implementation).
Spacecraft (spacecraft.h)
struct Spacecraft {
char name[64];
double mass;
int parent_index;
// Orbital elements from config
OrbitalElements orbit;
// Global frame (from origin)
Vec3 global_position;
Vec3 global_velocity;
// Local frame (relative to parent)
Vec3 local_position;
Vec3 local_velocity;
};
Maneuver (maneuver.h)
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)
struct OrbitalMetrics {
double kinetic_energy;
double potential_energy;
double total_energy;
double orbital_radius;
double velocity_magnitude;
double angular_position;
};
OrbitTracker (test_utilities.h)
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 pointersevaluate_acceleration(Vec3 relative_pos, double body_mass, double parent_mass)- computes gravitational acceleration from parent
Orbital Mechanics (orbital_mechanics.cpp/h)
Keplerian orbital elements to Cartesian coordinate conversion. Supports all orbit types with planar orbits (3D orientation deferred).
Key functions:
orbital_elements_to_cartesian(OrbitalElements elements, double parent_mass, Vec3* out_position, Vec3* out_velocity)- converts Keplerian elements to local position/velocity
Implementation:
- Circular orbits (e=0): Position on circle, velocity from vis-viva equation
- Elliptical orbits (0<e<1): Position from polar equation, velocity from vis-viva
- Parabolic orbits (e≈1): Uses
semi_latus_rectum(p), positionr = p/(1+cos(ν)), velocityv = √(2μ/r) - Hyperbolic orbits (e>1): Same as elliptical with negative semi_major_axis
- All elements use SI units (meters, radians)
true_anomaly = 0is at periapsis (closest approach)- 3D orientation (inclination, RAAN, argument of periapsis) defined but not yet applied (deferred)
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 dominanceupdate_soi()- calculates sphere of influence radius using Hill sphereupdate_simulation()- runs one physics step: finds dominant parent, calculates gravity, applies RK4 integrationinitialize_local_coordinates()- converts global to local coordinates on loadcompute_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 stateadd_body_to_simulation()/add_spacecraft()- dynamic addition of bodies and spacecraftupdate_bodies_physics()/update_spacecraft_physics()- separated physics updatesexecute_pending_maneuvers()- processes scheduled burn maneuverscompute_spacecraft_globals()- calculates global coordinates for all spacecraftinitialize_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, CUSTOMTriggerType: 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 from orbital elements and SOI radii.
Key functions:
load_system_config()- main entry point, loads bodies/spacecraft/maneuvers from config fileparse_toml_body()- parses individual body entriesparse_toml_spacecraft()- parses spacecraft entriesparse_toml_maneuver()- parses maneuver entriesload_spacecraft_from_toml()- loads spacecraft array from configload_maneuvers_from_toml()- loads maneuvers array from config
Config format details:
- TOML arrays:
[[bodies]],[[spacecraft]],[[maneuvers]] - Comments start with
# parent_index = -1indicates root body (star)- Supports nested orbits (planets with moons)
- All numeric values accept integers or floats
- Uses orbital elements table (Keplerian elements) instead of state vectors
Config format (TOML) - Bodies:
[[bodies]]
name = "Sun"
mass = 1.989e30
radius = 6.96e8
parent_index = -1
color = { r = 1.0, g = 1.0, b = 0.0 }
orbit = {
semi_major_axis = 0.0,
eccentricity = 0.0,
true_anomaly = 0.0
}
[[bodies]]
name = "Earth"
mass = 5.972e24
radius = 6.371e6
parent_index = 0
color = { r = 0.0, g = 0.5, b = 1.0 }
orbit = {
semi_major_axis = 1.496e11,
eccentricity = 0.0,
true_anomaly = 0.0
}
Config format (TOML) - Spacecraft:
[[spacecraft]]
name = "LEO_Satellite"
mass = 1000.0
parent_index = 1
orbit = {
semi_major_axis = 6.571e6,
eccentricity = 0.0,
true_anomaly = 0.0
}
- Uses
orbittable for orbital elements (same as bodies) - Altitude can be used instead of
semi_major_axisfor non-parabolic orbits (convenience feature, added to parent radius) - Optional 3D orbital elements (inclination, longitude_of_ascending_node, argument_of_periapsis) are defined but not yet applied (deferred)
Config format (TOML) - Maneuvers:
[[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
Config Validator (config_validator.cpp/h)
Validation module that ensures config files define physically realistic systems. Called automatically after loading config and initializing orbital objects.
Key functions:
run_all_config_validations()- master validation function that calls all validatorsvalidate_parent_index_ordering()- ensures parent indices are properly orderedvalidate_orbital_elements()- checks orbital elements for valid valuesvalidate_initial_positions()- ensures bodies aren't inside their parentvalidate_mass_ratios()- checks parent-child mass ratios for hierarchical consistencyvalidate_soi_overlap()- detects bodies with overlapping spheres of influencevalidate_nested_orbits()- ensures moons orbit within stable boundaries
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_indexmust be < body index or -1 (root) - Spacecraft
parent_indexmust reference valid body - Maneuver
spacecraft_namemust reference existing spacecraft - Maneuver names must be unique within config
- Eccentricity must be >= 0
- Parabolic orbits (e≈1) require
semi_latus_rectum(notsemi_major_axis) - Elliptical/hyperbolic orbits require
semi_major_axis(notsemi_latus_rectum) - Elliptical orbits (e<1) require positive
semi_major_axis - Bodies with
eccentricity < 1andsemi_major_axis <= 0are rejected - Parent-child distance must exceed combined radii
- Spacecraft must be at least parent's radius away
- Mass ratio validation: For root children with radius > 50% of parent, mass ratio >= 1000
- SOI overlap validation: Bodies sharing same parent must not have overlapping SOIs
- Nested orbit validation: Moons (small radius, circular orbits) must orbit within 5x parent SOI
Renderer (renderer.cpp/h)
Raylib 3D visualization system. See docs/rendering.md for complete documentation including:
- Camera controls and follow system
- Object rendering (bodies, spacecraft, maneuvers)
- Orbit path rendering (elliptical, parabolic, hyperbolic)
- Coordinate transformation and scaling
UI Renderer (ui_renderer.cpp/h)
Raygui-based UI system for rendering interactive panels. Handles all 2D overlay elements.
Key functions:
render_info()- Bottom-left info panel showing simulation time, FPS, and controlsrender_body_list_ui()- Top-left objects list panel for selecting bodies/spacecraftrender_body_info_ui()- Top-right panel showing selected object detailsrender_maneuver_list_ui()- Maneuver list panel for spacecraft planning
UI State:
body_list_scroll- Scroll position for objects listbody_list_active- Currently selected item index
Implementation:
- Uses raygui header-only library
- All UI rendering happens after 3D scene rendering
- Manages user interaction for object selection and camera following
Test Utilities (test_utilities.cpp/h)
Test helper functions for orbital mechanics validation.
Key functions:
calculate_kinetic_energy()- computes kinetic energy of a bodycalculate_potential_energy_pair()- computes gravitational potential energy between two bodiescalculate_system_total_energy()- sums total energy of entire systemcalculate_orbital_metrics()- returns comprehensive orbital state metricscreate_orbit_tracker()- initializes orbit completion trackingupdate_orbit_tracker()- tracks orbital progress and detects completioncompare_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 toorbit_simmake rebuild- Clean and rebuildmake clean- Remove build artifactsmake clean-all- Clean everything including raylibmake run- Build and run the simulationmake test- Run full automated test suitemake 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
- use
- Test Configs:
tests/configs/contains test scenariossolar_system.toml- Full solar system with moonsearth_circular.toml,mars_circular.toml- Simple orbital testsparabolic_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 validationtest_moon_orbits.cpp- Moon orbital stability teststest_orbital_period.cpp- Orbital period verificationtest_parabolic_orbit.cpp- Parabolic orbit teststest_hyperbolic_orbit.cpp- Hyperbolic orbit teststest_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
- Configuration file →
load_system_config()→ populatesSimulationState:- Loads bodies array from
[[bodies]] - Loads spacecraft array from
[[spacecraft]] - Loads maneuvers array from
[[maneuvers]]
- Loads bodies array from
initialize_orbital_objects()→ combined initialization:- Converts orbital elements to local position/velocity for all bodies
- 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 and spacecraft
- Initializes spacecraft global coordinates
run_all_config_validations()→ validates loaded configuration:- Checks parent_index ordering
- Validates orbital elements
- Checks mass ratios between parent and child bodies
- Detects overlapping SOIs between bodies sharing same parent
- Validates nested orbit boundaries (moons within parent's SOI)
- Ensures initial positions don't collide with parent body
Main Simulation Loop
update_simulation()→ for each body:find_dominant_body()→ determine gravitational parent based on SOIevaluate_acceleration()→ compute gravitational force from parentrk4_step()→ update position/velocity using Runge-Kutta 4th order
render_simulation()→ for each body:scale_position()→ convert to render coordinates using logarithmic scalingscale_radius()→ convert to render size using exponential scalingrender_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