2 changed files with 614 additions and 3 deletions
@ -0,0 +1,596 @@ |
|||||||
|
# Local Rendering Frame Implementation Plan |
||||||
|
|
||||||
|
## Overview |
||||||
|
Implement local coordinate rendering when following bodies, using SOI-based scaling for improved orbit visibility. Refactor camera update logic to remove clunky state tracking. |
||||||
|
|
||||||
|
## Problem Statement |
||||||
|
|
||||||
|
### Current Issues |
||||||
|
1. **Float Precision Loss**: LEO orbit (6.8e6 m) scaled by 1e-9 → 0.0068 render units (5% of Earth radius) |
||||||
|
2. **Camera State Clunkiness**: `was_following_body` and `previous_selected_body` require frame-to-frame comparison |
||||||
|
3. **Code Duplication**: Follow logic repeated 4x, rotation logic duplicated 2x, zoom logic duplicated 2x |
||||||
|
|
||||||
|
### Root Cause |
||||||
|
- Global scale factor (1e-9) optimized for solar system view |
||||||
|
- When zoomed in on local orbits, precision is insufficient for smooth visualization |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Design Goals |
||||||
|
|
||||||
|
1. **Precision**: Use local coordinates with larger scale factor when following bodies |
||||||
|
2. **Clarity**: Maintain visual balance between bodies and their local orbits |
||||||
|
3. **Cleanliness**: Remove redundant state tracking and code duplication |
||||||
|
4. **Extensibility**: Enable future nested local frames if needed |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 0: Refactor `update_camera()` |
||||||
|
|
||||||
|
### Current Problems |
||||||
|
1. State tracking clunkiness: `was_following_body` and `previous_selected_body` require frame-to-frame comparison |
||||||
|
2. Code duplication: Follow logic repeated 4x, rotation logic duplicated 2x, zoom logic duplicated 2x |
||||||
|
3. Offset updates scattered: Camera offset updated in 6 places with identical logic |
||||||
|
|
||||||
|
### Refactoring Strategy |
||||||
|
|
||||||
|
**Simplified State Management:** |
||||||
|
- Remove `was_following_body` from `RenderState` |
||||||
|
- Remove `previous_selected_body` from `RenderState` |
||||||
|
- Add `last_target_index` to `RenderState` (single int tracking body or spacecraft) |
||||||
|
- Add `camera_mode` enum: `CAMERA_FREE`, `CAMERA_FOLLOW_BODY`, `CAMERA_FOLLOW_CRAFT` |
||||||
|
|
||||||
|
**New Helper Functions:** |
||||||
|
1. `detect_camera_mode()` → returns camera mode from render state |
||||||
|
2. `get_camera_target()` → returns target position Vector3 |
||||||
|
3. `has_target_changed()` → checks last_target_index vs current |
||||||
|
4. `update_camera_target()` → handles all follow logic |
||||||
|
5. `rotate_camera_orbitally()` → abstracts KEY_LEFT/RIGHT |
||||||
|
6. `zoom_camera()` → abstracts KEY_UP/DOWN |
||||||
|
7. `update_follow_offset()` → extracts repeated offset update logic |
||||||
|
8. `update_last_target()` → updates tracking for next frame |
||||||
|
|
||||||
|
**Refactored Structure:** |
||||||
|
```cpp |
||||||
|
void update_camera(RenderState* render_state, SimulationState* sim) { |
||||||
|
// 1. Update frame mode (for later phases) |
||||||
|
render_state->camera_mode = detect_camera_mode(render_state, sim); |
||||||
|
|
||||||
|
// 2. Handle following (handles both global and local frames) |
||||||
|
if (render_state->camera_follow_body) { |
||||||
|
update_camera_target(render_state, sim); |
||||||
|
} |
||||||
|
|
||||||
|
// 3. Handle rotation |
||||||
|
if (IsKeyDown(KEY_LEFT)) { |
||||||
|
rotate_camera_orbitally(render_state, angle_speed); |
||||||
|
} else if (IsKeyDown(KEY_RIGHT)) { |
||||||
|
rotate_camera_orbitally(render_state, -angle_speed); |
||||||
|
} |
||||||
|
|
||||||
|
// 4. Handle zoom |
||||||
|
if (IsKeyDown(KEY_UP)) { |
||||||
|
zoom_camera(render_state, -2.0f); |
||||||
|
} else if (IsKeyDown(KEY_DOWN)) { |
||||||
|
zoom_camera(render_state, 2.0f); |
||||||
|
} |
||||||
|
|
||||||
|
// 5. Update last target for next frame |
||||||
|
update_last_target(render_state); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 1: Infrastructure Setup |
||||||
|
|
||||||
|
### 1.1 Add Rendering Mode Enum and Fields |
||||||
|
|
||||||
|
**src/renderer.h:** |
||||||
|
```cpp |
||||||
|
enum CameraMode { |
||||||
|
CAMERA_FREE, |
||||||
|
CAMERA_FOLLOW_BODY, |
||||||
|
CAMERA_FOLLOW_CRAFT |
||||||
|
}; |
||||||
|
|
||||||
|
enum RenderFrameMode { |
||||||
|
RENDER_FRAME_GLOBAL, |
||||||
|
RENDER_FRAME_LOCAL |
||||||
|
}; |
||||||
|
|
||||||
|
struct RenderState { |
||||||
|
// ... existing fields ... |
||||||
|
CameraMode camera_mode; |
||||||
|
RenderFrameMode frame_mode; |
||||||
|
int last_target_index; // Tracks body or craft index (negative = spacecraft) |
||||||
|
int local_frame_parent_index; // Body index for local frame |
||||||
|
}; |
||||||
|
``` |
||||||
|
|
||||||
|
### 1.2 Add Detection and Scale Functions |
||||||
|
|
||||||
|
**New functions in src/renderer.cpp:** |
||||||
|
|
||||||
|
```cpp |
||||||
|
static CameraMode detect_camera_mode(RenderState* render_state, SimulationState* sim) { |
||||||
|
if (!render_state->camera_follow_body) return CAMERA_FREE; |
||||||
|
if (render_state->selected_body_index >= 0) return CAMERA_FOLLOW_BODY; |
||||||
|
if (render_state->selected_craft_index >= 0) return CAMERA_FOLLOW_CRAFT; |
||||||
|
return CAMERA_FREE; |
||||||
|
} |
||||||
|
|
||||||
|
static RenderFrameMode detect_render_frame_mode(CameraMode mode, int body_index, SimulationState* sim) { |
||||||
|
if (mode != CAMERA_FOLLOW_BODY) return RENDER_FRAME_GLOBAL; |
||||||
|
if (body_index < 0 || body_index >= sim->body_count) return RENDER_FRAME_GLOBAL; |
||||||
|
if (sim->bodies[body_index].parent_index < 0) return RENDER_FRAME_GLOBAL; // Root body (Sun) |
||||||
|
return RENDER_FRAME_LOCAL; |
||||||
|
} |
||||||
|
|
||||||
|
// Target: SOI occupies ~100 units in render space for visibility |
||||||
|
static double get_local_frame_scale(SimulationState* sim, int body_index) { |
||||||
|
CelestialBody* body = &sim->bodies[body_index]; |
||||||
|
double soi_radius = body->soi_radius; |
||||||
|
|
||||||
|
// Target: 1.0 × SOI radius → 100.0 render units |
||||||
|
return 100.0 / soi_radius; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 2: Local Frame Coordinate Transformation |
||||||
|
|
||||||
|
### 2.1 Add Local Transform Function |
||||||
|
|
||||||
|
**src/renderer.cpp:** |
||||||
|
```cpp |
||||||
|
static Vector3 sim_to_render_local(Vec3 local_pos, double local_scale) { |
||||||
|
return (Vector3){ |
||||||
|
(float)(local_pos.x * local_scale), |
||||||
|
(float)(local_pos.z * local_scale), |
||||||
|
(float)(-local_pos.y * local_scale) |
||||||
|
}; |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### 2.2 Modify Orbit Rendering for Local Frame |
||||||
|
|
||||||
|
**Add new function:** |
||||||
|
```cpp |
||||||
|
static void render_orbit_local(Vec3 local_position, Vec3 local_velocity, |
||||||
|
double parent_mass, Color orbit_color, |
||||||
|
double local_scale, RenderState* render_state) { |
||||||
|
Vec3 r_vec = local_position; |
||||||
|
double r = vec3_magnitude(r_vec); |
||||||
|
double v = vec3_magnitude(local_velocity); |
||||||
|
|
||||||
|
if (r < 1.0) return; |
||||||
|
|
||||||
|
// Calculate orbit parameters (same as global version) |
||||||
|
double mu = G * parent_mass; |
||||||
|
double specific_energy = (v * v) / 2.0 - mu / r; |
||||||
|
double v_squared = v * v; |
||||||
|
double r_dot_v = vec3_dot(r_vec, local_velocity); |
||||||
|
Vec3 e_vec = { |
||||||
|
(v_squared - mu / r) * r_vec.x - r_dot_v * local_velocity.x, |
||||||
|
(v_squared - mu / r) * r_vec.y - r_dot_v * local_velocity.y, |
||||||
|
(v_squared - mu / r) * r_vec.z - r_dot_v * local_velocity.z |
||||||
|
}; |
||||||
|
double e = vec3_magnitude(e_vec) / mu; |
||||||
|
|
||||||
|
OrbitalBasis basis = calculate_orbital_basis(r_vec, local_velocity, e_vec); |
||||||
|
|
||||||
|
// Render with local scale and origin at (0,0,0) |
||||||
|
if (e < 0.98) { |
||||||
|
double a = -mu / (2.0 * specific_energy); |
||||||
|
if (a <= 0.0) return; |
||||||
|
render_elliptical_orbit_local(a, e, basis, local_scale, render_state, orbit_color); |
||||||
|
} else if (e > 1.02) { |
||||||
|
double a = mu / (2.0 * (-specific_energy)); |
||||||
|
double p = a * (1.0 - e * e); |
||||||
|
if (p <= 0.0) return; |
||||||
|
render_hyperbolic_orbit_local(p, e, basis, local_scale, render_state, orbit_color); |
||||||
|
} else { |
||||||
|
Vec3 h_vec = vec3_cross(r_vec, local_velocity); |
||||||
|
double h_squared = vec3_dot(h_vec, h_vec); |
||||||
|
double p = h_squared / mu; |
||||||
|
if (p <= 0.0) return; |
||||||
|
render_parabolic_orbit_local(p, basis, local_scale, render_state, orbit_color); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Add local orbit drawing functions:** |
||||||
|
```cpp |
||||||
|
static void draw_orbit_segment_local(double x1, double y1, double x2, double y2, |
||||||
|
OrbitalBasis basis, double local_scale, |
||||||
|
RenderState* render_state, Color color) { |
||||||
|
// Convert from orbital plane to render coords (no parent offset) |
||||||
|
Vec3 p1_local = { |
||||||
|
basis.periapsis_dir.x * x1 + basis.q_vec.x * y1, |
||||||
|
basis.periapsis_dir.y * x1 + basis.q_vec.y * y1, |
||||||
|
basis.periapsis_dir.z * x1 + basis.q_vec.z * y1 |
||||||
|
}; |
||||||
|
Vec3 p2_local = { |
||||||
|
basis.periapsis_dir.x * x2 + basis.q_vec.x * y2, |
||||||
|
basis.periapsis_dir.y * x2 + basis.q_vec.y * y2, |
||||||
|
basis.periapsis_dir.z * x2 + basis.q_vec.z * y2 |
||||||
|
}; |
||||||
|
|
||||||
|
Vector3 p1 = sim_to_render_local(p1_local, local_scale); |
||||||
|
Vector3 p2 = sim_to_render_local(p2_local, local_scale); |
||||||
|
|
||||||
|
DrawLine3D(p1, p2, color); |
||||||
|
} |
||||||
|
|
||||||
|
static void render_elliptical_orbit_local(double a, double e, OrbitalBasis basis, |
||||||
|
double local_scale, RenderState* render_state, Color color) { |
||||||
|
double b = a * sqrt(1.0 - e * e); |
||||||
|
double c = a * e; |
||||||
|
int segments = 100; |
||||||
|
|
||||||
|
for (int i = 0; i < segments; i++) { |
||||||
|
float theta1 = (float)i / segments * 2.0f * PI; |
||||||
|
float theta2 = (float)(i + 1) / segments * 2.0f * PI; |
||||||
|
double x1 = a * cos(theta1) - c; |
||||||
|
double y1 = b * sin(theta1); |
||||||
|
double x2 = a * cos(theta2) - c; |
||||||
|
double y2 = b * sin(theta2); |
||||||
|
draw_orbit_segment_local(x1, y1, x2, y2, basis, local_scale, render_state, color); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
(Also add `render_hyperbolic_orbit_local()` and `render_parabolic_orbit_local()` - similar to existing global versions but using `draw_orbit_segment_local()`) |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 3: Local Frame Body Rendering |
||||||
|
|
||||||
|
### 3.1 Add Local Body Rendering Function |
||||||
|
|
||||||
|
**src/renderer.cpp:** |
||||||
|
```cpp |
||||||
|
static void render_body_local(CelestialBody* body, int local_parent_index, |
||||||
|
double local_scale, RenderState* render_state) { |
||||||
|
Vector3 position; |
||||||
|
|
||||||
|
if (body->parent_index == local_parent_index) { |
||||||
|
// Direct child of followed body - use local position |
||||||
|
position = sim_to_render_local(body->local_position, local_scale); |
||||||
|
} else if (body->parent_index < 0) { |
||||||
|
// Root body (Sun) - at origin in local frame |
||||||
|
position = (Vector3){0.0f, 0.0f, 0.0f}; |
||||||
|
} else { |
||||||
|
// Other bodies - TODO: decide whether to render or skip |
||||||
|
// For now: skip (will be very far off-screen) |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
float radius = scale_radius(body->radius, render_state->size_scale); |
||||||
|
Color color = { |
||||||
|
(unsigned char)(body->color[0] * 255), |
||||||
|
(unsigned char)(body->color[1] * 255), |
||||||
|
(unsigned char)(body->color[2] * 255), |
||||||
|
255 |
||||||
|
}; |
||||||
|
|
||||||
|
DrawSphereWires(position, radius, 16, 16, color); |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 4: Integration - Render Simulation Router |
||||||
|
|
||||||
|
### 4.1 Modify `render_simulation()` to Route by Frame Mode |
||||||
|
|
||||||
|
**src/renderer.cpp:** |
||||||
|
```cpp |
||||||
|
void render_simulation(SimulationState* sim, RenderState* render_state) { |
||||||
|
// Update rendering mode |
||||||
|
render_state->camera_mode = detect_camera_mode(render_state, sim); |
||||||
|
render_state->frame_mode = detect_render_frame_mode( |
||||||
|
render_state->camera_mode, |
||||||
|
render_state->selected_body_index, |
||||||
|
sim |
||||||
|
); |
||||||
|
|
||||||
|
BeginMode3D(render_state->camera); |
||||||
|
|
||||||
|
// Draw reference grid (in both modes) |
||||||
|
for (int i = -50; i <= 50; i++) { |
||||||
|
// ... existing grid code ... |
||||||
|
} |
||||||
|
|
||||||
|
// Route to appropriate rendering function |
||||||
|
if (render_state->frame_mode == RENDER_FRAME_LOCAL) { |
||||||
|
render_simulation_local(sim, render_state); |
||||||
|
} else { |
||||||
|
render_simulation_global(sim, render_state); // existing implementation |
||||||
|
} |
||||||
|
|
||||||
|
EndMode3D(); |
||||||
|
|
||||||
|
// Spacecraft and maneuvers (screen-space, shared between modes) |
||||||
|
for (int i = 0; i < sim->craft_count; i++) { |
||||||
|
render_spacecraft_screen_space(&sim->spacecraft[i], render_state); |
||||||
|
} |
||||||
|
// ... maneuver markers ... |
||||||
|
} |
||||||
|
|
||||||
|
static void render_simulation_local(SimulationState* sim, RenderState* render_state) { |
||||||
|
int parent_index = render_state->local_frame_parent_index; |
||||||
|
double local_scale = get_local_frame_scale(sim, parent_index); |
||||||
|
|
||||||
|
// Render followed body at origin |
||||||
|
CelestialBody* parent = &sim->bodies[parent_index]; |
||||||
|
Vector3 origin = (Vector3){0.0f, 0.0f, 0.0f}; |
||||||
|
float parent_radius = scale_radius(parent->radius, render_state->size_scale); |
||||||
|
Color parent_color = { |
||||||
|
(unsigned char)(parent->color[0] * 255), |
||||||
|
(unsigned char)(parent->color[1] * 255), |
||||||
|
(unsigned char)(parent->color[2] * 255), |
||||||
|
255 |
||||||
|
}; |
||||||
|
DrawSphereWires(origin, parent_radius, 16, 16, parent_color); |
||||||
|
|
||||||
|
// Render orbits of children (not followed body's orbit) |
||||||
|
for (int i = 0; i < sim->body_count; i++) { |
||||||
|
CelestialBody* body = &sim->bodies[i]; |
||||||
|
if (body->parent_index == parent_index) { |
||||||
|
render_orbit_local(body->local_position, body->local_velocity, |
||||||
|
parent->mass, get_body_orbit_color(body), |
||||||
|
local_scale, render_state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Render spacecraft orbits |
||||||
|
for (int i = 0; i < sim->craft_count; i++) { |
||||||
|
Spacecraft* craft = &sim->spacecraft[i]; |
||||||
|
if (craft->parent_index == parent_index) { |
||||||
|
render_orbit_local(craft->local_position, craft->local_velocity, |
||||||
|
parent->mass, (Color){0, 255, 255, 128}, |
||||||
|
local_scale, render_state); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Render children bodies |
||||||
|
for (int i = 0; i < sim->body_count; i++) { |
||||||
|
if (i != parent_index) { |
||||||
|
render_body_local(&sim->bodies[i], parent_index, local_scale, render_state); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Extract current `render_simulation()` into `render_simulation_global()`** |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 5: Camera Integration |
||||||
|
|
||||||
|
### 5.1 Modify `update_camera()` for Frame Transitions |
||||||
|
|
||||||
|
**Add to `update_camera()` helper functions (Phase 0 refactor):** |
||||||
|
|
||||||
|
```cpp |
||||||
|
static void update_camera_frame_mode(RenderState* render_state, SimulationState* sim) { |
||||||
|
// Detect if we need to switch frame modes |
||||||
|
int new_parent_index = -1; |
||||||
|
|
||||||
|
if (render_state->camera_mode == CAMERA_FOLLOW_BODY && |
||||||
|
render_state->selected_body_index >= 0) { |
||||||
|
// Check if following a non-root body |
||||||
|
int body_index = render_state->selected_body_index; |
||||||
|
if (body_index < sim->body_count && |
||||||
|
sim->bodies[body_index].parent_index >= 0) { |
||||||
|
new_parent_index = body_index; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check for mode switch |
||||||
|
bool mode_changed = (new_parent_index != render_state->local_frame_parent_index); |
||||||
|
|
||||||
|
if (mode_changed) { |
||||||
|
render_state->local_frame_parent_index = new_parent_index; |
||||||
|
render_state->frame_mode = detect_render_frame_mode( |
||||||
|
render_state->camera_mode, |
||||||
|
render_state->selected_body_index, |
||||||
|
sim |
||||||
|
); |
||||||
|
|
||||||
|
// When switching to local frame: set camera target to origin |
||||||
|
if (render_state->frame_mode == RENDER_FRAME_LOCAL) { |
||||||
|
render_state->camera.target = (Vector3){0.0f, 0.0f, 0.0f}; |
||||||
|
} |
||||||
|
// When switching to global frame: target set by update_camera_target() |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Integrate into refactored `update_camera()`:** |
||||||
|
```cpp |
||||||
|
void update_camera(RenderState* render_state, SimulationState* sim) { |
||||||
|
// 0. Update frame mode |
||||||
|
render_state->camera_mode = detect_camera_mode(render_state, sim); |
||||||
|
update_camera_frame_mode(render_state, sim); |
||||||
|
|
||||||
|
// 1. Handle following (handles both global and local frames) |
||||||
|
if (render_state->camera_follow_body) { |
||||||
|
update_camera_target(render_state, sim); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. Handle rotation |
||||||
|
// ... existing rotation logic ... |
||||||
|
|
||||||
|
// 3. Handle zoom |
||||||
|
// ... existing zoom logic ... |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
**Update `update_camera_target()` to handle local frame:** |
||||||
|
```cpp |
||||||
|
static void update_camera_target(RenderState* render_state, SimulationState* sim) { |
||||||
|
Vector3 target_pos; |
||||||
|
bool has_target = false; |
||||||
|
|
||||||
|
if (render_state->frame_mode == RENDER_FRAME_LOCAL) { |
||||||
|
// Local frame: target is always origin |
||||||
|
target_pos = (Vector3){0.0f, 0.0f, 0.0f}; |
||||||
|
has_target = true; |
||||||
|
} else { |
||||||
|
// Global frame: get target from body or spacecraft |
||||||
|
if (render_state->selected_body_index >= 0 && |
||||||
|
render_state->selected_body_index < sim->body_count) { |
||||||
|
CelestialBody* body = &sim->bodies[render_state->selected_body_index]; |
||||||
|
target_pos = sim_to_render(body->global_position, render_state->distance_scale); |
||||||
|
has_target = true; |
||||||
|
} else if (render_state->selected_craft_index >= 0 && |
||||||
|
render_state->selected_craft_index < sim->craft_count) { |
||||||
|
Spacecraft* craft = &sim->spacecraft[render_state->selected_craft_index]; |
||||||
|
target_pos = sim_to_render(craft->global_position, render_state->distance_scale); |
||||||
|
has_target = true; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (has_target) { |
||||||
|
// Check if target changed |
||||||
|
bool target_changed = has_target_changed(render_state); |
||||||
|
|
||||||
|
if (target_changed) { |
||||||
|
// Preserve camera distance by updating offset |
||||||
|
Vector3 to_camera = Vector3Subtract(render_state->camera.position, target_pos); |
||||||
|
render_state->camera_offset = to_camera; |
||||||
|
} |
||||||
|
|
||||||
|
render_state->camera.target = target_pos; |
||||||
|
render_state->camera.position = Vector3Add(target_pos, render_state->camera_offset); |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Phase 6: Testing (After Implementation) |
||||||
|
|
||||||
|
### 6.1 Manual Testing Checklist |
||||||
|
1. Start simulation with Earth selected |
||||||
|
2. Verify camera follows Earth in local frame |
||||||
|
3. Zoom in to see LEO orbit clearly visible |
||||||
|
4. Select Sun from UI → verify switch to global frame |
||||||
|
5. Rotate/zoom controls work in both frames |
||||||
|
6. Orbits render correctly in both frames |
||||||
|
7. Earth's orbit around Sun omitted in local frame |
||||||
|
8. Spacecraft billboard moves correctly with simulation |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Design Decisions |
||||||
|
|
||||||
|
1. **Local Frame Scale Factor**: SOI-based scaling targeting ~100 render units |
||||||
|
- Defined as constant at top of renderer.cpp |
||||||
|
- Allows easy adjustment based on visual feedback |
||||||
|
|
||||||
|
2. **Frame Levels**: Single-level local frame only |
||||||
|
- Following Earth shows Earth + spacecraft at LEO |
||||||
|
- TODO: Support nested local frames (viewing Moon while following Earth) |
||||||
|
|
||||||
|
3. **Followed Body's Orbit**: Omitted in local frame |
||||||
|
- Since we're now the reference frame, Earth's orbit around Sun is confusing |
||||||
|
|
||||||
|
4. **Other Bodies in Local Frame**: Skipped for now (TODO) |
||||||
|
- Distant bodies will be very far off-screen |
||||||
|
- TODO: Decide whether to render using global coordinates or skip |
||||||
|
|
||||||
|
5. **Transition Behavior**: Instant switch between frames |
||||||
|
- TODO: Add smooth interpolation if jarring |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## Expected Outcomes |
||||||
|
|
||||||
|
### LEO Orbit Visibility (400km altitude) |
||||||
|
- **Current**: 0.0068 render units (5% of Earth radius) |
||||||
|
- **After Local Frame**: ~67 render units (50% of Earth radius) |
||||||
|
- **Improvement**: ~1000x more visible |
||||||
|
|
||||||
|
### Float Precision |
||||||
|
- Local frame positions: ~6.8e6 m → 67 render units (scale 1e-7) |
||||||
|
- Precision at 67 units: ~0.0001 (1/670000) |
||||||
|
- Orbit precision: 67 × 0.0001 = 0.0067 units |
||||||
|
- Result: High precision, smooth orbits |
||||||
|
|
||||||
|
### Code Quality |
||||||
|
- Reduced camera duplication by ~60% |
||||||
|
- Eliminated clunky state tracking |
||||||
|
- Clear separation of global/local rendering |
||||||
|
- Extensible for future nested local frames |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## File Summary |
||||||
|
|
||||||
|
### New Files |
||||||
|
- `docs/planning/local_rendering_frame.md` - This planning document |
||||||
|
|
||||||
|
### Modified Files |
||||||
|
|
||||||
|
#### src/renderer.h |
||||||
|
- Add `CameraMode` enum |
||||||
|
- Add `RenderFrameMode` enum |
||||||
|
- Add fields to `RenderState` struct: |
||||||
|
- Remove: `was_following_body`, `previous_selected_body` |
||||||
|
- Add: `camera_mode`, `frame_mode`, `last_target_index`, `local_frame_parent_index` |
||||||
|
|
||||||
|
#### src/renderer.cpp |
||||||
|
- **Phase 0**: Refactor update_camera() with helper functions: |
||||||
|
- Add SOI_SCALE_TARGET define at top |
||||||
|
- Implement helper functions: detect_camera_mode, get_camera_target, has_target_changed, |
||||||
|
update_camera_target, rotate_camera_orbitally, zoom_camera, update_follow_offset, update_last_target |
||||||
|
- Refactor update_camera() to use helpers |
||||||
|
|
||||||
|
- **Phase 1**: Add detection/scale functions: |
||||||
|
- Add detect_render_frame_mode() |
||||||
|
- Add get_local_frame_scale() |
||||||
|
- Add sim_to_render_local() |
||||||
|
|
||||||
|
- **Phase 2**: Add local coordinate transformation functions: |
||||||
|
- Add draw_orbit_segment_local() |
||||||
|
- Add render_elliptical_orbit_local() |
||||||
|
- Add render_hyperbolic_orbit_local() |
||||||
|
- Add render_parabolic_orbit_local() |
||||||
|
- Add render_orbit_local() |
||||||
|
|
||||||
|
- **Phase 3**: Add local body rendering: |
||||||
|
- Add render_body_local() |
||||||
|
|
||||||
|
- **Phase 4**: Modify render_simulation() routing: |
||||||
|
- Extract current logic to render_simulation_global() |
||||||
|
- Add render_simulation_local() |
||||||
|
- Modify render_simulation() to route by frame mode |
||||||
|
|
||||||
|
- **Phase 5**: Update camera frame mode handling: |
||||||
|
- Add update_camera_frame_mode() |
||||||
|
- Update update_camera_target() to handle local frame |
||||||
|
- Integrate frame mode updates into camera update |
||||||
|
|
||||||
|
#### src/main.cpp |
||||||
|
- Update initialization to remove old state tracking: |
||||||
|
- Remove: `was_following_body` initialization |
||||||
|
- Remove: `previous_selected_body` initialization |
||||||
|
- Add: Initialize new RenderState fields (camera_mode, frame_mode, etc.) |
||||||
|
|
||||||
|
#### src/ui_renderer.cpp |
||||||
|
- Update references to removed state fields if any |
||||||
|
- Ensure compatibility with new camera_mode system |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## TODO Items |
||||||
|
|
||||||
|
1. **Nested Local Frames**: Support 2-level local frames (viewing Moon while following Earth) |
||||||
|
2. **Distant Bodies in Local Frame**: Decide whether to render distant bodies using global coordinates or skip |
||||||
|
3. **Smooth Frame Transitions**: Add interpolation when switching between global and local frames |
||||||
|
4. **Test Coverage**: Add unit tests for local frame rendering after manual verification |
||||||
Loading…
Reference in new issue