specs_transform 0.5.0

transform 2d and 3d component for specs
use std::marker::PhantomData;
use std::ops::{Add, Mul, Neg, Sub};

use hibitset::BitSet;
use mat32;
use mat4;
use num_traits::Float;
use specs::prelude::{
    ComponentEvent, Entities, Join, ReadExpect, ReadStorage, ReaderId, System, World, WriteStorage,
};
use specs_scene_graph::{Event, SceneGraph};
use type_name;

use super::{GlobalTransform2D, GlobalTransform3D, Parent, Transform2D, Transform3D};

pub struct TransformSystem<T> {
    needs_global_2d: BitSet,
    needs_global_3d: BitSet,

    local_modified_2d: BitSet,
    global_modified_2d: BitSet,

    local_reader_id_2d: Option<ReaderId<ComponentEvent>>,
    global_reader_id_2d: Option<ReaderId<ComponentEvent>>,

    local_modified_3d: BitSet,
    global_modified_3d: BitSet,

    local_reader_id_3d: Option<ReaderId<ComponentEvent>>,
    global_reader_id_3d: Option<ReaderId<ComponentEvent>>,

    parent_events_id: Option<ReaderId<Event>>,

    _marker: PhantomData<T>,
}

impl<T> Default for TransformSystem<T> {
    #[inline]
    fn default() -> Self {
        TransformSystem {
            needs_global_2d: BitSet::default(),
            needs_global_3d: BitSet::default(),

            local_modified_2d: BitSet::default(),
            global_modified_2d: BitSet::default(),

            local_reader_id_2d: None,
            global_reader_id_2d: None,

            local_modified_3d: BitSet::default(),
            global_modified_3d: BitSet::default(),

            local_reader_id_3d: None,
            global_reader_id_3d: None,

            parent_events_id: None,

            _marker: PhantomData,
        }
    }
}

impl<T> TransformSystem<T> {
    #[inline]
    pub fn new() -> Self {
        Self::default()
    }
    #[inline]
    pub fn name() -> &'static str {
        type_name::get::<Self>()
    }
}

impl<'system, T> System<'system> for TransformSystem<T>
where
    T: 'static + Sync + Send + Float,
    for<'a, 'b> &'a T:
        Mul<&'b T, Output = T> + Neg<Output = T> + Add<&'b T, Output = T> + Sub<&'b T, Output = T>,
{
    type SystemData = (
        Entities<'system>,
        ReadExpect<'system, SceneGraph<Parent>>,
        ReadStorage<'system, Parent>,
        ReadStorage<'system, Transform2D<T>>,
        ReadStorage<'system, Transform3D<T>>,
        WriteStorage<'system, GlobalTransform2D<T>>,
        WriteStorage<'system, GlobalTransform3D<T>>,
    );

    #[inline]
    fn run(
        &mut self,
        (entities, hierarchy, parents, locals_2d, locals_3d, mut globals_2d, mut globals_3d): Self::SystemData,
    ) {
        // read events

        if let Some(local_reader_id_2d) = self.local_reader_id_2d.as_mut() {
            self.local_modified_2d.clear();

            for event in locals_2d.channel().read(local_reader_id_2d) {
                if let ComponentEvent::Modified(id) = event {
                    self.local_modified_2d.add(*id);
                }
            }
        }

        if let Some(global_reader_id_2d) = self.global_reader_id_2d.as_mut() {
            self.global_modified_2d.clear();

            for event in globals_2d.channel().read(global_reader_id_2d) {
                if let ComponentEvent::Modified(id) = event {
                    self.global_modified_2d.add(*id);
                }
            }
        }

        if let Some(local_reader_id_3d) = self.local_reader_id_3d.as_mut() {
            self.local_modified_3d.clear();

            for event in locals_3d.channel().read(local_reader_id_3d) {
                if let ComponentEvent::Modified(id) = event {
                    self.local_modified_3d.add(*id);
                }
            }
        }

        if let Some(global_reader_id_3d) = self.global_reader_id_3d.as_mut() {
            self.global_modified_3d.clear();

            for event in globals_3d.channel().read(global_reader_id_3d) {
                if let ComponentEvent::Modified(id) = event {
                    self.global_modified_3d.add(*id);
                }
            }
        }

        // read events from hierarchy

        for event in hierarchy
            .changed()
            .read(self.parent_events_id.as_mut().unwrap())
        {
            match *event {
                Event::Removed(entity) => {
                    let _ = entities.delete(entity);
                }
                Event::Modified(entity) => {
                    if locals_2d.contains(entity) {
                        self.local_modified_2d.add(entity.id());
                    }
                    if locals_3d.contains(entity) {
                        self.local_modified_3d.add(entity.id());
                    }
                }
            }
        }

        // everyone gets a global

        self.needs_global_2d.clear();
        for (entity, _, _) in (&*entities, &locals_2d, !&globals_2d).join() {
            self.needs_global_2d.add(entity.id());
        }
        for (entity, local_2d, _) in (&*entities, &locals_2d, &self.needs_global_2d).join() {
            self.global_modified_2d.add(entity.id());
            let _ = globals_2d.insert(entity, GlobalTransform2D::from(local_2d.matrix()));
        }

        self.needs_global_3d.clear();
        for (entity, _, _) in (&*entities, &locals_3d, !&globals_3d).join() {
            self.needs_global_3d.add(entity.id());
        }
        for (entity, local_3d, _) in (&*entities, &locals_3d, &self.needs_global_3d).join() {
            self.global_modified_3d.add(entity.id());
            let _ = globals_3d.insert(entity, GlobalTransform3D::from(local_3d.matrix()));
        }

        for (entity, _, local_2d, _) in
            (&*entities, &self.local_modified_2d, &locals_2d, !&parents).join()
        {
            self.global_modified_2d.add(entity.id());

            let global_2d = globals_2d
                .entry(entity)
                .unwrap()
                .or_insert_with(GlobalTransform2D::default);

            global_2d.0 = local_2d.matrix();
        }

        for (entity, _, local_3d, _) in
            (&*entities, &self.local_modified_3d, &locals_3d, !&parents).join()
        {
            self.global_modified_3d.add(entity.id());

            let global_3d = globals_3d
                .entry(entity)
                .unwrap()
                .or_insert_with(GlobalTransform3D::default);

            global_3d.0 = local_3d.matrix();
        }

        // update global matrices

        for entity in hierarchy.all() {
            match (
                parents.get(*entity),
                locals_2d.get(*entity),
                locals_3d.get(*entity),
            ) {
                (Some(parent), _, Some(local_3d)) => {
                    let self_dirty = self.local_modified_3d.contains(entity.id());
                    let parent_dirty = self.global_modified_3d.contains(parent.entity.id())
                        || self.global_modified_2d.contains(parent.entity.id());

                    if parent_dirty || self_dirty {
                        let mut global_3d_matrix = local_3d.matrix();

                        if locals_3d.contains(parent.entity) {
                            let parent_global_3d = globals_3d
                                .entry(parent.entity)
                                .unwrap()
                                .or_insert_with(GlobalTransform3D::default);

                            let local_3d_matrix = global_3d_matrix.clone();

                            mat4::mul(&mut global_3d_matrix, &parent_global_3d.0, &local_3d_matrix);
                        } else if locals_2d.contains(parent.entity) {
                            let parent_global_2d = globals_2d
                                .entry(parent.entity)
                                .unwrap()
                                .or_insert_with(GlobalTransform2D::default);

                            let local_3d_matrix = global_3d_matrix.clone();
                            let mut parent_global_3d_matrix = mat4::new_identity();

                            mat4::set_mat32(&mut parent_global_3d_matrix, &parent_global_2d.0);
                            mat4::mul(
                                &mut global_3d_matrix,
                                &parent_global_3d_matrix,
                                &local_3d_matrix,
                            );
                        }

                        let global_3d = globals_3d
                            .entry(*entity)
                            .unwrap()
                            .or_insert_with(GlobalTransform3D::default);

                        self.global_modified_3d.add(entity.id());
                        global_3d.0 = global_3d_matrix;
                    }
                }
                (Some(parent), Some(local_2d), _) => {
                    let self_dirty = self.local_modified_2d.contains(entity.id());
                    let parent_dirty = self.global_modified_3d.contains(parent.entity.id())
                        || self.global_modified_2d.contains(parent.entity.id());

                    if parent_dirty || self_dirty {
                        let mut global_2d_matrix = local_2d.matrix();

                        if locals_2d.contains(parent.entity) {
                            let parent_global_2d = globals_2d
                                .entry(parent.entity)
                                .unwrap()
                                .or_insert_with(GlobalTransform2D::default);

                            let local_2d_matrix = global_2d_matrix.clone();

                            mat32::mul(
                                &mut global_2d_matrix,
                                &parent_global_2d.0,
                                &local_2d_matrix,
                            );
                        } else if locals_3d.contains(parent.entity) {
                            let parent_global_3d = globals_3d
                                .entry(parent.entity)
                                .unwrap()
                                .or_insert_with(GlobalTransform3D::default);

                            let local_2d_matrix = global_2d_matrix.clone();
                            let mut parent_global_3d_matrix = mat32::new_identity();

                            mat32::set_mat4(&mut parent_global_3d_matrix, &parent_global_3d.0);

                            mat32::mul(
                                &mut global_2d_matrix,
                                &parent_global_3d_matrix,
                                &local_2d_matrix,
                            );
                        }

                        let global_2d = globals_2d
                            .entry(*entity)
                            .unwrap()
                            .or_insert_with(GlobalTransform2D::default);

                        self.global_modified_2d.add(entity.id());
                        global_2d.0 = global_2d_matrix;
                    }
                }
                _ => (),
            }
        }
    }

    #[inline]
    fn setup(&mut self, world: &mut World) {
        use specs::prelude::SystemData;

        Self::SystemData::setup(world);

        let mut hierarchy = world.fetch_mut::<SceneGraph<Parent>>();

        let mut locals_2d = WriteStorage::<Transform2D<T>>::fetch(world);
        let mut globals_2d = WriteStorage::<GlobalTransform2D<T>>::fetch(world);

        let mut locals_3d = WriteStorage::<Transform3D<T>>::fetch(world);
        let mut globals_3d = WriteStorage::<GlobalTransform3D<T>>::fetch(world);

        self.parent_events_id = Some(hierarchy.track());

        self.local_reader_id_2d = Some(locals_2d.register_reader());
        self.global_reader_id_2d = Some(globals_2d.register_reader());

        self.local_reader_id_3d = Some(locals_3d.register_reader());
        self.global_reader_id_3d = Some(globals_3d.register_reader());
    }
}

#[test]
fn test_transform_system_name() {
    assert_eq!(
        TransformSystem::<f32>::name(),
        "specs_transform::transform_system::TransformSystem<f32>"
    );

    struct Example;
    assert_eq!(
        TransformSystem::<Example>::name(),
        "specs_transform::transform_system::TransformSystem<specs_transform::transform_system::test_transform_system_name::Example>"
    );
}