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.
 
 
 
 
 

686 lines
25 KiB

#include "renderer.h"
#include "raymath.h"
#include <cmath>
#include <cstdio>
// 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);
SetTargetFPS(60);
}
// Close raylib
void close_renderer(RenderState* render_state) {
if (render_state->texture_loaded) {
UnloadTexture(render_state->spacecraft_texture);
for (int i = 0; i < 6; i++) {
UnloadTexture(render_state->maneuver_textures[i]);
}
render_state->texture_loaded = false;
}
CloseWindow();
}
Texture2D generate_spacecraft_texture() {
Image img = GenImageColor(64, 64, BLANK);
ImageDrawCircle(&img, 32, 32, 28, (Color){0, 255, 255, 255});
ImageDrawLine(&img, 32, 10, 32, 50, (Color){0, 0, 0, 255});
ImageDrawLine(&img, 32, 10, 25, 17, (Color){0, 0, 0, 255});
ImageDrawLine(&img, 32, 10, 39, 17, (Color){0, 0, 0, 255});
Texture2D tex = LoadTextureFromImage(img);
UnloadImage(img);
return tex;
}
Texture2D generate_maneuver_marker_texture(Color color) {
Image img = GenImageColor(32, 32, BLANK);
ImageDrawTriangle(&img,
(Vector2){16, 4},
(Vector2){4, 28},
(Vector2){28, 28},
color);
Texture2D tex = LoadTextureFromImage(img);
UnloadImage(img);
return tex;
}
void ensure_textures_loaded(RenderState* render_state) {
if (render_state->texture_loaded) return;
render_state->spacecraft_texture = generate_spacecraft_texture();
render_state->maneuver_textures[0] = generate_maneuver_marker_texture((Color){0, 255, 0, 200});
render_state->maneuver_textures[1] = generate_maneuver_marker_texture((Color){255, 0, 0, 200});
render_state->maneuver_textures[2] = generate_maneuver_marker_texture((Color){255, 255, 0, 200});
render_state->maneuver_textures[3] = generate_maneuver_marker_texture((Color){255, 165, 0, 200});
render_state->maneuver_textures[4] = generate_maneuver_marker_texture((Color){255, 0, 255, 200});
render_state->maneuver_textures[5] = generate_maneuver_marker_texture((Color){0, 255, 255, 200});
render_state->texture_loaded = true;
}
float calculate_spacecraft_angle(Vec3 velocity, Camera3D camera) {
if (vec3_magnitude(velocity) < 0.01) return 0.0f;
Vector3 render_vel = sim_to_render(velocity, 1.0);
Vector2 vel_2d = (Vector2){render_vel.x, render_vel.z};
Vector2 cam_2d = (Vector2){camera.position.x, camera.position.z};
return Vector2Angle(vel_2d, cam_2d);
}
// Setup the 3D camera
void setup_camera(RenderState* render_state) {
render_state->camera.position = (Vector3){ 0.0f, 50.0f, 100.0f };
render_state->camera.target = (Vector3){ 0.0f, 0.0f, 0.0f };
render_state->camera.up = (Vector3){ 0.0f, 1.0f, 0.0f };
render_state->camera.fovy = 45.0f;
render_state->camera.projection = CAMERA_PERSPECTIVE;
render_state->distance_scale = 1e-9;
render_state->size_scale = 0.02f;
render_state->texture_loaded = false;
render_state->spacecraft_texture = (Texture2D){0, 0, 0, 0, 0};
for (int i = 0; i < 6; i++) {
render_state->maneuver_textures[i] = (Texture2D){0, 0, 0, 0, 0};
}
}
static bool has_target_changed(RenderState* render_state) {
int current_target = -1;
if (render_state->selected_body_index >= 0) {
current_target = render_state->selected_body_index;
} else if (render_state->selected_craft_index >= 0) {
current_target = -(render_state->selected_craft_index + 1);
}
return current_target != render_state->last_target_index;
}
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];
// 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);
render_state->camera.target = craft_pos;
render_state->camera.position = Vector3Add(craft_pos, render_state->camera_offset);
}
}
static void rotate_camera_orbitally(RenderState* render_state, float angle) {
Vector3 to_camera = Vector3Subtract(render_state->camera.position, render_state->camera.target);
float camera_distance = Vector3Length(to_camera);
Vector3 forward = Vector3Normalize(to_camera);
float cos_a = cosf(angle);
float sin_a = sinf(angle);
Vector3 new_forward = Vector3Add(
Vector3Scale(forward, cos_a),
Vector3Scale(Vector3CrossProduct(render_state->camera.up, forward), sin_a)
);
render_state->camera.position = Vector3Add(
render_state->camera.target,
Vector3Scale(new_forward, camera_distance)
);
if (render_state->camera_target_enabled) {
render_state->camera_offset = Vector3Subtract(
render_state->camera.position,
render_state->camera.target
);
}
}
static void zoom_camera(RenderState* render_state, float distance_delta) {
Vector3 to_target = Vector3Subtract(render_state->camera.target, render_state->camera.position);
Vector3 direction = Vector3Normalize(to_target);
float camera_distance = Vector3Length(to_target);
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));
if (render_state->camera_target_enabled) {
render_state->camera_offset = Vector3Subtract(
render_state->camera.position,
render_state->camera.target
);
}
}
static void update_last_target(RenderState* render_state) {
if (render_state->selected_body_index >= 0) {
render_state->last_target_index = render_state->selected_body_index;
} else if (render_state->selected_craft_index >= 0) {
render_state->last_target_index = -(render_state->selected_craft_index + 1);
} else {
render_state->last_target_index = -1;
}
}
// Update camera with keyboard/mouse controls
void update_camera(RenderState* render_state, SimulationState* sim) {
float angle_speed = 0.02f;
bool target_changed = has_target_changed(render_state);
if (render_state->camera_target_enabled) {
if (target_changed) {
update_camera_target(render_state, sim);
} else if (render_state->selected_body_index >= 0) {
// 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;
render_state->camera.position = Vector3Add(craft_pos, render_state->camera_offset);
}
}
if (IsKeyDown(KEY_LEFT)) {
rotate_camera_orbitally(render_state, angle_speed);
}
if (IsKeyDown(KEY_RIGHT)) {
rotate_camera_orbitally(render_state, -angle_speed);
}
if (IsKeyDown(KEY_UP)) {
zoom_camera(render_state, 2.0f);
}
if (IsKeyDown(KEY_DOWN)) {
zoom_camera(render_state, -2.0f);
}
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) {
return (Vector3){
(float)(pos.x * scale),
(float)(pos.z * scale),
(float)(-pos.y * scale)
};
}
// Scale a radius for rendering (linear scaling)
float scale_radius(double radius, double scale) {
return (float)(radius * scale);
}
// Render a single celestial body
void render_body(CelestialBody* body, RenderState* render_state) {
Vector3 position = sim_to_render(body->global_position, render_state->distance_scale);
float radius = scale_radius(body->radius, render_state->size_scale);
Color color = {
(unsigned char)(body->color[0] * 255),
(unsigned char)(body->color[1] * 255),
(unsigned char)(body->color[2] * 255),
255
};
DrawSphereWires(position, radius, 16, 16, color);
}
void render_spacecraft_screen_space(Spacecraft* craft, RenderState* render_state) {
Vector3 render_pos = sim_to_render(craft->global_position, render_state->distance_scale);
Vector2 screen_pos = GetWorldToScreen(render_pos, render_state->camera);
int screen_width = GetScreenWidth();
int screen_height = GetScreenHeight();
if (screen_pos.x < -50 || screen_pos.x > screen_width + 50 ||
screen_pos.y < -50 || screen_pos.y > screen_height + 50) {
return;
}
ensure_textures_loaded(render_state);
float screen_size = 40.0f;
Color spacecraft_color = { 0, 255, 255, 255 };
float angle = calculate_spacecraft_angle(craft->local_velocity, render_state->camera);
Rectangle source = {0, 0, 64, 64};
Rectangle dest = {
screen_pos.x - screen_size/2,
screen_pos.y - screen_size/2,
screen_size,
screen_size
};
Vector2 origin = {screen_size/2, screen_size/2};
DrawTexturePro(render_state->spacecraft_texture,
source, dest, origin, angle, spacecraft_color);
}
void render_maneuver_marker_screen_space(Spacecraft* craft, Maneuver* maneuver, RenderState* render_state) {
if (maneuver->executed) {
return;
}
Vector3 render_pos = sim_to_render(craft->global_position, render_state->distance_scale);
Vector2 screen_pos = GetWorldToScreen(render_pos, render_state->camera);
int screen_width = GetScreenWidth();
int screen_height = GetScreenHeight();
if (screen_pos.x < -50 || screen_pos.x > screen_width + 50 ||
screen_pos.y < -50 || screen_pos.y > screen_height + 50) {
return;
}
ensure_textures_loaded(render_state);
float screen_size = (float)(maneuver->delta_v / 50.0f);
if (screen_size < 20.0f) screen_size = 20.0f;
if (screen_size > 60.0f) screen_size = 60.0f;
int texture_index;
switch (maneuver->direction) {
case BURN_PROGRADE:
texture_index = 0;
break;
case BURN_RETROGRADE:
texture_index = 1;
break;
case BURN_NORMAL:
texture_index = 2;
break;
case BURN_ANTINORMAL:
texture_index = 3;
break;
case BURN_RADIAL_IN:
texture_index = 4;
break;
case BURN_RADIAL_OUT:
texture_index = 5;
break;
default:
texture_index = 0;
break;
}
Rectangle source = {0, 0, 32, 32};
Rectangle dest = {
screen_pos.x - screen_size/2,
screen_pos.y - screen_size/2,
screen_size,
screen_size
};
Vector2 origin = {screen_size/2, screen_size/2};
DrawTexturePro(render_state->maneuver_textures[texture_index],
source, dest, origin, 0, WHITE);
}
struct OrbitalBasis {
Vec3 periapsis_dir;
Vec3 normal;
Vec3 q_vec;
};
static OrbitalBasis calculate_orbital_basis(Vec3 r_vec, Vec3 velocity, Vec3 e_vec) {
OrbitalBasis basis;
Vec3 e_dir = vec3_normalize(e_vec);
// For circular orbits, use position direction as periapsis direction
if (vec3_magnitude(e_dir) < 0.001) {
basis.periapsis_dir = vec3_normalize(r_vec);
} else {
basis.periapsis_dir = e_dir;
}
Vec3 h_vec = vec3_cross(r_vec, velocity);
basis.normal = vec3_normalize(h_vec);
basis.q_vec = vec3_cross(basis.normal, basis.periapsis_dir);
return basis;
}
static Vec3 orbital_to_cartesian(double x, double y, OrbitalBasis basis, Vec3 parent_pos) {
return {
basis.periapsis_dir.x * x + basis.q_vec.x * y + parent_pos.x,
basis.periapsis_dir.y * x + basis.q_vec.y * y + parent_pos.y,
basis.periapsis_dir.z * x + basis.q_vec.z * y + parent_pos.z
};
}
static void draw_orbit_segment(double x1, double y1, double x2, double y2,
OrbitalBasis basis, Vec3 parent_pos,
RenderState* render_state, Color color) {
Vec3 p1_sim = orbital_to_cartesian(x1, y1, basis, parent_pos);
Vec3 p2_sim = orbital_to_cartesian(x2, y2, basis, parent_pos);
Vector3 p1 = sim_to_render(p1_sim, render_state->distance_scale);
Vector3 p2 = sim_to_render(p2_sim, render_state->distance_scale);
DrawLine3D(p1, p2, color);
}
static void render_elliptical_orbit(double a, double e, OrbitalBasis basis,
Vec3 parent_pos, RenderState* render_state, Color color) {
double b = a * sqrt(1.0 - e * e);
double c = a * e;
int segments = 100;
for (int i = 0; i < segments; i++) {
float theta1 = (float)i / segments * 2.0f * PI;
float theta2 = (float)(i + 1) / segments * 2.0f * PI;
double x1 = a * cos(theta1) - c;
double y1 = b * sin(theta1);
double x2 = a * cos(theta2) - c;
double y2 = b * sin(theta2);
draw_orbit_segment(x1, y1, x2, y2, basis, parent_pos, render_state, color);
}
}
static void render_hyperbolic_orbit(double p, double e, OrbitalBasis basis,
Vec3 parent_pos, RenderState* render_state, Color color) {
double max_true_anomaly = (e > 1.01) ? acos(-1.0 / e) * 0.95 : PI * 0.48;
int segments = 60;
for (int i = 0; i < segments; i++) {
float theta1 = -max_true_anomaly + (float)i / segments * 2.0f * max_true_anomaly;
float theta2 = -max_true_anomaly + (float)(i + 1) / segments * 2.0f * max_true_anomaly;
double r1 = p / (1.0 + e * cos(theta1));
double r2 = p / (1.0 + e * cos(theta2));
double x1 = r1 * cos(theta1);
double y1 = r1 * sin(theta1);
double x2 = r2 * cos(theta2);
double y2 = r2 * sin(theta2);
draw_orbit_segment(x1, y1, x2, y2, basis, parent_pos, render_state, color);
}
}
static void render_parabolic_orbit(double p, OrbitalBasis basis,
Vec3 parent_pos, RenderState* render_state, Color color) {
double max_true_anomaly = PI * 0.95;
int segments = 80;
for (int i = 0; i < segments; i++) {
float theta1 = -max_true_anomaly + (float)i / segments * 2.0f * max_true_anomaly;
float theta2 = -max_true_anomaly + (float)(i + 1) / segments * 2.0f * max_true_anomaly;
double r1 = p / (1.0 + cos(theta1));
double r2 = p / (1.0 + cos(theta2));
double x1 = r1 * cos(theta1);
double y1 = r1 * sin(theta1);
double x2 = r2 * cos(theta2);
double y2 = r2 * sin(theta2);
draw_orbit_segment(x1, y1, x2, y2, basis, parent_pos, render_state, color);
}
}
Color get_body_orbit_color(CelestialBody* body) {
return (Color){
(unsigned char)(body->color[0] * 128),
(unsigned char)(body->color[1] * 128),
(unsigned char)(body->color[2] * 128),
128
};
}
// 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);
double r = vec3_magnitude(r_vec);
double v = vec3_magnitude(velocity);
if (r < 1.0) return;
double mu = G * parent_mass;
double specific_energy = (v * v) / 2.0 - mu / r;
double v_squared = v * v;
double r_dot_v = vec3_dot(r_vec, velocity);
Vec3 e_vec = {
(v_squared - mu / r) * r_vec.x - r_dot_v * velocity.x,
(v_squared - mu / r) * r_vec.y - r_dot_v * velocity.y,
(v_squared - mu / r) * r_vec.z - r_dot_v * velocity.z
};
double e = vec3_magnitude(e_vec) / mu;
OrbitalBasis basis = calculate_orbital_basis(r_vec, velocity, e_vec);
if (e < 0.98) {
double a = -mu / (2.0 * specific_energy);
if (a <= 0.0) return;
render_elliptical_orbit(a, e, basis, parent_position, render_state, orbit_color);
} else if (e > 1.02) {
double a = mu / (2.0 * (-specific_energy));
double p = a * (1.0 - e * e);
if (p <= 0.0) return;
render_hyperbolic_orbit(p, e, basis, parent_position, render_state, orbit_color);
} else {
Vec3 h_vec = vec3_cross(r_vec, velocity);
double h_squared = vec3_dot(h_vec, h_vec);
double p = h_squared / mu;
if (p <= 0.0) return;
render_parabolic_orbit(p, basis, parent_position, render_state, orbit_color);
}
}
void begin_frame() {
BeginDrawing();
ClearBackground(BLACK);
}
void end_frame() {
EndDrawing();
}
// Render the entire simulation
void render_simulation(SimulationState* sim, RenderState* render_state) {
BeginMode3D(render_state->camera);
// Draw a subtle reference grid
for (int i = -50; i <= 50; i++) {
if (i == 0) continue;
DrawLine3D((Vector3){(float)i * 10.0f, 0, -500.0f},
(Vector3){(float)i * 10.0f, 0, 500.0f},
(Color){20, 20, 20, 255});
DrawLine3D((Vector3){-500.0f, 0, (float)i * 10.0f},
(Vector3){500.0f, 0, (float)i * 10.0f},
(Color){20, 20, 20, 255});
}
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});
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 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);
}
}
} 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();
// Render spacecraft as screen-space overlays
for (int i = 0; i < sim->craft_count; i++) {
render_spacecraft_screen_space(&sim->spacecraft[i], render_state);
}
// Render maneuver markers as screen-space overlays
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_screen_space(craft, maneuver, render_state);
}
}
// Render child indicators
render_child_indicators(sim, render_state);
}