aboutsummaryrefslogtreecommitdiff
path: root/src/pipeline
diff options
context:
space:
mode:
authorSébastien Crozet <developer@crozet.re>2022-06-24 19:00:34 +0200
committerSébastien Crozet <developer@crozet.re>2022-07-01 12:00:32 +0200
commitc9d8277377681a6c5162abe4e8f17a058eebcfd4 (patch)
treeb5c01635c6530ddcbb1453e8f9b29e3bc9a50910 /src/pipeline
parentd6b61898612d05e12b52d9636e9bb21dccdca4bb (diff)
downloadrapier-c9d8277377681a6c5162abe4e8f17a058eebcfd4.tar.gz
rapier-c9d8277377681a6c5162abe4e8f17a058eebcfd4.tar.bz2
rapier-c9d8277377681a6c5162abe4e8f17a058eebcfd4.zip
Add contact force events generated above a user-defined threshold
Diffstat (limited to 'src/pipeline')
-rw-r--r--src/pipeline/event_handler.rs91
-rw-r--r--src/pipeline/physics_pipeline.rs47
2 files changed, 126 insertions, 12 deletions
diff --git a/src/pipeline/event_handler.rs b/src/pipeline/event_handler.rs
index 24b415c..55ede93 100644
--- a/src/pipeline/event_handler.rs
+++ b/src/pipeline/event_handler.rs
@@ -1,5 +1,6 @@
use crate::dynamics::RigidBodySet;
-use crate::geometry::{ColliderSet, CollisionEvent, ContactPair};
+use crate::geometry::{ColliderSet, CollisionEvent, CollisionForceEvent, ContactPair};
+use crate::math::Real;
use crossbeam::channel::Sender;
bitflags::bitflags! {
@@ -25,6 +26,8 @@ pub trait EventHandler: Send + Sync {
/// Handle a collision event.
///
/// A collision event is emitted when the state of intersection between two colliders changes.
+ /// At least one of the involved colliders must have the `ActiveEvents::COLLISION_EVENTS` flag
+ /// set.
///
/// # Parameters
/// * `event` - The collision event.
@@ -40,6 +43,26 @@ pub trait EventHandler: Send + Sync {
event: CollisionEvent,
contact_pair: Option<&ContactPair>,
);
+
+ /// Handle a force event.
+ ///
+ /// A force event is generated whenever the total force magnitude applied between two
+ /// colliders is `> Collider::contact_force_event_threshold` value of any of these
+ /// colliders.
+ ///
+ /// The "total force magnitude" here means "the sum of the magnitudes of the forces applied at
+ /// all the contact points in a contact pair". Therefore, if the contact pair involves two
+ /// forces `{0.0, 1.0, 0.0}` and `{0.0, -1.0, 0.0}`, then the total force magnitude tested
+ /// against the `contact_force_event_threshold` is `2.0` even if the sum of these forces is actually the
+ /// zero vector.
+ fn handle_contact_force_event(
+ &self,
+ dt: Real,
+ bodies: &RigidBodySet,
+ colliders: &ColliderSet,
+ contact_pair: &ContactPair,
+ total_force_magnitude: Real,
+ );
}
impl EventHandler for () {
@@ -51,17 +74,34 @@ impl EventHandler for () {
_contact_pair: Option<&ContactPair>,
) {
}
+
+ fn handle_contact_force_event(
+ &self,
+ _dt: Real,
+ _bodies: &RigidBodySet,
+ _colliders: &ColliderSet,
+ _contact_pair: &ContactPair,
+ _total_force_magnitude: Real,
+ ) {
+ }
}
/// A collision event handler that collects events into a crossbeam channel.
pub struct ChannelEventCollector {
- event_sender: Sender<CollisionEvent>,
+ collision_event_sender: Sender<CollisionEvent>,
+ contact_force_event_sender: Sender<CollisionForceEvent>,
}
impl ChannelEventCollector {
/// Initialize a new collision event handler from crossbeam channel senders.
- pub fn new(event_sender: Sender<CollisionEvent>) -> Self {
- Self { event_sender }
+ pub fn new(
+ collision_event_sender: Sender<CollisionEvent>,
+ contact_force_event_sender: Sender<CollisionForceEvent>,
+ ) -> Self {
+ Self {
+ collision_event_sender,
+ contact_force_event_sender,
+ }
}
}
@@ -73,6 +113,47 @@ impl EventHandler for ChannelEventCollector {
event: CollisionEvent,
_: Option<&ContactPair>,
) {
- let _ = self.event_sender.send(event);
+ let _ = self.collision_event_sender.send(event);
+ }
+
+ fn handle_contact_force_event(
+ &self,
+ dt: Real,
+ _bodies: &RigidBodySet,
+ _colliders: &ColliderSet,
+ contact_pair: &ContactPair,
+ total_force_magnitude: Real,
+ ) {
+ let mut result = CollisionForceEvent {
+ collider1: contact_pair.collider1,
+ collider2: contact_pair.collider2,
+ total_force_magnitude,
+ ..CollisionForceEvent::default()
+ };
+
+ for m in &contact_pair.manifolds {
+ let mut total_manifold_impulse = 0.0;
+ for pt in m.contacts() {
+ total_manifold_impulse += pt.data.impulse;
+
+ if pt.data.impulse > result.max_force_magnitude {
+ result.max_force_magnitude = pt.data.impulse;
+ result.max_force_direction = m.data.normal;
+ }
+ }
+
+ result.total_force += m.data.normal * total_manifold_impulse;
+ }
+
+ let inv_dt = crate::utils::inv(dt);
+ // NOTE: convert impulses to forces. Note that we
+ // don’t need to convert the `total_force_magnitude`
+ // because it’s an input of this function already
+ // assumed to be a force instead of an impulse.
+ result.total_force *= inv_dt;
+ result.max_force_direction *= inv_dt;
+ result.max_force_magnitude *= inv_dt;
+
+ let _ = self.contact_force_event_sender.send(result);
}
}
diff --git a/src/pipeline/physics_pipeline.rs b/src/pipeline/physics_pipeline.rs
index 16f2830..ea0f239 100644
--- a/src/pipeline/physics_pipeline.rs
+++ b/src/pipeline/physics_pipeline.rs
@@ -11,10 +11,10 @@ use crate::dynamics::{
use crate::dynamics::{JointGraphEdge, ParallelIslandSolver as IslandSolver};
use crate::geometry::{
BroadPhase, BroadPhasePairEvent, ColliderChanges, ColliderHandle, ColliderPair,
- ContactManifoldIndex, NarrowPhase,
+ ContactManifoldIndex, NarrowPhase, TemporaryInteractionIndex,
};
use crate::math::{Real, Vector};
-use crate::pipeline::{EventHandler, PhysicsHooks};
+use crate::pipeline::{ActiveEvents, EventHandler, PhysicsHooks};
use {crate::dynamics::RigidBodySet, crate::geometry::ColliderSet};
/// The physics pipeline, responsible for stepping the whole physics simulation.
@@ -31,6 +31,7 @@ use {crate::dynamics::RigidBodySet, crate::geometry::ColliderSet};
pub struct PhysicsPipeline {
/// Counters used for benchmarking only.
pub counters: Counters,
+ contact_pair_indices: Vec<TemporaryInteractionIndex>,
manifold_indices: Vec<Vec<ContactManifoldIndex>>,
joint_constraint_indices: Vec<Vec<ContactManifoldIndex>>,
broadphase_collider_pairs: Vec<ColliderPair>,
@@ -55,11 +56,12 @@ impl PhysicsPipeline {
pub fn new() -> PhysicsPipeline {
PhysicsPipeline {
counters: Counters::new(true),
- solvers: Vec::new(),
- manifold_indices: Vec::new(),
- joint_constraint_indices: Vec::new(),
- broadphase_collider_pairs: Vec::new(),
- broad_phase_events: Vec::new(),
+ solvers: vec![],
+ contact_pair_indices: vec![],
+ manifold_indices: vec![],
+ joint_constraint_indices: vec![],
+ broadphase_collider_pairs: vec![],
+ broad_phase_events: vec![],
}
}
@@ -148,6 +150,7 @@ impl PhysicsPipeline {
colliders: &mut ColliderSet,
impulse_joints: &mut ImpulseJointSet,
multibody_joints: &mut MultibodyJointSet,
+ events: &dyn EventHandler,
) {
self.counters.stages.island_construction_time.resume();
islands.update_active_set_with_contacts(
@@ -175,6 +178,7 @@ impl PhysicsPipeline {
narrow_phase.select_active_contacts(
islands,
bodies,
+ &mut self.contact_pair_indices,
&mut manifolds,
&mut self.manifold_indices,
);
@@ -275,6 +279,34 @@ impl PhysicsPipeline {
});
});
}
+
+ // Generate contact force events if needed.
+ let inv_dt = crate::utils::inv(integration_parameters.dt);
+ for pair_id in self.contact_pair_indices.drain(..) {
+ let pair = narrow_phase.contact_pair_at_index(pair_id);
+ let co1 = &colliders[pair.collider1];
+ let co2 = &colliders[pair.collider2];
+ let threshold = co1
+ .contact_force_event_threshold
+ .min(co2.contact_force_event_threshold);
+
+ if threshold < Real::MAX {
+ let total_magnitude = pair.total_impulse_magnitude() * inv_dt;
+
+ // NOTE: the strict inequality is important here, so we don’t
+ // trigger an event if the force is 0.0 and the threshold is 0.0.
+ if total_magnitude > threshold {
+ events.handle_contact_force_event(
+ integration_parameters.dt,
+ bodies,
+ colliders,
+ pair,
+ total_magnitude,
+ );
+ }
+ }
+ }
+
self.counters.stages.solver_time.pause();
}
@@ -507,6 +539,7 @@ impl PhysicsPipeline {
colliders,
impulse_joints,
multibody_joints,
+ events,
);
// If CCD is enabled, execute the CCD motion clamping.