Add animated package along trajectory with Kepler propagation

- Trajectory sampling: propagate Lambert transfer orbits using universal
  variable Kepler propagation, sample 60 points per leg
- Smooth curved trajectory lines on 2D canvas (real orbital arcs, not
  straight lines)
- Animated green package dot with radial glow traveling along trajectory
- Route progress slider (0-100%) with play/pause animation toggle
- Auto-animating ~20-second cycle through the full route
- WASM API: sample_route_trajectory() with per-leg NaN separators

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 12:21:58 -07:00
parent 22dcc5b6ec
commit 21c842acdc
8 changed files with 329 additions and 38 deletions

View File

@@ -102,6 +102,76 @@ pub fn get_station_names(stations_json: &str) -> String {
serde_json::to_string(&names).unwrap()
}
/// Sample trajectory points along a route for visualization.
/// Takes the stations JSON, route JSON (from compute_route), and current JD.
/// Returns flat Float64Array of [x,y,z, ...] in AU for all legs concatenated,
/// with a separator of NaN,NaN,NaN between legs.
#[wasm_bindgen]
pub fn sample_route_trajectory(
stations_json: &str,
route_json: &str,
jd: f64,
samples_per_leg: usize,
) -> Vec<f64> {
use mass_driver_core::route::RouteResult;
use orbital_mechanics::lambert;
let stations: Vec<station::Station> = match serde_json::from_str(stations_json) {
Ok(s) => s,
Err(_) => return vec![],
};
let route: RouteResult = match serde_json::from_str(route_json) {
Ok(r) => r,
Err(_) => return vec![],
};
let all_bodies = bodies::all_bodies();
let mut result = Vec::new();
for (i, leg) in route.legs.iter().enumerate() {
if i > 0 {
// Separator between legs
result.push(f64::NAN);
result.push(f64::NAN);
result.push(f64::NAN);
}
// Get station positions at departure/arrival times
let dep_jd = jd + (leg.departure_week as f64) * 7.0;
let arr_jd = jd + (leg.arrival_week as f64) * 7.0;
let tof_seconds = (arr_jd - dep_jd) * orbital_mechanics::constants::SECONDS_PER_DAY;
let r1_au = station::station_position(
&stations[leg.from_station], &all_bodies, dep_jd,
);
let r2_au = station::station_position(
&stations[leg.to_station], &all_bodies, arr_jd,
);
// Convert to km for Lambert solver
let au_km = orbital_mechanics::constants::AU_KM;
let r1_km = r1_au * au_km;
let r2_km = r2_au * au_km;
let points = lambert::sample_trajectory(
r1_km, r2_km, tof_seconds,
orbital_mechanics::constants::MU_SUN,
samples_per_leg,
);
// Convert back to AU
for chunk in points.chunks(3) {
if chunk.len() == 3 {
result.push(chunk[0] / au_km);
result.push(chunk[1] / au_km);
result.push(chunk[2] / au_km);
}
}
}
result
}
/// Compute transfer matrix and find optimal route between two stations.
///
/// This is the main computation function. It: