diff --git a/src/renderer.cpp b/src/renderer.cpp index f077ddb..22712b7 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -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); } diff --git a/src/renderer.h b/src/renderer.h index 8c9fefc..bd5454c 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -39,6 +39,7 @@ float calculate_spacecraft_angle(Vec3 velocity, Camera3D camera); // Camera setup and control void setup_camera(RenderState* render_state); void update_camera(RenderState* render_state, SimulationState* sim); +float get_initial_camera_distance(CelestialBody* body, SimulationState* sim, RenderState* render_state); // Rendering functions void render_body(CelestialBody* body, RenderState* render_state); @@ -48,6 +49,7 @@ void render_orbit(Vec3 position, Vec3 velocity, Vec3 parent_position, double par Color get_body_orbit_color(CelestialBody* body); // NOTE: needs to be called between begin_frame() and end_frame() void render_simulation(SimulationState* sim, RenderState* render_state); +void render_child_indicators(SimulationState* sim, RenderState* render_state); void begin_frame(); void end_frame();