2 changed files with 243 additions and 5 deletions
@ -0,0 +1,185 @@ |
|||||||
|
# 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 |
||||||
Loading…
Reference in new issue