Browse Source

Add enums and new fields for local frame rendering

main
cinnaboot 6 months ago
parent
commit
4459a86301
  1. 596
      docs/planning/local_rendering_frame.md
  2. 21
      src/renderer.h

596
docs/planning/local_rendering_frame.md

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

21
src/renderer.h

@ -6,6 +6,19 @@
#include "maneuver.h"
#include "raylib.h"
// Camera tracking modes
enum TrackingMode {
TRACKING_FREE,
TRACKING_BODY,
TRACKING_CRAFT
};
// Rendering coordinate frame modes
enum RenderFrameMode {
RENDER_FRAME_GLOBAL,
RENDER_FRAME_LOCAL
};
// Rendering state
struct RenderState {
Camera3D camera;
@ -13,10 +26,12 @@ struct RenderState {
double size_scale; // Scale factor for body sizes
int selected_body_index; // -1 = no selection
int selected_craft_index; // -1 = no selection
int previous_selected_body; // Previous selected body index
bool camera_follow_body; // Whether camera follows selected body
bool camera_target_enabled; // Whether camera follows selected body/craft
Vector3 camera_offset; // Offset from target when following body
bool was_following_body; // Previous frame follow state
TrackingMode 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
Texture2D spacecraft_texture; // Shared texture for all spacecraft
Texture2D maneuver_textures[6]; // One per burn direction

Loading…
Cancel
Save