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.
611 lines
22 KiB
611 lines
22 KiB
#include "renderer.h" |
|
#include "raylib.h" |
|
#include "raymath.h" |
|
#include "orbital_mechanics.h" |
|
#include <cmath> |
|
#include <cstdio> |
|
|
|
// Camera control constants |
|
#define CAMERA_ORBIT_ANGLE_SPEED 0.02f |
|
#define CAMERA_MIN_DISTANCE 0.1f |
|
#define CAMERA_INITIAL_HEIGHT_FACTOR 0.3f |
|
#define CAMERA_INITIAL_FOV 45.0f |
|
#define CAMERA_INITIAL_POSITION_Y 50.0f |
|
#define CAMERA_INITIAL_POSITION_Z 100.0f |
|
|
|
// Scaling constants |
|
#define DISTANCE_SCALE_DEFAULT 1e-9 |
|
#define SIZE_SCALE_DEFAULT 0.02f |
|
|
|
// Initial camera distance fallback |
|
#define INITIAL_DISTANCE_RADIUS_MULTIPLIER 100.0f |
|
|
|
// Dynamic zoom settings |
|
#define ZOOM_PERCENTAGE 0.07f |
|
#define ZOOM_MIN_DELTA 0.05f |
|
#define ZOOM_MAX_DELTA 5.0f |
|
|
|
// Minimum distance settings (in render units) |
|
#define MIN_DISTANCE_BODY_RADIUS_MULT 2.75f |
|
#define MIN_DISTANCE_SPACECRAFT_DEFAULT 0.5f |
|
#define MIN_DISTANCE_SPACECRAFT_PARENT_RADIUS_MULT 0.1f |
|
#define CAMERA_NEAR_CULL_DISTANCE 0.05f |
|
|
|
// Child indicator constants |
|
#define CHILD_INDICATOR_RADIUS 20.0f |
|
#define CHILD_INDICATOR_FONT_SIZE 10 |
|
|
|
// Zoom direction enumeration for improved readability |
|
enum ZoomDirection { |
|
ZOOM_IN = 1, |
|
ZOOM_OUT = -1 |
|
}; |
|
|
|
// TODO: Consider extracting other hardcoded constants: |
|
// - Reference grid parameters (lines 600-610) |
|
// - Spacecraft rendering (screen size, color) |
|
// - Maneuver marker rendering (size calculation, bounds) |
|
// - Orbit rendering (segments, eccentricity thresholds, max true anomaly) |
|
// - Screen space rendering (culling margins) |
|
// - Child indicators (radius, font size) |
|
|
|
// Forward declarations |
|
static Vector3 sim_to_render(Vec3 pos, double scale); |
|
|
|
static void draw_child_indicator(Vector2 screen_pos, const char* name, Color color) { |
|
DrawCircleLinesV(screen_pos, CHILD_INDICATOR_RADIUS, color); |
|
|
|
int font_size = CHILD_INDICATOR_FONT_SIZE; |
|
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) { |
|
for (int i = 0; i < 6; i++) { |
|
UnloadTexture(render_state->maneuver_textures[i]); |
|
} |
|
render_state->texture_loaded = false; |
|
} |
|
CloseWindow(); |
|
} |
|
|
|
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->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; |
|
} |
|
|
|
// Setup the 3D camera |
|
void setup_camera(RenderState* render_state) { |
|
render_state->camera.position = (Vector3){ 0.0f, CAMERA_INITIAL_POSITION_Y, CAMERA_INITIAL_POSITION_Z }; |
|
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 = CAMERA_INITIAL_FOV; |
|
render_state->camera.projection = CAMERA_PERSPECTIVE; |
|
|
|
render_state->distance_scale = DISTANCE_SCALE_DEFAULT; |
|
render_state->size_scale = SIZE_SCALE_DEFAULT; |
|
|
|
render_state->texture_loaded = false; |
|
for (int i = 0; i < 6; i++) { |
|
render_state->maneuver_textures[i] = (Texture2D){0, 0, 0, 0, 0}; |
|
} |
|
|
|
ensure_textures_loaded(render_state); |
|
} |
|
|
|
static bool has_target_changed(RenderState* render_state) { |
|
int current_target = render_state->selected_body_index; |
|
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) return; |
|
|
|
CelestialBody* body = &sim->bodies[render_state->selected_body_index]; |
|
render_state->camera.target = (Vector3){0, 0, 0}; |
|
float distance = get_initial_camera_distance(body, sim, render_state); |
|
render_state->camera.position = (Vector3){0, distance * CAMERA_INITIAL_HEIGHT_FACTOR, distance}; |
|
render_state->camera_offset = render_state->camera.position; |
|
} |
|
|
|
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 float get_target_min_distance(RenderState* render_state, SimulationState* sim) { |
|
if (render_state->selected_body_index < 0) { |
|
return CAMERA_MIN_DISTANCE; |
|
} |
|
|
|
CelestialBody* body = &sim->bodies[render_state->selected_body_index]; |
|
float target_radius = scale_radius(body->radius, render_state->distance_scale); |
|
|
|
float radius_based_dist = target_radius * MIN_DISTANCE_BODY_RADIUS_MULT; |
|
float cull_based_dist = target_radius + CAMERA_NEAR_CULL_DISTANCE + 0.001f; |
|
return (radius_based_dist > cull_based_dist) ? radius_based_dist : cull_based_dist; |
|
} |
|
|
|
static void zoom_camera(RenderState* render_state, SimulationState* sim, ZoomDirection zoom_dir) { |
|
Vector3 to_target = Vector3Subtract(render_state->camera.target, render_state->camera.position); |
|
Vector3 direction = Vector3Normalize(to_target); |
|
float camera_distance = Vector3Length(to_target); |
|
|
|
float raw_delta = camera_distance * ZOOM_PERCENTAGE * (float)zoom_dir; |
|
float distance_delta = raw_delta; |
|
|
|
if (fabsf(distance_delta) < ZOOM_MIN_DELTA) { |
|
distance_delta = ZOOM_MIN_DELTA * (float)zoom_dir; |
|
} |
|
if (fabsf(distance_delta) > ZOOM_MAX_DELTA) { |
|
distance_delta = ZOOM_MAX_DELTA * (float)zoom_dir; |
|
} |
|
|
|
float min_dist = get_target_min_distance(render_state, sim); |
|
|
|
if (zoom_dir == ZOOM_IN) { |
|
if (camera_distance - distance_delta <= min_dist) { |
|
distance_delta = camera_distance - min_dist - 0.001f; |
|
} |
|
} |
|
|
|
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) { |
|
render_state->last_target_index = render_state->selected_body_index; |
|
} |
|
|
|
// Update camera with keyboard/mouse controls |
|
void update_camera(RenderState* render_state, SimulationState* sim) { |
|
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) { |
|
render_state->camera.target = (Vector3){0, 0, 0}; |
|
render_state->camera.position = render_state->camera_offset; |
|
} |
|
} |
|
|
|
if (IsKeyDown(KEY_LEFT)) { |
|
rotate_camera_orbitally(render_state, CAMERA_ORBIT_ANGLE_SPEED); |
|
} |
|
if (IsKeyDown(KEY_RIGHT)) { |
|
rotate_camera_orbitally(render_state, -CAMERA_ORBIT_ANGLE_SPEED); |
|
} |
|
|
|
if (IsKeyDown(KEY_UP)) { |
|
zoom_camera(render_state, sim, ZOOM_IN); |
|
} |
|
if (IsKeyDown(KEY_DOWN)) { |
|
zoom_camera(render_state, sim, ZOOM_OUT); |
|
} |
|
|
|
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; |
|
} |
|
} |
|
|
|
float body_radius = scale_radius(body->radius, render_state->distance_scale); |
|
|
|
if (child_count > 0) { |
|
float average_distance = (float)(total_distance / child_count); |
|
float min_distance_from_radius = body_radius * INITIAL_DISTANCE_RADIUS_MULTIPLIER; |
|
return (average_distance > min_distance_from_radius) ? average_distance : min_distance_from_radius; |
|
} |
|
|
|
return body_radius * INITIAL_DISTANCE_RADIUS_MULTIPLIER; |
|
} |
|
|
|
// 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); |
|
} |
|
|
|
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; |
|
} |
|
|
|
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 = (maneuver->direction == BURN_CUSTOM) ? 0 : maneuver->direction; |
|
|
|
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 < (1.0 - PARABOLIC_TOLERANCE)) { |
|
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.0 + PARABOLIC_TOLERANCE)) { |
|
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(); |
|
} |
|
|
|
// FIXME: refactor for readability |
|
// 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); |
|
} |
|
} |
|
} |
|
|
|
EndMode3D(); |
|
|
|
// 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); |
|
}
|
|
|