use specs::{self, Entity, ReadStorage, WriteStorage};
use super::types::*;
pub struct Spatial {
local_transform: Iso3,
parent_entity: Option<Entity>,
}
impl Spatial {
pub fn new(parent_entity: Entity, local_transform: Iso3) -> Spatial {
Spatial {
parent_entity: Some(parent_entity),
local_transform: local_transform,
}
}
pub fn new_root() -> Spatial {
use num_traits::One;
Spatial {
parent_entity: None,
local_transform: Iso3::one(),
}
}
pub fn local_transform(&self) -> Iso3 {
self.local_transform
}
pub fn set_local_transform(&mut self, new_local_transform: Iso3) {
self.local_transform = new_local_transform;
}
pub fn parent_entity(&self) -> Option<Entity> {
self.parent_entity
}
}
impl specs::Component for Spatial {
type Storage = specs::VecStorage<Spatial>;
}
pub trait SpatialStorage {
fn depth_of(&self, entity: Entity) -> i32;
fn root_of(&self, entity: Entity) -> Entity;
fn have_common_ancestor(&self, a: Entity, b: Entity) -> bool;
fn lowest_common_ancestor(&self, a: Entity, b: Entity) -> Entity;
fn a_relative_to_b(&self, a: Entity, b: Entity) -> Iso3;
fn a_relative_to_ancestor_b(&self, a: Entity, b: Entity) -> Iso3;
fn a_local_transform_relative_to_ancestor_b(
&self,
a: Entity,
a_local_transform: Iso3,
b: Entity,
) -> Iso3;
}
pub trait MaybeMutStorage<'a, T> {
fn get(&self, e: Entity) -> Option<&T>;
}
impl<'a, T> MaybeMutStorage<'a, T> for ReadStorage<'a, T>
where
T: specs::Component,
{
fn get(&self, e: Entity) -> Option<&T> {
(self as &ReadStorage<T>).get(e)
}
}
impl<'a, T> MaybeMutStorage<'a, T> for WriteStorage<'a, T>
where
T: specs::Component,
{
fn get(&self, e: Entity) -> Option<&T> {
(self as &WriteStorage<T>).get(e)
}
}
impl<'e, S> SpatialStorage for S
where
S: MaybeMutStorage<'e, Spatial>,
{
fn depth_of(&self, entity: Entity) -> i32 {
let spatial = self.get(entity).expect(
"Given entity doesn't have a Spatial",
);
match spatial.parent_entity {
None => 0,
Some(parent) => 1 + self.depth_of(parent),
}
}
fn root_of(&self, entity: Entity) -> Entity {
let spatial = self.get(entity).expect(
"Given entity doesn't have a Spatial",
);
match spatial.parent_entity {
None => entity,
Some(parent) => self.root_of(parent),
}
}
fn have_common_ancestor(&self, a: Entity, b: Entity) -> bool {
self.root_of(a) == self.root_of(b)
}
fn lowest_common_ancestor(&self, mut a: Entity, mut b: Entity) -> Entity {
debug_assert!(self.have_common_ancestor(a, b));
let mut depth_delta = self.depth_of(a) - self.depth_of(b);
while depth_delta > 0 {
a = self.get(a)
.expect("Entity isn't a Spatial")
.parent_entity
.expect("I thought this Spatial had a parent...");
depth_delta -= 1;
}
while depth_delta < 0 {
b = self.get(b)
.expect("Entity isn't a Spatial")
.parent_entity
.expect("I thought this Spatial had a parent...");
depth_delta += 1;
}
while a != b {
a = self.get(a)
.expect("Entity isn't a Spatial")
.parent_entity
.expect(
"I thought this Spatial had a parent; maybe a and b do not share a root...",
);
b = self.get(b)
.expect("Entity isn't a Spatial")
.parent_entity
.expect(
"I thought this Spatial had a parent; maybe a and b do not share a root...",
);
}
a
}
fn a_relative_to_b(&self, a: Entity, b: Entity) -> Iso3 {
let lca = self.lowest_common_ancestor(a, b);
let a_relative_to_lca = self.a_relative_to_ancestor_b(a, lca);
let b_relative_to_lca = self.a_relative_to_ancestor_b(b, lca);
b_relative_to_lca.inverse() * a_relative_to_lca
}
fn a_relative_to_ancestor_b(&self, a: Entity, b: Entity) -> Iso3 {
self.a_local_transform_relative_to_ancestor_b(a, Iso3::identity(), b)
}
fn a_local_transform_relative_to_ancestor_b(
&self,
a: Entity,
a_local_transform: Iso3,
b: Entity,
) -> Iso3 {
if a == b {
a_local_transform
} else {
let a_spatial = self.get(a).expect("Entity isn't a Spatial");
let parent = a_spatial.parent_entity.expect(
"I thought this Spatial had a parent...",
);
self.a_local_transform_relative_to_ancestor_b(parent, a_spatial.local_transform(), b) *
a_local_transform
}
}
}
#[cfg(test)]
mod tests {
use specs;
use na;
use super::*;
struct SolarSystem {
pub world: specs::World,
pub sun: specs::Entity,
pub earth: specs::Entity,
pub moon: specs::Entity,
pub polar_satellite: specs::Entity,
}
impl SolarSystem {
pub fn new() -> SolarSystem {
let mut world = specs::World::new();
world.register::<Spatial>();
let sun = world.create_entity().with(Spatial::new_root()).build();
let earth_transform = Iso3::new(Vec3::new(1000.0, 2000.0, 0.0), na::zero());
let earth = world
.create_entity()
.with(Spatial::new(sun, earth_transform))
.build();
let moon_transform = Iso3::new(Vec3::new(300.0, 400.0, 0.0), na::zero());
let moon = world
.create_entity()
.with(Spatial::new(earth, moon_transform))
.build();
let eye = Pt3::new(0.0, 0.0, 10.0);
let target = Pt3::origin();
let polar_satellite_transform = Iso3::look_at_rh(&eye, &target, &Vec3::y());
let polar_satellite = world
.create_entity()
.with(Spatial::new(earth, polar_satellite_transform))
.build();
SolarSystem {
world: world,
sun: sun,
earth: earth,
moon: moon,
polar_satellite: polar_satellite,
}
}
}
#[test]
fn pos_relative_to_parent() {
let ss = SolarSystem::new();
let spatials = ss.world.read::<Spatial>();
let earth_from_sun = spatials.a_relative_to_b(ss.earth, ss.sun);
assert_relative_eq!(
earth_from_sun.translation.vector,
Vec3::new(1000.0, 2000.0, 0.0),
);
let sun_from_earth = spatials.a_relative_to_b(ss.sun, ss.earth);
assert_relative_eq!(
sun_from_earth.translation.vector,
Vec3::new(-1000.0, -2000.0, 0.0),
);
}
#[test]
fn pos_relative_to_grandparent() {
let ss = SolarSystem::new();
let spatials = ss.world.read::<Spatial>();
let moon_from_sun = spatials.a_relative_to_b(ss.moon, ss.sun);
assert_relative_eq!(
moon_from_sun.translation.vector,
Vec3::new(1300.0, 2400.0, 0.0),
);
let sun_from_moon = spatials.a_relative_to_b(ss.sun, ss.moon);
assert_relative_eq!(
sun_from_moon.translation.vector,
Vec3::new(-1300.0, -2400.0, 0.0),
);
}
#[test]
fn pos_accounting_for_orientation_relative_to_parent() {
let ss = SolarSystem::new();
let mut spatials = ss.world.write::<Spatial>();
let earth_from_polar_satellite = spatials.a_relative_to_b(ss.earth, ss.polar_satellite);
assert_relative_eq!(
earth_from_polar_satellite.translation.vector,
Vec3::new(0.0, 0.0, 10.0),
);
let eye = Pt3::from_coordinates(
spatials
.get(ss.earth)
.unwrap()
.local_transform()
.translation
.vector,
);
let target = Pt3::new(123.0, 321.0, 456.0);
let new_earth_transform = Iso3::look_at_rh(&eye, &target, &Vec3::y());
spatials.get_mut(ss.earth).unwrap().set_local_transform(
new_earth_transform,
);
let earth_from_polar_satellite = spatials.a_relative_to_b(ss.earth, ss.polar_satellite);
assert_relative_eq!(
earth_from_polar_satellite.translation.vector,
Vec3::new(0.0, 0.0, 10.0),
);
}
}