@ -6,6 +6,51 @@
// Forward declarations
static Vector3 sim_to_render ( Vec3 pos , double scale ) ;
static void draw_child_indicator ( Vector2 screen_pos , const char * name , Color color ) {
const float radius = 20.0f ;
DrawCircleLinesV ( screen_pos , radius , color ) ;
int font_size = 10 ;
int text_width = MeasureText ( name , font_size ) ;
Vector2 text_pos = { screen_pos . x - text_width / 2 , screen_pos . y - font_size / 2 } ;
DrawText ( name , ( int ) text_pos . x , ( int ) text_pos . y , font_size , color ) ;
}
void render_child_indicators ( SimulationState * sim , RenderState * render_state ) {
if ( render_state - > selected_body_index < 0 | | render_state - > selected_body_index > = sim - > body_count ) {
return ;
}
CelestialBody * selected = & sim - > bodies [ render_state - > selected_body_index ] ;
Color body_color = WHITE ;
Color craft_color = ( Color ) { 0 , 255 , 255 , 255 } ;
// Render child body indicators
for ( int i = 0 ; i < sim - > body_count ; i + + ) {
CelestialBody * child = & sim - > bodies [ i ] ;
if ( child - > parent_index = = render_state - > selected_body_index ) {
Vec3 child_rel = vec3_sub ( child - > global_position , selected - > global_position ) ;
Vector3 child_pos_3d = sim_to_render ( child_rel , render_state - > distance_scale ) ;
Vector2 screen_pos = GetWorldToScreen ( child_pos_3d , render_state - > camera ) ;
draw_child_indicator ( screen_pos , child - > name , body_color ) ;
}
}
// Render child spacecraft indicators
for ( int i = 0 ; i < sim - > craft_count ; i + + ) {
Spacecraft * craft = & sim - > spacecraft [ i ] ;
if ( craft - > parent_index = = render_state - > selected_body_index ) {
Vec3 craft_rel = vec3_sub ( craft - > global_position , selected - > global_position ) ;
Vector3 craft_pos_3d = sim_to_render ( craft_rel , render_state - > distance_scale ) ;
Vector2 screen_pos = GetWorldToScreen ( craft_pos_3d , render_state - > camera ) ;
draw_child_indicator ( screen_pos , craft - > name , craft_color ) ;
}
}
}
// Initialize raylib window
void init_renderer ( int width , int height , const char * title ) {
InitWindow ( width , height , title ) ;
@ -109,9 +154,14 @@ static bool has_target_changed(RenderState* render_state) {
static void update_camera_target ( RenderState * render_state , SimulationState * sim ) {
if ( render_state - > selected_body_index > = 0 ) {
CelestialBody * body = & sim - > bodies [ render_state - > selected_body_index ] ;
Vector3 body_pos = sim_to_render ( body - > global_position , render_state - > distance_scale ) ;
render_state - > camera . target = body_pos ;
render_state - > camera . position = Vector3Add ( body_pos , render_state - > camera_offset ) ;
// Camera target is at origin for selected body
render_state - > camera . target = ( Vector3 ) { 0 , 0 , 0 } ;
// Set initial camera position based on children distance
float distance = get_initial_camera_distance ( body , sim , render_state ) ;
render_state - > camera . position = ( Vector3 ) { 0 , distance * 0.3f , distance } ;
render_state - > camera_offset = render_state - > camera . position ;
} else if ( render_state - > selected_craft_index > = 0 ) {
Spacecraft * craft = & sim - > spacecraft [ render_state - > selected_craft_index ] ;
Vector3 craft_pos = sim_to_render ( craft - > global_position , render_state - > distance_scale ) ;
@ -151,7 +201,8 @@ static void zoom_camera(RenderState* render_state, float distance_delta) {
Vector3 direction = Vector3Normalize ( to_target ) ;
float camera_distance = Vector3Length ( to_target ) ;
if ( distance_delta > 0 & & camera_distance < = 10.0f ) return ;
float min_distance = 0.1f ;
if ( camera_distance - distance_delta < = min_distance ) return ;
render_state - > camera . position = Vector3Add ( render_state - > camera . position , Vector3Scale ( direction , distance_delta ) ) ;
@ -183,11 +234,11 @@ void update_camera(RenderState* render_state, SimulationState* sim) {
if ( target_changed ) {
update_camera_target ( render_state , sim ) ;
} else if ( render_state - > selected_body_index > = 0 ) {
CelestialBody * body = & sim - > bodies [ render_state - > selected_body_index ] ;
Vector3 body_pos = sim_to_render ( body - > global_position , render_state - > distance_scale ) ;
render_state - > camera . target = body_pos ;
render_state - > camera . position = Vector3Add ( body_pos , render_state - > camera_offset ) ;
// Body selected - camera orbits around origin (body at 0,0,0 in render space)
render_state - > camera . target = ( Vector3 ) { 0 , 0 , 0 } ;
render_state - > camera . position = render_state - > camera_offset ;
} else if ( render_state - > selected_craft_index > = 0 ) {
// Spacecraft selected - camera orbits around spacecraft
Spacecraft * craft = & sim - > spacecraft [ render_state - > selected_craft_index ] ;
Vector3 craft_pos = sim_to_render ( craft - > global_position , render_state - > distance_scale ) ;
render_state - > camera . target = craft_pos ;
@ -212,6 +263,41 @@ void update_camera(RenderState* render_state, SimulationState* sim) {
update_last_target ( render_state ) ;
}
// Calculate initial camera distance based on children of the selected body
float get_initial_camera_distance ( CelestialBody * body , SimulationState * sim , RenderState * render_state ) {
int body_index = body - sim - > bodies ;
// Calculate average distance to children (bodies and spacecraft)
int child_count = 0 ;
double total_distance = 0.0 ;
// Check body children
for ( int i = 0 ; i < sim - > body_count ; i + + ) {
if ( sim - > bodies [ i ] . parent_index = = body_index ) {
child_count + + ;
double dist = vec3_distance ( sim - > bodies [ i ] . global_position , body - > global_position ) ;
total_distance + = dist * render_state - > distance_scale ;
}
}
// Check spacecraft children
for ( int i = 0 ; i < sim - > craft_count ; i + + ) {
if ( sim - > spacecraft [ i ] . parent_index = = body_index ) {
child_count + + ;
double dist = vec3_distance ( sim - > spacecraft [ i ] . global_position , body - > global_position ) ;
total_distance + = dist * render_state - > distance_scale ;
}
}
if ( child_count > 0 ) {
return ( float ) ( total_distance / child_count ) ;
}
// No children - use 100x body radius as fallback
float body_radius = scale_radius ( body - > radius , render_state - > distance_scale ) ;
return body_radius * 100.0f ;
}
// Transform from simulation coordinates (XY plane) to render coordinates (XZ plane)
// Rotation matrix: 90 degrees around X-axis maps Y -> Z
Vector3 sim_to_render ( Vec3 pos , double scale ) {
@ -222,11 +308,9 @@ Vector3 sim_to_render(Vec3 pos, double scale) {
} ;
}
// Scale a radius for rendering (with minimum visible size )
// Scale a radius for rendering (linear scaling )
float scale_radius ( double radius , double scale ) {
float scaled = ( float ) ( scale * log10 ( radius ) ) ;
float min_radius = 0.01f ; // Minimum visible radius
return ( scaled > min_radius ) ? scaled : min_radius ;
return ( float ) ( radius * scale ) ;
}
// Render a single celestial body
@ -450,6 +534,7 @@ Color get_body_orbit_color(CelestialBody* body) {
} ;
}
// FIXME: orbital vector calculations should be in orbital_mechanics.h
void render_orbit ( Vec3 position , Vec3 velocity , Vec3 parent_position ,
double parent_mass , Color orbit_color , RenderState * render_state ) {
Vec3 r_vec = vec3_sub ( position , parent_position ) ;
@ -521,29 +606,63 @@ void render_simulation(SimulationState* sim, RenderState* render_state) {
DrawLine3D ( ( Vector3 ) { 0 , 0 , - 500.0f } , ( Vector3 ) { 0 , 0 , 500.0f } , ( Color ) { 40 , 40 , 40 , 255 } ) ;
DrawLine3D ( ( Vector3 ) { - 500.0f , 0 , 0 } , ( Vector3 ) { 500.0f , 0 , 0 } , ( Color ) { 40 , 40 , 40 , 255 } ) ;
// Render orbit paths first
for ( int i = 0 ; i < sim - > body_count ; i + + ) {
CelestialBody * body = & sim - > bodies [ i ] ;
if ( body - > parent_index > = 0 & & body - > parent_index < sim - > body_count ) {
CelestialBody * parent = & sim - > bodies [ body - > parent_index ] ;
render_orbit ( body - > global_position , body - > local_velocity , parent - > global_position ,
parent - > mass , get_body_orbit_color ( body ) , render_state ) ;
if ( render_state - > selected_body_index > = 0 ) {
// === BODY SELECTED MODE ===
CelestialBody * selected = & sim - > bodies [ render_state - > selected_body_index ] ;
// Render selected body at origin
Vector3 origin = { 0 , 0 , 0 } ;
float radius = scale_radius ( selected - > radius , render_state - > distance_scale ) ;
Color selected_color = { ( unsigned char ) ( selected - > color [ 0 ] * 255 ) , ( unsigned char ) ( selected - > color [ 1 ] * 255 ) , ( unsigned char ) ( selected - > color [ 2 ] * 255 ) , 255 } ;
DrawSphere ( origin , radius , selected_color ) ;
// Render child bodies and their orbits
for ( int i = 0 ; i < sim - > body_count ; i + + ) {
CelestialBody * child = & sim - > bodies [ i ] ;
if ( child - > parent_index = = render_state - > selected_body_index ) {
Vec3 child_rel = vec3_sub ( child - > global_position , selected - > global_position ) ;
Vector3 child_pos = sim_to_render ( child_rel , render_state - > distance_scale ) ;
float child_radius = scale_radius ( child - > radius , render_state - > distance_scale ) ;
Color child_color = { ( unsigned char ) ( child - > color [ 0 ] * 255 ) , ( unsigned char ) ( child - > color [ 1 ] * 255 ) , ( unsigned char ) ( child - > color [ 2 ] * 255 ) , 255 } ;
DrawSphere ( child_pos , child_radius , child_color ) ;
Vec3 origin_vec = { 0 , 0 , 0 } ;
render_orbit ( child_rel , child - > local_velocity , origin_vec , selected - > mass , child_color , render_state ) ;
}
}
}
// Render spacecraft orbits
for ( int i = 0 ; i < sim - > craft_count ; i + + ) {
Spacecraft * craft = & sim - > spacecraft [ i ] ;
if ( craft - > parent_index > = 0 & & craft - > parent_index < sim - > body_count ) {
CelestialBody * parent = & sim - > bodies [ craft - > parent_index ] ;
render_orbit ( craft - > global_position , craft - > local_velocity , parent - > global_position ,
parent - > mass , ( Color ) { 0 , 255 , 255 , 128 } , render_state ) ;
// Render child spacecraft and their orbits
for ( int i = 0 ; i < sim - > craft_count ; i + + ) {
Spacecraft * craft = & sim - > spacecraft [ i ] ;
if ( craft - > parent_index = = render_state - > selected_body_index ) {
Vec3 craft_rel = vec3_sub ( craft - > global_position , selected - > global_position ) ;
// Craft will be rendered in screen space after EndMode3D
// Just draw orbit here
Vec3 origin_vec = { 0 , 0 , 0 } ;
render_orbit ( craft_rel , craft - > local_velocity , origin_vec , selected - > mass , ( Color ) { 0 , 255 , 255 , 128 } , render_state ) ;
}
}
}
// Render all bodies
for ( int i = 0 ; i < sim - > body_count ; i + + ) {
render_body ( & sim - > bodies [ i ] , render_state ) ;
} else if ( render_state - > selected_craft_index > = 0 ) {
// === SPACECRAFT SELECTED MODE ===
Spacecraft * selected = & sim - > spacecraft [ render_state - > selected_craft_index ] ;
// Render parent body (for reference)
if ( selected - > parent_index > = 0 & & selected - > parent_index < sim - > body_count ) {
CelestialBody * parent = & sim - > bodies [ selected - > parent_index ] ;
Vec3 parent_rel = vec3_sub ( parent - > global_position , selected - > global_position ) ;
Vector3 parent_pos = sim_to_render ( parent_rel , render_state - > distance_scale ) ;
float parent_radius = scale_radius ( parent - > radius , render_state - > distance_scale ) ;
Color parent_color = { ( unsigned char ) ( parent - > color [ 0 ] * 255 ) , ( unsigned char ) ( parent - > color [ 1 ] * 255 ) , ( unsigned char ) ( parent - > color [ 2 ] * 255 ) , 255 } ;
DrawSphere ( parent_pos , parent_radius , parent_color ) ;
// Render spacecraft's orbit around parent
Vec3 origin_vec = { 0 , 0 , 0 } ;
render_orbit ( origin_vec , selected - > local_velocity , parent_rel , parent - > mass , ( Color ) { 0 , 255 , 255 , 128 } , render_state ) ;
}
}
EndMode3D ( ) ;
@ -561,4 +680,7 @@ void render_simulation(SimulationState* sim, RenderState* render_state) {
render_maneuver_marker_screen_space ( craft , maneuver , render_state ) ;
}
}
// Render child indicators
render_child_indicators ( sim , render_state ) ;
}