use crate::frame_tree::{FrameId, FrameTree};
use crate::ref_frame_state::RefFrameState;
pub trait FrameStorage {
type Id: Copy + Eq + std::fmt::Debug;
fn parent(&self, id: Self::Id) -> Option<Self::Id>;
fn state(&self, id: Self::Id) -> RefFrameState;
}
pub fn compose_to_ancestor<S: FrameStorage>(
storage: &S,
id: S::Id,
ancestor: S::Id,
) -> RefFrameState {
if id == ancestor {
return RefFrameState::default();
}
let mut composed = storage.state(id);
let mut current = id;
while let Some(parent) = storage.parent(current) {
if parent == ancestor {
return composed;
}
composed.incr_left(&storage.state(parent));
current = parent;
}
panic!(
"compose_to_ancestor: frame {id:?} is not a descendant of \
ancestor {ancestor:?}; common_ancestor walk should have caught this"
);
}
pub fn common_ancestor<S: FrameStorage>(storage: &S, a: S::Id, b: S::Id) -> Option<S::Id> {
let mut da = depth(storage, a);
let mut db = depth(storage, b);
let mut ca = a;
let mut cb = b;
while da > db {
ca = storage.parent(ca)?;
da -= 1;
}
while db > da {
cb = storage.parent(cb)?;
db -= 1;
}
while ca != cb {
ca = storage.parent(ca)?;
cb = storage.parent(cb)?;
}
Some(ca)
}
fn depth<S: FrameStorage>(storage: &S, id: S::Id) -> usize {
let mut d = 0usize;
let mut current = id;
while let Some(parent) = storage.parent(current) {
d += 1;
current = parent;
}
d
}
pub fn compute_relative_state<S: FrameStorage>(
storage: &S,
from: S::Id,
to: S::Id,
) -> RefFrameState {
if from == to {
return RefFrameState::default();
}
let ancestor = common_ancestor(storage, from, to).unwrap_or_else(|| {
panic!(
"compute_relative_state: frames {from:?} and {to:?} do not \
share a common ancestor"
)
});
let state_from = compose_to_ancestor(storage, from, ancestor);
let state_to = compose_to_ancestor(storage, to, ancestor);
let from_negated = RefFrameState::negate(&state_from);
from_negated.incr_right(&state_to)
}
impl FrameStorage for FrameTree {
type Id = FrameId;
fn parent(&self, id: FrameId) -> Option<FrameId> {
FrameTree::parent(self, id)
}
fn state(&self, id: FrameId) -> RefFrameState {
self.get(id).state
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ref_frame_state::{RefFrameRot, RefFrameTrans};
use astrodyn_quantities::frame_descriptor::{FrameClass, FrameRole, FrameUid, Namespace, Tag};
use glam::DVec3;
fn ext_uid(class: FrameClass) -> FrameUid {
use std::sync::atomic::{AtomicUsize, Ordering};
static N: AtomicUsize = AtomicUsize::new(0);
let n = N.fetch_add(1, Ordering::Relaxed);
FrameUid::external(
Namespace(2),
class,
FrameRole::Primary,
Tag::Named(format!("fs{n}").into()),
)
}
fn add_root(tree: &mut FrameTree, name: String) -> FrameId {
tree.add_root_uid(ext_uid(FrameClass::PlanetInertial), name)
}
fn add_child(
tree: &mut FrameTree,
parent: FrameId,
name: String,
state: RefFrameState,
) -> FrameId {
tree.add_child_uid(parent, ext_uid(FrameClass::External), name, state, None)
}
fn build_tree() -> (FrameTree, FrameId, FrameId, FrameId) {
let mut tree = FrameTree::new();
let root = add_root(&mut tree, "root".into());
let a = add_child(
&mut tree,
root,
"A".into(),
RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(1.0e6, 2.0e6, 3.0e6),
velocity: DVec3::new(10.0, 20.0, 30.0),
},
rot: RefFrameRot::default(),
},
);
let b = add_child(
&mut tree,
a,
"B".into(),
RefFrameState {
trans: RefFrameTrans {
position: DVec3::new(100.0, 200.0, 300.0),
velocity: DVec3::new(1.0, 2.0, 3.0),
},
rot: RefFrameRot::default(),
},
);
(tree, root, a, b)
}
#[test]
fn shared_compute_relative_state_matches_inherent() {
let (tree, root, _a, b) = build_tree();
let inherent = tree.compute_relative_state(root, b);
let shared = compute_relative_state(&tree, root, b);
assert_eq!(inherent.trans.position, shared.trans.position);
assert_eq!(inherent.trans.velocity, shared.trans.velocity);
assert_eq!(inherent.rot.t_parent_this, shared.rot.t_parent_this);
assert_eq!(inherent.rot.ang_vel_this, shared.rot.ang_vel_this);
}
#[test]
fn shared_common_ancestor_matches_inherent() {
let (tree, root, a, b) = build_tree();
let mut tree = tree;
let _c = add_child(&mut tree, root, "C".into(), RefFrameState::default());
assert_eq!(common_ancestor(&tree, b, a), Some(a));
assert_eq!(common_ancestor(&tree, b, root), Some(root));
assert_eq!(common_ancestor(&tree, a, b), Some(a));
}
}