QueryPipeline: add shape casting, point projection, and intersection queries.
This commit is contained in:
@@ -86,6 +86,15 @@ pub(crate) use self::narrow_phase::ContactManifoldIndex;
|
|||||||
pub(crate) use cdl::partitioning::SimdQuadTree;
|
pub(crate) use cdl::partitioning::SimdQuadTree;
|
||||||
pub use cdl::shape::*;
|
pub use cdl::shape::*;
|
||||||
|
|
||||||
|
pub(crate) fn default_persistent_query_dispatcher(
|
||||||
|
) -> std::sync::Arc<dyn cdl::query::PersistentQueryDispatcher<ContactManifoldData, ContactData>> {
|
||||||
|
std::sync::Arc::new(cdl::query::DefaultQueryDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn default_query_dispatcher() -> std::sync::Arc<dyn cdl::query::QueryDispatcher> {
|
||||||
|
std::sync::Arc::new(cdl::query::DefaultQueryDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
mod broad_phase_multi_sap;
|
mod broad_phase_multi_sap;
|
||||||
mod collider;
|
mod collider;
|
||||||
mod collider_set;
|
mod collider_set;
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ impl ColliderGraphIndices {
|
|||||||
pub struct NarrowPhase {
|
pub struct NarrowPhase {
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serde-serialize",
|
feature = "serde-serialize",
|
||||||
serde(skip, default = "default_query_dispatcher")
|
serde(skip, default = "crate::geometry::default_persistent_query_dispatcher")
|
||||||
)]
|
)]
|
||||||
query_dispatcher: Arc<dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>>,
|
query_dispatcher: Arc<dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>>,
|
||||||
contact_graph: InteractionGraph<ContactPair>,
|
contact_graph: InteractionGraph<ContactPair>,
|
||||||
@@ -47,11 +47,6 @@ pub struct NarrowPhase {
|
|||||||
removed_colliders: Option<Subscription<RemovedCollider>>,
|
removed_colliders: Option<Subscription<RemovedCollider>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_query_dispatcher() -> Arc<dyn PersistentQueryDispatcher<ContactManifoldData, ContactData>>
|
|
||||||
{
|
|
||||||
Arc::new(DefaultQueryDispatcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type ContactManifoldIndex = usize;
|
pub(crate) type ContactManifoldIndex = usize;
|
||||||
|
|
||||||
impl NarrowPhase {
|
impl NarrowPhase {
|
||||||
|
|||||||
@@ -1,15 +1,68 @@
|
|||||||
|
use crate::cdl::motion::RigidMotion;
|
||||||
use crate::dynamics::RigidBodySet;
|
use crate::dynamics::RigidBodySet;
|
||||||
use crate::geometry::{
|
use crate::geometry::{
|
||||||
Collider, ColliderHandle, ColliderSet, InteractionGroups, Ray, RayIntersection, SimdQuadTree,
|
Collider, ColliderHandle, ColliderSet, InteractionGroups, PointProjection, Ray,
|
||||||
|
RayIntersection, SimdQuadTree,
|
||||||
};
|
};
|
||||||
|
use crate::math::{Isometry, Point, Real, Vector};
|
||||||
|
use cdl::query::details::{
|
||||||
|
IntersectionCompositeShapeShapeBestFirstVisitor,
|
||||||
|
NonlinearTOICompositeShapeShapeBestFirstVisitor, PointCompositeShapeProjBestFirstVisitor,
|
||||||
|
PointCompositeShapeProjWithFeatureBestFirstVisitor,
|
||||||
|
RayCompositeShapeToiAndNormalBestFirstVisitor, RayCompositeShapeToiBestFirstVisitor,
|
||||||
|
TOICompositeShapeShapeBestFirstVisitor,
|
||||||
|
};
|
||||||
|
use cdl::query::{DefaultQueryDispatcher, QueryDispatcher, TOI};
|
||||||
|
use cdl::shape::{FeatureId, Shape, TypedSimdCompositeShape};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// A pipeline for performing queries on all the colliders of a scene.
|
/// A pipeline for performing queries on all the colliders of a scene.
|
||||||
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct QueryPipeline {
|
pub struct QueryPipeline {
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "serde-serialize",
|
||||||
|
serde(skip, default = "crate::geometry::default_query_dispatcher")
|
||||||
|
)]
|
||||||
|
query_dispatcher: Arc<dyn QueryDispatcher>,
|
||||||
quadtree: SimdQuadTree<ColliderHandle>,
|
quadtree: SimdQuadTree<ColliderHandle>,
|
||||||
tree_built: bool,
|
tree_built: bool,
|
||||||
dilation_factor: f32,
|
dilation_factor: Real,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct QueryPipelineAsCompositeShape<'a> {
|
||||||
|
query_pipeline: &'a QueryPipeline,
|
||||||
|
colliders: &'a ColliderSet,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TypedSimdCompositeShape for QueryPipelineAsCompositeShape<'a> {
|
||||||
|
type PartShape = dyn Shape;
|
||||||
|
type PartId = ColliderHandle;
|
||||||
|
|
||||||
|
fn map_typed_part_at(
|
||||||
|
&self,
|
||||||
|
shape_id: Self::PartId,
|
||||||
|
mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape),
|
||||||
|
) {
|
||||||
|
if let Some(collider) = self.colliders.get(shape_id) {
|
||||||
|
if collider.collision_groups.test(self.groups) {
|
||||||
|
f(Some(collider.position()), collider.shape())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_untyped_part_at(
|
||||||
|
&self,
|
||||||
|
shape_id: Self::PartId,
|
||||||
|
mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape),
|
||||||
|
) {
|
||||||
|
self.map_typed_part_at(shape_id, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_quadtree(&self) -> &SimdQuadTree<ColliderHandle> {
|
||||||
|
&self.query_pipeline.quadtree
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for QueryPipeline {
|
impl Default for QueryPipeline {
|
||||||
@@ -21,7 +74,27 @@ impl Default for QueryPipeline {
|
|||||||
impl QueryPipeline {
|
impl QueryPipeline {
|
||||||
/// Initializes an empty query pipeline.
|
/// Initializes an empty query pipeline.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
Self::with_query_dispatcher(DefaultQueryDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_composite_shape<'a>(
|
||||||
|
&'a self,
|
||||||
|
colliders: &'a ColliderSet,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
) -> QueryPipelineAsCompositeShape<'a> {
|
||||||
|
QueryPipelineAsCompositeShape {
|
||||||
|
query_pipeline: self,
|
||||||
|
colliders,
|
||||||
|
groups,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_query_dispatcher<D>(d: D) -> Self
|
||||||
|
where
|
||||||
|
D: 'static + QueryDispatcher,
|
||||||
|
{
|
||||||
Self {
|
Self {
|
||||||
|
query_dispatcher: Arc::new(d),
|
||||||
quadtree: SimdQuadTree::new(),
|
quadtree: SimdQuadTree::new(),
|
||||||
tree_built: false,
|
tree_built: false,
|
||||||
dilation_factor: 0.01,
|
dilation_factor: 0.01,
|
||||||
@@ -59,40 +132,46 @@ impl QueryPipeline {
|
|||||||
/// - `position`: the position of this shape.
|
/// - `position`: the position of this shape.
|
||||||
/// - `ray`: the ray to cast.
|
/// - `ray`: the ray to cast.
|
||||||
/// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
|
/// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
|
||||||
/// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `f32::MAX` for an unbounded ray.
|
/// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
|
||||||
pub fn cast_ray<'a>(
|
pub fn cast_ray(
|
||||||
&self,
|
&self,
|
||||||
colliders: &'a ColliderSet,
|
colliders: &ColliderSet,
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
max_toi: f32,
|
max_toi: Real,
|
||||||
|
solid: bool,
|
||||||
groups: InteractionGroups,
|
groups: InteractionGroups,
|
||||||
) -> Option<(ColliderHandle, &'a Collider, RayIntersection)> {
|
) -> Option<(ColliderHandle, Real)> {
|
||||||
// TODO: avoid allocation?
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
let mut inter = Vec::new();
|
let mut visitor =
|
||||||
self.quadtree.cast_ray(ray, max_toi, &mut inter);
|
RayCompositeShapeToiBestFirstVisitor::new(&pipeline_shape, ray, max_toi, solid);
|
||||||
|
|
||||||
let mut best = f32::MAX;
|
self.quadtree.traverse_best_first(&mut visitor).map(|h| h.1)
|
||||||
let mut result = None;
|
}
|
||||||
|
|
||||||
for handle in inter {
|
/// Find the closest intersection between a ray and a set of collider.
|
||||||
if let Some(collider) = colliders.get(handle) {
|
///
|
||||||
if collider.collision_groups.test(groups) {
|
/// # Parameters
|
||||||
if let Some(inter) = collider.shape().cast_ray_and_get_normal(
|
/// - `position`: the position of this shape.
|
||||||
collider.position(),
|
/// - `ray`: the ray to cast.
|
||||||
ray,
|
/// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
|
||||||
max_toi,
|
/// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
|
||||||
true,
|
pub fn cast_ray_and_get_normal(
|
||||||
) {
|
&self,
|
||||||
if inter.toi < best {
|
colliders: &ColliderSet,
|
||||||
best = inter.toi;
|
ray: &Ray,
|
||||||
result = Some((handle, collider, inter));
|
max_toi: Real,
|
||||||
}
|
solid: bool,
|
||||||
}
|
groups: InteractionGroups,
|
||||||
}
|
) -> Option<(ColliderHandle, RayIntersection)> {
|
||||||
}
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
}
|
let mut visitor = RayCompositeShapeToiAndNormalBestFirstVisitor::new(
|
||||||
|
&pipeline_shape,
|
||||||
|
ray,
|
||||||
|
max_toi,
|
||||||
|
solid,
|
||||||
|
);
|
||||||
|
|
||||||
result
|
self.quadtree.traverse_best_first(&mut visitor).map(|h| h.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Find the all intersections between a ray and a set of collider and passes them to a callback.
|
/// Find the all intersections between a ray and a set of collider and passes them to a callback.
|
||||||
@@ -101,7 +180,7 @@ impl QueryPipeline {
|
|||||||
/// - `position`: the position of this shape.
|
/// - `position`: the position of this shape.
|
||||||
/// - `ray`: the ray to cast.
|
/// - `ray`: the ray to cast.
|
||||||
/// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
|
/// - `max_toi`: the maximum time-of-impact that can be reported by this cast. This effectively
|
||||||
/// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `f32::MAX` for an unbounded ray.
|
/// limits the length of the ray to `ray.dir.norm() * max_toi`. Use `Real::MAX` for an unbounded ray.
|
||||||
/// - `callback`: function executed on each collider for which a ray intersection has been found.
|
/// - `callback`: function executed on each collider for which a ray intersection has been found.
|
||||||
/// There is no guarantees on the order the results will be yielded. If this callback returns `false`,
|
/// There is no guarantees on the order the results will be yielded. If this callback returns `false`,
|
||||||
/// this method will exit early, ignory any further raycast.
|
/// this method will exit early, ignory any further raycast.
|
||||||
@@ -109,7 +188,7 @@ impl QueryPipeline {
|
|||||||
&self,
|
&self,
|
||||||
colliders: &'a ColliderSet,
|
colliders: &'a ColliderSet,
|
||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
max_toi: f32,
|
max_toi: Real,
|
||||||
groups: InteractionGroups,
|
groups: InteractionGroups,
|
||||||
mut callback: impl FnMut(ColliderHandle, &'a Collider, RayIntersection) -> bool,
|
mut callback: impl FnMut(ColliderHandle, &'a Collider, RayIntersection) -> bool,
|
||||||
) {
|
) {
|
||||||
@@ -135,18 +214,106 @@ impl QueryPipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Find up to one collider intersecting the given shape.
|
||||||
|
fn intersection_with_shape(
|
||||||
|
&self,
|
||||||
|
colliders: &ColliderSet,
|
||||||
|
shape_pos: &Isometry<Real>,
|
||||||
|
shape: &dyn Shape,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
) -> Option<ColliderHandle> {
|
||||||
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
|
let mut visitor = IntersectionCompositeShapeShapeBestFirstVisitor::new(
|
||||||
|
&*self.query_dispatcher,
|
||||||
|
shape_pos,
|
||||||
|
&pipeline_shape,
|
||||||
|
shape,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.quadtree
|
||||||
|
.traverse_best_first(&mut visitor)
|
||||||
|
.map(|h| (h.1 .0))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: intersections_with_point (collect all colliders containing the point).
|
||||||
|
|
||||||
|
/// Projects a point on the scene.
|
||||||
|
fn project_point(
|
||||||
|
&self,
|
||||||
|
colliders: &ColliderSet,
|
||||||
|
point: &Point<Real>,
|
||||||
|
solid: bool,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
) -> Option<(ColliderHandle, PointProjection)> {
|
||||||
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
|
let mut visitor =
|
||||||
|
PointCompositeShapeProjBestFirstVisitor::new(&pipeline_shape, point, solid);
|
||||||
|
|
||||||
|
self.quadtree
|
||||||
|
.traverse_best_first(&mut visitor)
|
||||||
|
.map(|h| (h.1 .1, h.1 .0))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Projects a point on the scene and get
|
||||||
|
fn project_point_and_get_feature(
|
||||||
|
&self,
|
||||||
|
colliders: &ColliderSet,
|
||||||
|
point: &Point<Real>,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
) -> Option<(ColliderHandle, PointProjection, FeatureId)> {
|
||||||
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
|
let mut visitor =
|
||||||
|
PointCompositeShapeProjWithFeatureBestFirstVisitor::new(&pipeline_shape, point, false);
|
||||||
|
self.quadtree
|
||||||
|
.traverse_best_first(&mut visitor)
|
||||||
|
.map(|h| (h.1 .1 .0, h.1 .0, h.1 .1 .1))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cast_shape<'a>(
|
pub fn cast_shape<'a>(
|
||||||
&self,
|
&self,
|
||||||
colliders: &'a ColliderSet,
|
colliders: &'a ColliderSet,
|
||||||
shape_pos: &Isometry<Real>,
|
shape_pos: &Isometry<Real>,
|
||||||
|
shape_vel: &Vector<Real>,
|
||||||
shape: &dyn Shape,
|
shape: &dyn Shape,
|
||||||
max_toi: f32,
|
max_toi: Real,
|
||||||
|
target_distance: Real,
|
||||||
groups: InteractionGroups,
|
groups: InteractionGroups,
|
||||||
) -> Option<(ColliderHandle, &'a Collider, TOI)> {
|
) -> Option<(ColliderHandle, TOI)> {
|
||||||
unimplemented!()
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
|
let mut visitor = TOICompositeShapeShapeBestFirstVisitor::new(
|
||||||
|
&*self.query_dispatcher,
|
||||||
|
shape_pos,
|
||||||
|
shape_vel,
|
||||||
|
&pipeline_shape,
|
||||||
|
shape,
|
||||||
|
max_toi,
|
||||||
|
target_distance,
|
||||||
|
);
|
||||||
|
self.quadtree.traverse_best_first(&mut visitor).map(|h| h.1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nonlinear_cast_shape(
|
||||||
|
&self,
|
||||||
|
colliders: &ColliderSet,
|
||||||
|
shape_motion: &dyn RigidMotion,
|
||||||
|
shape: &dyn Shape,
|
||||||
|
max_toi: Real,
|
||||||
|
target_distance: Real,
|
||||||
|
groups: InteractionGroups,
|
||||||
|
) -> Option<(ColliderHandle, TOI)> {
|
||||||
|
let pipeline_shape = self.as_composite_shape(colliders, groups);
|
||||||
|
let mut visitor = NonlinearTOICompositeShapeShapeBestFirstVisitor::new(
|
||||||
|
&*self.query_dispatcher,
|
||||||
|
shape_motion,
|
||||||
|
&pipeline_shape,
|
||||||
|
shape,
|
||||||
|
max_toi,
|
||||||
|
target_distance,
|
||||||
|
);
|
||||||
|
self.quadtree.traverse_best_first(&mut visitor).map(|h| h.1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
/// Gets all the colliders with a shape intersecting the given `shape`.
|
/// Gets all the colliders with a shape intersecting the given `shape`.
|
||||||
pub fn intersections_with_shape<'a>(
|
pub fn intersections_with_shape<'a>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -1032,10 +1032,12 @@ impl Testbed {
|
|||||||
&self.physics.colliders,
|
&self.physics.colliders,
|
||||||
&ray,
|
&ray,
|
||||||
f32::MAX,
|
f32::MAX,
|
||||||
|
true,
|
||||||
InteractionGroups::all(),
|
InteractionGroups::all(),
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((_, collider, _)) = hit {
|
if let Some((handle, _)) = hit {
|
||||||
|
let collider = &self.physics.colliders[handle];
|
||||||
if self.physics.bodies[collider.parent()].is_dynamic() {
|
if self.physics.bodies[collider.parent()].is_dynamic() {
|
||||||
self.state.highlighted_body = Some(collider.parent());
|
self.state.highlighted_body = Some(collider.parent());
|
||||||
for node in self.graphics.body_nodes_mut(collider.parent()).unwrap() {
|
for node in self.graphics.body_nodes_mut(collider.parent()).unwrap() {
|
||||||
|
|||||||
Reference in New Issue
Block a user