# Dynamic Camera Zoom Implementation Plan ## Overview Replace fixed zoom behavior with adaptive zoom speed and minimum distance based on current camera distance and target characteristics. ## Goals - Zoom speed adapts to current distance (faster when far, slower when close) - Minimum camera distance based on target's physical size (radius) - Prevent zooming through body surfaces - Maintain smooth, controllable zoom with delta clamping ## Changes Required ### 1. Add New Constants (src/renderer.cpp) Add after existing camera constants: ```cpp // Dynamic zoom settings #define ZOOM_PERCENTAGE 0.07f // 7% of current distance per keypress #define ZOOM_MIN_DELTA 0.05f // Minimum zoom delta (prevents too-slow zoom) #define ZOOM_MAX_DELTA 5.0f // Maximum zoom delta (prevents too-fast zoom) // Minimum distance settings (in render units) #define MIN_DISTANCE_BODY_RADIUS_MULT 2.75f // 2.75x body radius minimum #define MIN_DISTANCE_SPACECRAFT_DEFAULT 0.5f // Fallback for orphaned spacecraft #define MIN_DISTANCE_SPACECRAFT_PARENT_RADIUS_MULT 0.1f // Scale parent radius for spacecraft ``` ### 2. Add Zoom Direction Enum (src/renderer.cpp) Local enum for improved readability: ```cpp enum ZoomDirection { ZOOM_IN = -1.0f, ZOOM_OUT = 1.0f }; ``` ### 3. Modify `zoom_camera()` Function **Location:** src/renderer.cpp:223 **Changes:** - Update signature: `static void zoom_camera(RenderState* render_state, SimulationState* sim, ZoomDirection direction)` - Replace fixed `CAMERA_ZOOM_DELTA` with dynamic calculation - Add minimum distance based on target radius **Implementation:** ```cpp static void zoom_camera(RenderState* render_state, SimulationState* sim, ZoomDirection direction) { Vector3 to_target = Vector3Subtract(render_state->camera.target, render_state->camera.position); Vector3 direction_vec = Vector3Normalize(to_target); float camera_distance = Vector3Length(to_target); // Calculate dynamic zoom delta based on current distance float raw_delta = camera_distance * ZOOM_PERCENTAGE * (float)direction; // Clamp delta to reasonable bounds float distance_delta = raw_delta; if (fabsf(distance_delta) < ZOOM_MIN_DELTA) { distance_delta = ZOOM_MIN_DELTA * (float)direction; } if (fabsf(distance_delta) > ZOOM_MAX_DELTA) { distance_delta = ZOOM_MAX_DELTA * (float)direction; } // Get target-specific minimum distance float min_dist = get_target_min_distance(render_state, sim); // Check if zoom would exceed minimum distance if (direction == ZOOM_IN && 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_vec, distance_delta)); if (render_state->camera_target_enabled) { render_state->camera_offset = Vector3Subtract( render_state->camera.position, render_state->camera.target ); } } ``` ### 4. Add `get_target_min_distance()` Helper Function **Location:** Add new static function before `zoom_camera()` ```cpp static float get_target_min_distance(RenderState* render_state, SimulationState* sim) { float target_radius = 0.0f; if (render_state->selected_body_index >= 0) { CelestialBody* body = &sim->bodies[render_state->selected_body_index]; target_radius = scale_radius(body->radius, render_state->distance_scale); } else if (render_state->selected_craft_index >= 0) { Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; if (craft->parent_index >= 0 && craft->parent_index < sim->body_count) { CelestialBody* parent = &sim->bodies[craft->parent_index]; target_radius = scale_radius(parent->radius, render_state->distance_scale); target_radius *= MIN_DISTANCE_SPACECRAFT_PARENT_RADIUS_MULT; } else { // Spacecraft without parent (shouldn't happen in valid configs) return MIN_DISTANCE_SPACECRAFT_DEFAULT; } } else { // No target selected, use existing fallback return CAMERA_MIN_DISTANCE; } return target_radius * MIN_DISTANCE_BODY_RADIUS_MULT; } ``` ### 5. Update `update_camera()` Calls **Location:** src/renderer.cpp:277-282 **Change from:** ```cpp if (IsKeyDown(KEY_UP)) { zoom_camera(render_state, CAMERA_ZOOM_DELTA); } if (IsKeyDown(KEY_DOWN)) { zoom_camera(render_state, -CAMERA_ZOOM_DELTA); } ``` **Change to:** ```cpp if (IsKeyDown(KEY_UP)) { zoom_camera(render_state, sim, ZOOM_IN); } if (IsKeyDown(KEY_DOWN)) { zoom_camera(render_state, sim, ZOOM_OUT); } ``` ### 6. Remove/Update Old Constants **Location:** src/renderer.cpp:8 **Remove:** `#define CAMERA_ZOOM_DELTA 2.0f` (replaced by dynamic calculation) **Keep:** `#define CAMERA_MIN_DISTANCE 0.1f` (as fallback when no target) ## Technical Details ### Dynamic Zoom Calculation 1. Calculate raw delta: `current_distance * ZOOM_PERCENTAGE * direction` 2. Clamp delta between `ZOOM_MIN_DELTA` and `ZOOM_MAX_DELTA` 3. Ensure sign matches zoom direction ### Minimum Distance Logic 1. For bodies: `body_radius * MIN_DISTANCE_BODY_RADIUS_MULT` 2. For spacecraft: `parent_radius * MIN_DISTANCE_SPACECRAFT_PARENT_RADIUS_MULT * MIN_DISTANCE_BODY_RADIUS_MULT` 3. For no target: `CAMERA_MIN_DISTANCE` (existing fallback) ### Sign Convention - `ZOOM_IN (-1.0)`: Move camera closer to target (decrease distance) - `ZOOM_OUT (+1.0)`: Move camera farther from target (increase distance) ### Edge Cases - Spacecraft without parent: Uses `MIN_DISTANCE_SPACECRAFT_DEFAULT` - No target selected: Uses existing `CAMERA_MIN_DISTANCE` - Very close zoom: Delta clamping prevents too-slow movement ## Implementation Order 1. Add new constants and enum to renderer.cpp 2. Implement `get_target_min_distance()` helper 3. Modify `zoom_camera()` function signature and implementation 4. Update `update_camera()` calls to `zoom_camera()` 5. Remove old `CAMERA_ZOOM_DELTA` constant 6. Build and test ## Testing Checklist - [ ] Zoom in/out works smoothly with new percentage-based behavior - [ ] Zoom speed adapts based on current distance (faster when far, slower when close) - [ ] Cannot zoom through body surface (minimum distance enforced) - [ ] Spacecraft zoom uses parent body radius (scaled down) - [ ] Zoom delta clamping prevents too-fast or too-slow zoom - [ ] No target selected still works (uses existing fallbacks) - [ ] Build succeeds without errors ## Future Enhancements (Not in scope) - Use parent's SOI radius instead of radius for more physically meaningful bounds - Consider orbital characteristics (semi-major axis) for context-aware minimums - Add configurable zoom speed presets (slow/normal/fast) - Smooth interpolation when zooming rapidly