specs-physics

[Fork] Integration of Amethyst, SPECS and Nphysics together.
git clone https://git.jojolepro.com/specs-physics.git
Log | Files | Refs | README | LICENSE

commit 697ebb7d51490989586dcee2eb6c75d041bd9424
parent 92b485cc45e09497376eff04ceddf1f47dd6ecfc
Author: Joël Lupien (Jojolepro) <jojolepromain@gmail.com>
Date:   Fri,  7 Dec 2018 21:01:42 -0500

Move to Rust 2018 and add Colliders

Diffstat:
M.gitignore | 3+--
MCargo.toml | 9+++++++--
Msrc/bodies.rs | 6++++++
Asrc/colliders/collider.rs | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/colliders/mod.rs | 7+++++++
Asrc/colliders/sync_colliders_to_physics.rs | 158+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 22+++++++++++++++++++---
Msrc/systems/mod.rs | 11++++++++++-
Msrc/systems/physics_stepper.rs | 4++--
Msrc/systems/sync_bodies_from_physics.rs | 48++++++++++++++++++++++++++++++++++++++++++------
Msrc/systems/sync_bodies_to_physics.rs | 6+++---
Msrc/systems/sync_gravity_to_physics.rs | 6+++---
12 files changed, 325 insertions(+), 22 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -2,4 +2,4 @@ **/*.rs.bk *.iml /.idea -cargo.lock- \ No newline at end of file +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml @@ -8,10 +8,15 @@ edition = "2018" # default = [ "nphysics3d" ] [dependencies] -nphysics3d = "0.9" -ncollide3d = "0.17" +nphysics3d = { git = "https://github.com/jojolepro/nphysics", branch = "tmp", features = ["serde"] } +#ncollide3d = "0.17" +#ncollide3d = { git = "https://github.com/jojolepro/ncollide", branch = "collidegroup", features = ["serde"] } +ncollide3d = { git = "https://github.com/jojolepro/ncollide", branch = "collidegroup" } nalgebra = "0.16" num-traits = "0.2" +derive-new = "0.5.6" +derive_builder = "0.7.0" +serde = { version = "1.0", features = ["derive"] } [dependencies.amethyst] git = "https://github.com/amethyst/amethyst" diff --git a/src/bodies.rs b/src/bodies.rs @@ -6,6 +6,7 @@ use nphysics3d::object::BodyHandle; use std::collections::HashMap; /// Physics body component for describing (currently) rigid body dynamics. +#[derive(Serialize, Deserialize, Debug, Clone)] pub enum DynamicBody { RigidBody(RigidPhysicsBody), Multibody(PhysicsMultibody), @@ -56,7 +57,10 @@ impl Component for DynamicBody { /// Rigid physics body, for use in `PhysicsBody` Component. /// Currently only the velocity is read and updated at runtime. /// The properties of mass are only written at physics body creation time. +#[derive(Serialize, Deserialize, Clone, Debug, new)] pub struct RigidPhysicsBody { + #[serde(skip)] + #[new(default)] pub(crate) handle: Option<BodyHandle>, pub velocity: Velocity<f32>, @@ -69,7 +73,9 @@ pub struct RigidPhysicsBody { } /// Multipart physics body, for use in `PhysicsBody` Component. Not implemented yet. +#[derive(Serialize, Deserialize, Clone, Debug)] pub struct PhysicsMultibody { + #[serde(skip)] pub handle: Option<BodyHandle>, } diff --git a/src/colliders/collider.rs b/src/colliders/collider.rs @@ -0,0 +1,67 @@ + +use ncollide::{ + shape::ShapeHandle, + world::GeometricQueryType, +}; +use nphysics::object::{Material, ColliderHandle}; +use nalgebra::Isometry3; +use amethyst::ecs::{Component, FlaggedStorage, DenseVecStorage}; + +#[derive(Clone, Serialize, Deserialize, Debug, new)] +pub enum ColliderType { + Collider, + Trigger, +} + +impl Default for ColliderType { + fn default() -> Self { + ColliderType::Collider + } +} + +impl ColliderType { + pub fn to_geometric_query_type(&self, margin: f32, prediction: f32, angular_prediction: f32) -> GeometricQueryType<f32> { + match *self { + ColliderType::Collider => GeometricQueryType::Contacts(margin + prediction * 0.5, angular_prediction), + ColliderType::Trigger => GeometricQueryType::Proximity(prediction * 0.5), + } + } +} + +#[derive(new, Clone, Builder/*, Serialize, Deserialize*/)] +#[builder(pattern = "owned")] +pub struct Collider { + #[new(default)] + //#[serde(skip)] + pub(crate) handle: Option<ColliderHandle>, + /// Warning: Changing the margin after inserting the entity will have no effect. + pub margin: f32, + pub shape: ShapeHandle<f32>, + pub offset_from_parent: Isometry3<f32>, + pub physics_material: Material<f32>, + pub collision_group: u32, + pub query_type: ColliderType, +} + +impl From<ShapeHandle<f32>> for ColliderBuilder { + fn from(shape: ShapeHandle<f32>) -> ColliderBuilder { + ColliderBuilder::default() + .margin(0.01) + .shape(shape) + .offset_from_parent(Isometry3::identity()) + .query_type(ColliderType::default()) + } +} + +impl ColliderBuilder { + pub fn trigger(mut self) -> Self { + // query type = trigger + self.query_type = Some(ColliderType::Trigger); + self + } +} + + +impl Component for Collider { + type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; +} diff --git a/src/colliders/mod.rs b/src/colliders/mod.rs @@ -0,0 +1,6 @@ + +mod collider; +mod sync_colliders_to_physics; + +pub use self::collider::*; +pub use self::sync_colliders_to_physics::*;+ \ No newline at end of file diff --git a/src/colliders/sync_colliders_to_physics.rs b/src/colliders/sync_colliders_to_physics.rs @@ -0,0 +1,157 @@ +use crate::bodies::DynamicBody; +use crate::PhysicsWorld; +use amethyst::core::GlobalTransform; +use amethyst::ecs::storage::{ComponentEvent, MaskedStorage, GenericReadStorage}; +use amethyst::ecs::{ + BitSet, Component, Entities, Join, ReadStorage, ReaderId, Resources, Storage, System, + SystemData, Tracked, WriteExpect, WriteStorage, +}; +use core::ops::Deref; +use nphysics::object::BodyHandle; +use crate::Collider; + +#[derive(Default, new)] +pub struct SyncCollidersToPhysicsSystem { + #[new(default)] + colliders_reader_id: Option<ReaderId<ComponentEvent>>, +} + +impl<'a> System<'a> for SyncCollidersToPhysicsSystem { + type SystemData = ( + WriteExpect<'a, PhysicsWorld>, + Entities<'a>, + ReadStorage<'a, GlobalTransform>, + ReadStorage<'a, DynamicBody>, + WriteStorage<'a, Collider>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut physical_world, entities, transforms, rigid_bodies, mut colliders) = data; + // TODO: Check for inserted/removed rigid_bodies. parent sync https://nphysics.org/rustdoc/nphysics3d/world/struct.World.html?search=#method.add_collider + let mut inserted_colliders = BitSet::new(); + let mut modified_colliders = BitSet::new(); + + iterate_events( + &transforms, + self.colliders_reader_id.as_mut().unwrap(), + &mut inserted_colliders, + &mut modified_colliders, + &mut physical_world, + &entities, + &colliders, + ); + + for (entity, mut collider, id) in ( + &entities, + &mut colliders, + &inserted_colliders + | &modified_colliders, + ) + .join() + { + if inserted_colliders.contains(id) { + println!("Detected inserted collider with id {:?}", id); + // Just inserted. Remove old one and insert new. + if collider.handle.is_some() + && physical_world + .collider(collider.handle.unwrap()) + .is_some() + { + physical_world.remove_colliders(&[collider.handle.unwrap()]); + } + + let parent = if let Some(rb) = rigid_bodies.get(entity) { + rb.handle().expect("You should normally have a body handle at this point. This is a bug.") + } else { + BodyHandle::ground() + }; + + collider.handle = Some(physical_world.add_collider( + collider.margin, + collider.shape.clone(), + parent, + collider.offset_from_parent, + collider.physics_material.clone(), + )); + + let prediction = physical_world.prediction(); + let angular_prediction = physical_world.angular_prediction(); + + let collision_world = physical_world.collision_world_mut(); + let collider_handle = collision_world.collision_object(collider.handle.unwrap()).unwrap().handle().clone(); + collision_world.set_collision_group(collider_handle, collider.collision_group); + + let collider_object = collision_world.collision_object_mut(collider.handle.unwrap()).unwrap(); + + collider_object.set_query_type(collider.query_type.to_geometric_query_type(collider.margin, prediction, angular_prediction)); + } else if modified_colliders.contains(id) || modified_colliders.contains(id) { + println!("Detected changed collider with id {:?}", id); + + let prediction = physical_world.prediction(); + let angular_prediction = physical_world.angular_prediction(); + + let collision_world = physical_world.collision_world_mut(); + let collider_handle = collision_world.collision_object(collider.handle.unwrap()).unwrap().handle().clone(); + + collision_world.set_collision_group(collider_handle, collider.collision_group); + collision_world.set_shape(collider_handle, collider.shape.clone()); + + let collider_object = collision_world.collision_object_mut(collider.handle.unwrap()).unwrap(); + //collider_handle.set_shape(collider_handle.shape); + collider_object.set_position(collider.offset_from_parent.clone()); + collider_object.set_query_type(collider.query_type.to_geometric_query_type(collider.margin, prediction, angular_prediction)); + collider_object.data_mut().set_material(collider.physics_material.clone()); + } + } + + + } + + fn setup(&mut self, res: &mut Resources) { + Self::SystemData::setup(res); + + let mut collider_storage: WriteStorage<Collider> = SystemData::fetch(&res); + self.colliders_reader_id = Some(collider_storage.register_reader()); + } +} + +fn iterate_events<'a, T, D, S>( + tracked_storage: &Storage<T, D>, + reader: &mut ReaderId<ComponentEvent>, + inserted: &mut BitSet, + modified: &mut BitSet, + world: &mut PhysicsWorld, + entities: &Entities, + colliders: &S +) where + T: Component, + T::Storage: Tracked, + D: Deref<Target = MaskedStorage<T>>, + S: GenericReadStorage<Component=Collider>, +{ + let events = tracked_storage.channel().read(reader); + + for event in events { + match event { + ComponentEvent::Modified(id) => { modified.add(*id); }, + ComponentEvent::Inserted(id) => { inserted.add(*id); }, + ComponentEvent::Removed(id) => { + match colliders.get(entities.entity(*id)) { + Some(collider) => { + match collider.handle { + Some(handle) => { + world.remove_colliders(&[handle]); + } + None => { + // TODO: Log missing handle + } + }; + } + None => { + // TODO: Log missing body + } + }; + } + }; + } +}+ \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs @@ -1,10 +1,26 @@ -extern crate amethyst; +//! nphysics-ecs-dumb +//! Straight forward wrapper around nphysics to allow its usage inside of Amethyst's ECS: Specs + pub extern crate ncollide3d as ncollide; pub extern crate nphysics3d as nphysics; -extern crate num_traits; +#[macro_use] +extern crate derive_builder; +#[macro_use] +extern crate derive_new; +#[macro_use] +extern crate serde; pub mod bodies; pub mod systems; +pub mod colliders; + +pub use self::bodies::*; +pub use self::forces::*; +pub use self::systems::*; +pub use self::colliders::*; -pub type World = self::nphysics::world::World<f32>; +/// The Physical World containing all physical objects. +pub type PhysicsWorld = self::nphysics::world::World<f32>; +/// Gravity is a type alias for a Vector of dimension 3. +/// It represent a constant acceleration affecting all physical objects in the scene. pub type Gravity = self::nphysics::math::Vector<f32>; diff --git a/src/systems/mod.rs b/src/systems/mod.rs @@ -6,8 +6,9 @@ mod sync_gravity_to_physics; use amethyst::core::bundle::{Result, SystemBundle}; use amethyst::core::specs::DispatcherBuilder; +use crate::colliders::SyncCollidersToPhysicsSystem; pub use self::physics_stepper::PhysicsStepperSystem; -pub use self::sync_bodies_from_physics::SyncBodiesFromPhysicsSystem; +pub use self::sync_bodies_from_physics::*; pub use self::sync_bodies_to_physics::SyncBodiesToPhysicsSystem; pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem; @@ -16,6 +17,7 @@ pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem; pub const SYNC_BODIES_TO_PHYSICS_SYSTEM: &str = "sync_bodies_to_physics_system"; pub const SYNC_GRAVITY_TO_PHYSICS_SYSTEM: &str = "sync_gravity_to_physics_system"; +pub const SYNC_COLLIDERS_TO_PHYSICS_SYSTEM: &str = "sync_colliders_to_physics_system"; pub const PHYSICS_STEPPER_SYSTEM: &str = "physics_stepper_system"; pub const SYNC_BODIES_FROM_PHYSICS_SYSTEM: &str = "sync_bodies_from_physics_system"; @@ -49,11 +51,18 @@ impl<'a, 'b, 'c> SystemBundle<'a, 'b> for PhysicsBundle<'c> { ); builder.add( + SyncCollidersToPhysicsSystem::new(), + SYNC_COLLIDERS_TO_PHYSICS_SYSTEM, + &[], + ); + + builder.add( PhysicsStepperSystem::new(), PHYSICS_STEPPER_SYSTEM, &[ SYNC_BODIES_TO_PHYSICS_SYSTEM, SYNC_GRAVITY_TO_PHYSICS_SYSTEM, + SYNC_COLLIDERS_TO_PHYSICS_SYSTEM, ], ); diff --git a/src/systems/physics_stepper.rs b/src/systems/physics_stepper.rs @@ -1,4 +1,4 @@ -use crate::World; +use crate::PhysicsWorld; use amethyst::core::Time; use amethyst::ecs::{Read, System, WriteExpect}; @@ -13,7 +13,7 @@ impl PhysicsStepperSystem { } impl<'a> System<'a> for PhysicsStepperSystem { - type SystemData = (WriteExpect<'a, World>, Read<'a, Time>); + type SystemData = (WriteExpect<'a, PhysicsWorld>, Read<'a, Time>); fn run(&mut self, (mut physical_world, time): Self::SystemData) { // Simulate world using the current time frame diff --git a/src/systems/sync_bodies_from_physics.rs b/src/systems/sync_bodies_from_physics.rs @@ -1,12 +1,19 @@ +use nphysics3d::object::ColliderHandle; +use crate::colliders::Collider; use crate::bodies::DynamicBody; -use crate::World; +use crate::PhysicsWorld; use amethyst::core::{GlobalTransform, Transform}; -use amethyst::ecs::{Join, ReadStorage, System, Write, ReadExpect, WriteStorage}; +use amethyst::ecs::{Join, ReadStorage, System, Write, ReadExpect, WriteStorage, Entity, Entities}; +use amethyst::ecs::world::EntitiesRes; use amethyst::shrev::EventChannel; use nalgebra::Vector3; -use ncollide3d::events::ContactEvent; +use ncollide3d::events::{ContactEvent, ProximityEvent}; use nphysics3d::object::Body; +// Might want to replace by better types. +pub type EntityContactEvent = (Entity, Entity, ContactEvent); +pub type EntityProximityEvent = (Entity, Entity, ProximityEvent); + #[derive(Default)] pub struct SyncBodiesFromPhysicsSystem; @@ -18,20 +25,26 @@ impl SyncBodiesFromPhysicsSystem { impl<'a> System<'a> for SyncBodiesFromPhysicsSystem { type SystemData = ( - ReadExpect<'a, World>, - Write<'a, EventChannel<ContactEvent>>, + Entities<'a>, + ReadExpect<'a, PhysicsWorld>, + Write<'a, EventChannel<EntityContactEvent>>, + Write<'a, EventChannel<EntityProximityEvent>>, WriteStorage<'a, GlobalTransform>, WriteStorage<'a, DynamicBody>, ReadStorage<'a, Transform>, + ReadStorage<'a, Collider>, ); fn run(&mut self, data: Self::SystemData) { let ( + entities, physical_world, - _contact_events, + mut contact_events, + mut proximity_events, mut global_transforms, mut physics_bodies, local_transforms, + colliders, ) = data; // Apply the updated values of the simulated world to our Components @@ -85,6 +98,25 @@ impl<'a> System<'a> for SyncBodiesFromPhysicsSystem { }; } + let collision_world = physical_world.collision_world(); + + contact_events.iter_write(collision_world.contact_events().iter().cloned().map(|ev| { + let (handle1, handle2) = match ev { + ContactEvent::Started(h1, h2) => (h1, h2), + ContactEvent::Stopped(h1, h2) => (h1, h2), + }; + + let e1 = entity_from_handle(&entities, &colliders, &handle1).expect("Failed to find entity for collider."); + let e2 = entity_from_handle(&entities, &colliders, &handle2).expect("Failed to find entity for collider."); + (e1, e2, ev) + })); + + proximity_events.iter_write(collision_world.proximity_events().iter().cloned().map(|ev| { + let e1 = entity_from_handle(&entities, &colliders, &ev.collider1).expect("Failed to find entity for collider."); + let e2 = entity_from_handle(&entities, &colliders, &ev.collider2).expect("Failed to find entity for collider."); + (e1, e2, ev) + })); + // TODO: reader id from other system? // Now that we changed them all, let's remove all those pesky events that were generated. // global_transforms @@ -97,3 +129,7 @@ impl<'a> System<'a> for SyncBodiesFromPhysicsSystem { // .for_each(|_| ()); } } + +pub fn entity_from_handle(entities: &EntitiesRes, colliders: &ReadStorage<Collider>, handle: &ColliderHandle) -> Option<Entity> { + (&*entities, colliders).join().find(|(_, c)| c.handle.expect("Collider has no handle and wasn't removed.") == *handle).map(|(e, _)| e) +} diff --git a/src/systems/sync_bodies_to_physics.rs b/src/systems/sync_bodies_to_physics.rs @@ -1,5 +1,5 @@ use crate::bodies::DynamicBody; -use crate::World; +use crate::PhysicsWorld; use amethyst::core::GlobalTransform; use amethyst::ecs::storage::{ComponentEvent, MaskedStorage, GenericReadStorage}; use amethyst::ecs::{ @@ -24,7 +24,7 @@ impl SyncBodiesToPhysicsSystem { impl<'a> System<'a> for SyncBodiesToPhysicsSystem { type SystemData = ( - WriteExpect<'a, World>, + WriteExpect<'a, PhysicsWorld>, Entities<'a>, ReadStorage<'a, GlobalTransform>, WriteStorage<'a, DynamicBody>, @@ -141,7 +141,7 @@ fn iterate_events<'a, T, D, S>( reader: &mut ReaderId<ComponentEvent>, inserted: &mut BitSet, modified: &mut BitSet, - world: &mut World, + world: &mut PhysicsWorld, entities: &Entities, bodies: &S ) where diff --git a/src/systems/sync_gravity_to_physics.rs b/src/systems/sync_gravity_to_physics.rs @@ -1,4 +1,4 @@ -use crate::{Gravity, World}; +use crate::{Gravity, PhysicsWorld}; use amethyst::ecs::{ReadExpect, Resources, System, SystemData, WriteExpect}; #[derive(Default)] @@ -11,7 +11,7 @@ impl SyncGravityToPhysicsSystem { } impl<'a> System<'a> for SyncGravityToPhysicsSystem { - type SystemData = (WriteExpect<'a, World>, ReadExpect<'a, Gravity>); + type SystemData = (WriteExpect<'a, PhysicsWorld>, ReadExpect<'a, Gravity>); fn run(&mut self, (mut world, gravity): Self::SystemData) { world.set_gravity(*gravity); @@ -23,6 +23,6 @@ impl<'a> System<'a> for SyncGravityToPhysicsSystem { res.entry::<Gravity>() .or_insert_with(|| Gravity::new(0.0, -9.80665, 0.0)); - res.entry::<World>().or_insert_with(World::new); + res.entry::<PhysicsWorld>().or_insert_with(PhysicsWorld::new); } }