Skip to content

Commit 5a7aa0f

Browse files
committed
Introduce ScaledTriMesh shape
1 parent d1c2bd6 commit 5a7aa0f

File tree

7 files changed

+292
-6
lines changed

7 files changed

+292
-6
lines changed

src/bounding_volume/bounding_sphere_trimesh.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::bounding_volume::BoundingSphere;
22
use crate::math::{Isometry, Real};
3-
use crate::shape::TriMesh;
3+
use crate::shape::{ScaledTriMesh, TriMesh};
44

55
impl TriMesh {
66
/// Computes the world-space bounding sphere of this triangle mesh, transformed by `pos`.
@@ -15,3 +15,17 @@ impl TriMesh {
1515
self.local_aabb().bounding_sphere()
1616
}
1717
}
18+
19+
impl ScaledTriMesh {
20+
/// Computes the world-space bounding sphere of this triangle mesh, transformed by `pos`.
21+
#[inline]
22+
pub fn bounding_sphere(&self, pos: &Isometry<Real>) -> BoundingSphere {
23+
self.local_aabb().bounding_sphere().transform_by(pos)
24+
}
25+
26+
/// Computes the local-space bounding sphere of this triangle mesh.
27+
#[inline]
28+
pub fn local_bounding_sphere(&self) -> BoundingSphere {
29+
self.local_aabb().bounding_sphere()
30+
}
31+
}

src/query/point/point_composite_shape.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use crate::query::{
77
visitors::CompositePointContainmentTest, PointProjection, PointQuery, PointQueryWithLocation,
88
};
99
use crate::shape::{
10-
Compound, FeatureId, Polyline, SegmentPointLocation, TriMesh, TrianglePointLocation,
11-
TypedSimdCompositeShape,
10+
Compound, FeatureId, Polyline, ScaledTriMesh, SegmentPointLocation, TriMesh,
11+
TrianglePointLocation, TypedSimdCompositeShape,
1212
};
1313
use na;
1414
use simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue};
@@ -70,6 +70,34 @@ impl PointQuery for TriMesh {
7070
}
7171
}
7272

73+
impl PointQuery for ScaledTriMesh {
74+
#[inline]
75+
fn project_local_point(&self, point: &Point<Real>, solid: bool) -> PointProjection {
76+
self.project_local_point_and_get_location(point, solid).0
77+
}
78+
79+
#[inline]
80+
fn project_local_point_and_get_feature(
81+
&self,
82+
point: &Point<Real>,
83+
) -> (PointProjection, FeatureId) {
84+
let mut visitor =
85+
PointCompositeShapeProjWithFeatureBestFirstVisitor::new(self, point, false);
86+
let (proj, (id, _feature)) = self.quadtree().traverse_best_first(&mut visitor).unwrap().1;
87+
let feature_id = FeatureId::Face(id);
88+
(proj, feature_id)
89+
}
90+
91+
// FIXME: implement distance_to_point too?
92+
93+
#[inline]
94+
fn contains_local_point(&self, point: &Point<Real>) -> bool {
95+
let mut visitor = CompositePointContainmentTest::new(self, point);
96+
self.quadtree().traverse_depth_first(&mut visitor);
97+
visitor.found
98+
}
99+
}
100+
73101
impl PointQuery for Compound {
74102
#[inline]
75103
fn project_local_point(&self, point: &Point<Real>, solid: bool) -> PointProjection {
@@ -127,6 +155,21 @@ impl PointQueryWithLocation for TriMesh {
127155
}
128156
}
129157

158+
impl PointQueryWithLocation for ScaledTriMesh {
159+
type Location = (u32, TrianglePointLocation);
160+
161+
#[inline]
162+
fn project_local_point_and_get_location(
163+
&self,
164+
point: &Point<Real>,
165+
solid: bool,
166+
) -> (PointProjection, Self::Location) {
167+
let mut visitor =
168+
PointCompositeShapeProjWithLocationBestFirstVisitor::new(self, point, solid);
169+
self.quadtree().traverse_best_first(&mut visitor).unwrap().1
170+
}
171+
}
172+
130173
/*
131174
* Visitors
132175
*/

src/query/ray/ray_composite_shape.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use crate::bounding_volume::SimdAABB;
22
use crate::math::{Real, SimdBool, SimdReal, SIMD_WIDTH};
33
use crate::partitioning::{SimdBestFirstVisitStatus, SimdBestFirstVisitor};
44
use crate::query::{Ray, RayCast, RayIntersection, SimdRay};
5-
use crate::shape::{Compound, FeatureId, Polyline, TriMesh, TypedSimdCompositeShape};
5+
use crate::shape::{
6+
Compound, FeatureId, Polyline, ScaledTriMesh, TriMesh, TypedSimdCompositeShape,
7+
};
68
use simba::simd::{SimdBool as _, SimdPartialOrd, SimdValue};
79

810
impl RayCast for TriMesh {
@@ -40,6 +42,41 @@ impl RayCast for TriMesh {
4042
}
4143
}
4244

45+
impl RayCast for ScaledTriMesh {
46+
#[inline]
47+
fn cast_local_ray(&self, ray: &Ray, max_toi: Real, solid: bool) -> Option<Real> {
48+
let mut visitor = RayCompositeShapeToiBestFirstVisitor::new(self, ray, max_toi, solid);
49+
50+
self.quadtree()
51+
.traverse_best_first(&mut visitor)
52+
.map(|res| res.1 .1)
53+
}
54+
55+
#[inline]
56+
fn cast_local_ray_and_get_normal(
57+
&self,
58+
ray: &Ray,
59+
max_toi: Real,
60+
solid: bool,
61+
) -> Option<RayIntersection> {
62+
let mut visitor =
63+
RayCompositeShapeToiAndNormalBestFirstVisitor::new(self, ray, max_toi, solid);
64+
65+
self.quadtree()
66+
.traverse_best_first(&mut visitor)
67+
.map(|(_, (best, mut res))| {
68+
// We hit a backface.
69+
// NOTE: we need this for `TriMesh::is_backface` to work properly.
70+
if res.feature == FeatureId::Face(1) {
71+
res.feature = FeatureId::Face(best + self.trimesh().indices().len() as u32)
72+
} else {
73+
res.feature = FeatureId::Face(best);
74+
}
75+
res
76+
})
77+
}
78+
}
79+
4380
impl RayCast for Polyline {
4481
#[inline]
4582
fn cast_local_ray(&self, ray: &Ray, max_toi: Real, solid: bool) -> Option<Real> {

src/shape/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ pub use self::cylinder::Cylinder;
3838
pub use self::heightfield3::{HeightField, HeightFieldCellStatus};
3939
#[cfg(feature = "dim3")]
4040
pub use self::polygonal_feature3d::PolygonalFeature;
41+
pub use self::scaled_trimesh::ScaledTriMesh;
4142
#[cfg(feature = "dim3")]
4243
pub use self::tetrahedron::{Tetrahedron, TetrahedronPointLocation};
4344
pub use self::trimesh::TriMesh;
@@ -91,6 +92,7 @@ mod heightfield3;
9192
#[cfg(feature = "dim3")]
9293
mod polygonal_feature3d;
9394
mod polygonal_feature_map;
95+
mod scaled_trimesh;
9496
#[cfg(feature = "dim3")]
9597
mod tetrahedron;
9698
mod trimesh;

src/shape/scaled_trimesh.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use std::sync::Arc;
2+
3+
use crate::bounding_volume::AABB;
4+
use crate::math::{Isometry, Point, Real, Vector};
5+
use crate::partitioning::QBVH;
6+
use crate::shape::composite_shape::SimdCompositeShape;
7+
use crate::shape::TriMesh;
8+
use crate::shape::{Shape, Triangle, TypedSimdCompositeShape};
9+
10+
#[derive(Clone)]
11+
#[cfg_attr(feature = "serde-serialize", derive(Serialize, Deserialize))]
12+
/// A scaled [`TriMesh`]
13+
pub struct ScaledTriMesh {
14+
/// The underlying triangle mesh
15+
trimesh: Arc<TriMesh>,
16+
/// Scaling factors for each dimension
17+
// This could easily be expanded into any invertible transform, if a use case arises.
18+
scaling_factors: Vector<Real>,
19+
quadtree: QBVH<u32>,
20+
}
21+
22+
impl ScaledTriMesh {
23+
/// Creates a triangle mesh by scaling `trimesh` along each axis
24+
pub fn new(trimesh: Arc<TriMesh>, scaling_factors: Vector<Real>) -> Self {
25+
// Future work: Would it be more efficient to scale trimesh.quadtree rather than building a
26+
// new one from scratch?
27+
let data = trimesh.triangles().enumerate().map(|(i, tri)| {
28+
let aabb = scale_tri(&tri, &scaling_factors).local_aabb();
29+
(i as u32, aabb)
30+
});
31+
let mut quadtree = QBVH::new();
32+
quadtree.clear_and_rebuild(data, 0.0);
33+
Self {
34+
trimesh,
35+
scaling_factors,
36+
quadtree,
37+
}
38+
}
39+
40+
/// The underlying unscaled trimesh
41+
pub fn trimesh(&self) -> &Arc<TriMesh> {
42+
&self.trimesh
43+
}
44+
45+
/// Scaling factors used to derive this shape from the underlying trimesh
46+
pub fn scaling_factors(&self) -> Vector<Real> {
47+
self.scaling_factors
48+
}
49+
50+
/// Compute the axis-aligned bounding box of this triangle mesh.
51+
pub fn aabb(&self, pos: &Isometry<Real>) -> AABB {
52+
self.quadtree.root_aabb().transform_by(pos)
53+
}
54+
55+
/// Gets the local axis-aligned bounding box of this triangle mesh.
56+
pub fn local_aabb(&self) -> &AABB {
57+
self.quadtree.root_aabb()
58+
}
59+
60+
/// The acceleration structure used by this triangle-mesh.
61+
pub fn quadtree(&self) -> &QBVH<u32> {
62+
&self.quadtree
63+
}
64+
65+
/// An iterator through all the scaled triangles of this mesh.
66+
pub fn triangles(&self) -> impl ExactSizeIterator<Item = Triangle> + '_ {
67+
self.trimesh
68+
.triangles()
69+
.map(move |tri| scale_tri(&tri, &self.scaling_factors))
70+
}
71+
72+
/// Get the `i`-th scaled triangle of this mesh.
73+
pub fn triangle(&self, i: u32) -> Triangle {
74+
scale_tri(&self.trimesh.triangle(i), &self.scaling_factors)
75+
}
76+
77+
/// The vertex buffer of this mesh.
78+
pub fn vertices(&self) -> impl ExactSizeIterator<Item = Point<Real>> + '_ {
79+
self.trimesh
80+
.vertices()
81+
.iter()
82+
.map(move |v| v.coords.component_mul(&self.scaling_factors).into())
83+
}
84+
85+
/// The index buffer of this mesh.
86+
pub fn indices(&self) -> &[[u32; 3]] {
87+
self.trimesh.indices()
88+
}
89+
}
90+
91+
fn scale_tri(tri: &Triangle, factors: &Vector<Real>) -> Triangle {
92+
Triangle {
93+
a: tri.a.coords.component_mul(&factors).into(),
94+
b: tri.b.coords.component_mul(&factors).into(),
95+
c: tri.c.coords.component_mul(&factors).into(),
96+
}
97+
}
98+
99+
impl SimdCompositeShape for ScaledTriMesh {
100+
fn map_part_at(&self, i: u32, f: &mut dyn FnMut(Option<&Isometry<Real>>, &dyn Shape)) {
101+
let tri = self.triangle(i);
102+
f(None, &tri)
103+
}
104+
105+
fn quadtree(&self) -> &QBVH<u32> {
106+
&self.quadtree
107+
}
108+
}
109+
110+
impl TypedSimdCompositeShape for ScaledTriMesh {
111+
type PartShape = Triangle;
112+
type PartId = u32;
113+
114+
#[inline(always)]
115+
fn map_typed_part_at(
116+
&self,
117+
i: u32,
118+
mut f: impl FnMut(Option<&Isometry<Real>>, &Self::PartShape),
119+
) {
120+
let tri = self.triangle(i);
121+
f(None, &tri)
122+
}
123+
124+
#[inline(always)]
125+
fn map_untyped_part_at(&self, i: u32, mut f: impl FnMut(Option<&Isometry<Real>>, &dyn Shape)) {
126+
let tri = self.triangle(i);
127+
f(None, &tri)
128+
}
129+
130+
fn typed_quadtree(&self) -> &QBVH<u32> {
131+
&self.quadtree
132+
}
133+
}

src/shape/shape.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use crate::shape::composite_shape::SimdCompositeShape;
77
use crate::shape::SharedShape;
88
use crate::shape::{
99
Ball, Capsule, Compound, Cuboid, FeatureId, HalfSpace, HeightField, PolygonalFeatureMap,
10-
Polyline, RoundCuboid, RoundShape, RoundTriangle, Segment, SupportMap, TriMesh, Triangle,
10+
Polyline, RoundCuboid, RoundShape, RoundTriangle, ScaledTriMesh, Segment, SupportMap, TriMesh,
11+
Triangle,
1112
};
1213
#[cfg(feature = "dim3")]
1314
use crate::shape::{
@@ -35,6 +36,8 @@ pub enum ShapeType {
3536
Triangle,
3637
/// A triangle mesh shape.
3738
TriMesh,
39+
/// A triangle mesh shape with scaling factors.
40+
ScaledTriMesh,
3841
/// A set of segments.
3942
Polyline,
4043
/// A shape representing a full half-space.
@@ -96,6 +99,8 @@ pub enum TypedShape<'a> {
9699
Triangle(&'a Triangle),
97100
/// A triangle mesh shape.
98101
TriMesh(&'a TriMesh),
102+
/// A triangle mesh shape with scaling factors.
103+
ScaledTriMesh(&'a ScaledTriMesh),
99104
/// A set of segments.
100105
Polyline(&'a Polyline),
101106
/// A shape representing a full half-space.
@@ -953,6 +958,58 @@ impl Shape for TriMesh {
953958
}
954959
}
955960

961+
impl Shape for ScaledTriMesh {
962+
fn clone_box(&self) -> Box<dyn Shape> {
963+
Box::new(self.clone())
964+
}
965+
966+
fn compute_local_aabb(&self) -> AABB {
967+
*self.local_aabb()
968+
}
969+
970+
fn compute_local_bounding_sphere(&self) -> BoundingSphere {
971+
self.local_bounding_sphere()
972+
}
973+
974+
fn compute_aabb(&self, position: &Isometry<Real>) -> AABB {
975+
self.aabb(position)
976+
}
977+
978+
fn mass_properties(&self, _density: Real) -> MassProperties {
979+
#[cfg(feature = "dim2")]
980+
return MassProperties::from_trimesh(
981+
_density * self.scaling_factors().iter().product::<Real>(),
982+
self.trimesh().vertices(),
983+
self.trimesh().indices(),
984+
);
985+
#[cfg(feature = "dim3")]
986+
return MassProperties::zero();
987+
}
988+
989+
fn shape_type(&self) -> ShapeType {
990+
ShapeType::ScaledTriMesh
991+
}
992+
993+
fn as_typed_shape(&self) -> TypedShape {
994+
TypedShape::ScaledTriMesh(self)
995+
}
996+
997+
fn ccd_thickness(&self) -> Real {
998+
// TODO: in 2D, return the smallest CCD thickness among triangles?
999+
0.0
1000+
}
1001+
1002+
fn ccd_angular_thickness(&self) -> Real {
1003+
// TODO: the value should depend on the angles between
1004+
// adjacent triangles of the trimesh.
1005+
Real::frac_pi_4()
1006+
}
1007+
1008+
fn as_composite_shape(&self) -> Option<&dyn SimdCompositeShape> {
1009+
Some(self as &dyn SimdCompositeShape)
1010+
}
1011+
}
1012+
9561013
impl Shape for HeightField {
9571014
fn clone_box(&self) -> Box<dyn Shape> {
9581015
Box::new(self.clone())

src/shape/trimesh.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl TriMesh {
7575
}
7676

7777
/// An iterator through all the triangles of this mesh.
78-
pub fn triangles(&self) -> impl Iterator<Item = Triangle> + '_ {
78+
pub fn triangles(&self) -> impl ExactSizeIterator<Item = Triangle> + '_ {
7979
self.indices.iter().map(move |ids| {
8080
Triangle::new(
8181
self.vertices[ids[0] as usize],

0 commit comments

Comments
 (0)