Make the WQuadTree more generic and use it as the trimesh acceleration structure.

This commit is contained in:
Crozet Sébastien
2020-10-06 10:46:59 +02:00
parent 721db2d49e
commit 8e432b298b
8 changed files with 146 additions and 98 deletions

View File

@@ -144,7 +144,7 @@ impl WAABBHierarchy {
if let Some(waabb2) = level.get(*i) { if let Some(waabb2) = level.get(*i) {
// NOTE: using `intersect.bitmask()` and performing bit comparisons // NOTE: using `intersect.bitmask()` and performing bit comparisons
// is much more efficient than testing if each intersect.extract(i) is true. // is much more efficient than testing if each intersect.extract(i) is true.
let intersect = waabb1.intersects_lanewise(waabb2); let intersect = waabb1.intersects(waabb2);
let bitmask = intersect.bitmask(); let bitmask = intersect.bitmask();
for j in 0..SIMD_WIDTH { for j in 0..SIMD_WIDTH {

View File

@@ -38,7 +38,7 @@ impl ColliderSet {
} }
/// Iterate through all the colliders on this set. /// Iterate through all the colliders on this set.
pub fn iter(&self) -> impl Iterator<Item = (ColliderHandle, &Collider)> { pub fn iter(&self) -> impl ExactSizeIterator<Item = (ColliderHandle, &Collider)> {
self.colliders.iter() self.colliders.iter()
} }

View File

@@ -5,7 +5,7 @@ use crate::geometry::{Collider, ContactManifold, Shape, Trimesh, WAABBHierarchyI
use crate::ncollide::bounding_volume::{BoundingVolume, AABB}; use crate::ncollide::bounding_volume::{BoundingVolume, AABB};
pub struct TrimeshShapeContactGeneratorWorkspace { pub struct TrimeshShapeContactGeneratorWorkspace {
interferences: WAABBHierarchyIntersections, interferences: Vec<usize>,
local_aabb2: AABB<f32>, local_aabb2: AABB<f32>,
old_interferences: Vec<usize>, old_interferences: Vec<usize>,
old_manifolds: Vec<ContactManifold>, old_manifolds: Vec<ContactManifold>,
@@ -14,7 +14,7 @@ pub struct TrimeshShapeContactGeneratorWorkspace {
impl TrimeshShapeContactGeneratorWorkspace { impl TrimeshShapeContactGeneratorWorkspace {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
interferences: WAABBHierarchyIntersections::new(), interferences: Vec::new(),
local_aabb2: AABB::new_invalid(), local_aabb2: AABB::new_invalid(),
old_interferences: Vec::new(), old_interferences: Vec::new(),
old_manifolds: Vec::new(), old_manifolds: Vec::new(),
@@ -74,7 +74,7 @@ fn do_generate_contacts(
let local_aabb2 = new_local_aabb2; // .loosened(ctxt.prediction_distance * 2.0); // FIXME: what would be the best value? let local_aabb2 = new_local_aabb2; // .loosened(ctxt.prediction_distance * 2.0); // FIXME: what would be the best value?
std::mem::swap( std::mem::swap(
&mut workspace.old_interferences, &mut workspace.old_interferences,
workspace.interferences.computed_interferences_mut(), &mut workspace.interferences,
); );
std::mem::swap(&mut workspace.old_manifolds, &mut ctxt.pair.manifolds); std::mem::swap(&mut workspace.old_manifolds, &mut ctxt.pair.manifolds);
ctxt.pair.manifolds.clear(); ctxt.pair.manifolds.clear();
@@ -108,16 +108,14 @@ fn do_generate_contacts(
// workspace.old_manifolds.len() // workspace.old_manifolds.len()
// ); // );
trimesh1 workspace.interferences = trimesh1.waabbs().intersect_aabb(&local_aabb2);
.waabbs()
.compute_interferences_with(local_aabb2, &mut workspace.interferences);
workspace.local_aabb2 = local_aabb2; workspace.local_aabb2 = local_aabb2;
} }
/* /*
* Dispatch to the specific solver by keeping the previous manifold if we already had one. * Dispatch to the specific solver by keeping the previous manifold if we already had one.
*/ */
let new_interferences = workspace.interferences.computed_interferences(); let new_interferences = &workspace.interferences;
let mut old_inter_it = workspace.old_interferences.drain(..).peekable(); let mut old_inter_it = workspace.old_interferences.drain(..).peekable();
let mut old_manifolds_it = workspace.old_manifolds.drain(..); let mut old_manifolds_it = workspace.old_manifolds.drain(..);

View File

@@ -5,7 +5,7 @@ use crate::geometry::{Collider, Proximity, Shape, Trimesh, WAABBHierarchyInterse
use crate::ncollide::bounding_volume::{BoundingVolume, AABB}; use crate::ncollide::bounding_volume::{BoundingVolume, AABB};
pub struct TrimeshShapeProximityDetectorWorkspace { pub struct TrimeshShapeProximityDetectorWorkspace {
interferences: WAABBHierarchyIntersections, interferences: Vec<usize>,
local_aabb2: AABB<f32>, local_aabb2: AABB<f32>,
old_interferences: Vec<usize>, old_interferences: Vec<usize>,
} }
@@ -13,7 +13,7 @@ pub struct TrimeshShapeProximityDetectorWorkspace {
impl TrimeshShapeProximityDetectorWorkspace { impl TrimeshShapeProximityDetectorWorkspace {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
interferences: WAABBHierarchyIntersections::new(), interferences: Vec::new(),
local_aabb2: AABB::new_invalid(), local_aabb2: AABB::new_invalid(),
old_interferences: Vec::new(), old_interferences: Vec::new(),
} }
@@ -67,19 +67,17 @@ fn do_detect_proximity(
let local_aabb2 = new_local_aabb2; // .loosened(ctxt.prediction_distance * 2.0); // FIXME: what would be the best value? let local_aabb2 = new_local_aabb2; // .loosened(ctxt.prediction_distance * 2.0); // FIXME: what would be the best value?
std::mem::swap( std::mem::swap(
&mut workspace.old_interferences, &mut workspace.old_interferences,
&mut workspace.interferences.computed_interferences_mut(), &mut workspace.interferences,
); );
trimesh1 workspace.interferences = trimesh1.waabbs().intersect_aabb(&local_aabb2);
.waabbs()
.compute_interferences_with(local_aabb2, &mut workspace.interferences);
workspace.local_aabb2 = local_aabb2; workspace.local_aabb2 = local_aabb2;
} }
/* /*
* Dispatch to the specific solver by keeping the previous manifold if we already had one. * Dispatch to the specific solver by keeping the previous manifold if we already had one.
*/ */
let new_interferences = workspace.interferences.computed_interferences(); let new_interferences = &workspace.interferences;
let mut old_inter_it = workspace.old_interferences.drain(..).peekable(); let mut old_inter_it = workspace.old_interferences.drain(..).peekable();
let mut best_proximity = Proximity::Disjoint; let mut best_proximity = Proximity::Disjoint;

View File

@@ -1,4 +1,4 @@
use crate::geometry::{Triangle, WAABBHierarchy}; use crate::geometry::{Triangle, WQuadtree};
use crate::math::{Isometry, Point}; use crate::math::{Isometry, Point};
use na::Point3; use na::Point3;
use ncollide::bounding_volume::{HasBoundingVolume, AABB}; use ncollide::bounding_volume::{HasBoundingVolume, AABB};
@@ -7,7 +7,7 @@ use ncollide::bounding_volume::{HasBoundingVolume, AABB};
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
/// A triangle mesh. /// A triangle mesh.
pub struct Trimesh { pub struct Trimesh {
waabb_tree: WAABBHierarchy, wquadtree: WQuadtree<usize>,
aabb: AABB<f32>, aabb: AABB<f32>,
vertices: Vec<Point<f32>>, vertices: Vec<Point<f32>>,
indices: Vec<Point3<u32>>, indices: Vec<Point3<u32>>,
@@ -25,41 +25,24 @@ impl Trimesh {
"A triangle mesh must contain at least one triangle." "A triangle mesh must contain at least one triangle."
); );
// z-sort the indices.
// indices.sort_unstable_by(|idx, jdx| {
// let ti = Triangle::new(
// vertices[idx[0] as usize],
// vertices[idx[1] as usize],
// vertices[idx[2] as usize],
// );
// let tj = Triangle::new(
// vertices[jdx[0] as usize],
// vertices[jdx[1] as usize],
// vertices[jdx[2] as usize],
// );
// let center_i = (ti.a.coords + ti.b.coords + ti.c.coords) / 3.0;
// let center_j = (tj.a.coords + tj.b.coords + tj.c.coords) / 3.0;
// crate::geometry::z_cmp_floats(center_i.as_slice(), center_j.as_slice())
// .unwrap_or(std::cmp::Ordering::Equal)
// });
let aabb = AABB::from_points(&vertices); let aabb = AABB::from_points(&vertices);
let data = indices.iter().enumerate().map(|(i, idx)| {
let aabb = Triangle::new(
vertices[idx[0] as usize],
vertices[idx[1] as usize],
vertices[idx[2] as usize],
)
.local_bounding_volume();
(i, aabb)
});
let aabbs: Vec<_> = indices let mut wquadtree = WQuadtree::new();
.iter() // NOTE: we apply no dilation factor because we won't
.map(|idx| { // update this tree dynamically.
Triangle::new( wquadtree.clear_and_rebuild(data, 0.0);
vertices[idx[0] as usize],
vertices[idx[1] as usize],
vertices[idx[2] as usize],
)
.local_bounding_volume()
})
.collect();
let waabb_tree = WAABBHierarchy::new(&aabbs);
Self { Self {
waabb_tree, wquadtree,
aabb, aabb,
vertices, vertices,
indices, indices,
@@ -71,8 +54,8 @@ impl Trimesh {
self.aabb.transform_by(pos) self.aabb.transform_by(pos)
} }
pub(crate) fn waabbs(&self) -> &WAABBHierarchy { pub(crate) fn waabbs(&self) -> &WQuadtree<usize> {
&self.waabb_tree &self.wquadtree
} }
/// The number of triangles forming this mesh. /// The number of triangles forming this mesh.

View File

@@ -156,7 +156,7 @@ impl WAABB {
} }
#[cfg(feature = "dim2")] #[cfg(feature = "dim2")]
pub fn contains_lanewise(&self, other: &WAABB) -> SimdBool { pub fn contains(&self, other: &WAABB) -> SimdBool {
self.mins.x.simd_le(other.mins.x) self.mins.x.simd_le(other.mins.x)
& self.mins.y.simd_le(other.mins.y) & self.mins.y.simd_le(other.mins.y)
& self.maxs.x.simd_ge(other.maxs.x) & self.maxs.x.simd_ge(other.maxs.x)
@@ -164,7 +164,7 @@ impl WAABB {
} }
#[cfg(feature = "dim3")] #[cfg(feature = "dim3")]
pub fn contains_lanewise(&self, other: &WAABB) -> SimdBool { pub fn contains(&self, other: &WAABB) -> SimdBool {
self.mins.x.simd_le(other.mins.x) self.mins.x.simd_le(other.mins.x)
& self.mins.y.simd_le(other.mins.y) & self.mins.y.simd_le(other.mins.y)
& self.mins.z.simd_le(other.mins.z) & self.mins.z.simd_le(other.mins.z)
@@ -174,7 +174,7 @@ impl WAABB {
} }
#[cfg(feature = "dim2")] #[cfg(feature = "dim2")]
pub fn intersects_lanewise(&self, other: &WAABB) -> SimdBool { pub fn intersects(&self, other: &WAABB) -> SimdBool {
self.mins.x.simd_le(other.maxs.x) self.mins.x.simd_le(other.maxs.x)
& other.mins.x.simd_le(self.maxs.x) & other.mins.x.simd_le(self.maxs.x)
& self.mins.y.simd_le(other.maxs.y) & self.mins.y.simd_le(other.maxs.y)
@@ -182,7 +182,7 @@ impl WAABB {
} }
#[cfg(feature = "dim3")] #[cfg(feature = "dim3")]
pub fn intersects_lanewise(&self, other: &WAABB) -> SimdBool { pub fn intersects(&self, other: &WAABB) -> SimdBool {
self.mins.x.simd_le(other.maxs.x) self.mins.x.simd_le(other.maxs.x)
& other.mins.x.simd_le(self.maxs.x) & other.mins.x.simd_le(self.maxs.x)
& self.mins.y.simd_le(other.maxs.y) & self.mins.y.simd_le(other.maxs.y)

View File

@@ -7,6 +7,31 @@ use simba::simd::{SimdBool, SimdValue};
use std::collections::VecDeque; use std::collections::VecDeque;
use std::ops::Range; use std::ops::Range;
pub trait IndexedData: Copy {
fn default() -> Self;
fn index(&self) -> usize;
}
impl IndexedData for usize {
fn default() -> Self {
u32::MAX as usize
}
fn index(&self) -> usize {
*self
}
}
impl IndexedData for ColliderHandle {
fn default() -> Self {
ColliderSet::invalid_handle()
}
fn index(&self) -> usize {
self.into_raw_parts().0
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
struct NodeIndex { struct NodeIndex {
@@ -41,38 +66,32 @@ struct WQuadtreeNode {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
struct WQuadtreeProxy { struct WQuadtreeProxy<T> {
node: NodeIndex, node: NodeIndex,
handle: ColliderHandle, // The collider handle. TODO: only set the collider generation here? data: T, // The collider data. TODO: only set the collider generation here?
} }
impl WQuadtreeProxy { impl<T: IndexedData> WQuadtreeProxy<T> {
fn invalid() -> Self { fn invalid() -> Self {
Self { Self {
node: NodeIndex::invalid(), node: NodeIndex::invalid(),
handle: ColliderSet::invalid_handle(), data: T::default(),
} }
} }
} }
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
pub struct WQuadtree { #[derive(Clone, Debug)]
pub struct WQuadtree<T> {
nodes: Vec<WQuadtreeNode>, nodes: Vec<WQuadtreeNode>,
dirty_nodes: VecDeque<u32>, dirty_nodes: VecDeque<u32>,
proxies: Vec<WQuadtreeProxy>, proxies: Vec<WQuadtreeProxy<T>>,
} }
impl WQuadtree { // FIXME: this should be generic too.
pub fn new() -> Self { impl WQuadtree<ColliderHandle> {
WQuadtree { pub fn pre_update(&mut self, data: ColliderHandle) {
nodes: Vec::new(), let id = data.into_raw_parts().0;
dirty_nodes: VecDeque::new(),
proxies: Vec::new(),
}
}
pub fn pre_update(&mut self, handle: ColliderHandle) {
let id = handle.into_raw_parts().0;
let node_id = self.proxies[id].node.index; let node_id = self.proxies[id].node.index;
let node = &mut self.nodes[node_id as usize]; let node = &mut self.nodes[node_id as usize];
if !node.dirty { if !node.dirty {
@@ -86,7 +105,7 @@ impl WQuadtree {
let dilation_factor = SimdFloat::splat(dilation_factor); let dilation_factor = SimdFloat::splat(dilation_factor);
while let Some(id) = self.dirty_nodes.pop_front() { while let Some(id) = self.dirty_nodes.pop_front() {
// NOTE: this will handle the case where we reach the root of the tree. // NOTE: this will data the case where we reach the root of the tree.
if let Some(node) = self.nodes.get(id as usize) { if let Some(node) = self.nodes.get(id as usize) {
// Compute the new WAABB. // Compute the new WAABB.
let mut new_aabbs = [AABB::new_invalid(); SIMD_WIDTH]; let mut new_aabbs = [AABB::new_invalid(); SIMD_WIDTH];
@@ -94,7 +113,7 @@ impl WQuadtree {
if node.leaf { if node.leaf {
// We are in a leaf: compute the colliders' AABBs. // We are in a leaf: compute the colliders' AABBs.
if let Some(proxy) = self.proxies.get(*child_id as usize) { if let Some(proxy) = self.proxies.get(*child_id as usize) {
let collider = &colliders[proxy.handle]; let collider = &colliders[proxy.data];
*new_aabb = collider.compute_aabb(); *new_aabb = collider.compute_aabb();
} }
} else { } else {
@@ -107,7 +126,7 @@ impl WQuadtree {
let node = &mut self.nodes[id as usize]; let node = &mut self.nodes[id as usize];
let new_waabb = WAABB::from(new_aabbs); let new_waabb = WAABB::from(new_aabbs);
if !node.waabb.contains_lanewise(&new_waabb).all() { if !node.waabb.contains(&new_waabb).all() {
node.waabb = new_waabb; node.waabb = new_waabb;
node.waabb.dilate_by_factor(dilation_factor); node.waabb.dilate_by_factor(dilation_factor);
self.dirty_nodes.push_back(node.parent.index); self.dirty_nodes.push_back(node.parent.index);
@@ -116,31 +135,40 @@ impl WQuadtree {
} }
} }
} }
}
pub fn clear_and_rebuild(&mut self, colliders: &ColliderSet, dilation_factor: f32) { impl<T: IndexedData> WQuadtree<T> {
pub fn new() -> Self {
WQuadtree {
nodes: Vec::new(),
dirty_nodes: VecDeque::new(),
proxies: Vec::new(),
}
}
pub fn clear_and_rebuild(
&mut self,
data: impl ExactSizeIterator<Item = (T, AABB)>,
dilation_factor: f32,
) {
self.nodes.clear(); self.nodes.clear();
self.proxies.clear(); self.proxies.clear();
// Create proxies. // Create proxies.
let mut indices = Vec::with_capacity(colliders.len()); let mut indices = Vec::with_capacity(data.len());
self.proxies = vec![WQuadtreeProxy::invalid(); colliders.len()]; let mut aabbs = vec![AABB::new_invalid(); data.len()];
self.proxies = vec![WQuadtreeProxy::invalid(); data.len()];
for (handle, collider) in colliders.iter() { for (data, aabb) in data {
let index = handle.into_raw_parts().0; let index = data.index();
if index >= self.proxies.len() { if index >= self.proxies.len() {
self.proxies.resize(index + 1, WQuadtreeProxy::invalid()); self.proxies.resize(index + 1, WQuadtreeProxy::invalid());
aabbs.resize(index + 1, AABB::new_invalid());
} }
self.proxies[index].handle = handle; self.proxies[index].data = data;
indices.push(index);
}
// Compute AABBs.
let mut aabbs = vec![AABB::new_invalid(); self.proxies.len()];
for (handle, collider) in colliders.iter() {
let index = handle.into_raw_parts().0;
let aabb = collider.compute_aabb();
aabbs[index] = aabb; aabbs[index] = aabb;
indices.push(index);
} }
// Build the tree recursively. // Build the tree recursively.
@@ -279,7 +307,47 @@ impl WQuadtree {
(id, my_aabb) (id, my_aabb)
} }
pub fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Vec<ColliderHandle> { // FIXME: implement a visitor pattern to merge intersect_aabb
// and intersect_ray into a single method.
pub fn intersect_aabb(&self, aabb: &AABB) -> Vec<T> {
let mut res = Vec::new();
if self.nodes.is_empty() {
return res;
}
// Special case for the root.
let mut stack = vec![0u32];
let waabb = WAABB::splat(*aabb);
while let Some(inode) = stack.pop() {
let node = self.nodes[inode as usize];
let intersections = node.waabb.intersects(&waabb);
let bitmask = intersections.bitmask();
for ii in 0..SIMD_WIDTH {
if (bitmask & (1 << ii)) != 0 {
if node.leaf {
// We found a leaf!
// Unfortunately, invalid AABBs return a intersection as well.
if let Some(proxy) = self.proxies.get(node.children[ii] as usize) {
res.push(proxy.data);
}
} else {
// Internal node, visit the child.
// Unfortunately, we have this check because invalid AABBs
// return a intersection as well.
if node.children[ii] as usize <= self.nodes.len() {
stack.push(node.children[ii]);
}
}
}
}
}
res
}
pub fn cast_ray(&self, ray: &Ray, max_toi: f32) -> Vec<T> {
let mut res = Vec::new(); let mut res = Vec::new();
if self.nodes.is_empty() { if self.nodes.is_empty() {
@@ -301,7 +369,7 @@ impl WQuadtree {
// We found a leaf! // We found a leaf!
// Unfortunately, invalid AABBs return a hit as well. // Unfortunately, invalid AABBs return a hit as well.
if let Some(proxy) = self.proxies.get(node.children[ii] as usize) { if let Some(proxy) = self.proxies.get(node.children[ii] as usize) {
res.push(proxy.handle); res.push(proxy.data);
} }
} else { } else {
// Internal node, visit the child. // Internal node, visit the child.
@@ -324,14 +392,14 @@ struct WQuadtreeIncrementalBuilderStep {
parent: NodeIndex, parent: NodeIndex,
} }
struct WQuadtreeIncrementalBuilder { struct WQuadtreeIncrementalBuilder<T> {
quadtree: WQuadtree, quadtree: WQuadtree<T>,
to_insert: Vec<WQuadtreeIncrementalBuilderStep>, to_insert: Vec<WQuadtreeIncrementalBuilderStep>,
aabbs: Vec<AABB>, aabbs: Vec<AABB>,
indices: Vec<usize>, indices: Vec<usize>,
} }
impl WQuadtreeIncrementalBuilder { impl<T: IndexedData> WQuadtreeIncrementalBuilder<T> {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
quadtree: WQuadtree::new(), quadtree: WQuadtree::new(),

View File

@@ -8,7 +8,7 @@ use ncollide::bounding_volume::BoundingVolume;
/// 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))]
pub struct QueryPipeline { pub struct QueryPipeline {
quadtree: WQuadtree, quadtree: WQuadtree<ColliderHandle>,
tree_built: bool, tree_built: bool,
dilation_factor: f32, dilation_factor: f32,
} }
@@ -32,9 +32,10 @@ impl QueryPipeline {
/// Update the acceleration structure on the query pipeline. /// Update the acceleration structure on the query pipeline.
pub fn update(&mut self, bodies: &RigidBodySet, colliders: &ColliderSet) { pub fn update(&mut self, bodies: &RigidBodySet, colliders: &ColliderSet) {
if !self.tree_built { if !self.tree_built {
self.quadtree let data = colliders.iter().map(|(h, c)| (h, c.compute_aabb()));
.clear_and_rebuild(colliders, self.dilation_factor); self.quadtree.clear_and_rebuild(data, self.dilation_factor);
// self.tree_built = true; // FIXME: uncomment this once we handle insertion/removals properly. // FIXME: uncomment this once we handle insertion/removals properly.
// self.tree_built = true;
return; return;
} }