Skip to main content

bevy_rapier2d/geometry/
collider_impl.rs

1#[cfg(all(feature = "dim3", feature = "async-collider"))]
2use {
3    bevy::mesh::{Indices, VertexAttributeValues},
4    bevy::prelude::*,
5};
6
7use rapier::{
8    geometry::{MeshConverter, MeshConverterError},
9    math::Pose,
10    parry::shape::{Compound, Polyline},
11    parry::transformation::voxelization::FillMode,
12    prelude::{FeatureId, Ray, SharedShape, Vector, Voxels, DIM},
13};
14
15use super::{get_snapped_scale, shape_views::*};
16#[cfg(all(feature = "dim3", feature = "async-collider"))]
17use crate::geometry::ComputedColliderShape;
18#[cfg(feature = "dim3")]
19use crate::geometry::HeightFieldFlags;
20use crate::math::{Real, Rot, Vect};
21#[cfg(feature = "dim2")]
22use bevy::shape::{Aabb2d as BevyAabb, BoundingCircle as BevyBoundingSphere};
23#[cfg(feature = "dim3")]
24use bevy::shape::{Aabb3d as BevyAabb, BoundingSphere as BevyBoundingSphere};
25use rapier::parry::query;
26
27use crate::dynamics::MassProperties;
28use crate::{
29    geometry::{
30        Collider, CompoundFlags, NonlinearMotion, PointProjection, PolylineFlags, RayIntersection,
31        ShapeCastHit, ShapeCastOptions, ShapeClosestPoints, ShapeContact, TriMeshFlags,
32        Unsupported, VHACDParameters,
33    },
34    math::IVect,
35};
36
37impl Collider {
38    /// The scaling factor that was applied to this collider.
39    pub fn scale(&self) -> Vect {
40        self.scale
41    }
42
43    /// This replaces the unscaled version of this collider by its scaled version,
44    /// and resets `self.scale()` to `1.0`.
45    pub fn promote_scaled_shape(&mut self) {
46        self.unscaled = self.raw.clone();
47        self.scale = Vect::ONE;
48    }
49
50    /// Initialize a new collider with a compound shape.
51    pub fn compound(shapes: Vec<(Vect, Rot, Collider)>) -> Self {
52        let shapes = shapes
53            .into_iter()
54            .map(|(t, r, s)| (crate::utils::pose_from(t, r), s.raw))
55            .collect();
56        SharedShape::compound(shapes).into()
57    }
58
59    /// Initialize a new collider with a compound shape and flags controlling its optional
60    /// pre-processing.
61    ///
62    /// `weld_tolerance` is the distance (in ULPs) under which two corners of different parts
63    /// are considered equal when detecting internal edges; `None` selects parry's default.
64    pub fn compound_with_flags(
65        shapes: Vec<(Vect, Rot, Collider)>,
66        flags: CompoundFlags,
67        weld_tolerance: Option<Real>,
68    ) -> Self {
69        let shapes = shapes
70            .into_iter()
71            .map(|(t, r, s)| (crate::utils::pose_from(t, r), s.raw))
72            .collect();
73        SharedShape::new(Compound::with_flags(shapes, flags, weld_tolerance)).into()
74    }
75
76    /// Initialize a new collider with a ball shape defined by its radius.
77    pub fn ball(radius: Real) -> Self {
78        SharedShape::ball(radius).into()
79    }
80
81    /// Initialize a new collider build with a half-space shape defined by the outward normal
82    /// of its planar boundary.
83    pub fn halfspace(outward_normal: Vect) -> Option<Self> {
84        let normal = Vector::from(outward_normal);
85        if normal.length_squared() < 1.0e-12 {
86            return None;
87        }
88        Some(SharedShape::halfspace(normal.normalize()).into())
89    }
90
91    /// Initialize a new collider with a cylindrical shape defined by its half-height
92    /// (along the y axis) and its radius.
93    #[cfg(feature = "dim3")]
94    pub fn cylinder(half_height: Real, radius: Real) -> Self {
95        SharedShape::cylinder(half_height, radius).into()
96    }
97
98    /// Initialize a new collider with a rounded cylindrical shape defined by its half-height
99    /// (along the y axis), its radius, and its roundedness (the
100    /// radius of the sphere used for dilating the cylinder).
101    #[cfg(feature = "dim3")]
102    pub fn round_cylinder(half_height: Real, radius: Real, border_radius: Real) -> Self {
103        SharedShape::round_cylinder(half_height, radius, border_radius).into()
104    }
105
106    /// Initialize a new collider with a cone shape defined by its half-height
107    /// (along the y axis) and its basis radius.
108    #[cfg(feature = "dim3")]
109    pub fn cone(half_height: Real, radius: Real) -> Self {
110        SharedShape::cone(half_height, radius).into()
111    }
112
113    /// Initialize a new collider with a rounded cone shape defined by its half-height
114    /// (along the y axis), its radius, and its roundedness (the
115    /// radius of the sphere used for dilating the cylinder).
116    #[cfg(feature = "dim3")]
117    pub fn round_cone(half_height: Real, radius: Real, border_radius: Real) -> Self {
118        SharedShape::round_cone(half_height, radius, border_radius).into()
119    }
120
121    /// Initialize a new collider with a cuboid shape defined by its half-extents.
122    #[cfg(feature = "dim2")]
123    pub fn cuboid(half_x: Real, half_y: Real) -> Self {
124        SharedShape::cuboid(half_x, half_y).into()
125    }
126
127    /// Initialize a new collider with a round cuboid shape defined by its half-extents
128    /// and border radius.
129    #[cfg(feature = "dim2")]
130    pub fn round_cuboid(half_x: Real, half_y: Real, border_radius: Real) -> Self {
131        SharedShape::round_cuboid(half_x, half_y, border_radius).into()
132    }
133
134    /// Initialize a new collider with a capsule shape.
135    pub fn capsule(start: Vect, end: Vect, radius: Real) -> Self {
136        SharedShape::capsule(start, end, radius).into()
137    }
138
139    /// Initialize a new collider with a capsule shape aligned with the `x` axis.
140    pub fn capsule_x(half_height: Real, radius: Real) -> Self {
141        let p = Vector::X * half_height;
142        SharedShape::capsule(-p, p, radius).into()
143    }
144
145    /// Initialize a new collider with a capsule shape aligned with the `y` axis.
146    pub fn capsule_y(half_height: Real, radius: Real) -> Self {
147        let p = Vector::Y * half_height;
148        SharedShape::capsule(-p, p, radius).into()
149    }
150
151    /// Initialize a new collider with a capsule shape aligned with the `z` axis.
152    #[cfg(feature = "dim3")]
153    pub fn capsule_z(half_height: Real, radius: Real) -> Self {
154        let p = Vector::Z * half_height;
155        SharedShape::capsule(-p, p, radius).into()
156    }
157
158    /// Initialize a new collider with a cuboid shape defined by its half-extents.
159    #[cfg(feature = "dim3")]
160    pub fn cuboid(hx: Real, hy: Real, hz: Real) -> Self {
161        SharedShape::cuboid(hx, hy, hz).into()
162    }
163
164    /// Initialize a new collider with a round cuboid shape defined by its half-extents
165    /// and border radius.
166    #[cfg(feature = "dim3")]
167    pub fn round_cuboid(half_x: Real, half_y: Real, half_z: Real, border_radius: Real) -> Self {
168        SharedShape::round_cuboid(half_x, half_y, half_z, border_radius).into()
169    }
170
171    /// Initializes a collider with a segment shape.
172    pub fn segment(a: Vect, b: Vect) -> Self {
173        SharedShape::segment(a, b).into()
174    }
175
176    /// Initializes a collider with a triangle shape.
177    pub fn triangle(a: Vect, b: Vect, c: Vect) -> Self {
178        SharedShape::triangle(a, b, c).into()
179    }
180
181    /// Initializes a collider with a triangle shape with round corners.
182    pub fn round_triangle(a: Vect, b: Vect, c: Vect, border_radius: Real) -> Self {
183        SharedShape::round_triangle(a, b, c, border_radius).into()
184    }
185
186    fn ivec_array_from_point_int_array(points: &[IVect]) -> Vec<IVect> {
187        points.to_vec()
188    }
189
190    fn vec_array_from_point_float_array(points: &[Vect]) -> Vec<Vect> {
191        points.to_vec()
192    }
193
194    /// Initializes a shape made of voxels.
195    ///
196    /// Each voxel has the size `voxel_size` and grid coordinate given by `grid_coords`.
197    /// The `primitive_geometry` controls the behavior of collision detection at voxels boundaries.
198    ///
199    /// For initializing a voxels shape from points in space, see [`Self::voxels_from_points`].
200    /// For initializing a voxels shape from a mesh to voxelize, see [`Self::voxelized_mesh`].
201    /// For initializing multiple voxels shape from the convex decomposition of a mesh, see
202    /// [`Self::voxelized_convex_decomposition`].
203    pub fn voxels(voxel_size: Vect, grid_coordinates: &[IVect]) -> Self {
204        let shape = Voxels::new(
205            voxel_size,
206            &Self::ivec_array_from_point_int_array(grid_coordinates),
207        );
208        SharedShape::new(shape).into()
209    }
210
211    /// Initializes a shape made of voxels.
212    ///
213    /// Each voxel has the size `voxel_size` and contains at least one point from `centers`.
214    /// The `primitive_geometry` controls the behavior of collision detection at voxels boundaries.
215    pub fn voxels_from_points(voxel_size: Vect, points: &[Vect]) -> Self {
216        SharedShape::voxels_from_points(voxel_size, &Self::vec_array_from_point_float_array(points))
217            .into()
218    }
219
220    /// Initializes a voxels shape obtained from the decomposition of the given trimesh (in 3D)
221    /// or polyline (in 2D) into voxelized convex parts.
222    pub fn voxelized_mesh(
223        vertices: &[Vect],
224        indices: &[[u32; DIM]],
225        voxel_size: Real,
226        fill_mode: FillMode,
227    ) -> Self {
228        let vertices = Self::vec_array_from_point_float_array(vertices);
229        SharedShape::voxelized_mesh(&vertices, indices, voxel_size, fill_mode).into()
230    }
231
232    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D)
233    /// or polyline (in 2D) into voxelized convex parts.
234    pub fn voxelized_convex_decomposition(vertices: &[Vect], indices: &[[u32; DIM]]) -> Vec<Self> {
235        Self::voxelized_convex_decomposition_with_params(
236            vertices,
237            indices,
238            &VHACDParameters::default(),
239        )
240    }
241
242    /// Initializes a compound shape obtained from the decomposition of the given trimesh (in 3D)
243    /// or polyline (in 2D) into voxelized convex parts.
244    pub fn voxelized_convex_decomposition_with_params(
245        vertices: &[Vect],
246        indices: &[[u32; DIM]],
247        params: &VHACDParameters,
248    ) -> Vec<Self> {
249        SharedShape::voxelized_convex_decomposition_with_params(
250            &Self::vec_array_from_point_float_array(vertices),
251            indices,
252            params,
253        )
254        .into_iter()
255        .map(|c| c.into())
256        .collect()
257    }
258
259    /// Initializes a collider with a polyline shape defined by its vertex and index buffers.
260    pub fn polyline(vertices: Vec<Vect>, indices: Option<Vec<[u32; 2]>>) -> Self {
261        let vertices = vertices.into_iter().collect();
262        SharedShape::polyline(vertices, indices).into()
263    }
264
265    /// Initializes a collider with a polyline shape defined by its vertex and index buffers, and
266    /// flags controlling its optional associated data (orientation, deformability).
267    pub fn polyline_with_flags(
268        vertices: Vec<Vect>,
269        indices: Option<Vec<[u32; 2]>>,
270        flags: PolylineFlags,
271    ) -> Self {
272        SharedShape::new(Polyline::with_flags(vertices, indices, flags)).into()
273    }
274
275    /// Initializes a collider with a one-sided (oriented) polyline shape.
276    ///
277    /// The segments only collide from their outward side, which is on the right of each
278    /// segment's direction: a counter-clockwise loop has a solid interior.
279    #[cfg(feature = "dim2")]
280    pub fn oriented_polyline(vertices: Vec<Vect>, indices: Option<Vec<[u32; 2]>>) -> Self {
281        Self::polyline_with_flags(vertices, indices, PolylineFlags::ORIENTED)
282    }
283
284    /// Initializes a collider with a triangle mesh shape defined by its vertex and index buffers.
285    pub fn trimesh(
286        vertices: Vec<Vect>,
287        indices: Vec<[u32; 3]>,
288    ) -> Result<Self, crate::rapier::prelude::TriMeshBuilderError> {
289        let vertices = vertices.into_iter().collect();
290        Ok(SharedShape::trimesh(vertices, indices)?.into())
291    }
292
293    /// Initializes a collider with a triangle mesh shape defined by its vertex and index buffers, and flags
294    /// controlling its pre-processing.
295    pub fn trimesh_with_flags(
296        vertices: Vec<Vect>,
297        indices: Vec<[u32; 3]>,
298        flags: TriMeshFlags,
299    ) -> Result<Self, crate::rapier::prelude::TriMeshBuilderError> {
300        let vertices = vertices.into_iter().collect();
301        Ok(SharedShape::trimesh_with_flags(vertices, indices, flags)?.into())
302    }
303
304    /// Initializes a collider with a shape converted from the given triangle mesh vertex and
305    /// index buffers, following the conversion rule described by `converter`.
306    ///
307    /// Conversions yielding a shape offset from the mesh origin ([`MeshConverter::Obb`] and
308    /// [`MeshConverter::Aabb`]) are wrapped into a single-part compound shape to keep that offset.
309    pub fn converted_trimesh(
310        vertices: Vec<Vect>,
311        indices: Vec<[u32; 3]>,
312        converter: MeshConverter,
313    ) -> Result<Self, MeshConverterError> {
314        let (shape, pose) = converter.convert(vertices, indices)?;
315        if pose == Pose::IDENTITY {
316            Ok(shape.into())
317        } else {
318            Ok(SharedShape::compound(vec![(pose, shape)]).into())
319        }
320    }
321
322    /// Initializes a collider with a Bevy Mesh.
323    ///
324    /// Returns `None` if the index buffer or vertex buffer of the mesh are in an incompatible format,
325    /// or if the shape computation failed.
326    #[cfg(all(feature = "dim3", feature = "async-collider"))]
327    pub fn from_bevy_mesh(mesh: &Mesh, collider_shape: &ComputedColliderShape) -> Option<Self> {
328        let (vtx, idx) = extract_mesh_vertices_indices(mesh)?;
329
330        match collider_shape {
331            ComputedColliderShape::TriMesh(flags) => Some(
332                SharedShape::trimesh_with_flags(vtx, idx, *flags)
333                    .ok()?
334                    .into(),
335            ),
336            ComputedColliderShape::ConvexHull => {
337                SharedShape::convex_hull(&vtx).map(|shape| shape.into())
338            }
339            ComputedColliderShape::ConvexDecomposition(params) => {
340                Some(SharedShape::convex_decomposition_with_params(&vtx, &idx, params).into())
341            }
342            ComputedColliderShape::Voxels {
343                voxel_size,
344                fill_mode,
345            } => Some(Self::voxelized_mesh(&vtx, &idx, *voxel_size, *fill_mode)),
346            ComputedColliderShape::Converted(converter) => {
347                Self::converted_trimesh(vtx, idx, *converter).ok()
348            }
349        }
350    }
351
352    /// Initializes a collider with a compound shape obtained from the decomposition of
353    /// the given trimesh (in 3D) or polyline (in 2D) into convex parts.
354    pub fn convex_decomposition(vertices: &[Vect], indices: &[[u32; DIM]]) -> Self {
355        let vertices: Vec<_> = vertices.to_vec();
356        SharedShape::convex_decomposition(&vertices, indices).into()
357    }
358
359    /// Initializes a collider with a compound shape obtained from the decomposition of
360    /// the given trimesh (in 3D) or polyline (in 2D) into convex parts dilated with round corners.
361    pub fn round_convex_decomposition(
362        vertices: &[Vect],
363        indices: &[[u32; DIM]],
364        border_radius: Real,
365    ) -> Self {
366        let vertices: Vec<_> = vertices.to_vec();
367        SharedShape::round_convex_decomposition(&vertices, indices, border_radius).into()
368    }
369
370    /// Initializes a collider with a compound shape obtained from the decomposition of
371    /// the given trimesh (in 3D) or polyline (in 2D) into convex parts.
372    pub fn convex_decomposition_with_params(
373        vertices: &[Vect],
374        indices: &[[u32; DIM]],
375        params: &VHACDParameters,
376    ) -> Self {
377        let vertices: Vec<_> = vertices.to_vec();
378        SharedShape::convex_decomposition_with_params(&vertices, indices, params).into()
379    }
380
381    /// Initializes a collider with a compound shape obtained from the decomposition of
382    /// the given trimesh (in 3D) or polyline (in 2D) into convex parts dilated with round corners.
383    pub fn round_convex_decomposition_with_params(
384        vertices: &[Vect],
385        indices: &[[u32; DIM]],
386        params: &VHACDParameters,
387        border_radius: Real,
388    ) -> Self {
389        let vertices: Vec<_> = vertices.to_vec();
390        SharedShape::round_convex_decomposition_with_params(
391            &vertices,
392            indices,
393            params,
394            border_radius,
395        )
396        .into()
397    }
398
399    /// Initializes a new collider with a 2D convex polygon or 3D convex polyhedron
400    /// obtained after computing the convex-hull of the given points.
401    pub fn convex_hull(points: &[Vect]) -> Option<Self> {
402        let points: Vec<_> = points.to_vec();
403        SharedShape::convex_hull(&points).map(Into::into)
404    }
405
406    /// Initializes a new collider with a round 2D convex polygon or 3D convex polyhedron
407    /// obtained after computing the convex-hull of the given points. The shape is dilated
408    /// by a sphere of radius `border_radius`.
409    pub fn round_convex_hull(points: &[Vect], border_radius: Real) -> Option<Self> {
410        let points: Vec<_> = points.to_vec();
411        SharedShape::round_convex_hull(&points, border_radius).map(Into::into)
412    }
413
414    /// Creates a new collider that is a convex polygon formed by the
415    /// given polyline assumed to be convex (no convex-hull will be automatically
416    /// computed).
417    #[cfg(feature = "dim2")]
418    pub fn convex_polyline(points: Vec<Vect>) -> Option<Self> {
419        SharedShape::convex_polyline(points).map(Into::into)
420    }
421
422    /// Creates a new collider that is a convex polygon formed by the given polyline assumed
423    /// to be convex and counter-clockwise.
424    ///
425    /// Unlike [`Self::convex_polyline`], no point is removed from the input even if some are
426    /// collinear. Returns `None` if `points` contains less than three points.
427    #[cfg(feature = "dim2")]
428    pub fn convex_polyline_unmodified(points: Vec<Vect>) -> Option<Self> {
429        SharedShape::convex_polyline_unmodified(points).map(Into::into)
430    }
431
432    /// Creates a new collider that is a round convex polygon formed by the
433    /// given polyline assumed to be convex (no convex-hull will be automatically
434    /// computed). The polygon shape is dilated by a sphere of radius `border_radius`.
435    #[cfg(feature = "dim2")]
436    pub fn round_convex_polyline(points: Vec<Vect>, border_radius: Real) -> Option<Self> {
437        SharedShape::round_convex_polyline(points, border_radius).map(Into::into)
438    }
439
440    /// Creates a new collider that is a convex polyhedron formed by the
441    /// given triangle-mesh assumed to be convex (no convex-hull will be automatically
442    /// computed).
443    #[cfg(feature = "dim3")]
444    pub fn convex_mesh(points: Vec<Vect>, indices: &[[u32; 3]]) -> Option<Self> {
445        let points = points.into_iter().collect();
446        SharedShape::convex_mesh(points, indices).map(Into::into)
447    }
448
449    /// Creates a new collider that is a round convex polyhedron formed by the
450    /// given triangle-mesh assumed to be convex (no convex-hull will be automatically
451    /// computed). The triangle mesh shape is dilated by a sphere of radius `border_radius`.
452    #[cfg(feature = "dim3")]
453    pub fn round_convex_mesh(
454        points: Vec<Vect>,
455        indices: &[[u32; 3]],
456        border_radius: Real,
457    ) -> Option<Self> {
458        let points = points.into_iter().collect();
459        SharedShape::round_convex_mesh(points, indices, border_radius).map(Into::into)
460    }
461
462    /// Initializes a collider with a heightfield shape defined by its set of height and a scale
463    /// factor along each coordinate axis.
464    #[cfg(feature = "dim2")]
465    pub fn heightfield(heights: Vec<Real>, scale: Vect) -> Self {
466        SharedShape::heightfield(heights, scale).into()
467    }
468
469    /// Initializes a collider with a heightfield shape defined by its set of height (in
470    /// column-major format) and a scale factor along each coordinate axis.
471    #[cfg(feature = "dim3")]
472    pub fn heightfield(heights: Vec<Real>, num_rows: usize, num_cols: usize, scale: Vect) -> Self {
473        assert_eq!(
474            heights.len(),
475            num_rows * num_cols,
476            "Invalid number of heights provided."
477        );
478        let heights = rapier::parry::utils::Array2::new(num_rows, num_cols, heights);
479        SharedShape::heightfield(heights, scale).into()
480    }
481
482    /// Initializes a collider with a heightfield shape defined by its set of height (in
483    /// column-major format), a scale factor along each coordinate axis, and flags controlling
484    /// its contact behavior.
485    #[cfg(feature = "dim3")]
486    pub fn heightfield_with_flags(
487        heights: Vec<Real>,
488        num_rows: usize,
489        num_cols: usize,
490        scale: Vect,
491        flags: HeightFieldFlags,
492    ) -> Self {
493        assert_eq!(
494            heights.len(),
495            num_rows * num_cols,
496            "Invalid number of heights provided."
497        );
498        let heights = rapier::parry::utils::Array2::new(num_rows, num_cols, heights);
499        SharedShape::heightfield_with_flags(heights, scale, flags).into()
500    }
501
502    /// Takes a strongly typed reference of this collider.
503    pub fn as_typed_shape(&self) -> ColliderView<'_> {
504        self.raw.as_typed_shape().into()
505    }
506
507    /// Takes a strongly typed reference of the unscaled version of this collider.
508    pub fn as_unscaled_typed_shape(&self) -> ColliderView<'_> {
509        self.unscaled.as_typed_shape().into()
510    }
511
512    /// Downcast this collider to a ball, if it is one.
513    pub fn as_ball(&self) -> Option<BallView<'_>> {
514        self.raw.as_ball().map(|s| BallView { raw: s })
515    }
516
517    /// Downcast this collider to a cuboid, if it is one.
518    pub fn as_cuboid(&self) -> Option<CuboidView<'_>> {
519        self.raw.as_cuboid().map(|s| CuboidView { raw: s })
520    }
521
522    /// Downcast this collider to a capsule, if it is one.
523    pub fn as_capsule(&self) -> Option<CapsuleView<'_>> {
524        self.raw.as_capsule().map(|s| CapsuleView { raw: s })
525    }
526
527    /// Downcast this collider to a segment, if it is one.
528    pub fn as_segment(&self) -> Option<SegmentView<'_>> {
529        self.raw.as_segment().map(|s| SegmentView { raw: s })
530    }
531
532    /// Downcast this collider to a triangle, if it is one.
533    pub fn as_triangle(&self) -> Option<TriangleView<'_>> {
534        self.raw.as_triangle().map(|s| TriangleView { raw: s })
535    }
536
537    /// Downcast this collider to a voxels, if it is one.
538    pub fn as_voxels(&self) -> Option<VoxelsView<'_>> {
539        self.raw.as_voxels().map(|s| VoxelsView { raw: s })
540    }
541
542    /// Downcast this collider to a triangle mesh, if it is one.
543    pub fn as_trimesh(&self) -> Option<TriMeshView<'_>> {
544        self.raw.as_trimesh().map(|s| TriMeshView { raw: s })
545    }
546
547    /// Downcast this collider to a polyline, if it is one.
548    pub fn as_polyline(&self) -> Option<PolylineView<'_>> {
549        self.raw.as_polyline().map(|s| PolylineView { raw: s })
550    }
551
552    /// Downcast this collider to a half-space, if it is one.
553    pub fn as_halfspace(&self) -> Option<HalfSpaceView<'_>> {
554        self.raw.as_halfspace().map(|s| HalfSpaceView { raw: s })
555    }
556
557    /// Downcast this collider to a heightfield, if it is one.
558    pub fn as_heightfield(&self) -> Option<HeightFieldView<'_>> {
559        self.raw
560            .as_heightfield()
561            .map(|s| HeightFieldView { raw: s })
562    }
563
564    /// Downcast this collider to a compound shape, if it is one.
565    pub fn as_compound(&self) -> Option<CompoundView<'_>> {
566        self.raw.as_compound().map(|s| CompoundView { raw: s })
567    }
568
569    /// Downcast this collider to a convex polygon, if it is one.
570    #[cfg(feature = "dim2")]
571    pub fn as_convex_polygon(&self) -> Option<ConvexPolygonView<'_>> {
572        self.raw
573            .as_convex_polygon()
574            .map(|s| ConvexPolygonView { raw: s })
575    }
576
577    /// Downcast this collider to a convex polyhedron, if it is one.
578    #[cfg(feature = "dim3")]
579    pub fn as_convex_polyhedron(&self) -> Option<ConvexPolyhedronView<'_>> {
580        self.raw
581            .as_convex_polyhedron()
582            .map(|s| ConvexPolyhedronView { raw: s })
583    }
584
585    /// Downcast this collider to a cylinder, if it is one.
586    #[cfg(feature = "dim3")]
587    pub fn as_cylinder(&self) -> Option<CylinderView<'_>> {
588        self.raw.as_cylinder().map(|s| CylinderView { raw: s })
589    }
590
591    /// Downcast this collider to a cone, if it is one.
592    #[cfg(feature = "dim3")]
593    pub fn as_cone(&self) -> Option<ConeView<'_>> {
594        self.raw.as_cone().map(|s| ConeView { raw: s })
595    }
596
597    /// Downcast this collider to a mutable ball, if it is one.
598    pub fn as_ball_mut(&mut self) -> Option<BallViewMut<'_>> {
599        self.raw
600            .make_mut()
601            .as_ball_mut()
602            .map(|s| BallViewMut { raw: s })
603    }
604
605    /// Downcast this collider to a mutable cuboid, if it is one.
606    pub fn as_cuboid_mut(&mut self) -> Option<CuboidViewMut<'_>> {
607        self.raw
608            .make_mut()
609            .as_cuboid_mut()
610            .map(|s| CuboidViewMut { raw: s })
611    }
612
613    /// Downcast this collider to a mutable capsule, if it is one.
614    pub fn as_capsule_mut(&mut self) -> Option<CapsuleViewMut<'_>> {
615        self.raw
616            .make_mut()
617            .as_capsule_mut()
618            .map(|s| CapsuleViewMut { raw: s })
619    }
620
621    /// Downcast this collider to a mutable segment, if it is one.
622    pub fn as_segment_mut(&mut self) -> Option<SegmentViewMut<'_>> {
623        self.raw
624            .make_mut()
625            .as_segment_mut()
626            .map(|s| SegmentViewMut { raw: s })
627    }
628
629    /// Downcast this collider to a mutable triangle, if it is one.
630    pub fn as_triangle_mut(&mut self) -> Option<TriangleViewMut<'_>> {
631        self.raw
632            .make_mut()
633            .as_triangle_mut()
634            .map(|s| TriangleViewMut { raw: s })
635    }
636
637    /// Downcast this collider to a mutable voxels, if it is one.
638    pub fn as_voxels_mut(&mut self) -> Option<VoxelsViewMut<'_>> {
639        self.raw
640            .make_mut()
641            .as_voxels_mut()
642            .map(|s| VoxelsViewMut { raw: s })
643    }
644
645    /// Downcast this collider to a mutable triangle mesh, if it is one.
646    pub fn as_trimesh_mut(&mut self) -> Option<TriMeshViewMut<'_>> {
647        self.raw
648            .make_mut()
649            .as_trimesh_mut()
650            .map(|s| TriMeshViewMut { raw: s })
651    }
652
653    /// Downcast this collider to a mutable polyline, if it is one.
654    pub fn as_polyline_mut(&mut self) -> Option<PolylineViewMut<'_>> {
655        self.raw
656            .make_mut()
657            .as_polyline_mut()
658            .map(|s| PolylineViewMut { raw: s })
659    }
660
661    /// Downcast this collider to a mutable half-space, if it is one.
662    pub fn as_halfspace_mut(&mut self) -> Option<HalfSpaceViewMut<'_>> {
663        self.raw
664            .make_mut()
665            .as_halfspace_mut()
666            .map(|s| HalfSpaceViewMut { raw: s })
667    }
668
669    /// Downcast this collider to a mutable heightfield, if it is one.
670    pub fn as_heightfield_mut(&mut self) -> Option<HeightFieldViewMut<'_>> {
671        self.raw
672            .make_mut()
673            .as_heightfield_mut()
674            .map(|s| HeightFieldViewMut { raw: s })
675    }
676
677    /// Downcast this collider to a mutable compound shape, if it is one.
678    ///
679    /// Like the other mutable downcasts, this clones the shape first if it is shared with
680    /// other colliders. The parts of the compound remain shared.
681    pub fn as_compound_mut(&mut self) -> Option<CompoundViewMut<'_>> {
682        self.raw
683            .make_mut()
684            .as_compound_mut()
685            .map(|s| CompoundViewMut { raw: s })
686    }
687
688    // /// Downcast this collider to a mutable convex polygon, if it is one.
689    // #[cfg(feature = "dim2")]
690    // pub fn as_convex_polygon_mut(&mut self) -> Option<ConvexPolygonViewMut> {
691    //     self.raw.make_mut()
692    //         .as_convex_polygon_mut()
693    //         .map(|s| ConvexPolygonViewMut { raw: s })
694    // }
695
696    // /// Downcast this collider to a mutable convex polyhedron, if it is one.
697    // #[cfg(feature = "dim3")]
698    // pub fn as_convex_polyhedron_mut(&mut self) -> Option<ConvexPolyhedronViewMut> {
699    //     self.raw.make_mut()
700    //         .as_convex_polyhedron_mut()
701    //         .map(|s| ConvexPolyhedronViewMut { raw: s })
702    // }
703
704    /// Downcast this collider to a mutable cylinder, if it is one.
705    #[cfg(feature = "dim3")]
706    pub fn as_cylinder_mut(&mut self) -> Option<CylinderViewMut<'_>> {
707        self.raw
708            .make_mut()
709            .as_cylinder_mut()
710            .map(|s| CylinderViewMut { raw: s })
711    }
712
713    /// Downcast this collider to a mutable cone, if it is one.
714    #[cfg(feature = "dim3")]
715    pub fn as_cone_mut(&mut self) -> Option<ConeViewMut<'_>> {
716        self.raw
717            .make_mut()
718            .as_cone_mut()
719            .map(|s| ConeViewMut { raw: s })
720    }
721
722    /// Set the scaling factor of this shape.
723    ///
724    /// If the scaling factor is non-uniform, and the scaled shape can’t be
725    /// represented as a supported smooth shape (for example scalling a Ball
726    /// with a non-uniform scale results in an ellipse which isn’t supported),
727    /// the shape is approximated by a convex polygon/convex polyhedron using
728    /// `num_subdivisions` subdivisions.
729    pub fn set_scale(&mut self, scale: Vect, num_subdivisions: u32) {
730        let scale = get_snapped_scale(scale);
731
732        if scale == self.scale {
733            // Nothing to do.
734            return;
735        }
736
737        if scale == Vect::ONE {
738            // Trivial case.
739            self.raw = self.unscaled.clone();
740            self.scale = Vect::ONE;
741            return;
742        }
743
744        if let Some(scaled) = self
745            .as_unscaled_typed_shape()
746            .raw_scale_by(scale, num_subdivisions)
747        {
748            self.raw = scaled;
749            self.scale = scale;
750        } else {
751            log::error!("Failed to create the scaled convex hull geometry.");
752        }
753    }
754
755    /// Projects a point on `self`, unless the projection lies further than the given max distance.
756    ///
757    /// The point is assumed to be expressed in the local-space of `self`.
758    pub fn project_local_point_with_max_dist(
759        &self,
760        point: Vect,
761        solid: bool,
762        max_dist: Real,
763    ) -> Option<PointProjection> {
764        self.raw
765            .project_local_point_with_max_dist(point, solid, max_dist)
766            .map(Into::into)
767    }
768
769    /// Projects a point on `self` transformed by `m`, unless the projection lies further than the given max distance.
770    pub fn project_point_with_max_dist(
771        &self,
772        translation: Vect,
773        rotation: Rot,
774        point: Vect,
775        solid: bool,
776        max_dist: Real,
777    ) -> Option<PointProjection> {
778        let pos = crate::utils::pose_from(translation, rotation);
779        self.raw
780            .project_point_with_max_dist(&pos, point, solid, max_dist)
781            .map(Into::into)
782    }
783
784    /// Projects a point on `self`.
785    ///
786    /// The point is assumed to be expressed in the local-space of `self`.
787    pub fn project_local_point(&self, point: Vect, solid: bool) -> PointProjection {
788        self.raw.project_local_point(point, solid).into()
789    }
790
791    /// Projects a point on the boundary of `self` and returns the id of the
792    /// feature the point was projected on.
793    pub fn project_local_point_and_get_feature(&self, point: Vect) -> (PointProjection, FeatureId) {
794        let (proj, feat) = self.raw.project_local_point_and_get_feature(point);
795        (proj.into(), feat)
796    }
797
798    /// Computes the minimal distance between a point and `self`.
799    pub fn distance_to_local_point(&self, point: Vect, solid: bool) -> Real {
800        self.raw.distance_to_local_point(point, solid)
801    }
802
803    /// Tests if the given point is inside of `self`.
804    pub fn contains_local_point(&self, point: Vect) -> bool {
805        self.raw.contains_local_point(point)
806    }
807
808    /// Projects a point on `self` transformed by `m`.
809    pub fn project_point(
810        &self,
811        translation: Vect,
812        rotation: Rot,
813        point: Vect,
814        solid: bool,
815    ) -> PointProjection {
816        let pos = crate::utils::pose_from(translation, rotation);
817        self.raw.project_point(&pos, point, solid).into()
818    }
819
820    /// Computes the minimal distance between a point and `self` transformed by `m`.
821    #[inline]
822    pub fn distance_to_point(
823        &self,
824        translation: Vect,
825        rotation: Rot,
826        point: Vect,
827        solid: bool,
828    ) -> Real {
829        let pos = crate::utils::pose_from(translation, rotation);
830        self.raw.distance_to_point(&pos, point, solid)
831    }
832
833    /// Projects a point on the boundary of `self` transformed by `m` and returns the id of the
834    /// feature the point was projected on.
835    pub fn project_point_and_get_feature(
836        &self,
837        translation: Vect,
838        rotation: Rot,
839        point: Vect,
840    ) -> (PointProjection, FeatureId) {
841        let pos = crate::utils::pose_from(translation, rotation);
842        let (proj, feat) = self.raw.project_point_and_get_feature(&pos, point);
843        (proj.into(), feat)
844    }
845
846    /// Tests if the given point is inside of `self` transformed by `m`.
847    pub fn contains_point(&self, translation: Vect, rotation: Rot, point: Vect) -> bool {
848        let pos = crate::utils::pose_from(translation, rotation);
849        self.raw.contains_point(&pos, point)
850    }
851
852    /// Computes the time of impact between this transform shape and a ray.
853    pub fn cast_local_ray(
854        &self,
855        ray_origin: Vect,
856        ray_dir: Vect,
857        max_time_of_impact: Real,
858        solid: bool,
859    ) -> Option<Real> {
860        let ray = Ray::new(ray_origin, ray_dir);
861        self.raw.cast_local_ray(&ray, max_time_of_impact, solid)
862    }
863
864    /// Computes the time of impact, and normal between this transformed shape and a ray.
865    pub fn cast_local_ray_and_get_normal(
866        &self,
867        ray_origin: Vect,
868        ray_dir: Vect,
869        max_time_of_impact: Real,
870        solid: bool,
871    ) -> Option<RayIntersection> {
872        let ray = Ray::new(ray_origin, ray_dir);
873        self.raw
874            .cast_local_ray_and_get_normal(&ray, max_time_of_impact, solid)
875            .map(|inter| RayIntersection::from_rapier(inter, ray_origin, ray_dir))
876    }
877
878    /// Tests whether a ray intersects this transformed shape.
879    pub fn intersects_local_ray(
880        &self,
881        ray_origin: Vect,
882        ray_dir: Vect,
883        max_time_of_impact: Real,
884    ) -> bool {
885        let ray = Ray::new(ray_origin, ray_dir);
886        self.raw.intersects_local_ray(&ray, max_time_of_impact)
887    }
888
889    /// Computes the time of impact between this transform shape and a ray.
890    pub fn cast_ray(
891        &self,
892        translation: Vect,
893        rotation: Rot,
894        ray_origin: Vect,
895        ray_dir: Vect,
896        max_time_of_impact: Real,
897        solid: bool,
898    ) -> Option<Real> {
899        let pos = crate::utils::pose_from(translation, rotation);
900        let ray = Ray::new(ray_origin, ray_dir);
901        self.raw.cast_ray(&pos, &ray, max_time_of_impact, solid)
902    }
903
904    /// Computes the time of impact, and normal between this transformed shape and a ray.
905    pub fn cast_ray_and_get_normal(
906        &self,
907        translation: Vect,
908        rotation: Rot,
909        ray_origin: Vect,
910        ray_dir: Vect,
911        max_time_of_impact: Real,
912        solid: bool,
913    ) -> Option<RayIntersection> {
914        let pos = crate::utils::pose_from(translation, rotation);
915        let ray = Ray::new(ray_origin, ray_dir);
916        self.raw
917            .cast_ray_and_get_normal(&pos, &ray, max_time_of_impact, solid)
918            .map(|inter| RayIntersection::from_rapier(inter, ray_origin, ray_dir))
919    }
920
921    /// Tests whether a ray intersects this transformed shape.
922    pub fn intersects_ray(
923        &self,
924        translation: Vect,
925        rotation: Rot,
926        ray_origin: Vect,
927        ray_dir: Vect,
928        max_time_of_impact: Real,
929    ) -> bool {
930        let pos = crate::utils::pose_from(translation, rotation);
931        let ray = Ray::new(ray_origin, ray_dir);
932        self.raw.intersects_ray(&pos, &ray, max_time_of_impact)
933    }
934
935    /// Computes the axis-aligned bounding box of `self` transformed by the given translation
936    /// and rotation.
937    pub fn aabb(&self, translation: Vect, rotation: Rot) -> BevyAabb {
938        let pos = crate::utils::pose_from(translation, rotation);
939        let aabb = self.raw.compute_aabb(&pos);
940        BevyAabb::new(aabb.center(), aabb.half_extents())
941    }
942
943    /// Computes the axis-aligned bounding box of `self` in its local-space.
944    pub fn local_aabb(&self) -> BevyAabb {
945        let aabb = self.raw.compute_local_aabb();
946        BevyAabb::new(aabb.center(), aabb.half_extents())
947    }
948
949    /// Computes a bounding sphere (or circle in 2D) of `self` transformed by the given
950    /// translation and rotation.
951    pub fn bounding_sphere(&self, translation: Vect, rotation: Rot) -> BevyBoundingSphere {
952        let pos = crate::utils::pose_from(translation, rotation);
953        let sphere = self.raw.compute_bounding_sphere(&pos);
954        BevyBoundingSphere::new(sphere.center, sphere.radius)
955    }
956
957    /// Computes a bounding sphere (or circle in 2D) of `self` in its local-space.
958    pub fn local_bounding_sphere(&self) -> BevyBoundingSphere {
959        let sphere = self.raw.compute_local_bounding_sphere();
960        BevyBoundingSphere::new(sphere.center, sphere.radius)
961    }
962
963    /// Computes the mass properties of `self`, assuming it has the given uniform density.
964    pub fn mass_properties(&self, density: Real) -> MassProperties {
965        MassProperties::from_rapier(self.raw.mass_properties(density))
966    }
967
968    /// Computes the contact between `self` and `other`, each transformed by their respective
969    /// translation and rotation.
970    ///
971    /// Returns `Ok(None)` if the colliders are separated by more than `prediction`. The contact
972    /// points and normals are expressed in world-space.
973    #[allow(clippy::too_many_arguments)]
974    pub fn contact(
975        &self,
976        translation: Vect,
977        rotation: Rot,
978        other: &Collider,
979        other_translation: Vect,
980        other_rotation: Rot,
981        prediction: Real,
982    ) -> Result<Option<ShapeContact>, Unsupported> {
983        let pos1 = crate::utils::pose_from(translation, rotation);
984        let pos2 = crate::utils::pose_from(other_translation, other_rotation);
985        Ok(
986            query::contact(&pos1, &*self.raw, &pos2, &*other.raw, prediction)?
987                .map(ShapeContact::from_rapier),
988        )
989    }
990
991    /// Computes the minimal distance between `self` and `other`, each transformed by their
992    /// respective translation and rotation.
993    ///
994    /// Returns `0.0` if the colliders are intersecting.
995    pub fn distance(
996        &self,
997        translation: Vect,
998        rotation: Rot,
999        other: &Collider,
1000        other_translation: Vect,
1001        other_rotation: Rot,
1002    ) -> Result<Real, Unsupported> {
1003        let pos1 = crate::utils::pose_from(translation, rotation);
1004        let pos2 = crate::utils::pose_from(other_translation, other_rotation);
1005        Ok(query::distance(&pos1, &*self.raw, &pos2, &*other.raw)?.distance)
1006    }
1007
1008    /// Tests whether `self` and `other`, each transformed by their respective translation and
1009    /// rotation, are intersecting.
1010    pub fn intersection_test(
1011        &self,
1012        translation: Vect,
1013        rotation: Rot,
1014        other: &Collider,
1015        other_translation: Vect,
1016        other_rotation: Rot,
1017    ) -> Result<bool, Unsupported> {
1018        let pos1 = crate::utils::pose_from(translation, rotation);
1019        let pos2 = crate::utils::pose_from(other_translation, other_rotation);
1020        Ok(query::intersection_test(&pos1, &*self.raw, &pos2, &*other.raw)?.intersecting)
1021    }
1022
1023    /// Computes the closest points between `self` and `other`, each transformed by their
1024    /// respective translation and rotation.
1025    ///
1026    /// Returns [`ShapeClosestPoints::Disjoint`] if the colliders are separated by more than
1027    /// `max_dist`. The closest points are expressed in world-space.
1028    #[allow(clippy::too_many_arguments)]
1029    pub fn closest_points(
1030        &self,
1031        translation: Vect,
1032        rotation: Rot,
1033        other: &Collider,
1034        other_translation: Vect,
1035        other_rotation: Rot,
1036        max_dist: Real,
1037    ) -> Result<ShapeClosestPoints, Unsupported> {
1038        let pos1 = crate::utils::pose_from(translation, rotation);
1039        let pos2 = crate::utils::pose_from(other_translation, other_rotation);
1040        Ok(ShapeClosestPoints::from_rapier(query::closest_points(
1041            &pos1,
1042            &*self.raw,
1043            &pos2,
1044            &*other.raw,
1045            max_dist,
1046        )?))
1047    }
1048
1049    /// Computes the first time of impact between `self` and `other`, each starting at their
1050    /// respective translation and rotation, and moving with a constant linear velocity.
1051    ///
1052    /// Returns `Ok(None)` if they do not hit within [`ShapeCastOptions::max_time_of_impact`].
1053    /// The witness points and normals of the result are expressed in the local-space of
1054    /// each collider.
1055    #[allow(clippy::too_many_arguments)]
1056    pub fn cast_shape(
1057        &self,
1058        translation: Vect,
1059        rotation: Rot,
1060        velocity: Vect,
1061        other: &Collider,
1062        other_translation: Vect,
1063        other_rotation: Rot,
1064        other_velocity: Vect,
1065        options: ShapeCastOptions,
1066    ) -> Result<Option<ShapeCastHit>, Unsupported> {
1067        let pos1 = crate::utils::pose_from(translation, rotation);
1068        let pos2 = crate::utils::pose_from(other_translation, other_rotation);
1069        Ok(query::cast_shapes(
1070            &pos1,
1071            velocity,
1072            &*self.raw,
1073            &pos2,
1074            other_velocity,
1075            &*other.raw,
1076            options,
1077        )?
1078        .map(|hit| ShapeCastHit::from_rapier(hit, options.compute_impact_geometry_on_penetration)))
1079    }
1080
1081    /// Computes the first time of impact between `self` and `other`, each following a
1082    /// nonlinear rigid motion (with both linear and angular velocities).
1083    ///
1084    /// Only the time interval `[start_time, end_time]` is considered. If
1085    /// `stop_at_penetration` is `true` and the colliders are initially penetrating, a hit
1086    /// at `start_time` is returned. The witness points and normals of the result are
1087    /// expressed in the local-space of each collider.
1088    pub fn cast_shape_nonlinear(
1089        &self,
1090        motion: &NonlinearMotion,
1091        other: &Collider,
1092        other_motion: &NonlinearMotion,
1093        start_time: Real,
1094        end_time: Real,
1095        stop_at_penetration: bool,
1096    ) -> Result<Option<ShapeCastHit>, Unsupported> {
1097        Ok(query::cast_shapes_nonlinear(
1098            &motion.into_rapier(),
1099            &*self.raw,
1100            &other_motion.into_rapier(),
1101            &*other.raw,
1102            start_time,
1103            end_time,
1104            stop_at_penetration,
1105        )?
1106        .map(|hit| ShapeCastHit::from_rapier(hit, false)))
1107    }
1108}
1109
1110impl Default for Collider {
1111    fn default() -> Self {
1112        Self::ball(0.5)
1113    }
1114}
1115
1116#[cfg(all(feature = "dim3", feature = "async-collider"))]
1117#[allow(clippy::type_complexity)]
1118fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<(Vec<Vector>, Vec<[u32; 3]>)> {
1119    let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
1120    let indices = mesh.indices()?;
1121
1122    let vtx: Vec<Vector> = match vertices {
1123        VertexAttributeValues::Float32(vtx) => Some(
1124            vtx.chunks(3)
1125                .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
1126                .collect(),
1127        ),
1128        VertexAttributeValues::Float32x3(vtx) => Some(
1129            vtx.iter()
1130                .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
1131                .collect(),
1132        ),
1133        _ => None,
1134    }?;
1135
1136    let idx = match indices {
1137        Indices::U16(idx) => idx
1138            .as_chunks::<3>()
1139            .0
1140            .iter()
1141            .map(|i| i.map(u32::from))
1142            .collect(),
1143        Indices::U32(idx) => idx.as_chunks::<3>().0.to_vec(),
1144    };
1145
1146    Some((vtx, idx))
1147}
1148
1149#[cfg(test)]
1150mod tests {
1151    use super::*;
1152    use crate::geometry::ShapeCastStatus;
1153
1154    #[cfg(feature = "dim2")]
1155    const ROT_ID: Rot = 0.0;
1156    #[cfg(feature = "dim3")]
1157    const ROT_ID: Rot = Rot::IDENTITY;
1158
1159    fn assert_approx_eq(a: Real, b: Real) {
1160        assert!((a - b).abs() < 1.0e-4, "{a} != {b}");
1161    }
1162
1163    fn line_vertices() -> Vec<Vect> {
1164        vec![Vect::ZERO, Vect::X, Vect::X * 2.0]
1165    }
1166
1167    #[test]
1168    fn polyline_with_flags_constructor() {
1169        let collider =
1170            Collider::polyline_with_flags(line_vertices(), None, PolylineFlags::DEFORMABLE);
1171        let polyline = collider.as_polyline().unwrap();
1172        assert_eq!(polyline.flags(), PolylineFlags::DEFORMABLE);
1173        assert_eq!(polyline.num_segments(), 2);
1174    }
1175
1176    #[test]
1177    #[cfg(feature = "dim2")]
1178    fn oriented_polyline_constructor() {
1179        let collider = Collider::oriented_polyline(line_vertices(), None);
1180        let polyline = collider.as_polyline().unwrap();
1181        assert!(polyline.flags().contains(PolylineFlags::ORIENTED));
1182        assert_eq!(polyline.pseudo_normals().unwrap().len(), 3);
1183    }
1184
1185    #[test]
1186    #[cfg(feature = "dim2")]
1187    fn convex_polyline_unmodified_keeps_collinear_points() {
1188        let points = vec![
1189            Vect::new(0.0, 0.0),
1190            Vect::new(1.0, 0.0),
1191            Vect::new(2.0, 0.0),
1192            Vect::new(2.0, 2.0),
1193            Vect::new(0.0, 2.0),
1194        ];
1195        let unmodified = Collider::convex_polyline_unmodified(points.clone()).unwrap();
1196        assert_eq!(unmodified.as_convex_polygon().unwrap().points().len(), 5);
1197        let simplified = Collider::convex_polyline(points).unwrap();
1198        assert_eq!(simplified.as_convex_polygon().unwrap().points().len(), 4);
1199    }
1200
1201    #[test]
1202    #[cfg(feature = "dim3")]
1203    fn heightfield_with_flags_constructor() {
1204        let collider = Collider::heightfield_with_flags(
1205            vec![0.0; 9],
1206            3,
1207            3,
1208            Vect::ONE,
1209            HeightFieldFlags::FIX_INTERNAL_EDGES,
1210        );
1211        let heightfield = collider.as_heightfield().unwrap();
1212        assert_eq!(heightfield.flags(), HeightFieldFlags::FIX_INTERNAL_EDGES);
1213    }
1214
1215    #[test]
1216    fn compound_with_flags_constructor_and_mutation() {
1217        let parts = vec![
1218            (Vect::ZERO, ROT_ID, Collider::ball(0.5)),
1219            (Vect::X, ROT_ID, Collider::ball(0.5)),
1220        ];
1221        let mut collider =
1222            Collider::compound_with_flags(parts, CompoundFlags::FIX_INTERNAL_EDGES, None);
1223        assert_eq!(
1224            collider.as_compound().unwrap().flags(),
1225            CompoundFlags::FIX_INTERNAL_EDGES
1226        );
1227        assert_eq!(collider.as_compound().unwrap().shapes().len(), 2);
1228
1229        let shared = collider.clone();
1230        collider
1231            .as_compound_mut()
1232            .unwrap()
1233            .set_flags(CompoundFlags::empty(), None);
1234        assert_eq!(
1235            collider.as_compound().unwrap().flags(),
1236            CompoundFlags::empty()
1237        );
1238        // The mutation must not leak into the clone sharing the same shape.
1239        assert_eq!(
1240            shared.as_compound().unwrap().flags(),
1241            CompoundFlags::FIX_INTERNAL_EDGES
1242        );
1243    }
1244
1245    #[test]
1246    fn converted_trimesh_constructor() {
1247        let offset = Vect::ONE * 3.0;
1248        let (vertices, indices) = cuboid_mesh_for_tests(offset);
1249
1250        let aabb =
1251            Collider::converted_trimesh(vertices.clone(), indices.clone(), MeshConverter::Aabb)
1252                .unwrap();
1253        // The offset of the AABB is preserved by a single-part compound.
1254        let compound = aabb.as_compound().unwrap();
1255        let (translation, _, part) = compound.shapes().next().unwrap();
1256        assert!((translation - offset).length() < 1.0e-5);
1257        assert!(matches!(part, ColliderView::Cuboid(_)));
1258
1259        let hull = Collider::converted_trimesh(
1260            vertices.clone(),
1261            indices.clone(),
1262            MeshConverter::ConvexHull,
1263        )
1264        .unwrap();
1265        #[cfg(feature = "dim2")]
1266        assert!(hull.as_convex_polygon().is_some());
1267        #[cfg(feature = "dim3")]
1268        assert!(hull.as_convex_polyhedron().is_some());
1269
1270        let trimesh =
1271            Collider::converted_trimesh(vertices, indices, MeshConverter::TriMesh).unwrap();
1272        assert!(trimesh.as_trimesh().is_some());
1273    }
1274
1275    #[test]
1276    fn trimesh_view_set_vertices() {
1277        let (vertices, indices) = cuboid_mesh_for_tests(Vect::ZERO);
1278        let mut collider =
1279            Collider::trimesh_with_flags(vertices.clone(), indices, TriMeshFlags::DEFORMABLE)
1280                .unwrap();
1281        assert_eq!(
1282            collider.as_trimesh().unwrap().flags(),
1283            TriMeshFlags::DEFORMABLE
1284        );
1285
1286        let shift = Vect::X * 10.0;
1287        let shifted: Vec<_> = vertices.iter().map(|v| *v + shift).collect();
1288        collider.as_trimesh_mut().unwrap().set_vertices(&shifted);
1289        let aabb = collider.local_aabb();
1290        assert_approx_eq(aabb.min.x, 9.5);
1291        assert_approx_eq(aabb.max.x, 10.5);
1292
1293        collider
1294            .as_trimesh_mut()
1295            .unwrap()
1296            .update_vertices(|vtx| vtx.iter_mut().for_each(|v| *v -= shift));
1297        assert_approx_eq(collider.local_aabb().max.x, 0.5);
1298    }
1299
1300    #[test]
1301    fn polyline_view_set_vertices() {
1302        let mut collider =
1303            Collider::polyline_with_flags(line_vertices(), None, PolylineFlags::DEFORMABLE);
1304        let lifted = vec![Vect::ZERO, Vect::X + Vect::Y, Vect::X * 2.0];
1305        collider.as_polyline_mut().unwrap().set_vertices(&lifted);
1306        assert_approx_eq(collider.local_aabb().max.y, 1.0);
1307    }
1308
1309    #[test]
1310    fn voxels_view_set_voxel_size() {
1311        let mut collider = Collider::voxels(Vect::ONE, &[IVect::ZERO, IVect::X]);
1312        let voxels = collider.as_voxels().unwrap();
1313        assert_eq!(voxels.voxel_size(), Vect::ONE);
1314        assert_eq!(voxels.voxel_center(IVect::X), Vect::X + Vect::splat(0.5));
1315        assert_eq!(
1316            voxels
1317                .cropped(IVect::ZERO, IVect::ZERO)
1318                .unwrap()
1319                .voxels()
1320                .count(),
1321            1
1322        );
1323
1324        collider
1325            .as_voxels_mut()
1326            .unwrap()
1327            .set_voxel_size(Vect::splat(2.0));
1328        let voxels = collider.as_voxels().unwrap();
1329        assert_eq!(voxels.voxel_size(), Vect::splat(2.0));
1330        assert_eq!(voxels.voxel_center(IVect::X), Vect::X * 2.0 + Vect::ONE);
1331    }
1332
1333    #[test]
1334    #[cfg(feature = "dim3")]
1335    fn convex_polyhedron_topology() {
1336        let (vertices, _) = cuboid_mesh_for_tests(Vect::ZERO);
1337        let collider = Collider::convex_hull(&vertices).unwrap();
1338        let polyhedron = collider.as_convex_polyhedron().unwrap();
1339        assert_eq!(polyhedron.num_vertices(), 8);
1340        assert_eq!(polyhedron.edges().count(), 12);
1341        assert_eq!(polyhedron.num_faces(), 6);
1342        for i in 0..polyhedron.num_faces() {
1343            assert_eq!(polyhedron.face_vertices(i).len(), 4);
1344            assert_eq!(polyhedron.face_edges(i).len(), 4);
1345            assert_approx_eq(polyhedron.face_normal(i).length(), 1.0);
1346        }
1347        for i in 0..polyhedron.num_vertices() {
1348            assert_eq!(polyhedron.vertex_adjacent_faces(i).len(), 3);
1349        }
1350        let points: Vec<_> = polyhedron.points().collect();
1351        for edge in polyhedron.edges() {
1352            let [a, b] = polyhedron.edge_vertices(edge as usize);
1353            let dir = (points[b as usize] - points[a as usize]).normalize();
1354            assert!((dir - polyhedron.edge_direction(edge as usize)).length() < 1.0e-5);
1355            let [f1, f2] = polyhedron.edge_faces(edge as usize);
1356            assert!(polyhedron.face_edges(f1 as usize).contains(&edge));
1357            assert!(polyhedron.face_edges(f2 as usize).contains(&edge));
1358        }
1359    }
1360
1361    #[test]
1362    fn custom_shape_view_does_not_panic() {
1363        let collider = Collider::ball(1.0);
1364        let view = ColliderView::Custom(&*collider.raw);
1365        assert!(matches!(
1366            view.as_typed_shape(),
1367            rapier::parry::shape::TypedShape::Custom(_)
1368        ));
1369        let shared = view.to_shared_shape();
1370        assert!(shared.as_ball().is_some());
1371        assert!(view.raw_scale_by(Vect::splat(2.0), 10).is_some());
1372    }
1373
1374    #[test]
1375    fn bounding_volumes_and_mass_properties() {
1376        let collider = Collider::ball(1.0);
1377        let translation = Vect::X * 2.0;
1378
1379        let aabb = collider.aabb(translation, ROT_ID);
1380        assert_approx_eq(aabb.min.x, 1.0);
1381        assert_approx_eq(aabb.max.x, 3.0);
1382        assert_approx_eq(aabb.max.y, 1.0);
1383
1384        let sphere = collider.bounding_sphere(translation, ROT_ID);
1385        assert_approx_eq(sphere.center.x, 2.0);
1386        assert_approx_eq(sphere.radius(), 1.0);
1387
1388        let mprops = collider.mass_properties(2.0);
1389        #[cfg(feature = "dim2")]
1390        assert_approx_eq(mprops.mass, 2.0 * core::f32::consts::PI);
1391        #[cfg(feature = "dim3")]
1392        assert_approx_eq(mprops.mass, 2.0 * 4.0 / 3.0 * core::f32::consts::PI);
1393    }
1394
1395    #[test]
1396    fn pairwise_queries() {
1397        let ball = Collider::ball(1.0);
1398        let pos1 = Vect::ZERO;
1399        let pos2 = Vect::X * 3.0;
1400
1401        let distance = ball.distance(pos1, ROT_ID, &ball, pos2, ROT_ID).unwrap();
1402        assert_approx_eq(distance, 1.0);
1403
1404        assert!(!ball
1405            .intersection_test(pos1, ROT_ID, &ball, pos2, ROT_ID)
1406            .unwrap());
1407        assert!(ball
1408            .intersection_test(pos1, ROT_ID, &ball, Vect::X, ROT_ID)
1409            .unwrap());
1410
1411        let contact = ball
1412            .contact(pos1, ROT_ID, &ball, pos2, ROT_ID, 2.0)
1413            .unwrap()
1414            .unwrap();
1415        assert_approx_eq(contact.distance, 1.0);
1416        assert!((contact.normal1 - Vect::X).length() < 1.0e-5);
1417        assert!((contact.point1 - Vect::X).length() < 1.0e-5);
1418        assert!((contact.point2 - Vect::X * 2.0).length() < 1.0e-5);
1419        assert!(ball
1420            .contact(pos1, ROT_ID, &ball, pos2, ROT_ID, 0.5)
1421            .unwrap()
1422            .is_none());
1423
1424        match ball
1425            .closest_points(pos1, ROT_ID, &ball, pos2, ROT_ID, 2.0)
1426            .unwrap()
1427        {
1428            ShapeClosestPoints::WithinMargin(p1, p2) => {
1429                assert!((p1 - Vect::X).length() < 1.0e-5);
1430                assert!((p2 - Vect::X * 2.0).length() < 1.0e-5);
1431            }
1432            other => panic!("Unexpected closest points: {other:?}"),
1433        }
1434        assert_eq!(
1435            ball.closest_points(pos1, ROT_ID, &ball, pos2, ROT_ID, 0.5)
1436                .unwrap(),
1437            ShapeClosestPoints::Disjoint
1438        );
1439        assert_eq!(
1440            ball.closest_points(pos1, ROT_ID, &ball, Vect::X, ROT_ID, 0.5)
1441                .unwrap(),
1442            ShapeClosestPoints::Intersecting
1443        );
1444
1445        let hit = ball
1446            .cast_shape(
1447                pos1,
1448                ROT_ID,
1449                Vect::X,
1450                &ball,
1451                pos2,
1452                ROT_ID,
1453                Vect::ZERO,
1454                ShapeCastOptions::with_max_time_of_impact(10.0),
1455            )
1456            .unwrap()
1457            .unwrap();
1458        assert_approx_eq(hit.time_of_impact, 1.0);
1459        assert_eq!(hit.status, ShapeCastStatus::Converged);
1460        let details = hit.details.unwrap();
1461        assert!((details.normal1 - Vect::X).length() < 1.0e-4);
1462
1463        let mut motion1 = NonlinearMotion::constant_position(pos1, ROT_ID);
1464        motion1.linear_velocity = Vect::X;
1465        let motion2 = NonlinearMotion::constant_position(pos2, ROT_ID);
1466        let hit = ball
1467            .cast_shape_nonlinear(&motion1, &ball, &motion2, 0.0, 10.0, true)
1468            .unwrap()
1469            .unwrap();
1470        assert!((hit.time_of_impact - 1.0).abs() < 1.0e-2);
1471    }
1472
1473    /// The vertex and index buffers of a unit cube (3D) or square (2D) mesh centered at `offset`.
1474    fn cuboid_mesh_for_tests(offset: Vect) -> (Vec<Vect>, Vec<[u32; 3]>) {
1475        let (vtx, idx) = rapier::prelude::Cuboid::new(Vect::splat(0.5)).to_trimesh();
1476        (vtx.into_iter().map(|v| v + offset).collect(), idx)
1477    }
1478}