11 KiB
Orbital Mechanics Simulation - Conceptual Reference
Overview
2-body orbital mechanics simulator using analytical propagation for precise Keplerian trajectories. Supports elliptical, parabolic, and hyperbolic orbits with dynamic Sphere of Influence (SOI) transitions, impulsive burns, and 3D visualization via Raylib.
Architecture
Modular C-style C++ (structs/functions, no classes). Module dependencies:
Main → Simulation → Orbital Mechanics → Physics
→ Maneuver
→ Rendezvous
→ Config Loader
Renderer and UI Renderer depend on Config Loader for display data. Test Utilities are standalone.
Coordinate Frame System
Local Frame: Position/velocity relative to parent body. Primary system for orbital mechanics calculations. Maintains double-precision accuracy for small orbits (LEO, lunar).
Global Frame: Position/velocity from simulation origin (Sun at 0,0,0). Computed as global = parent.global + local. Used for SOI distance calculations and rendering.
Conversion: Simple vector addition/subtraction. All bodies and spacecraft store both local and global coordinates.
Core Data Structures
Vec3
3D vector (x, y, z). Operations: add, sub, cross, scale, magnitude, distance, normalize, dot.
Mat3
3x3 row-major matrix. Used for orbital plane rotations (z-x-z Euler angles).
OrbitalElements
Keplerian elements with union for parabolic/hyperbolic distinction:
struct OrbitalElements {
union {
double semi_major_axis; // elliptical (e<1), hyperbolic (e>1)
double semi_latus_rectum; // parabolic (e≈1)
};
double eccentricity;
double true_anomaly;
double inclination;
double longitude_of_ascending_node;
double argument_of_periapsis;
};
CelestialBody
Planet/moon with mass, radius, parent_index, color, orbital elements, local/global coordinates, SOI radius.
Spacecraft
Similar to CelestialBody but without radius or SOI. Supports standalone (parent_index = -1) and orbiting spacecraft.
Maneuver
Impulsive burn: name, craft_index, direction, delta_v, trigger_type, trigger_value, scheduled_dt, executed flag, executed_time.
HohmannTransfer
Hohmann transfer calculation result: dv1 (first burn delta-v), dv2 (second burn delta-v), transfer_time (half-period of transfer ellipse), true_anomaly_2 (true anomaly at second burn, typically π).
SimulationState
Top-level container: arrays of bodies/spacecraft/maneuvers (with counts and capacities), time, dt, config_name.
Orbital Propagation Algorithm
Analytical propagation solves Kepler's equations exactly (no integration error, perfect energy conservation).
Common Pattern:
- Compute mean motion: n = √(μ/|a|³)
- Convert true anomaly to appropriate anomaly form
- Advance mean anomaly: M_new = M + n·dt
- Solve Kepler's equation for new anomaly
- Convert back to true anomaly
- Update elements
Elliptical (e < 1): Newton-Raphson on E - e·sin(E) = M. Tolerance: 1e-10, max 50 iterations.
Parabolic (|e-1| < PARABOLIC_TOLERANCE): Barker's equation D + D³/3 = M with closed-form solution using cubic roots. Mean motion: n = √(μ/p³) where p = semi_latus_rectum.
Hyperbolic (e > 1): Newton-Raphson on H - e·sinh(H) = M. Mean motion: n = √(μ/(-a)³) using negative semi-major axis. Initial guess: H = log(2M/e) when e·sinh(M) > M, else H = M.
Orbital Element Reconstruction
Elements are dynamically maintained and reconstructed from Cartesian state when:
- SOI transitions: Parent changes, local coordinates update
- Burns: Velocity changes impulsively
- Velocity drift: |v_local - v_expected| > 1e-6 m/s before propagation
Reconstruction uses cartesian_to_orbital_elements() which handles edge cases (near-circular e < 1e-10, near-parabolic).
Coordinate Transformations
Orbital plane orientation via z-x-z Euler rotation:
R_total = R_z(Ω) · R_x(i) · R_z(ω)
Sequence: argument_of_periapsis (ω) → inclination (i) → longitude_of_ascending_node (Ω).
Forward: orbital_elements_to_cartesian() applies rotation.
Reverse: cartesian_to_orbital_elements() computes angles from angular momentum and eccentricity vectors.
Sphere of Influence Mechanics
SOI Radius: Hill sphere approximation r_soi = a × (m/M)^(2/5).
SOI Transitions:
find_dominant_body()called before each physics update- For non-root bodies: stay if distance < parent.soi_radius, else switch to root
- For root bodies: find closest body where distance < body.soi_radius
- On parent change:
- Compute global position/velocity from old parent
- Update parent_index
- Compute new local coordinates
- Reconstruct orbital elements
SOI in Loop: Checked for all bodies and spacecraft before physics propagation.
Maneuver System
Burn Directions
- BURN_PROGRADE: normalized velocity vector
- BURN_RETROGRADE: opposite velocity
- BURN_NORMAL: normalized (position × velocity)
- BURN_ANTINORMAL: opposite normal
- BURN_RADIAL_IN: -normalized position
- BURN_RADIAL_OUT: normalized position
- BURN_CUSTOM: user-specified vector
Exact Position Execution
True anomaly triggers use analytical mean anomaly delta to compute exact time to target, eliminating per-frame propagation probes:
check_maneuver_trigger()converts current and target true anomaly to mean anomaly, computes delta-M, divides by mean motion to getdt_needed- If
0 < dt_needed <= sim->dt, trigger fires andscheduled_dtis set - In
update_spacecraft_physics(), for each spacecraft: check all pending maneuvers for that craft - If a maneuver fires: propagate by
burn_dt(scheduled_dt), execute burn, propagate remaining (sim->dt - burn_dt) - No separate maneuver execution step — all inline in the spacecraft propagation loop
TRIGGER_TIME: scheduled_dt is always 0 (burn at step boundary, quantization error in [0, DT)).
TRIGGER_TRUE_ANOMALY: Sub-step timing supported via analytical mean anomaly calculation.
Future TODO: Parabolic (Barker's equation) and hyperbolic branches for check_maneuver_trigger().
Hohmann Transfer
calculate_hohmann_transfer() computes optimal two-burn transfer between two circular orbits using the vis-viva equation. Transfer time equals half the period of the transfer ellipse.
Rendezvous Module
Handles orbital rendezvous planning and execution via Hohmann transfers and phasing maneuvers.
Hohmann Transfer Planning:
calculate_required_separation_for_hohmann()- computes ideal angular separation between chaser and target at first burncalculate_wait_time_for_hohmann()- determines wait time before starting transfer (positive = wait, negative = late)calculate_next_hohmann_wait_time()- like above but always returns non-negative by advancing to next phasing opportunitycalculate_relative_orbit_period()- time between consecutive phasing opportunities
Maneuver Creation:
create_hohmann_departure_maneuver()- adds prograde burn to enter transfer orbit (supports true anomaly or immediate trigger)create_hohmann_arrival_maneuver()- adds circularization burn to match target orbit (triggered at target radius)
Verification:
verify_hohmann_transfer_orbit()- checks if current orbit matches expected Hohmann transfer parametersvalidate_hohmann_transfer_parameters()- validates transfer parameters before calculationhohmann_transfer_complete()- checks if transfer time has elapsed and spacecraft is at target radius
Simulation Loop
Initialization:
- create_simulation()
- load_system_config() - parse TOML
- run_all_config_validations()
- initialize_orbital_objects() - convert elements to Cartesian, compute globals, calculate SOI
- Main loop begins
Main Loop Order:
- update_bodies_physics() - SOI checks, drift detection, propagation
- compute_global_coordinates()
- update_spacecraft_physics() - maneuver checking, propagation, burns
- compute_spacecraft_globals()
- time += dt
Spacecraft Physics Per-Frame (update_spacecraft_physics):
- For each spacecraft:
- Validate local velocity against expected Keplerian velocity; if vel_diff > 1e-6, recalculate orbital elements
- Check all pending maneuvers for this craft — if a trigger fires:
a. Propagate by
burn_dt(scheduled sub-step offset) b. Execute the maneuver (apply delta-v) c. Propagate remaining (sim->dt - burn_dt) - If no maneuver: propagate full
sim->dt
Body Physics Per-Frame (update_bodies_physics):
- Check SOI via find_dominant_body()
- Handle transitions (compute global, update parent, compute local, reconstruct elements)
- Check velocity drift (> 1e-6 m/s) and reconstruct if needed
- Propagate via propagate_orbital_elements()
- Update local position/velocity
Orbit Types
Elliptical (e < 1): ε < 0, bound periodic motion. a = -μ/(2ε) > 0.
Parabolic (|e-1| < tolerance): ε ≈ 0, escape trajectory. p = h²/μ.
Hyperbolic (e > 1): ε > 0, unbound. a = -μ/(2ε) < 0.
All satisfy vis-viva: v² = μ(2/r - 1/a).
Configuration
TOML Format: bodies, spacecraft, maneuvers arrays.
Validation Rules:
- Parent index ordering (parents before children)
- Orbital element consistency (e ≥ 0, parabolic p > 0, etc.)
- True anomaly ranges for hyperbolic orbits
- Mass ratios (MIN_MASS_RATIO = 1000.0 for large-radius bodies)
- SOI overlap (siblings must not overlap)
- Nested orbits (grandchild semi-major axis ≤ 5× parent SOI)
- Initial positions (no overlap with parent)
Testing Utilities
OrbitalMetrics: kinetic_energy, potential_energy, total_energy, orbital_radius, velocity_magnitude, angular_position.
OrbitTracker: Tracks orbit completion via quadrant transitions and total rotation. 3D mode uses orbital elements for inclined orbits.
Energy Functions:
- KE = 0.5 × m × v²
- PE = -G × m1 × m2 / r (min distance clamped to 1.0)
- Total = ΣKE + ΣPE_pairs
Visualization
Renderer (Raylib):
- XY simulation plane → XZ render plane (90° rotation around X)
- Scale: 1e-9 (1 unit = 1 billion meters)
- Relative rendering when body selected
- Orbit paths with appropriate segment counts
- Wireframe spheres, child indicators
UI Renderer (raygui):
- Bottom-left: Sim time, body count, FPS, controls, config
- Top-left: Objects list with selection
- Top-right: Selected object details
- Below info: Maneuver list
Build System
Targets: make, make all, make raylib, make run, make test, make test-build, make clean, make rebuild.
Dependencies: g++ (C++14), raylib (git submodule), raygui (header-only), tomlc17, Catch2 (tests only).
Testing:
make test # Run all tests
./orbit_test '[config_name]' # Run specific config
./orbit_test -s '[config]' # With debug output
Use WithinAbs(expected, tolerance) for floating-point comparisons (NOT Approx()).
Hybrid Documentation Strategy
This document provides high-level concepts and architecture. Per-module summary files (src/*.summary.md) contain detailed function documentation including signatures, parameters, formulas, and edge cases. Use both: this for the big picture, summaries for implementation details.