@ -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 & &
// 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 ;
// Use current offset to maintain distance to new body
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 , " Object s" ) ;
// Draw list view (inside window, leave space for title bar)
Rectangle list_bounds = {
@ -493,42 +593,51 @@ 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 ) ;
GuiListView ( list_bounds , 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 ) {
// 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 ;
// Enable camera follow when body is selected
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: %s \n " , sim - > bodies [ render_state - > selected_body_index ] . name ) ;
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
// 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 ;
}
// Panel dimensions
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 } ;
// 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)
@ -607,4 +716,183 @@ void render_body_info_ui(SimulationState* sim, RenderState* render_state) {
} ;
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 \n Time: %.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 ) ;
}