use astrodyn::Planet;
use bevy::prelude::*;
#[allow(unused_imports)] use super::super::integration::{frame_switch_system, integration_system};
use crate::components::*;
#[allow(clippy::type_complexity)]
pub fn register_source_frames_system<P: Planet>(
mut commands: Commands,
root_frame_entity: Res<crate::RootFrameEntityR>,
sources: Query<
(
Entity,
Option<&Name>,
&SourceInertialPositionC,
Option<&SourceInertialVelocityC>,
Option<&RotationModelC>,
Option<&PlanetFixedRotationC<P>>,
),
(With<GravitySourceC>, Without<FrameEntityC>),
>,
) {
for (entity, name, pos, vel, rotation_model, pfix_rot) in &sources {
let label = name
.map(|n| n.as_str().to_string())
.unwrap_or_else(|| format!("source{:?}", entity));
let init_pos = pos.0.raw_si();
let init_vel = vel.map_or(glam::DVec3::ZERO, |v| v.0.raw_si());
let source_frame_entity = commands
.spawn((
Name::new(format!("{label}.frame.inertial")),
InertialFrameMarker,
FrameTransC {
position: init_pos,
velocity: init_vel,
},
FrameRotC::default(),
FrameAngVelC::default(),
ChildOf(root_frame_entity.0),
))
.id();
commands
.entity(entity)
.insert(FrameEntityC(source_frame_entity));
if pfix_rot.is_some() {
let default_model = astrodyn::RotationModel::EarthRNP;
let model_value = rotation_model.map_or(default_model, |m| m.0);
if !matches!(model_value, astrodyn::RotationModel::None) {
let pfix_frame_entity = commands
.spawn((
Name::new(format!("{label}.frame.pfix")),
PlanetFixedFrameMarker,
FrameTransC::default(),
FrameRotC::default(),
FrameAngVelC::default(),
ChildOf(source_frame_entity),
))
.id();
commands
.entity(entity)
.insert(PfixFrameEntityC(pfix_frame_entity));
}
}
}
}
#[allow(clippy::type_complexity)]
pub fn register_pfix_frames_system<P: Planet>(
mut commands: Commands,
sources: Query<
(
Entity,
Option<&Name>,
// The source's own frame entity: the spawned pfix frame
// entity ChildOf-links under it. Required for registration
// — `register_source_frames_system` always inserts it.
&FrameEntityC,
Option<&RotationModelC>,
// ECS-entity retirement marker so we reuse instead of leak
// on toggle cycles.
Option<&RetiredPfixFrameEntityC>,
),
(
With<GravitySourceC>,
With<PlanetFixedRotationC<P>>,
Without<PfixFrameEntityC>,
),
>,
mut frame_trans: Query<&mut FrameTransC>,
mut frame_rots: Query<&mut FrameRotC>,
mut frame_ang_vels: Query<&mut FrameAngVelC>,
) {
for (entity, name, source_frame_entity, rotation_model, retired_entity) in &sources {
let default_model = astrodyn::RotationModel::EarthRNP;
let model_value = rotation_model.map_or(default_model, |m| m.0);
if matches!(model_value, astrodyn::RotationModel::None) {
continue;
}
let label = name
.map(|n| n.as_str().to_string())
.unwrap_or_else(|| format!("source{:?}", entity));
let pfix_frame_entity = if let Some(retired_e) = retired_entity {
commands
.entity(retired_e.0)
.insert(Name::new(format!("{label}.frame.pfix")));
let mut t = frame_trans.get_mut(retired_e.0).unwrap_or_else(|err| {
panic!(
"register_pfix_frames_system: source {entity:?} \
carries RetiredPfixFrameEntityC({:?}) but that \
entity has no FrameTransC ({err:?}). The retired \
pfix frame entity must be alive with FrameTransC / \
FrameRotC / FrameAngVelC intact (set up by \
planet_fixed_rotation_system on retirement). Do not \
despawn or strip components from a retired pfix \
frame entity while its source still carries the \
marker.",
retired_e.0
)
});
*t = FrameTransC::default();
let mut r = frame_rots.get_mut(retired_e.0).unwrap_or_else(|err| {
panic!(
"register_pfix_frames_system: source {entity:?} \
carries RetiredPfixFrameEntityC({:?}) but that \
entity has no FrameRotC ({err:?}). The retired \
pfix frame entity must be alive with FrameTransC / \
FrameRotC / FrameAngVelC intact (set up by \
planet_fixed_rotation_system on retirement). Do not \
despawn or strip components from a retired pfix \
frame entity while its source still carries the \
marker.",
retired_e.0
)
});
*r = FrameRotC::default();
let mut av = frame_ang_vels.get_mut(retired_e.0).unwrap_or_else(|err| {
panic!(
"register_pfix_frames_system: source {entity:?} \
carries RetiredPfixFrameEntityC({:?}) but that \
entity has no FrameAngVelC ({err:?}). The retired \
pfix frame entity must be alive with FrameTransC / \
FrameRotC / FrameAngVelC intact (set up by \
planet_fixed_rotation_system on retirement). Do not \
despawn or strip components from a retired pfix \
frame entity while its source still carries the \
marker.",
retired_e.0
)
});
*av = FrameAngVelC::default();
commands.entity(entity).remove::<RetiredPfixFrameEntityC>();
retired_e.0
} else {
commands
.spawn((
Name::new(format!("{label}.frame.pfix")),
PlanetFixedFrameMarker,
FrameTransC::default(),
FrameRotC::default(),
FrameAngVelC::default(),
ChildOf(source_frame_entity.0),
))
.id()
};
commands
.entity(entity)
.insert(PfixFrameEntityC(pfix_frame_entity));
}
}
#[allow(clippy::type_complexity)]
pub fn sync_source_to_frame_system<P: Planet>(
sources: Query<(
&FrameEntityC,
&SourceInertialPositionC,
Option<&SourceInertialVelocityC>,
Option<&TranslationalStateC<P>>,
)>,
mut frame_states: Query<&mut FrameTransC>,
) {
for (fe, pos, vel, trans) in &sources {
let position = pos.0.raw_si();
let velocity = vel
.map(|v| v.0.raw_si())
.or_else(|| trans.map(|t| t.0.velocity.raw_si()));
let mut frame_trans = frame_states.get_mut(fe.0).unwrap_or_else(|err| {
panic!(
"sync_source_to_frame_system: source has \
FrameEntityC({:?}) but that entity has no FrameTransC \
({err:?}). The source's frame entity must be alive \
with FrameTransC attached (spawned by PlanetBundle / \
register_*_frames_system). Either remove the stale \
FrameEntityC marker before despawning the frame \
entity, or ensure the frame entity stays alive for \
as long as the source carries the handle.",
fe.0
)
});
frame_trans.position = position;
if let Some(v) = velocity {
frame_trans.velocity = v;
}
}
}
#[allow(clippy::type_complexity)]
pub fn register_body_frames_system<P: Planet>(
mut commands: Commands,
root_frame_entity: Res<crate::RootFrameEntityR>,
sources: Query<&FrameEntityC, With<GravitySourceC>>,
bodies: Query<
(
Entity,
Option<&Name>,
&TranslationalStateC<P>,
Option<&IntegSourceC>,
// Wire the frame-side `MassPointRef` back-pointer at
// body-frame registration time for any entity that also
// carries `MassPropertiesC` (i.e. participates in the
// mass tree). In the current Bevy adapter the body /
// mass / frame ECS entity is one and the same, so the
// back-pointer resolves to `MassPointRef(self)`. The
// component is skipped for kinematic-only bodies (no
// `MassPropertiesC`), matching the "absent for
// kinematic-only attaches" contract on the type.
Has<MassPropertiesC>,
),
(
With<TranslationalStateC<P>>,
With<DynamicsConfigC>,
Without<FrameEntityC>,
),
>,
) {
for (entity, name, trans, integ_source, has_mass) in &bodies {
let label = name
.map(|n| n.as_str().to_string())
.unwrap_or_else(|| format!("body{:?}", entity));
let integ_frame_entity = match integ_source.and_then(|c| c.0) {
Some(source_entity) => {
sources
.get(source_entity)
.map(|fe| fe.0)
.unwrap_or_else(|err| {
panic!(
"register_body_frames_system: body {entity:?} has \
IntegSourceC pointing at {source_entity:?}, but that \
entity is not a registered gravity source (missing \
FrameEntityC + GravitySourceC). Spawn the source via \
PlanetBundle before the body, or remove IntegSourceC. \
Underlying error: {err:?}"
)
})
}
None => root_frame_entity.0,
};
let init_pos = trans.0.position.raw_si();
let init_vel = trans.0.velocity.raw_si();
commands
.entity(integ_frame_entity)
.insert(IntegrationFrameMarker);
let body_frame_entity = commands
.spawn((
Name::new(format!("{label}.frame.body")),
BodyFrameMarker,
FrameTransC {
position: init_pos,
velocity: init_vel,
},
FrameRotC::default(),
FrameAngVelC::default(),
ChildOf(integ_frame_entity),
))
.id();
let mut entity_cmds = commands.entity(entity);
entity_cmds.insert(FrameEntityC(body_frame_entity));
if has_mass {
entity_cmds.insert(MassPointRef(entity));
}
}
}
#[allow(clippy::type_complexity)]
pub fn sync_body_mass_point_ref_system(
mut commands: Commands,
acquired: Query<
Entity,
(
With<FrameEntityC>,
With<DynamicsConfigC>,
With<MassPropertiesC>,
Without<MassPointRef>,
),
>,
lost: Query<
Entity,
(
With<FrameEntityC>,
With<DynamicsConfigC>,
With<MassPointRef>,
Without<MassPropertiesC>,
),
>,
) {
for entity in &acquired {
commands.entity(entity).insert(MassPointRef(entity));
}
for entity in &lost {
commands.entity(entity).remove::<MassPointRef>();
}
}
pub fn sync_body_to_frame_system<P: Planet>(
bodies: Query<(&TranslationalStateC<P>, &FrameEntityC), With<DynamicsConfigC>>,
mut frame_states: Query<&mut FrameTransC>,
) {
for (trans, frame_entity) in &bodies {
let position = trans.0.position.raw_si();
let velocity = trans.0.velocity.raw_si();
let mut frame_trans = frame_states.get_mut(frame_entity.0).unwrap_or_else(|err| {
panic!(
"sync_body_to_frame_system: body has FrameEntityC({:?}) \
but that entity has no FrameTransC ({err:?}). The \
body's frame entity must be alive with FrameTransC \
attached (spawned by register_body_frames_system). \
Either remove the stale FrameEntityC marker before \
despawning the frame entity, or ensure the frame \
entity stays alive for as long as the body carries \
the handle.",
frame_entity.0
)
});
frame_trans.position = position;
frame_trans.velocity = velocity;
}
}