use astrodyn::{
Acceleration, AngularAcceleration, BodyFrame, Force, Planet, PlanetInertial, RootInertial,
RotationalStateTyped, SelfRef, StructuralFrame, Torque, TranslationalStateTyped,
};
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use glam::DVec3;
use crate::components::{
DynamicsConfigC, ExternalForceC, ExternalForceStructC, ExternalTorqueC, ExternalTorqueStructC,
FrameDerivativesC, MassPropertiesC, RotationalStateC, TranslationalStateC,
};
#[derive(SystemParam)]
pub struct BodyReader<'w, 's, P: Planet> {
body_filter: Query<'w, 's, (), With<DynamicsConfigC>>,
trans: Query<'w, 's, &'static TranslationalStateC<P>>,
rot: Query<'w, 's, &'static RotationalStateC>,
mass: Query<'w, 's, &'static MassPropertiesC>,
frame_derivs: Query<'w, 's, &'static FrameDerivativesC>,
ext_force: Query<'w, 's, &'static ExternalForceC>,
ext_torque: Query<'w, 's, &'static ExternalTorqueC>,
ext_force_struct: Query<'w, 's, &'static ExternalForceStructC>,
ext_torque_struct: Query<'w, 's, &'static ExternalTorqueStructC>,
names: Query<'w, 's, &'static Name>,
}
impl<P: Planet> BodyReader<'_, '_, P> {
pub fn body_translational_state(
&self,
body: Entity,
) -> TranslationalStateTyped<PlanetInertial<P>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_translational_state",
);
self.trans
.get(body)
.unwrap_or_else(|err| {
panic!(
"BodyReader::body_translational_state: {label} is a registered \
dynamic body but lacks TranslationalStateC<P> ({err:?}). \
Spawn the body via VehicleConfig::spawn_bevy (which inserts \
TranslationalStateC), or attach the component directly.",
label = _entity_label(&self.names, body),
)
})
.0
}
pub fn body_rotational_state(&self, body: Entity) -> Option<RotationalStateTyped<SelfRef>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_rotational_state",
);
self.rot.get(body).ok().map(|c| c.0)
}
pub fn body_mass(&self, body: Entity) -> Option<astrodyn::MassPropertiesTyped<SelfRef>> {
_assert_is_body(&self.body_filter, &self.names, body, "body_mass");
self.mass.get(body).ok().map(|c| c.0)
}
pub fn body_trans_accel(&self, body: Entity) -> Acceleration<RootInertial> {
_assert_is_body(&self.body_filter, &self.names, body, "body_trans_accel");
self.frame_derivs
.get(body)
.map(|c| c.0.trans_accel)
.unwrap_or_default()
}
pub fn body_rot_accel(&self, body: Entity) -> Option<AngularAcceleration<BodyFrame<SelfRef>>> {
_assert_is_body(&self.body_filter, &self.names, body, "body_rot_accel");
self.rot.get(body).ok().map(|_| {
self.frame_derivs
.get(body)
.map(|c| c.0.rot_accel)
.unwrap_or_default()
})
}
pub fn body_external_force(&self, body: Entity) -> Force<RootInertial> {
_assert_is_body(&self.body_filter, &self.names, body, "body_external_force");
self.ext_force.get(body).map(|c| c.0).unwrap_or_default()
}
pub fn body_external_torque(&self, body: Entity) -> Torque<BodyFrame<SelfRef>> {
_assert_is_body(&self.body_filter, &self.names, body, "body_external_torque");
self.ext_torque.get(body).map(|c| c.0).unwrap_or_default()
}
pub fn body_external_force_struct(&self, body: Entity) -> Force<StructuralFrame<SelfRef>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_external_force_struct",
);
self.ext_force_struct
.get(body)
.map(|c| c.0)
.unwrap_or_default()
}
pub fn body_external_torque_struct(&self, body: Entity) -> Torque<StructuralFrame<SelfRef>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_external_torque_struct",
);
self.ext_torque_struct
.get(body)
.map(|c| c.0)
.unwrap_or_default()
}
}
#[derive(SystemParam)]
pub struct BodyMutator<'w, 's, P: Planet> {
commands: Commands<'w, 's>,
body_filter: Query<'w, 's, (), With<DynamicsConfigC>>,
ext_force: Query<'w, 's, &'static mut ExternalForceC, With<DynamicsConfigC>>,
ext_torque: Query<'w, 's, &'static mut ExternalTorqueC, With<DynamicsConfigC>>,
ext_force_struct: Query<'w, 's, &'static mut ExternalForceStructC, With<DynamicsConfigC>>,
ext_torque_struct: Query<'w, 's, &'static mut ExternalTorqueStructC, With<DynamicsConfigC>>,
trans_read: Query<'w, 's, &'static TranslationalStateC<P>>,
rot_read: Query<'w, 's, &'static RotationalStateC>,
frame_derivs_read: Query<'w, 's, &'static FrameDerivativesC>,
names: Query<'w, 's, &'static Name>,
}
impl<P: Planet> BodyMutator<'_, '_, P> {
pub fn body_translational_state(
&self,
body: Entity,
) -> TranslationalStateTyped<PlanetInertial<P>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_translational_state",
);
self.trans_read
.get(body)
.unwrap_or_else(|err| {
panic!(
"BodyMutator::body_translational_state: {label} is a registered \
dynamic body but lacks TranslationalStateC<P> ({err:?}).",
label = _entity_label(&self.names, body),
)
})
.0
}
pub fn body_rotational_state(&self, body: Entity) -> Option<RotationalStateTyped<SelfRef>> {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"body_rotational_state",
);
self.rot_read.get(body).ok().map(|c| c.0)
}
pub fn body_trans_accel(&self, body: Entity) -> Acceleration<RootInertial> {
_assert_is_body(&self.body_filter, &self.names, body, "body_trans_accel");
self.frame_derivs_read
.get(body)
.map(|c| c.0.trans_accel)
.unwrap_or_default()
}
pub fn set_body_external_force_typed(&mut self, body: Entity, force: Force<RootInertial>) {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"set_body_external_force_typed",
);
if let Ok(mut c) = self.ext_force.get_mut(body) {
c.0 = force;
} else {
self.commands.entity(body).insert(ExternalForceC(force));
}
}
pub fn set_body_external_torque_typed(
&mut self,
body: Entity,
torque: Torque<BodyFrame<SelfRef>>,
) {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"set_body_external_torque_typed",
);
if let Ok(mut c) = self.ext_torque.get_mut(body) {
c.0 = torque;
} else {
self.commands.entity(body).insert(ExternalTorqueC(torque));
}
}
pub fn set_body_external_force_struct_typed(
&mut self,
body: Entity,
force: Force<StructuralFrame<SelfRef>>,
) {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"set_body_external_force_struct_typed",
);
if let Ok(mut c) = self.ext_force_struct.get_mut(body) {
c.0 = force;
} else {
self.commands
.entity(body)
.insert(ExternalForceStructC(force));
}
}
pub fn set_body_external_torque_struct_typed(
&mut self,
body: Entity,
torque: Torque<StructuralFrame<SelfRef>>,
) {
_assert_is_body(
&self.body_filter,
&self.names,
body,
"set_body_external_torque_struct_typed",
);
if let Ok(mut c) = self.ext_torque_struct.get_mut(body) {
c.0 = torque;
} else {
self.commands
.entity(body)
.insert(ExternalTorqueStructC(torque));
}
}
pub fn set_body_external_force(&mut self, body: Entity, force: DVec3) {
self.set_body_external_force_typed(body, Force::<RootInertial>::from_raw_si(force));
}
pub fn set_body_external_torque(&mut self, body: Entity, torque: DVec3) {
let typed = Torque::<BodyFrame<SelfRef>>::from_raw_si(torque);
self.set_body_external_torque_typed(body, typed);
}
pub fn set_body_external_force_struct(&mut self, body: Entity, force: DVec3) {
let typed = Force::<StructuralFrame<SelfRef>>::from_raw_si(force);
self.set_body_external_force_struct_typed(body, typed);
}
pub fn set_body_external_torque_struct(&mut self, body: Entity, torque: DVec3) {
let typed = Torque::<StructuralFrame<SelfRef>>::from_raw_si(torque);
self.set_body_external_torque_struct_typed(body, typed);
}
}
fn _entity_label(names: &Query<&'static Name>, entity: Entity) -> String {
match names.get(entity) {
Ok(name) => format!("{name} ({entity:?})"),
Err(_) => format!("{entity:?}"),
}
}
fn _assert_is_body(
body_filter: &Query<(), With<DynamicsConfigC>>,
names: &Query<&'static Name>,
body: Entity,
method: &str,
) {
if body_filter.get(body).is_err() {
panic!(
"BodyReader/BodyMutator::{method}: {label} is not a registered dynamic body \
— it is missing DynamicsConfigC. Spawn the body via \
`VehicleConfig::spawn_bevy(...)` (which inserts DynamicsConfigC + the \
per-stage state / load components) before reading or writing its loads. \
If the entity is a gravity source (planet), pass it to SourceReader / \
SourceMutator instead — bodies and sources occupy disjoint entity \
slots and the SystemParam filter is the demarcation.",
label = _entity_label(names, body),
);
}
}