pub struct EPA { /* private fields */ }Expand description
The Expanding Polytope Algorithm in 3D.
This structure computes the penetration depth between two shapes when they are overlapping. It’s used after GJK (Gilbert-Johnson-Keerthi) determines that two shapes are penetrating.
§What does EPA do?
EPA finds:
- The penetration depth: How far the shapes are overlapping
- The contact normal: The direction to separate the shapes
- The contact points: Where the shapes are touching on each surface
§How it works in 3D
In 3D, EPA maintains a convex polyhedron (made of triangular faces) in the Minkowski difference space (CSO - Configuration Space Obstacle) that encloses the origin. It iteratively:
- Finds the triangular face closest to the origin
- Expands the polyhedron by adding a new support point in the direction of that face’s normal
- Removes faces that can be “seen” from the new point (they’re now inside)
- Creates new faces connecting the boundary edges (silhouette) to the new point
- Repeats until the polyhedron cannot expand further (convergence)
The final closest face provides the penetration depth (distance to origin) and contact normal.
§Example
use parry3d::query::epa::EPA;
use parry3d::query::gjk::VoronoiSimplex;
use parry3d::shape::Ball;
use parry3d::na::Isometry3;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let pos12 = Isometry3::translation(1.5, 0.0, 0.0); // Overlapping spheres
// After GJK determines penetration and fills a simplex:
let mut epa = EPA::new();
let simplex = VoronoiSimplex::new(); // Would be filled by GJK
// EPA computes the contact details
// if let Some((pt1, pt2, normal)) = epa.closest_points(&pos12, &ball1, &ball2, &simplex) {
// println!("Penetration depth: {}", (pt2 - pt1).norm());
// println!("Contact normal: {}", normal);
// }§Reusability
The EPA structure can be reused across multiple queries to avoid allocations.
Internal buffers are cleared and reused in each call to closest_points.
§Convergence and Failure Cases
EPA may return None in these situations:
- The shapes are not actually penetrating (GJK should be used instead)
- Degenerate or nearly-degenerate geometry causes numerical instability
- The initial simplex from GJK is invalid
- The algorithm fails to converge after 100 iterations
- Silhouette extraction fails (topology issues)
When None is returned, the shapes may be touching at a single point, edge, or face,
or there may be numerical precision issues with the input geometry.
§Complexity
The 3D EPA implementation is more complex than 2D because:
- It maintains a 3D mesh topology with face adjacency information
- It needs to compute silhouettes (visible edges from a point)
- It handles more degenerate cases (coplanar faces, edge cases)
Implementations§
Source§impl EPA
impl EPA
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new instance of the 3D Expanding Polytope Algorithm.
This allocates internal data structures (vertices, faces, silhouette buffer, and a priority heap).
The same EPA instance can be reused for multiple queries to avoid repeated allocations.
§Example
use parry3d::query::epa::EPA;
let mut epa = EPA::new();
// Use epa for multiple queries...Sourcepub fn project_origin<G: ?Sized + SupportMap>(
&mut self,
m: &Isometry<f32>,
g: &G,
simplex: &VoronoiSimplex,
) -> Option<Point<f32>>
pub fn project_origin<G: ?Sized + SupportMap>( &mut self, m: &Isometry<f32>, g: &G, simplex: &VoronoiSimplex, ) -> Option<Point<f32>>
Projects the origin onto the boundary of the given shape.
This is a specialized version of closest_points for projecting
a point (the origin) onto a single shape’s surface.
§Parameters
m: The position and orientation of the shape in world spaceg: The shape to project onto (must implementSupportMap)simplex: A Voronoi simplex from GJK that encloses the origin (indicating the origin is inside the shape)
§Returns
Some(point): The closest point on the shape’s boundary to the origin, in the local space ofgNone: If the origin is not inside the shape, or if EPA failed to converge
§Prerequisites
The origin must be inside the shape. If it’s outside, use GJK instead. Typically, you would:
- Run GJK to detect if a point is inside a shape
- If inside, use this method to find the closest boundary point
§Example
use parry3d::query::epa::EPA;
use parry3d::query::gjk::VoronoiSimplex;
use parry3d::shape::Ball;
use parry3d::na::Isometry3;
let ball = Ball::new(2.0);
let pos = Isometry3::<f32>::identity();
// Assume GJK determined the origin is inside and filled simplex
let simplex = VoronoiSimplex::new();
let mut epa = EPA::new();
// Find the closest point on the ball's surface to the origin
// if let Some(surface_point) = epa.project_origin(&pos, &ball, &simplex) {
// println!("Closest surface point: {:?}", surface_point);
// }Sourcepub fn closest_points<G1, G2>(
&mut self,
pos12: &Isometry<f32>,
g1: &G1,
g2: &G2,
simplex: &VoronoiSimplex,
) -> Option<(Point<f32>, Point<f32>, Unit<Vector<f32>>)>
pub fn closest_points<G1, G2>( &mut self, pos12: &Isometry<f32>, g1: &G1, g2: &G2, simplex: &VoronoiSimplex, ) -> Option<(Point<f32>, Point<f32>, Unit<Vector<f32>>)>
Computes the closest points between two penetrating shapes and their contact normal.
This is the main EPA method that computes detailed contact information for overlapping shapes. It should be called after GJK determines that two shapes are penetrating.
§Parameters
pos12: The relative position/orientation fromg2’s frame tog1’s frame (typically computed aspos1.inverse() * pos2)g1: The first shape (must implementSupportMap)g2: The second shape (must implementSupportMap)simplex: A Voronoi simplex from GJK that encloses the origin, indicating penetration
§Returns
Returns Some((point1, point2, normal)) where:
point1: Contact point on shapeg1ing1’s local framepoint2: Contact point on shapeg2ing2’s local framenormal: Contact normal pointing fromg2towardg1, normalized
The penetration depth can be computed as (point1 - point2).norm() after transforming
both points to the same coordinate frame.
Returns None if:
- The shapes are not actually penetrating
- EPA fails to converge (degenerate geometry, numerical issues)
- The simplex is invalid or empty
- The algorithm reaches the maximum iteration limit (100 iterations)
- Silhouette extraction fails (indicates topology corruption)
§Prerequisites
The shapes must be penetrating. The typical workflow is:
- Run GJK to check if shapes intersect
- If GJK detects penetration and returns a simplex enclosing the origin
- Use EPA with that simplex to compute detailed contact information
§Example
use parry3d::query::epa::EPA;
use parry3d::query::gjk::{GJKResult, VoronoiSimplex};
use parry3d::shape::Ball;
use parry3d::na::Isometry3;
let ball1 = Ball::new(1.0);
let ball2 = Ball::new(1.0);
let pos1 = Isometry3::identity();
let pos2 = Isometry3::translation(1.5, 0.0, 0.0); // Overlapping
let pos12 = pos1.inverse() * pos2;
// After GJK detects penetration:
let mut simplex = VoronoiSimplex::new();
// ... simplex would be filled by GJK ...
let mut epa = EPA::new();
// if let Some((pt1, pt2, normal)) = epa.closest_points(&pos12, &ball1, &ball2, &simplex) {
// println!("Contact on shape 1: {:?}", pt1);
// println!("Contact on shape 2: {:?}", pt2);
// println!("Contact normal: {}", normal);
// println!("Penetration depth: ~0.5");
// }§Technical Details
The algorithm works in the Minkowski difference space (also called the Configuration Space Obstacle or CSO), where the difference between support points of the two shapes forms a new shape. When shapes penetrate, this CSO contains the origin.
EPA iteratively expands a convex polyhedron (in 3D) that surrounds the origin. At each iteration:
- It finds the triangular face closest to the origin
- Computes a support point in that face’s normal direction
- Determines which existing faces are visible from the new point (the silhouette)
- Removes visible faces and creates new ones connecting the silhouette boundary to the new point
This process maintains a valid convex hull that progressively tightens around the origin until convergence, at which point the closest face defines the penetration depth and contact normal.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for EPA
impl RefUnwindSafe for EPA
impl Send for EPA
impl Sync for EPA
impl Unpin for EPA
impl UnwindSafe for EPA
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> 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.