commit 697ebb7d51490989586dcee2eb6c75d041bd9424
parent 92b485cc45e09497376eff04ceddf1f47dd6ecfc
Author: Joël Lupien (Jojolepro) <jojolepromain@gmail.com>
Date: Fri, 7 Dec 2018 21:01:42 -0500
Move to Rust 2018 and add Colliders
Diffstat:
12 files changed, 325 insertions(+), 22 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,4 +2,4 @@
**/*.rs.bk
*.iml
/.idea
-cargo.lock-
\ No newline at end of file
+Cargo.lock
diff --git a/Cargo.toml b/Cargo.toml
@@ -8,10 +8,15 @@ edition = "2018"
# default = [ "nphysics3d" ]
[dependencies]
-nphysics3d = "0.9"
-ncollide3d = "0.17"
+nphysics3d = { git = "https://github.com/jojolepro/nphysics", branch = "tmp", features = ["serde"] }
+#ncollide3d = "0.17"
+#ncollide3d = { git = "https://github.com/jojolepro/ncollide", branch = "collidegroup", features = ["serde"] }
+ncollide3d = { git = "https://github.com/jojolepro/ncollide", branch = "collidegroup" }
nalgebra = "0.16"
num-traits = "0.2"
+derive-new = "0.5.6"
+derive_builder = "0.7.0"
+serde = { version = "1.0", features = ["derive"] }
[dependencies.amethyst]
git = "https://github.com/amethyst/amethyst"
diff --git a/src/bodies.rs b/src/bodies.rs
@@ -6,6 +6,7 @@ use nphysics3d::object::BodyHandle;
use std::collections::HashMap;
/// Physics body component for describing (currently) rigid body dynamics.
+#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum DynamicBody {
RigidBody(RigidPhysicsBody),
Multibody(PhysicsMultibody),
@@ -56,7 +57,10 @@ impl Component for DynamicBody {
/// 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.
+#[derive(Serialize, Deserialize, Clone, Debug, new)]
pub struct RigidPhysicsBody {
+ #[serde(skip)]
+ #[new(default)]
pub(crate) handle: Option<BodyHandle>,
pub velocity: Velocity<f32>,
@@ -69,7 +73,9 @@ pub struct RigidPhysicsBody {
}
/// Multipart physics body, for use in `PhysicsBody` Component. Not implemented yet.
+#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PhysicsMultibody {
+ #[serde(skip)]
pub handle: Option<BodyHandle>,
}
diff --git a/src/colliders/collider.rs b/src/colliders/collider.rs
@@ -0,0 +1,67 @@
+
+use ncollide::{
+ shape::ShapeHandle,
+ world::GeometricQueryType,
+};
+use nphysics::object::{Material, ColliderHandle};
+use nalgebra::Isometry3;
+use amethyst::ecs::{Component, FlaggedStorage, DenseVecStorage};
+
+#[derive(Clone, Serialize, Deserialize, Debug, new)]
+pub enum ColliderType {
+ Collider,
+ Trigger,
+}
+
+impl Default for ColliderType {
+ fn default() -> Self {
+ ColliderType::Collider
+ }
+}
+
+impl ColliderType {
+ pub fn to_geometric_query_type(&self, margin: f32, prediction: f32, angular_prediction: f32) -> GeometricQueryType<f32> {
+ match *self {
+ ColliderType::Collider => GeometricQueryType::Contacts(margin + prediction * 0.5, angular_prediction),
+ ColliderType::Trigger => GeometricQueryType::Proximity(prediction * 0.5),
+ }
+ }
+}
+
+#[derive(new, Clone, Builder/*, Serialize, Deserialize*/)]
+#[builder(pattern = "owned")]
+pub struct Collider {
+ #[new(default)]
+ //#[serde(skip)]
+ pub(crate) handle: Option<ColliderHandle>,
+ /// Warning: Changing the margin after inserting the entity will have no effect.
+ pub margin: f32,
+ pub shape: ShapeHandle<f32>,
+ pub offset_from_parent: Isometry3<f32>,
+ pub physics_material: Material<f32>,
+ pub collision_group: u32,
+ pub query_type: ColliderType,
+}
+
+impl From<ShapeHandle<f32>> for ColliderBuilder {
+ fn from(shape: ShapeHandle<f32>) -> ColliderBuilder {
+ ColliderBuilder::default()
+ .margin(0.01)
+ .shape(shape)
+ .offset_from_parent(Isometry3::identity())
+ .query_type(ColliderType::default())
+ }
+}
+
+impl ColliderBuilder {
+ pub fn trigger(mut self) -> Self {
+ // query type = trigger
+ self.query_type = Some(ColliderType::Trigger);
+ self
+ }
+}
+
+
+impl Component for Collider {
+ type Storage = FlaggedStorage<Self, DenseVecStorage<Self>>;
+}
diff --git a/src/colliders/mod.rs b/src/colliders/mod.rs
@@ -0,0 +1,6 @@
+
+mod collider;
+mod sync_colliders_to_physics;
+
+pub use self::collider::*;
+pub use self::sync_colliders_to_physics::*;+
\ No newline at end of file
diff --git a/src/colliders/sync_colliders_to_physics.rs b/src/colliders/sync_colliders_to_physics.rs
@@ -0,0 +1,157 @@
+use crate::bodies::DynamicBody;
+use crate::PhysicsWorld;
+use amethyst::core::GlobalTransform;
+use amethyst::ecs::storage::{ComponentEvent, MaskedStorage, GenericReadStorage};
+use amethyst::ecs::{
+ BitSet, Component, Entities, Join, ReadStorage, ReaderId, Resources, Storage, System,
+ SystemData, Tracked, WriteExpect, WriteStorage,
+};
+use core::ops::Deref;
+use nphysics::object::BodyHandle;
+use crate::Collider;
+
+#[derive(Default, new)]
+pub struct SyncCollidersToPhysicsSystem {
+ #[new(default)]
+ colliders_reader_id: Option<ReaderId<ComponentEvent>>,
+}
+
+impl<'a> System<'a> for SyncCollidersToPhysicsSystem {
+ type SystemData = (
+ WriteExpect<'a, PhysicsWorld>,
+ Entities<'a>,
+ ReadStorage<'a, GlobalTransform>,
+ ReadStorage<'a, DynamicBody>,
+ WriteStorage<'a, Collider>,
+ );
+
+ fn run(&mut self, data: Self::SystemData) {
+ let (mut physical_world, entities, transforms, rigid_bodies, mut colliders) = data;
+ // TODO: Check for inserted/removed rigid_bodies. parent sync https://nphysics.org/rustdoc/nphysics3d/world/struct.World.html?search=#method.add_collider
+ let mut inserted_colliders = BitSet::new();
+ let mut modified_colliders = BitSet::new();
+
+ iterate_events(
+ &transforms,
+ self.colliders_reader_id.as_mut().unwrap(),
+ &mut inserted_colliders,
+ &mut modified_colliders,
+ &mut physical_world,
+ &entities,
+ &colliders,
+ );
+
+ for (entity, mut collider, id) in (
+ &entities,
+ &mut colliders,
+ &inserted_colliders
+ | &modified_colliders,
+ )
+ .join()
+ {
+ if inserted_colliders.contains(id) {
+ println!("Detected inserted collider with id {:?}", id);
+ // Just inserted. Remove old one and insert new.
+ if collider.handle.is_some()
+ && physical_world
+ .collider(collider.handle.unwrap())
+ .is_some()
+ {
+ physical_world.remove_colliders(&[collider.handle.unwrap()]);
+ }
+
+ let parent = if let Some(rb) = rigid_bodies.get(entity) {
+ rb.handle().expect("You should normally have a body handle at this point. This is a bug.")
+ } else {
+ BodyHandle::ground()
+ };
+
+ collider.handle = Some(physical_world.add_collider(
+ collider.margin,
+ collider.shape.clone(),
+ parent,
+ collider.offset_from_parent,
+ collider.physics_material.clone(),
+ ));
+
+ let prediction = physical_world.prediction();
+ let angular_prediction = physical_world.angular_prediction();
+
+ let collision_world = physical_world.collision_world_mut();
+ let collider_handle = collision_world.collision_object(collider.handle.unwrap()).unwrap().handle().clone();
+ collision_world.set_collision_group(collider_handle, collider.collision_group);
+
+ let collider_object = collision_world.collision_object_mut(collider.handle.unwrap()).unwrap();
+
+ collider_object.set_query_type(collider.query_type.to_geometric_query_type(collider.margin, prediction, angular_prediction));
+ } else if modified_colliders.contains(id) || modified_colliders.contains(id) {
+ println!("Detected changed collider with id {:?}", id);
+
+ let prediction = physical_world.prediction();
+ let angular_prediction = physical_world.angular_prediction();
+
+ let collision_world = physical_world.collision_world_mut();
+ let collider_handle = collision_world.collision_object(collider.handle.unwrap()).unwrap().handle().clone();
+
+ collision_world.set_collision_group(collider_handle, collider.collision_group);
+ collision_world.set_shape(collider_handle, collider.shape.clone());
+
+ let collider_object = collision_world.collision_object_mut(collider.handle.unwrap()).unwrap();
+ //collider_handle.set_shape(collider_handle.shape);
+ collider_object.set_position(collider.offset_from_parent.clone());
+ collider_object.set_query_type(collider.query_type.to_geometric_query_type(collider.margin, prediction, angular_prediction));
+ collider_object.data_mut().set_material(collider.physics_material.clone());
+ }
+ }
+
+
+ }
+
+ fn setup(&mut self, res: &mut Resources) {
+ Self::SystemData::setup(res);
+
+ let mut collider_storage: WriteStorage<Collider> = SystemData::fetch(&res);
+ self.colliders_reader_id = Some(collider_storage.register_reader());
+ }
+}
+
+fn iterate_events<'a, T, D, S>(
+ tracked_storage: &Storage<T, D>,
+ reader: &mut ReaderId<ComponentEvent>,
+ inserted: &mut BitSet,
+ modified: &mut BitSet,
+ world: &mut PhysicsWorld,
+ entities: &Entities,
+ colliders: &S
+) where
+ T: Component,
+ T::Storage: Tracked,
+ D: Deref<Target = MaskedStorage<T>>,
+ S: GenericReadStorage<Component=Collider>,
+{
+ let events = tracked_storage.channel().read(reader);
+
+ for event in events {
+ match event {
+ ComponentEvent::Modified(id) => { modified.add(*id); },
+ ComponentEvent::Inserted(id) => { inserted.add(*id); },
+ ComponentEvent::Removed(id) => {
+ match colliders.get(entities.entity(*id)) {
+ Some(collider) => {
+ match collider.handle {
+ Some(handle) => {
+ world.remove_colliders(&[handle]);
+ }
+ None => {
+ // TODO: Log missing handle
+ }
+ };
+ }
+ None => {
+ // TODO: Log missing body
+ }
+ };
+ }
+ };
+ }
+}+
\ No newline at end of file
diff --git a/src/lib.rs b/src/lib.rs
@@ -1,10 +1,26 @@
-extern crate amethyst;
+//! nphysics-ecs-dumb
+//! Straight forward wrapper around nphysics to allow its usage inside of Amethyst's ECS: Specs
+
pub extern crate ncollide3d as ncollide;
pub extern crate nphysics3d as nphysics;
-extern crate num_traits;
+#[macro_use]
+extern crate derive_builder;
+#[macro_use]
+extern crate derive_new;
+#[macro_use]
+extern crate serde;
pub mod bodies;
pub mod systems;
+pub mod colliders;
+
+pub use self::bodies::*;
+pub use self::forces::*;
+pub use self::systems::*;
+pub use self::colliders::*;
-pub type World = self::nphysics::world::World<f32>;
+/// The Physical World containing all physical objects.
+pub type PhysicsWorld = self::nphysics::world::World<f32>;
+/// Gravity is a type alias for a Vector of dimension 3.
+/// It represent a constant acceleration affecting all physical objects in the scene.
pub type Gravity = self::nphysics::math::Vector<f32>;
diff --git a/src/systems/mod.rs b/src/systems/mod.rs
@@ -6,8 +6,9 @@ mod sync_gravity_to_physics;
use amethyst::core::bundle::{Result, SystemBundle};
use amethyst::core::specs::DispatcherBuilder;
+use crate::colliders::SyncCollidersToPhysicsSystem;
pub use self::physics_stepper::PhysicsStepperSystem;
-pub use self::sync_bodies_from_physics::SyncBodiesFromPhysicsSystem;
+pub use self::sync_bodies_from_physics::*;
pub use self::sync_bodies_to_physics::SyncBodiesToPhysicsSystem;
pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem;
@@ -16,6 +17,7 @@ pub use self::sync_gravity_to_physics::SyncGravityToPhysicsSystem;
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 SYNC_COLLIDERS_TO_PHYSICS_SYSTEM: &str = "sync_colliders_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";
@@ -49,11 +51,18 @@ impl<'a, 'b, 'c> SystemBundle<'a, 'b> for PhysicsBundle<'c> {
);
builder.add(
+ SyncCollidersToPhysicsSystem::new(),
+ SYNC_COLLIDERS_TO_PHYSICS_SYSTEM,
+ &[],
+ );
+
+ builder.add(
PhysicsStepperSystem::new(),
PHYSICS_STEPPER_SYSTEM,
&[
SYNC_BODIES_TO_PHYSICS_SYSTEM,
SYNC_GRAVITY_TO_PHYSICS_SYSTEM,
+ SYNC_COLLIDERS_TO_PHYSICS_SYSTEM,
],
);
diff --git a/src/systems/physics_stepper.rs b/src/systems/physics_stepper.rs
@@ -1,4 +1,4 @@
-use crate::World;
+use crate::PhysicsWorld;
use amethyst::core::Time;
use amethyst::ecs::{Read, System, WriteExpect};
@@ -13,7 +13,7 @@ impl PhysicsStepperSystem {
}
impl<'a> System<'a> for PhysicsStepperSystem {
- type SystemData = (WriteExpect<'a, World>, Read<'a, Time>);
+ type SystemData = (WriteExpect<'a, PhysicsWorld>, Read<'a, Time>);
fn run(&mut self, (mut physical_world, time): Self::SystemData) {
// Simulate world using the current time frame
diff --git a/src/systems/sync_bodies_from_physics.rs b/src/systems/sync_bodies_from_physics.rs
@@ -1,12 +1,19 @@
+use nphysics3d::object::ColliderHandle;
+use crate::colliders::Collider;
use crate::bodies::DynamicBody;
-use crate::World;
+use crate::PhysicsWorld;
use amethyst::core::{GlobalTransform, Transform};
-use amethyst::ecs::{Join, ReadStorage, System, Write, ReadExpect, WriteStorage};
+use amethyst::ecs::{Join, ReadStorage, System, Write, ReadExpect, WriteStorage, Entity, Entities};
+use amethyst::ecs::world::EntitiesRes;
use amethyst::shrev::EventChannel;
use nalgebra::Vector3;
-use ncollide3d::events::ContactEvent;
+use ncollide3d::events::{ContactEvent, ProximityEvent};
use nphysics3d::object::Body;
+// Might want to replace by better types.
+pub type EntityContactEvent = (Entity, Entity, ContactEvent);
+pub type EntityProximityEvent = (Entity, Entity, ProximityEvent);
+
#[derive(Default)]
pub struct SyncBodiesFromPhysicsSystem;
@@ -18,20 +25,26 @@ impl SyncBodiesFromPhysicsSystem {
impl<'a> System<'a> for SyncBodiesFromPhysicsSystem {
type SystemData = (
- ReadExpect<'a, World>,
- Write<'a, EventChannel<ContactEvent>>,
+ Entities<'a>,
+ ReadExpect<'a, PhysicsWorld>,
+ Write<'a, EventChannel<EntityContactEvent>>,
+ Write<'a, EventChannel<EntityProximityEvent>>,
WriteStorage<'a, GlobalTransform>,
WriteStorage<'a, DynamicBody>,
ReadStorage<'a, Transform>,
+ ReadStorage<'a, Collider>,
);
fn run(&mut self, data: Self::SystemData) {
let (
+ entities,
physical_world,
- _contact_events,
+ mut contact_events,
+ mut proximity_events,
mut global_transforms,
mut physics_bodies,
local_transforms,
+ colliders,
) = data;
// Apply the updated values of the simulated world to our Components
@@ -85,6 +98,25 @@ impl<'a> System<'a> for SyncBodiesFromPhysicsSystem {
};
}
+ let collision_world = physical_world.collision_world();
+
+ contact_events.iter_write(collision_world.contact_events().iter().cloned().map(|ev| {
+ let (handle1, handle2) = match ev {
+ ContactEvent::Started(h1, h2) => (h1, h2),
+ ContactEvent::Stopped(h1, h2) => (h1, h2),
+ };
+
+ let e1 = entity_from_handle(&entities, &colliders, &handle1).expect("Failed to find entity for collider.");
+ let e2 = entity_from_handle(&entities, &colliders, &handle2).expect("Failed to find entity for collider.");
+ (e1, e2, ev)
+ }));
+
+ proximity_events.iter_write(collision_world.proximity_events().iter().cloned().map(|ev| {
+ let e1 = entity_from_handle(&entities, &colliders, &ev.collider1).expect("Failed to find entity for collider.");
+ let e2 = entity_from_handle(&entities, &colliders, &ev.collider2).expect("Failed to find entity for collider.");
+ (e1, e2, ev)
+ }));
+
// TODO: reader id from other system?
// Now that we changed them all, let's remove all those pesky events that were generated.
// global_transforms
@@ -97,3 +129,7 @@ impl<'a> System<'a> for SyncBodiesFromPhysicsSystem {
// .for_each(|_| ());
}
}
+
+pub fn entity_from_handle(entities: &EntitiesRes, colliders: &ReadStorage<Collider>, handle: &ColliderHandle) -> Option<Entity> {
+ (&*entities, colliders).join().find(|(_, c)| c.handle.expect("Collider has no handle and wasn't removed.") == *handle).map(|(e, _)| e)
+}
diff --git a/src/systems/sync_bodies_to_physics.rs b/src/systems/sync_bodies_to_physics.rs
@@ -1,5 +1,5 @@
use crate::bodies::DynamicBody;
-use crate::World;
+use crate::PhysicsWorld;
use amethyst::core::GlobalTransform;
use amethyst::ecs::storage::{ComponentEvent, MaskedStorage, GenericReadStorage};
use amethyst::ecs::{
@@ -24,7 +24,7 @@ impl SyncBodiesToPhysicsSystem {
impl<'a> System<'a> for SyncBodiesToPhysicsSystem {
type SystemData = (
- WriteExpect<'a, World>,
+ WriteExpect<'a, PhysicsWorld>,
Entities<'a>,
ReadStorage<'a, GlobalTransform>,
WriteStorage<'a, DynamicBody>,
@@ -141,7 +141,7 @@ fn iterate_events<'a, T, D, S>(
reader: &mut ReaderId<ComponentEvent>,
inserted: &mut BitSet,
modified: &mut BitSet,
- world: &mut World,
+ world: &mut PhysicsWorld,
entities: &Entities,
bodies: &S
) where
diff --git a/src/systems/sync_gravity_to_physics.rs b/src/systems/sync_gravity_to_physics.rs
@@ -1,4 +1,4 @@
-use crate::{Gravity, World};
+use crate::{Gravity, PhysicsWorld};
use amethyst::ecs::{ReadExpect, Resources, System, SystemData, WriteExpect};
#[derive(Default)]
@@ -11,7 +11,7 @@ impl SyncGravityToPhysicsSystem {
}
impl<'a> System<'a> for SyncGravityToPhysicsSystem {
- type SystemData = (WriteExpect<'a, World>, ReadExpect<'a, Gravity>);
+ type SystemData = (WriteExpect<'a, PhysicsWorld>, ReadExpect<'a, Gravity>);
fn run(&mut self, (mut world, gravity): Self::SystemData) {
world.set_gravity(*gravity);
@@ -23,6 +23,6 @@ impl<'a> System<'a> for SyncGravityToPhysicsSystem {
res.entry::<Gravity>()
.or_insert_with(|| Gravity::new(0.0, -9.80665, 0.0));
- res.entry::<World>().or_insert_with(World::new);
+ res.entry::<PhysicsWorld>().or_insert_with(PhysicsWorld::new);
}
}