From 879875f49ab88b17fa84e47fb3368d4ed18635f9 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Sat, 17 Jan 2026 19:16:37 -0500 Subject: [PATCH] Fix camera follow - maintain distance and proper orbital rotation Problems fixed: 1. Camera now maintains fixed distance to followed body 2. Camera uses proper up vector and cross products for rotation Technical changes: - Added camera_offset and was_following_body to RenderState - Store offset when follow is first enabled - Update camera position = body_pos + offset each frame - Use cross products for proper orbital camera rotation: - Calculate right vector from up x forward - Rotate forward vector around right axis - Update camera offset after each movement - Zoom controls also update offset when following Result: Camera maintains consistent perspective and distance while tracking moving celestial bodies. --- src/main.cpp | 2 + src/renderer.cpp | 106 +++++++++++++++++++++++++++++++++++++---------- src/renderer.h | 2 + 3 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index c816075..d3d60f5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -43,6 +43,8 @@ void run_gui_simulation(SimulationState* sim) { render_state.body_list_scroll = 0; // Initial scroll position render_state.body_list_active = -1; // No active item initially render_state.camera_follow_body = false; // Camera not following initially + render_state.camera_offset = (Vector3){ 0, 50, 100 }; // Default offset + render_state.was_following_body = false; bool paused = false; double speed_multiplier = 1.0; diff --git a/src/renderer.cpp b/src/renderer.cpp index df83eee..1e0d88d 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -37,46 +37,107 @@ void setup_camera(RenderState* render_state) { // Update camera with keyboard/mouse controls void update_camera(RenderState* render_state, SimulationState* sim) { - // If following a body, update target to body position + float angle_speed = 0.02f; + + // Handle camera follow state transitions + if (render_state->camera_follow_body && !render_state->was_following_body) { + // Just started following - store current offset + if (render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { + CelestialBody* body = &sim->bodies[render_state->selected_body_index]; + Vector3 body_pos = sim_to_render(body->position, render_state->distance_scale); + render_state->camera.target = body_pos; + render_state->camera_offset = Vector3Subtract(render_state->camera.position, body_pos); + } + } + + // Update target position when following if (render_state->camera_follow_body && render_state->selected_body_index >= 0 && render_state->selected_body_index < sim->body_count) { CelestialBody* body = &sim->bodies[render_state->selected_body_index]; Vector3 body_pos = sim_to_render(body->position, render_state->distance_scale); render_state->camera.target = body_pos; + render_state->camera.position = Vector3Add(body_pos, render_state->camera_offset); } - // Orbital camera rotation with arrow keys - float camera_distance = Vector3Distance(render_state->camera.position, render_state->camera.target); - float angle_speed = 0.02f; + // Camera rotation with proper up vector + Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Global up for space + Vector3 to_camera = Vector3Subtract(render_state->camera.position, render_state->camera.target); + float camera_distance = Vector3Length(to_camera); - // Rotate around target + // Rotate around target using cross products if (IsKeyDown(KEY_LEFT)) { - Vector3 pos = render_state->camera.position; - float angle = angle_speed; - float x = pos.x * cosf(angle) - pos.z * sinf(angle); - float z = pos.x * sinf(angle) + pos.z * cosf(angle); - render_state->camera.position.x = x; - render_state->camera.position.z = z; + Vector3 right = Vector3Normalize(Vector3CrossProduct(up, to_camera)); + Vector3 forward = Vector3Normalize(to_camera); + + // Rotate forward vector around right axis + float cos_a = cosf(angle_speed); + float sin_a = sinf(angle_speed); + + Vector3 new_forward = Vector3Add( + Vector3Scale(forward, cos_a), + Vector3Scale(Vector3CrossProduct(right, forward), sin_a) + ); + + render_state->camera.position = Vector3Add( + render_state->camera.target, + Vector3Scale(new_forward, camera_distance) + ); + + if (render_state->camera_follow_body) { + render_state->camera_offset = Vector3Subtract( + render_state->camera.position, + render_state->camera.target + ); + } } if (IsKeyDown(KEY_RIGHT)) { - Vector3 pos = render_state->camera.position; - float angle = -angle_speed; - float x = pos.x * cosf(angle) - pos.z * sinf(angle); - float z = pos.x * sinf(angle) + pos.z * cosf(angle); - render_state->camera.position.x = x; - render_state->camera.position.z = z; + Vector3 right = Vector3Normalize(Vector3CrossProduct(up, to_camera)); + Vector3 forward = Vector3Normalize(to_camera); + + // Rotate forward vector around right axis + float cos_a = cosf(-angle_speed); + float sin_a = sinf(-angle_speed); + + Vector3 new_forward = Vector3Add( + Vector3Scale(forward, cos_a), + Vector3Scale(Vector3CrossProduct(right, forward), sin_a) + ); + + render_state->camera.position = Vector3Add( + render_state->camera.target, + Vector3Scale(new_forward, camera_distance) + ); + + if (render_state->camera_follow_body) { + render_state->camera_offset = Vector3Subtract( + render_state->camera.position, + render_state->camera.target + ); + } } // Zoom in/out with up/down keys if (IsKeyDown(KEY_UP) && camera_distance > 10.0f) { - Vector3 direction = Vector3Subtract(render_state->camera.target, render_state->camera.position); - direction = Vector3Normalize(direction); + Vector3 direction = Vector3Normalize(Vector3Subtract(render_state->camera.target, render_state->camera.position)); render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); + + if (render_state->camera_follow_body) { + render_state->camera_offset = Vector3Subtract( + render_state->camera.position, + render_state->camera.target + ); + } } if (IsKeyDown(KEY_DOWN)) { - Vector3 direction = Vector3Subtract(render_state->camera.position, render_state->camera.target); - direction = Vector3Normalize(direction); + Vector3 direction = Vector3Normalize(Vector3Subtract(render_state->camera.position, render_state->camera.target)); render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); + + if (render_state->camera_follow_body) { + render_state->camera_offset = Vector3Subtract( + render_state->camera.position, + render_state->camera.target + ); + } } // Toggle info display with I key @@ -98,6 +159,9 @@ void update_camera(RenderState* render_state, SimulationState* sim) { render_state->camera_follow_body = false; } } + + // Store previous follow state + render_state->was_following_body = render_state->camera_follow_body; } // Transform from simulation coordinates (XY plane) to render coordinates (XZ plane) diff --git a/src/renderer.h b/src/renderer.h index 312a0e6..5cc4f9b 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -16,6 +16,8 @@ struct RenderState { int body_list_scroll; // Scroll position for body list int body_list_active; // Active item index in body list 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 }; // Renderer initialization and cleanup