diff --git a/src/main.cpp b/src/main.cpp index 829bf79..f4aec74 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -47,6 +47,7 @@ void run_gui_simulation(SimulationState* sim) { // Initialize UI state render_state.selected_body_index = root_body_index >= 0 ? root_body_index : -1; + render_state.selected_craft_index = -1; // No spacecraft selected initially render_state.previous_selected_body = -1; // Previous selected body index render_state.body_list_scroll = 0; // Initial scroll position render_state.body_list_active = root_body_index >= 0 ? root_body_index : -1; // Sync with selected body diff --git a/src/renderer.cpp b/src/renderer.cpp index 779a447..34a102a 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -46,21 +46,32 @@ void update_camera(RenderState* render_state, SimulationState* sim) { Vector3 body_pos = sim_to_render(body->position, render_state->distance_scale); render_state->camera.target = body_pos; render_state->camera_offset = Vector3Subtract(render_state->camera.position, body_pos); + } else if (render_state->selected_craft_index >= 0 && render_state->selected_craft_index < sim->craft_count) { + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + Vector3 craft_pos = sim_to_render(craft->position, render_state->distance_scale); + render_state->camera.target = craft_pos; + render_state->camera_offset = Vector3Subtract(render_state->camera.position, craft_pos); } } - // Preserve distance when switching to different body - if (render_state->camera_follow_body && render_state->selected_body_index >= 0 && - render_state->selected_body_index != render_state->previous_selected_body && - render_state->selected_body_index < sim->body_count) { - // Body selection changed - recalculate offset to maintain distance - Vector3 body_pos = sim_to_render(sim->bodies[render_state->selected_body_index].position, render_state->distance_scale); - render_state->camera.target = body_pos; - // Use current offset to maintain distance to new body - render_state->camera.position = Vector3Add(body_pos, render_state->camera_offset); + // Preserve distance when switching to different body or spacecraft + if (render_state->camera_follow_body) { + if (render_state->selected_body_index >= 0 && + render_state->selected_body_index != render_state->previous_selected_body && + render_state->selected_body_index < sim->body_count) { + // Body selection changed - recalculate offset to maintain distance + Vector3 body_pos = sim_to_render(sim->bodies[render_state->selected_body_index].position, render_state->distance_scale); + render_state->camera.target = body_pos; + render_state->camera.position = Vector3Add(body_pos, render_state->camera_offset); + } else if (render_state->selected_craft_index >= 0 && sim->craft_count > 0) { + // Spacecraft selected - update camera to follow it + Vector3 craft_pos = sim_to_render(sim->spacecraft[render_state->selected_craft_index].position, render_state->distance_scale); + render_state->camera.target = craft_pos; + render_state->camera.position = Vector3Add(craft_pos, render_state->camera_offset); + } } - // Update target position when following + // Update target position when following body if (render_state->camera_follow_body && render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { CelestialBody* body = &sim->bodies[render_state->selected_body_index]; @@ -69,6 +80,15 @@ void update_camera(RenderState* render_state, SimulationState* sim) { render_state->camera.position = Vector3Add(body_pos, render_state->camera_offset); } + // Update target position when following spacecraft + if (render_state->camera_follow_body && render_state->selected_craft_index >= 0 && + render_state->selected_craft_index < sim->craft_count) { + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + Vector3 craft_pos = sim_to_render(craft->position, render_state->distance_scale); + render_state->camera.target = craft_pos; + render_state->camera.position = Vector3Add(craft_pos, render_state->camera_offset); + } + // Camera rotation using camera's up vector Vector3 to_camera = Vector3Subtract(render_state->camera.position, render_state->camera.target); float camera_distance = Vector3Length(to_camera); @@ -186,6 +206,59 @@ void render_body(CelestialBody* body, RenderState* render_state) { DrawSphereWires(position, radius, 16, 16, color); } +// Render a single spacecraft as a simple geometric marker +void render_spacecraft(Spacecraft* craft, RenderState* render_state) { + Vector3 position = sim_to_render(craft->position, render_state->distance_scale); + + // Fixed size for visibility at solar system distances + float marker_radius = 5.0f; + Color spacecraft_color = { 0, 255, 255, 255 }; // Cyan + + DrawSphereWires(position, marker_radius, 8, 8, spacecraft_color); +} + +// Render a maneuver marker for a spacecraft +void render_maneuver_marker(Spacecraft* craft, Maneuver* maneuver, RenderState* render_state) { + if (maneuver->executed) { + return; // Don't show markers for executed maneuvers + } + + Vector3 position = sim_to_render(craft->position, render_state->distance_scale); + + // Size based on delta-v magnitude + float marker_size = (float)(maneuver->delta_v / 100.0f); // Scale by delta-v + if (marker_size < 2.0f) marker_size = 2.0f; + if (marker_size > 10.0f) marker_size = 10.0f; + + // Color based on direction + Color marker_color; + switch (maneuver->direction) { + case BURN_PROGRADE: + marker_color = (Color){ 0, 255, 0, 200 }; // Green + break; + case BURN_RETROGRADE: + marker_color = (Color){ 255, 0, 0, 200 }; // Red + break; + case BURN_NORMAL: + marker_color = (Color){ 255, 255, 0, 200 }; // Yellow + break; + case BURN_ANTINORMAL: + marker_color = (Color){ 255, 165, 0, 200 }; // Orange + break; + case BURN_RADIAL_IN: + marker_color = (Color){ 255, 0, 255, 200 }; // Magenta + break; + case BURN_RADIAL_OUT: + marker_color = (Color){ 0, 255, 255, 200 }; // Cyan + break; + default: + marker_color = (Color){ 255, 255, 255, 200 }; // White + break; + } + + DrawCube(position, marker_size, marker_size, marker_size, marker_color); +} + struct OrbitalBasis { Vec3 periapsis_dir; Vec3 normal; @@ -388,12 +461,27 @@ void render_simulation(SimulationState* sim, RenderState* render_state) { render_body(&sim->bodies[i], render_state); } + // Render all spacecraft (after bodies for visibility) + for (int i = 0; i < sim->craft_count; i++) { + render_spacecraft(&sim->spacecraft[i], render_state); + } + + // Render maneuver markers (top layer) + for (int i = 0; i < sim->maneuver_count; i++) { + Maneuver* maneuver = &sim->maneuvers[i]; + if (!maneuver->executed && maneuver->craft_index >= 0 && maneuver->craft_index < sim->craft_count) { + Spacecraft* craft = &sim->spacecraft[maneuver->craft_index]; + render_maneuver_marker(craft, maneuver, render_state); + } + } + EndMode3D(); // Render UI panels (always shown) render_info(sim); render_body_list_ui(sim, render_state); render_body_info_ui(sim, render_state); + render_maneuver_list_ui(sim, render_state); EndDrawing(); } @@ -466,23 +554,35 @@ void render_body_list_ui(SimulationState* sim, RenderState* render_state) { Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - // Create body list string for raygui (NULL-terminated) - int buffer_size = sim->body_count * (64 + 1) + 1; - char* body_list_text = (char*)malloc(buffer_size); - if (!body_list_text) return; + // Create list string for raygui (NULL-terminated) + // Include both bodies and spacecraft + int total_items = sim->body_count + sim->craft_count; + int buffer_size = total_items * (64 + 3 + 1) + 1; // Extra space for "šŸš€ " prefix + char* list_text = (char*)malloc(buffer_size); + if (!list_text) return; - body_list_text[0] = '\0'; + list_text[0] = '\0'; int offset = 0; + int item_index = 0; + // Add bodies to list for (int i = 0; i < sim->body_count; i++) { - if (i > 0) { - offset += snprintf(body_list_text + offset, buffer_size - offset, ";"); + if (item_index > 0) { + offset += snprintf(list_text + offset, buffer_size - offset, ";"); } - offset += snprintf(body_list_text + offset, buffer_size - offset, "%s", sim->bodies[i].name); + offset += snprintf(list_text + offset, buffer_size - offset, "%s", sim->bodies[i].name); + item_index++; + } + + // Add spacecraft to list with "šŸš€ " prefix + for (int i = 0; i < sim->craft_count; i++) { + offset += snprintf(list_text + offset, buffer_size - offset, ";"); + offset += snprintf(list_text + offset, buffer_size - offset, "šŸš€ %s", sim->spacecraft[i].name); + item_index++; } // Draw window (no close button - panels always shown) - GuiWindowBox(panel_bounds, "Bodies"); + GuiWindowBox(panel_bounds, "Objects"); // Draw list view (inside window, leave space for title bar) Rectangle list_bounds = { @@ -493,118 +593,306 @@ void render_body_list_ui(SimulationState* sim, RenderState* render_state) { }; int previous_active = render_state->body_list_active; - GuiListView(list_bounds, body_list_text, &render_state->body_list_scroll, &render_state->body_list_active); - - // Check if a body was selected (active changed from -1 or previous value) - if (render_state->body_list_active >= 0 && render_state->body_list_active < sim->body_count && - render_state->body_list_active != previous_active) { - render_state->selected_body_index = render_state->body_list_active; - // Enable camera follow when body is selected - render_state->camera_follow_body = true; - printf("Camera follow enabled for: %s\n", sim->bodies[render_state->selected_body_index].name); + GuiListView(list_bounds, list_text, &render_state->body_list_scroll, &render_state->body_list_active); + + // Check if an item was selected + if (render_state->body_list_active >= 0 && render_state->body_list_active != previous_active) { + if (render_state->body_list_active < sim->body_count) { + // A body was selected + render_state->selected_body_index = render_state->body_list_active; + render_state->selected_craft_index = -1; + render_state->camera_follow_body = true; + printf("Camera follow enabled for body: %s\n", sim->bodies[render_state->selected_body_index].name); + } else { + // A spacecraft was selected + int craft_index = render_state->body_list_active - sim->body_count; + if (craft_index >= 0 && craft_index < sim->craft_count) { + render_state->selected_body_index = -1; + render_state->selected_craft_index = craft_index; + render_state->camera_follow_body = true; + printf("Camera follow enabled for spacecraft: %s\n", sim->spacecraft[craft_index].name); + } + } } - free(body_list_text); + free(list_text); } // Render body information UI panel void render_body_info_ui(SimulationState* sim, RenderState* render_state) { - if (render_state->selected_body_index < 0 || render_state->selected_body_index >= sim->body_count) { - // No body selected - render empty panel - int panel_width = 250; - int panel_height = 300; - int panel_x = GetScreenWidth() - panel_width - 10; - int panel_y = 10; - - Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - GuiWindowBox(panel_bounds, "Info"); - return; - } - // Panel dimensions int panel_width = 250; int panel_height = 300; int panel_x = GetScreenWidth() - panel_width - 10; int panel_y = 10; + if ((render_state->selected_body_index < 0 || render_state->selected_body_index >= sim->body_count) && + (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count)) { + // No body or spacecraft selected - render empty panel + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + GuiWindowBox(panel_bounds, "Info"); + return; + } + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; - CelestialBody* body = &sim->bodies[render_state->selected_body_index]; + // Check if a body is selected + if (render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { + CelestialBody* body = &sim->bodies[render_state->selected_body_index]; - // Draw window (no close button - panels always shown) - GuiWindowBox(panel_bounds, body->name); + // Draw window (no close button - panels always shown) + GuiWindowBox(panel_bounds, body->name); - // Prepare info text - char info_text[1024]; - char temp_buffer[256]; + // Prepare info text + char info_text[1024]; + char temp_buffer[256]; - snprintf(info_text, sizeof(info_text), "Name: %s", body->name); + snprintf(info_text, sizeof(info_text), "Name: %s", body->name); - snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", body->mass); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); + snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", body->mass); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); - snprintf(temp_buffer, sizeof(temp_buffer), "Radius: %.2e m", body->radius); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); + snprintf(temp_buffer, sizeof(temp_buffer), "Radius: %.2e m", body->radius); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + if (body->parent_index <= 0) { + // Position + snprintf(temp_buffer, sizeof(temp_buffer), "Position: (%.2e, %.2e, %.2e)", + body->position.x, body->position.y, body->position.z); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Velocity + double vel_mag = vec3_magnitude(body->velocity); + snprintf(temp_buffer, sizeof(temp_buffer), "Velocity: %.2f m/s", vel_mag); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + } else { + // Position (Local) + snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", + body->local_position.x, body->local_position.y, body->local_position.z); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Velocity (Local) + double parent_vel_mag = vec3_magnitude(sim->bodies[body->parent_index].velocity); + double vel_mag = vec3_magnitude(body->velocity) - parent_vel_mag; + snprintf(temp_buffer, sizeof(temp_buffer), "local_velocity: %.2f m/s", vel_mag); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + } - if (body->parent_index <= 0) { - // Position - snprintf(temp_buffer, sizeof(temp_buffer), "Position: (%.2e, %.2e, %.2e)", - body->position.x, body->position.y, body->position.z); + // Orbital elements + snprintf(temp_buffer, sizeof(temp_buffer), "Eccentricity: %.3f", body->eccentricity); strcat(info_text, "\n"); strcat(info_text, temp_buffer); - // Velocity - double vel_mag = vec3_magnitude(body->velocity); - snprintf(temp_buffer, sizeof(temp_buffer), "Velocity: %.2f m/s", vel_mag); + snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->semi_major_axis); strcat(info_text, "\n"); strcat(info_text, temp_buffer); - } else { + + // Parent body + if (body->parent_index >= 0 && body->parent_index < sim->body_count) { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[body->parent_index].name); + } else { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); + } + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // SOI + snprintf(temp_buffer, sizeof(temp_buffer), "SOI radius: %.2e m", body->soi_radius); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Draw info text (inside window, leave space for title bar) + Rectangle text_bounds = { + (float)panel_x + 8, + (float)panel_y + 25, + (float)panel_width - 16, + (float)panel_height - 29 + }; + + GuiLabel(text_bounds, info_text); + return; + } + + // Check if a spacecraft is selected + if (render_state->selected_craft_index >= 0 && render_state->selected_craft_index < sim->craft_count) { + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + + // Draw window (no close button - panels always shown) + GuiWindowBox(panel_bounds, craft->name); + + // Prepare info text + char info_text[1024]; + char temp_buffer[256]; + + snprintf(info_text, sizeof(info_text), "Name: %s", craft->name); + + snprintf(temp_buffer, sizeof(temp_buffer), "Mass: %.2e kg", craft->mass); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + // Position (Local) snprintf(temp_buffer, sizeof(temp_buffer), "local_position: (%.2e, %.2e, %.2e)", - body->local_position.x, body->local_position.y, body->local_position.z); + craft->local_position.x, craft->local_position.y, craft->local_position.z); strcat(info_text, "\n"); strcat(info_text, temp_buffer); // Velocity (Local) - double parent_vel_mag = vec3_magnitude(sim->bodies[body->parent_index].velocity); - double vel_mag = vec3_magnitude(body->velocity) - parent_vel_mag; + double vel_mag = vec3_magnitude(craft->local_velocity); snprintf(temp_buffer, sizeof(temp_buffer), "local_velocity: %.2f m/s", vel_mag); strcat(info_text, "\n"); strcat(info_text, temp_buffer); + + // Parent body + if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[craft->parent_index].name); + } else { + snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); + } + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Maneuver info + int pending_maneuvers = 0; + int executed_maneuvers = 0; + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (sim->maneuvers[i].executed) { + executed_maneuvers++; + } else { + pending_maneuvers++; + } + } + } + + snprintf(temp_buffer, sizeof(temp_buffer), "Pending maneuvers: %d", pending_maneuvers); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + snprintf(temp_buffer, sizeof(temp_buffer), "Executed maneuvers: %d", executed_maneuvers); + strcat(info_text, "\n"); + strcat(info_text, temp_buffer); + + // Draw info text (inside window, leave space for title bar) + Rectangle text_bounds = { + (float)panel_x + 8, + (float)panel_y + 25, + (float)panel_width - 16, + (float)panel_height - 29 + }; + + GuiLabel(text_bounds, info_text); + return; } +} - // Orbital elements - snprintf(temp_buffer, sizeof(temp_buffer), "Eccentricity: %.3f", body->eccentricity); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); +// Render maneuver list UI panel +void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state) { + if (render_state->selected_craft_index < 0 || render_state->selected_craft_index >= sim->craft_count) { + return; // No spacecraft selected + } - snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->semi_major_axis); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); + int panel_width = 300; + int panel_height = 400; + int panel_x = GetScreenWidth() - panel_width - 10; + int panel_y = 320; // Below info panel - // Parent body - if (body->parent_index >= 0 && body->parent_index < sim->body_count) { - snprintf(temp_buffer, sizeof(temp_buffer), "Parent: %s", sim->bodies[body->parent_index].name); - } else { - snprintf(temp_buffer, sizeof(temp_buffer), "Parent: None"); + Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height }; + + Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; + + // Create title with spacecraft name + char title[128]; + snprintf(title, sizeof(title), "Maneuvers: %s", craft->name); + + // Draw window + GuiWindowBox(panel_bounds, title); + + // Count maneuvers for this spacecraft + int craft_maneuver_count = 0; + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + craft_maneuver_count++; + } } - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); - // SOI - snprintf(temp_buffer, sizeof(temp_buffer), "SOI radius: %.2e m", body->soi_radius); - strcat(info_text, "\n"); - strcat(info_text, temp_buffer); + if (craft_maneuver_count == 0) { + // No maneuvers for this spacecraft + Rectangle text_bounds = { + (float)panel_x + 8, + (float)panel_y + 25, + (float)panel_width - 16, + (float)panel_height - 29 + }; + GuiLabel(text_bounds, "No maneuvers defined"); + return; + } - // Draw info text (inside window, leave space for title bar) - Rectangle text_bounds = { - (float)panel_x + 8, + // Create maneuver list string for raygui + int buffer_size = craft_maneuver_count * (64 + 10 + 1) + 1; + char* maneuver_list_text = (char*)malloc(buffer_size); + if (!maneuver_list_text) return; + + maneuver_list_text[0] = '\0'; + int offset = 0; + + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (offset > 0) { + offset += snprintf(maneuver_list_text + offset, buffer_size - offset, ";"); + } + + const char* status_char = sim->maneuvers[i].executed ? "[DONE]" : "[PENDING]"; + offset += snprintf(maneuver_list_text + offset, buffer_size - offset, "%s %s", + status_char, sim->maneuvers[i].name); + } + } + + // Draw list view (inside window, leave space for title bar) + Rectangle list_bounds = { + (float)panel_x + 4, (float)panel_y + 25, - (float)panel_width - 16, - (float)panel_height - 29 + (float)panel_width - 8, + (float)panel_height - 100 // Leave space for details below }; - GuiLabel(text_bounds, info_text); + int maneuver_scroll = 0; + int maneuver_active = -1; + GuiListView(list_bounds, maneuver_list_text, &maneuver_scroll, &maneuver_active); + + // Show details for first pending or last executed maneuver + char details_text[512]; + details_text[0] = '\0'; + + for (int i = 0; i < sim->maneuver_count; i++) { + if (sim->maneuvers[i].craft_index == render_state->selected_craft_index) { + if (sim->maneuvers[i].executed) { + snprintf(details_text, sizeof(details_text), + "Last executed:\n%s\nĪ”v: %.1f m/s\nTime: %.1f s", + sim->maneuvers[i].name, sim->maneuvers[i].delta_v, sim->maneuvers[i].executed_time); + } else { + snprintf(details_text, sizeof(details_text), + "Next pending:\n%s\nĪ”v: %.1f m/s", + sim->maneuvers[i].name, sim->maneuvers[i].delta_v); + break; // Show first pending + } + } + } + + if (details_text[0] != '\0') { + Rectangle details_bounds = { + (float)panel_x + 8, + (float)panel_y + (float)panel_height - 70, + (float)panel_width - 16, + 65 + }; + GuiLabel(details_bounds, details_text); + } + + free(maneuver_list_text); } diff --git a/src/renderer.h b/src/renderer.h index 2037e28..4dddf28 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -2,6 +2,8 @@ #define RENDERER_H #include "simulation.h" +#include "spacecraft.h" +#include "maneuver.h" #include "raylib.h" // Rendering state @@ -10,6 +12,7 @@ struct RenderState { double distance_scale; // Scale factor for distances double size_scale; // Scale factor for body sizes int selected_body_index; // -1 = no selection + int selected_craft_index; // -1 = no selection int previous_selected_body; // Previous selected body index int body_list_scroll; // Scroll position for body list int body_list_active; // Active item index in body list @@ -28,12 +31,15 @@ void update_camera(RenderState* render_state, SimulationState* sim); // Rendering functions void render_body(CelestialBody* body, RenderState* render_state); +void render_spacecraft(Spacecraft* craft, RenderState* render_state); +void render_maneuver_marker(Spacecraft* craft, Maneuver* maneuver, RenderState* render_state); void render_simulation(SimulationState* sim, RenderState* render_state); void render_info(SimulationState* sim); // UI rendering functions void render_body_list_ui(SimulationState* sim, RenderState* render_state); void render_body_info_ui(SimulationState* sim, RenderState* render_state); +void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state); // Scaling functions Vector3 scale_position(Vec3 pos, double scale);