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 pub fn scale(&self) -> Vect {
40 self.scale
41 }
42
43 pub fn promote_scaled_shape(&mut self) {
46 self.unscaled = self.raw.clone();
47 self.scale = Vect::ONE;
48 }
49
50 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 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 pub fn ball(radius: Real) -> Self {
78 SharedShape::ball(radius).into()
79 }
80
81 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 #[cfg(feature = "dim3")]
94 pub fn cylinder(half_height: Real, radius: Real) -> Self {
95 SharedShape::cylinder(half_height, radius).into()
96 }
97
98 #[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 #[cfg(feature = "dim3")]
109 pub fn cone(half_height: Real, radius: Real) -> Self {
110 SharedShape::cone(half_height, radius).into()
111 }
112
113 #[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 #[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 #[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 pub fn capsule(start: Vect, end: Vect, radius: Real) -> Self {
136 SharedShape::capsule(start, end, radius).into()
137 }
138
139 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 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 #[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 #[cfg(feature = "dim3")]
160 pub fn cuboid(hx: Real, hy: Real, hz: Real) -> Self {
161 SharedShape::cuboid(hx, hy, hz).into()
162 }
163
164 #[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 pub fn segment(a: Vect, b: Vect) -> Self {
173 SharedShape::segment(a, b).into()
174 }
175
176 pub fn triangle(a: Vect, b: Vect, c: Vect) -> Self {
178 SharedShape::triangle(a, b, c).into()
179 }
180
181 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 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 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 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 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 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 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 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 #[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 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 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 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 #[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 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 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 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 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 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 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 #[cfg(feature = "dim2")]
418 pub fn convex_polyline(points: Vec<Vect>) -> Option<Self> {
419 SharedShape::convex_polyline(points).map(Into::into)
420 }
421
422 #[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 #[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 #[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 #[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 #[cfg(feature = "dim2")]
465 pub fn heightfield(heights: Vec<Real>, scale: Vect) -> Self {
466 SharedShape::heightfield(heights, scale).into()
467 }
468
469 #[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 #[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 pub fn as_typed_shape(&self) -> ColliderView<'_> {
504 self.raw.as_typed_shape().into()
505 }
506
507 pub fn as_unscaled_typed_shape(&self) -> ColliderView<'_> {
509 self.unscaled.as_typed_shape().into()
510 }
511
512 pub fn as_ball(&self) -> Option<BallView<'_>> {
514 self.raw.as_ball().map(|s| BallView { raw: s })
515 }
516
517 pub fn as_cuboid(&self) -> Option<CuboidView<'_>> {
519 self.raw.as_cuboid().map(|s| CuboidView { raw: s })
520 }
521
522 pub fn as_capsule(&self) -> Option<CapsuleView<'_>> {
524 self.raw.as_capsule().map(|s| CapsuleView { raw: s })
525 }
526
527 pub fn as_segment(&self) -> Option<SegmentView<'_>> {
529 self.raw.as_segment().map(|s| SegmentView { raw: s })
530 }
531
532 pub fn as_triangle(&self) -> Option<TriangleView<'_>> {
534 self.raw.as_triangle().map(|s| TriangleView { raw: s })
535 }
536
537 pub fn as_voxels(&self) -> Option<VoxelsView<'_>> {
539 self.raw.as_voxels().map(|s| VoxelsView { raw: s })
540 }
541
542 pub fn as_trimesh(&self) -> Option<TriMeshView<'_>> {
544 self.raw.as_trimesh().map(|s| TriMeshView { raw: s })
545 }
546
547 pub fn as_polyline(&self) -> Option<PolylineView<'_>> {
549 self.raw.as_polyline().map(|s| PolylineView { raw: s })
550 }
551
552 pub fn as_halfspace(&self) -> Option<HalfSpaceView<'_>> {
554 self.raw.as_halfspace().map(|s| HalfSpaceView { raw: s })
555 }
556
557 pub fn as_heightfield(&self) -> Option<HeightFieldView<'_>> {
559 self.raw
560 .as_heightfield()
561 .map(|s| HeightFieldView { raw: s })
562 }
563
564 pub fn as_compound(&self) -> Option<CompoundView<'_>> {
566 self.raw.as_compound().map(|s| CompoundView { raw: s })
567 }
568
569 #[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 #[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 #[cfg(feature = "dim3")]
587 pub fn as_cylinder(&self) -> Option<CylinderView<'_>> {
588 self.raw.as_cylinder().map(|s| CylinderView { raw: s })
589 }
590
591 #[cfg(feature = "dim3")]
593 pub fn as_cone(&self) -> Option<ConeView<'_>> {
594 self.raw.as_cone().map(|s| ConeView { raw: s })
595 }
596
597 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 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 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 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 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 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 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 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 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 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 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 #[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 #[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 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 return;
735 }
736
737 if scale == Vect::ONE {
738 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 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 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 pub fn project_local_point(&self, point: Vect, solid: bool) -> PointProjection {
788 self.raw.project_local_point(point, solid).into()
789 }
790
791 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 pub fn distance_to_local_point(&self, point: Vect, solid: bool) -> Real {
800 self.raw.distance_to_local_point(point, solid)
801 }
802
803 pub fn contains_local_point(&self, point: Vect) -> bool {
805 self.raw.contains_local_point(point)
806 }
807
808 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 #[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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn mass_properties(&self, density: Real) -> MassProperties {
965 MassProperties::from_rapier(self.raw.mass_properties(density))
966 }
967
968 #[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 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 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 #[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 #[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 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 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 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 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}