use glam::DVec3;
use astrodyn::{FrameId, IntegOrigin, Vec3Ext};
use super::Simulation;
use crate::error::StepError;
mod derived;
mod environment;
mod ephemeris;
mod integrate;
mod interactions;
mod kinematic;
#[cfg(feature = "phase_timing")]
pub mod timings;
impl Simulation {
pub fn step(&mut self) -> Result<(), StepError> {
self.step_internal(self.dt)
}
pub fn frame_origin(&self, frame_id: FrameId) -> (DVec3, DVec3) {
if frame_id == self.root_frame_id {
return (DVec3::ZERO, DVec3::ZERO);
}
let state = self
.frame_tree
.compute_relative_state(self.root_frame_id, frame_id);
(state.trans.position, state.trans.velocity)
}
pub fn frame_origin_typed(&self, frame_id: FrameId) -> IntegOrigin {
let (pos, vel) = astrodyn::frame_orchestration::frame_origin(
&self.frame_tree,
self.root_frame_id,
frame_id,
);
IntegOrigin {
position: pos.m_at::<astrodyn::RootInertial>(),
velocity: vel.m_per_s_at::<astrodyn::RootInertial>(),
}
}
fn step_internal(&mut self, dt: f64) -> Result<(), StepError> {
self.has_stepped = true;
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.time.advance(dt);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.time_advance += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.update_ephemeris()?;
#[cfg(feature = "phase_timing")]
{
self.phase_timings.ephemeris += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
for body in &mut self.bodies {
if let Some(ref mut mass) = body.mass {
mass.recompute_derived();
}
}
#[cfg(feature = "phase_timing")]
{
self.phase_timings.mass_recompute += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
let body_integ_origins: Vec<IntegOrigin> = self
.bodies
.iter()
.map(|b| self.frame_origin_typed(b.integ_frame_id))
.collect();
#[cfg(feature = "phase_timing")]
{
self.phase_timings.integ_origins_pre += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.propagate_frame_attached_state(&body_integ_origins);
self.propagate_kinematic_state(&body_integ_origins);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.kinematic_pre += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.update_environment(&body_integ_origins);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.environment += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
let (sun_pos, moon_pos) = self.compute_interactions(dt, &body_integ_origins);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.interactions += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.run_integration(dt, &body_integ_origins)?;
#[cfg(feature = "phase_timing")]
{
self.phase_timings.integration += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
let body_integ_origins_post: Vec<IntegOrigin> = self
.bodies
.iter()
.map(|b| self.frame_origin_typed(b.integ_frame_id))
.collect();
#[cfg(feature = "phase_timing")]
{
self.phase_timings.integ_origins_post += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.propagate_frame_attached_state(&body_integ_origins_post);
self.propagate_kinematic_state(&body_integ_origins_post);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.kinematic_post += _t0.elapsed();
}
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.compute_derived_states(sun_pos, moon_pos, &body_integ_origins_post);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.derived += _t0.elapsed();
}
if !self.detached_subtrees.is_empty() {
#[cfg(feature = "phase_timing")]
let _t0 = std::time::Instant::now();
self.step_detached_subtrees(dt * self.time.time_scale_factor);
#[cfg(feature = "phase_timing")]
{
self.phase_timings.detached_subtrees += _t0.elapsed();
}
}
#[cfg(feature = "phase_timing")]
{
self.phase_timings.steps += 1;
}
Ok(())
}
pub fn step_n(&mut self, n: usize) -> Result<(), StepError> {
for _ in 0..n {
self.step()?;
}
Ok(())
}
pub fn step_until(&mut self, target_time: f64) -> Result<(), StepError> {
while self.time.simtime + self.dt <= target_time + 0.001 {
self.step()?;
}
let remainder = target_time - self.time.simtime;
if remainder > 0.001 {
let has_multistep = self.bodies.iter().any(|b| {
matches!(
b.integrator,
astrodyn::IntegratorType::GaussJackson(..) | astrodyn::IntegratorType::Abm4
)
});
assert!(
!has_multistep,
"step_until() would take a fractional step ({remainder:.6}s vs dt={:.6}s). \
Multi-step integrators (GaussJackson, ABM4) require constant dt. \
Ensure target_time is an integer multiple of dt.",
self.dt
);
self.step_internal(remainder)?;
}
Ok(())
}
}