Browse Source

Update rendering documentation to reflect relative rendering implementation

main
cinnaboot 5 months ago
parent
commit
2b68e2e579
  1. 596
      docs/planning/local_rendering_frame.md
  2. 60
      docs/rendering.md

596
docs/planning/local_rendering_frame.md

@ -1,596 +0,0 @@
# 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

60
docs/rendering.md

@ -1,7 +1,7 @@
# Rendering System - Technical Reference
## Overview
3D visualization system using raylib for interactive orbital mechanics simulation. Supports logarithmic distance scaling, orbit path rendering, spacecraft tracking, and maneuver planning visualization.
3D visualization system using raylib for interactive orbital mechanics simulation. Supports linear distance scaling, relative rendering with child indicators, orbit path rendering, spacecraft tracking, and maneuver planning visualization.
## Core Data Structure
@ -13,12 +13,11 @@ 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
int last_target_index; // Tracks body or craft index (negative = spacecraft)
int body_list_scroll; // Scroll position for body list
int body_list_active; // Active item index in body list
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
};
```
@ -45,23 +44,17 @@ Vector3 sim_to_render(Vec3 pos, double scale) {
### Scaling Factors
**Distance Scale:** `1e-9` (1 render unit = 1 billion meters)
- Optimized for solar system scale
- Used for position transformations
- Used for both position transformations and radius scaling
**Size Scale:** `0.02` (logarithmic radius scaling factor)
- Applied to body radii using logarithmic scaling
- Minimum visible radius: 0.01 units (ensures tiny bodies are visible)
**Size Scale:** Same as distance scale (linear scaling)
**Radius Scaling:**
```cpp
float scale_radius(double radius, double scale) {
float scaled = (float)(scale * log10(radius));
float min_radius = 0.01f;
return (scaled > min_radius) ? scaled : min_radius;
return (float)(radius * scale);
}
```
Uses logarithmic scaling to handle extreme radius ranges (1.5M to 700M meters).
Scale factor 0.02 produces: Sun ~0.18, planets 0.13-0.16, moons 0.12-0.13 render units.
Uses linear scaling for consistent representation at all scales.
## Camera System
@ -132,6 +125,19 @@ Scale factor 0.02 produces: Sun ~0.18, planets 0.13-0.16, moons 0.12-0.13 render
**Render Order:** Top layer, after spacecraft (rendered as 2D overlays after 3D scene)
### Child Indicators
Screen-space 2D overlays for children of selected body (similar to NASA Eyes).
**Rendering:**
- NASA Eyes-style hollow circles
- Radius: 20px, Thickness: 2px
- Text label centered inside indicator
- White color for bodies, cyan (0, 255, 255) for spacecraft
- Only shown when body selected (not when spacecraft selected)
- Uses `GetWorldToScreen()` for 2D positioning after `EndMode3D()`
**Purpose:** Indicates location of children bodies/spacecraft that may be off-screen or hard to locate
## Orbit Rendering
### Orbital Basis Calculation
@ -181,6 +187,25 @@ Scale factor 0.02 produces: Sun ~0.18, planets 0.13-0.16, moons 0.12-0.13 render
3. Bodies
4. Spacecraft
5. Maneuver markers
6. Child indicators (screen-space overlays)
### Relative Rendering
When a body is selected, the scene is rendered relative to that body:
- Selected body is rendered at origin (0, 0, 0)
- Children are rendered relative to selected body
- Children's orbits are rendered around origin
- Uses full float precision for small orbital distances (e.g., LEO)
When a spacecraft is selected:
- Parent body is rendered relative to spacecraft
- Spacecraft's orbit is rendered around parent
- No sibling spacecraft rendered
**Benefits:**
- Improved visibility of small orbits (LEO, moon orbits)
- Better float precision for local coordinate systems
- Automatic camera positioning based on children distances
## UI System (ui_renderer + raygui)
@ -272,6 +297,7 @@ Scale factor 0.02 produces: Sun ~0.18, planets 0.13-0.16, moons 0.12-0.13 render
#### Camera
- `update_camera(render_state, sim)` - Handle input and camera follow
- `get_initial_camera_distance(body, sim, render_state)` - Auto-position camera based on children distance
#### Rendering
- `render_body(body, render_state)` - Draw single celestial body
@ -317,3 +343,9 @@ Note: UI rendering functions are called after `render_simulation()` to ensure UI
- UI panels allocate temporary buffers for lists
- List text freed after rendering
- No persistent UI state allocation
## TODO Items
1. **Distant Bodies**: When body selected, show indicators for bodies that are not children (currently only shows direct children)
2. **Smooth Frame Transitions**: Add interpolation when switching between different selected bodies (instant switch currently)

Loading…
Cancel
Save