use astrodyn::{Planet, Vec3Ext};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use glam::{DMat3, DVec3};
use crate::components::{
CentralSourceMarker, FrameEntityC, FrameRotC, FrameTransC, GravitySourceC, PfixFrameEntityC,
SourceInertialPositionC, SourceInertialVelocityC, TranslationalStateC,
};
#[derive(SystemParam)]
pub struct SourceReader<'w, 's> {
frame_entities: Query<'w, 's, &'static FrameEntityC, With<GravitySourceC>>,
pfix_frame_entities: Query<'w, 's, &'static PfixFrameEntityC, With<GravitySourceC>>,
frame_trans: Query<'w, 's, &'static FrameTransC>,
frame_rots: Query<'w, 's, &'static FrameRotC>,
names: Query<'w, 's, &'static Name>,
}
impl SourceReader<'_, '_> {
pub fn source_frame_entity(&self, source: Entity) -> Entity {
_fetch_frame_entity(
&self.frame_entities,
&self.names,
source,
"SourceReader",
"source_frame_entity",
)
}
pub fn source_position(&self, source: Entity) -> DVec3 {
let fe = self.source_frame_entity(source);
self.frame_trans
.get(fe)
.map(|t| t.position)
.unwrap_or_else(|err| {
panic!(
"SourceReader::source_position: {label} has \
FrameEntityC({fe:?}) but that entity has no \
FrameTransC ({err:?}). The source's frame entity \
must be alive with FrameTransC attached (spawned \
by register_source_frames_system).",
label = _entity_label(&self.names, source),
)
})
}
pub fn source_pfix_rotation(&self, source: Entity) -> Option<DMat3> {
let _ = self.source_frame_entity(source);
let pfix_fe = self.pfix_frame_entities.get(source).ok()?;
let rot = self.frame_rots.get(pfix_fe.0).unwrap_or_else(|err| {
panic!(
"SourceReader::source_pfix_rotation: {label} has \
PfixFrameEntityC({fe:?}) but that entity has no \
FrameRotC ({err:?}). The pfix frame entity must be \
alive with FrameRotC attached (spawned by \
register_pfix_frames_system).",
fe = pfix_fe.0,
label = _entity_label(&self.names, source),
)
});
Some(rot.t_parent_this)
}
}
#[derive(SystemParam)]
pub struct SourceMutator<'w, 's, P: Planet> {
commands: Commands<'w, 's>,
frame_entities: Query<'w, 's, &'static FrameEntityC, With<GravitySourceC>>,
pfix_frame_entities: Query<'w, 's, &'static PfixFrameEntityC, With<GravitySourceC>>,
frame_trans: Query<'w, 's, &'static mut FrameTransC>,
frame_rots: Query<'w, 's, &'static FrameRotC>,
positions: Query<'w, 's, &'static mut SourceInertialPositionC, With<GravitySourceC>>,
velocities: Query<'w, 's, &'static mut SourceInertialVelocityC, With<GravitySourceC>>,
translational: Query<'w, 's, &'static mut TranslationalStateC<P>, With<GravitySourceC>>,
central: Query<'w, 's, (), With<CentralSourceMarker>>,
names: Query<'w, 's, &'static Name>,
}
impl<P: Planet> SourceMutator<'_, '_, P> {
pub fn source_frame_entity(&self, source: Entity) -> Entity {
_fetch_frame_entity(
&self.frame_entities,
&self.names,
source,
"SourceMutator",
"source_frame_entity",
)
}
pub fn source_position(&self, source: Entity) -> DVec3 {
let fe = self.source_frame_entity(source);
self.frame_trans
.get(fe)
.map(|t| t.position)
.unwrap_or_else(|err| {
panic!(
"SourceMutator::source_position: {label} has \
FrameEntityC({fe:?}) but that entity has no \
FrameTransC ({err:?}). The source's frame entity \
must be alive with FrameTransC attached (spawned \
by register_source_frames_system).",
label = _entity_label(&self.names, source),
)
})
}
pub fn source_pfix_rotation(&self, source: Entity) -> Option<DMat3> {
let _ = self.source_frame_entity(source);
let pfix_fe = self.pfix_frame_entities.get(source).ok()?;
let rot = self.frame_rots.get(pfix_fe.0).unwrap_or_else(|err| {
panic!(
"SourceMutator::source_pfix_rotation: {label} has \
PfixFrameEntityC({fe:?}) but that entity has no \
FrameRotC ({err:?}). The pfix frame entity must be \
alive with FrameRotC attached (spawned by \
register_pfix_frames_system).",
fe = pfix_fe.0,
label = _entity_label(&self.names, source),
)
});
Some(rot.t_parent_this)
}
pub fn set_source_position(&mut self, source: Entity, position: DVec3) {
let fe = self.source_frame_entity(source);
self.assert_not_central(source, "set_source_position");
let label = _entity_label(&self.names, source);
let mut frame_trans = self.frame_trans.get_mut(fe).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_position: {label} has \
FrameEntityC({fe:?}) but that entity has no \
FrameTransC ({err:?}). The source's frame entity \
must be alive with FrameTransC attached (spawned by \
register_source_frames_system)."
)
});
frame_trans.position = position;
let typed_pos = position.m_at::<astrodyn::RootInertial>();
let mut pos_c = self.positions.get_mut(source).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_position: {label} is a \
registered gravity source (has FrameEntityC) but \
lacks SourceInertialPositionC ({err:?}). Spawn the \
source via PlanetBundle (which includes both \
components) — without SourceInertialPositionC, \
`sync_source_to_frame_system` would overwrite the \
frame entity's position back on the next step."
)
});
pos_c.0 = typed_pos;
let mut ts = self.translational.get_mut(source).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_position: {label} is a \
registered gravity source (has FrameEntityC) but \
lacks TranslationalStateC ({err:?}). Spawn the \
source via PlanetBundle (which includes \
TranslationalStateC) so per-step systems observe a \
consistent position across the source's components."
)
});
ts.0.position = typed_pos.relabel_to::<astrodyn::PlanetInertial<P>>();
}
pub fn set_source_state(&mut self, source: Entity, position: DVec3, velocity: DVec3) {
let fe = self.source_frame_entity(source);
self.assert_not_central(source, "set_source_state");
let label = _entity_label(&self.names, source);
let mut frame_trans = self.frame_trans.get_mut(fe).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_state: {label} has \
FrameEntityC({fe:?}) but that entity has no \
FrameTransC ({err:?}). The source's frame entity \
must be alive with FrameTransC attached (spawned by \
register_source_frames_system)."
)
});
frame_trans.position = position;
frame_trans.velocity = velocity;
let typed_pos = position.m_at::<astrodyn::RootInertial>();
let typed_vel = velocity.m_per_s_at::<astrodyn::RootInertial>();
let mut pos_c = self.positions.get_mut(source).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_state: {label} is a \
registered gravity source (has FrameEntityC) but \
lacks SourceInertialPositionC ({err:?}). Spawn the \
source via PlanetBundle (which includes both \
components) — without SourceInertialPositionC, \
`sync_source_to_frame_system` would overwrite the \
frame entity's position back on the next step."
)
});
pos_c.0 = typed_pos;
match self.velocities.get_mut(source) {
Ok(mut vc) => vc.0 = typed_vel,
Err(_) => {
self.commands
.entity(source)
.insert(SourceInertialVelocityC(typed_vel));
}
}
let mut ts = self.translational.get_mut(source).unwrap_or_else(|err| {
panic!(
"SourceMutator::set_source_state: {label} is a \
registered gravity source (has FrameEntityC) but \
lacks TranslationalStateC ({err:?}). Spawn the \
source via PlanetBundle (which includes \
TranslationalStateC) so per-step systems observe a \
consistent state across the source's components."
)
});
ts.0.position = typed_pos.relabel_to::<astrodyn::PlanetInertial<P>>();
ts.0.velocity = typed_vel.relabel_to::<astrodyn::PlanetInertial<P>>();
}
fn assert_not_central(&self, source: Entity, method: &str) {
if self.central.get(source).is_ok() {
panic!(
"SourceMutator::{method}: {label} carries CentralSourceMarker \
— the central body's state is pinned by convention. Remove \
the marker (or target a different gravity source) if \
retargeting the central body is really intended.",
label = _entity_label(&self.names, source),
);
}
}
}
fn _entity_label(names: &Query<&'static Name>, entity: Entity) -> String {
match names.get(entity) {
Ok(name) => format!("{name} ({entity:?})"),
Err(_) => format!("{entity:?}"),
}
}
fn _fetch_frame_entity(
frame_entities: &Query<&'static FrameEntityC, With<GravitySourceC>>,
names: &Query<&'static Name>,
source: Entity,
type_name: &str,
method: &str,
) -> Entity {
frame_entities
.get(source)
.map(|c| c.0)
.unwrap_or_else(|err| {
panic!(
"{type_name}::{method}: {label} is not a registered \
gravity source — it is missing GravitySourceC and/or FrameEntityC. \
Spawn it via PlanetBundle (which inserts GravitySourceC + \
SourceInertialPositionC) and let `register_source_frames_system` \
attach the FrameEntityC during Startup before mutating the source. \
If the entity is a body (not a planet), pass the *planet's* entity \
instead — bodies carry FrameEntityC too but do not represent gravity \
sources. Underlying error: {err:?}",
label = _entity_label(names, source),
)
})
}