rhusics_ecs/collide/systems/
spatial_collision.rs

1use std::fmt::Debug;
2
3use cgmath::prelude::*;
4use cgmath::BaseFloat;
5use collision::dbvt::{DynamicBoundingVolumeTree, TreeValue};
6use collision::prelude::*;
7use shrev::EventChannel;
8use specs::prelude::{
9    BitSet, Component, ComponentEvent, Entities, Entity, Join, ReadStorage, ReaderId, Resources,
10    System, Tracked, Write,
11};
12
13use core::{
14    tree_collide, BroadPhase, CollisionData, CollisionShape, ContactEvent, GetId, NarrowPhase,
15    NextFrame, Primitive,
16};
17
18/// Collision detection [system](https://docs.rs/specs/0.9.5/specs/trait.System.html) for use with
19/// [`specs`](https://docs.rs/specs/0.9.5/specs/).
20///
21/// Will perform spatial sorting of the collision world.
22///
23/// Has support for both broad phase and narrow phase collision detection. Will only do narrow phase
24/// if both broad and narrow phase is activated. If no broad phase is set, it will use a DBVT based
25/// broad phase that has complexity O(m log^2 n), where m is the number of shapes that have a dirty
26/// pose.
27///
28/// Can handle any transform component type, as long as the type implements
29/// [`Transform`](https://docs.rs/cgmath/0.15.0/cgmath/trait.Transform.html), and as long as the
30/// storage is wrapped in
31/// [`FlaggedStorage`](https://docs.rs/specs/0.9.5/specs/struct.FlaggedStorage.html).
32///
33/// ### Type parameters:
34///
35/// - `P`: Shape primitive
36/// - `T`: Transform
37/// - `D`: Data accepted by broad phase
38/// - `Y`: Shape type, see `Collider`
39///
40/// ### System Function:
41///
42/// `fn(Entities, T, NextFrame<T>, CollisionShape, DynamicBoundingVolumeTree<D>) -> (DynamicBoundingVolumeTree<D>, EventChannel<ContactEvent>)`
43pub struct SpatialCollisionSystem<P, T, D, B, Y = ()>
44where
45    P: Primitive,
46    B: Bound,
47{
48    narrow: Option<Box<NarrowPhase<P, T, B, Y>>>,
49    broad: Option<Box<BroadPhase<D>>>,
50    dirty: BitSet,
51    pose_reader: Option<ReaderId<ComponentEvent>>,
52    next_pose_reader: Option<ReaderId<ComponentEvent>>,
53}
54
55impl<P, T, D, B, Y> SpatialCollisionSystem<P, T, D, B, Y>
56where
57    P: Primitive + Send + Sync + 'static,
58    <P::Point as EuclideanSpace>::Diff: Debug,
59    <P::Point as EuclideanSpace>::Scalar: BaseFloat,
60    B: Clone
61        + Debug
62        + Send
63        + Sync
64        + 'static
65        + Bound<Point = P::Point>
66        + Union<B, Output = B>
67        + Contains<B>
68        + SurfaceArea<Scalar = <P::Point as EuclideanSpace>::Scalar>,
69    T: Transform<P::Point> + Component,
70    D: HasBound<Bound = B>,
71{
72    /// Create a new collision detection system, with no broad or narrow phase activated.
73    pub fn new() -> Self {
74        SpatialCollisionSystem {
75            narrow: None,
76            broad: None,
77            dirty: BitSet::default(),
78            pose_reader: None,
79            next_pose_reader: None,
80        }
81    }
82
83    /// Specify what narrow phase algorithm to use
84    pub fn with_narrow_phase<N: NarrowPhase<P, T, B, Y> + 'static>(mut self, narrow: N) -> Self {
85        self.narrow = Some(Box::new(narrow));
86        self
87    }
88
89    /// Specify what broad phase algorithm to use
90    pub fn with_broad_phase<V: BroadPhase<D> + 'static>(mut self, broad: V) -> Self {
91        self.broad = Some(Box::new(broad));
92        self
93    }
94}
95
96impl<'a, P, T, Y, B, D> System<'a> for SpatialCollisionSystem<P, T, (usize, D), B, Y>
97where
98    P: Primitive + ComputeBound<B> + Send + Sync + 'static,
99    P::Point: EuclideanSpace,
100    <P::Point as EuclideanSpace>::Scalar: BaseFloat + Send + Sync + 'static,
101    B: Clone
102        + Debug
103        + Send
104        + Sync
105        + 'static
106        + Bound<Point = P::Point>
107        + Union<B, Output = B>
108        + Discrete<B>
109        + Contains<B>
110        + SurfaceArea<Scalar = <P::Point as EuclideanSpace>::Scalar>,
111    <P::Point as EuclideanSpace>::Diff: Debug + Send + Sync + 'static,
112    P::Point: Debug + Send + Sync + 'static,
113    T: Component + Clone + Debug + Transform<P::Point> + Send + Sync + 'static,
114    T::Storage: Tracked,
115    Y: Default + Send + Sync + 'static,
116    D: Send + Sync + 'static + TreeValue<Bound = B> + HasBound<Bound = B> + GetId<Entity>,
117{
118    type SystemData = (
119        Entities<'a>,
120        ReadStorage<'a, T>,
121        ReadStorage<'a, NextFrame<T>>,
122        ReadStorage<'a, CollisionShape<P, T, B, Y>>,
123        Write<'a, EventChannel<ContactEvent<Entity, P::Point>>>,
124        Write<'a, DynamicBoundingVolumeTree<D>>,
125    );
126
127    fn run(&mut self, system_data: Self::SystemData) {
128        let (entities, poses, next_poses, shapes, mut event_channel, mut tree) = system_data;
129        self.dirty.clear();
130
131        for event in poses.channel().read(self.pose_reader.as_mut().unwrap()) {
132            match event {
133                ComponentEvent::Inserted(index) => {
134                    self.dirty.add(*index);
135                }
136                ComponentEvent::Modified(index) => {
137                    self.dirty.add(*index);
138                }
139                ComponentEvent::Removed(index) => {
140                    self.dirty.remove(*index);
141                }
142            }
143        }
144        for event in next_poses
145            .channel()
146            .read(self.next_pose_reader.as_mut().unwrap())
147        {
148            match event {
149                ComponentEvent::Inserted(index) => {
150                    self.dirty.add(*index);
151                }
152                ComponentEvent::Modified(index) => {
153                    self.dirty.add(*index);
154                }
155                ComponentEvent::Removed(index) => {
156                    self.dirty.remove(*index);
157                }
158            }
159        }
160
161        event_channel.iter_write(tree_collide(
162            &SpatialCollisionData {
163                poses,
164                shapes,
165                next_poses,
166                entities,
167                dirty: &self.dirty,
168            },
169            &mut *tree,
170            &mut self.broad,
171            &self.narrow,
172        ));
173    }
174
175    fn setup(&mut self, res: &mut Resources) {
176        use specs::prelude::{SystemData, WriteStorage};
177        Self::SystemData::setup(res);
178        let mut poses = WriteStorage::<T>::fetch(res);
179        self.pose_reader = Some(poses.register_reader());
180        let mut next_poses = WriteStorage::<NextFrame<T>>::fetch(res);
181        self.next_pose_reader = Some(next_poses.register_reader());
182    }
183}
184
185/// Collision data used by ECS systems
186pub struct SpatialCollisionData<'a, P, T, B, Y>
187where
188    P: Primitive + ComputeBound<B> + Send + Sync + 'static,
189    P::Point: Debug + Send + Sync + 'static,
190    <P::Point as EuclideanSpace>::Scalar: Send + Sync + 'static,
191    <P::Point as EuclideanSpace>::Diff: Debug + Send + Sync + 'static,
192    T: Component + Transform<P::Point> + Send + Sync + Clone + 'static,
193    Y: Default + Send + Sync + 'static,
194    B: Bound<Point = P::Point> + Send + Sync + 'static + Union<B, Output = B> + Clone,
195{
196    /// collision shapes
197    pub shapes: ReadStorage<'a, CollisionShape<P, T, B, Y>>,
198    /// current frame poses
199    pub poses: ReadStorage<'a, T>,
200    /// next frame poses
201    pub next_poses: ReadStorage<'a, NextFrame<T>>,
202    /// entities
203    pub entities: Entities<'a>,
204    /// dirty poses
205    pub dirty: &'a BitSet,
206}
207
208impl<'a, P, T, B, Y, D> CollisionData<Entity, P, T, B, Y, D>
209    for SpatialCollisionData<'a, P, T, B, Y>
210where
211    P: Primitive + ComputeBound<B> + Send + Sync + 'static,
212    P::Point: Debug + Send + Sync + 'static,
213    <P::Point as EuclideanSpace>::Scalar: Send + Sync + 'static,
214    <P::Point as EuclideanSpace>::Diff: Debug + Send + Sync + 'static,
215    T: Component + Transform<P::Point> + Send + Sync + Clone + 'static,
216    Y: Default + Send + Sync + 'static,
217    B: Bound<Point = P::Point> + Send + Sync + 'static + Union<B, Output = B> + Clone,
218{
219    fn get_broad_data(&self) -> Vec<D> {
220        Vec::default()
221    }
222
223    fn get_shape(&self, id: Entity) -> Option<&CollisionShape<P, T, B, Y>> {
224        self.shapes.get(id)
225    }
226
227    fn get_pose(&self, id: Entity) -> Option<&T> {
228        self.poses.get(id)
229    }
230
231    fn get_dirty_poses(&self) -> Vec<Entity> {
232        (&*self.entities, self.dirty, &self.shapes)
233            .join()
234            .map(|(entity, _, _)| entity)
235            .collect()
236    }
237
238    fn get_next_pose(&self, id: Entity) -> Option<&T> {
239        self.next_poses.get(id).as_ref().map(|p| &p.value)
240    }
241}