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:
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);
+ }
+}