Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bfafb69
heavy wip ; I could reproduce a wrong shapecast :thinking:
ThierryBerger Dec 17, 2024
a6f6503
better example with all cases shown at once
ThierryBerger Dec 17, 2024
9fa67fd
up epsilon for gjk
ThierryBerger Dec 17, 2024
881557d
add changelog
ThierryBerger Dec 17, 2024
d8cd6bf
removed incorrect lint
ThierryBerger Dec 18, 2024
4e3077c
add example to cargo toml + rename with 2d postfix
ThierryBerger Dec 18, 2024
e9cf114
center shape to raycast
ThierryBerger Jan 6, 2025
d4e460e
add unit test
ThierryBerger Jan 20, 2025
f0118bb
add ability to customize gjk at the dispatcher level (still needs to …
ThierryBerger Jun 25, 2025
7067ee2
add dedicated gjk options struct and passed in most convenient places
ThierryBerger Jul 2, 2025
67bd749
wip: using point query options as dyn ; dispatching them depending on…
ThierryBerger Jul 8, 2025
ca45b43
add an example to test out API for compound shape options
ThierryBerger Jul 9, 2025
ebe46f3
Merge branch 'master' into incorrect-shapecast
ThierryBerger Jul 15, 2025
cd33ad8
QueryOptionsDispatcherMap makes it possible to forward options to a c…
ThierryBerger Jul 16, 2025
b2de11a
code cleanup
ThierryBerger Jul 16, 2025
df4e569
more docs + fix default gjk epsilon
ThierryBerger Jul 17, 2025
4c0c3ae
fix bench + fix default epsilon used in QueryOptions default
ThierryBerger Jul 17, 2025
04b00e6
update changelog
ThierryBerger Jul 17, 2025
6769e55
more consistent headings
ThierryBerger Jul 17, 2025
ee97266
Merge branch 'master' into incorrect-shapecast
ThierryBerger Jul 23, 2025
f0e82d7
wip ray support
ThierryBerger Jul 24, 2025
80a1b25
Made more clear when query options are needed or not, adapted example…
ThierryBerger Jul 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions crates/parry2d/examples/debug_shape_cast2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use common_macroquad2d::draw_point;
use macroquad::prelude::*;
use nalgebra::{Isometry2, Point2};
use parry2d::math::Isometry;
use parry2d::query::{self, Ray, ShapeCastOptions};
use parry2d::query::gjk::eps_tol;
use parry2d::query::{self, DefaultQueryDispatcher, Ray, ShapeCastOptions};
use parry2d::shape::{Ball, ConvexPolygon, Shape};

const RENDER_SCALE: f32 = 1.0;
Expand Down Expand Up @@ -101,14 +102,17 @@ fn shape_cast_debug(
let vel2 = [0.0, 0.0];
let g2 = to_cast_against.clone_dyn();

let toi = query::cast_shapes(
let toi = query::cast_shapes_with_dispatcher(
&pos1.into(),
&vel1,
&g1,
&pos2.into(),
&vel2.into(),
&*g2,
ShapeCastOptions::with_max_time_of_impact(1.0),
DefaultQueryDispatcher {
gjk_espilon_tolerance: eps_tol() * 1000f32,
},
)
.unwrap();

Expand Down
2 changes: 1 addition & 1 deletion crates/parry2d/tests/geometry/epa2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fn cuboids_large_size_ratio_issue_181() {

let pos_b = Isometry2::new(Vector2::new(5.0, 0.0), 1.5);

let dispatcher = DefaultQueryDispatcher;
let dispatcher = DefaultQueryDispatcher::default();
let mut p = Vector2::new(0.0, 0.0);
let mut angle = 0.0;

Expand Down
2 changes: 1 addition & 1 deletion src/query/closest_points/closest_points_shape_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn closest_points(
max_dist: Real,
) -> Result<ClosestPoints, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
DefaultQueryDispatcher
DefaultQueryDispatcher::default()
.closest_points(&pos12, g1, g2, max_dist)
.map(|res| res.transform_by(pos1, pos2))
}
2 changes: 1 addition & 1 deletion src/query/contact/contact_shape_shape.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub fn contact(
prediction: Real,
) -> Result<Option<Contact>, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
let mut result = DefaultQueryDispatcher.contact(&pos12, g1, g2, prediction);
let mut result = DefaultQueryDispatcher::default().contact(&pos12, g1, g2, prediction);

if let Ok(Some(contact)) = &mut result {
contact.transform_by_mut(pos1, pos2);
Expand Down
15 changes: 14 additions & 1 deletion src/query/default_query_dispatcher.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::math::{Isometry, Point, Real, Vector};
use crate::query::details::ShapeCastOptions;
use crate::query::gjk;
use crate::query::{
self, details::NonlinearShapeCastMode, ClosestPoints, Contact, NonlinearRigidMotion,
QueryDispatcher, ShapeCastHit, Unsupported,
Expand All @@ -14,7 +15,18 @@ use crate::shape::{HalfSpace, Segment, Shape, ShapeType};

/// A dispatcher that exposes built-in queries
#[derive(Debug, Clone)]
pub struct DefaultQueryDispatcher;
pub struct DefaultQueryDispatcher {
/// The absolute tolerance used by the GJK algorithm.
pub gjk_espilon_tolerance: Real,
}

impl Default for DefaultQueryDispatcher {
fn default() -> Self {
Self {
gjk_espilon_tolerance: gjk::eps_tol(),
}
}
}

impl QueryDispatcher for DefaultQueryDispatcher {
fn intersection_test(
Expand Down Expand Up @@ -333,6 +345,7 @@ impl QueryDispatcher for DefaultQueryDispatcher {
s1,
s2,
options,
self.gjk_espilon_tolerance,
));
} else if let Some(c1) = shape1.as_composite_shape() {
return Ok(query::details::cast_shapes_composite_shape_shape(
Expand Down
2 changes: 1 addition & 1 deletion src/query/distance/distance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@ pub fn distance(
g2: &dyn Shape,
) -> Result<Real, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
DefaultQueryDispatcher.distance(&pos12, g1, g2)
DefaultQueryDispatcher::default().distance(&pos12, g1, g2)
}
38 changes: 24 additions & 14 deletions src/query/gjk/gjk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub enum GJKResult {
/// The absolute tolerance used by the GJK algorithm.
pub fn eps_tol() -> Real {
let _eps = crate::math::DEFAULT_EPSILON;
_eps * 10_000.0
_eps
}

/// Projects the origin on the boundary of the given shape.
Expand Down Expand Up @@ -197,6 +197,7 @@ pub fn cast_local_ray<G: ?Sized + SupportMap>(
ray,
max_time_of_impact,
simplex,
eps_tol(),
)
}

Expand All @@ -210,25 +211,33 @@ pub fn directional_distance<G1, G2>(
g2: &G2,
dir: &Vector<Real>,
simplex: &mut VoronoiSimplex,
gjk_espilon_tolerance: Real,
) -> Option<(Real, Vector<Real>, Point<Real>, Point<Real>)>
where
G1: ?Sized + SupportMap,
G2: ?Sized + SupportMap,
{
let ray = Ray::new(Point::origin(), *dir);
minkowski_ray_cast(pos12, g1, g2, &ray, Real::max_value(), simplex).map(
|(time_of_impact, normal)| {
let witnesses = if !time_of_impact.is_zero() {
result(simplex, simplex.dimension() == DIM)
} else {
// If there is penetration, the witness points
// are undefined.
(Point::origin(), Point::origin())
};

(time_of_impact, normal, witnesses.0, witnesses.1)
},
minkowski_ray_cast(
pos12,
g1,
g2,
&ray,
Real::max_value(),
simplex,
gjk_espilon_tolerance,
)
.map(|(time_of_impact, normal)| {
let witnesses = if !time_of_impact.is_zero() {
result(simplex, simplex.dimension() == DIM)
} else {
// If there is penetration, the witness points
// are undefined.
(Point::origin(), Point::origin())
};

(time_of_impact, normal, witnesses.0, witnesses.1)
})
}

// Ray-cast on the Minkowski Difference `g1 - pos12 * g2`.
Expand All @@ -239,13 +248,14 @@ fn minkowski_ray_cast<G1, G2>(
ray: &Ray,
max_time_of_impact: Real,
simplex: &mut VoronoiSimplex,
gjk_espilon_tolerance: Real,
) -> Option<(Real, Vector<Real>)>
where
G1: ?Sized + SupportMap,
G2: ?Sized + SupportMap,
{
let _eps = crate::math::DEFAULT_EPSILON;
let _eps_tol: Real = eps_tol();
let _eps_tol: Real = gjk_espilon_tolerance;
let _eps_rel: Real = ComplexField::sqrt(_eps_tol);

let ray_length = ray.dir.norm();
Expand Down
2 changes: 1 addition & 1 deletion src/query/intersection_test/intersection_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ pub fn intersection_test(
g2: &dyn Shape,
) -> Result<bool, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
DefaultQueryDispatcher.intersection_test(&pos12, g1, g2)
DefaultQueryDispatcher::default().intersection_test(&pos12, g1, g2)
}
4 changes: 3 additions & 1 deletion src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ pub use self::point::{PointProjection, PointQuery, PointQueryWithLocation};
pub use self::query_dispatcher::PersistentQueryDispatcher;
pub use self::query_dispatcher::{QueryDispatcher, QueryDispatcherChain};
pub use self::ray::{Ray, RayCast, RayIntersection, SimdRay};
pub use self::shape_cast::{cast_shapes, ShapeCastHit, ShapeCastOptions, ShapeCastStatus};
pub use self::shape_cast::{
cast_shapes, cast_shapes_with_dispatcher, ShapeCastHit, ShapeCastOptions, ShapeCastStatus,
};
pub use self::split::{IntersectResult, SplitResult};

mod clip;
Expand Down
2 changes: 1 addition & 1 deletion src/query/nonlinear_shape_cast/nonlinear_shape_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub fn cast_shapes_nonlinear(
end_time: Real,
stop_at_penetration: bool,
) -> Result<Option<ShapeCastHit>, Unsupported> {
DefaultQueryDispatcher.cast_shapes_nonlinear(
DefaultQueryDispatcher::default().cast_shapes_nonlinear(
motion1,
g1,
motion2,
Expand Down
4 changes: 3 additions & 1 deletion src/query/shape_cast/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
//! Implementation details of the `cast_shapes` function.

pub use self::shape_cast::{cast_shapes, ShapeCastHit, ShapeCastOptions, ShapeCastStatus};
pub use self::shape_cast::{
cast_shapes, cast_shapes_with_dispatcher, ShapeCastHit, ShapeCastOptions, ShapeCastStatus,
};
pub use self::shape_cast_ball_ball::cast_shapes_ball_ball;
pub use self::shape_cast_halfspace_support_map::{
cast_shapes_halfspace_support_map, cast_shapes_support_map_halfspace,
Expand Down
22 changes: 21 additions & 1 deletion src/query/shape_cast/shape_cast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,5 +149,25 @@ pub fn cast_shapes(
) -> Result<Option<ShapeCastHit>, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
let vel12 = pos1.inverse_transform_vector(&(vel2 - vel1));
DefaultQueryDispatcher.cast_shapes(&pos12, &vel12, g1, g2, options)
DefaultQueryDispatcher::default().cast_shapes(&pos12, &vel12, g1, g2, options)
}

/// Computes the smallest time when two shapes under translational movement are separated by a
/// distance smaller or equal to `distance`.
///
/// Returns `0.0` if the objects are touching or closer than `options.target_distance`,
/// or penetrating.
pub fn cast_shapes_with_dispatcher(
pos1: &Isometry<Real>,
vel1: &Vector<Real>,
g1: &dyn Shape,
pos2: &Isometry<Real>,
vel2: &Vector<Real>,
g2: &dyn Shape,
options: ShapeCastOptions,
dispatcher: impl QueryDispatcher,
) -> Result<Option<ShapeCastHit>, Unsupported> {
let pos12 = pos1.inv_mul(pos2);
let vel12 = pos1.inverse_transform_vector(&(vel2 - vel1));
dispatcher.cast_shapes(&pos12, &vel12, g1, g2, options)
}
19 changes: 17 additions & 2 deletions src/query/shape_cast/shape_cast_support_map_support_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub fn cast_shapes_support_map_support_map<G1, G2>(
g1: &G1,
g2: &G2,
options: ShapeCastOptions,
gjk_espilon_tolerance: Real,
) -> Option<ShapeCastHit>
where
G1: ?Sized + SupportMap,
Expand All @@ -25,9 +26,23 @@ where
inner_shape: g1,
border_radius: options.target_distance,
};
gjk::directional_distance(pos12, &round_g1, g2, vel12, &mut VoronoiSimplex::new())
gjk::directional_distance(
pos12,
&round_g1,
g2,
vel12,
&mut VoronoiSimplex::new(),
gjk_espilon_tolerance,
)
} else {
gjk::directional_distance(pos12, g1, g2, vel12, &mut VoronoiSimplex::new())
gjk::directional_distance(
pos12,
g1,
g2,
vel12,
&mut VoronoiSimplex::new(),
gjk_espilon_tolerance,
)
};

gjk_result.and_then(|(time_of_impact, normal1, witness1, witness2)| {
Expand Down