For when you want some nphysics in your Specs! Somewhat better than sliced bread!
Remember those "FooBarDefault" types in the nphysics tutorial? specs-physics provides ECS Component-based implementations for nphysics data sets, as well as faculties for synchronizing pose data to your position type of choice, and stepping functionality for stepping your simulations to a real good beat.
To use specs-physics with a 3D nphysics world, add the following dependency to your project's Cargo.toml:
[dependencies]
specs-physics = { version = "0.4.0", features = ["dim3"] }For 2D nphysics, replace dim3 with dim2.
You must enable one of these two features, and you can only enable one of them!
Also available is an amethyst feature,
which adds synchronization support for Amethyst
through amethyst_core's Transform type
as well as a SystemBundle trait impl for PhysicsBundle.
Usage is explained further below.
All Systems and Components 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.
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 andf64. nalgebra
is re-exported under specs_physics::nalgebra.
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:
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
}
}If you're using Amethyst, you can enable the "amethyst" feature for this
crate which provides a Position<Float> impl for Transform.
[dependencies]
specs-physics = { version = "0.3", features = ["amethyst"] }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:
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();specs_physics::PhysicsColliders are the counterpart to PhysicsBodys.
They can exist on their own or as a part of a PhysicsBody
PhysicsColliders are used to define and create Collider's in
nphysics.
Example:
use specs_physics::{
colliders::Shape,
nalgebra::{Isometry3, Vector3},
ncollide::world::CollisionGroups,
nphysics::material::{BasicMaterial, MaterialHandle},
PhysicsColliderBuilder,
};
let physics_collider = PhysicsColliderBuilder::from(
Shape::Cuboid{ half_extents: Vector3::new(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.
The following Systems currently exist and should be added to your
Dispatcher in order:
-
specs_physics::systems::SyncBodiesToPhysicsSystem- handles the creation, modification and removal of RigidBody's based on thePhysicsBodyComponentand an implementation of thePositiontrait. -
specs_physics::systems::SyncCollidersToPhysicsSystem- handles the creation, modification and removal of Collider's based on thePhysicsColliderComponent. ThisSystemdepends onSyncBodiesToPhysicsSystemas Collider can depend on RigidBody. -
specs_physics::systems::SyncParametersToPhysicsSystem- handles the modification of the nphysicsWorlds parameters. -
specs_physics::systems::PhysicsStepperSystem- handles the progression of the nphysicsWorldand causes objects to actually move and change their position. ThisSystemis the backbone for collision detection. -
specs_physics::systems::SyncBodiesFromPhysicsSystem- handles the synchronisation of RigidBody positions and dynamics back into the SpecsComponents. ThisSystemalso utilises thePositiontrait implementation.
An example Dispatcher with all required Systems:
use specs::DispatcherBuilder;
use specs_physics::{
systems::{
PhysicsStepperSystem,
PhysicsPoseSystem,
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(
PhysicsPoseSystem::<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::<f32, 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 Systems for
you.