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 750547eaa9bec4e367975fd0d8c84771ac01170a
parent d92444108d0ef4ab2736a46b66c3e349c19eb081
Author: Kel <kel@unclear.info>
Date:   Sun, 23 Jun 2019 10:44:44 -0400

Release 0.3.0 (#6)

- Moves examples from README.md into library docs, now compiling correctly as doctests
- Adds SimplePosition struct for easy Isometry+Position usage
- Integrates Amethyst Transforms for usage with Position trait with feature amethyst_core
- Adds method apply_external_force for applying (cumulative) external forces to PhysicsBodys
- Adds synchronization of PhysicsBodys from physics world, renaming SyncPositionsFromPhysicsSystem to SyncBodiesFromPhysicsSystem to make this clear.
- Re-exports ncollide and nphysics while also cleaning up namespace and use statements.

Diffstat:
MCargo.toml | 14++++++++++----
MREADME.md | 148+------------------------------------------------------------------------------
Mexamples/basic.rs | 42++++++++----------------------------------
Mexamples/collision.rs | 56++++++++++++++------------------------------------------
Mexamples/events.rs | 52++++++++++++----------------------------------------
Mexamples/hierarchy.rs | 50+++++++++++---------------------------------------
Mexamples/positions.rs | 48+++++++++++-------------------------------------
Msrc/bodies.rs | 133++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Msrc/colliders.rs | 29+++++++++++++++--------------
Msrc/events.rs | 3+--
Msrc/lib.rs | 291+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Msrc/parameters.rs | 7+++++--
Msrc/systems/mod.rs | 7++++---
Msrc/systems/physics_stepper.rs | 9+++++----
Asrc/systems/sync_bodies_from_physics.rs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/systems/sync_bodies_to_physics.rs | 87+++++++++++++++++--------------------------------------------------------------
Msrc/systems/sync_colliders_to_physics.rs | 70+++++++++++++++++++++++++---------------------------------------------
Msrc/systems/sync_parameters_to_physics.rs | 20++++++++++++++------
Dsrc/systems/sync_positions_from_physics.rs | 62--------------------------------------------------------------
19 files changed, 608 insertions(+), 584 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "specs-physics" -version = "0.2.2" -authors = ["Benjamin Amling <benjamin@amling.net>"] -repository = "https://github.com/bamling/specs-physics.git" -homepage = "https://github.com/bamling/specs-physics.git" +version = "0.3.0" +authors = ["Benjamin Amling <benjamin@amling.net>", "Kel <x@unclear.info>", "jojolepro <jojolepromain@gmail.com>"] +repository = "https://github.com/amethyst/specs-physics.git" +homepage = "https://github.com/amethyst/specs-physics.git" edition = "2018" license = "MIT" @@ -13,6 +13,11 @@ description = "nphysics integration for the Specs entity component system" keywords = ["specs", "nphysics", "nphysics3d"] +[features] +default = [] + +amethyst = ["amethyst_core"] + [dependencies] log = "0.4.6" specs = "0.14.3" @@ -21,6 +26,7 @@ shrev = "1.1.1" nalgebra = "0.18.0" ncollide3d = "0.19.1" nphysics3d = "0.11.1" +amethyst_core = { version = "0.6.0", optional = true } [dev-dependencies] simple_logger = "1.2.0" diff --git a/README.md b/README.md @@ -17,152 +17,6 @@ The dream is to *simply* create `Entity`s with a set of configurable `Component`s and have most of your physics covered, be it collision/proximity detection, velocity and acceleration or gravity. -## Usage - -To use **specs-physics**, add the following dependency to your projects *Cargo.toml*: - -```toml -[dependencies] -specs-physics = "0.2.2" -``` - -**specs-physics** defines a set of [Specs](https://slide-rs.github.io/specs/) `System`s and `Component`s to handle the creation, modification and removal of [nphysics](https://www.nphysics.org/) objects ([RigidBody](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies), [Collider](https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders)) and the synchronisation of object positions and global gravity between both worlds. - -### Generic types - -All `System`s and `Component`s provided by this crate require between one and two type parameters to function properly. These were explicitly introduced to keep this integration as generic as possible and allow compatibility with as many external crates and game engines as possible. - -`N: RealField` - [nphysics](https://www.nphysics.org/) is built upon [nalgebra](https://nalgebra.org/) and uses various types and structures from this crate. **specs-physics** builds up on this even further and utilises the same structures, which all work with any type that implements `nalgebra::RealField`. `nalgebra::RealField` is by default implemented for various standard types, such as `f32` and`f64`. `nalgebra` is re-exported under `specs_physics::math`. - -`P: Position<N>` - a type parameter which implements the `specs_physics::bodies::Position` *trait*, requiring also a `Component` implementation with a `FlaggedStorage`. This `Position` `Component` is used to initially place a [RigidBody](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies) in the [nphysics](https://www.nphysics.org/) world and later used to synchronise the updated translation and rotation of these bodies back into the [Specs](https://slide-rs.github.io/specs/) world. - -Example for a `Position` `Component`, simply using the "Isometry" type (aka combined translation and rotation structure) directly: -```rust -use specs::{Component, DenseVecStorage, FlaggedStorage}; -use specs_physics::{ - bodies::Position, - math::Isometry3, -}; - -struct Position(Isometry3<f32>); - -impl Component for Position { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for Position { - fn as_isometry(&self) -> Isometry3<f32> { - self.0 - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - self.0 = isometry; - } -} -``` - -### Components - -##### PhysicsBody - -The `specs_physics::PhysicsBody` `Component` is used to define [RigidBody](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies) from the comforts of your [Specs](https://slide-rs.github.io/specs/) world. Changes to the `PhysicsBody` will automatically be synchronised with [nphysics](https://www.nphysics.org/). - -Example: - -```rust -use specs_physics::{ - bodies::BodyStatus, - math::{Matrix3, Point3, Vector3}, - PhysicsBodyBuilder, -}; - -let physics_body = PhysicsBodyBuilder::from(BodyStatus::Dynamic) - .gravity_enabled(true) - .velocity(Vector3::new(1.0, 1.0, 1.0)) - .angular_inertia(Matrix3::from_diagonal_element(3.0)) - .mass(1.3) - .local_center_of_mass(Point3::new(0.0, 0.0, 0.0)) - .build(); -``` - -##### PhysicsCollider - -`specs_physics::PhysicsCollider`s are the counterpart to `PhysicsBody`s. They can exist on their own or as a part of a `PhysicsBody` `PhysicsCollider`s are used to define and create [Colliders](https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders) in [nphysics](https://www.nphysics.org/). - -Example: - -```rust -use specs_physics::{ - colliders::{ - material::{BasicMaterial, MaterialHandle}, - CollisionGroups, - }, - math::Isometry3, - PhysicsColliderBuilder, - Shape, -}; - -let physics_collider = PhysicsColliderBuilder::from(Shape::Rectangle(10.0, 10.0, 1.0)) - .offset_from_parent(Isometry3::identity()) - .density(1.2) - .material(MaterialHandle::new(BasicMaterial::default())) - .margin(0.02) - .collision_groups(CollisionGroups::default()) - .linear_prediction(0.001) - .angular_prediction(0.0) - .sensor(true) - .build(); -``` - -To assign multiple [Colliders](https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders) the the same body, [Entity hierarchy](https://github.com/bamling/specs-physics/blob/master/examples/hierarchy.rs) can be used. This utilises [specs-hierarchy](https://github.com/rustgd/specs-hierarchy). - -### Systems - -The following `System`s currently exist and should be added to your `Dispatcher` in order: - -1. `specs_physics::systems::SyncBodiesToPhysicsSystem` - handles the creation, modification and removal of [RigidBodies](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies) based on the `PhysicsBody` `Component` and an implementation of the `Position` *trait*. -2. `specs_physics::systems::SyncCollidersToPhysicsSystem` - handles the creation, modification and removal of [Colliders](https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders) based on the `PhysicsCollider` `Component`. This `System` depends on `SyncBodiesToPhysicsSystem` as [Colliders](https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders) can depend on [RigidBodies](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies). -3. `specs_physics::systems::SyncGravityToPhysicsSystem` - handles the modification of the [nphysics](https://www.nphysics.org/) `World`s gravity. -4. `specs_physics::systems::PhysicsStepperSystem` - handles the progression of the [nphysics](https://www.nphysics.org/) `World` and causes objects to actually move and change their position. This `System` is the backbone for collision detection. -5. `specs_physics::systems::SyncPositionsFromPhysicsSystem` - handles the synchronisation of [RigidBodies](https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies) positions back into the [Specs](https://slide-rs.github.io/specs/) `Component`s. This `System` also utilises the `Position` *trait* implementation. - -An example `Dispatcher` with all required `System`s: - -```rust -let dispatcher = DispatcherBuilder::new() - .with( - SyncBodiesToPhysicsSystem::<f32, MyPosition>::default(), - "sync_bodies_to_physics_system", - &[], - ) - .with( - SyncCollidersToPhysicsSystem::<f32, MyPosition>::default(), - "sync_colliders_to_physics_system", - &["sync_bodies_to_physics_system"], - ) - .with( - SyncGravityToPhysicsSystem::<f32>::default(), - "sync_gravity_to_physics_system", - &[], - ) - .with( - PhysicsStepperSystem::<f32>::default(), - "physics_stepper_system", - &[ - "sync_bodies_to_physics_system", - "sync_colliders_to_physics_system", - "sync_gravity_to_physics_system", - ], - ) - .with( - SyncPositionsFromPhysicsSystem::<f32, MyPosition>::default(), - "sync_positions_from_physics_system", - &["physics_stepper_system"], - ) - .build(); -``` - -Alternatively to building your own `Dispatcher`, you can always fall back on the convenience function `specs_physics::physics_dispatcher()`, which returns a configured *default* `Dispatcher` for you or `specs_physics::register_physics_systems()` which takes a `DispatcherBuilder` as an argument and registers the required `System`s for you . ### Examples @@ -181,7 +35,7 @@ Full *TODO* sheet can be found in [this nphysics issue][todo] - [x] RigidBody Components - [x] Collider Components - [x] Proximity and Contact EventChannels -- [ ] External force property +- [x] External force property - [x] `log` based logging - [ ] Handling Body Activation & Sleeping - [ ] Multibody-based Component Joints diff --git a/examples/basic.rs b/examples/basic.rs @@ -1,41 +1,17 @@ extern crate log; extern crate simple_logger; -use specs::{world::Builder, Component, DenseVecStorage, FlaggedStorage, World}; +use specs::world::{Builder, World}; use specs_physics::{ - bodies::{BodyStatus, Position}, colliders::Shape, - math::Isometry3, + nalgebra::Isometry3, + nphysics::object::BodyStatus, physics_dispatcher, PhysicsBodyBuilder, PhysicsColliderBuilder, + SimplePosition, }; -/// `SimpleTranslation` struct for synchronisation of the position between the -/// ECS and nphysics; this has to implement both `Component` and `Position` -struct SimpleTranslation { - x: f32, - y: f32, - z: f32, -} - -impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } -} - fn main() { // initialise the logger for system logs simple_logger::init().unwrap(); @@ -45,17 +21,15 @@ fn main() { // create the dispatcher containing all relevant Systems; alternatively to using // the convenience function you can add all required Systems by hand - let mut dispatcher = physics_dispatcher::<f32, SimpleTranslation>(); + let mut dispatcher = physics_dispatcher::<f32, SimplePosition<f32>>(); dispatcher.setup(&mut world.res); // create an Entity containing the required Components world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with(PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic).build()) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(1.0, 1.0, 1.0)).build()) .build(); diff --git a/examples/collision.rs b/examples/collision.rs @@ -2,41 +2,17 @@ extern crate log; extern crate simple_logger; -use specs::{world::Builder, Component, DenseVecStorage, FlaggedStorage, World}; +use specs::world::{Builder, World}; use specs_physics::{ - bodies::{BodyStatus, Position}, colliders::Shape, - math::{Isometry3, Vector3}, + nalgebra::Isometry3, + nphysics::{algebra::Velocity3, object::BodyStatus}, physics_dispatcher, PhysicsBodyBuilder, PhysicsColliderBuilder, + SimplePosition, }; -/// `SimpleTranslation` struct for synchronisation of the position between the -/// ECS and nphysics; this has to implement both `Component` and `Position` -struct SimpleTranslation { - x: f32, - y: f32, - z: f32, -} - -impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } -} - fn main() { // initialise the logger for system logs simple_logger::init().unwrap(); @@ -46,20 +22,18 @@ fn main() { // create the dispatcher containing all relevant Systems; alternatively to using // the convenience function you can add all required Systems by hand - let mut dispatcher = physics_dispatcher::<f32, SimpleTranslation>(); + let mut dispatcher = physics_dispatcher::<f32, SimplePosition<f32>>(); dispatcher.setup(&mut world.res); // create an Entity with a dynamic PhysicsBody component and a velocity let entity = world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with( PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic) - .velocity(Vector3::new(1.0, 0.0, 0.0)) + .velocity(Velocity3::linear(1.0, 0.0, 0.0)) .build(), ) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(2.0, 2.0, 1.0)).build()) @@ -69,11 +43,9 @@ fn main() { // one world .create_entity() - .with(SimpleTranslation { - x: 3.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 3.0, 1.0, 1.0, + ))) .with(PhysicsBodyBuilder::<f32>::from(BodyStatus::Static).build()) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(2.0, 2.0, 1.0)).build()) .build(); @@ -83,8 +55,8 @@ fn main() { // fetch the translation component for the Entity with the dynamic body; the // position should still be approx the same - let pos_storage = world.read_storage::<SimpleTranslation>(); + let pos_storage = world.read_storage::<SimplePosition<f32>>(); let pos = pos_storage.get(entity).unwrap(); - info!("updated position: x={}, y={}, z={},", pos.x, pos.y, pos.z); + info!("updated position: {}", pos.0.translation); } diff --git a/examples/events.rs b/examples/events.rs @@ -2,42 +2,18 @@ extern crate log; extern crate simple_logger; -use specs::{world::Builder, Component, DenseVecStorage, FlaggedStorage, World}; +use specs::world::{Builder, World}; use specs_physics::{ - bodies::{BodyStatus, Position}, colliders::Shape, events::ContactEvents, - math::{Isometry3, Vector3}, + nalgebra::Isometry3, + nphysics::{algebra::Velocity3, object::BodyStatus}, physics_dispatcher, PhysicsBodyBuilder, PhysicsColliderBuilder, + SimplePosition, }; -/// `SimpleTranslation` struct for synchronisation of the position between the -/// ECS and nphysics; this has to implement both `Component` and `Position` -struct SimpleTranslation { - x: f32, - y: f32, - z: f32, -} - -impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } -} - fn main() { // initialise the logger for system logs simple_logger::init().unwrap(); @@ -47,21 +23,19 @@ fn main() { // create the dispatcher containing all relevant Systems; alternatively to using // the convenience function you can add all required Systems by hand - let mut dispatcher = physics_dispatcher::<f32, SimpleTranslation>(); + let mut dispatcher = physics_dispatcher::<f32, SimplePosition<f32>>(); dispatcher.setup(&mut world.res); let mut contact_event_reader = world.res.fetch_mut::<ContactEvents>().register_reader(); // create an Entity with a dynamic PhysicsBody component and a velocity world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with( PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic) - .velocity(Vector3::new(1.0, 0.0, 0.0)) + .velocity(Velocity3::linear(1.0, 0.0, 0.0)) .build(), ) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(2.0, 2.0, 1.0)).build()) @@ -71,11 +45,9 @@ fn main() { // one world .create_entity() - .with(SimpleTranslation { - x: 3.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 3.0, 1.0, 1.0, + ))) .with(PhysicsBodyBuilder::<f32>::from(BodyStatus::Static).build()) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(2.0, 2.0, 1.0)).build()) .build(); diff --git a/examples/hierarchy.rs b/examples/hierarchy.rs @@ -1,42 +1,18 @@ extern crate log; extern crate simple_logger; -use specs::{world::Builder, Component, DenseVecStorage, FlaggedStorage, World}; +use specs::world::{Builder, World}; use specs_physics::{ - bodies::{BodyStatus, Position}, colliders::Shape, - math::Isometry3, + nalgebra::Isometry3, + nphysics::object::BodyStatus, physics_dispatcher, PhysicsBodyBuilder, PhysicsColliderBuilder, PhysicsParent, + SimplePosition, }; -/// `SimpleTranslation` struct for synchronisation of the position between the -/// ECS and nphysics; this has to implement both `Component` and `Position` -struct SimpleTranslation { - x: f32, - y: f32, - z: f32, -} - -impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } -} - fn main() { // initialise the logger for system logs simple_logger::init().unwrap(); @@ -46,18 +22,16 @@ fn main() { // create the dispatcher containing all relevant Systems; alternatively to using // the convenience function you can add all required Systems by hand - let mut dispatcher = physics_dispatcher::<f32, SimpleTranslation>(); + let mut dispatcher = physics_dispatcher::<f32, SimplePosition<f32>>(); dispatcher.setup(&mut world.res); // create an Entity containing the required Components; this Entity will be the // parent let parent = world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with(PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic).build()) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(1.0, 1.0, 1.0)).build()) .build(); @@ -68,11 +42,9 @@ fn main() { // collider will be attached to the parent let _child = world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with( PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(1.0, 1.0, 1.0)) .sensor(true) diff --git a/examples/positions.rs b/examples/positions.rs @@ -2,41 +2,17 @@ extern crate log; extern crate simple_logger; -use specs::{world::Builder, Component, DenseVecStorage, FlaggedStorage, World}; +use specs::world::{Builder, World}; use specs_physics::{ - bodies::{BodyStatus, Position}, colliders::Shape, - math::{Isometry3, Vector3}, + nalgebra::Isometry3, + nphysics::{algebra::Velocity3, object::BodyStatus}, physics_dispatcher, PhysicsBodyBuilder, PhysicsColliderBuilder, + SimplePosition, }; -/// `SimpleTranslation` struct for synchronisation of the position between the -/// ECS and nphysics; this has to implement both `Component` and `Position` -struct SimpleTranslation { - x: f32, - y: f32, - z: f32, -} - -impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; -} - -impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } -} - fn main() { // initialise the logger for system logs simple_logger::init().unwrap(); @@ -46,21 +22,19 @@ fn main() { // create the dispatcher containing all relevant Systems; alternatively to using // the convenience function you can add all required Systems by hand - let mut dispatcher = physics_dispatcher::<f32, SimpleTranslation>(); + let mut dispatcher = physics_dispatcher::<f32, SimplePosition<f32>>(); dispatcher.setup(&mut world.res); // create an Entity containing the required Components; for this examples sake // we'll give the PhysicsBody a velocity let entity = world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with( PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic) - .velocity(Vector3::new(1.0, 0.0, 0.0)) + .velocity(Velocity3::linear(1.0, 0.0, 0.0)) .build(), ) .with(PhysicsColliderBuilder::<f32>::from(Shape::Rectangle(1.0, 1.0, 1.0)).build()) @@ -70,8 +44,8 @@ fn main() { dispatcher.dispatch(&world.res); // fetch the SimpleTranslation component for the created Entity - let pos_storage = world.read_storage::<SimpleTranslation>(); + let pos_storage = world.read_storage::<SimplePosition<f32>>(); let pos = pos_storage.get(entity).unwrap(); - info!("updated position: x={}, y={}, z={},", pos.x, pos.y, pos.z); + info!("updated position: {}", pos.0.translation); } diff --git a/src/bodies.rs b/src/bodies.rs @@ -1,9 +1,43 @@ -use nalgebra::RealField; -use nphysics::object::BodyHandle; -pub use nphysics::object::BodyStatus; use specs::{Component, DenseVecStorage, FlaggedStorage}; -use crate::math::{Isometry3, Matrix3, Point3, Vector3}; +use crate::{ + nalgebra::{Isometry3, Matrix3, Point3, RealField}, + nphysics::{ + algebra::{Force3, ForceType, Velocity3}, + object::{Body, BodyHandle, BodyPart, BodyStatus, RigidBody, RigidBodyDesc}, + }, +}; + +pub mod util { + use specs::{Component, DenseVecStorage, FlaggedStorage}; + + use crate::{ + bodies::Position, + nalgebra::{Isometry3, RealField}, + }; + + pub struct SimplePosition<N: RealField>(pub Isometry3<N>); + + impl<N: RealField> Position<N> for SimplePosition<N> { + fn isometry(&self) -> &Isometry3<N> { + &self.0 + } + + fn isometry_mut(&mut self) -> &mut Isometry3<N> { + &mut self.0 + } + + fn set_isometry(&mut self, isometry: &Isometry3<N>) -> &mut Self { + self.0.rotation = isometry.rotation; + self.0.translation = isometry.translation; + self + } + } + + impl<N: RealField> Component for SimplePosition<N> { + type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; + } +} /// An implementation of the `Position` trait is required for the /// synchronisation of the position of Specs and nphysics objects. @@ -14,8 +48,24 @@ use crate::math::{Isometry3, Matrix3, Point3, Vector3}; pub trait Position<N: RealField>: Component<Storage = FlaggedStorage<Self, DenseVecStorage<Self>>> + Send + Sync { - fn as_isometry(&self) -> Isometry3<N>; - fn set_isometry(&mut self, isometry: &Isometry3<N>); + fn isometry(&self) -> &Isometry3<N>; + fn isometry_mut(&mut self) -> &mut Isometry3<N>; + fn set_isometry(&mut self, isometry: &Isometry3<N>) -> &mut Self; +} + +#[cfg(feature = "amethyst")] +impl Position<amethyst_core::Float> for amethyst_core::Transform { + fn isometry(&self) -> &Isometry3<amethyst_core::Float> { + self.isometry() + } + + fn isometry_mut(&mut self) -> &mut Isometry3<amethyst_core::Float> { + self.isometry_mut() + } + + fn set_isometry(&mut self, isometry: &Isometry3<amethyst_core::Float>) -> &mut Self { + self.set_isometry(*isometry) + } } /// The `PhysicsBody` `Component` represents a `PhysicsWorld` `RigidBody` in @@ -26,16 +76,70 @@ pub struct PhysicsBody<N: RealField> { pub(crate) handle: Option<BodyHandle>, pub gravity_enabled: bool, pub body_status: BodyStatus, - pub velocity: Vector3<N>, + pub velocity: Velocity3<N>, pub angular_inertia: Matrix3<N>, pub mass: N, pub local_center_of_mass: Point3<N>, + external_forces: Force3<N>, } impl<N: RealField> Component for PhysicsBody<N> { type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; } +impl<N: RealField> PhysicsBody<N> { + pub fn check_external_force(&self) -> &Force3<N> { + &self.external_forces + } + + pub fn apply_external_force(&mut self, force: &Force3<N>) -> &mut Self { + self.external_forces += *force; + self + } + + /// For creating new rigid body from this component's values + pub(crate) fn to_rigid_body_desc(&self) -> RigidBodyDesc<N> { + RigidBodyDesc::new() + .gravity_enabled(self.gravity_enabled) + .status(self.body_status) + .velocity(self.velocity) + .angular_inertia(self.angular_inertia) + .mass(self.mass) + .local_center_of_mass(self.local_center_of_mass) + } + + /// Note: applies forces by draining external force property + pub(crate) fn apply_to_physics_world(&mut self, rigid_body: &mut RigidBody<N>) -> &mut Self { + rigid_body.enable_gravity(self.gravity_enabled); + rigid_body.set_status(self.body_status); + rigid_body.set_velocity(self.velocity); + rigid_body.set_angular_inertia(self.angular_inertia); + rigid_body.set_mass(self.mass); + rigid_body.set_local_center_of_mass(self.local_center_of_mass); + rigid_body.apply_force(0, &self.drain_external_force(), ForceType::Force, true); + self + } + + pub(crate) fn update_from_physics_world(&mut self, rigid_body: &RigidBody<N>) -> &mut Self { + // These two probably won't be modified but hey + self.gravity_enabled = rigid_body.gravity_enabled(); + self.body_status = rigid_body.status(); + + self.velocity = *rigid_body.velocity(); + + let local_inertia = rigid_body.local_inertia(); + self.angular_inertia = local_inertia.angular; + self.mass = local_inertia.linear; + self + } + + fn drain_external_force(&mut self) -> Force3<N> { + let value = self.external_forces; + self.external_forces = Force3::<N>::zero(); + value + } +} + /// The `PhysicsBodyBuilder` implements the builder pattern for `PhysicsBody`s /// and is the recommended way of instantiating and customising new /// `PhysicsBody` instances. @@ -44,14 +148,14 @@ impl<N: RealField> Component for PhysicsBody<N> { /// /// ```rust /// use specs_physics::{ -/// bodies::BodyStatus, -/// math::{Matrix3, Point3, Vector3}, +/// nalgebra::{Matrix3, Point3}, +/// nphysics::{algebra::Velocity3, object::BodyStatus}, /// PhysicsBodyBuilder, /// }; /// /// let physics_body = PhysicsBodyBuilder::from(BodyStatus::Dynamic) /// .gravity_enabled(true) -/// .velocity(Vector3::new(1.0, 1.0, 1.0)) +/// .velocity(Velocity3::linear(1.0, 1.0, 1.0)) /// .angular_inertia(Matrix3::from_diagonal_element(3.0)) /// .mass(1.3) /// .local_center_of_mass(Point3::new(0.0, 0.0, 0.0)) @@ -60,7 +164,7 @@ impl<N: RealField> Component for PhysicsBody<N> { pub struct PhysicsBodyBuilder<N: RealField> { gravity_enabled: bool, body_status: BodyStatus, - velocity: Vector3<N>, + velocity: Velocity3<N>, angular_inertia: Matrix3<N>, mass: N, local_center_of_mass: Point3<N>, @@ -73,10 +177,10 @@ impl<N: RealField> From<BodyStatus> for PhysicsBodyBuilder<N> { Self { gravity_enabled: false, body_status, - velocity: Vector3::repeat(N::zero()), + velocity: Velocity3::zero(), angular_inertia: Matrix3::zeros(), mass: N::from_f32(1.2).unwrap(), - local_center_of_mass: Point3::new(N::zero(), N::zero(), N::zero()), + local_center_of_mass: Point3::origin(), } } } @@ -89,7 +193,7 @@ impl<N: RealField> PhysicsBodyBuilder<N> { } // Sets the `velocity` value of the `PhysicsBodyBuilder`. - pub fn velocity(mut self, velocity: Vector3<N>) -> Self { + pub fn velocity(mut self, velocity: Velocity3<N>) -> Self { self.velocity = velocity; self } @@ -123,6 +227,7 @@ impl<N: RealField> PhysicsBodyBuilder<N> { angular_inertia: self.angular_inertia, mass: self.mass, local_center_of_mass: self.local_center_of_mass, + external_forces: Force3::zero(), } } } diff --git a/src/colliders.rs b/src/colliders.rs @@ -1,15 +1,18 @@ use std::{f32::consts::PI, fmt}; -use nalgebra::RealField; -use ncollide::shape::{Ball, Cuboid, ShapeHandle}; -pub use ncollide::world::CollisionGroups; -pub use nphysics::material; -use nphysics::object::ColliderHandle; use specs::{Component, DenseVecStorage, FlaggedStorage}; -use crate::math::{Isometry3, Vector3}; - -use self::material::{BasicMaterial, MaterialHandle}; +use crate::{ + nalgebra::{Isometry3, RealField, Vector3}, + ncollide::{ + shape::{Ball, Cuboid, ShapeHandle}, + world::CollisionGroups, + }, + nphysics::{ + material::{BasicMaterial, MaterialHandle}, + object::ColliderHandle, + }, +}; /// `Shape` serves as an abstraction over nphysics `ShapeHandle`s and makes it /// easier to configure and define said `ShapeHandle`s for the user without @@ -101,12 +104,10 @@ impl<N: RealField> PhysicsCollider<N> { /// /// ```rust /// use specs_physics::{ -/// colliders::{ -/// material::{BasicMaterial, MaterialHandle}, -/// CollisionGroups, -/// Shape, -/// }, -/// math::Isometry3, +/// colliders::Shape, +/// nalgebra::Isometry3, +/// ncollide::world::CollisionGroups, +/// nphysics::material::{BasicMaterial, MaterialHandle}, /// PhysicsColliderBuilder, /// }; /// diff --git a/src/events.rs b/src/events.rs @@ -1,7 +1,6 @@ -use shrev::EventChannel; use specs::Entity; -pub use ncollide::query::Proximity; +use crate::{ncollide::query::Proximity, shrev::EventChannel}; /// The `ContactType` is set accordingly to whether a contact began or ended. #[derive(Debug)] diff --git a/src/lib.rs b/src/lib.rs @@ -1,37 +1,282 @@ +//! ## Usage +//! +//! To use **specs-physics**, add the following dependency to your projects +//! *Cargo.toml*: +//! +//! ```toml +//! [dependencies] +//! specs-physics = "0.3.0" +//! ``` +//! +//! **specs-physics** defines a set of [Specs][] `System`s and `Component`s to +//! handle the creation, modification and removal of [nphysics][] objects +//! ([RigidBody][], [Collider][]) and the synchronisation of object positions +//! and global gravity between both worlds. +//! +//! ### Generic types +//! +//! All `System`s and `Component`s provided by this crate require between one +//! and two type parameters to function properly. These were explicitly +//! introduced to keep this integration as generic as possible and allow +//! compatibility with as many external crates and game engines as possible. +//! +//! #### `N: RealField` +//! +//! [nphysics][] is built upon [nalgebra][] and uses various types and +//! structures from this crate. **specs-physics** builds up on this even further +//! and utilises the same structures, which all work with any type that +//! implements `nalgebra::RealField`. `nalgebra::RealField` is by default +//! implemented for various standard types, such as `f32` and`f64`. `nalgebra` +//! is re-exported under `specs_physics::nalgebra`. +//! +//! #### `P: Position<N>` +//! +//! a type parameter which implements the `specs_physics::bodies::Position` +//! *trait*, requiring also a `Component` implementation with a +//! `FlaggedStorage`. This `Position` `Component` is used to initially place a +//! [RigidBody][] in the [nphysics][] world and later used to synchronise the +//! updated translation and rotation of these bodies back into the [Specs][] +//! world. +//! +//! Example for a `Position` `Component`, simply using the "Isometry" type (aka +//! combined translation and rotation structure) directly: +//! +//! ```rust +//! use specs::{Component, DenseVecStorage, FlaggedStorage}; +//! use specs_physics::{bodies::Position, nalgebra::Isometry3}; +//! +//! struct Pos(pub Isometry3<f32>); +//! +//! impl Component for Pos { +//! type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; +//! } +//! +//! impl Position<f32> for Pos { +//! fn isometry(&self) -> &Isometry3<f32> { +//! &self.0 +//! } +//! +//! fn isometry_mut(&mut self) -> &mut Isometry3<f32> { +//! &mut self.0 +//! } +//! +//! fn set_isometry(&mut self, isometry: &Isometry3<f32>) -> &mut Pos { +//! self.0 = *isometry; +//! self +//! } +//! } +//! ``` +//! +//! If you're using [Amethyst][], you can enable the "amethyst" feature for this +//! crate which provides a `Position<Float>` impl for `Transform`. +//! +//! ```toml +//! [dependencies] +//! specs-physics = { version = "0.3", features = ["amethyst"] } +//! ``` +//! +//! ### Components +//! +//! ##### PhysicsBody +//! +//! The `specs_physics::PhysicsBody` `Component` is used to define [RigidBody][] +//! from the comforts of your [Specs][] world. Changes to the `PhysicsBody` will +//! automatically be synchronised with [nphysics][]. +//! +//! Example: +//! +//! ```rust +//! use specs_physics::{ +//! nalgebra::{Matrix3, Point3}, +//! nphysics::{algebra::Velocity3, object::BodyStatus}, +//! PhysicsBodyBuilder, +//! }; +//! +//! let physics_body = PhysicsBodyBuilder::from(BodyStatus::Dynamic) +//! .gravity_enabled(true) +//! .velocity(Velocity3::linear(1.0, 1.0, 1.0)) +//! .angular_inertia(Matrix3::from_diagonal_element(3.0)) +//! .mass(1.3) +//! .local_center_of_mass(Point3::new(0.0, 0.0, 0.0)) +//! .build(); +//! ``` +//! +//! ##### PhysicsCollider +//! +//! `specs_physics::PhysicsCollider`s are the counterpart to `PhysicsBody`s. +//! They can exist on their own or as a part of a `PhysicsBody` +//! `PhysicsCollider`s are used to define and create [Collider][]'s in +//! [nphysics][]. +//! +//! Example: +//! +//! ```rust +//! use specs_physics::{ +//! colliders::Shape, +//! nalgebra::Isometry3, +//! ncollide::world::CollisionGroups, +//! nphysics::material::{BasicMaterial, MaterialHandle}, +//! PhysicsColliderBuilder, +//! }; +//! +//! let physics_collider = PhysicsColliderBuilder::from(Shape::Rectangle(10.0, 10.0, 1.0)) +//! .offset_from_parent(Isometry3::identity()) +//! .density(1.2) +//! .material(MaterialHandle::new(BasicMaterial::default())) +//! .margin(0.02) +//! .collision_groups(CollisionGroups::default()) +//! .linear_prediction(0.001) +//! .angular_prediction(0.0) +//! .sensor(true) +//! .build(); +//! ``` +//! +//! To assign multiple [Collider][]'s the the same body, [Entity hierarchy][] +//! can be used. This utilises [specs-hierarchy][]. +//! +//! ### Systems +//! +//! The following `System`s currently exist and should be added to your +//! `Dispatcher` in order: +//! +//! 1. `specs_physics::systems::SyncBodiesToPhysicsSystem` - handles the +//! creation, modification and removal of [RigidBody][]'s based on the +//! `PhysicsBody` `Component` and an implementation of the `Position` +//! *trait*. +//! +//! 2. `specs_physics::systems::SyncCollidersToPhysicsSystem` - handles +//! the creation, modification and removal of [Collider][]'s based on the +//! `PhysicsCollider` `Component`. This `System` depends on +//! `SyncBodiesToPhysicsSystem` as [Collider][] can depend on [RigidBody][]. +//! +//! 3. `specs_physics::systems::SyncParametersToPhysicsSystem` - handles the +//! modification of the [nphysics][] `World`s parameters. +//! +//! 4. `specs_physics::systems::PhysicsStepperSystem` - handles the progression +//! of the [nphysics][] `World` and causes objects to actually move and +//! change their position. This `System` is the backbone for collision +//! detection. +//! +//! 5. `specs_physics::systems::SyncBodiesFromPhysicsSystem` - +//! handles the synchronisation of [RigidBody][] positions and dynamics back +//! into the [Specs][] `Component`s. This `System` also utilises the +//! `Position` *trait* implementation. +//! +//! An example `Dispatcher` with all required `System`s: +//! +//! ```rust +//! use specs::DispatcherBuilder; +//! use specs_physics::{ +//! systems::{ +//! PhysicsStepperSystem, +//! SyncBodiesFromPhysicsSystem, +//! SyncBodiesToPhysicsSystem, +//! SyncCollidersToPhysicsSystem, +//! SyncParametersToPhysicsSystem, +//! }, +//! SimplePosition, +//! }; +//! +//! let dispatcher = DispatcherBuilder::new() +//! .with( +//! SyncBodiesToPhysicsSystem::<f32, SimplePosition<f32>>::default(), +//! "sync_bodies_to_physics_system", +//! &[], +//! ) +//! .with( +//! SyncCollidersToPhysicsSystem::<f32, SimplePosition<f32>>::default(), +//! "sync_colliders_to_physics_system", +//! &["sync_bodies_to_physics_system"], +//! ) +//! .with( +//! SyncParametersToPhysicsSystem::<f32>::default(), +//! "sync_gravity_to_physics_system", +//! &[], +//! ) +//! .with( +//! PhysicsStepperSystem::<f32>::default(), +//! "physics_stepper_system", +//! &[ +//! "sync_bodies_to_physics_system", +//! "sync_colliders_to_physics_system", +//! "sync_gravity_to_physics_system", +//! ], +//! ) +//! .with( +//! SyncBodiesFromPhysicsSystem::<f32, SimplePosition<f32>>::default(), +//! "sync_bodies_from_physics_system", +//! &["physics_stepper_system"], +//! ) +//! .build(); +//! ``` +//! +//! If you're using [Amethyst][] Transforms directly, you'd pass the generic +//! arguments like so: +//! +//! ``` +//! use amethyst_core::{Float, Transform}; +//! use specs_physics::systems::SyncBodiesToPhysicsSystem; +//! SyncBodiesToPhysicsSystem::<Float, Transform>::default(); +//! ``` +//! +//! Alternatively to building your own `Dispatcher`, you can always fall back on +//! the convenience function `specs_physics::physics_dispatcher()`, which +//! returns a configured *default* `Dispatcher` for you or +//! `specs_physics::register_physics_systems()` which takes a +//! `DispatcherBuilder` as an argument and registers the required `System`s for +//! you. +//! +//! [Specs]: https://slide-rs.github.io/specs/ +//! [nphysics]: https://www.nphysics.org/ +//! [nalgebra]: https://nalgebra.org/ +//! [RigidBody]: https://www.nphysics.org/rigid_body_simulations_with_contacts/#rigid-bodies +//! [Collider]: https://www.nphysics.org/rigid_body_simulations_with_contacts/#colliders +//! [Amethyst]: https://amethyst.rs/ +//! [Entity hierarchy]: https://github.com/bamling/specs-physics/blob/master/examples/hierarchy.rs +//! [specs-hierarchy]: https://github.com/rustgd/specs-hierarchy + #[macro_use] extern crate log; -extern crate ncollide3d as ncollide; -extern crate nphysics3d as nphysics; + +pub use nalgebra; +pub use ncollide3d as ncollide; +pub use nphysics3d as nphysics; +pub use shrev; use std::collections::HashMap; -pub use nalgebra as math; -use nphysics::{ - counters::Counters, - material::MaterialsCoefficientsTable, - object::{BodyHandle, ColliderHandle}, - solver::IntegrationParameters, - world::World, +use specs::{ + world::Index, + Component, + DenseVecStorage, + Dispatcher, + DispatcherBuilder, + Entity, + FlaggedStorage, }; -pub use shrev; -use specs::world::Index; +use specs_hierarchy::Parent; pub use self::{ - bodies::{PhysicsBody, PhysicsBodyBuilder}, + bodies::{util::SimplePosition, PhysicsBody, PhysicsBodyBuilder}, colliders::{PhysicsCollider, PhysicsColliderBuilder}, }; -use specs::{Component, DenseVecStorage, Dispatcher, DispatcherBuilder, Entity, FlaggedStorage}; -use specs_hierarchy::Parent; use self::{ bodies::Position, - math::{RealField, Vector3}, + nalgebra::{RealField, Vector3}, + nphysics::{ + counters::Counters, + material::MaterialsCoefficientsTable, + object::{BodyHandle, ColliderHandle}, + solver::IntegrationParameters, + world::World, + }, systems::{ PhysicsStepperSystem, + SyncBodiesFromPhysicsSystem, SyncBodiesToPhysicsSystem, SyncCollidersToPhysicsSystem, SyncParametersToPhysicsSystem, - SyncPositionsFromPhysicsSystem, }, }; @@ -130,6 +375,12 @@ impl Parent for PhysicsParent { /// Convenience function for configuring and building a `Dispatcher` with all /// required physics related `System`s. +/// +/// # Examples +/// ``` +/// use specs_physics::bodies::util::SimplePosition; +/// let dispatcher = specs_physics::physics_dispatcher::<f32, SimplePosition<f32>>(); +/// ``` pub fn physics_dispatcher<'a, 'b, N, P>() -> Dispatcher<'a, 'b> where N: RealField, @@ -183,16 +434,16 @@ where &[ "sync_bodies_to_physics_system", "sync_colliders_to_physics_system", - "sync_gravity_to_physics_system", + "sync_parameters_to_physics_system", ], ); - // add SyncPositionsFromPhysicsSystem last as it handles the + // add SyncBodiesFromPhysicsSystem last as it handles the // synchronisation between nphysics World bodies and the Position // components; this depends on the PhysicsStepperSystem dispatcher_builder.add( - SyncPositionsFromPhysicsSystem::<N, P>::default(), - "sync_positions_from_physics_system", + SyncBodiesFromPhysicsSystem::<N, P>::default(), + "sync_bodies_from_physics_system", &["physics_stepper_system"], ); } diff --git a/src/parameters.rs b/src/parameters.rs @@ -2,10 +2,13 @@ //! Resources for modifying the various simulation parameters of the //! nphysics World. -use crate::math::{self as na, RealField, Scalar, Vector3}; -use nphysics::solver::IntegrationParameters; use std::ops::{Deref, DerefMut}; +use crate::{ + nalgebra::{self as na, RealField, Scalar, Vector3}, + nphysics::solver::IntegrationParameters, +}; + /// The `TimeStep` is used to set the timestep of the nphysics integration, see /// `nphysics::world::World::set_timestep(..)`. /// diff --git a/src/systems/mod.rs b/src/systems/mod.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use specs::{ storage::{ComponentEvent, MaskedStorage}, BitSet, @@ -6,21 +8,20 @@ use specs::{ Storage, Tracked, }; -use std::ops::Deref; pub use self::{ physics_stepper::PhysicsStepperSystem, + sync_bodies_from_physics::SyncBodiesFromPhysicsSystem, sync_bodies_to_physics::SyncBodiesToPhysicsSystem, sync_colliders_to_physics::SyncCollidersToPhysicsSystem, sync_parameters_to_physics::SyncParametersToPhysicsSystem, - sync_positions_from_physics::SyncPositionsFromPhysicsSystem, }; mod physics_stepper; +mod sync_bodies_from_physics; mod sync_bodies_to_physics; mod sync_colliders_to_physics; mod sync_parameters_to_physics; -mod sync_positions_from_physics; /// Iterated over the `ComponentEvent::Inserted`s of a given, tracked `Storage` /// and returns the results in a `BitSet`. diff --git a/src/systems/physics_stepper.rs b/src/systems/physics_stepper.rs @@ -1,6 +1,5 @@ -use nalgebra::RealField; -use ncollide::{events::ContactEvent as NContactEvent, world::CollisionObjectHandle}; -use nphysics::world::ColliderWorld; +use std::marker::PhantomData; + use specs::{ world::Index, Entities, @@ -12,10 +11,12 @@ use specs::{ Write, WriteExpect, }; -use std::marker::PhantomData; use crate::{ events::{ContactEvent, ContactEvents, ContactType, ProximityEvent, ProximityEvents}, + nalgebra::RealField, + ncollide::{events::ContactEvent as NContactEvent, world::CollisionObjectHandle}, + nphysics::world::ColliderWorld, parameters::TimeStep, Physics, }; diff --git a/src/systems/sync_bodies_from_physics.rs b/src/systems/sync_bodies_from_physics.rs @@ -0,0 +1,64 @@ +use std::marker::PhantomData; + +use specs::{Join, ReadExpect, Resources, System, SystemData, WriteStorage}; + +use crate::{ + bodies::{PhysicsBody, Position}, + nalgebra::RealField, + Physics, +}; + +/// The `SyncBodiesFromPhysicsSystem` synchronised the updated position of +/// the `RigidBody`s in the nphysics `World` with their Specs counterparts. This +/// affects the `Position` `Component` related to the `Entity`. +pub struct SyncBodiesFromPhysicsSystem<N, P> { + n_marker: PhantomData<N>, + p_marker: PhantomData<P>, +} + +impl<'s, N, P> System<'s> for SyncBodiesFromPhysicsSystem<N, P> +where + N: RealField, + P: Position<N>, +{ + type SystemData = ( + ReadExpect<'s, Physics<N>>, + WriteStorage<'s, PhysicsBody<N>>, + WriteStorage<'s, P>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (physics, mut physics_bodies, mut positions) = data; + + // iterate over all PhysicBody components joined with their Positions + for (physics_body, position) in (&mut physics_bodies, &mut positions).join() { + // if a RigidBody exists in the nphysics World we fetch it and update the + // Position component accordingly + if let Some(rigid_body) = physics.world.rigid_body(physics_body.handle.unwrap()) { + position.set_isometry(rigid_body.position()); + physics_body.update_from_physics_world(rigid_body); + } + } + } + + fn setup(&mut self, res: &mut Resources) { + info!("SyncBodiesFromPhysicsSystem.setup"); + Self::SystemData::setup(res); + + // initialise required resources + res.entry::<Physics<N>>().or_insert_with(Physics::default); + } +} + +impl<N, P> Default for SyncBodiesFromPhysicsSystem<N, P> +where + N: RealField, + P: Position<N>, +{ + fn default() -> Self { + Self { + n_marker: PhantomData, + p_marker: PhantomData, + } + } +} diff --git a/src/systems/sync_bodies_to_physics.rs b/src/systems/sync_bodies_to_physics.rs @@ -1,10 +1,5 @@ use std::marker::PhantomData; -use nalgebra::RealField; -use nphysics::{ - math::Velocity, - object::{Body, RigidBodyDesc}, -}; use specs::{ storage::ComponentEvent, world::Index, @@ -21,6 +16,7 @@ use specs::{ use crate::{ bodies::{PhysicsBody, Position}, + nalgebra::RealField, Physics, }; @@ -151,22 +147,11 @@ fn add_rigid_body<N, P>( physics.world.remove_bodies(&[body_handle]); } - let delta_time = physics.world.timestep(); - // create a new RigidBody in the PhysicsWorld and store its // handle for later usage - let handle = RigidBodyDesc::new() - .position(position.as_isometry()) - .gravity_enabled(physics_body.gravity_enabled) - .status(physics_body.body_status) - .velocity(Velocity::linear( - physics_body.velocity.x / delta_time, - physics_body.velocity.y / delta_time, - physics_body.velocity.z / delta_time, - )) - .angular_inertia(physics_body.angular_inertia) - .mass(physics_body.mass) - .local_center_of_mass(physics_body.local_center_of_mass) + let handle = physics_body + .to_rigid_body_desc() + .position(*position.isometry()) .user_data(id) .build(&mut physics.world) .handle(); @@ -191,26 +176,15 @@ fn update_rigid_body<N, P>( N: RealField, P: Position<N>, { - let delta_time = physics.world.timestep(); - if let Some(rigid_body) = physics.world.rigid_body_mut(physics_body.handle.unwrap()) { // the PhysicsBody was modified, update everything but the position if modified_physics_bodies.contains(id) { - rigid_body.enable_gravity(physics_body.gravity_enabled); - rigid_body.set_status(physics_body.body_status); - rigid_body.set_velocity(Velocity::linear( - physics_body.velocity.x / delta_time, - physics_body.velocity.y / delta_time, - physics_body.velocity.z / delta_time, - )); - rigid_body.set_angular_inertia(physics_body.angular_inertia); - rigid_body.set_mass(physics_body.mass); - rigid_body.set_local_center_of_mass(physics_body.local_center_of_mass); + physics_body.apply_to_physics_world(rigid_body); } // the Position was modified, update the position directly if modified_positions.contains(id) { - rigid_body.set_position(position.as_isometry()); + rigid_body.set_position(*position.isometry()); } trace!( @@ -234,46 +208,23 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::{bodies::BodyStatus, math::Isometry3, PhysicsBodyBuilder}; - use specs::{ - world::Builder, - Component, - DenseVecStorage, - DispatcherBuilder, - FlaggedStorage, - World, + use crate::{ + nalgebra::Isometry3, + nphysics::object::BodyStatus, + systems::SyncBodiesToPhysicsSystem, + Physics, + PhysicsBodyBuilder, + SimplePosition, }; - struct SimpleTranslation { - x: f32, - y: f32, - z: f32, - } - - impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; - } - - impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } - } + use specs::{world::Builder, DispatcherBuilder, World}; #[test] fn add_rigid_body() { let mut world = World::new(); let mut dispatcher = DispatcherBuilder::new() .with( - SyncBodiesToPhysicsSystem::<f32, SimpleTranslation>::default(), + SyncBodiesToPhysicsSystem::<f32, SimplePosition<f32>>::default(), "sync_bodies_to_physics_system", &[], ) @@ -283,11 +234,9 @@ mod tests { // create an Entity with the PhysicsBody component and execute the dispatcher world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .with(SimplePosition::<f32>(Isometry3::<f32>::translation( + 1.0, 1.0, 1.0, + ))) .with(PhysicsBodyBuilder::<f32>::from(BodyStatus::Dynamic).build()) .build(); dispatcher.dispatch(&mut world.res); diff --git a/src/systems/sync_colliders_to_physics.rs b/src/systems/sync_colliders_to_physics.rs @@ -1,4 +1,5 @@ -use nalgebra::RealField; +use std::marker::PhantomData; + use specs::{ storage::ComponentEvent, world::Index, @@ -11,11 +12,15 @@ use specs::{ WriteExpect, WriteStorage, }; -use std::marker::PhantomData; - -use nphysics::object::{BodyPartHandle, ColliderDesc}; -use crate::{bodies::Position, colliders::PhysicsCollider, Physics, PhysicsParent}; +use crate::{ + bodies::Position, + colliders::PhysicsCollider, + nalgebra::RealField, + nphysics::object::{BodyPartHandle, ColliderDesc}, + Physics, + PhysicsParent, +}; use super::iterate_component_events; @@ -177,11 +182,11 @@ fn add_collider<N, P>( // Position into consideration let translation = if parent_part_handle.is_ground() { // let scale = 1.0; may be added later - let mut iso = position.as_isometry(); + let iso = &mut position.isometry().clone(); iso.translation.vector += iso.rotation * physics_collider.offset_from_parent.translation.vector; //.component_mul(scale); iso.rotation *= physics_collider.offset_from_parent.rotation; - iso + *iso } else { physics_collider.offset_from_parent }; @@ -249,46 +254,23 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::{colliders::Shape, math::Isometry3, PhysicsColliderBuilder}; - use specs::{ - world::Builder, - Component, - DenseVecStorage, - DispatcherBuilder, - FlaggedStorage, - World, + use specs::{world::Builder, DispatcherBuilder, World}; + + use crate::{ + colliders::Shape, + nalgebra::Isometry3, + systems::SyncCollidersToPhysicsSystem, + Physics, + PhysicsColliderBuilder, + SimplePosition, }; - struct SimpleTranslation { - x: f32, - y: f32, - z: f32, - } - - impl Component for SimpleTranslation { - type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; - } - - impl Position<f32> for SimpleTranslation { - fn as_isometry(&self) -> Isometry3<f32> { - Isometry3::translation(self.x, self.y, self.z) - } - - fn set_isometry(&mut self, isometry: &Isometry3<f32>) { - let translation = isometry.translation.vector; - self.x = translation.x; - self.y = translation.y; - self.z = translation.z; - } - } - #[test] fn add_collider() { let mut world = World::new(); let mut dispatcher = DispatcherBuilder::new() .with( - SyncCollidersToPhysicsSystem::<f32, SimpleTranslation>::default(), + SyncCollidersToPhysicsSystem::<f32, SimplePosition<f32>>::default(), "sync_colliders_to_physics_system", &[], ) @@ -299,11 +281,9 @@ mod tests { // dispatcher world .create_entity() - .with(SimpleTranslation { - x: 1.0, - y: 1.0, - z: 1.0, - }) + .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); diff --git a/src/systems/sync_parameters_to_physics.rs b/src/systems/sync_parameters_to_physics.rs @@ -1,8 +1,12 @@ -use nalgebra::RealField; +use std::marker::PhantomData; + use specs::{Read, Resources, System, SystemData, WriteExpect}; -use crate::{parameters::*, Physics}; -use std::marker::PhantomData; +use crate::{ + nalgebra::RealField, + parameters::{Gravity, PhysicsIntegrationParameters, PhysicsProfilingEnabled}, + Physics, +}; /// The `SyncParametersToPhysicsSystem` synchronises the simulation parameters /// with the nphysics `World`. @@ -75,11 +79,15 @@ where #[cfg(test)] mod tests { - use super::*; - use crate::Physics; - use nalgebra::Vector3; use specs::{DispatcherBuilder, World}; + use crate::{ + nalgebra::Vector3, + parameters::Gravity, + systems::SyncParametersToPhysicsSystem, + Physics, + }; + #[test] fn update_gravity() { let mut world = World::new(); diff --git a/src/systems/sync_positions_from_physics.rs b/src/systems/sync_positions_from_physics.rs @@ -1,62 +0,0 @@ -use nalgebra::RealField; -use specs::{Join, ReadExpect, ReadStorage, Resources, System, SystemData, WriteStorage}; -use std::marker::PhantomData; - -use crate::{ - bodies::{PhysicsBody, Position}, - Physics, -}; - -/// The `SyncPositionsFromPhysicsSystem` synchronised the updated position of -/// the `RigidBody`s in the nphysics `World` with their Specs counterparts. This -/// affects the `Position` `Component` related to the `Entity`. -pub struct SyncPositionsFromPhysicsSystem<N, P> { - n_marker: PhantomData<N>, - p_marker: PhantomData<P>, -} - -impl<'s, N, P> System<'s> for SyncPositionsFromPhysicsSystem<N, P> -where - N: RealField, - P: Position<N>, -{ - type SystemData = ( - ReadExpect<'s, Physics<N>>, - ReadStorage<'s, PhysicsBody<N>>, - WriteStorage<'s, P>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (physics, physics_bodies, mut positions) = data; - - // iterate over all PhysicBody components joined with their Positions - for (physics_body, position) in (&physics_bodies, &mut positions).join() { - // if a RigidBody exists in the nphysics World we fetch it and update the - // Position component accordingly - if let Some(rigid_body) = physics.world.rigid_body(physics_body.handle.unwrap()) { - position.set_isometry(rigid_body.position()); - } - } - } - - fn setup(&mut self, res: &mut Resources) { - info!("SyncPositionsFromPhysicsSystem.setup"); - Self::SystemData::setup(res); - - // initialise required resources - res.entry::<Physics<N>>().or_insert_with(Physics::default); - } -} - -impl<N, P> Default for SyncPositionsFromPhysicsSystem<N, P> -where - N: RealField, - P: Position<N>, -{ - fn default() -> Self { - Self { - n_marker: PhantomData, - p_marker: PhantomData, - } - } -}