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 76db00ebed59ba7879b8b454c8cd481bff82f9fd
Author: kel <distransient@protonmail.com>
Date:   Sun, 25 Nov 2018 00:15:03 -0500

initial commit, prototype

Diffstat:
A.gitignore | 5+++++
ACargo.toml | 14++++++++++++++
AREADME.md | 4++++
Aresources/display.ron | 11+++++++++++
Asrc/lib.rs | 316+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 350 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +*.iml +cargo.lock+ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nphysics-ecs-dumb" +version = "0.1.0" +authors = ["kel <distransient@protonmail.com>"] +edition = "2018" + +#[features] +# default = [ "nphysics3d" ] + +[dependencies] +amethyst = { git = "https://github.com/amethyst/amethyst", branch = "master" } +nphysics3d = "0.9" +ncollide3d = "0.17" +nalgebra = "0.16" diff --git a/README.md b/README.md @@ -0,0 +1,3 @@ +# nphysics DUMB + +Don't use. Work in progress. Many things are incomplete!+ \ No newline at end of file diff --git a/resources/display.ron b/resources/display.ron @@ -0,0 +1,10 @@ +( + dimensions: None, + max_dimensions: None, + min_dimensions: None, + fullscreen: false, + multisampling: 0, + title: "Sphere example", + visibility: true, + vsync: true, +)+ \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs @@ -0,0 +1,315 @@ +extern crate amethyst; +pub extern crate nphysics3d as nphysics; +pub extern crate ncollide3d as ncollide; + +use self::ncollide::events::ContactEvent; +use self::nphysics::math::{Inertia, Point, Velocity}; +use self::nphysics::object::{Body, BodyHandle, RigidBody}; +use amethyst::ecs::*; +use amethyst::ecs::prelude::*; +use amethyst::core::{GlobalTransform, Time}; +use amethyst::core::nalgebra::try_convert; +use amethyst::core::nalgebra::base::Matrix3; +use amethyst::shrev::EventChannel; + +/// Physics body component for describing (currently) rigid body dynamics. +pub enum PhysicsBody { + RigidBody(RigidPhysicsBody), + Multibody(PhysicsMultibody), + Ground(PhysicsGround), +} + +impl Component for PhysicsBody { + type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>; +} + +/// Rigid physics body, for use in `PhysicsBody` Component. +/// 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 { + handle: Option<BodyHandle>, + pub velocity: Velocity<f32>, + + // TODO: update these in the physics system below. + pub mass: f32, + pub angular_mass: Matrix3<f32>, + pub center_of_mass: Point<f32>, +} + +/// Multipart physics body, for use in `PhysicsBody` Component. Not implemented yet. +pub struct PhysicsMultibody { + handle: Option<BodyHandle>, +} + +/// Nphysics ground physics body, for use in `PhysicsBody` Not implemented yet. +pub struct PhysicsGround { + handle: Option<BodyHandle>, +} + +impl PhysicsBody { + fn handle(&self) -> Option<BodyHandle> { + match self { + PhysicsBody::RigidBody(x) => x.handle, + PhysicsBody::Multibody(x) => x.handle, + PhysicsBody::Ground(x) => x.handle, + } + } +} + +/// Iterates over entities with both a GlobalTransform and a PhysicsBody, +/// and synchronizes their state to an nphysics world simulation, +/// stepping the simulation each frame. +#[derive(Default)] +pub struct Dumb3dPhysicsSystem { + 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>>, +} + +impl <'a> System<'a> for Dumb3dPhysicsSystem { + type SystemData = ( + WriteExpect<'a, self::nphysics::world::World<f32>>, + Write<'a, EventChannel<ContactEvent>>, + Read<'a, Time>, + Entities<'a>, + WriteStorage<'a, GlobalTransform>, + WriteStorage<'a, PhysicsBody>, + ); + + fn run(&mut self, data: Self::SystemData) { + let ( + mut physical_world, + mut contact_events, + time, + entities, + mut transforms, + mut physics_bodies, + ) = 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); }, + 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, mut transform, mut body, id) in ( + &entities, + &mut 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) { + match body { + PhysicsBody::RigidBody(ref mut rigid_body) => { + 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 mut physical_body = physical_world.rigid_body_mut( + rigid_body.handle.unwrap()).unwrap(); + + physical_body.set_velocity(rigid_body.velocity); + }, + PhysicsBody::Multibody(x) => { + // TODO + }, + PhysicsBody::Ground(x) => { + // TODO + }, + } + } else if self.modified_transforms.contains(id) + || self.modified_physics_bodies.contains(id) { + match body { + PhysicsBody::RigidBody(ref mut rigid_body) => { + let mut 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! + }, + PhysicsBody::Multibody(x) => { + // TODO + }, + PhysicsBody::Ground(x) => { + // TODO + }, + } + } + } + + // 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) in (&mut transforms,&mut physics_bodies).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) { + (PhysicsBody::RigidBody(ref mut rigid_body), Body::RigidBody(ref updated_rigid_body)) => { + updated_rigid_body.position(); + 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(); + }, + (PhysicsBody::Multibody(multibody), Body::Multibody(updated_multibody)) => { + match updated_multibody.links().next() { + Some(link) => link.position(), + None => continue, + }; + } + (PhysicsBody::Ground(ground), Body::Ground(updated_ground)) => { + updated_ground.position(); + } + _ => {} + }; + } + } + + // TODO: resources need set up here. Including initializing the physics world. + 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<PhysicsBody> = SystemData::fetch(&res); + self.physics_bodies_reader_id = Some(physics_body_storage.register_reader()); + } +} + +#[cfg(test)] +mod tests { + use amethyst::{GameData, StateData, SimpleState, GameDataBuilder, Application}; + use amethyst::prelude::Builder; + use amethyst::renderer::{PosNormTex, PointLight, Rgba, AmbientColor, DrawShaded, Shape, ScreenDimensions, Light, Camera, Texture}; + use amethyst::core::Transform; + use amethyst::core::transform::bundle::TransformBundle; + use amethyst::core::nalgebra::Vector3; + use super::Dumb3dPhysicsSystem; + + struct GameState; + + impl<'a, 'b> SimpleState<'a, 'b> for GameState { + fn on_start(&mut self, data: StateData<GameData>) { + // Create a texture for using. + let texture = data.world.read_resource::<amethyst::assets::Loader>() + .load_from_data::<Texture, ()>( + [144.0, 144.0, 144.0, 0.5].into(), + (), + &data.world.read_resource()); + + // Get resolution of the screen. + let (x, y) = { + let resolution = data.world.res.fetch::<ScreenDimensions>(); + (resolution.width(), resolution.height()) + }; + + // Add Camera + data.world.create_entity() + .with(Camera::standard_3d(x, y)) + .with(Transform::from(Vector3::new(0.0, 0.0, -4.0))); + + // Add Light + data.world.add_resource(AmbientColor(Rgba::from([0.01; 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(); + + // Add Sphere (todo: add many, add rigidbodies and colliders) + data.world.create_entity() + .with(Shape::Sphere(32, 32).generate::<Vec<PosNormTex>>(None)) + .with(texture) + .build(); + } + } + + #[test] + fn app() -> amethyst::Result<()> { + amethyst::start_logger(Default::default()); + + let game_data = GameDataBuilder::default() + .with_basic_renderer( + "./resources/display.ron", + DrawShaded::<PosNormTex>::new(), + false)? + .with_bundle(TransformBundle::new())? + .with(Dumb3dPhysicsSystem::default(), "physics", &[]); + + let mut application + = Application::new("./", GameState, game_data,); + + assert_eq!(application.is_ok(), true); + + application.ok().unwrap().run(); + + Ok(()) + } +}+ \ No newline at end of file