diff options
| author | Sébastien Crozet <developer@crozet.re> | 2020-10-06 16:53:54 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-10-06 16:53:54 +0200 |
| commit | 24a25f8ae7a62c5c5afa24825b063fbb1b603922 (patch) | |
| tree | 5302f5282fe963b72dbd9e94f422994b6ab11eca | |
| parent | 99f28ba4b4a14254b4160a191cbeb15211cdd2d2 (diff) | |
| parent | 25b8486ebf8bdfa0d165300a30877293e9e40c51 (diff) | |
| download | rapier-24a25f8ae7a62c5c5afa24825b063fbb1b603922.tar.gz rapier-24a25f8ae7a62c5c5afa24825b063fbb1b603922.tar.bz2 rapier-24a25f8ae7a62c5c5afa24825b063fbb1b603922.zip | |
Merge pull request #28 from dimforge/raycast
Add the QueryPipeline for ray-casting and other geometrical queries in the future
37 files changed, 1768 insertions, 694 deletions
diff --git a/.github/workflows/rapier-ci-bench.yml b/.github/workflows/rapier-ci-bench.yml index 89ed247..9cce1b2 100644 --- a/.github/workflows/rapier-ci-bench.yml +++ b/.github/workflows/rapier-ci-bench.yml @@ -9,20 +9,23 @@ on: jobs: send-bench-message: + env: + BENCHBOT_AMQP_USER: ${{ secrets.BENCHBOT_AMQP_USER }} + BENCHBOT_AMQP_PASS: ${{ secrets.BENCHBOT_AMQP_PASS }} + BENCHBOT_AMQP_VHOST: ${{ secrets.BENCHBOT_AMQP_VHOST }} + BENCHBOT_AMQP_HOST: ${{ secrets.BENCHBOT_AMQP_HOST }} + BENCHBOT_TARGET_REPO: ${{ github.repository }} + BENCHBOT_TARGET_COMMIT: ${{ github.event.pull_request.head.sha }} + BENCHBOT_SHA: ${{ github.sha }} + BENCHBOT_HEAD_REF: ${{github.head_ref}} runs-on: ubuntu-latest steps: - - name: Get the latest commit on PR - id: get-latest-commit - uses: ActionsRML/get-PR-latest-commit@v1 + - name: Find commit SHA + if: github.ref == 'refs/heads/master' + run: | + echo "::set-env name=BENCHBOT_TARGET_COMMIT::$BENCHBOT_SHA" - name: Send 3D bench message shell: bash - env: - BENCHBOT_AMQP_USER: ${{ secrets.BENCHBOT_AMQP_USER }} - BENCHBOT_AMQP_PASS: ${{ secrets.BENCHBOT_AMQP_PASS }} - BENCHBOT_AMQP_VHOST: ${{ secrets.BENCHBOT_AMQP_VHOST }} - BENCHBOT_AMQP_HOST: ${{ secrets.BENCHBOT_AMQP_HOST }} - BENCHBOT_TARGET_REPO: ${{ github.repository }} - BENCHBOT_TARGET_COMMIT: ${{ steps.get-latest-commit.outputs.latest_commit_sha }} run: curl -u $BENCHBOT_AMQP_USER:$BENCHBOT_AMQP_PASS -i -H "content-type:application/json" -X POST https://$BENCHBOT_AMQP_HOST/api/exchanges/$BENCHBOT_AMQP_VHOST//publish @@ -1,10 +1,15 @@ ## v0.2.0 - WIP +The most significant change on this version is the addition of the `QueryPipeline` responsible for performing +scene-wide queries. So far only ray-casting has been implemented. -- Add `PhysicsPipeline::remove_collider(...)` to remove a collider from the `ColliderSet`. +- Add `ColliderSet::remove(...)` to remove a collider from the `ColliderSet`. +- Replace `PhysicsPipeline::remove_rigid_body` by `RigidBodySet::remove`. +- The `JointSet.iter()` now returns an iterator yielding `(JointHandle, &Joint)` instead of just `&Joint`. - Add `ColliderDesc::translation(...)` to set the translation of a collider relative to the rigid-body it is attached to. - Add `ColliderDesc::rotation(...)` to set the rotation of a collider relative to the rigid-body it is attached to. - Add `ColliderDesc::position(...)` to set the position of a collider relative to the rigid-body it is attached to. - Add `Collider::position_wrt_parent()` to get the position of a collider relative to the rigid-body it is attached to. +- Modify `RigidBody::set_position(...)` so it also resets the next kinematic position to the same value. - Deprecate `Collider::delta()` in favor of the new `Collider::position_wrt_parent()`. - Fix multiple issues occurring when having colliders resulting in a non-zero center-of-mass. - Fix a crash happening when removing a rigid-body with a collider, stepping the simulation, adding another rigid-body @@ -4,6 +4,7 @@ members = [ "build/rapier2d", "build/rapier_testbed2d", "examples2d", "benchmark [patch.crates-io] #wrapped2d = { git = "https://github.com/Bastacyclop/rust_box2d.git" } +#simba = { path = "../simba" } [profile.release] #debug = true diff --git a/build/rapier2d/Cargo.toml b/build/rapier2d/Cargo.toml index ec3a026..890dfe2 100644 --- a/build/rapier2d/Cargo.toml +++ b/build/rapier2d/Cargo.toml @@ -37,7 +37,7 @@ instant = { version = "0.1", features = [ "now" ]} num-traits = "0.2" nalgebra = "0.22" ncollide2d = "0.24" -simba = "0.2" +simba = "^0.2.1" approx = "0.3" rayon = { version = "1", optional = true } crossbeam = "0.7" diff --git a/build/rapier3d/Cargo.toml b/build/rapier3d/Cargo.toml index dcf5aca..298d655 100644 --- a/build/rapier3d/Cargo.toml +++ b/build/rapier3d/Cargo.toml @@ -37,7 +37,7 @@ instant = { version = "0.1", features = [ "now" ]} num-traits = "0.2" nalgebra = "0.22" ncollide3d = "0.24" -simba = "0.2" +simba = "^0.2.1" approx = "0.3" rayon = { version = "1", optional = true } crossbeam = "0.7" diff --git a/examples2d/add_remove2.rs b/examples2d/add_remove2.rs index 3a32a95..67075fe 100644 --- a/examples2d/add_remove2.rs +++ b/examples2d/add_remove2.rs @@ -28,14 +28,9 @@ pub fn init_world(testbed: &mut Testbed) { .map(|e| e.0) .collect(); for handle in to_remove { - physics.pipeline.remove_rigid_body( - handle, - &mut physics.broad_phase, - &mut physics.narrow_phase, - &mut physics.bodies, - &mut physics.colliders, - &mut physics.joints, - ); + physics + .bodies + .remove(handle, &mut physics.colliders, &mut physics.joints); graphics.remove_body_nodes(window, handle); } }); diff --git a/examples3d/add_remove3.rs b/examples3d/add_remove3.rs index 17aad44..6b58adf 100644 --- a/examples3d/add_remove3.rs +++ b/examples3d/add_remove3.rs @@ -28,14 +28,9 @@ pub fn init_world(testbed: &mut Testbed) { .map(|e| e.0) .collect(); for handle in to_remove { - physics.pipeline.remove_rigid_body( - handle, - &mut physics.broad_phase, - &mut physics.narrow_phase, - &mut physics.bodies, - &mut physics.colliders, - &mut physics.joints, - ); + physics + .bodies + .remove(handle, &mut physics.colliders, &mut physics.joints); graphics.remove_body_nodes(window, handle); } }); diff --git a/examples3d/platform3.rs b/examples3d/platform3.rs index 3836baf..6c3483e 100644 --- a/examples3d/platform3.rs +++ b/examples3d/platform3.rs @@ -64,22 +64,29 @@ pub fn init_world(testbed: &mut Testbed) { /* * Setup a callback to control the platform. */ + let mut count = 0; testbed.add_callback(move |_, physics, _, _, time| { - let mut platform = physics.bodies.get_mut(platform_handle).unwrap(); - let mut next_pos = platform.position; + count += 1; + if count % 100 > 50 { + return; + } - let dt = 0.016; - next_pos.translation.vector.y += (time * 5.0).sin() * dt; - next_pos.translation.vector.z += time.sin() * 5.0 * dt; + if let Some(mut platform) = physics.bodies.get_mut(platform_handle) { + let mut next_pos = platform.position; - if next_pos.translation.vector.z >= rad * 10.0 { - next_pos.translation.vector.z -= dt; - } - if next_pos.translation.vector.z <= -rad * 10.0 { - next_pos.translation.vector.z += dt; - } + let dt = 0.016; + next_pos.translation.vector.y += (time * 5.0).sin() * dt; + next_pos.translation.vector.z += time.sin() * 5.0 * dt; - platform.set_next_kinematic_position(next_pos); + if next_pos.translation.vector.z >= rad * 10.0 { + next_pos.translation.vector.z -= dt; + } + if next_pos.translation.vector.z <= -rad * 10.0 { + next_pos.translation.vector.z += dt; + } + + platform.set_next_kinematic_position(next_pos); + } }); /* diff --git a/src/data/mod.rs b/src/data/mod.rs index 7c00442..5d3efa6 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -2,3 +2,4 @@ pub mod arena; pub(crate) mod graph; +pub mod pubsub; diff --git a/src/data/pubsub.rs b/src/data/pubsub.rs new file mode 100644 index 0000000..b2c9e27 --- /dev/null +++ b/src/data/pubsub.rs @@ -0,0 +1,175 @@ +//! Publish-subscribe mechanism for internal events. + +use std::collections::VecDeque; +use std::marker::PhantomData; + +/// A permanent subscription to a pub-sub queue. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +pub struct Subscription<T> { + // Position on the cursor array. + id: u32, + _phantom: PhantomData<T>, +} + +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +struct PubSubCursor { + // Position on the offset array. + id: u32, + // Index of the next message to read. + // NOTE: Having this here is not actually necessary because + // this value is supposed to be equal to `offsets[self.id]`. + // However, we keep it because it lets us avoid one lookup + // on the `offsets` array inside of message-polling loops + // based on `read_ith`. + next: u32, +} + +impl PubSubCursor { + fn id(&self, num_deleted: u32) -> usize { + (self.id - num_deleted) as usize + } + + fn next(&self, num_deleted: u32) -> usize { + (self.next - num_deleted) as usize + } +} + +/// A pub-sub queue. +#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] +pub struct PubSub<T> { + deleted_messages: u32, + deleted_offsets: u32, + messages: VecDeque<T>, + offsets: VecDeque<u32>, + cursors: Vec<PubSubCursor>, +} + +impl<T> PubSub<T> { + /// Create a new empty pub-sub queue. + pub fn new() -> Self { + Self { + deleted_offsets: 0, + deleted_messages: 0, + messages: VecDeque::new(), + offsets: VecDeque::new(), + cursors: Vec::new(), + } + } + + fn reset_shifts(&mut self) { + for offset in &mut self.offsets { + *offset -= self.deleted_messages; + } + + for cursor in &mut self.cursors { + cursor.id -= self.deleted_offsets; + cursor.next -= self.deleted_messages; + } + + self.deleted_offsets = 0; + self.deleted_messages = 0; + } + + /// Publish a new message. + pub fn publish(&mut self, message: T) { + if self.offsets.is_empty() { + // No subscribers, drop the message. + return; + } + + self.messages.push_back(message); + } + + /// Subscribe to the queue. + /// + /// A subscription cannot be cancelled. + #[must_use] + pub fn subscribe(&mut self) -> Subscription<T> { + let cursor = PubSubCursor { + next: self.messages.len() as u32 + self.deleted_messages, + id: self.offsets.len() as u32 + self.deleted_offsets, + }; + + let subscription = Subscription { + id: self.cursors.len() as u32, + _phantom: PhantomData, + }; + + self.offsets.push_back(cursor.next); + self.cursors.push(cursor); + subscription + } + + /// Read the i-th message not yet read by the given subsciber. + pub fn read_ith(&self, sub: &Subscription<T>, i: usize) -> Option<&T> { + let cursor = &self.cursors[sub.id as usize]; + self.messages + .get(cursor.next(self.deleted_messages) as usize + i) + } + + /// Get the messages not yet read by the given subscriber. + pub fn read(&self, sub: &Subscription<T>) -> impl Iterator<Item = &T> { + let cursor = &self.cursors[sub.id as usize]; + let next = cursor.next(self.deleted_messages); + + // TODO: use self.queue.range(next..) once it is stabilised. + MessageRange { + queue: &self.messages, + next, + } + } + + /// Makes the given subscribe acknowledge all the messages in the queue. + /// + /// A subscriber cannot read acknowledged messages any more. + pub fn ack(&mut self, sub: &Subscription<T>) { + // Update the cursor. + let cursor = &mut self.cursors[sub.id as usize]; + + self.offsets[cursor.id(self.deleted_offsets)] = u32::MAX; + cursor.id = self.offsets.len() as u32 + self.deleted_offsets; + + cursor.next = self.messages.len() as u32 + self.deleted_messages; + self.offsets.push_back(cursor.next); + + // Now clear the messages we don't need to + // maintain in memory anymore. + while self.offsets.front() == Some(&u32::MAX) { + self.offsets.pop_front(); + self.deleted_offsets += 1; + } + + // There must be at least one offset otherwise + // that would mean we have no subscribers. + let next = self.offsets.front().unwrap(); + let num_to_delete = *next - self.deleted_messages; + + for _ in 0..num_to_delete { + self.messages.pop_front(); + } + + self.deleted_messages += num_to_delete; + + if self.deleted_messages > u32::MAX / 2 || self.deleted_offsets > u32::MAX / 2 { + // Don't let the deleted_* shifts grow indefinitely otherwise + // they will end up overflowing, breaking everything. + self.reset_shifts(); + } + } +} + +struct MessageRange<'a, T> { + queue: &'a VecDeque<T>, + next: usize, +} + +impl<'a, T> Iterator for MessageRange<'a, T> { + type Item = &'a T; + + #[inline(always)] + fn next(&mut self) -> Option<&'a T> { + let result = self.queue.get(self.next); + self.next += 1; + result + } +} diff --git a/src/dynamics/integration_parameters.rs b/src/dynamics/integration_parameters.rs index faf22e1..b31c3f6 100644 --- a/src/dynamics/integration_parameters.rs +++ b/src/dynamics/integration_parameters.rs @@ -2,7 +2,7 @@ #[derive(Clone)] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] pub struct IntegrationParameters { - /// The timestep (default: `1.0 / 60.0`) + /// The timestep length (default: `1.0 / 60.0`) dt: f32, /// The inverse of `dt`. inv_dt: f32, diff --git a/src/dynamics/joint/joint_set.rs b/src/dynamics/joint/joint_set.rs index 51d7432..2b1895f 100644 --- a/src/dynamics/joint/joint_set.rs +++ b/src/dynamics/joint/joint_set.rs @@ -66,17 +66,21 @@ impl JointSet { } /// Iterates through all the joint on this set. - pub fn iter(&self) -> impl Iterator<Item = &Joint> { - self.joint_graph.graph.edges.iter().map(|e| &e.weight) + pub fn iter(&self) -> impl Iterator<Item = (JointHandle, &Joint)> { + self.joint_graph + .graph + .edges + .iter() + .map(|e| (e.weight.handle, &e.weight)) } /// Iterates mutably through all the joint on this set. - pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Joint> { + pub fn iter_mut(&mut self) -> impl Iterator<Item = (JointHandle, &mut Joint)> { self.joint_graph .graph .edges .iter_mut() - .map(|e| &mut e.weight) + .map(|e| (e.weight.handle, &mut e.weight)) } // /// The set of joints as an array. @@ -202,8 +206,8 @@ impl JointSet { } // Wake up the attached bodies. - bodies.wake_up(h1); - bodies.wake_up(h2); + bodies.wake_up(h1, true); + bodies.wake_up(h2, true); } if let Some(other) = self.joint_graph.remove_node(deleted_id) { diff --git a/src/dynamics/rigid_body.rs b/src/dynamics/rigid_body.rs index a2fcacc..af1fb4a 100644 --- a/src/dynamics/rigid_body.rs +++ b/src/dynamics/rigid_body.rs @@ -137,6 +137,15 @@ impl RigidBody { crate::utils::inv(self.mass_properties.inv_mass) } + /// The predicted position of this rigid-body. + /// + /// If this rigid-body is kinematic this value is set by the `set_next_kinematic_position` + /// method and is used for estimating the kinematic body velocity at the next timestep. + /// For non-kinematic bodies, this value is currently unspecified. + pub fn predicted_position(&self) -> &Isometry<f32> { + &self.predicted_position + } + /// Adds a collider to this rigid-body. pub(crate) fn add_collider_internal(&mut self, handle: ColliderHandle, coll: &Collider) { let mass_properties = coll @@ -172,10 +181,13 @@ impl RigidBody { } /// Wakes up this rigid body if it is sleeping. - pub fn wake_up(&mut self) { + /// + /// If `strong` is `true` then it is assured that the rigid-body will + /// remain awake during multiple subsequent timesteps. + pub fn wake_up(&mut self, strong: bool) { self.activation.sleeping = false; - if self.activation.energy == 0.0 && self.is_dynamic() { + if (strong || self.activation.energy == 0.0) && self.is_dynamic() { self.activation.energy = self.activation.threshold.abs() * 2.0; } } @@ -189,9 +201,18 @@ impl RigidBody { /// Is this rigid body sleeping? pub fn is_sleeping(&self) -> bool { + // TODO: should we: + // - return false for static bodies. + // - return true for non-sleeping dynamic bodies. + // - return true only for kinematic bodies with non-zero velocity? self.activation.sleeping } + /// Is the velocity of this body not zero? + pub fn is_moving(&self) -> bool { + !self.linvel.is_zero() || !self.angvel.is_zero() + } + fn integrate_velocity(&self, dt: f32) -> Isometry<f32> { let com = &self.position * self.mass_properties.local_com; let shift = Translation::from(com.coords); @@ -201,12 +222,17 @@ impl RigidBody { self.position = self.integrate_velocity(dt) * self.position; } - /// Sets the position of this rigid body. + /// Sets the position and `next_kinematic_position` of this rigid body. + /// + /// This will teleport the rigid-body to the specified position/orientation, + /// completely ignoring any physics rule. If this body is kinematic, this will + /// also set the next kinematic position to the same value, effectively + /// resetting to zero the next interpolated velocity of the kinematic body. pub fn set_position(&mut self, pos: Isometry<f32>) { self.position = pos; // TODO: update the predicted position for dynamic bodies too? - if self.is_static() { + if self.is_static() || self.is_kinematic() { self.predicted_position = pos; } } diff --git a/src/dynamics/rigid_body_set.rs b/src/dynamics/rigid_body_set.rs index 99d6f10..83f1c51 100644 --- a/src/dynamics/rigid_body_set.rs +++ b/src/dynamics/rigid_body_set.rs @@ -2,8 +2,8 @@ use rayon::prelude::*; use crate::data::arena::Arena; -use crate::dynamics::{Joint, RigidBody}; -use crate::geometry::{ColliderSet, ContactPair, InteractionGraph}; +use crate::dynamics::{BodyStatus, Joint, JointSet, RigidBody}; +use crate::geometry::{ColliderHandle, ColliderSet, ContactPair, InteractionGraph}; use crossbeam::channel::{Receiver, Sender}; use std::ops::{Deref, DerefMut, Index, IndexMut}; @@ -128,9 +128,23 @@ impl RigidBodySet { pub(crate) fn activate(&mut self, handle: RigidBodyHandle) { let mut rb = &mut self.bodies[handle]; - if self.active_dynamic_set.get(rb.active_set_id) != Some(&handle) { - rb.active_set_id = self.active_dynamic_set.len(); - self.active_dynamic_set.push(handle); + match rb.body_status { + // XXX: this case should only concern the dynamic bodies. + // For static bodies we should use the modified_inactive_set, or something + // similar. Right now we do this for static bodies as well so the broad-phase + // takes them into account the first time they are inserted. + BodyStatus::Dynamic | BodyStatus::Static => { + if self.active_dynamic_set.get(rb.active_set_id) != Some(&handle) { + rb.active_set_id = self.active_dynamic_set.len(); + self.active_dynamic_set.push(handle); + } + } + BodyStatus::Kinematic => { + if self.active_kinematic_set.get(rb.active_set_id) != Some(&handle) { + rb.active_set_id = self.active_kinematic_set.len(); + self.active_kinematic_set.push(handle); + } + } } } @@ -143,13 +157,14 @@ impl RigidBodySet { pub fn insert(&mut self, rb: RigidBody) -> RigidBodyHandle { let handle = self.bodies.insert(rb); let rb = &mut self.bodies[handle]; - rb.active_set_id = self.active_dynamic_set.len(); if !rb.is_sleeping() && rb.is_dynamic() { + rb.active_set_id = self.active_dynamic_set.len(); self.active_dynamic_set.push(handle); } if rb.is_kinematic() { + rb.active_set_id = self.active_kinematic_set.len(); self.active_kinematic_set.push(handle); } @@ -160,29 +175,57 @@ impl RigidBodySet { handle } - pub(crate) fn num_islands(&self) -> usize { - self.active_islands.len() - 1 - } - - pub(crate) fn remove_internal(&mut self, handle: RigidBodyHandle) -> Option<RigidBody> { + /// Removes a rigid-body, and all its attached colliders and joints, from these sets. + pub fn remove( + &mut self, + handle: RigidBodyHandle, + colliders: &mut ColliderSet, + joints: &mut JointSet, + ) -> Option<RigidBody> { let rb = self.bodies.remove(handle)?; - // Remove this body from the active dynamic set. - if self.active_dynamic_set.get(rb.active_set_id) == Some(&handle) { - self.active_dynamic_set.swap_remove(rb.active_set_id); + /* + * Update active sets. + */ + let mut active_sets = [&mut self.active_kinematic_set, &mut self.active_dynamic_set]; + + for active_set in &mut active_sets { + if active_set.get(rb.active_set_id) == Some(&handle) { + active_set.swap_remove(rb.active_set_id); - if let Some(replacement) = self.active_dynamic_set.get(rb.active_set_id) { - self.bodies[*replacement].active_set_id = rb.active_set_id; + if let Some(replacement) = active_set.get(rb.active_set_id) { + self.bodies[*replacement].active_set_id = rb.active_set_id; + } } } + /* + * Remove colliders attached to this rigid-body. + */ + for collider in &rb.colliders { + colliders.remove(*collider, self); + } + + /* + * Remove joints attached to this rigid-body. + */ + joints.remove_rigid_body(rb.joint_graph_index, self); + Some(rb) } + pub(crate) fn num_islands(&self) -> usize { + self.active_islands.len() - 1 + } + /// Forces the specified rigid-body to wake up if it is dynamic. - pub fn wake_up(&mut self, handle: RigidBodyHandle) { + /// + /// If `strong` is `true` then it is assured that the rigid-body will + /// remain awake during multiple subsequent timesteps. + pub fn wake_up(&mut self, handle: RigidBodyHandle, strong: bool) { if let Some(rb) = self.bodies.get_mut(handle) { + // TODO: what about kinematic bodies? if rb.is_dynamic() { - rb.wake_up(); + rb.wake_up(strong); if self.active_dynamic_set.get(rb.active_set_id) != Some(&handle) { rb.active_set_id = self.active_dynamic_set.len(); @@ -214,8 +257,11 @@ impl RigidBodySet { /// /// Using this is discouraged in favor of `self.get_mut(handle)` which does not /// suffer form the ABA problem. - pub fn get_unknown_gen_mut(&mut self, i: usize) -> Option<(&mut RigidBody, RigidBodyHandle)> { - self.bodies.get_unknown_gen_mut(i) + pub fn get_unknown_gen_mut(&mut self, i: usize) -> Option<(RigidBodyMut, RigidBodyHandle)> { + let sender = &self.activation_channel.0; + self.bodies + .get_unknown_gen_mut(i) + .map(|(rb, handle)| (RigidBodyMut::new(handle, rb, sender), handle)) } /// Gets the rigid-body with the given handle. @@ -256,6 +302,16 @@ impl RigidBodySet { .map(move |(h, rb)| (h, RigidBodyMut::new(h, rb, sender))) } + /// Iter through all the active kinematic rigid-bodies on this set. + pub fn iter_active_kinematic<'a>( + &'a self, + ) -> impl Iterator<Item = (RigidBodyHandle, &'a RigidBody)> { + let bodies: &'a _ = &self.bodies; + self.active_kinematic_set + .iter() + .filter_map(move |h| Some((*h, bodies.get(*h)?))) + } + /// Iter through all the active dynamic rigid-bodies on this set. pub fn iter_active_dynamic<'a>( &'a self, @@ -425,6 +481,45 @@ impl RigidBodySet { } } + // Read all the contacts and push objects touching touching this rigid-body. + #[inline(always)] + fn push_contacting_colliders( + rb: &RigidBody, + collid |
