From 060ff403239f12b62e4780eee8a352f7627c1dd2 Mon Sep 17 00:00:00 2001 From: cinnaboot Date: Mon, 5 Jan 2026 13:24:01 -0500 Subject: [PATCH] Add dynamic orbital rendering with hyperbolic/parabolic support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Calculate orbital elements dynamically from current state and parent - Orbits now update automatically when parent body changes - Add support for parabolic trajectories (e ≈ 1) - Add support for hyperbolic trajectories (e > 1) with limited extent - Properly orient orbits in 3D space using eccentricity vector - Limit hyperbolic/parabolic drawing to avoid infinite vertices Refactored for code reuse and separation of concerns: - calculate_orbital_basis(): Computes orbital reference frame - orbital_to_cartesian(): Transforms orbital coords to 3D space - draw_orbit_segment(): Handles rendering of individual segments - render_elliptical_orbit(): Draws closed elliptical paths - render_hyperbolic_orbit(): Draws open hyperbolic/parabolic paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- src/renderer.cpp | 142 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 29 deletions(-) diff --git a/src/renderer.cpp b/src/renderer.cpp index 2fed07f..56da12a 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -102,23 +102,115 @@ void render_body(CelestialBody* body, RenderState* render_state) { DrawSphereWires(position, radius, 16, 16, color); } +struct OrbitalBasis { + Vec3 periapsis_dir; + Vec3 normal; + Vec3 q_vec; +}; + +static OrbitalBasis calculate_orbital_basis(Vec3 r_vec, Vec3 velocity, Vec3 e_vec) { + OrbitalBasis basis; + basis.periapsis_dir = vec3_normalize(e_vec); + + Vec3 h_vec = { + r_vec.y * velocity.z - r_vec.z * velocity.y, + r_vec.z * velocity.x - r_vec.x * velocity.z, + r_vec.x * velocity.y - r_vec.y * velocity.x + }; + basis.normal = vec3_normalize(h_vec); + + basis.q_vec = { + basis.normal.y * basis.periapsis_dir.z - basis.normal.z * basis.periapsis_dir.y, + basis.normal.z * basis.periapsis_dir.x - basis.normal.x * basis.periapsis_dir.z, + basis.normal.x * basis.periapsis_dir.y - basis.normal.y * basis.periapsis_dir.x + }; + + return basis; +} + +static Vec3 orbital_to_cartesian(double x, double y, OrbitalBasis basis, Vec3 parent_pos) { + return { + basis.periapsis_dir.x * x + basis.q_vec.x * y + parent_pos.x, + basis.periapsis_dir.y * x + basis.q_vec.y * y + parent_pos.y, + basis.periapsis_dir.z * x + basis.q_vec.z * y + parent_pos.z + }; +} + +static void draw_orbit_segment(double x1, double y1, double x2, double y2, + OrbitalBasis basis, Vec3 parent_pos, + RenderState* render_state, Color color) { + Vec3 p1_sim = orbital_to_cartesian(x1, y1, basis, parent_pos); + Vec3 p2_sim = orbital_to_cartesian(x2, y2, basis, parent_pos); + + Vector3 p1 = sim_to_render(p1_sim, render_state->distance_scale); + Vector3 p2 = sim_to_render(p2_sim, render_state->distance_scale); + + DrawLine3D(p1, p2, color); +} + +static void render_elliptical_orbit(double a, double e, OrbitalBasis basis, + Vec3 parent_pos, 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(x1, y1, x2, y2, basis, parent_pos, render_state, color); + } +} + +static void render_hyperbolic_orbit(double p, double e, OrbitalBasis basis, + Vec3 parent_pos, RenderState* render_state, Color color) { + double max_true_anomaly = (e > 1.01) ? acos(-1.0 / e) * 0.95 : PI * 0.48; + + int segments = 60; + for (int i = 0; i < segments; i++) { + float theta1 = -max_true_anomaly + (float)i / segments * 2.0f * max_true_anomaly; + float theta2 = -max_true_anomaly + (float)(i + 1) / segments * 2.0f * max_true_anomaly; + + double r1 = p / (1.0 + e * cos(theta1)); + double r2 = p / (1.0 + e * cos(theta2)); + + double x1 = r1 * cos(theta1); + double y1 = r1 * sin(theta1); + double x2 = r2 * cos(theta2); + double y2 = r2 * sin(theta2); + + draw_orbit_segment(x1, y1, x2, y2, basis, parent_pos, render_state, color); + } +} + // Render orbit path for a body void render_orbit(CelestialBody* body, CelestialBody* parent, RenderState* render_state) { if (body->parent_index == -1 || parent == NULL) { return; } - double a = body->semi_major_axis; - double e = body->eccentricity; + Vec3 r_vec = vec3_sub(body->position, parent->position); + double r = vec3_magnitude(r_vec); + double v = vec3_magnitude(body->velocity); - if (a <= 0.0) { - return; - } + if (r < 1.0) return; - double b = a * sqrt(1.0 - e * e); - double c = a * e; + double mu = G * parent->mass; + double specific_energy = (v * v) / 2.0 - mu / r; - Vector3 parent_pos = sim_to_render(parent->position, render_state->distance_scale); + double v_squared = v * v; + double r_dot_v = r_vec.x * body->velocity.x + r_vec.y * body->velocity.y + r_vec.z * body->velocity.z; + Vec3 e_vec = { + (v_squared - mu / r) * r_vec.x - r_dot_v * body->velocity.x, + (v_squared - mu / r) * r_vec.y - r_dot_v * body->velocity.y, + (v_squared - mu / r) * r_vec.z - r_dot_v * body->velocity.z + }; + double e = vec3_magnitude(e_vec) / mu; Color orbit_color = { (unsigned char)(body->color[0] * 128), @@ -127,28 +219,20 @@ void render_orbit(CelestialBody* body, CelestialBody* parent, RenderState* rende 128 }; - 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; + OrbitalBasis basis = calculate_orbital_basis(r_vec, body->velocity, e_vec); + + if (e < 0.98) { + double a = -mu / (2.0 * specific_energy); + if (a <= 0.0) return; + + render_elliptical_orbit(a, e, basis, parent->position, render_state, orbit_color); + } else { + double a = (e > 1.01) ? mu / (2.0 * (-specific_energy)) : r / (1.0 + e); + double p = a * (1.0 - e * e); + + if (p <= 0.0) return; - float x1 = (float)(a * cos(theta1) - c); - float y1 = (float)(b * sin(theta1)); - float x2 = (float)(a * cos(theta2) - c); - float y2 = (float)(b * sin(theta2)); - - Vector3 p1 = { - parent_pos.x + x1 * (float)render_state->distance_scale, - parent_pos.y, - parent_pos.z + y1 * (float)render_state->distance_scale - }; - Vector3 p2 = { - parent_pos.x + x2 * (float)render_state->distance_scale, - parent_pos.y, - parent_pos.z + y2 * (float)render_state->distance_scale - }; - - DrawLine3D(p1, p2, orbit_color); + render_hyperbolic_orbit(p, e, basis, parent->position, render_state, orbit_color); } }