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 057cb9aa6f714d8750fcd6690282b411e4d2b1b9
parent 12b96dbb72c6f873ad2086230469fc405836a03f
Author: bamlin <b.amling@tarent.de>
Date:   Thu, 30 May 2019 17:10:08 +0200

added core components and updated README.md

Diffstat:
MCargo.toml | 10++++++++++
MREADME.md | 33++++++++++++++++++++++++++++++++-
Arustfmt.toml | 8++++++++
Asrc/body.rs | 115+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/collider.rs | 219+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib.rs | 24++++++++++++++++++++++++
Csrc/lib.rs -> src/systems/mod.rs | 0
7 files changed, 408 insertions(+), 1 deletion(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -4,6 +4,15 @@ version = "0.0.1" authors = ["Benjamin Amling <benjamin@amling.net>"] repository = "https://github.com/bamling/specs-physics.git" homepage = "https://github.com/bamling/specs-physics.git" +edition = "2018" [dependencies] +log = "0.4.6" +specs = "0.14.3" +specs-hierarchy = "0.3.1" +nalgebra = "0.18.0" +ncollide3d = "0.19.1" +nphysics3d = "0.11.1" +[dev-dependencies] +simple_logger = "1.2.0"+ \ No newline at end of file diff --git a/README.md b/README.md @@ -1,2 +1,32 @@ # specs-physics -nphysics integration for the Specs entity component system + +**specs-physics** aims to be an easily usable and extendable [nphysics](https://www.nphysics.org/) physics engine integration for applications and games that utilise the [Specs ECS](https://slide-rs.github.io/specs/). + +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 + +```toml +[dependencies] +specs-physics = "0.0.1" +``` + +### Example + +TBD + +## Contributing + +TBD + +## License + +Distributed under the MIT License. See [LICENSE](https://github.com/bamling/specs-physics/blob/master/LICENSE) for more information. + +## Acknowledgments + +This project is heavily inspired by [nphysics-ecs-dumb](https://github.com/distransient/nphysics-ecs-dumb); they did most of the heavy lifting, I'm just building up on what they have started! + +**Special thanks to:** +- [distransient](https://github.com/distransient) +- [jojolepro](https://github.com/jojolepro)+ \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml @@ -0,0 +1,7 @@ +imports_layout = "HorizontalVertical" +condense_wildcard_suffixes = true +#brace_style = "PreferSameLine" +merge_imports = true +reorder_impl_items = true +use_field_init_shorthand = true +wrap_comments = true+ \ No newline at end of file diff --git a/src/body.rs b/src/body.rs @@ -0,0 +1,115 @@ +use nalgebra::RealField; +use nphysics::object::BodyHandle; +pub use nphysics::object::BodyStatus; +use specs::{Component, DenseVecStorage, FlaggedStorage}; + +use crate::math::{Matrix3, Point3, Vector3}; + +/// The `PhysicsBody` `Component` represents a `PhysicsWorld` `RigidBody` in +/// Specs and contains all the data required for the synchronisation between +/// both worlds. +#[derive(Clone, Copy, Debug)] +pub struct PhysicsBody<N: RealField> { + pub(crate) handle: Option<BodyHandle>, + pub gravity_enabled: bool, + pub body_status: BodyStatus, + pub velocity: Vector3<N>, + pub angular_inertia: Matrix3<N>, + pub mass: N, + pub local_center_of_mass: Point3<N>, +} + +impl<N: RealField> Component for PhysicsBody<N> { + type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; +} + +/// The `PhysicsBodyBuilder` implements the builder pattern for `PhysicsBody`s +/// and is the recommended way of instantiating and customising new +/// `PhysicsBody` instances. +/// +/// # Example +/// +/// ```rust +/// use specs_physics::{ +/// body::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(); +/// ``` +pub struct PhysicsBodyBuilder<N: RealField> { + gravity_enabled: bool, + body_status: BodyStatus, + velocity: Vector3<N>, + angular_inertia: Matrix3<N>, + mass: N, + local_center_of_mass: Point3<N>, +} + +impl<N: RealField> From<BodyStatus> for PhysicsBodyBuilder<N> { + /// Creates a new `PhysicsBodyBuilder` from the given `BodyStatus`. This + /// also populates the `PhysicsBody` with sane defaults. + fn from(body_status: BodyStatus) -> Self { + Self { + gravity_enabled: false, + body_status, + velocity: Vector3::new(N::zero(), N::zero(), N::zero()), + angular_inertia: Matrix3::zeros(), + mass: N::from_f32(1.2).unwrap(), + local_center_of_mass: Point3::new(N::zero(), N::zero(), N::zero()), + } + } +} + +impl<N: RealField> PhysicsBodyBuilder<N> { + /// Sets the `gravity_enabled` value of the `PhysicsBodyBuilder`. + pub fn gravity_enabled(mut self, gravity_enabled: bool) -> Self { + self.gravity_enabled = gravity_enabled; + self + } + + // Sets the `velocity` value of the `PhysicsBodyBuilder`. + pub fn velocity(mut self, velocity: Vector3<N>) -> Self { + self.velocity = velocity; + self + } + + /// Sets the `angular_inertia` value of the `PhysicsBodyBuilder`. + pub fn angular_inertia(mut self, angular_inertia: Matrix3<N>) -> Self { + self.angular_inertia = angular_inertia; + self + } + + /// Sets the `mass` value of the `PhysicsBodyBuilder`. + pub fn mass(mut self, mass: N) -> Self { + self.mass = mass; + self + } + + /// Sets the `local_center_of_mass` value of the `PhysicsBodyBuilder`. + pub fn local_center_of_mass(mut self, local_center_of_mass: Point3<N>) -> Self { + self.local_center_of_mass = local_center_of_mass; + self + } + + /// Builds the `PhysicsBody` from the values set in the `PhysicsBodyBuilder` + /// instance. + pub fn build(self) -> PhysicsBody<N> { + PhysicsBody { + handle: None, + gravity_enabled: self.gravity_enabled, + body_status: self.body_status, + velocity: self.velocity, + angular_inertia: self.angular_inertia, + mass: self.mass, + local_center_of_mass: self.local_center_of_mass, + } + } +} diff --git a/src/collider.rs b/src/collider.rs @@ -0,0 +1,219 @@ +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}; + +/// `Shape` serves as an abstraction over nphysics `ShapeHandle`s and makes it +/// easier to configure and define said `ShapeHandle`s for the user without +/// having to know the underlying nphysics API. +#[derive(Clone, Copy, Debug)] +pub enum Shape<N: RealField> { + Circle(N), + Rectangle(N, N, N), +} + +impl<N: RealField> Shape<N> { + /// Converts a `Shape` and its values into its corresponding `ShapeHandle` + /// type. The `ShapeHandle` is used to define a `Collider` in the + /// `PhysicsWorld`. + fn handle(&self, margin: N) -> ShapeHandle<N> { + match *self { + Shape::Circle(radius) => ShapeHandle::new(Ball::new(radius)), + Shape::Rectangle(width, height, depth) => ShapeHandle::new(Cuboid::new(Vector3::new( + width / N::from_f32(2.0).unwrap() - margin, + height / N::from_f32(2.0).unwrap() - margin, + depth / N::from_f32(2.0).unwrap() - margin, + ))), + } + } +} + +/// The `PhysicsCollider` `Component` represents a `Collider` in the physics +/// world. A physics `Collider` is automatically created when this `Component` +/// is added to an `Entity`. Value changes are automatically synchronised with +/// the physic worlds `Collider`. +#[derive(Clone)] +pub struct PhysicsCollider<N: RealField> { + pub(crate) handle: Option<ColliderHandle>, + pub shape: Shape<N>, + pub offset_from_parent: Isometry3<N>, + pub density: N, + pub material: MaterialHandle<N>, + pub margin: N, + pub collision_groups: CollisionGroups, + pub linear_prediction: N, + pub angular_prediction: N, + pub sensor: bool, +} + +impl<N: RealField> Component for PhysicsCollider<N> { + type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; +} + +impl<N: RealField> fmt::Debug for PhysicsCollider<N> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PhysicsCollider {{ \ + handle: {:?}, \ + offset_from_parent: {:?}, \ + density: {}, \ + margin: {}, \ + collision_group: {:?}, \ + linear_prediction: {}, \ + angular_prediction: {}, \ + sensor: {} \ + }}", + self.handle, + self.offset_from_parent, + self.density, + self.margin, + self.collision_groups, + self.linear_prediction, + self.angular_prediction, + self.sensor, + )?; + Ok(()) + } +} + +impl<N: RealField> PhysicsCollider<N> { + /// Returns the `ShapeHandle` for `shape`, taking the `margin` into + /// consideration. + pub(crate) fn shape_handle(&self) -> ShapeHandle<N> { + self.shape.handle(self.margin) + } +} + +/// The `PhysicsColliderBuilder` implements the builder pattern for +/// `PhysicsCollider`s and is the recommended way of instantiating and +/// customising new `PhysicsCollider` instances. +/// +/// # Example +/// +/// ```rust +/// use specs_physics::{ +/// collider::{ +/// 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(); +/// ``` +pub struct PhysicsColliderBuilder<N: RealField> { + shape: Shape<N>, + offset_from_parent: Isometry3<N>, + density: N, + material: MaterialHandle<N>, + margin: N, + collision_groups: CollisionGroups, + linear_prediction: N, + angular_prediction: N, + sensor: bool, +} + +impl<N: RealField> From<Shape<N>> for PhysicsColliderBuilder<N> { + /// Creates a new `PhysicsColliderBuilder` from the given `Shape`. This + // also populates the `PhysicsCollider` with sane defaults. + fn from(shape: Shape<N>) -> Self { + Self { + shape, + offset_from_parent: Isometry3::identity(), + density: N::from_f32(1.3).unwrap(), + material: MaterialHandle::new(BasicMaterial::default()), + margin: N::from_f32(0.2).unwrap(), // default was: 0.01 + collision_groups: CollisionGroups::default(), + linear_prediction: N::from_f32(0.002).unwrap(), + angular_prediction: N::from_f32(PI / 180.0 * 5.0).unwrap(), + sensor: false, + } + } +} + +impl<N: RealField> PhysicsColliderBuilder<N> { + /// Sets the `offset_from_parent` value of the `PhysicsColliderBuilder`. + pub fn offset_from_parent(mut self, offset_from_parent: Isometry3<N>) -> Self { + self.offset_from_parent = offset_from_parent; + self + } + + /// Sets the `density` value of the `PhysicsColliderBuilder`. + pub fn density(mut self, density: N) -> Self { + self.density = density; + self + } + + /// Sets the `material` value of the `PhysicsColliderBuilder`. + pub fn material(mut self, material: MaterialHandle<N>) -> Self { + self.material = material; + self + } + + /// Sets the `margin` value of the `PhysicsColliderBuilder`. + pub fn margin(mut self, margin: N) -> Self { + self.margin = margin; + self + } + + /// Sets the `collision_groups` value of the `PhysicsColliderBuilder`. + pub fn collision_groups(mut self, collision_groups: CollisionGroups) -> Self { + self.collision_groups = collision_groups; + self + } + + /// Sets the `linear_prediction` value of the `PhysicsColliderBuilder`. + pub fn linear_prediction(mut self, linear_prediction: N) -> Self { + self.linear_prediction = linear_prediction; + self + } + + /// Sets the `angular_prediction` value of the `PhysicsColliderBuilder`. + pub fn angular_prediction(mut self, angular_prediction: N) -> Self { + self.angular_prediction = angular_prediction; + self + } + + /// Sets the `sensor` value of the `PhysicsColliderBuilder`. + pub fn sensor(mut self, sensor: bool) -> Self { + self.sensor = sensor; + self + } + + /// Builds the `PhysicsCollider` from the values set in the + /// `PhysicsColliderBuilder` instance. + pub fn build(self) -> PhysicsCollider<N> { + PhysicsCollider { + handle: None, + shape: self.shape, + offset_from_parent: self.offset_from_parent, + density: self.density, + material: self.material, + margin: self.margin, + collision_groups: self.collision_groups, + linear_prediction: self.linear_prediction, + angular_prediction: self.angular_prediction, + sensor: self.sensor, + } + } +} diff --git a/src/lib.rs b/src/lib.rs @@ -0,0 +1,24 @@ +#[macro_use] +extern crate log; +extern crate ncollide3d as ncollide; +extern crate nphysics3d as nphysics; + +pub use nalgebra as math; +use nphysics::world::World; + +use self::math::Vector3; +pub use self::{ + body::{PhysicsBody, PhysicsBodyBuilder}, + collider::{PhysicsCollider, PhysicsColliderBuilder, Shape}, +}; + +pub mod body; +pub mod collider; +mod systems; + +///// The `PhysicsWorld` containing all physical objects. +//pub type PhysicsWorld<N> = World<N>; +// +///// `Gravity` is a type alias for `Vector3<f32>`. It represents a constant +///// acceleration affecting all physical objects in the scene. +//pub type Gravity<N> = Vector3<N>; diff --git a/src/lib.rs b/src/systems/mod.rs