From 42f4b12cfcc44a4fcbbb98b8f33fd7e55ba9e79f Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Fri, 23 Jan 2026 12:30:20 -0500 Subject: [PATCH] Refactor to billboard rendering - Replace wireframe spheres with proper billboard textures - Spacecraft rendered as cyan circle with arrow (64x64 texture) - Maneuver markers as directional arrow billboards - Spacecraft billboards rotate to show velocity direction - Programmatic texture generation (no external assets) - Lazy texture loading when spacecraft/maneuvers present - Proper texture cleanup on renderer shutdown --- src/main.cpp | 2 +- src/renderer.cpp | 132 +++++++++++++++++++++++++++++++++++++---------- src/renderer.h | 16 +++++- 3 files changed, 120 insertions(+), 30 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index f4aec74..bf92db1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -96,7 +96,7 @@ void run_gui_simulation(SimulationState* sim) { render_simulation(sim, &render_state); } - close_renderer(); + close_renderer(&render_state); printf("\nSimulation ended. Final time: %.2f days\n", sim->time / 86400.0); } diff --git a/src/renderer.cpp b/src/renderer.cpp index 34a102a..f8d3f9e 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -17,10 +17,71 @@ void init_renderer(int width, int height, const char* title) { } // Close raylib -void close_renderer() { +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 }; @@ -29,9 +90,14 @@ void setup_camera(RenderState* render_state) { render_state->camera.fovy = 45.0f; render_state->camera.projection = CAMERA_PERSPECTIVE; - // Set scaling factors (same scale for distances and sizes) - render_state->distance_scale = 1e-9; // Meters to scaled units (1 unit = 1 billion meters) - render_state->size_scale = 1e-9; // Same scale for body sizes (minimum size still applies) + render_state->distance_scale = 1e-9; + render_state->size_scale = 1e-9; + + 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}; + } } // Update camera with keyboard/mouse controls @@ -206,57 +272,67 @@ 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); + ensure_textures_loaded(render_state); - // 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); + Vector3 position = sim_to_render(craft->position, render_state->distance_scale); + float billboard_size = 20.0f; + Color spacecraft_color = { 0, 255, 255, 255 }; + float angle = calculate_spacecraft_angle(craft->local_velocity, render_state->camera); + + DrawBillboardPro(render_state->camera, + render_state->spacecraft_texture, + (Rectangle){0, 0, 64, 64}, + position, + (Vector3){0, 1, 0}, + (Vector2){billboard_size, billboard_size}, + (Vector2){billboard_size/2, billboard_size/2}, + angle, + 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 + return; } - Vector3 position = sim_to_render(craft->position, render_state->distance_scale); + ensure_textures_loaded(render_state); - // 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; + Vector3 position = sim_to_render(craft->position, render_state->distance_scale); + float marker_size = (float)(maneuver->delta_v / 100.0f); + if (marker_size < 4.0f) marker_size = 4.0f; + if (marker_size > 12.0f) marker_size = 12.0f; - // Color based on direction - Color marker_color; + int texture_index; switch (maneuver->direction) { case BURN_PROGRADE: - marker_color = (Color){ 0, 255, 0, 200 }; // Green + texture_index = 0; break; case BURN_RETROGRADE: - marker_color = (Color){ 255, 0, 0, 200 }; // Red + texture_index = 1; break; case BURN_NORMAL: - marker_color = (Color){ 255, 255, 0, 200 }; // Yellow + texture_index = 2; break; case BURN_ANTINORMAL: - marker_color = (Color){ 255, 165, 0, 200 }; // Orange + texture_index = 3; break; case BURN_RADIAL_IN: - marker_color = (Color){ 255, 0, 255, 200 }; // Magenta + texture_index = 4; break; case BURN_RADIAL_OUT: - marker_color = (Color){ 0, 255, 255, 200 }; // Cyan + texture_index = 5; break; default: - marker_color = (Color){ 255, 255, 255, 200 }; // White + texture_index = 0; break; } - DrawCube(position, marker_size, marker_size, marker_size, marker_color); + DrawBillboard(render_state->camera, + render_state->maneuver_textures[texture_index], + position, + marker_size, + WHITE); } struct OrbitalBasis { diff --git a/src/renderer.h b/src/renderer.h index 4dddf28..819325c 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -19,11 +19,25 @@ struct RenderState { bool camera_follow_body; // Whether camera follows selected body Vector3 camera_offset; // Offset from target when following body bool was_following_body; // Previous frame follow state + + Texture2D spacecraft_texture; // Shared texture for all spacecraft + Texture2D maneuver_textures[6]; // One per burn direction + bool texture_loaded; }; // Renderer initialization and cleanup void init_renderer(int width, int height, const char* title); -void close_renderer(); +void close_renderer(RenderState* render_state); + +// Texture generation +Texture2D generate_spacecraft_texture(); +Texture2D generate_maneuver_marker_texture(Color color); + +// Texture management +void ensure_textures_loaded(RenderState* render_state); + +// Angle calculation +float calculate_spacecraft_angle(Vec3 velocity, Camera3D camera); // Camera setup and control void setup_camera(RenderState* render_state);