From e8a56c93c1093ee302ef5de65e56c4465347f1b4 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Fri, 2 Jan 2026 10:50:52 -0500 Subject: [PATCH] Initial commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Orbital mechanics simulation with 2-body physics and SOI transitions. Core Features: - 2-body gravitational physics with sphere of influence transitions - Real-time 3D visualization using raylib - Configurable star systems via text files - Interactive controls (camera, pause, speed) Technical Implementation: - C-style C++ (structs and functions, no classes) - Modular architecture (physics, bodies, config loader, renderer) - Euler integration for orbital mechanics - SOI detection using Hill sphere approximation Configuration System: - Solar system with realistic data (Sun, 8 planets, 5 major moons) - Binary star system example - Easy to create custom systems via simple text format 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .gitignore | 14 ++ .gitmodules | 3 + Makefile | 59 ++++++++ README.md | 144 +++++++++++++++++++ configs/example_binary_star.txt | 20 +++ configs/solar_system.txt | 31 +++++ docs/implementation_plan.md | 235 ++++++++++++++++++++++++++++++++ ext/raylib | 1 + src/bodies.cpp | 133 ++++++++++++++++++ src/bodies.h | 38 ++++++ src/config_loader.cpp | 138 +++++++++++++++++++ src/config_loader.h | 19 +++ src/main.cpp | 90 ++++++++++++ src/physics.cpp | 71 ++++++++++ src/physics.h | 28 ++++ src/renderer.cpp | 159 +++++++++++++++++++++ src/renderer.h | 32 +++++ 17 files changed, 1215 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 Makefile create mode 100644 README.md create mode 100644 configs/example_binary_star.txt create mode 100644 configs/solar_system.txt create mode 100644 docs/implementation_plan.md create mode 160000 ext/raylib create mode 100644 src/bodies.cpp create mode 100644 src/bodies.h create mode 100644 src/config_loader.cpp create mode 100644 src/config_loader.h create mode 100644 src/main.cpp create mode 100644 src/physics.cpp create mode 100644 src/physics.h create mode 100644 src/renderer.cpp create mode 100644 src/renderer.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00f4914 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build artifacts +build/ +orbit_sim + +# Editor/IDE files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..8e69575 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/raylib"] + path = ext/raylib + url = https://github.com/raysan5/raylib.git diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d2e75b3 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +# Compiler and flags +CXX = g++ +CXXFLAGS = -Wall -Wextra -std=c++11 -I./src -I./ext/raylib/src +LDFLAGS = -L./ext/raylib/src -lraylib -lm -lpthread -ldl -lrt -lX11 + +# Directories +SRC_DIR = src +BUILD_DIR = build +RAYLIB_DIR = ext/raylib/src +TARGET = orbit_sim + +# Source files +SOURCES = $(SRC_DIR)/main.cpp \ + $(SRC_DIR)/physics.cpp \ + $(SRC_DIR)/bodies.cpp \ + $(SRC_DIR)/config_loader.cpp \ + $(SRC_DIR)/renderer.cpp + +# Object files +OBJECTS = $(SOURCES:$(SRC_DIR)/%.cpp=$(BUILD_DIR)/%.o) + +# Default target +all: raylib $(BUILD_DIR) $(TARGET) + +# Build raylib +raylib: + @if [ ! -f $(RAYLIB_DIR)/libraylib.a ]; then \ + echo "Building raylib..."; \ + cd $(RAYLIB_DIR) && $(MAKE) PLATFORM=PLATFORM_DESKTOP; \ + fi + +# Create build directory +$(BUILD_DIR): + mkdir -p $(BUILD_DIR) + +# Link the executable +$(TARGET): $(OBJECTS) raylib + $(CXX) $(OBJECTS) -o $(TARGET) $(LDFLAGS) + +# Compile source files +$(BUILD_DIR)/%.o: $(SRC_DIR)/%.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Clean build files +clean: + rm -rf $(BUILD_DIR) $(TARGET) + +# Clean everything including raylib +clean-all: clean + cd $(RAYLIB_DIR) && $(MAKE) clean + +# Rebuild +rebuild: clean all + +# Run the simulation +run: $(TARGET) + ./$(TARGET) + +.PHONY: all clean clean-all rebuild run raylib diff --git a/README.md b/README.md new file mode 100644 index 0000000..b94358c --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# Orbital Mechanics Simulation + +A 3D orbital mechanics simulation using a 2-body gravitational model with sphere of influence (SOI) transitions. Features real-time visualization of celestial bodies using raylib. + +## Features + +- **2-body gravitational physics** with Euler integration +- **Sphere of influence (SOI)** transitions between gravitational parents +- **3D real-time visualization** using raylib +- **Configurable star systems** via simple text files +- **Interactive camera controls** (rotate, zoom) +- **Simulation controls** (pause, resume, speed adjustment) +- Solar system and binary star example configurations + +## Dependencies (Debian 13) + +Install the required packages: + +```bash +sudo apt-get update +sudo apt-get install -y \ + build-essential \ + g++ \ + make \ + libraylib-dev \ + libx11-dev \ + libxcursor-dev \ + libxrandr-dev \ + libxinerama-dev \ + libxi-dev \ + libgl1-mesa-dev \ + libglu1-mesa-dev +``` + +If `libraylib-dev` is not available in the repositories, you can build raylib from source: + +```bash +# Install raylib build dependencies +sudo apt-get install -y cmake git + +# Clone and build raylib +git clone https://github.com/raysan5/raylib.git +cd raylib +mkdir build && cd build +cmake .. -DBUILD_SHARED_LIBS=ON +make +sudo make install +sudo ldconfig +cd ../.. +``` + +## Building + +```bash +make +``` + +This will create the `orbit_sim` executable in the project directory. + +## Running + +Run with the default solar system configuration: + +```bash +./orbit_sim +``` + +Run with a custom configuration file: + +```bash +./orbit_sim configs/example_binary_star.txt +``` + +## Controls + +- **Arrow Keys**: Rotate and zoom camera +- **Space**: Pause/Resume simulation +- **+/-**: Speed up/slow down simulation +- **I**: Toggle info display +- **ESC**: Quit + +## Configuration File Format + +Configuration files define celestial bodies in a simple text format: + +``` +# Comments start with # +# Format: name mass(kg) radius(m) x(m) y(m) z(m) parent_index r g b + +Sun 1.989e30 6.96e8 0 0 0 -1 1.0 1.0 0.0 +Earth 5.972e24 6.371e6 1.496e11 0 0 0 0.0 0.5 1.0 +Moon 7.342e22 1.737e6 1.500e11 0 0 1 0.7 0.7 0.7 +``` + +Fields: +- **name**: Body name (string, no spaces) +- **mass**: Mass in kilograms +- **radius**: Radius in meters +- **x, y, z**: Initial position in meters +- **parent_index**: Index of gravitational parent (-1 for root bodies like stars) +- **r, g, b**: RGB color values (0.0 to 1.0) + +Velocities are calculated automatically for circular orbits. + +## Project Structure + +``` +claudes_game/ +├── src/ +│ ├── main.cpp - Main program loop +│ ├── physics.cpp/h - Vector math and physics +│ ├── bodies.cpp/h - Celestial bodies and simulation +│ ├── config_loader.cpp/h - Configuration file parser +│ └── renderer.cpp/h - 3D rendering with raylib +├── configs/ +│ ├── solar_system.txt - Solar system configuration +│ └── example_binary_star.txt - Binary star example +├── docs/ +│ └── implementation_plan.md - Detailed implementation plan +├── Makefile +└── README.md +``` + +## Technical Details + +- **Physics**: 2-body gravitational model using Newton's law of gravitation +- **Integration**: Euler method with configurable time step (default: 60 seconds) +- **SOI Detection**: Hill sphere approximation for sphere of influence +- **Rendering**: Logarithmic distance scaling and exponential size scaling for visualization +- **Language**: C-style C++ (structs and functions, no classes or templates) + +## Future Enhancements + +- Quaternion-based rotations for realistic body orientation +- Orbit trail rendering +- N-body simulation mode +- More accurate integration methods (RK4, Verlet) +- Save/load simulation state +- Interactive body selection and information display +- Multiple reference frames + +## License + +This project is provided as-is for educational and research purposes. diff --git a/configs/example_binary_star.txt b/configs/example_binary_star.txt new file mode 100644 index 0000000..dea2d18 --- /dev/null +++ b/configs/example_binary_star.txt @@ -0,0 +1,20 @@ +# Binary Star System with Planets +# A simple example of two stars orbiting each other with planets around them + +# Star A (yellow, index 0) - positioned to the right +StarA 1.5e30 8.0e8 3.0e11 0 0 -1 1.0 1.0 0.2 + +# Star B (blue, index 1) - positioned to the left +StarB 1.2e30 7.0e8 -3.7e11 0 0 -1 0.3 0.5 1.0 + +# Planet orbiting Star A (index 2, parent is StarA at index 0) +PlanetA1 6.0e24 7.0e6 3.5e11 0 0 0 0.8 0.3 0.2 + +# Planet orbiting Star B (index 3, parent is StarB at index 1) +PlanetB1 4.0e24 6.0e6 -4.2e11 0 0 1 0.2 0.8 0.6 + +# Moon orbiting PlanetA1 (index 4, parent is PlanetA1 at index 2) +MoonA1 1.0e23 2.0e6 3.52e11 0 0 2 0.7 0.7 0.7 + +# Second planet orbiting Star A (index 5, parent is StarA at index 0) +PlanetA2 8.0e24 8.0e6 4.0e11 0 0 0 0.5 0.6 0.3 diff --git a/configs/solar_system.txt b/configs/solar_system.txt new file mode 100644 index 0000000..9df58d2 --- /dev/null +++ b/configs/solar_system.txt @@ -0,0 +1,31 @@ +# Solar System Configuration +# Format: name mass(kg) radius(m) x(m) y(m) z(m) parent_index r g b +# parent_index: -1 for Sun, 0 for Sun's children, etc. +# Colors are RGB values from 0.0 to 1.0 + +# The Sun (index 0) +Sun 1.989e30 6.96e8 0 0 0 -1 1.0 1.0 0.0 + +# Inner planets +Mercury 3.285e23 2.4397e6 5.791e10 0 0 0 0.5 0.5 0.5 +Venus 4.867e24 6.0518e6 1.082e11 0 0 0 0.9 0.7 0.3 +Earth 5.972e24 6.371e6 1.496e11 0 0 0 0.0 0.5 1.0 +Mars 6.39e23 3.3895e6 2.279e11 0 0 0 0.8 0.3 0.1 + +# Outer planets +Jupiter 1.898e27 6.9911e7 7.785e11 0 0 0 0.9 0.7 0.5 +Saturn 5.683e26 5.8232e7 1.434e12 0 0 0 0.9 0.8 0.6 +Uranus 8.681e25 2.5362e7 2.871e12 0 0 0 0.5 0.8 0.9 +Neptune 1.024e26 2.4622e7 4.495e12 0 0 0 0.2 0.4 0.9 + +# Earth's Moon (index 9, parent is Earth at index 3) +Moon 7.342e22 1.737e6 1.500e11 0 0 3 0.7 0.7 0.7 + +# Jupiter's major moons (parent is Jupiter at index 5) +Io 8.93e22 1.822e6 8.207e11 0 0 5 0.9 0.9 0.3 +Europa 4.80e22 1.561e6 8.456e11 0 0 5 0.8 0.8 0.7 +Ganymede 1.48e23 2.634e6 8.853e11 0 0 5 0.6 0.6 0.5 +Callisto 1.08e23 2.410e6 9.670e11 0 0 5 0.5 0.5 0.4 + +# Saturn's largest moon (parent is Saturn at index 6) +Titan 1.345e23 2.575e6 1.556e12 0 0 6 0.9 0.6 0.3 diff --git a/docs/implementation_plan.md b/docs/implementation_plan.md new file mode 100644 index 0000000..725e485 --- /dev/null +++ b/docs/implementation_plan.md @@ -0,0 +1,235 @@ +# Orbital Mechanics Simulation - Implementation Plan + +## Project Overview +A 3D orbital mechanics simulation using 2-body gravitational model with sphere of influence (SOI) transitions. Real-time visualization of the solar system (Sun, planets, moons) using raylib with C-style C++. + +## Technical Constraints +- C-style C++ only: structs and functions, no classes or templates +- Small, focused functions +- Simple rotations (Euler angles / axis-angle) - quaternions deferred for later +- Euler integration for physics +- raylib for 3D visualization + +## Project Structure + +``` +claudes_game/ +├── src/ +│ ├── main.cpp +│ ├── physics.cpp +│ ├── physics.h +│ ├── bodies.cpp +│ ├── bodies.h +│ ├── renderer.cpp +│ ├── renderer.h +│ ├── config_loader.cpp +│ └── config_loader.h +├── configs/ +│ ├── solar_system.txt +│ └── example_binary_star.txt +├── Makefile +└── README.md +``` + +## Core Data Structures + +### Vec3 (physics.h) +```cpp +struct Vec3 { + double x, y, z; +}; +``` + +### CelestialBody (bodies.h) +```cpp +struct CelestialBody { + char name[64]; + double mass; // kg + double radius; // meters + Vec3 position; // meters from solar system origin + Vec3 velocity; // m/s + double soi_radius; // sphere of influence radius (meters) + int parent_index; // index of gravitational parent (-1 for Sun) + float color[3]; // RGB for rendering +}; +``` + +### SimulationState (bodies.h) +```cpp +struct SimulationState { + CelestialBody* bodies; + int body_count; + double time; // simulation time (seconds) + double dt; // time step (seconds) +}; +``` + +## Key Components + +### 1. Physics Module (physics.c/h) +**Functions:** +- `Vec3 vec3_add(Vec3 a, Vec3 b)` - vector addition +- `Vec3 vec3_sub(Vec3 a, Vec3 b)` - vector subtraction +- `Vec3 vec3_scale(Vec3 v, double s)` - scalar multiplication +- `double vec3_magnitude(Vec3 v)` - vector length +- `double vec3_distance(Vec3 a, Vec3 b)` - distance between points +- `Vec3 vec3_normalize(Vec3 v)` - unit vector +- `Vec3 calculate_gravity_force(CelestialBody* body, CelestialBody* parent)` - 2-body force +- `Vec3 calculate_acceleration(Vec3 force, double mass)` - F = ma +- `void euler_step(CelestialBody* body, Vec3 acceleration, double dt)` - Euler integration + +### 2. Bodies Module (bodies.c/h) +**Functions:** +- `SimulationState* create_simulation(int max_bodies)` - allocate simulation +- `void destroy_simulation(SimulationState* sim)` - cleanup +- `void add_body(SimulationState* sim, const char* name, double mass, double radius, Vec3 pos, Vec3 vel, float r, float g, float b)` - add celestial body +- `int find_dominant_body(SimulationState* sim, int body_index)` - determine which body has gravitational dominance +- `void update_soi(CelestialBody* body, CelestialBody* parent)` - calculate sphere of influence radius +- `void update_simulation(SimulationState* sim)` - single time step update + +**SOI Calculation:** +Using Hill sphere approximation: `r_soi = a * (m/M)^(2/5)` +where a = semi-major axis, m = body mass, M = parent mass + +### 3. Config Loader Module (config_loader.cpp/h) +**Functions:** +- `bool load_system_config(SimulationState* sim, const char* filepath)` - load bodies from config file +- `bool parse_body_line(const char* line, CelestialBody* body)` - parse single body definition +- `void calculate_initial_velocities(SimulationState* sim)` - compute circular orbit velocities from positions + +**Config File Format (simple text):** +``` +# Comment lines start with # +# Format: name mass(kg) radius(m) x(m) y(m) z(m) parent_index r g b +Sun 1.989e30 6.96e8 0 0 0 -1 1.0 1.0 0.0 +Earth 5.972e24 6.371e6 1.496e11 0 0 0 0.0 0.5 1.0 +Moon 7.342e22 1.737e6 1.4996e11 0 0 1 0.7 0.7 0.7 +# Velocity is calculated automatically for circular orbits +``` + +**Example configs to provide:** +- `configs/solar_system.txt` - Our solar system with Sun, 8 planets, major moons +- `configs/example_binary_star.txt` - Binary star system with planets + +### 4. Renderer Module (renderer.c/h) +**Functions:** +- `void init_renderer(int width, int height, const char* title)` - initialize raylib window +- `void setup_camera(Camera3D* camera)` - configure 3D camera +- `void render_body(CelestialBody* body, double scale)` - draw sphere for body +- `void render_simulation(SimulationState* sim, Camera3D* camera)` - render all bodies +- `void draw_orbit_trail(Vec3* positions, int count, Color color)` - draw orbit path (future enhancement) +- `void close_renderer()` - cleanup raylib + +**Rendering approach:** +- Use logarithmic scaling for distances (solar system is huge) +- Use exponential scaling for body sizes (make small bodies visible) +- Simple camera controls (rotate around Sun, zoom in/out) +- Display FPS and simulation time + +### 5. Main Loop (main.cpp) +```cpp +1. Parse command line arguments (config file path, default to configs/solar_system.txt) +2. Initialize raylib window and camera +3. Create simulation and load system from config file +4. Main loop: + a. Handle input (camera controls, pause/resume, speed adjustment) + b. Update physics (multiple sub-steps per frame for stability) + c. Check for SOI transitions + d. Render scene + e. Update camera +5. Cleanup and exit +``` + +## Implementation Steps + +### Phase 1: Foundation +1. Create project structure and Makefile +2. Implement Vec3 and basic vector math functions (physics.c/h) +3. Define CelestialBody and SimulationState structs (bodies.h) +4. Create basic simulation functions (create, destroy, add_body) + +### Phase 2: Physics Core +5. Implement 2-body gravity calculation +6. Implement Euler integration step +7. Implement SOI detection and parent switching logic +8. Create update_simulation() function + +### Phase 3: Config Loading System +9. Implement config file parser (parse_body_line, skip comments/empty lines) +10. Implement load_system_config() to read and populate simulation +11. Create configs/solar_system.txt with our solar system data +12. Implement calculate_initial_velocities() for circular orbits +13. Calculate SOI radii for all bodies after loading + +### Phase 4: Visualization +14. Initialize raylib window and 3D camera +15. Implement render_body() with scaling +16. Implement render_simulation() to draw all bodies +17. Add basic camera controls (orbital rotation, zoom) + +### Phase 5: Integration and Refinement +18. Integrate rendering with physics loop in main.cpp +19. Add command line argument parsing for config file selection +20. Add time scaling controls (speed up/slow down simulation) +21. Add pause/resume functionality +22. Display simulation info (time, FPS, body count, current config) +23. Create example_binary_star.txt config for testing +24. Test and tune time step for stability + +## Technical Notes + +### Config File System Benefits +- **Flexibility**: Easily create any star system without recompiling +- **Testing**: Quickly test different scenarios (binary stars, close encounters, etc.) +- **Sharing**: Users can share interesting system configurations +- **Debugging**: Simplified test cases with just 2-3 bodies +- **Education**: Learn about different orbital configurations by experimenting + +### Scaling for Visualization +- **Distance scale**: Use logarithmic or power-law scaling (e.g., `display_pos = sign(pos) * pow(abs(pos), 0.3)`) +- **Size scale**: Make minimum visible radius (e.g., max(actual_radius * scale, min_visible_radius)) +- Keep Sun at origin for simplicity + +### Time Step Considerations +- Solar system scale requires small time steps (try dt = 60 seconds initially) +- May need multiple physics updates per render frame +- Allow user to adjust simulation speed multiplier + +### SOI Transition Logic +``` +For each body (except Sun): + 1. Calculate distance to current parent + 2. Calculate distance to all other potential parents + 3. Check if body is within SOI of a different parent + 4. If yes, switch parent_index and recalculate relative state +``` + +### Initial Conditions +- Use Keplerian orbital elements or simplified circular orbits +- Ensure velocity is perpendicular to position vector for circular orbits +- Circular orbit velocity: v = sqrt(G * M / r) + +## Files to Create +1. `src/physics.h` - Vector math and physics declarations +2. `src/physics.cpp` - Vector math and physics implementations +3. `src/bodies.h` - Body and simulation data structures +4. `src/bodies.cpp` - Body management and simulation update +5. `src/config_loader.h` - Config file parsing declarations +6. `src/config_loader.cpp` - Config file loading implementation +7. `src/renderer.h` - Rendering declarations +8. `src/renderer.cpp` - raylib rendering implementation +9. `src/main.cpp` - Main loop and program entry +10. `configs/solar_system.txt` - Our solar system configuration +11. `configs/example_binary_star.txt` - Example binary star system +12. `Makefile` - Build configuration +13. `README.md` - Project documentation and config format docs + +## Future Enhancements (Not in Initial Implementation) +- Quaternion-based rotations +- Orbit trail rendering +- N-body simulation mode +- Patched conic approximation +- More accurate integration (RK4, Verlet) +- Save/load simulation state +- Interactive body selection and info display +- Reference frame switching diff --git a/ext/raylib b/ext/raylib new file mode 160000 index 0000000..ca89934 --- /dev/null +++ b/ext/raylib @@ -0,0 +1 @@ +Subproject commit ca89934ed5af9161f781a9b35b808b765dc40f3a diff --git a/src/bodies.cpp b/src/bodies.cpp new file mode 100644 index 0000000..f594ce0 --- /dev/null +++ b/src/bodies.cpp @@ -0,0 +1,133 @@ +#include "bodies.h" +#include +#include +#include + +// Create a new simulation +SimulationState* create_simulation(int max_bodies, double time_step) { + SimulationState* sim = (SimulationState*)malloc(sizeof(SimulationState)); + sim->bodies = (CelestialBody*)malloc(sizeof(CelestialBody) * max_bodies); + sim->body_count = 0; + sim->max_bodies = max_bodies; + sim->time = 0.0; + sim->dt = time_step; + return sim; +} + +// Destroy simulation and free memory +void destroy_simulation(SimulationState* sim) { + if (sim) { + if (sim->bodies) { + free(sim->bodies); + } + free(sim); + } +} + +// Add a celestial body to the simulation +void add_body(SimulationState* sim, const char* name, double mass, double radius, + Vec3 pos, Vec3 vel, int parent_index, float r, float g, float b) { + if (sim->body_count >= sim->max_bodies) { + return; // No more space + } + + CelestialBody* body = &sim->bodies[sim->body_count]; + strncpy(body->name, name, 63); + body->name[63] = '\0'; + body->mass = mass; + body->radius = radius; + body->position = pos; + body->velocity = vel; + body->soi_radius = 0.0; // Will be calculated later + body->parent_index = parent_index; + body->color[0] = r; + body->color[1] = g; + body->color[2] = b; + + sim->body_count++; +} + +// Find which body is gravitationally dominant for the given body +int find_dominant_body(SimulationState* sim, int body_index) { + if (body_index < 0 || body_index >= sim->body_count) { + return -1; + } + + CelestialBody* body = &sim->bodies[body_index]; + int dominant = body->parent_index; + + // Check all other bodies to see if we're within their SOI + for (int i = 0; i < sim->body_count; i++) { + if (i == body_index) continue; + + CelestialBody* potential_parent = &sim->bodies[i]; + double distance = vec3_distance(body->position, potential_parent->position); + + // If we're within this body's SOI and it's not our current parent + if (distance < potential_parent->soi_radius && i != dominant) { + // Check if this body is more dominant (closer or more massive) + if (dominant == -1) { + dominant = i; + } else { + CelestialBody* current_parent = &sim->bodies[dominant]; + double dist_to_current = vec3_distance(body->position, current_parent->position); + + // Switch if this potential parent is significantly closer + if (distance < dist_to_current * 0.5) { + dominant = i; + } + } + } + } + + return dominant; +} + +// Update sphere of influence radius using Hill sphere approximation +// r_soi = a * (m/M)^(2/5) where a = semi-major axis, m = body mass, M = parent mass +void update_soi(CelestialBody* body, CelestialBody* parent, double semi_major_axis) { + if (parent == NULL || parent->mass <= 0.0) { + // Root body (like Sun) has infinite SOI, use a large value + body->soi_radius = 1e15; // 1000 AU in meters + return; + } + + double mass_ratio = body->mass / parent->mass; + body->soi_radius = semi_major_axis * pow(mass_ratio, 0.4); // 2/5 = 0.4 +} + +// Update the entire simulation by one time step +void update_simulation(SimulationState* sim) { + // Update each body's physics (except the root body which is stationary) + for (int i = 0; i < sim->body_count; i++) { + CelestialBody* body = &sim->bodies[i]; + + // Skip if this is a root body (parent_index == -1) + if (body->parent_index == -1) { + continue; + } + + // Check if parent has changed (SOI transition) + int new_parent = find_dominant_body(sim, i); + if (new_parent != body->parent_index && new_parent != -1) { + body->parent_index = new_parent; + } + + // Get the current parent + if (body->parent_index >= 0 && body->parent_index < sim->body_count) { + CelestialBody* parent = &sim->bodies[body->parent_index]; + + // Calculate gravitational force from parent + Vec3 force = calculate_gravity_force(body, parent); + + // Calculate acceleration + Vec3 acceleration = calculate_acceleration(force, body->mass); + + // Perform Euler integration step + euler_step(body, acceleration, sim->dt); + } + } + + // Update simulation time + sim->time += sim->dt; +} diff --git a/src/bodies.h b/src/bodies.h new file mode 100644 index 0000000..2266bfc --- /dev/null +++ b/src/bodies.h @@ -0,0 +1,38 @@ +#ifndef BODIES_H +#define BODIES_H + +#include "physics.h" + +// Celestial body structure +struct CelestialBody { + char name[64]; + double mass; // kg + double radius; // meters + Vec3 position; // meters from origin + Vec3 velocity; // m/s + double soi_radius; // sphere of influence radius (meters) + int parent_index; // index of gravitational parent (-1 for root body like Sun) + float color[3]; // RGB color for rendering +}; + +// Simulation state +struct SimulationState { + CelestialBody* bodies; + int body_count; + int max_bodies; + double time; // simulation time (seconds) + double dt; // time step (seconds) +}; + +// Simulation management functions +SimulationState* create_simulation(int max_bodies, double time_step); +void destroy_simulation(SimulationState* sim); +void add_body(SimulationState* sim, const char* name, double mass, double radius, + Vec3 pos, Vec3 vel, int parent_index, float r, float g, float b); + +// SOI and simulation update functions +int find_dominant_body(SimulationState* sim, int body_index); +void update_soi(CelestialBody* body, CelestialBody* parent, double semi_major_axis); +void update_simulation(SimulationState* sim); + +#endif diff --git a/src/config_loader.cpp b/src/config_loader.cpp new file mode 100644 index 0000000..2db092f --- /dev/null +++ b/src/config_loader.cpp @@ -0,0 +1,138 @@ +#include "config_loader.h" +#include +#include +#include + +// Parse a single body definition line +bool parse_body_line(const char* line, char* name, double* mass, double* radius, + Vec3* pos, int* parent_index, float* r, float* g, float* b) { + // Skip empty lines and comments + if (line[0] == '\0' || line[0] == '#' || line[0] == '\n') { + return false; + } + + // Parse: name mass(kg) radius(m) x(m) y(m) z(m) parent_index r g b + int result = sscanf(line, "%63s %lf %lf %lf %lf %lf %d %f %f %f", + name, mass, radius, &pos->x, &pos->y, &pos->z, + parent_index, r, g, b); + + return result == 10; // All fields must be present +} + +// Load system configuration from file +bool load_system_config(SimulationState* sim, const char* filepath) { + FILE* file = fopen(filepath, "r"); + if (!file) { + printf("Error: Could not open config file: %s\n", filepath); + return false; + } + + char line[256]; + char name[64]; + double mass, radius; + Vec3 pos; + int parent_index; + float r, g, b; + + while (fgets(line, sizeof(line), file)) { + if (parse_body_line(line, name, &mass, &radius, &pos, &parent_index, &r, &g, &b)) { + Vec3 vel = {0.0, 0.0, 0.0}; // Velocity will be calculated later + add_body(sim, name, mass, radius, pos, vel, parent_index, r, g, b); + } + } + + fclose(file); + + if (sim->body_count == 0) { + printf("Error: No bodies loaded from config file\n"); + return false; + } + + // Calculate initial velocities for circular orbits + calculate_initial_velocities(sim); + + // Calculate SOI radii + calculate_soi_radii(sim); + + printf("Loaded %d bodies from %s\n", sim->body_count, filepath); + return true; +} + +// Calculate circular orbit velocity: v = sqrt(G * M / r) +// Velocity is perpendicular to position vector +void calculate_initial_velocities(SimulationState* sim) { + for (int i = 0; i < sim->body_count; i++) { + CelestialBody* body = &sim->bodies[i]; + + // Skip root body (no parent) + if (body->parent_index == -1) { + body->velocity = {0.0, 0.0, 0.0}; + continue; + } + + // Get parent body + if (body->parent_index >= 0 && body->parent_index < sim->body_count) { + CelestialBody* parent = &sim->bodies[body->parent_index]; + + // Calculate relative position + Vec3 r = vec3_sub(body->position, parent->position); + double distance = vec3_magnitude(r); + + if (distance < 1.0) { + body->velocity = {0.0, 0.0, 0.0}; + continue; + } + + // Calculate circular orbit speed + double speed = sqrt(G * parent->mass / distance); + + // Create velocity perpendicular to position vector + // If position is mostly in XY plane, make velocity in XY plane + // Cross product of r with z-axis gives perpendicular vector in XY plane + Vec3 z_axis = {0.0, 0.0, 1.0}; + + // Calculate cross product: r x z_axis + Vec3 vel_dir = { + r.y * z_axis.z - r.z * z_axis.y, + r.z * z_axis.x - r.x * z_axis.z, + r.x * z_axis.y - r.y * z_axis.x + }; + + // If r is parallel to z-axis, use x-axis instead + double cross_mag = vec3_magnitude(vel_dir); + if (cross_mag < 0.01) { + Vec3 x_axis = {1.0, 0.0, 0.0}; + vel_dir.x = r.y * x_axis.z - r.z * x_axis.y; + vel_dir.y = r.z * x_axis.x - r.x * x_axis.z; + vel_dir.z = r.x * x_axis.y - r.y * x_axis.x; + } + + // Normalize and scale by orbital speed + vel_dir = vec3_normalize(vel_dir); + body->velocity = vec3_scale(vel_dir, speed); + + // Add parent's velocity for absolute reference frame + body->velocity = vec3_add(body->velocity, parent->velocity); + } + } +} + +// Calculate SOI radii for all bodies +void calculate_soi_radii(SimulationState* sim) { + for (int i = 0; i < sim->body_count; i++) { + CelestialBody* body = &sim->bodies[i]; + + if (body->parent_index == -1) { + // Root body has very large SOI + body->soi_radius = 1e15; // ~1000 AU + } else if (body->parent_index >= 0 && body->parent_index < sim->body_count) { + CelestialBody* parent = &sim->bodies[body->parent_index]; + + // Calculate semi-major axis (distance to parent) + double semi_major_axis = vec3_distance(body->position, parent->position); + + // Update SOI using Hill sphere approximation + update_soi(body, parent, semi_major_axis); + } + } +} diff --git a/src/config_loader.h b/src/config_loader.h new file mode 100644 index 0000000..26b83da --- /dev/null +++ b/src/config_loader.h @@ -0,0 +1,19 @@ +#ifndef CONFIG_LOADER_H +#define CONFIG_LOADER_H + +#include "bodies.h" + +// Load a system configuration from a file +bool load_system_config(SimulationState* sim, const char* filepath); + +// Parse a single body definition line +bool parse_body_line(const char* line, char* name, double* mass, double* radius, + Vec3* pos, int* parent_index, float* r, float* g, float* b); + +// Calculate initial circular orbit velocities for all bodies +void calculate_initial_velocities(SimulationState* sim); + +// Calculate SOI radii for all bodies +void calculate_soi_radii(SimulationState* sim); + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..06d58ab --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,90 @@ +#include "physics.h" +#include "bodies.h" +#include "config_loader.h" +#include "renderer.h" +#include +#include + +int main(int argc, char** argv) { + // Parse command line arguments + const char* config_file = "configs/solar_system.txt"; + if (argc > 1) { + config_file = argv[1]; + } + + printf("=== Orbital Mechanics Simulation ===\n"); + printf("Loading configuration: %s\n", config_file); + + // Create simulation with time step of 60 seconds + const int MAX_BODIES = 100; + const double TIME_STEP = 60.0; // 60 seconds per step + SimulationState* sim = create_simulation(MAX_BODIES, TIME_STEP); + + // Load system configuration + if (!load_system_config(sim, config_file)) { + printf("Failed to load configuration file\n"); + destroy_simulation(sim); + return 1; + } + + // Initialize renderer + init_renderer(1280, 720, "Orbital Mechanics Simulation"); + + // Setup rendering state + RenderState render_state; + setup_camera(&render_state); + + // Simulation control variables + bool paused = false; + double speed_multiplier = 1.0; + int physics_steps_per_frame = 100; // Multiple physics steps per frame for stability + + printf("\nSimulation started!\n"); + printf("Controls:\n"); + printf(" Arrow keys: Rotate and zoom camera\n"); + printf(" Space: Pause/Resume\n"); + printf(" +/-: Speed up/slow down simulation\n"); + printf(" I: Toggle info display\n"); + printf(" ESC: Quit\n\n"); + + // Main loop + while (!WindowShouldClose()) { + // Handle input + if (IsKeyPressed(KEY_SPACE)) { + paused = !paused; + printf("Simulation %s\n", paused ? "paused" : "resumed"); + } + + if (IsKeyPressed(KEY_EQUAL) || IsKeyPressed(KEY_KP_ADD)) { + speed_multiplier *= 2.0; + printf("Speed multiplier: %.1fx\n", speed_multiplier); + } + + if (IsKeyPressed(KEY_MINUS) || IsKeyPressed(KEY_KP_SUBTRACT)) { + speed_multiplier /= 2.0; + if (speed_multiplier < 0.125) speed_multiplier = 0.125; + printf("Speed multiplier: %.1fx\n", speed_multiplier); + } + + // Update camera + update_camera(&render_state); + + // Update physics (multiple steps per frame) + if (!paused) { + int steps = (int)(physics_steps_per_frame * speed_multiplier); + for (int i = 0; i < steps; i++) { + update_simulation(sim); + } + } + + // Render + render_simulation(sim, &render_state); + } + + // Cleanup + close_renderer(); + destroy_simulation(sim); + + printf("\nSimulation ended. Final time: %.2f days\n", sim->time / 86400.0); + return 0; +} diff --git a/src/physics.cpp b/src/physics.cpp new file mode 100644 index 0000000..372bf34 --- /dev/null +++ b/src/physics.cpp @@ -0,0 +1,71 @@ +#include "physics.h" +#include "bodies.h" +#include + +// Vector addition +Vec3 vec3_add(Vec3 a, Vec3 b) { + return {a.x + b.x, a.y + b.y, a.z + b.z}; +} + +// Vector subtraction +Vec3 vec3_sub(Vec3 a, Vec3 b) { + return {a.x - b.x, a.y - b.y, a.z - b.z}; +} + +// Scalar multiplication +Vec3 vec3_scale(Vec3 v, double s) { + return {v.x * s, v.y * s, v.z * s}; +} + +// Vector magnitude +double vec3_magnitude(Vec3 v) { + return sqrt(v.x * v.x + v.y * v.y + v.z * v.z); +} + +// Distance between two points +double vec3_distance(Vec3 a, Vec3 b) { + Vec3 diff = vec3_sub(a, b); + return vec3_magnitude(diff); +} + +// Normalize vector to unit length +Vec3 vec3_normalize(Vec3 v) { + double mag = vec3_magnitude(v); + if (mag > 0.0) { + return vec3_scale(v, 1.0 / mag); + } + return {0.0, 0.0, 0.0}; +} + +// Calculate gravitational force using Newton's law: F = G * m1 * m2 / r^2 +Vec3 calculate_gravity_force(CelestialBody* body, CelestialBody* parent) { + Vec3 r = vec3_sub(parent->position, body->position); + double distance = vec3_magnitude(r); + + // Avoid division by zero + if (distance < 1.0) { + distance = 1.0; + } + + double force_magnitude = G * body->mass * parent->mass / (distance * distance); + Vec3 direction = vec3_normalize(r); + + return vec3_scale(direction, force_magnitude); +} + +// Calculate acceleration from force: a = F / m +Vec3 calculate_acceleration(Vec3 force, double mass) { + if (mass > 0.0) { + return vec3_scale(force, 1.0 / mass); + } + return {0.0, 0.0, 0.0}; +} + +// Euler integration step: update position and velocity +void euler_step(CelestialBody* body, Vec3 acceleration, double dt) { + // Update velocity: v = v + a * dt + body->velocity = vec3_add(body->velocity, vec3_scale(acceleration, dt)); + + // Update position: p = p + v * dt + body->position = vec3_add(body->position, vec3_scale(body->velocity, dt)); +} diff --git a/src/physics.h b/src/physics.h new file mode 100644 index 0000000..8cd9585 --- /dev/null +++ b/src/physics.h @@ -0,0 +1,28 @@ +#ifndef PHYSICS_H +#define PHYSICS_H + +// Forward declaration +struct CelestialBody; + +// 3D Vector +struct Vec3 { + double x, y, z; +}; + +// Gravitational constant (m^3 kg^-1 s^-2) +const double G = 6.67430e-11; + +// Vector math functions +Vec3 vec3_add(Vec3 a, Vec3 b); +Vec3 vec3_sub(Vec3 a, Vec3 b); +Vec3 vec3_scale(Vec3 v, double s); +double vec3_magnitude(Vec3 v); +double vec3_distance(Vec3 a, Vec3 b); +Vec3 vec3_normalize(Vec3 v); + +// Physics functions +Vec3 calculate_gravity_force(CelestialBody* body, CelestialBody* parent); +Vec3 calculate_acceleration(Vec3 force, double mass); +void euler_step(CelestialBody* body, Vec3 acceleration, double dt); + +#endif diff --git a/src/renderer.cpp b/src/renderer.cpp new file mode 100644 index 0000000..be0a0c5 --- /dev/null +++ b/src/renderer.cpp @@ -0,0 +1,159 @@ +#include "renderer.h" +#include "raymath.h" +#include +#include + +// Initialize raylib window +void init_renderer(int width, int height, const char* title) { + InitWindow(width, height, title); + SetTargetFPS(60); +} + +// Close raylib +void close_renderer() { + CloseWindow(); +} + +// Setup the 3D camera +void setup_camera(RenderState* render_state) { + render_state->camera.position = (Vector3){ 0.0f, 50.0f, 100.0f }; + render_state->camera.target = (Vector3){ 0.0f, 0.0f, 0.0f }; + render_state->camera.up = (Vector3){ 0.0f, 1.0f, 0.0f }; + render_state->camera.fovy = 45.0f; + render_state->camera.projection = CAMERA_PERSPECTIVE; + + // Set scaling factors + render_state->distance_scale = 1e-9; // Meters to scaled units (1 unit = 1 billion meters) + render_state->size_scale = 5e-7; // Make bodies visible + render_state->show_info = true; +} + +// Update camera with keyboard/mouse controls +void update_camera(RenderState* render_state) { + // Orbital camera rotation with arrow keys + float camera_distance = Vector3Distance(render_state->camera.position, render_state->camera.target); + float angle_speed = 0.02f; + + // Rotate around target + if (IsKeyDown(KEY_LEFT)) { + Vector3 pos = render_state->camera.position; + float angle = angle_speed; + float x = pos.x * cosf(angle) - pos.z * sinf(angle); + float z = pos.x * sinf(angle) + pos.z * cosf(angle); + render_state->camera.position.x = x; + render_state->camera.position.z = z; + } + if (IsKeyDown(KEY_RIGHT)) { + Vector3 pos = render_state->camera.position; + float angle = -angle_speed; + float x = pos.x * cosf(angle) - pos.z * sinf(angle); + float z = pos.x * sinf(angle) + pos.z * cosf(angle); + render_state->camera.position.x = x; + render_state->camera.position.z = z; + } + + // Zoom in/out with up/down keys + if (IsKeyDown(KEY_UP) && camera_distance > 10.0f) { + Vector3 direction = Vector3Subtract(render_state->camera.target, render_state->camera.position); + direction = Vector3Normalize(direction); + render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); + } + if (IsKeyDown(KEY_DOWN)) { + Vector3 direction = Vector3Subtract(render_state->camera.position, render_state->camera.target); + direction = Vector3Normalize(direction); + render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); + } + + // Toggle info display with I key + if (IsKeyPressed(KEY_I)) { + render_state->show_info = !render_state->show_info; + } +} + +// Scale a position for rendering +Vector3 scale_position(Vec3 pos, double scale) { + return (Vector3){ + (float)(pos.x * scale), + (float)(pos.y * scale), + (float)(pos.z * scale) + }; +} + +// Scale a radius for rendering (with minimum visible size) +float scale_radius(double radius, double scale) { + float scaled = (float)(radius * scale); + float min_radius = 0.5f; // Minimum visible radius + return (scaled > min_radius) ? scaled : min_radius; +} + +// Render a single celestial body +void render_body(CelestialBody* body, RenderState* render_state) { + Vector3 position = scale_position(body->position, render_state->distance_scale); + float radius = scale_radius(body->radius, render_state->size_scale); + + Color color = { + (unsigned char)(body->color[0] * 255), + (unsigned char)(body->color[1] * 255), + (unsigned char)(body->color[2] * 255), + 255 + }; + + DrawSphere(position, radius, color); +} + +// Render the entire simulation +void render_simulation(SimulationState* sim, RenderState* render_state) { + BeginDrawing(); + ClearBackground(BLACK); + + BeginMode3D(render_state->camera); + + // Draw a reference grid + DrawGrid(100, 10.0f); + + // Render all bodies + for (int i = 0; i < sim->body_count; i++) { + render_body(&sim->bodies[i], render_state); + } + + EndMode3D(); + + // Render 2D info overlay + if (render_state->show_info) { + render_info(sim, "solar_system.txt"); + } + + EndDrawing(); +} + +// Render simulation information overlay +void render_info(SimulationState* sim, const char* config_name) { + DrawText("Orbital Mechanics Simulation", 10, 10, 20, WHITE); + + char buffer[256]; + + // Simulation time (in days) + double days = sim->time / 86400.0; // seconds to days + snprintf(buffer, sizeof(buffer), "Time: %.2f days", days); + DrawText(buffer, 10, 40, 16, LIGHTGRAY); + + // Body count + snprintf(buffer, sizeof(buffer), "Bodies: %d", sim->body_count); + DrawText(buffer, 10, 60, 16, LIGHTGRAY); + + // Config name + snprintf(buffer, sizeof(buffer), "Config: %s", config_name); + DrawText(buffer, 10, 80, 16, LIGHTGRAY); + + // FPS + snprintf(buffer, sizeof(buffer), "FPS: %d", GetFPS()); + DrawText(buffer, 10, 100, 16, LIGHTGRAY); + + // Controls + DrawText("Controls:", 10, 130, 16, YELLOW); + DrawText(" Arrows: Rotate/Zoom camera", 10, 150, 14, LIGHTGRAY); + DrawText(" Space: Pause/Resume", 10, 170, 14, LIGHTGRAY); + DrawText(" +/-: Speed up/slow down", 10, 190, 14, LIGHTGRAY); + DrawText(" I: Toggle info", 10, 210, 14, LIGHTGRAY); + DrawText(" ESC: Quit", 10, 230, 14, LIGHTGRAY); +} diff --git a/src/renderer.h b/src/renderer.h new file mode 100644 index 0000000..7f1ed73 --- /dev/null +++ b/src/renderer.h @@ -0,0 +1,32 @@ +#ifndef RENDERER_H +#define RENDERER_H + +#include "bodies.h" +#include "raylib.h" + +// Rendering state +struct RenderState { + Camera3D camera; + double distance_scale; // Scale factor for distances + double size_scale; // Scale factor for body sizes + bool show_info; // Display simulation info +}; + +// Renderer initialization and cleanup +void init_renderer(int width, int height, const char* title); +void close_renderer(); + +// Camera setup and control +void setup_camera(RenderState* render_state); +void update_camera(RenderState* render_state); + +// Rendering functions +void render_body(CelestialBody* body, RenderState* render_state); +void render_simulation(SimulationState* sim, RenderState* render_state); +void render_info(SimulationState* sim, const char* config_name); + +// Scaling functions +Vector3 scale_position(Vec3 pos, double scale); +float scale_radius(double radius, double scale); + +#endif