Browse Source

Extract UI rendering code into separate ui_renderer module

- Create ui_renderer.cpp/h with all raygui UI panel rendering functions
- Remove UI rendering code from renderer.cpp (421 lines moved)
- Remove UI state fields from RenderState, create separate UIState struct
- Remove raygui dependency from renderer module
- Update main.cpp to initialize UIState and call UI render functions
- Update documentation to reflect new module structure
- Improve separation of concerns between 3D rendering and UI overlays
main
cinnaboot 6 months ago
parent
commit
a16863607b
  1. 26
      docs/rendering.md
  2. 19
      docs/technical_reference.md
  3. 19
      src/main.cpp
  4. 421
      src/renderer.cpp
  5. 8
      src/renderer.h
  6. 421
      src/ui_renderer.cpp
  7. 20
      src/ui_renderer.h

26
docs/rendering.md

@ -177,7 +177,7 @@ float scale_radius(double radius, double scale) {
4. Spacecraft
5. Maneuver markers
## UI System (raygui)
## UI System (ui_renderer + raygui)
### Info Panel (Bottom-Left)
**Content:**
@ -258,29 +258,35 @@ float scale_radius(double radius, double scale) {
## Module Functions
### Initialization
### Renderer Module (renderer.cpp/h)
#### Initialization
- `init_renderer(width, height, title)` - Setup raylib window
- `close_renderer()` - Cleanup raylib
- `setup_camera(render_state)` - Initialize camera parameters
### Camera
#### Camera
- `update_camera(render_state, sim)` - Handle input and camera follow
### Rendering
#### Rendering
- `render_body(body, render_state)` - Draw single celestial body
- `render_spacecraft(craft, render_state)` - Draw spacecraft marker
- `render_maneuver_marker(craft, maneuver, render_state)` - Draw burn indicator
- `render_simulation(sim, render_state)` - Full scene rendering
- `render_simulation(sim, render_state)` - Full scene rendering (3D only)
#### Scaling
- `scale_position(pos, scale)` - Transform simulation to render position
- `scale_radius(radius, scale)` - Apply size scaling with minimum
### UI
### UI Renderer Module (ui_renderer.cpp/h)
#### UI Rendering
- `render_info(sim)` - Bottom-left info panel
- `render_body_list_ui(sim, render_state)` - Objects selection panel
- `render_body_list_ui(sim, render_state, ui_state)` - Objects selection panel
- `render_body_info_ui(sim, render_state)` - Top-right info panel
- `render_maneuver_list_ui(sim, render_state)` - Maneuver list panel
### Scaling
- `scale_position(pos, scale)` - Transform simulation to render position
- `scale_radius(radius, scale)` - Apply size scaling with minimum
Note: UI rendering functions are called after `render_simulation()` to ensure UI overlays appear on top of the 3D scene.
## Technical Notes

19
docs/technical_reference.md

@ -307,9 +307,26 @@ Raylib 3D visualization system. See **[docs/rendering.md](rendering.md)** for co
- Camera controls and follow system
- Object rendering (bodies, spacecraft, maneuvers)
- Orbit path rendering (elliptical, parabolic, hyperbolic)
- UI panels (info, body list, maneuver list)
- 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 controls
- `render_body_list_ui()` - Top-left objects list panel for selecting bodies/spacecraft
- `render_body_info_ui()` - Top-right panel showing selected object details
- `render_maneuver_list_ui()` - Maneuver list panel for spacecraft planning
**UI State:**
- `body_list_scroll` - Scroll position for objects list
- `body_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.

19
src/main.cpp

@ -2,6 +2,7 @@
#include "simulation.h"
#include "config_loader.h"
#include "renderer.h"
#include "ui_renderer.h"
#include <cstdio>
#include <cstring>
#include <cstdlib>
@ -36,6 +37,10 @@ void run_gui_simulation(SimulationState* sim) {
RenderState render_state;
setup_camera(&render_state);
UIState ui_state;
ui_state.body_list_scroll = 0;
ui_state.body_list_active = -1;
// Find root body (body with parent_index == -1)
int root_body_index = -1;
for (int i = 0; i < sim->body_count; i++) {
@ -45,16 +50,18 @@ void run_gui_simulation(SimulationState* sim) {
}
}
// Initialize UI state
// Initialize render 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
render_state.camera_follow_body = root_body_index >= 0; // Follow root body if found
render_state.camera_offset = (Vector3){ 0, 50, 100 }; // Default offset
render_state.was_following_body = false;
// Initialize UI state
ui_state.body_list_scroll = 0; // Initial scroll position
ui_state.body_list_active = root_body_index >= 0 ? root_body_index : -1; // Sync with selected body
bool paused = false;
double speed_multiplier = 1.0;
int physics_steps_per_frame = 100;
@ -94,6 +101,12 @@ void run_gui_simulation(SimulationState* sim) {
update_camera(&render_state, sim);
render_simulation(sim, &render_state);
// Render UI panels
render_info(sim);
render_body_list_ui(sim, &render_state, &ui_state);
render_body_info_ui(sim, &render_state);
render_maneuver_list_ui(sim, &render_state);
}
close_renderer(&render_state);

421
src/renderer.cpp

@ -3,10 +3,6 @@
#include <cmath>
#include <cstdio>
// raygui implementation (header-only library)
#define RAYGUI_IMPLEMENTATION
#include "raygui.h"
// Forward declarations
static Vector3 sim_to_render(Vec3 pos, double scale);
@ -553,422 +549,5 @@ void render_simulation(SimulationState* sim, RenderState* 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();
}
// Render simulation information overlay
void render_info(SimulationState* sim) {
// Panel dimensions
int panel_width = 300;
int panel_height = 300;
int panel_x = 10;
int panel_y = GetScreenHeight() - panel_height - 10;
Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height };
// Draw window
if (GuiWindowBox(panel_bounds, "Info")) {
// Window box close button doesn't close this panel
}
// Prepare info text
char info_text[1024];
char temp_buffer[512];
snprintf(info_text, sizeof(info_text), "Orbital Mechanics Simulation\n\n");
// Config name
if (sim->config_name[0] != '\0') {
snprintf(temp_buffer, sizeof(temp_buffer), "Config: %s\n\n", sim->config_name);
strcat(info_text, temp_buffer);
}
// Simulation time (in days)
double days = sim->time / 86400.0; // seconds to days
snprintf(temp_buffer, sizeof(temp_buffer), "Time: %.2f days\n", days);
strcat(info_text, temp_buffer);
// Body count
snprintf(temp_buffer, sizeof(temp_buffer), "Bodies: %d\n", sim->body_count);
strcat(info_text, temp_buffer);
// FPS
snprintf(temp_buffer, sizeof(temp_buffer), "FPS: %d\n\n", GetFPS());
strcat(info_text, temp_buffer);
// Controls
strcat(info_text, "Controls:\n");
strcat(info_text, " Arrows: Rotate/Zoom\n");
strcat(info_text, " Space: Pause/Resume\n");
strcat(info_text, " +/-: Speed control\n");
strcat(info_text, " ESC: Quit\n");
// 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);
}
// Render body list UI panel
void render_body_list_ui(SimulationState* sim, RenderState* render_state) {
// Panel dimensions
int panel_width = 200;
int panel_height = 400;
int panel_x = 10;
int panel_y = 10;
Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height };
// 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;
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 (item_index > 0) {
offset += snprintf(list_text + offset, buffer_size - offset, ";");
}
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, "Objects");
// Draw list view (inside window, leave space for title bar)
Rectangle list_bounds = {
(float)panel_x + 4,
(float)panel_y + 25,
(float)panel_width - 8,
(float)panel_height - 29
};
int previous_active = render_state->body_list_active;
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(list_text);
}
// Render body information UI panel
void render_body_info_ui(SimulationState* sim, RenderState* render_state) {
// 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 };
// 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);
// Prepare info text
char info_text[1024];
char temp_buffer[256];
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), "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);
}
// Orbital elements
snprintf(temp_buffer, sizeof(temp_buffer), "Eccentricity: %.3f", body->eccentricity);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->semi_major_axis);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
// 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)",
craft->local_position.x, craft->local_position.y, craft->local_position.z);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
// Velocity (Local)
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;
}
}
// 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
}
int panel_width = 300;
int panel_height = 400;
int panel_x = GetScreenWidth() - panel_width - 10;
int panel_y = 320; // Below info panel
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++;
}
}
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;
}
// 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 - 8,
(float)panel_height - 100 // Leave space for details below
};
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);
}

8
src/renderer.h

@ -14,8 +14,6 @@ struct RenderState {
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
bool camera_follow_body; // Whether camera follows selected body
Vector3 camera_offset; // Offset from target when following body
bool was_following_body; // Previous frame follow state
@ -48,12 +46,6 @@ 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);

421
src/ui_renderer.cpp

@ -0,0 +1,421 @@
#include "ui_renderer.h"
#include "spacecraft.h"
#include "maneuver.h"
#include <cstdio>
#include <cstring>
#include <cmath>
// raygui implementation (header-only library)
#define RAYGUI_IMPLEMENTATION
#include "raygui.h"
// Render simulation information overlay
void render_info(SimulationState* sim) {
// Panel dimensions
int panel_width = 300;
int panel_height = 300;
int panel_x = 10;
int panel_y = GetScreenHeight() - panel_height - 10;
Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height };
// Draw window
if (GuiWindowBox(panel_bounds, "Info")) {
// Window box close button doesn't close this panel
}
// Prepare info text
char info_text[1024];
char temp_buffer[512];
snprintf(info_text, sizeof(info_text), "Orbital Mechanics Simulation\n\n");
// Config name
if (sim->config_name[0] != '\0') {
snprintf(temp_buffer, sizeof(temp_buffer), "Config: %s\n\n", sim->config_name);
strcat(info_text, temp_buffer);
}
// Simulation time (in days)
double days = sim->time / 86400.0; // seconds to days
snprintf(temp_buffer, sizeof(temp_buffer), "Time: %.2f days\n", days);
strcat(info_text, temp_buffer);
// Body count
snprintf(temp_buffer, sizeof(temp_buffer), "Bodies: %d\n", sim->body_count);
strcat(info_text, temp_buffer);
// FPS
snprintf(temp_buffer, sizeof(temp_buffer), "FPS: %d\n\n", GetFPS());
strcat(info_text, temp_buffer);
// Controls
strcat(info_text, "Controls:\n");
strcat(info_text, " Arrows: Rotate/Zoom\n");
strcat(info_text, " Space: Pause/Resume\n");
strcat(info_text, " +/-: Speed control\n");
strcat(info_text, " ESC: Quit\n");
// 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);
}
// Render body list UI panel
void render_body_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state) {
// Panel dimensions
int panel_width = 200;
int panel_height = 400;
int panel_x = 10;
int panel_y = 10;
Rectangle panel_bounds = { (float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height };
// 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;
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 (item_index > 0) {
offset += snprintf(list_text + offset, buffer_size - offset, ";");
}
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, "Objects");
// Draw list view (inside window, leave space for title bar)
Rectangle list_bounds = {
(float)panel_x + 4,
(float)panel_y + 25,
(float)panel_width - 8,
(float)panel_height - 29
};
int previous_active = ui_state->body_list_active;
GuiListView(list_bounds, list_text, &ui_state->body_list_scroll, &ui_state->body_list_active);
// Check if an item was selected
if (ui_state->body_list_active >= 0 && ui_state->body_list_active != previous_active) {
if (ui_state->body_list_active < sim->body_count) {
// A body was selected
render_state->selected_body_index = ui_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 = ui_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(list_text);
}
// Render body information UI panel
void render_body_info_ui(SimulationState* sim, RenderState* render_state) {
// 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 };
// 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);
// Prepare info text
char info_text[1024];
char temp_buffer[256];
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), "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);
}
// Orbital elements
snprintf(temp_buffer, sizeof(temp_buffer), "Eccentricity: %.3f", body->eccentricity);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->semi_major_axis);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
// 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)",
craft->local_position.x, craft->local_position.y, craft->local_position.z);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
// Velocity (Local)
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;
}
}
// 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
}
int panel_width = 300;
int panel_height = 400;
int panel_x = GetScreenWidth() - panel_width - 10;
int panel_y = 320; // Below info panel
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++;
}
}
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;
}
// 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 - 8,
(float)panel_height - 100 // Leave space for details below
};
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);
}

20
src/ui_renderer.h

@ -0,0 +1,20 @@
#ifndef UI_RENDERER_H
#define UI_RENDERER_H
#include "simulation.h"
#include "renderer.h"
#include "raylib.h"
// UI state
struct UIState {
int body_list_scroll; // Scroll position for body list
int body_list_active; // Active item index in body list
};
// UI rendering functions
void render_info(SimulationState* sim);
void render_body_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state);
void render_body_info_ui(SimulationState* sim, RenderState* render_state);
void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state);
#endif
Loading…
Cancel
Save