use std::marker::PhantomData;
use specs::{
storage::ComponentEvent,
world::Index,
Join,
ReadStorage,
ReaderId,
Resources,
System,
SystemData,
WriteExpect,
WriteStorage,
};
use crate::{
bodies::Position,
colliders::PhysicsCollider,
nalgebra::RealField,
nphysics::object::{BodyPartHandle, ColliderDesc},
Physics,
PhysicsParent,
};
use super::iterate_component_events;
pub struct SyncCollidersToPhysicsSystem<N, P> {
positions_reader_id: Option<ReaderId<ComponentEvent>>,
physics_colliders_reader_id: Option<ReaderId<ComponentEvent>>,
n_marker: PhantomData<N>,
p_marker: PhantomData<P>,
}
impl<'s, N, P> System<'s> for SyncCollidersToPhysicsSystem<N, P>
where
N: RealField,
P: Position<N>,
{
type SystemData = (
ReadStorage<'s, P>,
ReadStorage<'s, PhysicsParent>,
WriteExpect<'s, Physics<N>>,
WriteStorage<'s, PhysicsCollider<N>>,
);
fn run(&mut self, data: Self::SystemData) {
let (positions, parent_entities, mut physics, mut physics_colliders) = data;
let (inserted_positions, ..) =
iterate_component_events(&positions, self.positions_reader_id.as_mut().unwrap());
let (inserted_physics_colliders, modified_physics_colliders, removed_physics_colliders) =
iterate_component_events(
&physics_colliders,
self.physics_colliders_reader_id.as_mut().unwrap(),
);
for (position, parent_entity, mut physics_collider, id) in (
&positions,
parent_entities.maybe(),
&mut physics_colliders,
&inserted_positions
| &inserted_physics_colliders
| &modified_physics_colliders
| &removed_physics_colliders,
)
.join()
{
if inserted_positions.contains(id) || inserted_physics_colliders.contains(id) {
debug!("Inserted PhysicsCollider with id: {}", id);
add_collider::<N, P>(
id,
parent_entity,
&position,
&mut physics,
&mut physics_collider,
);
}
if modified_physics_colliders.contains(id) {
debug!("Modified PhysicsCollider with id: {}", id);
update_collider::<N, P>(id, &mut physics, &physics_collider);
}
if removed_physics_colliders.contains(id) {
debug!("Removed PhysicsCollider with id: {}", id);
remove_collider::<N, P>(id, &mut physics);
}
}
}
fn setup(&mut self, res: &mut Resources) {
info!("SyncCollidersToPhysicsSystem.setup");
Self::SystemData::setup(res);
res.entry::<Physics<N>>().or_insert_with(Physics::default);
let mut position_storage: WriteStorage<P> = SystemData::fetch(&res);
self.positions_reader_id = Some(position_storage.register_reader());
let mut physics_collider_storage: WriteStorage<PhysicsCollider<N>> =
SystemData::fetch(&res);
self.physics_colliders_reader_id = Some(physics_collider_storage.register_reader());
}
}
impl<N, P> Default for SyncCollidersToPhysicsSystem<N, P>
where
N: RealField,
P: Position<N>,
{
fn default() -> Self {
Self {
positions_reader_id: None,
physics_colliders_reader_id: None,
n_marker: PhantomData,
p_marker: PhantomData,
}
}
}
fn add_collider<N, P>(
id: Index,
parent_entity: Option<&PhysicsParent>,
position: &P,
physics: &mut Physics<N>,
physics_collider: &mut PhysicsCollider<N>,
) where
N: RealField,
P: Position<N>,
{
if let Some(handle) = physics.collider_handles.remove(&id) {
warn!("Removing orphaned collider handle: {:?}", handle);
physics.world.remove_colliders(&[handle]);
}
let parent_part_handle = match physics.body_handles.get(&id) {
Some(parent_handle) => physics
.world
.rigid_body(*parent_handle)
.map_or(BodyPartHandle::ground(), |body| body.part_handle()),
None => {
if let Some(parent_entity) = parent_entity {
match physics.body_handles.get(&parent_entity.entity.id()) {
Some(parent_handle) => physics
.world
.rigid_body(*parent_handle)
.map_or(BodyPartHandle::ground(), |body| body.part_handle()),
None => {
BodyPartHandle::ground()
}
}
} else {
BodyPartHandle::ground()
}
}
};
let translation = if parent_part_handle.is_ground() {
let iso = &mut position.isometry().clone();
iso.translation.vector +=
iso.rotation * physics_collider.offset_from_parent.translation.vector; iso.rotation *= physics_collider.offset_from_parent.rotation;
*iso
} else {
physics_collider.offset_from_parent
};
let handle = ColliderDesc::new(physics_collider.shape_handle())
.position(translation)
.density(physics_collider.density)
.material(physics_collider.material.clone())
.margin(physics_collider.margin)
.collision_groups(physics_collider.collision_groups)
.linear_prediction(physics_collider.linear_prediction)
.angular_prediction(physics_collider.angular_prediction)
.sensor(physics_collider.sensor)
.user_data(id)
.build_with_parent(parent_part_handle, &mut physics.world)
.unwrap()
.handle();
physics_collider.handle = Some(handle);
physics.collider_handles.insert(id, handle);
info!(
"Inserted collider to world with values: {:?}",
physics_collider
);
}
fn update_collider<N, P>(id: Index, physics: &mut Physics<N>, physics_collider: &PhysicsCollider<N>)
where
N: RealField,
P: Position<N>,
{
debug!("Modified PhysicsCollider with id: {}", id);
let collider_handle = physics_collider.handle.unwrap();
let collider_world = physics.world.collider_world_mut();
collider_world.set_collision_groups(collider_handle, physics_collider.collision_groups);
info!(
"Updated collider in world with values: {:?}",
physics_collider
);
}
fn remove_collider<N, P>(id: Index, physics: &mut Physics<N>)
where
N: RealField,
P: Position<N>,
{
debug!("Removed PhysicsCollider with id: {}", id);
if let Some(handle) = physics.collider_handles.remove(&id) {
if physics.world.collider(handle).is_some() {
physics.world.remove_colliders(&[handle]);
}
info!("Removed collider from world with id: {}", id);
}
}
#[cfg(test)]
mod tests {
use specs::{world::Builder, DispatcherBuilder, World};
use crate::{
colliders::Shape,
nalgebra::Isometry3,
systems::SyncCollidersToPhysicsSystem,
Physics,
PhysicsColliderBuilder,
SimplePosition,
};
#[test]
fn add_collider() {
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(
SyncCollidersToPhysicsSystem::<f32, SimplePosition<f32>>::default(),
"sync_colliders_to_physics_system",
&[],
)
.build();
dispatcher.setup(&mut world.res);
world
.create_entity()
.with(SimplePosition::<f32>(Isometry3::<f32>::translation(
1.0, 1.0, 1.0,
)))
.with(PhysicsColliderBuilder::<f32>::from(Shape::Circle(5.0)).build())
.build();
dispatcher.dispatch(&mut world.res);
let physics = world.read_resource::<Physics<f32>>();
assert_eq!(physics.collider_handles.len(), 1);
assert_eq!(physics.world.colliders().count(), 1);
}
}