Browse Source

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.
main
cinnaboot 6 months ago
parent
commit
879875f49a
  1. 2
      src/main.cpp
  2. 106
      src/renderer.cpp
  3. 2
      src/renderer.h

2
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_scroll = 0; // Initial scroll position
render_state.body_list_active = -1; // No active item initially render_state.body_list_active = -1; // No active item initially
render_state.camera_follow_body = false; // Camera not following 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; bool paused = false;
double speed_multiplier = 1.0; double speed_multiplier = 1.0;

106
src/renderer.cpp

@ -37,46 +37,107 @@ void setup_camera(RenderState* render_state) {
// Update camera with keyboard/mouse controls // Update camera with keyboard/mouse controls
void update_camera(RenderState* render_state, SimulationState* sim) { 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 && if (render_state->camera_follow_body && render_state->selected_body_index >= 0 &&
render_state->selected_body_index < sim->body_count) { render_state->selected_body_index < sim->body_count) {
CelestialBody* body = &sim->bodies[render_state->selected_body_index]; CelestialBody* body = &sim->bodies[render_state->selected_body_index];
Vector3 body_pos = sim_to_render(body->position, render_state->distance_scale); Vector3 body_pos = sim_to_render(body->position, render_state->distance_scale);
render_state->camera.target = body_pos; render_state->camera.target = body_pos;
render_state->camera.position = Vector3Add(body_pos, render_state->camera_offset);
} }
// Orbital camera rotation with arrow keys // Camera rotation with proper up vector
float camera_distance = Vector3Distance(render_state->camera.position, render_state->camera.target); Vector3 up = (Vector3){ 0.0f, 1.0f, 0.0f }; // Global up for space
float angle_speed = 0.02f; 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)) { if (IsKeyDown(KEY_LEFT)) {
Vector3 pos = render_state->camera.position; Vector3 right = Vector3Normalize(Vector3CrossProduct(up, to_camera));
float angle = angle_speed; Vector3 forward = Vector3Normalize(to_camera);
float x = pos.x * cosf(angle) - pos.z * sinf(angle);
float z = pos.x * sinf(angle) + pos.z * cosf(angle); // Rotate forward vector around right axis
render_state->camera.position.x = x; float cos_a = cosf(angle_speed);
render_state->camera.position.z = z; 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)) { if (IsKeyDown(KEY_RIGHT)) {
Vector3 pos = render_state->camera.position; Vector3 right = Vector3Normalize(Vector3CrossProduct(up, to_camera));
float angle = -angle_speed; Vector3 forward = Vector3Normalize(to_camera);
float x = pos.x * cosf(angle) - pos.z * sinf(angle);
float z = pos.x * sinf(angle) + pos.z * cosf(angle); // Rotate forward vector around right axis
render_state->camera.position.x = x; float cos_a = cosf(-angle_speed);
render_state->camera.position.z = z; 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 // Zoom in/out with up/down keys
if (IsKeyDown(KEY_UP) && camera_distance > 10.0f) { if (IsKeyDown(KEY_UP) && camera_distance > 10.0f) {
Vector3 direction = Vector3Subtract(render_state->camera.target, render_state->camera.position); Vector3 direction = Vector3Normalize(Vector3Subtract(render_state->camera.target, render_state->camera.position));
direction = Vector3Normalize(direction);
render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); 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)) { if (IsKeyDown(KEY_DOWN)) {
Vector3 direction = Vector3Subtract(render_state->camera.position, render_state->camera.target); Vector3 direction = Vector3Normalize(Vector3Subtract(render_state->camera.position, render_state->camera.target));
direction = Vector3Normalize(direction);
render_state->camera.position = Vector3Add(render_state->camera.position, Vector3Scale(direction, 2.0f)); 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 // Toggle info display with I key
@ -98,6 +159,9 @@ void update_camera(RenderState* render_state, SimulationState* sim) {
render_state->camera_follow_body = false; 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) // Transform from simulation coordinates (XY plane) to render coordinates (XZ plane)

2
src/renderer.h

@ -16,6 +16,8 @@ struct RenderState {
int body_list_scroll; // Scroll position for body list int body_list_scroll; // Scroll position for body list
int body_list_active; // Active item index in body list int body_list_active; // Active item index in body list
bool camera_follow_body; // Whether camera follows selected body 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 // Renderer initialization and cleanup

Loading…
Cancel
Save