1#![allow(clippy::unnecessary_cast)]
2
3pub mod contact_query;
4
5#[cfg(feature = "2d")]
6mod primitives2d;
7#[cfg(feature = "3d")]
8mod primitives3d;
9
10#[cfg(feature = "2d")]
11pub use primitives2d::{EllipseColliderShape, RegularPolygonColliderShape};
12
13use crate::{make_isometry, prelude::*};
14#[cfg(feature = "collider-from-mesh")]
15use bevy::mesh::{Indices, VertexAttributeValues};
16use bevy::{log, prelude::*};
17use contact_query::UnsupportedShape;
18use itertools::Either;
19use parry::{
20 math::Point,
21 shape::{RoundShape, SharedShape, TypedShape, Voxels},
22};
23
24impl<T: IntoCollider<Collider>> From<T> for Collider {
25 fn from(value: T) -> Self {
26 value.collider()
27 }
28}
29
30#[derive(Clone, PartialEq, Debug, Reflect)]
34#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
35#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
36#[reflect(PartialEq, Debug)]
37pub struct VhacdParameters {
38 pub concavity: Scalar,
43 pub alpha: Scalar,
48 pub beta: Scalar,
53 pub resolution: u32,
57 pub plane_downsampling: u32,
62 pub convex_hull_downsampling: u32,
67 pub fill_mode: FillMode,
72 pub convex_hull_approximation: bool,
78 pub max_convex_hulls: u32,
82}
83
84impl Default for VhacdParameters {
85 fn default() -> Self {
86 Self {
87 #[cfg(feature = "3d")]
88 resolution: 64,
89 #[cfg(feature = "3d")]
90 concavity: 0.01,
91 #[cfg(feature = "2d")]
92 resolution: 256,
93 #[cfg(feature = "2d")]
94 concavity: 0.1,
95 plane_downsampling: 4,
96 convex_hull_downsampling: 4,
97 alpha: 0.05,
98 beta: 0.05,
99 convex_hull_approximation: true,
100 max_convex_hulls: 1024,
101 fill_mode: FillMode::FloodFill {
102 detect_cavities: false,
103 #[cfg(feature = "2d")]
104 detect_self_intersections: false,
105 },
106 }
107 }
108}
109
110impl From<VhacdParameters> for parry::transformation::vhacd::VHACDParameters {
111 fn from(value: VhacdParameters) -> Self {
112 Self {
113 concavity: value.concavity,
114 alpha: value.alpha,
115 beta: value.beta,
116 resolution: value.resolution,
117 plane_downsampling: value.plane_downsampling,
118 convex_hull_downsampling: value.convex_hull_downsampling,
119 fill_mode: value.fill_mode.into(),
120 convex_hull_approximation: value.convex_hull_approximation,
121 max_convex_hulls: value.max_convex_hulls,
122 }
123 }
124}
125
126#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
129#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
130#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
131#[reflect(Hash, PartialEq, Debug)]
132pub enum FillMode {
133 SurfaceOnly,
136 FloodFill {
140 detect_cavities: bool,
142 #[cfg(feature = "2d")]
144 detect_self_intersections: bool,
145 },
146}
147
148impl From<FillMode> for parry::transformation::voxelization::FillMode {
149 fn from(value: FillMode) -> Self {
150 match value {
151 FillMode::SurfaceOnly => Self::SurfaceOnly,
152 FillMode::FloodFill {
153 detect_cavities,
154 #[cfg(feature = "2d")]
155 detect_self_intersections,
156 } => Self::FloodFill {
157 detect_cavities,
158 #[cfg(feature = "2d")]
159 detect_self_intersections,
160 },
161 }
162 }
163}
164
165#[repr(transparent)]
167#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
168#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
169#[reflect(opaque, Hash, PartialEq, Debug)]
170pub struct TrimeshFlags(u8);
171
172bitflags::bitflags! {
173 impl TrimeshFlags: u8 {
174 const HALF_EDGE_TOPOLOGY = 0b0000_0001;
176 const CONNECTED_COMPONENTS = 0b0000_0010;
182 const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
184 const ORIENTED = 0b0000_1000;
188 const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
193 const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
199 const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
205 const FIX_INTERNAL_EDGES = 0b1000_0000 | Self::ORIENTED.bits() | Self::MERGE_DUPLICATE_VERTICES.bits();
212 }
213}
214
215impl From<TrimeshFlags> for parry::shape::TriMeshFlags {
216 fn from(value: TrimeshFlags) -> Self {
217 Self::from_bits(value.bits().into()).unwrap()
218 }
219}
220
221pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError;
223
224#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
232#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
233#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
238#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
239#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
251#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
252#[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")]
259#[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")]
260#[cfg_attr(
263 feature = "2d",
264 doc = " commands.spawn((RigidBody::Static, Collider::rectangle(5.0, 0.5)));"
265)]
266#[cfg_attr(
267 feature = "3d",
268 doc = " commands.spawn((RigidBody::Static, Collider::cuboid(5.0, 0.5, 5.0)));"
269)]
270#[cfg_attr(
281 feature = "3d",
282 doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
283)]
284#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
294#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
295#[cfg_attr(
301 feature = "2d",
302 doc = " .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
303)]
304#[cfg_attr(
305 feature = "3d",
306 doc = " .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
307)]
308#[cfg_attr(
311 feature = "2d",
312 doc = " children.spawn((Collider::circle(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
313 children.spawn((Collider::circle(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
314)]
315#[cfg_attr(
316 feature = "3d",
317 doc = " children.spawn((Collider::sphere(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
318 children.spawn((Collider::sphere(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
319)]
320#[cfg_attr(
340 feature = "3d",
341 doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
342)]
343#[derive(Clone, Component, Debug)]
359#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
360#[require(
361 ColliderMarker,
362 ColliderAabb,
363 CollisionLayers,
364 ColliderDensity,
365 ColliderMassProperties
366)]
367pub struct Collider {
368 shape: SharedShape,
370 scaled_shape: SharedShape,
375 scale: Vector,
377}
378
379impl From<SharedShape> for Collider {
380 fn from(value: SharedShape) -> Self {
381 Self {
382 shape: value.clone(),
383 scaled_shape: value,
384 scale: Vector::ONE,
385 }
386 }
387}
388
389impl Default for Collider {
390 fn default() -> Self {
391 #[cfg(feature = "2d")]
392 {
393 Self::rectangle(0.5, 0.5)
394 }
395 #[cfg(feature = "3d")]
396 {
397 Self::cuboid(0.5, 0.5, 0.5)
398 }
399 }
400}
401
402impl AnyCollider for Collider {
403 type Context = ();
404
405 fn aabb_with_context(
406 &self,
407 position: Vector,
408 rotation: impl Into<Rotation>,
409 _: AabbContext<Self::Context>,
410 ) -> ColliderAabb {
411 let aabb = self
412 .shape_scaled()
413 .compute_aabb(&make_isometry(position, rotation));
414 ColliderAabb {
415 min: aabb.mins.into(),
416 max: aabb.maxs.into(),
417 }
418 }
419
420 fn contact_manifolds_with_context(
421 &self,
422 other: &Self,
423 position1: Vector,
424 rotation1: impl Into<Rotation>,
425 position2: Vector,
426 rotation2: impl Into<Rotation>,
427 prediction_distance: Scalar,
428 manifolds: &mut Vec<ContactManifold>,
429 _: ContactManifoldContext<Self::Context>,
430 ) {
431 contact_query::contact_manifolds(
432 self,
433 position1,
434 rotation1,
435 other,
436 position2,
437 rotation2,
438 prediction_distance,
439 manifolds,
440 )
441 }
442}
443
444#[cfg(feature = "2d")]
447impl ComputeMassProperties for Collider {
448 fn mass(&self, density: f32) -> f32 {
449 let props = self.shape_scaled().mass_properties(density as Scalar);
450 props.mass() as f32
451 }
452
453 fn unit_angular_inertia(&self) -> f32 {
454 self.angular_inertia(1.0)
455 }
456
457 fn angular_inertia(&self, mass: f32) -> f32 {
458 let props = self.shape_scaled().mass_properties(mass as Scalar);
459 props.principal_inertia() as f32
460 }
461
462 fn center_of_mass(&self) -> Vec2 {
463 let props = self.shape_scaled().mass_properties(1.0);
464 Vector::from(props.local_com).f32()
465 }
466
467 fn mass_properties(&self, density: f32) -> MassProperties {
468 let props = self.shape_scaled().mass_properties(density as Scalar);
469
470 MassProperties {
471 mass: props.mass() as f32,
472 #[cfg(feature = "2d")]
473 angular_inertia: props.principal_inertia() as f32,
474 #[cfg(feature = "3d")]
475 principal_angular_inertia: Vector::from(props.principal_inertia()).f32(),
476 #[cfg(feature = "3d")]
477 local_inertial_frame: Quaternion::from(props.principal_inertia_local_frame).f32(),
478 center_of_mass: Vector::from(props.local_com).f32(),
479 }
480 }
481}
482
483#[cfg(feature = "3d")]
484impl ComputeMassProperties for Collider {
485 fn mass(&self, density: f32) -> f32 {
486 let props = self.shape_scaled().mass_properties(density as Scalar);
487 props.mass() as f32
488 }
489
490 fn unit_principal_angular_inertia(&self) -> Vec3 {
491 self.principal_angular_inertia(1.0)
492 }
493
494 fn principal_angular_inertia(&self, mass: f32) -> Vec3 {
495 let props = self.shape_scaled().mass_properties(mass as Scalar);
496 Vector::from(props.principal_inertia()).f32()
497 }
498
499 fn local_inertial_frame(&self) -> Quat {
500 let props = self.shape_scaled().mass_properties(1.0);
501 Quaternion::from(props.principal_inertia_local_frame).f32()
502 }
503
504 fn center_of_mass(&self) -> Vec3 {
505 let props = self.shape_scaled().mass_properties(1.0);
506 Vector::from(props.local_com).f32()
507 }
508
509 fn mass_properties(&self, density: f32) -> MassProperties {
510 let props = self.shape_scaled().mass_properties(density as Scalar);
511
512 MassProperties {
513 mass: props.mass() as f32,
514 #[cfg(feature = "2d")]
515 angular_inertia: props.principal_inertia() as f32,
516 #[cfg(feature = "3d")]
517 principal_angular_inertia: Vector::from(props.principal_inertia()).f32(),
518 #[cfg(feature = "3d")]
519 local_inertial_frame: Quaternion::from(props.principal_inertia_local_frame).f32(),
520 center_of_mass: Vector::from(props.local_com).f32(),
521 }
522 }
523}
524
525impl ScalableCollider for Collider {
526 fn scale(&self) -> Vector {
527 self.scale()
528 }
529
530 fn set_scale(&mut self, scale: Vector, detail: u32) {
531 self.set_scale(scale, detail)
532 }
533}
534
535impl Collider {
536 pub fn shape(&self) -> &SharedShape {
538 &self.shape
539 }
540
541 pub fn shape_mut(&mut self) -> &mut SharedShape {
543 &mut self.shape
544 }
545
546 pub fn shape_scaled(&self) -> &SharedShape {
548 &self.scaled_shape
549 }
550
551 pub fn set_shape(&mut self, shape: SharedShape) {
553 self.shape = shape;
554
555 if let Ok(scaled) = scale_shape(&self.shape, self.scale, 10) {
557 self.scaled_shape = scaled;
558 } else {
559 log::error!("Failed to create convex hull for scaled collider.");
560 }
561 }
562
563 pub fn scale(&self) -> Vector {
565 self.scale
566 }
567
568 pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
576 if scale == self.scale {
577 return;
578 }
579
580 if scale == Vector::ONE {
581 self.scaled_shape = self.shape.clone();
583 self.scale = Vector::ONE;
584 return;
585 }
586
587 if let Ok(scaled) = scale_shape(&self.shape, scale, num_subdivisions) {
588 self.scaled_shape = scaled;
589 self.scale = scale;
590 } else {
591 log::error!("Failed to create convex hull for scaled collider.");
592 }
593 }
594
595 pub fn project_point(
601 &self,
602 translation: impl Into<Position>,
603 rotation: impl Into<Rotation>,
604 point: Vector,
605 solid: bool,
606 ) -> (Vector, bool) {
607 let projection = self.shape_scaled().project_point(
608 &make_isometry(translation, rotation),
609 &point.into(),
610 solid,
611 );
612 (projection.point.into(), projection.is_inside)
613 }
614
615 pub fn distance_to_point(
621 &self,
622 translation: impl Into<Position>,
623 rotation: impl Into<Rotation>,
624 point: Vector,
625 solid: bool,
626 ) -> Scalar {
627 self.shape_scaled().distance_to_point(
628 &make_isometry(translation, rotation),
629 &point.into(),
630 solid,
631 )
632 }
633
634 pub fn contains_point(
636 &self,
637 translation: impl Into<Position>,
638 rotation: impl Into<Rotation>,
639 point: Vector,
640 ) -> bool {
641 self.shape_scaled()
642 .contains_point(&make_isometry(translation, rotation), &point.into())
643 }
644
645 pub fn cast_ray(
658 &self,
659 translation: impl Into<Position>,
660 rotation: impl Into<Rotation>,
661 ray_origin: Vector,
662 ray_direction: Vector,
663 max_distance: Scalar,
664 solid: bool,
665 ) -> Option<(Scalar, Vector)> {
666 let hit = self.shape_scaled().cast_ray_and_get_normal(
667 &make_isometry(translation, rotation),
668 &parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
669 max_distance,
670 solid,
671 );
672 hit.map(|hit| (hit.time_of_impact, hit.normal.into()))
673 }
674
675 pub fn intersects_ray(
683 &self,
684 translation: impl Into<Position>,
685 rotation: impl Into<Rotation>,
686 ray_origin: Vector,
687 ray_direction: Vector,
688 max_distance: Scalar,
689 ) -> bool {
690 self.shape_scaled().intersects_ray(
691 &make_isometry(translation, rotation),
692 &parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
693 max_distance,
694 )
695 }
696
697 pub fn compound(
705 shapes: Vec<(
706 impl Into<Position>,
707 impl Into<Rotation>,
708 impl Into<Collider>,
709 )>,
710 ) -> Self {
711 let shapes = shapes
712 .into_iter()
713 .map(|(p, r, c)| {
714 (
715 make_isometry(*p.into(), r.into()),
716 c.into().shape_scaled().clone(),
717 )
718 })
719 .collect::<Vec<_>>();
720 SharedShape::compound(shapes).into()
721 }
722
723 #[cfg(feature = "2d")]
725 pub fn circle(radius: Scalar) -> Self {
726 SharedShape::ball(radius).into()
727 }
728
729 #[cfg(feature = "3d")]
731 pub fn sphere(radius: Scalar) -> Self {
732 SharedShape::ball(radius).into()
733 }
734
735 #[cfg(feature = "2d")]
737 pub fn ellipse(half_width: Scalar, half_height: Scalar) -> Self {
738 SharedShape::new(EllipseColliderShape(Ellipse::new(
739 half_width as f32,
740 half_height as f32,
741 )))
742 .into()
743 }
744
745 #[cfg(feature = "2d")]
747 pub fn rectangle(x_length: Scalar, y_length: Scalar) -> Self {
748 SharedShape::cuboid(x_length * 0.5, y_length * 0.5).into()
749 }
750
751 #[cfg(feature = "3d")]
753 pub fn cuboid(x_length: Scalar, y_length: Scalar, z_length: Scalar) -> Self {
754 SharedShape::cuboid(x_length * 0.5, y_length * 0.5, z_length * 0.5).into()
755 }
756
757 #[cfg(feature = "2d")]
759 pub fn round_rectangle(x_length: Scalar, y_length: Scalar, border_radius: Scalar) -> Self {
760 SharedShape::round_cuboid(x_length * 0.5, y_length * 0.5, border_radius).into()
761 }
762
763 #[cfg(feature = "3d")]
765 pub fn round_cuboid(
766 x_length: Scalar,
767 y_length: Scalar,
768 z_length: Scalar,
769 border_radius: Scalar,
770 ) -> Self {
771 SharedShape::round_cuboid(
772 x_length * 0.5,
773 y_length * 0.5,
774 z_length * 0.5,
775 border_radius,
776 )
777 .into()
778 }
779
780 #[cfg(feature = "3d")]
783 pub fn cylinder(radius: Scalar, height: Scalar) -> Self {
784 SharedShape::cylinder(height * 0.5, radius).into()
785 }
786
787 #[cfg(feature = "3d")]
790 pub fn cone(radius: Scalar, height: Scalar) -> Self {
791 SharedShape::cone(height * 0.5, radius).into()
792 }
793
794 pub fn capsule(radius: Scalar, length: Scalar) -> Self {
797 SharedShape::capsule(
798 (Vector::Y * length * 0.5).into(),
799 (Vector::NEG_Y * length * 0.5).into(),
800 radius,
801 )
802 .into()
803 }
804
805 pub fn capsule_endpoints(radius: Scalar, a: Vector, b: Vector) -> Self {
807 SharedShape::capsule(a.into(), b.into(), radius).into()
808 }
809
810 pub fn half_space(outward_normal: Vector) -> Self {
813 SharedShape::halfspace(nalgebra::Unit::new_normalize(outward_normal.into())).into()
814 }
815
816 pub fn segment(a: Vector, b: Vector) -> Self {
818 SharedShape::segment(a.into(), b.into()).into()
819 }
820
821 #[cfg(feature = "2d")]
829 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
830 let mut triangle = parry::shape::Triangle::new(a.into(), b.into(), c.into());
831
832 if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
834 triangle.reverse();
835 }
836
837 SharedShape::new(triangle).into()
838 }
839
840 #[cfg(feature = "2d")]
847 pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
848 SharedShape::triangle(a.into(), b.into(), c.into()).into()
849 }
850
851 #[cfg(feature = "3d")]
853 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
854 SharedShape::triangle(a.into(), b.into(), c.into()).into()
855 }
856
857 #[cfg(feature = "2d")]
859 pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
860 RegularPolygon::new(circumradius, sides).collider()
861 }
862
863 pub fn polyline(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
865 let vertices = vertices.into_iter().map(|v| v.into()).collect();
866 SharedShape::polyline(vertices, indices).into()
867 }
868
869 pub fn trimesh(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
882 Self::try_trimesh(vertices, indices)
883 .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
884 }
885
886 pub fn try_trimesh(
899 vertices: Vec<Vector>,
900 indices: Vec<[u32; 3]>,
901 ) -> Result<Self, TrimeshBuilderError> {
902 let vertices = vertices.into_iter().map(|v| v.into()).collect();
903 SharedShape::trimesh(vertices, indices).map(|trimesh| trimesh.into())
904 }
905
906 pub fn trimesh_with_config(
920 vertices: Vec<Vector>,
921 indices: Vec<[u32; 3]>,
922 flags: TrimeshFlags,
923 ) -> Self {
924 Self::try_trimesh_with_config(vertices, indices, flags)
925 .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
926 }
927
928 pub fn try_trimesh_with_config(
942 vertices: Vec<Vector>,
943 indices: Vec<[u32; 3]>,
944 flags: TrimeshFlags,
945 ) -> Result<Self, TrimeshBuilderError> {
946 let vertices = vertices.into_iter().map(|v| v.into()).collect();
947 SharedShape::trimesh_with_flags(vertices, indices, flags.into())
948 .map(|trimesh| trimesh.into())
949 }
950
951 #[cfg(feature = "2d")]
954 pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 2]>) -> Self {
955 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
956 SharedShape::convex_decomposition(&vertices, &indices).into()
957 }
958
959 #[cfg(feature = "3d")]
962 pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
963 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
964 SharedShape::convex_decomposition(&vertices, &indices).into()
965 }
966
967 #[cfg(feature = "2d")]
971 pub fn convex_decomposition_with_config(
972 vertices: Vec<Vector>,
973 indices: Vec<[u32; 2]>,
974 params: &VhacdParameters,
975 ) -> Self {
976 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
977 SharedShape::convex_decomposition_with_params(&vertices, &indices, ¶ms.clone().into())
978 .into()
979 }
980
981 #[cfg(feature = "3d")]
985 pub fn convex_decomposition_with_config(
986 vertices: Vec<Vector>,
987 indices: Vec<[u32; 3]>,
988 params: VhacdParameters,
989 ) -> Self {
990 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
991 SharedShape::convex_decomposition_with_params(&vertices, &indices, ¶ms.clone().into())
992 .into()
993 }
994
995 #[cfg(feature = "2d")]
998 pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
999 let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
1000 SharedShape::convex_hull(&points).map(Into::into)
1001 }
1002
1003 #[cfg(feature = "3d")]
1006 pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
1007 let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
1008 SharedShape::convex_hull(&points).map(Into::into)
1009 }
1010
1011 #[cfg(feature = "2d")]
1015 pub fn convex_polyline(points: Vec<Vector>) -> Option<Self> {
1016 let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
1017 SharedShape::convex_polyline(points).map(Into::into)
1018 }
1019
1020 pub fn voxels(voxel_size: Vector, grid_coordinates: &[IVector]) -> Self {
1024 let shape = Voxels::new(
1025 voxel_size.into(),
1026 &Self::ivec_array_from_point_int_array(grid_coordinates),
1027 );
1028 SharedShape::new(shape).into()
1029 }
1030
1031 pub fn voxels_from_points(voxel_size: Vector, points: &[Vector]) -> Self {
1035 SharedShape::voxels_from_points(
1036 voxel_size.into(),
1037 &Self::vec_array_from_point_float_array(points),
1038 )
1039 .into()
1040 }
1041
1042 #[cfg(feature = "2d")]
1044 pub fn voxelized_polyline(
1045 vertices: &[Vector],
1046 indices: &[[u32; 2]],
1047 voxel_size: Scalar,
1048 fill_mode: FillMode,
1049 ) -> Self {
1050 let vertices = Self::vec_array_from_point_float_array(vertices);
1051 SharedShape::voxelized_mesh(&vertices, indices, voxel_size, fill_mode.into()).into()
1052 }
1053
1054 #[cfg(feature = "3d")]
1056 pub fn voxelized_trimesh(
1057 vertices: &[Vector],
1058 indices: &[[u32; 3]],
1059 voxel_size: Scalar,
1060 fill_mode: FillMode,
1061 ) -> Self {
1062 let vertices = Self::vec_array_from_point_float_array(vertices);
1063 SharedShape::voxelized_mesh(&vertices, indices, voxel_size, fill_mode.into()).into()
1064 }
1065
1066 #[cfg(feature = "collider-from-mesh")]
1070 pub fn voxelized_trimesh_from_mesh(
1071 mesh: &Mesh,
1072 voxel_size: Scalar,
1073 fill_mode: FillMode,
1074 ) -> Option<Self> {
1075 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1076 SharedShape::voxelized_mesh(&vertices, &indices, voxel_size, fill_mode.into()).into()
1077 })
1078 }
1079
1080 #[cfg_attr(
1081 feature = "2d",
1082 doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1083 )]
1084 #[cfg_attr(
1085 feature = "3d",
1086 doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1087 )]
1088 pub fn voxelized_convex_decomposition(
1089 vertices: &[Vector],
1090 indices: &[[u32; DIM]],
1091 ) -> Vec<Self> {
1092 Self::voxelized_convex_decomposition_with_config(
1093 vertices,
1094 indices,
1095 &VhacdParameters::default(),
1096 )
1097 }
1098
1099 #[cfg_attr(
1100 feature = "2d",
1101 doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1102 )]
1103 #[cfg_attr(
1104 feature = "3d",
1105 doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1106 )]
1107 pub fn voxelized_convex_decomposition_with_config(
1108 vertices: &[Vector],
1109 indices: &[[u32; DIM]],
1110 parameters: &VhacdParameters,
1111 ) -> Vec<Self> {
1112 SharedShape::voxelized_convex_decomposition_with_params(
1113 &Self::vec_array_from_point_float_array(vertices),
1114 indices,
1115 ¶meters.clone().into(),
1116 )
1117 .into_iter()
1118 .map(|c| c.into())
1119 .collect()
1120 }
1121
1122 fn ivec_array_from_point_int_array(points: &[IVector]) -> Vec<Point<i32>> {
1123 points
1124 .iter()
1125 .map(|p| {
1126 #[cfg(feature = "2d")]
1127 return Point::new(p.x, p.y);
1128 #[cfg(feature = "3d")]
1129 return Point::new(p.x, p.y, p.z);
1130 })
1131 .collect::<Vec<_>>()
1132 }
1133
1134 fn vec_array_from_point_float_array(points: &[Vector]) -> Vec<Point<Scalar>> {
1135 points
1136 .iter()
1137 .map(|p| {
1138 #[cfg(feature = "2d")]
1139 return Point::new(p.x, p.y);
1140 #[cfg(feature = "3d")]
1141 return Point::new(p.x, p.y, p.z);
1142 })
1143 .collect::<Vec<_>>()
1144 }
1145
1146 #[cfg(feature = "2d")]
1153 pub fn heightfield(heights: Vec<Scalar>, scale: Vector) -> Self {
1154 SharedShape::heightfield(heights.into(), scale.into()).into()
1155 }
1156
1157 #[cfg(feature = "3d")]
1167 pub fn heightfield(heights: Vec<Vec<Scalar>>, scale: Vector) -> Self {
1168 let row_count = heights.len();
1169 let column_count = heights[0].len();
1170 let data: Vec<Scalar> = heights.into_iter().flatten().collect();
1171
1172 assert_eq!(
1173 data.len(),
1174 row_count * column_count,
1175 "Each row in `heights` must have the same amount of points"
1176 );
1177
1178 let heights = nalgebra::DMatrix::from_vec(row_count, column_count, data);
1179 SharedShape::heightfield(heights, scale.into()).into()
1180 }
1181
1182 #[cfg(feature = "collider-from-mesh")]
1205 pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
1206 extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1207 SharedShape::trimesh_with_flags(
1208 vertices,
1209 indices,
1210 TrimeshFlags::MERGE_DUPLICATE_VERTICES.into(),
1211 )
1212 .map(|trimesh| trimesh.into())
1213 .ok()
1214 })
1215 }
1216
1217 #[cfg(feature = "collider-from-mesh")]
1241 pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TrimeshFlags) -> Option<Self> {
1242 extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1243 SharedShape::trimesh_with_flags(vertices, indices, flags.into())
1244 .map(|trimesh| trimesh.into())
1245 .ok()
1246 })
1247 }
1248
1249 #[cfg(feature = "collider-from-mesh")]
1266 pub fn convex_hull_from_mesh(mesh: &Mesh) -> Option<Self> {
1267 extract_mesh_vertices_indices(mesh)
1268 .and_then(|(vertices, _)| SharedShape::convex_hull(&vertices).map(|shape| shape.into()))
1269 }
1270
1271 #[cfg(feature = "collider-from-mesh")]
1288 pub fn convex_decomposition_from_mesh(mesh: &Mesh) -> Option<Self> {
1289 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1290 SharedShape::convex_decomposition(&vertices, &indices).into()
1291 })
1292 }
1293
1294 #[cfg(feature = "collider-from-mesh")]
1316 pub fn convex_decomposition_from_mesh_with_config(
1317 mesh: &Mesh,
1318 parameters: &VhacdParameters,
1319 ) -> Option<Self> {
1320 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1321 SharedShape::convex_decomposition_with_params(
1322 &vertices,
1323 &indices,
1324 ¶meters.clone().into(),
1325 )
1326 .into()
1327 })
1328 }
1329
1330 #[cfg_attr(
1335 feature = "collider-from-mesh",
1336 doc = "Returns `None` in the following cases:
1337- The given [`ColliderConstructor`] requires a mesh, but none was provided.
1338- Creating the collider from the given [`ColliderConstructor`] failed."
1339 )]
1340 #[cfg_attr(
1341 not(feature = "collider-from-mesh"),
1342 doc = "Returns `None` if creating the collider from the given [`ColliderConstructor`] failed."
1343 )]
1344 pub fn try_from_constructor(
1345 collider_constructor: ColliderConstructor,
1346 #[cfg(feature = "collider-from-mesh")] mesh: Option<&Mesh>,
1347 ) -> Option<Self> {
1348 match collider_constructor {
1349 #[cfg(feature = "2d")]
1350 ColliderConstructor::Circle { radius } => Some(Self::circle(radius)),
1351 #[cfg(feature = "3d")]
1352 ColliderConstructor::Sphere { radius } => Some(Self::sphere(radius)),
1353 #[cfg(feature = "2d")]
1354 ColliderConstructor::Ellipse {
1355 half_width,
1356 half_height,
1357 } => Some(Self::ellipse(half_width, half_height)),
1358 #[cfg(feature = "2d")]
1359 ColliderConstructor::Rectangle { x_length, y_length } => {
1360 Some(Self::rectangle(x_length, y_length))
1361 }
1362 #[cfg(feature = "3d")]
1363 ColliderConstructor::Cuboid {
1364 x_length,
1365 y_length,
1366 z_length,
1367 } => Some(Self::cuboid(x_length, y_length, z_length)),
1368 #[cfg(feature = "2d")]
1369 ColliderConstructor::RoundRectangle {
1370 x_length,
1371 y_length,
1372 border_radius,
1373 } => Some(Self::round_rectangle(x_length, y_length, border_radius)),
1374 #[cfg(feature = "3d")]
1375 ColliderConstructor::RoundCuboid {
1376 x_length,
1377 y_length,
1378 z_length,
1379 border_radius,
1380 } => Some(Self::round_cuboid(
1381 x_length,
1382 y_length,
1383 z_length,
1384 border_radius,
1385 )),
1386 #[cfg(feature = "3d")]
1387 ColliderConstructor::Cylinder { radius, height } => {
1388 Some(Self::cylinder(radius, height))
1389 }
1390 #[cfg(feature = "3d")]
1391 ColliderConstructor::Cone { radius, height } => Some(Self::cone(radius, height)),
1392 ColliderConstructor::Capsule { radius, height } => Some(Self::capsule(radius, height)),
1393 ColliderConstructor::CapsuleEndpoints { radius, a, b } => {
1394 Some(Self::capsule_endpoints(radius, a, b))
1395 }
1396 ColliderConstructor::HalfSpace { outward_normal } => {
1397 Some(Self::half_space(outward_normal))
1398 }
1399 ColliderConstructor::Segment { a, b } => Some(Self::segment(a, b)),
1400 ColliderConstructor::Triangle { a, b, c } => Some(Self::triangle(a, b, c)),
1401 #[cfg(feature = "2d")]
1402 ColliderConstructor::RegularPolygon {
1403 circumradius,
1404 sides,
1405 } => Some(Self::regular_polygon(circumradius, sides)),
1406 ColliderConstructor::Polyline { vertices, indices } => {
1407 Some(Self::polyline(vertices, indices))
1408 }
1409 ColliderConstructor::Trimesh { vertices, indices } => {
1410 Some(Self::trimesh(vertices, indices))
1411 }
1412 ColliderConstructor::TrimeshWithConfig {
1413 vertices,
1414 indices,
1415 flags,
1416 } => Some(Self::trimesh_with_config(vertices, indices, flags)),
1417 #[cfg(feature = "2d")]
1418 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1419 Some(Self::convex_decomposition(vertices, indices))
1420 }
1421 #[cfg(feature = "3d")]
1422 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1423 Some(Self::convex_decomposition(vertices, indices))
1424 }
1425 #[cfg(feature = "2d")]
1426 ColliderConstructor::ConvexDecompositionWithConfig {
1427 vertices,
1428 indices,
1429 params,
1430 } => Some(Self::convex_decomposition_with_config(
1431 vertices, indices, ¶ms,
1432 )),
1433 #[cfg(feature = "3d")]
1434 ColliderConstructor::ConvexDecompositionWithConfig {
1435 vertices,
1436 indices,
1437 params,
1438 } => Some(Self::convex_decomposition_with_config(
1439 vertices, indices, params,
1440 )),
1441 #[cfg(feature = "2d")]
1442 ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
1443 #[cfg(feature = "3d")]
1444 ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
1445 #[cfg(feature = "2d")]
1446 ColliderConstructor::ConvexPolyline { points } => Self::convex_polyline(points),
1447 ColliderConstructor::Voxels {
1448 voxel_size,
1449 grid_coordinates,
1450 } => Some(Self::voxels(voxel_size, &grid_coordinates)),
1451 #[cfg(feature = "2d")]
1452 ColliderConstructor::VoxelizedPolyline {
1453 vertices,
1454 indices,
1455 voxel_size,
1456 fill_mode,
1457 } => Some(Self::voxelized_polyline(
1458 &vertices, &indices, voxel_size, fill_mode,
1459 )),
1460 #[cfg(feature = "3d")]
1461 ColliderConstructor::VoxelizedTrimesh {
1462 vertices,
1463 indices,
1464 voxel_size,
1465 fill_mode,
1466 } => Some(Self::voxelized_trimesh(
1467 &vertices, &indices, voxel_size, fill_mode,
1468 )),
1469 #[cfg(feature = "2d")]
1470 ColliderConstructor::Heightfield { heights, scale } => {
1471 Some(Self::heightfield(heights, scale))
1472 }
1473 #[cfg(feature = "3d")]
1474 ColliderConstructor::Heightfield { heights, scale } => {
1475 Some(Self::heightfield(heights, scale))
1476 }
1477 #[cfg(feature = "collider-from-mesh")]
1478 ColliderConstructor::TrimeshFromMesh => Self::trimesh_from_mesh(mesh?),
1479 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1480 ColliderConstructor::TrimeshFromMeshWithConfig(flags) => {
1481 Self::trimesh_from_mesh_with_config(mesh?, flags)
1482 }
1483 #[cfg(feature = "collider-from-mesh")]
1484 ColliderConstructor::ConvexDecompositionFromMesh => {
1485 Self::convex_decomposition_from_mesh(mesh?)
1486 }
1487 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1488 ColliderConstructor::ConvexDecompositionFromMeshWithConfig(params) => {
1489 Self::convex_decomposition_from_mesh_with_config(mesh?, ¶ms)
1490 }
1491 #[cfg(feature = "collider-from-mesh")]
1492 ColliderConstructor::ConvexHullFromMesh => Self::convex_hull_from_mesh(mesh?),
1493 #[cfg(feature = "collider-from-mesh")]
1494 ColliderConstructor::VoxelizedTrimeshFromMesh {
1495 voxel_size,
1496 fill_mode,
1497 } => Self::voxelized_trimesh_from_mesh(mesh?, voxel_size, fill_mode),
1498 ColliderConstructor::Compound(compound_constructors) => {
1499 let shapes: Vec<_> =
1500 ColliderConstructor::flatten_compound_constructors(compound_constructors)
1501 .into_iter()
1502 .filter_map(|(position, rotation, collider_constructor)| {
1503 Self::try_from_constructor(
1504 collider_constructor,
1505 #[cfg(feature = "collider-from-mesh")]
1506 mesh,
1507 )
1508 .map(|collider| (position, rotation, collider))
1509 })
1510 .collect();
1511
1512 (!shapes.is_empty()).then(|| Self::compound(shapes))
1513 }
1514 }
1515 }
1516}
1517
1518#[cfg(feature = "collider-from-mesh")]
1519type VerticesIndices = (Vec<nalgebra::Point3<Scalar>>, Vec<[u32; 3]>);
1520
1521#[cfg(feature = "collider-from-mesh")]
1522fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<VerticesIndices> {
1523 let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
1524 let indices = mesh.indices()?;
1525
1526 let vtx: Vec<_> = match vertices {
1527 VertexAttributeValues::Float32(vtx) => Some(
1528 vtx.chunks(3)
1529 .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
1530 .collect(),
1531 ),
1532 VertexAttributeValues::Float32x3(vtx) => Some(
1533 vtx.iter()
1534 .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
1535 .collect(),
1536 ),
1537 _ => None,
1538 }?;
1539
1540 let idx = match indices {
1541 Indices::U16(idx) => idx
1542 .chunks_exact(3)
1543 .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
1544 .collect(),
1545 Indices::U32(idx) => idx.chunks_exact(3).map(|i| [i[0], i[1], i[2]]).collect(),
1546 };
1547
1548 Some((vtx, idx))
1549}
1550
1551fn scale_shape(
1552 shape: &SharedShape,
1553 scale: Vector,
1554 num_subdivisions: u32,
1555) -> Result<SharedShape, UnsupportedShape> {
1556 let scale = scale.abs();
1557 match shape.as_typed_shape() {
1558 TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(&scale.abs().into()))),
1559 TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
1560 border_radius: s.border_radius,
1561 inner_shape: s.inner_shape.scaled(&scale.abs().into()),
1562 })),
1563 TypedShape::Capsule(c) => match c.scaled(&scale.abs().into(), num_subdivisions) {
1564 None => {
1565 log::error!("Failed to apply scale {} to Capsule shape.", scale);
1566 Ok(SharedShape::ball(0.0))
1567 }
1568 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1569 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1570 },
1571 TypedShape::Ball(b) => {
1572 #[cfg(feature = "2d")]
1573 {
1574 if scale.x == scale.y {
1575 Ok(SharedShape::ball(b.radius * scale.x.abs()))
1576 } else {
1577 Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1579 half_size: Vec2::splat(b.radius as f32) * scale.f32().abs(),
1580 })))
1581 }
1582 }
1583 #[cfg(feature = "3d")]
1584 match b.scaled(&scale.abs().into(), num_subdivisions) {
1585 None => {
1586 log::error!("Failed to apply scale {} to Ball shape.", scale);
1587 Ok(SharedShape::ball(0.0))
1588 }
1589 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1590 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1591 }
1592 }
1593 TypedShape::Segment(s) => Ok(SharedShape::new(s.scaled(&scale.into()))),
1594 TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(&scale.into()))),
1595 TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
1596 border_radius: t.border_radius,
1597 inner_shape: t.inner_shape.scaled(&scale.into()),
1598 })),
1599 TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(&scale.into()))),
1600 TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(&scale.into()))),
1601 TypedShape::HalfSpace(h) => match h.scaled(&scale.into()) {
1602 None => {
1603 log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
1604 Ok(SharedShape::ball(0.0))
1605 }
1606 Some(scaled) => Ok(SharedShape::new(scaled)),
1607 },
1608 TypedShape::Voxels(v) => Ok(SharedShape::new(v.clone().scaled(&scale.into()))),
1609 TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(&scale.into()))),
1610 #[cfg(feature = "2d")]
1611 TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(&scale.into()) {
1612 None => {
1613 log::error!("Failed to apply scale {} to ConvexPolygon shape.", scale);
1614 Ok(SharedShape::ball(0.0))
1615 }
1616 Some(scaled) => Ok(SharedShape::new(scaled)),
1617 },
1618 #[cfg(feature = "2d")]
1619 TypedShape::RoundConvexPolygon(cp) => match cp.inner_shape.clone().scaled(&scale.into()) {
1620 None => {
1621 log::error!(
1622 "Failed to apply scale {} to RoundConvexPolygon shape.",
1623 scale
1624 );
1625 Ok(SharedShape::ball(0.0))
1626 }
1627 Some(scaled) => Ok(SharedShape::new(RoundShape {
1628 border_radius: cp.border_radius,
1629 inner_shape: scaled,
1630 })),
1631 },
1632 #[cfg(feature = "3d")]
1633 TypedShape::ConvexPolyhedron(cp) => match cp.clone().scaled(&scale.into()) {
1634 None => {
1635 log::error!("Failed to apply scale {} to ConvexPolyhedron shape.", scale);
1636 Ok(SharedShape::ball(0.0))
1637 }
1638 Some(scaled) => Ok(SharedShape::new(scaled)),
1639 },
1640 #[cfg(feature = "3d")]
1641 TypedShape::RoundConvexPolyhedron(cp) => {
1642 match cp.clone().inner_shape.scaled(&scale.into()) {
1643 None => {
1644 log::error!(
1645 "Failed to apply scale {} to RoundConvexPolyhedron shape.",
1646 scale
1647 );
1648 Ok(SharedShape::ball(0.0))
1649 }
1650 Some(scaled) => Ok(SharedShape::new(RoundShape {
1651 border_radius: cp.border_radius,
1652 inner_shape: scaled,
1653 })),
1654 }
1655 }
1656 #[cfg(feature = "3d")]
1657 TypedShape::Cylinder(c) => match c.scaled(&scale.abs().into(), num_subdivisions) {
1658 None => {
1659 log::error!("Failed to apply scale {} to Cylinder shape.", scale);
1660 Ok(SharedShape::ball(0.0))
1661 }
1662 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1663 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1664 },
1665 #[cfg(feature = "3d")]
1666 TypedShape::RoundCylinder(c) => {
1667 match c.inner_shape.scaled(&scale.abs().into(), num_subdivisions) {
1668 None => {
1669 log::error!("Failed to apply scale {} to RoundCylinder shape.", scale);
1670 Ok(SharedShape::ball(0.0))
1671 }
1672 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1673 border_radius: c.border_radius,
1674 inner_shape: scaled,
1675 })),
1676 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1677 border_radius: c.border_radius,
1678 inner_shape: scaled,
1679 })),
1680 }
1681 }
1682 #[cfg(feature = "3d")]
1683 TypedShape::Cone(c) => match c.scaled(&scale.into(), num_subdivisions) {
1684 None => {
1685 log::error!("Failed to apply scale {} to Cone shape.", scale);
1686 Ok(SharedShape::ball(0.0))
1687 }
1688 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1689 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1690 },
1691 #[cfg(feature = "3d")]
1692 TypedShape::RoundCone(c) => match c.inner_shape.scaled(&scale.into(), num_subdivisions) {
1693 None => {
1694 log::error!("Failed to apply scale {} to RoundCone shape.", scale);
1695 Ok(SharedShape::ball(0.0))
1696 }
1697 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1698 border_radius: c.border_radius,
1699 inner_shape: scaled,
1700 })),
1701 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1702 border_radius: c.border_radius,
1703 inner_shape: scaled,
1704 })),
1705 },
1706 TypedShape::Compound(c) => {
1707 let mut scaled = Vec::with_capacity(c.shapes().len());
1708
1709 for (iso, shape) in c.shapes() {
1710 scaled.push((
1711 #[cfg(feature = "2d")]
1712 make_isometry(
1713 Vector::from(iso.translation) * scale,
1714 Rotation::radians(iso.rotation.angle()),
1715 ),
1716 #[cfg(feature = "3d")]
1717 make_isometry(
1718 Vector::from(iso.translation) * scale,
1719 Quaternion::from(iso.rotation),
1720 ),
1721 scale_shape(shape, scale, num_subdivisions)?,
1722 ));
1723 }
1724 Ok(SharedShape::compound(scaled))
1725 }
1726 TypedShape::Custom(_shape) => {
1727 #[cfg(feature = "2d")]
1728 {
1729 if let Some(ellipse) = _shape.as_shape::<EllipseColliderShape>() {
1730 return Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1731 half_size: ellipse.half_size * scale.f32().abs(),
1732 })));
1733 }
1734 if let Some(polygon) = _shape.as_shape::<RegularPolygonColliderShape>() {
1735 if scale.x == scale.y {
1736 return Ok(SharedShape::new(RegularPolygonColliderShape(
1737 RegularPolygon::new(
1738 polygon.circumradius() * scale.x.abs() as f32,
1739 polygon.sides,
1740 ),
1741 )));
1742 } else {
1743 let vertices = polygon
1744 .vertices(0.0)
1745 .into_iter()
1746 .map(|v| v.adjust_precision().into())
1747 .collect::<Vec<_>>();
1748
1749 return scale_shape(
1750 &SharedShape::convex_hull(&vertices).unwrap(),
1751 scale,
1752 num_subdivisions,
1753 );
1754 }
1755 }
1756 }
1757 Err(parry::query::Unsupported)
1758 }
1759 }
1760}
1761
1762#[cfg(all(test, feature = "3d"))]
1763mod tests {
1764 use super::*;
1765 use approx::assert_relative_eq;
1766
1767 #[test]
1768 fn test_flatten_compound_constructors() {
1769 let input = vec![
1770 (
1771 Position(Vector::new(10.0, 0.0, 0.0)),
1772 Rotation::default(),
1773 ColliderConstructor::Sphere { radius: 1.0 },
1774 ),
1775 (
1776 Position(Vector::new(5.0, 0.0, 0.0)),
1777 Rotation::from(Quaternion::from_rotation_z(PI / 2.0)),
1778 ColliderConstructor::Compound(vec![
1779 (
1780 Position(Vector::new(2.0, 0.0, 0.0)),
1781 Rotation::from(Quaternion::from_rotation_y(PI)),
1782 ColliderConstructor::Compound(vec![(
1783 Position(Vector::new(1.0, 0.0, 0.0)),
1784 Rotation::default(),
1785 ColliderConstructor::Sphere { radius: 0.5 },
1786 )]),
1787 ),
1788 (
1789 Position(Vector::new(0.0, 3.0, 0.0)),
1790 Rotation::default(),
1791 ColliderConstructor::Sphere { radius: 0.25 },
1792 ),
1793 ]),
1794 ),
1795 ];
1796
1797 let flattened = ColliderConstructor::flatten_compound_constructors(input);
1798 assert_eq!(flattened.len(), 3);
1799
1800 let unchanged_simple_sphere = &flattened[0];
1801 let flattened_grandchild = &flattened[1];
1802 let flattened_sibling = &flattened[2];
1803
1804 assert_eq!(
1806 unchanged_simple_sphere.0,
1807 Position(Vector::new(10.0, 0.0, 0.0))
1808 );
1809 assert_eq!(unchanged_simple_sphere.1, Rotation::default());
1810
1811 let expected_grandchild_world_pos = Vector::new(5.0, 1.0, 0.0);
1817 let actual_grandchild_world_pos = flattened_grandchild.0.0;
1818
1819 assert_relative_eq!(
1820 actual_grandchild_world_pos.x,
1821 expected_grandchild_world_pos.x,
1822 epsilon = 1e-6
1823 );
1824 assert_relative_eq!(
1825 actual_grandchild_world_pos.y,
1826 expected_grandchild_world_pos.y,
1827 epsilon = 1e-6
1828 );
1829 assert_relative_eq!(
1830 actual_grandchild_world_pos.z,
1831 expected_grandchild_world_pos.z,
1832 epsilon = 1e-6
1833 );
1834
1835 let expected_sibling_world_pos = Vector::new(2.0, 0.0, 0.0);
1839 let actual_sibling_world_pos = flattened_sibling.0.0;
1840
1841 assert_relative_eq!(
1842 actual_sibling_world_pos.x,
1843 expected_sibling_world_pos.x,
1844 epsilon = 1e-6
1845 );
1846 assert_relative_eq!(
1847 actual_sibling_world_pos.y,
1848 expected_sibling_world_pos.y,
1849 epsilon = 1e-6
1850 );
1851 assert_relative_eq!(
1852 actual_sibling_world_pos.z,
1853 expected_sibling_world_pos.z,
1854 epsilon = 1e-6
1855 );
1856 }
1857}