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 7528e5a401fbf8dffa42613fc0373277acdbabc2
parent 945b844adde5f99266986471c9f89ecfe4338076
Author: kel <distransient@protonmail.com>
Date:   Fri,  7 Dec 2018 01:52:50 -0500

Split single system into many, create bundle.

Diffstat:
MREADME.md | 43+++++++++++++++++++++++++++++++------------
Aexamples/amethyst.rs | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dexamples/app.rs | 127-------------------------------------------------------------------------------
Msrc/bodies.rs | 2+-
Msrc/forces.rs | 3++-
Dsrc/systems.rs | 283-------------------------------------------------------------------------------
Asrc/systems/mod.rs | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/systems/physics_stepper.rs | 24++++++++++++++++++++++++
Asrc/systems/sync_bodies_from_physics.rs | 98+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/systems/sync_bodies_to_physics.rs | 164+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/systems/sync_force_generators_to_physics.rs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/systems/sync_gravity_to_physics.rs | 28++++++++++++++++++++++++++++
12 files changed, 611 insertions(+), 424 deletions(-)

diff --git a/README.md b/README.md @@ -1,15 +1,34 @@ -# nphysics DUMB +# Nphysics - Amethyst connector Don't use. Work in progress. Many things are incomplete! -TODO sheet from [this nphysics issue](https://github.com/rustsim/nphysics/issues/149) - -- [x] Be able to have something able to simulate one rigid body that falls under gravity. This excludes collision detection, constraint solver, etc. -- [ ] Add support for forces generators. -- [ ] Add support for proxies (which are colliders without contact generation) and proximity detection. -- [ ] Add colliders, without constraints solver. -- [ ] Add support for the constraints solver, but without handling island computation nor body activation/sleeping. -- [ ] Add support for kinematic bodies. -- [ ] Add support for activation/sleeping. -- [ ] Add support for island computation. -- [ ] Add support for joint constraints +Currently specific to 3d Nphysics and Amethyst due to Amethyst handling of Transforms and to keep iteration quick, +although I currently plan on allowing 3d and 2d nphysics implementations to be used to configuration settings, and +both amethyst and plain specs interfaces to be exposed, also behind configuration settings. + +## System Sequence + +I'll update this as I go along. + +1. `SyncBodiestoPhysicsSystem` - Apply FlaggedStorage changes to nphysics world +1. `PhysicsStepperSystem` - Step physics simulation +1. `SyncSynchronize changes back to components from nphysics world + + +## Current Roadmap + +Full *TODO* sheet can be found in [this nphysics issue](https://github.com/rustsim/nphysics/issues/149) + +Ongoing work: + +- [x] RigidBody Components +- [ ] Force Generator Components +- [ ] Collider Components +- [ ] Proximity and Contact EventChannels + +Investigating: + +- [ ] Multibody-based Component Joints +- [ ] Constraint-based Joints +- [ ] Kinematics +- [ ] Body Activation & Sleeping. diff --git a/examples/amethyst.rs b/examples/amethyst.rs @@ -0,0 +1,125 @@ +use amethyst::assets::{Handle, Loader}; +use amethyst::core::nalgebra::{Matrix3, Vector3}; +use amethyst::core::specs::world::Builder; +use amethyst::core::{GlobalTransform, Transform, TransformBundle}; +use amethyst::renderer::{ + AmbientColor, Camera, DisplayConfig, DrawShaded, Light, Material, MaterialDefaults, MeshData, + MeshHandle, Pipeline, PointLight, PosNormTex, RenderBundle, Rgba, ScreenDimensions, Shape, + Stage, Texture, +}; +use amethyst::{Application, GameData, GameDataBuilder, SimpleState, StateData}; +use nphysics_ecs_dumb::bodies::DynamicBody; +use nphysics_ecs_dumb::forces::DefaultForceGenerators; +use nphysics_ecs_dumb::nphysics::math::{Point, Velocity}; +use nphysics_ecs_dumb::systems::PhysicsBundle; +use num_traits::identities::One; + +struct GameState; + +impl SimpleState for GameState { + fn on_start(&mut self, data: StateData<GameData>) { + data.world.register::<DynamicBody>(); + data.world.register::<MeshData>(); + data.world.register::<Handle<Texture>>(); + + // Create a texture for using. + let texture = data + .world + .read_resource::<Loader>() + .load_from_data::<Texture, ()>( + [170.0, 170.0, 255.0, 1.0].into(), + (), + &data.world.read_resource(), + ); + + let material = Material { + albedo: texture, + ..data.world.read_resource::<MaterialDefaults>().0.clone() + }; + + // Get resolution of the screen. + let (x, y) = { + let resolution = data.world.res.fetch::<ScreenDimensions>(); + (resolution.width(), resolution.height()) + }; + + let camera_transform = Transform::from(Vector3::new(0.0, 0.0, 0.0)); + + // Add Camera + data.world + .create_entity() + .with(Camera::standard_3d(x, y)) + .with(camera_transform) + .build(); + + // Add Light + data.world.add_resource(AmbientColor(Rgba::from([0.5; 3]))); + data.world + .create_entity() + .with(Light::Point(PointLight { + intensity: 3.0, + color: Rgba::white(), + radius: 5.0, + smoothness: 4.0, + })) + .with(Transform::from(Vector3::new(2.0, 2.0, -2.0))) + .build(); + + let sphere_shape = Shape::Sphere(32, 32).generate::<Vec<PosNormTex>>(None); + let sphere_handle: MeshHandle = data.world.read_resource::<Loader>().load_from_data( + sphere_shape, + (), + &data.world.read_resource(), + ); + + // Add Sphere (todo: add many, add rigidbodies and colliders) + data.world + .create_entity() + .with(sphere_handle) + .with(material) + .with(Transform::from(Vector3::new(0.0, 0.0, -10.0))) + .with(GlobalTransform::default()) + .with(DynamicBody::new_rigidbody_with_velocity( + Velocity::linear(0.0, 10.0, 0.0), + 10.0, + Matrix3::one(), + Point::new(0.0, 0.0, 0.0), + )) + .build(); + } +} + +fn main() -> amethyst::Result<()> { + amethyst::start_logger(Default::default()); + + let display_config = DisplayConfig { + title: "Amethyst + Nphysics".to_string(), + fullscreen: false, + dimensions: Some((800, 400)), + min_dimensions: Some((800, 400)), + max_dimensions: None, + vsync: true, + multisampling: 0, // Must be multiple of 2, use 0 to disable + visibility: true, + }; + let pipe = Pipeline::build().with_stage( + Stage::with_backbuffer() + .clear_target([0.1, 0.1, 0.1, 1.0], 1.0) + .with_pass(DrawShaded::<PosNormTex>::new()), + ); + + let game_data = GameDataBuilder::default() + .with_bundle(TransformBundle::new())? + .with_bundle( + PhysicsBundle::<DefaultForceGenerators>::new().with_dep(&["transform_system"]), + )? + .with_bundle(RenderBundle::new(pipe, Some(display_config)))?; + + let application = Application::new("./", GameState, game_data); + + assert_eq!(application.is_ok(), true); + + application.ok().unwrap().run(); + + Ok(()) +} diff --git a/examples/app.rs b/examples/app.rs @@ -1,127 +0,0 @@ -use amethyst::assets::{Handle, Loader}; -use amethyst::core::nalgebra::{Matrix3, Vector3}; -use amethyst::core::specs::world::Builder; -use amethyst::core::{GlobalTransform, Transform, TransformBundle}; -use amethyst::renderer::{ - AmbientColor, Camera, DisplayConfig, DrawShaded, Light, Material, MaterialDefaults, MeshData, - MeshHandle, Pipeline, PointLight, PosNormTex, RenderBundle, Rgba, ScreenDimensions, Shape, - Stage, Texture, -}; -use amethyst::{Application, GameData, GameDataBuilder, SimpleState, StateData}; -use nphysics_ecs_dumb::bodies::DynamicBody; -use nphysics_ecs_dumb::forces::DefaultForceGenerators; -use nphysics_ecs_dumb::nphysics::math::{Point, Velocity}; -use nphysics_ecs_dumb::systems::Dumb3dPhysicsSystem; -use num_traits::identities::One; - -struct GameState; - -impl SimpleState for GameState { - fn on_start(&mut self, data: StateData<GameData>) { - data.world.register::<DynamicBody>(); - data.world.register::<MeshData>(); - data.world.register::<Handle<Texture>>(); - - // Create a texture for using. - let texture = data - .world - .read_resource::<Loader>() - .load_from_data::<Texture, ()>( - [170.0, 170.0, 255.0, 1.0].into(), - (), - &data.world.read_resource(), - ); - - let material = Material { - albedo: texture, - ..data.world.read_resource::<MaterialDefaults>().0.clone() - }; - - // Get resolution of the screen. - let (x, y) = { - let resolution = data.world.res.fetch::<ScreenDimensions>(); - (resolution.width(), resolution.height()) - }; - - let camera_transform = Transform::from(Vector3::new(0.0, 0.0, 0.0)); - - // Add Camera - data.world - .create_entity() - .with(Camera::standard_3d(x, y)) - .with(camera_transform) - .build(); - - // Add Light - data.world.add_resource(AmbientColor(Rgba::from([0.5; 3]))); - data.world - .create_entity() - .with(Light::Point(PointLight { - intensity: 3.0, - color: Rgba::white(), - radius: 5.0, - smoothness: 4.0, - })) - .with(Transform::from(Vector3::new(2.0, 2.0, -2.0))) - .build(); - - let sphere_shape = Shape::Sphere(32, 32).generate::<Vec<PosNormTex>>(None); - let sphere_handle: MeshHandle = data.world.read_resource::<Loader>().load_from_data( - sphere_shape, - (), - &data.world.read_resource(), - ); - - // Add Sphere (todo: add many, add rigidbodies and colliders) - data.world - .create_entity() - .with(sphere_handle) - .with(material) - .with(Transform::from(Vector3::new(0.0, 0.0, -10.0))) - .with(GlobalTransform::default()) - .with(DynamicBody::new_rigidbody_with_velocity( - Velocity::linear(0.0, 10.0, 0.0), - 10.0, - Matrix3::one(), - Point::new(0.0, 0.0, 0.0), - )) - .build(); - } -} - -fn main() -> amethyst::Result<()> { - amethyst::start_logger(Default::default()); - - let display_config = DisplayConfig { - title: "Amethyst + Nphysics".to_string(), - fullscreen: false, - dimensions: Some((800, 400)), - min_dimensions: Some((800, 400)), - max_dimensions: None, - vsync: true, - multisampling: 0, // Must be multiple of 2, use 0 to disable - visibility: true, - }; - let pipe = Pipeline::build().with_stage( - Stage::with_backbuffer() - .clear_target([0.1, 0.1, 0.1, 1.0], 1.0) - .with_pass(DrawShaded::<PosNormTex>::new()), - ); - - let game_data = GameDataBuilder::default() - .with_bundle(TransformBundle::new())? - .with( - Dumb3dPhysicsSystem::<DefaultForceGenerators>::default(), - "physics", - &[], - ) - .with_bundle(RenderBundle::new(pipe, Some(display_config)))?; - - let application = Application::new("./", GameState, game_data); - - assert_eq!(application.is_ok(), true); - - application.ok().unwrap().run(); - - Ok(()) -} diff --git a/src/bodies.rs b/src/bodies.rs @@ -56,7 +56,7 @@ impl Component for DynamicBody { /// Currently only the velocity is read and updated at runtime. /// The properties of mass are only written at physics body creation time. pub struct RigidPhysicsBody { - pub handle: Option<BodyHandle>, + pub(crate) handle: Option<BodyHandle>, pub velocity: Velocity<f32>, // TODO: update these in the physics system below. diff --git a/src/forces.rs b/src/forces.rs @@ -7,7 +7,7 @@ use nphysics3d::math::{Force, Point, Vector, Velocity}; use nphysics3d::object::{BodyHandle, BodySet}; use nphysics3d::solver::IntegrationParameters; -pub trait ForceGenerators { +pub trait ForceGenerators: Default + Send + Sync { type LocalForceGenerators: LocalForceGenerators; type LinkedForceGenerators: LinkedForceGenerators; } @@ -23,6 +23,7 @@ pub trait LinkedForceGenerators: fn affected_bodies(&self) -> DynamicsBodyRelations; // TODO this signature is definitely wrong. } +#[derive(Default)] pub struct DefaultForceGenerators; impl ForceGenerators for DefaultForceGenerators { diff --git a/src/systems.rs b/src/systems.rs @@ -1,283 +0,0 @@ -use crate::bodies::DynamicBody; -use crate::forces::{DefaultForceGenerators, ForceGenerators}; -use crate::{Gravity, World}; -use amethyst::core::{GlobalTransform, Time, Transform}; -use amethyst::ecs::storage::ComponentEvent; -use amethyst::ecs::{ - BitSet, Entities, Join, Read, ReadExpect, ReadStorage, ReaderId, Resources, System, SystemData, - Write, WriteExpect, WriteStorage, -}; -use amethyst::shrev::EventChannel; -use core::marker::PhantomData; -use nalgebra::try_convert; -use nalgebra::Vector3; -use ncollide3d::events::ContactEvent; -use nphysics3d::math::Inertia; -use nphysics3d::object::Body; - -/// Iterates over entities with both a GlobalTransform and a PhysicsBody, -/// and synchronizes their state to an nphysics world simulation, -/// stepping the simulation each frame. -pub struct Dumb3dPhysicsSystem<F = DefaultForceGenerators> -where - F: ForceGenerators, -{ - inserted_transforms: BitSet, - modified_transforms: BitSet, - - inserted_physics_bodies: BitSet, - modified_physics_bodies: BitSet, - - transforms_reader_id: Option<ReaderId<ComponentEvent>>, - physics_bodies_reader_id: Option<ReaderId<ComponentEvent>>, - - phantom_force_generators: PhantomData<F>, -} - -impl<F> Default for Dumb3dPhysicsSystem<F> -where - F: ForceGenerators, -{ - fn default() -> Self { - Dumb3dPhysicsSystem::<F> { - inserted_transforms: BitSet::default(), - modified_transforms: BitSet::default(), - inserted_physics_bodies: BitSet::default(), - modified_physics_bodies: BitSet::default(), - transforms_reader_id: None, - physics_bodies_reader_id: None, - phantom_force_generators: PhantomData, - } - } -} - -impl<'a, F> System<'a> for Dumb3dPhysicsSystem<F> -where - F: ForceGenerators, -{ - type SystemData = ( - WriteExpect<'a, World>, - Write<'a, EventChannel<ContactEvent>>, - Read<'a, Time>, - Entities<'a>, - WriteStorage<'a, GlobalTransform>, - WriteStorage<'a, DynamicBody>, - ReadStorage<'a, Transform>, - ReadExpect<'a, Gravity>, - ReadStorage<'a, F::LocalForceGenerators>, - ReadStorage<'a, F::LinkedForceGenerators>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut physical_world, - _contact_events, - time, - entities, - mut transforms, - mut physics_bodies, - locals, - gravity, - _local_force_generators, - _linked_force_generators, - ) = data; - - // Clear bitsets - self.inserted_transforms.clear(); - self.modified_transforms.clear(); - self.inserted_physics_bodies.clear(); - self.modified_physics_bodies.clear(); - - // Get change flag events for transforms, removing deleted ones from the physics world. - { - let events = transforms - .channel() - .read(&mut self.transforms_reader_id.as_mut().unwrap()); - for event in events { - match event { - ComponentEvent::Modified(id) => { - self.modified_transforms.add(*id); - } - ComponentEvent::Inserted(id) => { - self.inserted_transforms.add(*id); - } - ComponentEvent::Removed(id) => { - physical_world.remove_bodies(&[physics_bodies - .get(entities.entity(*id)) - .unwrap() - .handle() - .unwrap()]); - } - }; - } - } - - // Get change flag events for physics bodies, removing deleted ones from the physics world. - { - let events = physics_bodies - .channel() - .read(&mut self.physics_bodies_reader_id.as_mut().unwrap()); - for event in events { - match event { - ComponentEvent::Modified(id) => { - self.modified_physics_bodies.add(*id); - } - ComponentEvent::Inserted(id) => { - self.inserted_physics_bodies.add(*id); - println!("I'm in!"); - } - ComponentEvent::Removed(id) => { - physical_world.remove_bodies(&[physics_bodies - .get(entities.entity(*id)) - .unwrap() - .handle() - .unwrap()]); - } - }; - } - } - - // Update simulation world with the value of Components flagged as changed - for (_entity, transform, mut body, id) in ( - &entities, - &transforms, - &mut physics_bodies, - &self.modified_transforms - | &self.inserted_transforms - | &self.modified_physics_bodies - | &self.inserted_physics_bodies, - ) - .join() - { - if self.inserted_transforms.contains(id) || self.inserted_physics_bodies.contains(id) { - println!("heya I'm new here!"); - match body { - DynamicBody::RigidBody(ref mut rigid_body) => { - // Just inserted. Remove old one and insert new. - if rigid_body.handle.is_some() - && physical_world - .rigid_body(rigid_body.handle.unwrap()) - .is_some() - { - physical_world.remove_bodies(&[rigid_body.handle.unwrap()]); - } - - rigid_body.handle = Some(physical_world.add_rigid_body( - try_convert(transform.0).unwrap(), - Inertia::new(rigid_body.mass, rigid_body.angular_mass), - rigid_body.center_of_mass, - )); - - let physical_body = physical_world - .rigid_body_mut(rigid_body.handle.unwrap()) - .unwrap(); - - physical_body.set_velocity(rigid_body.velocity); - } - DynamicBody::Multibody(_) => { - // TODO - } - } - } else if self.modified_transforms.contains(id) - || self.modified_physics_bodies.contains(id) - { - println!("oh shit, I'm changed!"); - match body { - DynamicBody::RigidBody(ref mut rigid_body) => { - let physical_body = physical_world - .rigid_body_mut(rigid_body.handle.unwrap()) - .unwrap(); - - physical_body.set_position(try_convert(transform.0).unwrap()); - physical_body.set_velocity(rigid_body.velocity); - - // if you changed the mass properties at all... too bad! - } - DynamicBody::Multibody(_) => { - // TODO - } - } - } - } - - physical_world.set_gravity(*gravity); - - // Simulate world using the current time frame - physical_world.set_timestep(time.delta_seconds()); - physical_world.step(); - - //Why does this break. why - // TODO: Fix contact events, use Entity id's instead of nphysics handles. - //contact_events.iter_write(physical_world.contact_events()); - - // Apply the updated values of the simulated world to our Components - for (mut transform, mut body, local) in - (&mut transforms, &mut physics_bodies, locals.maybe()).join() - { - let updated_body = physical_world.body(body.handle().unwrap()); - - if updated_body.is_ground() || !updated_body.is_active() || updated_body.is_static() { - continue; - } - - match (body, updated_body) { - ( - DynamicBody::RigidBody(ref mut rigid_body), - Body::RigidBody(ref updated_rigid_body), - ) => { - println!( - "super power: change mehhh! new pos: {:?}", - updated_rigid_body.position() - ); - - // TODO: Might get rid of the scale!!! - transform.0 = updated_rigid_body - .position() - .to_homogeneous() - .prepend_nonuniform_scaling( - local - .map(|tr| tr.scale()) - .unwrap_or(&Vector3::new(1.0, 1.0, 1.0)), - ); - - rigid_body.velocity = *updated_rigid_body.velocity(); - let inertia = updated_rigid_body.inertia(); - rigid_body.mass = inertia.linear; - rigid_body.angular_mass = inertia.angular; - rigid_body.center_of_mass = updated_rigid_body.center_of_mass(); - } - (DynamicBody::Multibody(_multibody), Body::Multibody(_updated_multibody)) => { - // match updated_multibody.links().next() { - // Some(link) => link.position(), - // None => continue, - // }; - } - _ => println!("Unexpected body component and nphysics body pair."), - }; - } - - // Now that we changed them all, let's remove all those pesky events that were generated. - transforms - .channel() - .read(&mut self.transforms_reader_id.as_mut().unwrap()) - .for_each(|_| ()); - physics_bodies - .channel() - .read(&mut self.physics_bodies_reader_id.as_mut().unwrap()) - .for_each(|_| ()); - } - - fn setup(&mut self, res: &mut Resources) { - Self::SystemData::setup(res); - - res.entry::<Gravity>() - .or_insert_with(|| Gravity::new(0.0, -9.80665, 0.0)); - res.entry::<World>().or_insert_with(World::new); - - let mut transform_storage: WriteStorage<GlobalTransform> = SystemData::fetch(&res); - self.transforms_reader_id = Some(transform_storage.register_reader()); - - let mut physics_body_storage: WriteStorage<DynamicBody> = SystemData::fetch(&res); - self.physics_bodies_reader_id = Some(physics_body_storage.register_reader()); - } -} diff --git a/src/systems/mod.rs b/src/systems/mod.rs @@ -0,0 +1,89 @@ +mod physics_stepper; +mod sync_bodies_from_physics; +mod sync_bodies_to_physics; +mod sync_force_generators_to_physics; +mod sync_gravity_to_physics; + +use crate::forces::ForceGenerators; +use amethyst::core::bundle::{Result, SystemBundle}; +use amethyst::core::specs::DispatcherBuilder; +use core::marker::PhantomData; + +pub use self::physics_stepper::PhysicsStepperSystem; +pub use self::sync_bodies_from_physics::SyncBodiesFromPhysicsSystem; +pub use self::sync_bodies_to_physics::SyncBodiesToPhysicsSystem; +pub use self::sync_force_generators_to_physics::SyncForceGeneratorsToPhysicsSystem; +pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem; + +// TODO: Implement contact events, use Entity id's instead of nphysics handles. +// contact_events.iter_write(physical_world.contact_events()); + +pub const SYNC_FORCE_GENERATORS_TO_PHYSICS_SYSTEM: &str = "sync_force_generators_to_physics_system"; +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 PHYSICS_STEPPER_SYSTEM: &str = "physics_stepper_system"; +pub const SYNC_BODIES_FROM_PHYSICS_SYSTEM: &str = "sync_bodies_from_physics_system"; + +#[derive(Default)] +pub struct PhysicsBundle<'a, F> +where + F: ForceGenerators, +{ + dep: &'a [&'a str], + phantom_force_generators: PhantomData<F>, +} + +impl<'a, F> PhysicsBundle<'a, F> +where + F: ForceGenerators, +{ + pub fn new() -> Self { + Default::default() + } + + pub fn with_dep(mut self, dep: &'a [&'a str]) -> Self { + self.dep = dep; + self + } +} + +impl<'a, 'b, 'c, F: 'a> SystemBundle<'a, 'b> for PhysicsBundle<'c, F> +where + F: ForceGenerators, +{ + fn build(self, builder: &mut DispatcherBuilder<'a, 'b>) -> Result<()> { + builder.add( + SyncForceGeneratorsToPhysicsSystem::<F>::new(), + SYNC_FORCE_GENERATORS_TO_PHYSICS_SYSTEM, + self.dep, + ); + builder.add( + SyncBodiesToPhysicsSystem::new(), + SYNC_BODIES_TO_PHYSICS_SYSTEM, + self.dep, + ); + builder.add( + SyncGravityToPhysicsSystem::new(), + SYNC_GRAVITY_TO_PHYSICS_SYSTEM, + self.dep, + ); + + builder.add( + PhysicsStepperSystem::new(), + PHYSICS_STEPPER_SYSTEM, + &[ + SYNC_FORCE_GENERATORS_TO_PHYSICS_SYSTEM, + SYNC_BODIES_TO_PHYSICS_SYSTEM, + SYNC_GRAVITY_TO_PHYSICS_SYSTEM, + ], + ); + + builder.add( + SyncBodiesFromPhysicsSystem::new(), + SYNC_BODIES_FROM_PHYSICS_SYSTEM, + &[PHYSICS_STEPPER_SYSTEM], + ); + + Ok(()) + } +} diff --git a/src/systems/physics_stepper.rs b/src/systems/physics_stepper.rs @@ -0,0 +1,24 @@ +use crate::World; +use amethyst::core::Time; +use amethyst::ecs::{Read, System, WriteExpect}; + +/// Simulates a step of the physics world. +#[derive(Default)] +pub struct PhysicsStepperSystem; + +impl PhysicsStepperSystem { + pub fn new() -> Self { + Default::default() + } +} + +impl<'a> System<'a> for PhysicsStepperSystem { + type SystemData = (WriteExpect<'a, World>, Read<'a, Time>); + + fn run(&mut self, (mut physical_world, time): Self::SystemData) { + // Simulate world using the current time frame + // TODO: Bound timestep deltas + physical_world.set_timestep(time.delta_seconds()); + physical_world.step(); + } +} diff --git a/src/systems/sync_bodies_from_physics.rs b/src/systems/sync_bodies_from_physics.rs @@ -0,0 +1,98 @@ +use crate::bodies::DynamicBody; +use crate::World; +use amethyst::core::{GlobalTransform, Transform}; +use amethyst::ecs::{Join, ReadStorage, System, Write, WriteExpect, WriteStorage}; +use amethyst::shrev::EventChannel; +use nalgebra::Vector3; +use ncollide3d::events::ContactEvent; +use nphysics3d::object::Body; + +#[derive(Default)] +pub struct SyncBodiesFromPhysicsSystem; + +impl SyncBodiesFromPhysicsSystem { + pub fn new() -> Self { + Default::default() + } +} + +impl<'a> System<'a> for SyncBodiesFromPhysicsSystem { + type SystemData = ( + WriteExpect<'a, World>, + Write<'a, EventChannel<ContactEvent>>, + WriteStorage<'a, GlobalTransform>, + WriteStorage<'a, DynamicBody>, + ReadStorage<'a, Transform>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut physical_world, + _contact_events, + mut global_transforms, + mut physics_bodies, + local_transforms, + ) = data; + + // Apply the updated values of the simulated world to our Components + for (mut global_transform, mut body, local_transform) in ( + &mut global_transforms, + &mut physics_bodies, + local_transforms.maybe(), + ) + .join() + { + let updated_body = physical_world.body(body.handle().unwrap()); + + if updated_body.is_ground() || !updated_body.is_active() || updated_body.is_static() { + continue; + } + + match (body, updated_body) { + ( + DynamicBody::RigidBody(ref mut rigid_body), + Body::RigidBody(ref updated_rigid_body), + ) => { + println!( + "super power: change mehhh! new pos: {:?}", + updated_rigid_body.position() + ); + + // TODO: Might get rid of the scale!!! + global_transform.0 = updated_rigid_body + .position() + .to_homogeneous() + .prepend_nonuniform_scaling( + local_transform + .map(|tr| tr.scale()) + .unwrap_or(&Vector3::new(1.0, 1.0, 1.0)), + ); + + rigid_body.velocity = *updated_rigid_body.velocity(); + let inertia = updated_rigid_body.inertia(); + rigid_body.mass = inertia.linear; + rigid_body.angular_mass = inertia.angular; + rigid_body.center_of_mass = updated_rigid_body.center_of_mass(); + } + (DynamicBody::Multibody(_multibody), Body::Multibody(_updated_multibody)) => { + // match updated_multibody.links().next() { + // Some(link) => link.position(), + // None => continue, + // }; + } + _ => println!("Unexpected dynamics body pair."), + }; + } + + // TODO: reader id from other system? + // Now that we changed them all, let's remove all those pesky events that were generated. + // global_transforms + // .channel() + // .read(&mut self.transforms_reader_id.as_mut().unwrap()) + // .for_each(|_| ()); + // physics_bodies + // .channel() + // .read(&mut self.physics_bodies_reader_id.as_mut().unwrap()) + // .for_each(|_| ()); + } +} diff --git a/src/systems/sync_bodies_to_physics.rs b/src/systems/sync_bodies_to_physics.rs @@ -0,0 +1,164 @@ +use crate::bodies::DynamicBody; +use crate::World; +use amethyst::core::GlobalTransform; +use amethyst::ecs::storage::ComponentEvent; +use amethyst::ecs::{ + BitSet, Entities, Join, ReaderId, Resources, System, SystemData, Write, WriteExpect, + WriteStorage, +}; +use amethyst::shrev::EventChannel; +use nalgebra::try_convert; +use ncollide3d::events::ContactEvent; +use nphysics3d::math::Inertia; + +#[derive(Default)] +pub struct SyncBodiesToPhysicsSystem { + transforms_reader_id: Option<ReaderId<ComponentEvent>>, + physics_bodies_reader_id: Option<ReaderId<ComponentEvent>>, +} + +impl SyncBodiesToPhysicsSystem { + pub fn new() -> Self { + Default::default() + } +} + +impl<'a> System<'a> for SyncBodiesToPhysicsSystem { + type SystemData = ( + WriteExpect<'a, World>, + Write<'a, EventChannel<ContactEvent>>, + Entities<'a>, + WriteStorage<'a, GlobalTransform>, + WriteStorage<'a, DynamicBody>, + ); + + fn run(&mut self, data: Self::SystemData) { + let (mut physical_world, _contact_events, entities, mut transforms, mut physics_bodies) = + data; + + let mut inserted_transforms = BitSet::new(); + let mut modified_transforms = BitSet::new(); + let mut inserted_physics_bodies = BitSet::new(); + let mut modified_physics_bodies = BitSet::new(); + + // Get change flag events for transforms, removing deleted ones from the physics world. + { + let events = transforms + .channel() + .read(&mut self.transforms_reader_id.as_mut().unwrap()); + for event in events { + match event { + ComponentEvent::Modified(id) => { + modified_transforms.add(*id); + } + ComponentEvent::Inserted(id) => { + inserted_transforms.add(*id); + } + ComponentEvent::Removed(id) => { + physical_world.remove_bodies(&[physics_bodies + .get(entities.entity(*id)) + .unwrap() + .handle() + .unwrap()]); + } + }; + } + } + + // Get change flag events for physics bodies, removing deleted ones from the physics world. + { + let events = physics_bodies + .channel() + .read(&mut self.physics_bodies_reader_id.as_mut().unwrap()); + for event in events { + match event { + ComponentEvent::Modified(id) => { + modified_physics_bodies.add(*id); + } + ComponentEvent::Inserted(id) => { + inserted_physics_bodies.add(*id); + println!("I'm in!"); + } + ComponentEvent::Removed(id) => { + physical_world.remove_bodies(&[physics_bodies + .get(entities.entity(*id)) + .unwrap() + .handle() + .unwrap()]); + } + }; + } + } + + // Update simulation world with the value of Components flagged as changed + for (_entity, transform, mut body, id) in ( + &entities, + &transforms, + &mut physics_bodies, + &modified_transforms + | &inserted_transforms + | &modified_physics_bodies + | &inserted_physics_bodies, + ) + .join() + { + if inserted_transforms.contains(id) || inserted_physics_bodies.contains(id) { + println!("Detected inserted dynamics body with id {:?}", id); + match body { + DynamicBody::RigidBody(ref mut rigid_body) => { + // Just inserted. Remove old one and insert new. + if rigid_body.handle.is_some() + && physical_world + .rigid_body(rigid_body.handle.unwrap()) + .is_some() + { + physical_world.remove_bodies(&[rigid_body.handle.unwrap()]); + } + + rigid_body.handle = Some(physical_world.add_rigid_body( + try_convert(transform.0).unwrap(), + Inertia::new(rigid_body.mass, rigid_body.angular_mass), + rigid_body.center_of_mass, + )); + + let physical_body = physical_world + .rigid_body_mut(rigid_body.handle.unwrap()) + .unwrap(); + + physical_body.set_velocity(rigid_body.velocity); + } + DynamicBody::Multibody(_) => { + // TODO + } + } + } else if modified_transforms.contains(id) || modified_physics_bodies.contains(id) { + println!("Detected changed dynamics body with id {:?}", id); + match body { + DynamicBody::RigidBody(ref mut rigid_body) => { + let physical_body = physical_world + .rigid_body_mut(rigid_body.handle.unwrap()) + .unwrap(); + + physical_body.set_position(try_convert(transform.0).unwrap()); + physical_body.set_velocity(rigid_body.velocity); + + // if you changed the mass properties at all... too bad! + } + DynamicBody::Multibody(_) => { + // TODO + } + } + } + } + } + + fn setup(&mut self, res: &mut Resources) { + Self::SystemData::setup(res); + + let mut transform_storage: WriteStorage<GlobalTransform> = SystemData::fetch(&res); + self.transforms_reader_id = Some(transform_storage.register_reader()); + + let mut physics_body_storage: WriteStorage<DynamicBody> = SystemData::fetch(&res); + self.physics_bodies_reader_id = Some(physics_body_storage.register_reader()); + } +} diff --git a/src/systems/sync_force_generators_to_physics.rs b/src/systems/sync_force_generators_to_physics.rs @@ -0,0 +1,49 @@ +use crate::forces::{DefaultForceGenerators, ForceGenerators}; +use crate::World; +use amethyst::ecs::{ReadStorage, System, Write, WriteExpect}; +use amethyst::shrev::EventChannel; +use core::marker::PhantomData; +use ncollide3d::events::ContactEvent; + +// TODO: Synchronize force generators, tidy up api. + +pub struct SyncForceGeneratorsToPhysicsSystem<F = DefaultForceGenerators> +where + F: ForceGenerators, +{ + phantom_force_generators: PhantomData<F>, +} + +impl<F> SyncForceGeneratorsToPhysicsSystem<F> +where + F: ForceGenerators, +{ + pub fn new() -> Self { + Default::default() + } +} + +impl<F> Default for SyncForceGeneratorsToPhysicsSystem<F> +where + F: ForceGenerators, +{ + fn default() -> Self { + SyncForceGeneratorsToPhysicsSystem::<F> { + phantom_force_generators: PhantomData, + } + } +} + +impl<'a, F> System<'a> for SyncForceGeneratorsToPhysicsSystem<F> +where + F: ForceGenerators, +{ + type SystemData = ( + WriteExpect<'a, World>, + Write<'a, EventChannel<ContactEvent>>, + ReadStorage<'a, F::LocalForceGenerators>, + ReadStorage<'a, F::LinkedForceGenerators>, + ); + + fn run(&mut self, _data: Self::SystemData) {} +} diff --git a/src/systems/sync_gravity_to_physics.rs b/src/systems/sync_gravity_to_physics.rs @@ -0,0 +1,28 @@ +use crate::{Gravity, World}; +use amethyst::ecs::{ReadExpect, Resources, System, SystemData, WriteExpect}; + +#[derive(Default)] +pub struct SyncGravityToPhysicsSystem; + +impl SyncGravityToPhysicsSystem { + pub fn new() -> Self { + Default::default() + } +} + +impl<'a> System<'a> for SyncGravityToPhysicsSystem { + type SystemData = (WriteExpect<'a, World>, ReadExpect<'a, Gravity>); + + fn run(&mut self, (mut world, gravity): Self::SystemData) { + world.set_gravity(*gravity); + } + + fn setup(&mut self, res: &mut Resources) { + Self::SystemData::setup(res); + + res.entry::<Gravity>() + .or_insert_with(|| Gravity::new(0.0, -9.80665, 0.0)); + + res.entry::<World>().or_insert_with(World::new); + } +}