pub struct ContactManifold<ManifoldData, ContactData> {
pub points: Vec<TrackedContact<ContactData>>,
pub local_n1: Vector<f32>,
pub local_n2: Vector<f32>,
pub subshape1: u32,
pub subshape2: u32,
pub subshape_pos1: Option<Isometry<f32>>,
pub subshape_pos2: Option<Isometry<f32>>,
pub data: ManifoldData,
}Expand description
A contact manifold between two shapes.
A ContactManifold describes a collection of contact points between two shapes that share
the same contact normal and contact kinematics. This is a fundamental data structure for
physics simulation, providing stable and persistent contact information across multiple frames.
§Key Concepts
§What is a Contact Manifold?
Instead of treating each contact point independently, a contact manifold groups together all contact points that share the same properties:
- Same contact normal: All contacts push the shapes apart in the same direction
- Same contact kinematics: All contacts describe the same type of interaction
- Coherent geometry: All contacts belong to the same collision feature pair
For example, when a box sits on a plane, you get a manifold with 4 contact points (one for each corner of the box touching the plane), all sharing the same upward normal.
§Why Use Manifolds?
Contact manifolds are essential for stable physics simulation:
- Stability: Multiple contact points prevent rotation and provide stable support
- Performance: Grouped contacts can be processed more efficiently
- Persistence: Contact tracking across frames enables warm-starting and reduces jitter
- Natural representation: Matches the physical reality of contact patches
§Generic Parameters
ManifoldData: User-defined data associated with the entire manifoldContactData: User-defined data associated with each individual contact point
Both can be () if you don’t need to store additional data.
§Examples
§Basic Usage: Two Balls Colliding
use parry3d::query::{ContactManifold, TrackedContact};
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
// Create two balls
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
// Position them so they overlap
let pos12 = Isometry::translation(1.5, 0.0, 0.0); // Overlapping by 0.5
// Create an empty manifold
let mut manifold = ContactManifold::<(), ()>::new();
// Compute contacts (no prediction distance)
contact_manifold_ball_ball(&pos12, &ball1, &ball2, 0.0, &mut manifold);
// Check the results
assert!(!manifold.points.is_empty());
println!("Number of contacts: {}", manifold.points.len());
println!("Contact normal (local): {:?}", manifold.local_n1);
if let Some(contact) = manifold.points.first() {
println!("Penetration depth: {}", -contact.dist);
}§Contact Prediction
Contact prediction allows detecting contacts before shapes actually touch, which is useful for continuous collision detection:
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
// Balls are separated by 0.1
let pos12 = Isometry::translation(2.1, 0.0, 0.0);
let mut manifold = ContactManifold::<(), ()>::new();
// With prediction distance of 0.2, we can detect the near-contact
let prediction = 0.2;
contact_manifold_ball_ball(&pos12, &ball1, &ball2, prediction, &mut manifold);
if !manifold.points.is_empty() {
let contact = &manifold.points[0];
println!("Predicted contact distance: {}", contact.dist);
assert!(contact.dist > 0.0); // Positive = separated but predicted
}§Efficient Contact Updates with Spatial Coherence
One of the main benefits of contact manifolds is efficient updates:
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let mut manifold = ContactManifold::<(), ()>::new();
// Frame 1: Initial contact
let pos12_frame1 = Isometry::translation(1.9, 0.0, 0.0);
contact_manifold_ball_ball(&pos12_frame1, &ball1, &ball2, 0.1, &mut manifold);
println!("Frame 1: {} contacts", manifold.points.len());
// Frame 2: Small movement - try to update efficiently
let pos12_frame2 = Isometry::translation(1.85, 0.0, 0.0);
if manifold.try_update_contacts(&pos12_frame2) {
println!("Successfully updated contacts using spatial coherence");
} else {
println!("Shapes moved too much, recomputing from scratch");
contact_manifold_ball_ball(&pos12_frame2, &ball1, &ball2, 0.1, &mut manifold);
}§Working with Multiple Contacts
Some shape pairs produce multiple contact points:
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_cuboid_cuboid;
use parry3d::shape::Cuboid;
use parry3d::math::{Isometry, Vector};
// Two boxes
let cuboid1 = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
let cuboid2 = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
// One box sitting on top of another
let pos12 = Isometry::translation(0.0, 1.9, 0.0); // Slight overlap
let mut manifold = ContactManifold::<(), ()>::new();
contact_manifold_cuboid_cuboid(&pos12, &cuboid1, &cuboid2, 0.0, &mut manifold);
println!("Number of contact points: {}", manifold.points.len());
// Find the deepest penetration
if let Some(deepest) = manifold.find_deepest_contact() {
println!("Deepest penetration: {}", -deepest.dist);
}
// Iterate over all contacts
for (i, contact) in manifold.points.iter().enumerate() {
println!("Contact {}: dist={}, fid1={:?}, fid2={:?}",
i, contact.dist, contact.fid1, contact.fid2);
}§Storing Custom Data
You can attach custom data to both the manifold and individual contacts:
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
// Custom data structures
#[derive(Clone, Default, Copy)]
struct MyManifoldData {
collision_id: u32,
first_contact_frame: u32,
}
#[derive(Clone, Default, Copy)]
struct MyContactData {
accumulated_impulse: f32,
contact_age: u32,
}
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let pos12 = Isometry::translation(1.8, 0.0, 0.0);
// Create manifold with custom data
let manifold_data = MyManifoldData {
collision_id: 42,
first_contact_frame: 100,
};
let mut manifold: ContactManifold<MyManifoldData, MyContactData> =
ContactManifold::with_data(0, 0, manifold_data);
contact_manifold_ball_ball(&pos12, &ball1, &ball2, 0.0, &mut manifold);
// Access manifold data
println!("Collision ID: {}", manifold.data.collision_id);
// Set contact-specific data
if let Some(contact) = manifold.points.first_mut() {
contact.data.accumulated_impulse = 10.0;
contact.data.contact_age = 5;
}§Contact Normal Convention
The contact normal (local_n1 and local_n2) points from the first shape toward the
second shape. To separate the shapes:
- Move shape 1 in the direction of
-local_n1 - Move shape 2 in the direction of
local_n2(which equals-local_n1in world space)
§Working with Composite Shapes
When dealing with composite shapes (like triangle meshes or compounds), the manifold tracks which subshapes are involved:
use parry3d::query::ContactManifold;
// For composite shapes, the manifold tracks subshape indices
let manifold = ContactManifold::<(), ()>::with_data(
5, // subshape1: 5th subshape of first shape
12, // subshape2: 12th subshape of second shape
(), // manifold data
);
println!("Contact between subshape {} and {}",
manifold.subshape1, manifold.subshape2);Fields§
§points: Vec<TrackedContact<ContactData>>The contacts points.
local_n1: Vector<f32>The contact normal of all the contacts of this manifold, expressed in the local space of the first shape.
local_n2: Vector<f32>The contact normal of all the contacts of this manifold, expressed in the local space of the second shape.
subshape1: u32The first subshape involved in this contact manifold.
This is zero if the first shape is not a composite shape.
subshape2: u32The second subshape involved in this contact manifold.
This is zero if the second shape is not a composite shape.
subshape_pos1: Option<Isometry<f32>>If the first shape involved is a composite shape, this contains the position of its subshape involved in this contact.
subshape_pos2: Option<Isometry<f32>>If the second shape involved is a composite shape, this contains the position of its subshape involved in this contact.
data: ManifoldDataAdditional tracked data associated to this contact manifold.
Implementations§
Source§impl<ManifoldData, ContactData: Default + Copy> ContactManifold<ManifoldData, ContactData>
impl<ManifoldData, ContactData: Default + Copy> ContactManifold<ManifoldData, ContactData>
Sourcepub fn with_data(subshape1: u32, subshape2: u32, data: ManifoldData) -> Self
pub fn with_data(subshape1: u32, subshape2: u32, data: ManifoldData) -> Self
Create a new empty contact-manifold with the given associated data.
Sourcepub fn take(&mut self) -> Selfwhere
ManifoldData: Clone,
pub fn take(&mut self) -> Selfwhere
ManifoldData: Clone,
Clones self and then remove all contact points from self.
Sourcepub fn contacts(&self) -> &[TrackedContact<ContactData>]
pub fn contacts(&self) -> &[TrackedContact<ContactData>]
Returns a slice of all the contact points in this manifold.
This provides read-only access to all contact points. The contacts are stored in the order they were added during manifold computation.
§Example
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let pos12 = Isometry::translation(1.8, 0.0, 0.0);
let mut manifold = ContactManifold::<(), ()>::new();
contact_manifold_ball_ball(&pos12, &ball1, &ball2, 0.0, &mut manifold);
// Access all contacts
for (i, contact) in manifold.contacts().iter().enumerate() {
println!("Contact {}: distance = {}", i, contact.dist);
}Sourcepub fn try_update_contacts(&mut self, pos12: &Isometry<f32>) -> bool
pub fn try_update_contacts(&mut self, pos12: &Isometry<f32>) -> bool
Attempts to efficiently update contact points using spatial coherence.
This method tries to update the contact points based on the new relative position
of the shapes (pos12) without recomputing the entire contact manifold. This is
much faster than full recomputation but only works when:
- The shapes haven’t moved or rotated too much
- The contact normal hasn’t changed significantly
- The contact configuration is still valid
Returns true if the update succeeded, false if full recomputation is needed.
§When to Use This
Use this method every frame after the initial contact computation. It exploits
temporal coherence in physics simulation where shapes typically move smoothly.
When it returns false, fall back to full contact manifold recomputation.
§Thresholds
This method uses default thresholds for angle and distance changes. For custom
thresholds, use try_update_contacts_eps.
§Example
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let mut manifold = ContactManifold::<(), ()>::new();
// Initial computation
let pos12_old = Isometry::translation(1.9, 0.0, 0.0);
contact_manifold_ball_ball(&pos12_old, &ball1, &ball2, 0.1, &mut manifold);
// Next frame: shapes moved slightly
let pos12_new = Isometry::translation(1.85, 0.05, 0.0);
if manifold.try_update_contacts(&pos12_new) {
println!("Updated contacts efficiently!");
} else {
println!("Need to recompute from scratch");
contact_manifold_ball_ball(&pos12_new, &ball1, &ball2, 0.1, &mut manifold);
}Sourcepub fn try_update_contacts_eps(
&mut self,
pos12: &Isometry<f32>,
angle_dot_threshold: f32,
dist_sq_threshold: f32,
) -> bool
pub fn try_update_contacts_eps( &mut self, pos12: &Isometry<f32>, angle_dot_threshold: f32, dist_sq_threshold: f32, ) -> bool
Attempts to use spatial coherence to update contacts points, using user-defined tolerances.
Sourcepub fn match_contacts(&mut self, old_contacts: &[TrackedContact<ContactData>])
pub fn match_contacts(&mut self, old_contacts: &[TrackedContact<ContactData>])
Transfers contact data from previous frame’s contacts to current contacts based on feature IDs.
This method is crucial for maintaining persistent contact information across frames. It matches contacts between the old and new manifolds by comparing their feature IDs (which geometric features are in contact). When a match is found, the user data is transferred from the old contact to the new one.
This enables important physics features like:
- Warm-starting: Reusing accumulated impulses speeds up constraint solving
- Contact aging: Tracking how long a contact has existed
- Friction state: Maintaining tangential impulse information
§When to Use
Call this method after recomputing the contact manifold, passing the old contact points from the previous frame. This preserves contact-specific solver state.
§Example
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_ball_ball;
use parry3d::shape::Ball;
use parry3d::math::Isometry;
#[derive(Clone, Default, Copy)]
struct MyContactData {
accumulated_impulse: f32,
age: u32,
}
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let mut manifold = ContactManifold::<(), MyContactData>::new();
// Frame 1: Compute contacts
let pos12_frame1 = Isometry::translation(1.9, 0.0, 0.0);
contact_manifold_ball_ball(&pos12_frame1, &ball1, &ball2, 0.0, &mut manifold);
// Simulate physics, accumulate impulse
if let Some(contact) = manifold.points.first_mut() {
contact.data.accumulated_impulse = 42.0;
contact.data.age = 1;
}
// Frame 2: Save old contacts, recompute
let old_contacts = manifold.points.clone();
let pos12_frame2 = Isometry::translation(1.85, 0.0, 0.0);
contact_manifold_ball_ball(&pos12_frame2, &ball1, &ball2, 0.0, &mut manifold);
// Transfer data from old to new based on feature ID matching
manifold.match_contacts(&old_contacts);
// Data is preserved!
if let Some(contact) = manifold.points.first() {
assert_eq!(contact.data.accumulated_impulse, 42.0);
}Sourcepub fn match_contacts_using_positions(
&mut self,
old_contacts: &[TrackedContact<ContactData>],
dist_threshold: f32,
)
pub fn match_contacts_using_positions( &mut self, old_contacts: &[TrackedContact<ContactData>], dist_threshold: f32, )
Copy data associated to contacts from old_contacts to the new contacts in self
based on matching the contact positions.
Sourcepub fn find_deepest_contact(&self) -> Option<&TrackedContact<ContactData>>
pub fn find_deepest_contact(&self) -> Option<&TrackedContact<ContactData>>
Finds and returns the contact with the deepest penetration.
This returns the contact with the smallest (most negative) distance value,
which corresponds to the largest penetration depth. Returns None if the
manifold has no contact points.
§Use Cases
- Finding the primary contact for simplified physics resolution
- Determining the severity of an overlap for collision response
- Prioritizing contacts in contact reduction algorithms
- Debug visualization of the most significant contact
§Example
use parry3d::query::ContactManifold;
use parry3d::query::details::contact_manifold_cuboid_cuboid;
use parry3d::shape::Cuboid;
use parry3d::math::{Isometry, Vector};
let cuboid1 = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
let cuboid2 = Cuboid::new(Vector::new(1.0, 1.0, 1.0));
// Position with some penetration
let pos12 = Isometry::translation(0.0, 1.8, 0.0);
let mut manifold = ContactManifold::<(), ()>::new();
contact_manifold_cuboid_cuboid(&pos12, &cuboid1, &cuboid2, 0.0, &mut manifold);
if let Some(deepest) = manifold.find_deepest_contact() {
let penetration_depth = -deepest.dist;
println!("Maximum penetration: {}", penetration_depth);
println!("Deepest contact point (shape 1): {:?}", deepest.local_p1);
}Trait Implementations§
Source§impl<ManifoldData: Clone, ContactData: Clone> Clone for ContactManifold<ManifoldData, ContactData>
impl<ManifoldData: Clone, ContactData: Clone> Clone for ContactManifold<ManifoldData, ContactData>
Source§fn clone(&self) -> ContactManifold<ManifoldData, ContactData>
fn clone(&self) -> ContactManifold<ManifoldData, ContactData>
1.0.0 · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl<ManifoldData, ContactData> Freeze for ContactManifold<ManifoldData, ContactData>where
ManifoldData: Freeze,
impl<ManifoldData, ContactData> RefUnwindSafe for ContactManifold<ManifoldData, ContactData>where
ManifoldData: RefUnwindSafe,
ContactData: RefUnwindSafe,
impl<ManifoldData, ContactData> Send for ContactManifold<ManifoldData, ContactData>
impl<ManifoldData, ContactData> Sync for ContactManifold<ManifoldData, ContactData>
impl<ManifoldData, ContactData> Unpin for ContactManifold<ManifoldData, ContactData>
impl<ManifoldData, ContactData> UnwindSafe for ContactManifold<ManifoldData, ContactData>where
ManifoldData: UnwindSafe,
ContactData: UnwindSafe,
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be
downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further
downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.Source§impl<T> DowncastSend for T
impl<T> DowncastSend for T
Source§impl<T> DowncastSync for T
impl<T> DowncastSync for T
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
impl<SS, SP> SupersetOf<SS> for SPwhere
SS: SubsetOf<SP>,
Source§fn to_subset(&self) -> Option<SS>
fn to_subset(&self) -> Option<SS>
self from the equivalent element of its
superset. Read moreSource§fn is_in_subset(&self) -> bool
fn is_in_subset(&self) -> bool
self is actually part of its subset T (and can be converted to it).Source§fn to_subset_unchecked(&self) -> SS
fn to_subset_unchecked(&self) -> SS
self.to_subset but without any property checks. Always succeeds.Source§fn from_subset(element: &SS) -> SP
fn from_subset(element: &SS) -> SP
self to the equivalent element of its superset.