Browse Source

Implement dynamic camera zoom with percentage-based speed and distance clamping

main
cinnaboot 5 months ago
parent
commit
e6c70acd77
  1. 185
      docs/planning/dynamic-camera-zoom-plan.md
  2. 63
      src/renderer.cpp

185
docs/planning/dynamic-camera-zoom-plan.md

@ -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

63
src/renderer.cpp

@ -5,7 +5,6 @@
// Camera control constants
#define CAMERA_ORBIT_ANGLE_SPEED 0.02f
#define CAMERA_ZOOM_DELTA 2.0f
#define CAMERA_MIN_DISTANCE 0.1f
#define CAMERA_INITIAL_HEIGHT_FACTOR 0.3f
#define CAMERA_INITIAL_FOV 45.0f
@ -19,6 +18,22 @@
// Initial camera distance fallback
#define INITIAL_DISTANCE_RADIUS_MULTIPLIER 100.0f
// Dynamic zoom settings
#define ZOOM_PERCENTAGE 0.07f
#define ZOOM_MIN_DELTA 0.05f
#define ZOOM_MAX_DELTA 5.0f
// Minimum distance settings (in render units)
#define MIN_DISTANCE_BODY_RADIUS_MULT 2.75f
#define MIN_DISTANCE_SPACECRAFT_DEFAULT 0.5f
#define MIN_DISTANCE_SPACECRAFT_PARENT_RADIUS_MULT 0.1f
// Zoom direction enumeration for improved readability
enum ZoomDirection {
ZOOM_IN = 1,
ZOOM_OUT = -1
};
// TODO: Consider extracting other hardcoded constants:
// - Reference grid parameters (lines 600-610)
// - Spacecraft rendering (screen size, color)
@ -220,12 +235,50 @@ static void rotate_camera_orbitally(RenderState* render_state, float angle) {
}
}
static void zoom_camera(RenderState* render_state, float distance_delta) {
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 {
return MIN_DISTANCE_SPACECRAFT_DEFAULT;
}
} else {
return CAMERA_MIN_DISTANCE;
}
return target_radius * MIN_DISTANCE_BODY_RADIUS_MULT;
}
static void zoom_camera(RenderState* render_state, SimulationState* sim, ZoomDirection zoom_dir) {
Vector3 to_target = Vector3Subtract(render_state->camera.target, render_state->camera.position);
Vector3 direction = Vector3Normalize(to_target);
float camera_distance = Vector3Length(to_target);
if (camera_distance - distance_delta <= CAMERA_MIN_DISTANCE) return;
float raw_delta = camera_distance * ZOOM_PERCENTAGE * (float)zoom_dir;
float distance_delta = raw_delta;
if (fabsf(distance_delta) < ZOOM_MIN_DELTA) {
distance_delta = ZOOM_MIN_DELTA * (float)zoom_dir;
}
if (fabsf(distance_delta) > ZOOM_MAX_DELTA) {
distance_delta = ZOOM_MAX_DELTA * (float)zoom_dir;
}
float min_dist = get_target_min_distance(render_state, sim);
if (zoom_dir == ZOOM_IN) {
if (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, distance_delta));
@ -275,10 +328,10 @@ void update_camera(RenderState* render_state, SimulationState* sim) {
}
if (IsKeyDown(KEY_UP)) {
zoom_camera(render_state, CAMERA_ZOOM_DELTA);
zoom_camera(render_state, sim, ZOOM_IN);
}
if (IsKeyDown(KEY_DOWN)) {
zoom_camera(render_state, -CAMERA_ZOOM_DELTA);
zoom_camera(render_state, sim, ZOOM_OUT);
}
update_last_target(render_state);

Loading…
Cancel
Save