Files
orbital-test/orbit_drawer.py
Lucy 655f1c9af6 Initial commit: Keplerian orbital mechanics with patched-conics SOI transitions
- orbital_elements.py: elliptical + hyperbolic orbit support
- orbit_drawer.py: orbit point generation with SOI truncation
- soi_calculator.py: SOI crossing time calculator
- frame_transition.py: reference frame switching
- test_orbital.py: 147 assertions, all passing
- visual_test.py: pygame flyby visualization
2026-05-21 20:31:17 +02:00

78 lines
2.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Orbit trajectory point generation for rendering.
generate_orbit_points() — full orbit (elliptical) or truncated
hyperbolic path within a given cut-off radius.
"""
import math
from typing import List, Optional, Tuple
from orbital_elements import OrbitalElements, TAU
def generate_orbit_points(
orbit: OrbitalElements,
num_points: int = 200,
max_radius: Optional[float] = None,
) -> List[Tuple[float, float]]:
"""
Generate *num_points* world-space positions along the orbit.
Elliptical orbits → full closed loop, equally spaced in E.
Hyperbolic orbits → samples H ∈ [H_max, +H_max], truncated
at *max_radius* (e.g. the planet's SOI edge).
Parameters
----------
max_radius : float or None
Only used for hyperbolic orbits. Points with r > max_radius
are omitted. If None, samples out to r ≈ 5·|a| from focus.
"""
if orbit.is_hyperbolic:
return _hyperbolic_points(orbit, num_points, max_radius)
return _elliptical_points(orbit, num_points)
def _elliptical_points(
orbit: OrbitalElements, num_points: int,
) -> List[Tuple[float, float]]:
points = []
for i in range(num_points):
E = TAU * i / num_points
points.append(orbit.position_from_E(E))
return points
def _hyperbolic_points(
orbit: OrbitalElements,
num_points: int,
max_radius: Optional[float],
) -> List[Tuple[float, float]]:
"""
Sample the hyperbolic branch that passes through periapsis.
Truncates at *max_radius* by solving for the H values where
r = max_radius, then sampling uniformly between those limits.
"""
r_limit = max_radius if max_radius is not None else 5.0 * abs(orbit.a)
# Find H where r = r_limit.
# r = |a|(e·cosh(H) 1) → cosh(H) = (r/|a| + 1) / e
abs_a = abs(orbit.a)
cosh_Hmax = (r_limit / abs_a + 1.0) / orbit.e
if cosh_Hmax < 1.0:
return [] # entire orbit is outside the radius
H_max = math.acosh(cosh_Hmax)
points = []
for i in range(num_points):
frac = -1.0 + 2.0 * i / (num_points - 1) if num_points > 1 else 0.0
H = frac * H_max
x, y = orbit.position_from_H(H)
r = math.hypot(x, y)
if r <= r_limit + 1e-9:
points.append((x, y))
return points