vibe coding an orbital mechanics simulation to try out claude code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1488 lines
60 KiB

#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"
#include "amber/style_amber.h"
// UI Panel constants
#define INFO_PANEL_WIDTH 300
#define INFO_PANEL_HEIGHT 350
#define BODY_LIST_WIDTH 200
#define BODY_LIST_HEIGHT 400
#define BODY_INFO_WIDTH 250
#define BODY_INFO_HEIGHT 300
#define MANEUVER_LIST_WIDTH 300
#define MANEUVER_LIST_HEIGHT 450
#define MANEUVER_LIST_Y_OFFSET 320
static inline bool needs_body_list_rebuild(UIState* ui_state, SimulationState* sim) {
return (ui_state->cached_body_count != sim->body_count ||
ui_state->cached_craft_count != sim->craft_count ||
ui_state->body_list_buffer == NULL);
}
static inline bool needs_maneuver_list_rebuild(UIState* ui_state, SimulationState* sim, int current_executed_count) {
return (ui_state->cached_maneuver_count != sim->maneuver_count ||
ui_state->cached_executed_count != current_executed_count ||
ui_state->maneuver_list_buffer == NULL);
}
static inline bool needs_hohmann_rebuild(UIState* ui_state, SimulationState* sim) {
return (ui_state->cached_body_count != sim->body_count ||
ui_state->cached_craft_count != sim->craft_count ||
ui_state->craft_list_buffer == NULL ||
ui_state->body_dropdown_buffer == NULL);
}
void gui_init() {
GuiLoadStyleAmber();
}
// Render simulation information overlay
void render_info(SimulationState* sim) {
int panel_width = INFO_PANEL_WIDTH;
int panel_height = INFO_PANEL_HEIGHT;
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) {
int panel_width = BODY_LIST_WIDTH;
int panel_height = BODY_LIST_HEIGHT;
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;
// Check if buffer needs rebuilding
bool needs_rebuild = needs_body_list_rebuild(ui_state, sim);
if (needs_rebuild) {
int buffer_size = total_items * (64 + 3 + 1) + 1;
if (ui_state->body_list_buffer == NULL || ui_state->body_list_buffer_size < buffer_size) {
if (ui_state->body_list_buffer != NULL) {
free(ui_state->body_list_buffer);
}
ui_state->body_list_buffer = (char*)malloc(buffer_size);
if (!ui_state->body_list_buffer) return;
ui_state->body_list_buffer_size = buffer_size;
}
ui_state->body_list_buffer[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(ui_state->body_list_buffer + offset, ui_state->body_list_buffer_size - offset, ";");
}
offset += snprintf(ui_state->body_list_buffer + offset, ui_state->body_list_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(ui_state->body_list_buffer + offset, ui_state->body_list_buffer_size - offset, ";");
offset += snprintf(ui_state->body_list_buffer + offset, ui_state->body_list_buffer_size - offset, "🚀 %s", sim->spacecraft[i].name);
item_index++;
}
ui_state->cached_body_count = sim->body_count;
ui_state->cached_craft_count = sim->craft_count;
}
// 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, ui_state->body_list_buffer, &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) {
render_state->selected_body_index = ui_state->body_list_active;
ui_state->selected_craft_index = -1;
render_state->camera_target_enabled = true;
printf("Camera follow enabled for body: %s\n", sim->bodies[render_state->selected_body_index].name);
} else {
int craft_index = ui_state->body_list_active - sim->body_count;
if (craft_index >= 0 && craft_index < sim->craft_count) {
Spacecraft* craft = &sim->spacecraft[craft_index];
render_state->selected_body_index = craft->parent_index;
ui_state->selected_craft_index = craft_index;
render_state->camera_target_enabled = true;
printf("Camera follow enabled for spacecraft: %s\n", craft->name);
}
}
}
}
// Render body information UI panel
void render_body_info_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state) {
int panel_width = BODY_INFO_WIDTH;
int panel_height = BODY_INFO_HEIGHT;
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) &&
(ui_state->selected_craft_index < 0 || ui_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->global_position.x, body->global_position.y, body->global_position.z);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
// Velocity
double vel_mag = vec3_magnitude(body->global_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].global_velocity);
double vel_mag = vec3_magnitude(body->global_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->orbit.eccentricity);
strcat(info_text, "\n");
strcat(info_text, temp_buffer);
snprintf(temp_buffer, sizeof(temp_buffer), "Semi-major axis: %.2e m", body->orbit.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 (ui_state->selected_craft_index >= 0 && ui_state->selected_craft_index < sim->craft_count) {
Spacecraft* craft = &sim->spacecraft[ui_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 == ui_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;
}
}
void render_maneuver_list_ui(SimulationState* sim, RenderState* render_state, UIState* ui_state) {
if (ui_state->selected_craft_index < 0 || ui_state->selected_craft_index >= sim->craft_count) {
return;
}
int panel_width = MANEUVER_LIST_WIDTH;
int panel_height = MANEUVER_LIST_HEIGHT;
int panel_x = GetScreenWidth() - panel_width - 10;
int panel_y = MANEUVER_LIST_Y_OFFSET;
Rectangle panel_bounds = {(float)panel_x, (float)panel_y, (float)panel_width, (float)panel_height};
Spacecraft* craft = &sim->spacecraft[ui_state->selected_craft_index];
char title[128];
snprintf(title, sizeof(title), "Maneuvers: %s", craft->name);
GuiWindowBox(panel_bounds, title);
int craft_maneuver_count = 0;
int current_executed_count = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == ui_state->selected_craft_index) {
craft_maneuver_count++;
if (sim->maneuvers[i].executed) {
current_executed_count++;
}
}
}
if (craft_maneuver_count == 0) {
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");
Rectangle create_btn = {(float)panel_x + 8, (float)panel_y + (float)panel_height - 28, (float)panel_width - 16, 20};
if (GuiButton(create_btn, "[+] Create Maneuver")) {
ui_state->maneuver_dialog.open = true;
ui_state->maneuver_dialog.active_tab = MD_TAB_CREATE;
ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index;
ui_state->maneuver_dialog.edit_maneuver_index = -1;
ui_state->maneuver_dialog.direction_active = 0;
ui_state->maneuver_dialog.delta_v = 1000.0;
ui_state->maneuver_dialog.trigger_type_active = 0;
ui_state->maneuver_dialog.trigger_value = 0.0;
ui_state->maneuver_dialog.name[0] = '\0';
ui_state->maneuver_dialog.show_preview = false;
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
ui_state->maneuver_dialog.show_delete_confirm = false;
ui_state->selected_maneuver_index = -1;
}
return;
}
int maneuver_indices[craft_maneuver_count];
int maneuver_index_count = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == ui_state->selected_craft_index) {
maneuver_indices[maneuver_index_count++] = i;
}
}
// Check if buffer needs rebuilding
bool needs_rebuild = needs_maneuver_list_rebuild(ui_state, sim, current_executed_count);
if (needs_rebuild) {
int buffer_size = craft_maneuver_count * (64 + 10 + 1) + 1;
if (ui_state->maneuver_list_buffer == NULL || ui_state->maneuver_list_buffer_size < buffer_size) {
if (ui_state->maneuver_list_buffer != NULL) {
free(ui_state->maneuver_list_buffer);
}
ui_state->maneuver_list_buffer = (char*)malloc(buffer_size);
if (!ui_state->maneuver_list_buffer) return;
ui_state->maneuver_list_buffer_size = buffer_size;
}
ui_state->maneuver_list_buffer[0] = '\0';
int offset = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == ui_state->selected_craft_index) {
if (offset > 0) {
offset += snprintf(ui_state->maneuver_list_buffer + offset, ui_state->maneuver_list_buffer_size - offset, ";");
}
const char* status_char = sim->maneuvers[i].executed ? "[DONE]" : "[PENDING]";
offset += snprintf(ui_state->maneuver_list_buffer + offset, ui_state->maneuver_list_buffer_size - offset, "%s %s",
status_char, sim->maneuvers[i].name);
}
}
ui_state->cached_maneuver_count = sim->maneuver_count;
ui_state->cached_executed_count = current_executed_count;
}
Rectangle list_bounds = {
(float)panel_x + 4,
(float)panel_y + 25,
(float)panel_width - 8,
(float)panel_height - 280
};
int previous_active = ui_state->maneuver_list_active;
GuiListView(list_bounds, ui_state->maneuver_list_buffer, &ui_state->maneuver_list_scroll, &ui_state->maneuver_list_active);
if (ui_state->maneuver_list_active >= 0 && ui_state->maneuver_list_active != previous_active) {
if (ui_state->maneuver_list_active >= 0 && ui_state->maneuver_list_active < maneuver_index_count) {
ui_state->selected_maneuver_index = maneuver_indices[ui_state->maneuver_list_active];
}
}
int preview_y = panel_y + panel_height - 250;
if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) {
Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index];
if (selected->craft_index == ui_state->selected_craft_index) {
char selected_text[512];
char status_str[16];
snprintf(status_str, sizeof(status_str), selected->executed ? "Executed" : "Pending");
snprintf(selected_text, sizeof(selected_text),
"Selected: %s\n\nStatus: %s\nDirection: %s\nΔv: %.1f m/s\n",
selected->name, status_str, get_burn_direction_name(selected->direction), selected->delta_v);
if (selected->trigger_type == TRIGGER_TIME) {
snprintf(selected_text + strlen(selected_text), sizeof(selected_text) - strlen(selected_text),
"Trigger: Time @ %.1f s", selected->trigger_value);
} else {
snprintf(selected_text + strlen(selected_text), sizeof(selected_text) - strlen(selected_text),
"Trigger: True Anomaly @ %.2f rad", selected->trigger_value);
}
Rectangle preview_text_bounds = {
(float)panel_x + 8,
(float)preview_y,
(float)panel_width - 16,
80
};
GuiLabel(preview_text_bounds, selected_text);
int orbital_preview_y = preview_y + 85;
ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index;
ui_state->maneuver_dialog.direction_active = (int)selected->direction;
ui_state->maneuver_dialog.delta_v = selected->delta_v;
update_orbital_preview(sim, ui_state);
if (ui_state->maneuver_dialog.show_preview) {
Rectangle orbital_bounds = {
(float)panel_x + 8,
(float)orbital_preview_y,
(float)panel_width - 16,
110
};
render_orbital_preview(sim, ui_state, orbital_bounds);
}
}
}
int button_row_y = panel_y + panel_height - 50;
int btn_width = (panel_width - 20) / 4;
Rectangle create_btn = {(float)panel_x + 10, (float)button_row_y, (float)btn_width, 30};
if (GuiButton(create_btn, "Create")) {
ui_state->maneuver_dialog.open = true;
ui_state->maneuver_dialog.active_tab = MD_TAB_CREATE;
ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index;
ui_state->maneuver_dialog.edit_maneuver_index = -1;
ui_state->maneuver_dialog.direction_active = 0;
ui_state->maneuver_dialog.delta_v = 1000.0;
ui_state->maneuver_dialog.trigger_type_active = 0;
ui_state->maneuver_dialog.trigger_value = 0.0;
ui_state->maneuver_dialog.name[0] = '\0';
ui_state->maneuver_dialog.show_preview = false;
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
ui_state->maneuver_dialog.show_delete_confirm = false;
ui_state->selected_maneuver_index = -1;
}
Rectangle hohmann_btn = {(float)panel_x + 10 + btn_width, (float)button_row_y, (float)btn_width, 30};
if (GuiButton(hohmann_btn, "Hohmann...")) {
ui_state->maneuver_dialog.open = true;
ui_state->maneuver_dialog.active_tab = MD_TAB_HOHMANN;
ui_state->maneuver_dialog.craft_index = ui_state->selected_craft_index;
ui_state->maneuver_dialog.edit_maneuver_index = -1;
ui_state->maneuver_dialog.name[0] = '\0';
ui_state->maneuver_dialog.show_preview = false;
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
ui_state->maneuver_dialog.show_delete_confirm = false;
}
Rectangle edit_btn = {(float)panel_x + 10 + btn_width * 2, (float)button_row_y, (float)btn_width, 30};
if (GuiButton(edit_btn, "Edit")) {
if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) {
Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index];
if (selected->craft_index == ui_state->selected_craft_index) {
load_maneuver_into_dialog(sim, ui_state, ui_state->selected_maneuver_index);
ui_state->maneuver_dialog.open = true;
ui_state->maneuver_dialog.active_tab = MD_TAB_EDIT;
ui_state->maneuver_dialog.show_delete_confirm = false;
}
}
}
Rectangle delete_btn = {(float)panel_x + 10 + btn_width * 3, (float)button_row_y, (float)btn_width, 30};
if (GuiButton(delete_btn, "Delete")) {
if (ui_state->selected_maneuver_index >= 0 && ui_state->selected_maneuver_index < sim->maneuver_count) {
Maneuver* selected = &sim->maneuvers[ui_state->selected_maneuver_index];
if (selected->craft_index == ui_state->selected_craft_index) {
remove_maneuver_by_index(sim, ui_state->selected_maneuver_index);
ui_state->selected_maneuver_index = -1;
ui_state->maneuver_list_active = -1;
}
}
}
}
void render_maneuver_create_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height) {
int current_y = y;
Rectangle craft_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(craft_label, "Spacecraft:");
current_y += 35;
Rectangle name_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(name_label, "Name:");
Rectangle name_text = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
GuiTextBox(name_text, ui_state->maneuver_dialog.name, 64, false);
current_y += 35;
Rectangle dir_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(dir_label, "Direction:");
if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) {
static bool edit_mode_01 = false;
Rectangle dir_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
const char* directions = "Prograde;Retrograde;Normal;Antinormal;Radial In;Radial Out;Custom";
if (GuiDropdownBox(dir_combo, directions, &ui_state->maneuver_dialog.direction_active, edit_mode_01)) {
edit_mode_01 = !edit_mode_01;
}
if (edit_mode_01) return;
}
current_y += 35;
Rectangle dv_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(dv_label, "Delta-V:");
Rectangle dv_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20};
float dv_float = (float)ui_state->maneuver_dialog.delta_v;
GuiSlider(dv_slider, "0", "50000", &dv_float, 0, 50000);
ui_state->maneuver_dialog.delta_v = dv_float;
char dv_text[32];
snprintf(dv_text, sizeof(dv_text), "%.1f m/s", ui_state->maneuver_dialog.delta_v);
Rectangle dv_value = {(float)x + width - 90, (float)current_y, 85, 25};
GuiLabel(dv_value, dv_text);
current_y += 35;
Rectangle trigger_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(trigger_label, "Trigger:");
if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) {
static bool edit_mode_trigger_create = false;
Rectangle trigger_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
const char* trigger_types = "Time;True Anomaly";
if (GuiDropdownBox(trigger_combo, trigger_types, &ui_state->maneuver_dialog.trigger_type_active, edit_mode_trigger_create)) {
edit_mode_trigger_create = !edit_mode_trigger_create;
}
if (edit_mode_trigger_create) return;
}
current_y += 35;
Rectangle value_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(value_label, "Value:");
Rectangle value_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20};
float value_float = (float)ui_state->maneuver_dialog.trigger_value;
if (ui_state->maneuver_dialog.trigger_type_active == 0) {
GuiSlider(value_slider, "0", "1000000", &value_float, 0, 1000000);
} else {
GuiSlider(value_slider, "0", "6.28", &value_float, 0, 2 * M_PI);
}
ui_state->maneuver_dialog.trigger_value = value_float;
char value_text[32];
if (ui_state->maneuver_dialog.trigger_type_active == 0) {
snprintf(value_text, sizeof(value_text), "%.1f s", ui_state->maneuver_dialog.trigger_value);
} else {
snprintf(value_text, sizeof(value_text), "%.2f rad", ui_state->maneuver_dialog.trigger_value);
}
Rectangle value_text_label = {(float)x + width - 90, (float)current_y, 85, 25};
GuiLabel(value_text_label, value_text);
update_orbital_preview(sim, ui_state);
if (ui_state->maneuver_dialog.show_preview) {
Rectangle preview_bounds = {(float)x + 8, (float)current_y + 10, (float)width - 16, 110};
render_orbital_preview(sim, ui_state, preview_bounds);
}
}
void update_orbital_preview(SimulationState* sim, UIState* ui_state) {
if (ui_state->maneuver_dialog.craft_index < 0 ||
ui_state->maneuver_dialog.craft_index >= sim->craft_count) {
ui_state->maneuver_dialog.show_preview = false;
return;
}
Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index];
if (craft->parent_index < 0 || craft->parent_index >= sim->body_count) {
ui_state->maneuver_dialog.show_preview = false;
return;
}
CelestialBody* parent = &sim->bodies[craft->parent_index];
double parent_mass = parent->mass;
BurnDirection direction = (BurnDirection)ui_state->maneuver_dialog.direction_active;
double delta_v = ui_state->maneuver_dialog.delta_v;
ui_state->maneuver_dialog.preview_elements = preview_burn_result(craft, direction, delta_v, sim);
ui_state->maneuver_dialog.show_preview = true;
ui_state->maneuver_dialog.preview_valid = validate_burn_parameters(craft, direction, delta_v, parent_mass);
}
void render_orbital_preview(SimulationState* sim, UIState* ui_state, Rectangle bounds) {
if (!ui_state->maneuver_dialog.show_preview) {
return;
}
if (ui_state->maneuver_dialog.craft_index < 0 ||
ui_state->maneuver_dialog.craft_index >= sim->craft_count) {
return;
}
Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index];
OrbitalElements* current = &craft->orbit;
OrbitalElements* preview = &ui_state->maneuver_dialog.preview_elements;
double current_periapsis = current->semi_major_axis * (1.0 - current->eccentricity);
double current_apoapsis = current->semi_major_axis * (1.0 + current->eccentricity);
double preview_periapsis = preview->semi_major_axis * (1.0 - preview->eccentricity);
double preview_apoapsis = preview->semi_major_axis * (1.0 + preview->eccentricity);
double delta_periapsis = preview_periapsis - current_periapsis;
double delta_apoapsis = preview_apoapsis - current_apoapsis;
int y = (int)bounds.y;
int x = (int)bounds.x;
int width = (int)bounds.width;
char current_text[128];
snprintf(current_text, sizeof(current_text),
"Current: e=%.4f, a=%.3e m, ν=%.2f rad",
current->eccentricity, current->semi_major_axis, current->true_anomaly);
char preview_text[128];
snprintf(preview_text, sizeof(preview_text),
"Preview: e=%.4f, a=%.3e m, ν=%.2f rad",
preview->eccentricity, preview->semi_major_axis, preview->true_anomaly);
char delta_text[128];
snprintf(delta_text, sizeof(delta_text),
"Δperiapsis: %+.3e m Δapoapsis: %+.3e m",
delta_periapsis, delta_apoapsis);
Rectangle current_bounds = {(float)x, (float)y, (float)width, 25};
GuiLabel(current_bounds, current_text);
y += 25;
Rectangle preview_bounds = {(float)x, (float)y, (float)width, 25};
GuiLabel(preview_bounds, preview_text);
y += 25;
Rectangle delta_bounds = {(float)x, (float)y, (float)width, 25};
GuiLabel(delta_bounds, delta_text);
y += 30;
Rectangle status_bounds = {(float)x, (float)y, (float)width, 25};
const char* status_text = ui_state->maneuver_dialog.preview_valid ? "✓ All parameters valid" : "✗ Invalid parameters";
GuiLabel(status_bounds, status_text);
}
void render_maneuver_hohmann_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height) {
int current_y = y;
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
if (ui_state->maneuver_dialog.craft_index >= 0 &&
ui_state->maneuver_dialog.craft_index < sim->craft_count) {
Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index];
if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) {
ui_state->maneuver_dialog.current_body_index = craft->parent_index;
}
}
bool needs_rebuild = needs_hohmann_rebuild(ui_state, sim);
if (needs_rebuild) {
int craft_buffer_size = sim->craft_count * 64 + 1;
if (ui_state->craft_list_buffer == NULL || ui_state->craft_list_buffer_size < craft_buffer_size) {
if (ui_state->craft_list_buffer != NULL) {
free(ui_state->craft_list_buffer);
}
ui_state->craft_list_buffer = (char*)malloc(craft_buffer_size);
if (!ui_state->craft_list_buffer) return;
ui_state->craft_list_buffer_size = craft_buffer_size;
}
ui_state->craft_list_buffer[0] = '\0';
int offset = 0;
for (int i = 0; i < sim->craft_count; i++) {
if (i > 0) {
offset += snprintf(ui_state->craft_list_buffer + offset, ui_state->craft_list_buffer_size - offset, ";");
}
offset += snprintf(ui_state->craft_list_buffer + offset, ui_state->craft_list_buffer_size - offset, "%s", sim->spacecraft[i].name);
}
int body_buffer_size = sim->body_count * 64 + 1;
if (ui_state->body_dropdown_buffer == NULL || ui_state->body_dropdown_buffer_size < body_buffer_size) {
if (ui_state->body_dropdown_buffer != NULL) {
free(ui_state->body_dropdown_buffer);
}
ui_state->body_dropdown_buffer = (char*)malloc(body_buffer_size);
if (!ui_state->body_dropdown_buffer) return;
ui_state->body_dropdown_buffer_size = body_buffer_size;
}
ui_state->body_dropdown_buffer[0] = '\0';
offset = 0;
for (int i = 0; i < sim->body_count; i++) {
if (i > 0) {
offset += snprintf(ui_state->body_dropdown_buffer + offset, ui_state->body_dropdown_buffer_size - offset, ";");
}
offset += snprintf(ui_state->body_dropdown_buffer + offset, ui_state->body_dropdown_buffer_size - offset, "%s", sim->bodies[i].name);
}
ui_state->cached_body_count = sim->body_count;
ui_state->cached_craft_count = sim->craft_count;
}
GuiLabel({(float)x, (float)current_y, (float)width, 25}, "Calculate optimal Hohmann transfer between orbits");
current_y += 30;
GuiLabel({(float)x, (float)current_y, 120, 25}, "Spacecraft:");
current_y += 35;
if (sim->craft_count > 0) {
static bool edit_mode_spacecraft = false;
if (GuiDropdownBox({(float)x + 120, (float)(current_y - 35), (float)(width - 130), 25},
ui_state->craft_list_buffer, &ui_state->maneuver_dialog.craft_index, edit_mode_spacecraft)) {
edit_mode_spacecraft = !edit_mode_spacecraft;
}
if (edit_mode_spacecraft) return;
}
current_y += 35;
GuiLabel({(float)x, (float)current_y, 120, 25}, "Transfer Parent:");
current_y += 35;
if (sim->body_count > 0) {
static bool edit_mode_transfer_parent = false;
if (GuiDropdownBox({(float)x + 120, (float)(current_y - 35), (float)(width - 130), 25},
ui_state->body_dropdown_buffer, &ui_state->maneuver_dialog.transfer_body_index, edit_mode_transfer_parent)) {
edit_mode_transfer_parent = !edit_mode_transfer_parent;
}
if (edit_mode_transfer_parent) return;
}
current_y += 35;
GuiLabel({(float)x, (float)current_y, 120, 25}, "Current Body:");
current_y += 35;
if (sim->body_count > 0) {
static bool edit_mode_current_body = false;
if (GuiDropdownBox({(float)x + 120, (float)(current_y - 35), (float)(width - 130), 25},
ui_state->body_dropdown_buffer, &ui_state->maneuver_dialog.current_body_index, edit_mode_current_body)) {
edit_mode_current_body = !edit_mode_current_body;
}
if (edit_mode_current_body) return;
}
current_y += 35;
GuiLabel({(float)x, (float)current_y, 120, 25}, "Target Body:");
current_y += 35;
if (sim->body_count > 0) {
static bool edit_mode_target_body = false;
if (GuiDropdownBox({(float)x + 120, (float)(current_y - 35), (float)(width - 130), 25},
ui_state->body_dropdown_buffer, &ui_state->maneuver_dialog.target_body_index, edit_mode_target_body)) {
edit_mode_target_body = !edit_mode_target_body;
}
if (edit_mode_target_body) return;
}
current_y += 35;
Rectangle calculate_btn = {(float)x, (float)current_y, (float)width, 30};
if (GuiButton(calculate_btn, "Calculate Transfer")) {
calculate_hohmann_for_dialog(sim, ui_state);
}
current_y += 40;
if (ui_state->maneuver_dialog.show_error) {
Rectangle error_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(error_bounds, TextFormat("Error: %s", ui_state->maneuver_dialog.error_message));
current_y += 30;
}
if (ui_state->maneuver_dialog.show_hohmann_preview) {
HohmannTransfer* ht = &ui_state->maneuver_dialog.last_calc;
char transfer_info[256];
double current_r = (ui_state->maneuver_dialog.current_body_index >= 0 &&
ui_state->maneuver_dialog.current_body_index < sim->body_count) ?
sim->bodies[ui_state->maneuver_dialog.current_body_index].orbit.semi_major_axis : 0.0;
double target_r = (ui_state->maneuver_dialog.target_body_index >= 0 &&
ui_state->maneuver_dialog.target_body_index < sim->body_count) ?
sim->bodies[ui_state->maneuver_dialog.target_body_index].orbit.semi_major_axis : 0.0;
snprintf(transfer_info, sizeof(transfer_info),
"Transfer semi-major axis: %.3e m",
(current_r + target_r) / 2.0);
Rectangle info_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(info_bounds, transfer_info);
current_y += 25;
char time_info[128];
double days = ht->transfer_time / 86400.0;
snprintf(time_info, sizeof(time_info), "Transfer time: %.1f days", days);
Rectangle time_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(time_bounds, time_info);
current_y += 30;
char burn1_info[256];
snprintf(burn1_info, sizeof(burn1_info),
"Burn 1 (at periapsis): Δv1 = %.2f km/s, Prograde, ν = 0.00 rad",
ht->dv1 / 1000.0);
Rectangle burn1_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(burn1_bounds, burn1_info);
current_y += 25;
char burn2_info[256];
snprintf(burn2_info, sizeof(burn2_info),
"Burn 2 (at apoapsis): Δv2 = %.2f km/s, Prograde, ν = %.2f rad",
ht->dv2 / 1000.0, ht->true_anomaly_2);
Rectangle burn2_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(burn2_bounds, burn2_info);
current_y += 30;
char total_info[128];
snprintf(total_info, sizeof(total_info), "Total Δv: %.2f km/s", (ht->dv1 + ht->dv2) / 1000.0);
Rectangle total_bounds = {(float)x + 8, (float)current_y, (float)width - 16, 25};
GuiLabel(total_bounds, total_info);
current_y += 30;
Rectangle both_btn = {(float)x, (float)current_y, (float)(width / 2 - 5), 30};
Rectangle burn1_btn = {(float)x + (float)(width / 2 + 5), (float)current_y, (float)(width / 2 - 5), 30};
if (GuiButton(both_btn, "Create Both Burns")) {
create_hohmann_burns(sim, ui_state, true);
}
if (GuiButton(burn1_btn, "Create Burn 1 Only")) {
create_hohmann_burns(sim, ui_state, false);
}
}
}
void calculate_hohmann_for_dialog(SimulationState* sim, UIState* ui_state) {
ui_state->maneuver_dialog.show_hohmann_preview = false;
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
if (ui_state->maneuver_dialog.craft_index < 0 ||
ui_state->maneuver_dialog.craft_index >= sim->craft_count) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"No spacecraft selected");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (ui_state->maneuver_dialog.transfer_body_index < 0 ||
ui_state->maneuver_dialog.transfer_body_index >= sim->body_count) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Invalid transfer parent body selected");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (ui_state->maneuver_dialog.current_body_index < 0 ||
ui_state->maneuver_dialog.current_body_index >= sim->body_count) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Invalid current body selected");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (ui_state->maneuver_dialog.target_body_index < 0 ||
ui_state->maneuver_dialog.target_body_index >= sim->body_count) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Invalid target body selected");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (ui_state->maneuver_dialog.transfer_body_index == ui_state->maneuver_dialog.target_body_index) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Current and target bodies must be different");
ui_state->maneuver_dialog.show_error = true;
return;
}
Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index];
if (craft->parent_index != ui_state->maneuver_dialog.current_body_index) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Spacecraft is not orbiting the selected current body");
ui_state->maneuver_dialog.show_error = true;
return;
}
CelestialBody* transfer_body = &sim->bodies[ui_state->maneuver_dialog.transfer_body_index];
CelestialBody* current_body = &sim->bodies[ui_state->maneuver_dialog.current_body_index];
CelestialBody* target_body = &sim->bodies[ui_state->maneuver_dialog.target_body_index];
double r1 = current_body->orbit.semi_major_axis;
double r2 = target_body->orbit.semi_major_axis;
if (r1 <= 0) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Current body has invalid orbit (semi-major axis <= 0)");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (r2 <= 0) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Target body has invalid orbit (semi-major axis <= 0)");
ui_state->maneuver_dialog.show_error = true;
return;
}
if (transfer_body->mass <= 0) {
snprintf(ui_state->maneuver_dialog.error_message, sizeof(ui_state->maneuver_dialog.error_message),
"Transfer parent has invalid mass");
ui_state->maneuver_dialog.show_error = true;
return;
}
ui_state->maneuver_dialog.last_calc = calculate_hohmann_transfer(r1, r2, transfer_body->mass);
ui_state->maneuver_dialog.show_hohmann_preview = true;
}
void render_maneuver_dialog(SimulationState* sim, RenderState* render_state, UIState* ui_state) {
if (!ui_state->maneuver_dialog.open) {
return;
}
int width = 550;
int height = 480;
int x = (GetScreenWidth() - width) / 2;
int y = (GetScreenHeight() - height) / 2;
Rectangle dialog_bounds = {(float)x, (float)y, (float)width, (float)height};
if (GuiWindowBox(dialog_bounds, "Maneuver Management")) {
ui_state->maneuver_dialog.open = false;
return;
}
const char* tabs[] = {"Create Maneuver", "Hohmann Transfer", "Edit Maneuver"};
int tab_count = 3;
Rectangle tab_bounds = {x + 8, y + 30, width - 16, 25};
GuiTabBar(tab_bounds, tabs, tab_count, (int*)&ui_state->maneuver_dialog.active_tab);
Rectangle cancel_btn = {x + width - 280, y + height - 50, 100, 35};
Rectangle action_btn = {x + width - 170, y + height - 50, 160, 35};
if (GuiButton(cancel_btn, "Cancel")) {
ui_state->maneuver_dialog.open = false;
}
const char* action_text = "Create Maneuver";
if (ui_state->maneuver_dialog.active_tab == MD_TAB_HOHMANN) {
action_text = "Calculate Transfer";
} else if (ui_state->maneuver_dialog.active_tab == MD_TAB_EDIT) {
action_text = "Save Changes";
}
if (GuiButton(action_btn, action_text)) {
switch (ui_state->maneuver_dialog.active_tab) {
case MD_TAB_CREATE:
create_maneuver_from_dialog(sim, ui_state);
break;
case MD_TAB_HOHMANN:
break;
case MD_TAB_EDIT:
update_maneuver_from_dialog(sim, ui_state);
break;
}
}
if (ui_state->maneuver_dialog.active_tab == MD_TAB_CREATE) {
render_maneuver_create_tab(sim, ui_state, x + 8, y + 60, width - 16, height - 100);
} else if (ui_state->maneuver_dialog.active_tab == MD_TAB_HOHMANN) {
render_maneuver_hohmann_tab(sim, ui_state, x + 8, y + 60, width - 16, height - 100);
} else if (ui_state->maneuver_dialog.active_tab == MD_TAB_EDIT) {
if (ui_state->maneuver_dialog.show_delete_confirm) {
Rectangle confirm_rect = {(float)x + width / 4, (float)y + height / 3, (float)width / 2, 120};
GuiWindowBox(confirm_rect, "Confirm Delete");
Rectangle confirm_label = {(float)x + width / 4 + 10, (float)y + height / 3 + 25, (float)width / 2 - 20, 40};
GuiLabel(confirm_label, "Are you sure you want to delete this maneuver?");
Rectangle cancel_confirm_btn = {(float)x + width / 4 + 10, (float)y + height / 3 + 80, 100, 30};
if (GuiButton(cancel_confirm_btn, "Cancel")) {
ui_state->maneuver_dialog.show_delete_confirm = false;
}
Rectangle confirm_btn = {(float)x + width - 10 - 110, (float)y + height / 3 + 80, 100, 30};
if (GuiButton(confirm_btn, "Delete")) {
delete_maneuver_from_dialog(sim, ui_state);
}
} else {
render_maneuver_edit_tab(sim, ui_state, x + 8, y + 60, width - 16, height - 100);
}
}
}
bool validate_maneuver_inputs(SimulationState* sim, UIState* ui_state) {
ui_state->maneuver_dialog.show_error = false;
ui_state->maneuver_dialog.error_message[0] = '\0';
if (ui_state->maneuver_dialog.craft_index < 0 ||
ui_state->maneuver_dialog.craft_index >= sim->craft_count) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"No spacecraft selected");
ui_state->maneuver_dialog.show_error = true;
return false;
}
for (int i = 0; i < sim->maneuver_count; i++) {
if (strcmp(sim->maneuvers[i].name, ui_state->maneuver_dialog.name) == 0) {
if (ui_state->maneuver_dialog.edit_maneuver_index >= 0 &&
ui_state->maneuver_dialog.edit_maneuver_index == i) {
continue;
}
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Maneuver name already exists");
ui_state->maneuver_dialog.show_error = true;
return false;
}
}
if (ui_state->maneuver_dialog.delta_v < 0) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Delta-V cannot be negative");
ui_state->maneuver_dialog.show_error = true;
return false;
}
if (ui_state->maneuver_dialog.delta_v > 50000) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Delta-V too large (max: 50000 m/s)");
ui_state->maneuver_dialog.show_error = true;
return false;
}
if (ui_state->maneuver_dialog.trigger_type_active == 1) {
if (ui_state->maneuver_dialog.trigger_value < 0 ||
ui_state->maneuver_dialog.trigger_value > 2 * M_PI) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"True anomaly must be 0 to 6.28 rad");
ui_state->maneuver_dialog.show_error = true;
return false;
}
} else {
if (ui_state->maneuver_dialog.trigger_value < 0) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Time cannot be negative");
ui_state->maneuver_dialog.show_error = true;
return false;
}
}
Spacecraft* craft = &sim->spacecraft[ui_state->maneuver_dialog.craft_index];
if (craft->parent_index < 0 || craft->parent_index >= sim->body_count) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Spacecraft has no valid parent body");
ui_state->maneuver_dialog.show_error = true;
return false;
}
if (ui_state->maneuver_dialog.edit_maneuver_index < 0) {
if (sim->maneuver_count >= sim->max_maneuvers) {
snprintf(ui_state->maneuver_dialog.error_message,
sizeof(ui_state->maneuver_dialog.error_message),
"Maximum maneuvers reached");
ui_state->maneuver_dialog.show_error = true;
return false;
}
}
return true;
}
void generate_maneuver_name(SimulationState* sim, UIState* ui_state) {
if (strlen(ui_state->maneuver_dialog.name) > 0) {
return;
}
int burn_num = 0;
for (int i = 0; i < sim->maneuver_count; i++) {
if (sim->maneuvers[i].craft_index == ui_state->maneuver_dialog.craft_index) {
burn_num++;
}
}
const char* direction_name = get_burn_direction_name((BurnDirection)ui_state->maneuver_dialog.direction_active);
if (ui_state->maneuver_dialog.trigger_type_active == 0) {
snprintf(ui_state->maneuver_dialog.name, 64, "%s #%d", direction_name, burn_num);
} else {
snprintf(ui_state->maneuver_dialog.name, 64, "%s @ ν=%.2f #%d",
direction_name, ui_state->maneuver_dialog.trigger_value, burn_num);
}
}
void create_maneuver_from_dialog(SimulationState* sim, UIState* ui_state) {
if (!validate_maneuver_inputs(sim, ui_state)) {
return;
}
generate_maneuver_name(sim, ui_state);
Maneuver maneuver = create_maneuver(
ui_state->maneuver_dialog.name,
ui_state->maneuver_dialog.craft_index,
(BurnDirection)ui_state->maneuver_dialog.direction_active,
ui_state->maneuver_dialog.delta_v,
(TriggerType)ui_state->maneuver_dialog.trigger_type_active,
ui_state->maneuver_dialog.trigger_value
);
int maneuver_index = add_maneuver_to_simulation(sim, &maneuver);
if (maneuver_index >= 0) {
ui_state->maneuver_dialog.open = false;
}
}
void update_maneuver_from_dialog(SimulationState* sim, UIState* ui_state) {
int edit_index = ui_state->maneuver_dialog.edit_maneuver_index;
if (edit_index < 0 || edit_index >= sim->maneuver_count) {
return;
}
if (!validate_maneuver_inputs(sim, ui_state)) {
return;
}
Maneuver* maneuver = &sim->maneuvers[edit_index];
strncpy(maneuver->name, ui_state->maneuver_dialog.name, 64);
maneuver->craft_index = ui_state->maneuver_dialog.craft_index;
maneuver->direction = (BurnDirection)ui_state->maneuver_dialog.direction_active;
maneuver->delta_v = ui_state->maneuver_dialog.delta_v;
maneuver->trigger_type = (TriggerType)ui_state->maneuver_dialog.trigger_type_active;
maneuver->trigger_value = ui_state->maneuver_dialog.trigger_value;
ui_state->maneuver_dialog.open = false;
}
void create_hohmann_burns(SimulationState* sim, UIState* ui_state, bool create_both_burns) {
if (ui_state->maneuver_dialog.craft_index < 0 ||
ui_state->maneuver_dialog.craft_index >= sim->craft_count) {
return;
}
if (!ui_state->maneuver_dialog.show_hohmann_preview) {
return;
}
HohmannTransfer* ht = &ui_state->maneuver_dialog.last_calc;
const char* current_name = (ui_state->maneuver_dialog.transfer_body_index >= 0 &&
ui_state->maneuver_dialog.transfer_body_index < sim->body_count) ?
sim->bodies[ui_state->maneuver_dialog.transfer_body_index].name : "Unknown";
const char* target_name = (ui_state->maneuver_dialog.target_body_index >= 0 &&
ui_state->maneuver_dialog.target_body_index < sim->body_count) ?
sim->bodies[ui_state->maneuver_dialog.target_body_index].name : "Unknown";
char burn1_name[128];
snprintf(burn1_name, sizeof(burn1_name), "Hohmann Burn 1 (%s→%s)", current_name, target_name);
Maneuver burn1 = create_maneuver(
burn1_name,
ui_state->maneuver_dialog.craft_index,
BURN_PROGRADE,
ht->dv1,
TRIGGER_TRUE_ANOMALY,
0.0
);
int burn1_index = add_maneuver_to_simulation(sim, &burn1);
if (burn1_index < 0) {
return;
}
if (create_both_burns) {
char burn2_name[128];
snprintf(burn2_name, sizeof(burn2_name), "Hohmann Burn 2 (%s→%s)", current_name, target_name);
Maneuver burn2 = create_maneuver(
burn2_name,
ui_state->maneuver_dialog.craft_index,
BURN_PROGRADE,
ht->dv2,
TRIGGER_TRUE_ANOMALY,
ht->true_anomaly_2
);
int burn2_index = add_maneuver_to_simulation(sim, &burn2);
if (burn2_index < 0) {
return;
}
}
ui_state->maneuver_dialog.open = false;
}
void load_maneuver_into_dialog(SimulationState* sim, UIState* ui_state, int maneuver_index) {
if (maneuver_index < 0 || maneuver_index >= sim->maneuver_count) {
return;
}
Maneuver* maneuver = &sim->maneuvers[maneuver_index];
ui_state->maneuver_dialog.edit_maneuver_index = maneuver_index;
ui_state->maneuver_dialog.craft_index = maneuver->craft_index;
strncpy(ui_state->maneuver_dialog.name, maneuver->name, 64);
ui_state->maneuver_dialog.direction_active = (int)maneuver->direction;
ui_state->maneuver_dialog.delta_v = maneuver->delta_v;
ui_state->maneuver_dialog.trigger_type_active = (int)maneuver->trigger_type;
ui_state->maneuver_dialog.trigger_value = maneuver->trigger_value;
ui_state->maneuver_dialog.show_delete_confirm = false;
}
void render_maneuver_edit_tab(SimulationState* sim, UIState* ui_state, int x, int y, int width, int height) {
int current_y = y;
if (ui_state->maneuver_dialog.edit_maneuver_index < 0 ||
ui_state->maneuver_dialog.edit_maneuver_index >= sim->maneuver_count) {
GuiLabel({(float)x, (float)current_y, (float)width, 25}, "No maneuver selected for editing");
return;
}
Maneuver* maneuver = &sim->maneuvers[ui_state->maneuver_dialog.edit_maneuver_index];
char title[128];
snprintf(title, sizeof(title), "Edit: %s", maneuver->name);
GuiLabel({(float)x, (float)current_y, (float)width, 25}, title);
current_y += 30;
GuiLabel({(float)x, (float)current_y, 80, 25}, "Spacecraft:");
if (maneuver->craft_index >= 0 && maneuver->craft_index < sim->craft_count) {
char craft_name[64];
snprintf(craft_name, sizeof(craft_name), "%s (locked)", sim->spacecraft[maneuver->craft_index].name);
Rectangle craft_label = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
GuiLabel(craft_label, craft_name);
}
current_y += 35;
Rectangle name_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(name_label, "Name:");
Rectangle name_text = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
GuiTextBox(name_text, ui_state->maneuver_dialog.name, 64, true);
current_y += 35;
Rectangle dir_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(dir_label, "Direction:");
if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) {
static bool edit_mode_direction_edit = false;
Rectangle dir_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
const char* directions = "Prograde;Retrograde;Normal;Antinormal;Radial In;Radial Out;Custom";
if (GuiDropdownBox(dir_combo, directions, &ui_state->maneuver_dialog.direction_active, edit_mode_direction_edit)) {
edit_mode_direction_edit = !edit_mode_direction_edit;
}
if (edit_mode_direction_edit) return;
}
current_y += 35;
Rectangle dv_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(dv_label, "Delta-V:");
Rectangle dv_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20};
float dv_float = (float)ui_state->maneuver_dialog.delta_v;
GuiSlider(dv_slider, "0", "50000", &dv_float, 0, 50000);
ui_state->maneuver_dialog.delta_v = dv_float;
char dv_text[32];
snprintf(dv_text, sizeof(dv_text), "%.1f m/s", ui_state->maneuver_dialog.delta_v);
Rectangle dv_value = {(float)x + width - 90, (float)current_y, 85, 25};
GuiLabel(dv_value, dv_text);
current_y += 35;
Rectangle trigger_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(trigger_label, "Trigger:");
if (ui_state->maneuver_dialog.craft_index >= 0 && ui_state->maneuver_dialog.craft_index < sim->craft_count) {
static bool edit_mode_trigger_edit = false;
Rectangle trigger_combo = {(float)x + 85, (float)current_y, (float)(width - 95), 25};
const char* trigger_types = "Time;True Anomaly";
if (GuiDropdownBox(trigger_combo, trigger_types, &ui_state->maneuver_dialog.trigger_type_active, edit_mode_trigger_edit)) {
edit_mode_trigger_edit = !edit_mode_trigger_edit;
}
if (edit_mode_trigger_edit) return;
}
current_y += 35;
Rectangle value_label = {(float)x, (float)current_y, 80, 25};
GuiLabel(value_label, "Value:");
Rectangle value_slider = {(float)x + 85, (float)current_y, (float)(width - 180), 20};
float value_float = (float)ui_state->maneuver_dialog.trigger_value;
if (ui_state->maneuver_dialog.trigger_type_active == 0) {
GuiSlider(value_slider, "0", "1000000", &value_float, 0, 1000000);
} else {
GuiSlider(value_slider, "0", "6.28", &value_float, 0, 2 * M_PI);
}
ui_state->maneuver_dialog.trigger_value = value_float;
char value_text[32];
if (ui_state->maneuver_dialog.trigger_type_active == 0) {
snprintf(value_text, sizeof(value_text), "%.1f s", ui_state->maneuver_dialog.trigger_value);
} else {
snprintf(value_text, sizeof(value_text), "%.2f rad", ui_state->maneuver_dialog.trigger_value);
}
Rectangle value_text_label = {(float)x + width - 90, (float)current_y, 85, 25};
GuiLabel(value_text_label, value_text);
update_orbital_preview(sim, ui_state);
if (ui_state->maneuver_dialog.show_preview) {
Rectangle preview_bounds = {(float)x + 8, (float)current_y + 10, (float)width - 16, 110};
render_orbital_preview(sim, ui_state, preview_bounds);
}
current_y += 130;
Rectangle delete_btn = {(float)x, (float)current_y, (float)(width - 10) / 2, 30};
if (GuiButton(delete_btn, "Delete Maneuver")) {
ui_state->maneuver_dialog.show_delete_confirm = true;
}
}
void delete_maneuver_from_dialog(SimulationState* sim, UIState* ui_state) {
int edit_index = ui_state->maneuver_dialog.edit_maneuver_index;
if (edit_index < 0 || edit_index >= sim->maneuver_count) {
ui_state->maneuver_dialog.show_delete_confirm = false;
return;
}
bool success = remove_maneuver_by_index(sim, edit_index);
if (success) {
ui_state->maneuver_dialog.show_delete_confirm = false;
ui_state->maneuver_dialog.open = false;
}
}
void ui_state_init_buffers(UIState* ui_state) {
*ui_state = (UIState){0};
}
void ui_state_free_buffers(UIState* ui_state) {
if (ui_state->body_list_buffer != NULL) {
free(ui_state->body_list_buffer);
ui_state->body_list_buffer = NULL;
}
ui_state->body_list_buffer_size = 0;
if (ui_state->craft_list_buffer != NULL) {
free(ui_state->craft_list_buffer);
ui_state->craft_list_buffer = NULL;
}
ui_state->craft_list_buffer_size = 0;
if (ui_state->body_dropdown_buffer != NULL) {
free(ui_state->body_dropdown_buffer);
ui_state->body_dropdown_buffer = NULL;
}
ui_state->body_dropdown_buffer_size = 0;
if (ui_state->maneuver_list_buffer != NULL) {
free(ui_state->maneuver_list_buffer);
ui_state->maneuver_list_buffer = NULL;
}
ui_state->maneuver_list_buffer_size = 0;
}