22 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;
};
Mat3 (physics.h)
struct Mat3 {
double m00, m01, m02;
double m10, m11, m12;
double m20, m21, m22;
};
Row-major 3x3 matrix for 3D rotation operations.
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 is fully implemented using rotation matrices. The rotation sequence is z-x-z Euler angles: R_z(Ω) · R_x(i) · R_z(ω). All 3D parameters are applied in orbital_elements_to_cartesian().
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;
// Orbital elements for 3D angle calculation
double inclination;
double longitude_of_ascending_node;
double argument_of_periapsis;
bool has_orbital_elements;
};
3D Support: When has_orbital_elements is true, the tracker transforms 3D positions back to the orbital plane using the stored orbital elements before calculating the angle. This enables accurate period measurement for inclined orbits.
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- Matrix operations for 3D orbital orientation:
mat3_identity()- returns identity matrixmat3_multiply(Mat3 a, Mat3 b)- matrix-matrix multiplicationmat3_multiply_vec3(Mat3 m, Vec3 v)- matrix-vector multiplicationmat3_rotation_x(double angle)- rotation about X axismat3_rotation_z(double angle)- rotation about Z axismat3_rotation_orbital(double omega, double i, double Omega)- combined z-x-z orbital rotation
Implementation:
- Row-major 3x3 matrix format (different from raylib's column-major 4x4)
- Rotation matrices follow standard right-hand rule convention
- Combined orbital rotation: R_z(Ω) · R_x(i) · R_z(ω) for z-x-z Euler angles
Orbital Mechanics (orbital_mechanics.cpp/h)
Keplerian orbital elements to Cartesian coordinate conversion. Supports all orbit types with full 3D orientation using rotation matrices.
Key functions:
orbital_elements_to_cartesian(OrbitalElements elements, double parent_mass, Vec3* out_position, Vec3* out_velocity)- converts Keplerian elements to local position/velocity with 3D orientation
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: applies z-x-z Euler rotations (R_z(Ω) · R_x(i) · R_z(ω)) to position and velocity vectors
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) - 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