diff --git a/crates/parry3d/benches/common/dispatcher.rs b/crates/parry3d/benches/common/dispatcher.rs new file mode 100644 index 00000000..3cb3ce42 --- /dev/null +++ b/crates/parry3d/benches/common/dispatcher.rs @@ -0,0 +1,96 @@ +use na::Isometry3; +use parry3d::math::Isometry; +use parry3d::query::composable_dispatcher::ComposableQueryDispatcher; +use parry3d::query::{self, Unsupported}; +use parry3d::query::{ + composable_dispatcher::create_intersection_dispatcher, DefaultQueryDispatcher, QueryDispatcher, +}; +use parry3d::shape::{Ball, ConvexPolyhedron, Shape}; +use rand::SeedableRng; +use rand_isaac::IsaacRng; +use test::Bencher; + +use crate::common::{generate, unref}; + +#[path = "../common/macros.rs"] +#[macro_use] +mod macros; + +#[bench] +fn bench_dispatcher_creation(bh: &mut Bencher) { + let mut i = 0; + bh.iter(|| unsafe { test::black_box(create_intersection_dispatcher()) }) +} + +pub fn intersection_default( + pos12: &Isometry, + g1: &dyn Shape, + g2: &dyn Shape, +) -> Result { + DefaultQueryDispatcher.intersection_test(pos12, g1, g2) +} + +bench_free_fn!( + bench_intersection_ball_ball, + intersection_default, + pos12: Isometry3, + b1: Ball, + b2: Ball +); + +#[bench] +fn bench_intersection_ball_ball_composable(bh: &mut Bencher) { + const LEN: usize = 1 << 7; + let mut rng: IsaacRng = SeedableRng::seed_from_u64(0); + let pos12: Vec> = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let b1: Vec = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let b2: Vec = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let mut i = 0; + let dispatcher = ComposableQueryDispatcher { + intersections: create_intersection_dispatcher(), + }; + bh.iter(|| { + i = (i + 1) & (LEN - 1); + unsafe { + test::black_box(dispatcher.intersection_test( + unref(pos12.get_unchecked(i)), + unref(b1.get_unchecked(i)), + unref(b2.get_unchecked(i)), + )) + } + }); +} + +bench_free_fn!( + bench_intersection_convex_polyhedron_convex_polyhedron, + intersection_default, + pos12: Isometry3, + b1: ConvexPolyhedron, + b2: ConvexPolyhedron +); + +#[bench] +fn bench_intersection_convex_polyhedron_convex_polyhedron_composable(bh: &mut Bencher) { + const LEN: usize = 1 << 7; + let mut rng: IsaacRng = SeedableRng::seed_from_u64(0); + let pos12: Vec> = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let b1: Vec = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let b2: Vec = (0usize..LEN).map(|_| generate(&mut rng)).collect(); + let mut i = 0; + let dispatcher = ComposableQueryDispatcher { + intersections: create_intersection_dispatcher(), + }; + bh.iter(|| { + i = (i + 1) & (LEN - 1); + unsafe { + assert!(matches!( + test::black_box(dispatcher.intersection_test( + unref(pos12.get_unchecked(i)), + unref(b1.get_unchecked(i)), + unref(b2.get_unchecked(i)), + )), + Ok(_) + )) + } + }); +} diff --git a/crates/parry3d/benches/common/mod.rs b/crates/parry3d/benches/common/mod.rs index 746805fc..e17e35fe 100644 --- a/crates/parry3d/benches/common/mod.rs +++ b/crates/parry3d/benches/common/mod.rs @@ -3,5 +3,6 @@ pub use self::generators::generate_trimesh_around_origin; pub use self::unref::unref; mod default_gen; +mod dispatcher; mod generators; mod unref; diff --git a/src/query/composable_dispatcher.rs b/src/query/composable_dispatcher.rs new file mode 100644 index 00000000..f2d522df --- /dev/null +++ b/src/query/composable_dispatcher.rs @@ -0,0 +1,583 @@ +//! Logic to dispatch queries to the appropriate handler. +//! +//! This can be customized to add support to your own types. + +mod function_dispatch; +mod intersection; + +pub use intersection::*; +#[cfg(test)] +mod tests; + +use core::any::TypeId; +use std::collections::HashMap; + +use function_dispatch::{DispatcherTypeKey, FunctionDispatch}; + +use crate::math::{Isometry, Point, Real, Vector}; +use crate::query::details::ShapeCastOptions; +use crate::query::{ + self, details::NonlinearShapeCastMode, ClosestPoints, Contact, NonlinearRigidMotion, + QueryDispatcher, ShapeCastHit, Unsupported, +}; +#[cfg(feature = "std")] +use crate::query::{ + contact_manifolds::{ContactManifoldsWorkspace, NormalConstraints}, + query_dispatcher::PersistentQueryDispatcher, + ContactManifold, +}; +use crate::shape::*; + +/// A dispatcher that exposes built-in queries + +#[derive(Debug)] +pub struct ComposableQueryDispatcher { + pub intersections: FunctionDispatch, +} + +impl QueryDispatcher for ComposableQueryDispatcher { + fn intersection_test( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + ) -> Result { + return self + .intersections + .dispatch(self, pos12, shape1, shape2) + .map_err(|_| Unsupported); + } + + /// Computes the minimum distance separating two shapes. + /// + /// Returns `0.0` if the objects are touching or penetrating. + fn distance( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + ) -> Result { + let ball1 = shape1.as_ball(); + let ball2 = shape2.as_ball(); + + if let (Some(b1), Some(b2)) = (ball1, ball2) { + let p2 = Point::from(pos12.translation.vector); + Ok(query::details::distance_ball_ball(b1, &p2, b2)) + } else if let (Some(b1), true) = (ball1, shape2.is_convex()) { + Ok(query::details::distance_ball_convex_polyhedron( + pos12, b1, shape2, + )) + } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) { + Ok(query::details::distance_convex_polyhedron_ball( + pos12, shape1, b2, + )) + } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) { + Ok(query::details::distance_cuboid_cuboid(pos12, c1, c2)) + } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) { + Ok(query::details::distance_segment_segment(pos12, s1, s2)) + } else if let (Some(p1), Some(s2)) = + (shape1.as_shape::(), shape2.as_support_map()) + { + Ok(query::details::distance_halfspace_support_map( + pos12, p1, s2, + )) + } else if let (Some(s1), Some(p2)) = + (shape1.as_support_map(), shape2.as_shape::()) + { + Ok(query::details::distance_support_map_halfspace( + pos12, s1, p2, + )) + } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) { + Ok(query::details::distance_support_map_support_map( + pos12, s1, s2, + )) + } else { + #[cfg(feature = "std")] + if let Some(c1) = shape1.as_composite_shape() { + return Ok(query::details::distance_composite_shape_shape( + self, pos12, c1, shape2, + )); + } else if let Some(c2) = shape2.as_composite_shape() { + return Ok(query::details::distance_shape_composite_shape( + self, pos12, shape1, c2, + )); + } + + Err(Unsupported) + } + } + + fn contact( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + prediction: Real, + ) -> Result, Unsupported> { + let ball1 = shape1.as_ball(); + let ball2 = shape2.as_ball(); + + if let (Some(b1), Some(b2)) = (ball1, ball2) { + Ok(query::details::contact_ball_ball(pos12, b1, b2, prediction)) + // } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) { + // Ok(query::details::contact_cuboid_cuboid( + // pos12, c1, c2, prediction, + // )) + } else if let (Some(p1), Some(s2)) = + (shape1.as_shape::(), shape2.as_support_map()) + { + Ok(query::details::contact_halfspace_support_map( + pos12, p1, s2, prediction, + )) + } else if let (Some(s1), Some(p2)) = + (shape1.as_support_map(), shape2.as_shape::()) + { + Ok(query::details::contact_support_map_halfspace( + pos12, s1, p2, prediction, + )) + } else if let (Some(b1), true) = (ball1, shape2.is_convex()) { + Ok(query::details::contact_ball_convex_polyhedron( + pos12, b1, shape2, prediction, + )) + } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) { + Ok(query::details::contact_convex_polyhedron_ball( + pos12, shape1, b2, prediction, + )) + } else { + #[cfg(feature = "std")] + if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) { + return Ok(query::details::contact_support_map_support_map( + pos12, s1, s2, prediction, + )); + } else if let Some(c1) = shape1.as_composite_shape() { + return Ok(query::details::contact_composite_shape_shape( + self, pos12, c1, shape2, prediction, + )); + } else if let Some(c2) = shape2.as_composite_shape() { + return Ok(query::details::contact_shape_composite_shape( + self, pos12, shape1, c2, prediction, + )); + } + + Err(Unsupported) + } + } + + fn closest_points( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + max_dist: Real, + ) -> Result { + let ball1 = shape1.as_ball(); + let ball2 = shape2.as_ball(); + + if let (Some(b1), Some(b2)) = (ball1, ball2) { + Ok(query::details::closest_points_ball_ball( + pos12, b1, b2, max_dist, + )) + } else if let (Some(b1), true) = (ball1, shape2.is_convex()) { + Ok(query::details::closest_points_ball_convex_polyhedron( + pos12, b1, shape2, max_dist, + )) + } else if let (true, Some(b2)) = (shape1.is_convex(), ball2) { + Ok(query::details::closest_points_convex_polyhedron_ball( + pos12, shape1, b2, max_dist, + )) + } else if let (Some(s1), Some(s2)) = + (shape1.as_shape::(), shape2.as_shape::()) + { + Ok(query::details::closest_points_segment_segment( + pos12, s1, s2, max_dist, + )) + // } else if let (Some(c1), Some(c2)) = (shape1.as_cuboid(), shape2.as_cuboid()) { + // Ok(query::details::closest_points_cuboid_cuboid( + // pos12, c1, c2, max_dist, + // )) + } else if let (Some(s1), Some(s2)) = (shape1.as_segment(), shape2.as_segment()) { + Ok(query::details::closest_points_segment_segment( + pos12, s1, s2, max_dist, + )) + // } else if let (Some(c1), Some(t2)) = (shape1.as_cuboid(), shape2.as_triangle()) { + // Ok(query::details::closest_points_cuboid_triangle( + // pos12, c1, t2, max_dist, + // )) + } else if let (Some(t1), Some(c2)) = (shape1.as_triangle(), shape2.as_cuboid()) { + Ok(query::details::closest_points_triangle_cuboid( + pos12, t1, c2, max_dist, + )) + } else if let (Some(p1), Some(s2)) = + (shape1.as_shape::(), shape2.as_support_map()) + { + Ok(query::details::closest_points_halfspace_support_map( + pos12, p1, s2, max_dist, + )) + } else if let (Some(s1), Some(p2)) = + (shape1.as_support_map(), shape2.as_shape::()) + { + Ok(query::details::closest_points_support_map_halfspace( + pos12, s1, p2, max_dist, + )) + } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) { + Ok(query::details::closest_points_support_map_support_map( + pos12, s1, s2, max_dist, + )) + } else { + #[cfg(feature = "std")] + if let Some(c1) = shape1.as_composite_shape() { + return Ok(query::details::closest_points_composite_shape_shape( + self, pos12, c1, shape2, max_dist, + )); + } else if let Some(c2) = shape2.as_composite_shape() { + return Ok(query::details::closest_points_shape_composite_shape( + self, pos12, shape1, c2, max_dist, + )); + } + + Err(Unsupported) + } + } + + fn cast_shapes( + &self, + pos12: &Isometry, + local_vel12: &Vector, + shape1: &dyn Shape, + shape2: &dyn Shape, + options: ShapeCastOptions, + ) -> Result, Unsupported> { + if let (Some(b1), Some(b2)) = (shape1.as_ball(), shape2.as_ball()) { + Ok(query::details::cast_shapes_ball_ball( + pos12, + local_vel12, + b1, + b2, + options, + )) + } else if let (Some(p1), Some(s2)) = + (shape1.as_shape::(), shape2.as_support_map()) + { + Ok(query::details::cast_shapes_halfspace_support_map( + pos12, + local_vel12, + p1, + s2, + options, + )) + } else if let (Some(s1), Some(p2)) = + (shape1.as_support_map(), shape2.as_shape::()) + { + Ok(query::details::cast_shapes_support_map_halfspace( + pos12, + local_vel12, + s1, + p2, + options, + )) + } else { + #[cfg(feature = "std")] + if let Some(heightfield1) = shape1.as_heightfield() { + return query::details::cast_shapes_heightfield_shape( + self, + pos12, + local_vel12, + heightfield1, + shape2, + options, + ); + } else if let Some(heightfield2) = shape2.as_heightfield() { + return query::details::cast_shapes_shape_heightfield( + self, + pos12, + local_vel12, + shape1, + heightfield2, + options, + ); + } else if let (Some(s1), Some(s2)) = (shape1.as_support_map(), shape2.as_support_map()) + { + return Ok(query::details::cast_shapes_support_map_support_map( + pos12, + local_vel12, + s1, + s2, + options, + )); + } else if let Some(c1) = shape1.as_composite_shape() { + return Ok(query::details::cast_shapes_composite_shape_shape( + self, + pos12, + local_vel12, + c1, + shape2, + options, + )); + } else if let Some(c2) = shape2.as_composite_shape() { + return Ok(query::details::cast_shapes_shape_composite_shape( + self, + pos12, + local_vel12, + shape1, + c2, + options, + )); + } + + Err(Unsupported) + } + } + + fn cast_shapes_nonlinear( + &self, + motion1: &NonlinearRigidMotion, + shape1: &dyn Shape, + motion2: &NonlinearRigidMotion, + shape2: &dyn Shape, + start_time: Real, + end_time: Real, + stop_at_penetration: bool, + ) -> Result, Unsupported> { + if let (Some(sm1), Some(sm2)) = (shape1.as_support_map(), shape2.as_support_map()) { + let mode = if stop_at_penetration { + NonlinearShapeCastMode::StopAtPenetration + } else { + NonlinearShapeCastMode::directional_toi(shape1, shape2) + }; + + Ok( + query::details::cast_shapes_nonlinear_support_map_support_map( + self, motion1, sm1, shape1, motion2, sm2, shape2, start_time, end_time, mode, + ), + ) + } else { + #[cfg(feature = "std")] + if let Some(c1) = shape1.as_composite_shape() { + return Ok(query::details::cast_shapes_nonlinear_composite_shape_shape( + self, + motion1, + c1, + motion2, + shape2, + start_time, + end_time, + stop_at_penetration, + )); + } else if let Some(c2) = shape2.as_composite_shape() { + return Ok(query::details::cast_shapes_nonlinear_shape_composite_shape( + self, + motion1, + shape1, + motion2, + c2, + start_time, + end_time, + stop_at_penetration, + )); + } + /* } else if let (Some(p1), Some(s2)) = (shape1.as_shape::(), shape2.as_support_map()) { + // query::details::cast_shapes_nonlinear_halfspace_support_map(m1, vel1, p1, m2, vel2, s2) + unimplemented!() + } else if let (Some(s1), Some(p2)) = (shape1.as_support_map(), shape2.as_shape::()) { + // query::details::cast_shapes_nonlinear_support_map_halfspace(m1, vel1, s1, m2, vel2, p2) + unimplemented!() */ + + Err(Unsupported) + } + } +} + +#[cfg(feature = "std")] +impl PersistentQueryDispatcher + for ComposableQueryDispatcher +where + ManifoldData: Default + Clone, + ContactData: Default + Copy, +{ + fn contact_manifolds( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + prediction: Real, + manifolds: &mut Vec>, + workspace: &mut Option, + ) -> Result<(), Unsupported> { + use crate::query::contact_manifolds::*; + + let composite1 = shape1.as_composite_shape(); + let composite2 = shape2.as_composite_shape(); + + if let (Some(composite1), Some(composite2)) = (composite1, composite2) { + contact_manifolds_composite_shape_composite_shape( + self, pos12, composite1, composite2, prediction, manifolds, workspace, + ); + + return Ok(()); + } + + match (shape1.shape_type(), shape2.shape_type()) { + (ShapeType::TriMesh, _) | (_, ShapeType::TriMesh) => { + contact_manifolds_trimesh_shape_shapes( + self, pos12, shape1, shape2, prediction, manifolds, workspace, + ); + } + (ShapeType::HeightField, _) => { + if let Some(composite2) = composite2 { + contact_manifolds_heightfield_composite_shape( + self, + pos12, + &pos12.inverse(), + shape1.as_heightfield().unwrap(), + composite2, + prediction, + manifolds, + workspace, + false, + ) + } else { + contact_manifolds_heightfield_shape_shapes( + self, pos12, shape1, shape2, prediction, manifolds, workspace, + ); + } + } + (_, ShapeType::HeightField) => { + if let Some(composite1) = composite1 { + contact_manifolds_heightfield_composite_shape( + self, + &pos12.inverse(), + pos12, + shape2.as_heightfield().unwrap(), + composite1, + prediction, + manifolds, + workspace, + true, + ) + } else { + contact_manifolds_heightfield_shape_shapes( + self, pos12, shape1, shape2, prediction, manifolds, workspace, + ); + } + } + _ => { + if let Some(composite1) = composite1 { + contact_manifolds_composite_shape_shape( + self, pos12, composite1, shape2, prediction, manifolds, workspace, false, + ); + } else if let Some(composite2) = composite2 { + contact_manifolds_composite_shape_shape( + self, + &pos12.inverse(), + composite2, + shape1, + prediction, + manifolds, + workspace, + true, + ); + } else { + if manifolds.is_empty() { + manifolds.push(ContactManifold::new()); + } + + return self.contact_manifold_convex_convex( + pos12, + shape1, + shape2, + None, + None, + prediction, + &mut manifolds[0], + ); + } + } + } + + Ok(()) + } + + fn contact_manifold_convex_convex( + &self, + pos12: &Isometry, + shape1: &dyn Shape, + shape2: &dyn Shape, + normal_constraints1: Option<&dyn NormalConstraints>, + normal_constraints2: Option<&dyn NormalConstraints>, + prediction: Real, + manifold: &mut ContactManifold, + ) -> Result<(), Unsupported> { + use crate::query::contact_manifolds::*; + + match (shape1.shape_type(), shape2.shape_type()) { + (ShapeType::Ball, ShapeType::Ball) => { + contact_manifold_ball_ball_shapes(pos12, shape1, shape2, prediction, manifold) + } + (ShapeType::Cuboid, ShapeType::Cuboid) => + contact_manifold_cuboid_cuboid_shapes(pos12, shape1, shape2, prediction, manifold) + , + // (ShapeType::Polygon, ShapeType::Polygon) => ( + // PrimitiveContactGenerator { + // generate_contacts: super::generate_contacts_polygon_polygon, + // ..PrimitiveContactGenerator::default() + // }, + // None, + // ), + (ShapeType::Capsule, ShapeType::Capsule) => { + contact_manifold_capsule_capsule_shapes(pos12, shape1, shape2, prediction, manifold) + } + (_, ShapeType::Ball) | (ShapeType::Ball, _) => { + contact_manifold_convex_ball_shapes(pos12, shape1, shape2, normal_constraints1, normal_constraints2, prediction, manifold) + } + // (ShapeType::Capsule, ShapeType::Cuboid) | (ShapeType::Cuboid, ShapeType::Capsule) => + // contact_manifold_cuboid_capsule_shapes(pos12, shape1, shape2, prediction, manifold), + (ShapeType::Triangle, ShapeType::Cuboid) | (ShapeType::Cuboid, ShapeType::Triangle) => { + contact_manifold_cuboid_triangle_shapes(pos12, shape1, shape2, normal_constraints1, normal_constraints2, prediction, manifold) + } + (ShapeType::HalfSpace, _) => { + if let Some((pfm2, border_radius2)) = shape2.as_polygonal_feature_map() { + contact_manifold_halfspace_pfm( + pos12, + shape1.as_halfspace().unwrap(), + pfm2, + border_radius2, + prediction, + manifold, + false + ) + } else { + return Err(Unsupported) + } + } + (_, ShapeType::HalfSpace) => { + if let Some((pfm1, border_radius1)) = shape1.as_polygonal_feature_map() { + contact_manifold_halfspace_pfm( + &pos12.inverse(), + shape2.as_halfspace().unwrap(), + pfm1, + border_radius1, + prediction, + manifold, + true + ) + } else { + return Err(Unsupported) + } + } + _ => { + if let (Some(pfm1), Some(pfm2)) = ( + shape1.as_polygonal_feature_map(), + shape2.as_polygonal_feature_map(), + ) { + contact_manifold_pfm_pfm( + pos12, pfm1.0, pfm1.1, normal_constraints1, pfm2.0, pfm2.1, normal_constraints2, prediction, manifold, + ) + } else { + return Err(Unsupported); + } + } + } + + Ok(()) + } +} diff --git a/src/query/composable_dispatcher/function_dispatch.rs b/src/query/composable_dispatcher/function_dispatch.rs new file mode 100644 index 00000000..a79500c4 --- /dev/null +++ b/src/query/composable_dispatcher/function_dispatch.rs @@ -0,0 +1,267 @@ +use crate::utils::hashmap::HashMap; +use core::any::TypeId; + +use log::warn; + +use crate::{ + math::{Isometry, Real}, + query::QueryDispatcher, + shape::{RoundShape, Shape}, +}; + +use super::ComposableQueryDispatcher; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct DispatcherTypeKey(pub TypeId, pub TypeId); + +pub trait IntersectionWithDispatcher: Send + Sync { + fn intersection_with_dispatcher( + &self, + dispatcher: &ComposableQueryDispatcher, + pos12: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result; +} +pub struct BoxedIntersectionWithDispatcher(Box); +impl IntersectionWithDispatcher for BoxedIntersectionWithDispatcher { + fn intersection_with_dispatcher( + &self, + dispatcher: &ComposableQueryDispatcher, + pos12: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result { + self.0 + .intersection_with_dispatcher(dispatcher, pos12, s1, s2) + } +} +impl IntersectionWithDispatcher for fn(&Isometry, &dyn Shape, &dyn Shape) -> bool { + fn intersection_with_dispatcher( + &self, + _dispatcher: &ComposableQueryDispatcher, + pos12: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result { + Ok(self(pos12, s1, s2)) + } +} +/* +impl IntersectionWithDispatcher + for fn(&ComposableQueryDispatcher, &Isometry, &dyn Shape, &dyn Shape) -> bool +{ + fn intersection_with_dispatcher( + &self, + dispatcher: &ComposableQueryDispatcher, + pos12: &Isometry, + other: &dyn Shape, + ) -> bool { + self(dispatcher, pos12, other, other) + } +}*/ + +impl IntersectionWithDispatcher for F +where + F: Fn(&ComposableQueryDispatcher, &Isometry, &dyn Shape, &dyn Shape) -> Result + + Send + + Sync, +{ + fn intersection_with_dispatcher( + &self, + dispatcher: &ComposableQueryDispatcher, + pos12: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result { + self(dispatcher, pos12, s1, s2) + } +} + +pub struct FunctionDispatch { + /// Map for the function to call when both parameters are known types. + pub functions: HashMap>, +} + +impl core::fmt::Debug for FunctionDispatch { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("FunctionDispatch") + .field("known_both", &self.functions.keys()) + .finish() + } +} + +impl FunctionDispatch { + pub fn new() -> Self { + Self { + functions: HashMap::default(), + } + } + pub fn add_function_known_12( + &mut self, + inner: impl Fn(&Isometry, &S1, &S2) -> bool + 'static + Send + Sync + Copy, + ) { + if self + .functions + .insert( + DispatcherTypeKey(TypeId::of::(), TypeId::of::()), + Box::new( + move |_dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + let shape1 = s1.as_shape::().ok_or(())?; + let shape2 = s2.as_shape::().ok_or(())?; + Ok(inner(pose, shape1, shape2)) + }, + ), + ) + .is_some() + { + warn!( + "Overwriting function for types {:?} and {:?}", + TypeId::of::(), + TypeId::of::() + ); + } + } + + pub fn add_function_known_1x( + &mut self, + inner: impl Fn(&Isometry, &S1, &dyn Shape) -> bool + 'static + Send + Sync + Copy, + tids: Vec, + ) { + for type_s2 in tids { + self.add_both_combinations(inner, TypeId::of::(), type_s2); + self.add_both_combinations(inner, TypeId::of::>(), type_s2); + } + } + + pub fn add_function_dyn_dispatcher( + &mut self, + inner: impl Fn(&ComposableQueryDispatcher, &Isometry, &dyn Shape, &dyn Shape) -> bool + + 'static + + Send + + Sync + + Copy, + type_s1: TypeId, + type_s2: TypeId, + ) { + self.add_raw_function( + type_s1, + type_s2, + move |dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { Ok(inner(dispatcher, pose, s1, s2)) }, + ); + + self.add_raw_function( + type_s2, + type_s1, + move |dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { Ok(inner(dispatcher, &pose.inverse(), s2, s1)) }, + ); + } + + fn add_both_combinations( + &mut self, + inner: impl Fn(&Isometry, &S1, &dyn Shape) -> bool + 'static + Send + Sync + Copy, + type_s1: TypeId, + type_s2: TypeId, + ) { + self.add_raw_function( + type_s1, + type_s2, + move |_dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + let shape1 = s1.as_shape::().ok_or(())?; + Ok(inner(pose, shape1, s2)) + }, + ); + // add the inverse function + self.add_raw_function( + type_s2, + type_s1, + move |_dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + let shape2 = s2.as_shape::().ok_or(())?; + Ok(inner(&pose.inverse(), shape2, s1)) + }, + ); + } + + pub fn add_raw_function( + &mut self, + type_s1: TypeId, + type_s2: TypeId, + function: impl IntersectionWithDispatcher + 'static + Send + Sync + Copy, + ) { + if self + .functions + .insert(DispatcherTypeKey(type_s1, type_s2), Box::new(function)) + .is_some() + { + warn!( + "Overwriting function for types {:?} and {:?}", + type_s1, type_s2 + ); + } + } + + fn add_both_combinations_dispatcher( + &mut self, + inner: impl Fn(&ComposableQueryDispatcher, &Isometry, &S1, &dyn Shape) -> bool + + 'static + + Send + + Sync + + Copy, + type_s1: TypeId, + type_s2: TypeId, + ) { + self.add_raw_function( + type_s1, + type_s2, + move |dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + let shape1 = s1.as_shape::().ok_or(())?; + Ok(inner(dispatcher, pose, shape1, s2)) + }, + ); + // add the inverse function + self.add_raw_function( + type_s2, + type_s1, + move |dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + let shape2 = s2.as_shape::().ok_or(())?; + Ok(inner(dispatcher, &pose.inverse(), shape2, s1)) + }, + ); + } + + pub fn dispatch( + &self, + dispatcher: &ComposableQueryDispatcher, + pose: &Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result { + let key = DispatcherTypeKey(s1.type_id(), s2.type_id()); + if let Some(func) = self.functions.get(&key) { + return func.intersection_with_dispatcher(dispatcher, pose, s1, s2); + } + + Err(()) + } +} diff --git a/src/query/composable_dispatcher/intersection.rs b/src/query/composable_dispatcher/intersection.rs new file mode 100644 index 00000000..50e7a6ad --- /dev/null +++ b/src/query/composable_dispatcher/intersection.rs @@ -0,0 +1,215 @@ +use core::any::TypeId; + +use crate::{ + math::Point, + query::{self, QueryDispatcher}, + shape::*, +}; + +use super::function_dispatch::FunctionDispatch; + +pub fn create_intersection_dispatcher() -> FunctionDispatch { + let mut dispatcher = FunctionDispatch::new(); + + // Register intersection functions here. + + dispatcher.add_function_known_12(query::details::intersection_test_cuboid_cuboid); + dispatcher.add_function_known_12(query::details::intersection_test_cuboid_segment); + dispatcher.add_function_known_12(query::details::intersection_test_segment_cuboid); + dispatcher.add_function_known_12(query::details::intersection_test_cuboid_triangle); + dispatcher.add_function_known_12(query::details::intersection_test_triangle_cuboid); + dispatcher.add_function_known_12(|pos12, b1, b2| { + let p12 = Point::from(pos12.translation.vector); + query::details::intersection_test_ball_ball(&p12, b1, b2) + }); + dispatcher.add_function_known_1x( + query::details::intersection_test_ball_point_query, + vec![ + // TODO: add Tetrahedron once shape is implemented. + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + #[cfg(feature = "dim2")] + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + TypeId::of::(), + // RoundShapes + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + #[cfg(feature = "dim2")] + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + TypeId::of::>(), + ], + ); + dispatcher.add_function_known_1x( + |pos12, halfspace: &HalfSpace, other| { + query::details::intersection_test_halfspace_support_map( + pos12, + halfspace, + other.as_support_map().expect("calling `as_support_map` on a non-support-map shape, make sure your type mapping is correct."), + ) + }, + vec![ + TypeId::of::(), + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + #[cfg(feature = "dim2")] + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + TypeId::of::(), + #[cfg(feature = "dim3")] + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + // RoundShapes + TypeId::of::>(), + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + #[cfg(feature = "dim2")] + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + TypeId::of::>(), + #[cfg(feature = "dim3")] + TypeId::of::>(), + TypeId::of::>(), + TypeId::of::>(), + ], + ); + fn intersection_test_sm_sm( + _: &query::composable_dispatcher::ComposableQueryDispatcher, + pos12: &crate::math::Isometry, + s1: &dyn Shape, + s2: &dyn Shape, + ) -> Result { + Ok(query::details::intersection_test_support_map_support_map( + pos12, + s1.as_support_map().unwrap(), + s2.as_support_map().unwrap(), + )) + } + // TODO: add support map support map + #[cfg(feature = "dim3")] + dispatcher.add_raw_function( + TypeId::of::(), + TypeId::of::(), + |_: &query::composable_dispatcher::ComposableQueryDispatcher, + pos12: &crate::math::Isometry, + s1: &dyn Shape, + s2: &dyn Shape| { + Ok(query::details::intersection_test_support_map_support_map( + pos12, + s1.as_support_map().unwrap(), + s2.as_support_map().unwrap(), + )) + }, + ); + + // TODO: add composite shapes + macro_rules! shape_impls { + ($($prefix:tt $inner:ty),*) => { + { + use std::collections::HashMap; + let mut types = HashMap::new(); + $( + register_shape!(types, $prefix $inner); + )* + types + } + }; + } + macro_rules! register_shape { + ($types:expr, any $shape:ty) => { + _ = $types.insert(TypeId::of::<$shape>(), stringify!($shape)); + }; + ($types:tt, $f:tt $shape:ty) => { + #[cfg(feature = $f)] + assert!($types + .insert(TypeId::of::<$shape>(), stringify!($shape)) + .is_none()); + }; + } + + let shape_impls = shape_impls!( + any Ball, + any Cuboid, + any Capsule, + any Triangle, + any Segment, + any Compound, + any Polyline, + any TriMesh, + any HeightField, + "dim2" ConvexPolygon, + "dim3" ConvexPolyhedron, + "dim3" Cylinder, + "dim3" Cone, + any HalfSpace, + // Round shapes + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + any RoundShape::, + "dim2" RoundShape::, + "dim3" RoundShape::, + "dim3" RoundShape::, + "dim3" RoundShape::, + any RoundShape:: + ); + + for composite_type in vec![ + TypeId::of::(), + TypeId::of::(), + TypeId::of::(), + ] { + for shape_type in shape_impls.iter() { + dispatcher.add_function_dyn_dispatcher( + |dispatcher, pos12, shape1, shape2| { + query::details::intersection_test_composite_shape_shape(dispatcher, + pos12, + shape1.as_composite_shape().expect("calling `as_composite_shape` on a non-support-map shape, make sure your type mapping is correct."), + shape2 + ) + }, + composite_type, *shape_type.0 + ); + } + } + + for k in dispatcher.functions.keys() { + println!( + "({} {})", + shape_impls.get(&k.0).unwrap_or(&"not found"), + shape_impls.get(&k.1).unwrap_or(&"not found") + ); + } + dispatcher +} diff --git a/src/query/composable_dispatcher/tests.rs b/src/query/composable_dispatcher/tests.rs new file mode 100644 index 00000000..e804a64b --- /dev/null +++ b/src/query/composable_dispatcher/tests.rs @@ -0,0 +1,39 @@ +extern crate nalgebra as na; + +#[cfg(feature = "dim2")] +mod tests2d { + use crate::query::composable_dispatcher::intersection::create_intersection_dispatcher; + use crate::query::composable_dispatcher::ComposableQueryDispatcher; + use crate::query::QueryDispatcher; + use crate::shape::{Ball, Cuboid}; + use na::{Isometry2, Vector2}; + + #[test] + fn intersection_test_dispatcher_configuration() { + let dispatcher = create_intersection_dispatcher(); + } + + #[test] + fn intersection_test() { + let cuboid = Cuboid::new(Vector2::new(1.0, 1.0)); + let ball = Ball::new(1.0); + + let cuboid_pos = Isometry2::identity(); + let ball_pos_intersecting = Isometry2::translation(1.0, 1.0); + let ball_pos_disjoint = Isometry2::translation(3.0, 3.0); + + let pos12 = ball_pos_intersecting.inv_mul(&cuboid_pos); + assert!(ComposableQueryDispatcher { + intersections: create_intersection_dispatcher() + } + .intersection_test(&pos12, &ball, &cuboid) + .unwrap()); + + let pos12 = ball_pos_disjoint.inv_mul(&cuboid_pos); + assert!(!ComposableQueryDispatcher { + intersections: create_intersection_dispatcher() + } + .intersection_test(&pos12, &ball, &cuboid) + .unwrap()); + } +} diff --git a/src/query/mod.rs b/src/query/mod.rs index ed6e1537..8fd14edd 100644 --- a/src/query/mod.rs +++ b/src/query/mod.rs @@ -46,6 +46,8 @@ pub use self::split::{IntersectResult, SplitResult}; mod clip; pub mod closest_points; +#[cfg(feature = "std")] +pub mod composable_dispatcher; pub mod contact; #[cfg(feature = "std")] mod contact_manifolds;