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 super::EnlargedAabb;
14use crate::{make_pose, math::Real, prelude::*};
15#[cfg(feature = "collider-from-mesh")]
16use bevy::mesh::{Indices, VertexAttributeValues};
17use bevy::{log, prelude::*};
18use contact_query::UnsupportedShape;
19use itertools::Either;
20use parry::{
21 bounding_volume::BoundingVolume,
22 shape::{RoundShape, SharedShape, TypedShape, Voxels},
23};
24
25impl<T: IntoCollider<Collider>> From<T> for Collider {
26 fn from(value: T) -> Self {
27 value.collider()
28 }
29}
30
31pub(crate) fn shape_supports_sweeps(shape: &dyn parry::shape::Shape) -> bool {
33 !matches!(
34 shape.as_typed_shape(),
35 TypedShape::TriMesh(_)
36 | TypedShape::Polyline(_)
37 | TypedShape::HeightField(_)
38 | TypedShape::Voxels(_)
39 | TypedShape::Segment(_)
40 | TypedShape::Triangle(_)
41 )
42}
43
44#[derive(Clone, PartialEq, Debug, Reflect)]
48#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
49#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
50#[reflect(PartialEq, Debug)]
51pub struct VhacdParameters {
52 pub concavity: Real,
57 pub alpha: Real,
62 pub beta: Real,
67 pub resolution: u32,
71 pub plane_downsampling: u32,
76 pub convex_hull_downsampling: u32,
81 pub fill_mode: FillMode,
86 pub convex_hull_approximation: bool,
92 pub max_convex_hulls: u32,
96}
97
98impl Default for VhacdParameters {
99 fn default() -> Self {
100 Self {
101 #[cfg(feature = "3d")]
102 resolution: 64,
103 #[cfg(feature = "3d")]
104 concavity: 0.01,
105 #[cfg(feature = "2d")]
106 resolution: 256,
107 #[cfg(feature = "2d")]
108 concavity: 0.1,
109 plane_downsampling: 4,
110 convex_hull_downsampling: 4,
111 alpha: 0.05,
112 beta: 0.05,
113 convex_hull_approximation: true,
114 max_convex_hulls: 1024,
115 fill_mode: FillMode::FloodFill {
116 detect_cavities: false,
117 #[cfg(feature = "2d")]
118 detect_self_intersections: false,
119 },
120 }
121 }
122}
123
124impl From<VhacdParameters> for parry::transformation::vhacd::VHACDParameters {
125 fn from(value: VhacdParameters) -> Self {
126 Self {
127 concavity: value.concavity,
128 alpha: value.alpha,
129 beta: value.beta,
130 resolution: value.resolution,
131 plane_downsampling: value.plane_downsampling,
132 convex_hull_downsampling: value.convex_hull_downsampling,
133 fill_mode: value.fill_mode.into(),
134 convex_hull_approximation: value.convex_hull_approximation,
135 max_convex_hulls: value.max_convex_hulls,
136 }
137 }
138}
139
140#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
143#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
144#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
145#[reflect(Hash, PartialEq, Debug)]
146pub enum FillMode {
147 SurfaceOnly,
150 FloodFill {
154 detect_cavities: bool,
156 #[cfg(feature = "2d")]
158 detect_self_intersections: bool,
159 },
160}
161
162impl From<FillMode> for parry::transformation::voxelization::FillMode {
163 fn from(value: FillMode) -> Self {
164 match value {
165 FillMode::SurfaceOnly => Self::SurfaceOnly,
166 FillMode::FloodFill {
167 detect_cavities,
168 #[cfg(feature = "2d")]
169 detect_self_intersections,
170 } => Self::FloodFill {
171 detect_cavities,
172 #[cfg(feature = "2d")]
173 detect_self_intersections,
174 },
175 }
176 }
177}
178
179#[repr(transparent)]
181#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
182#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
183#[reflect(opaque, Hash, PartialEq, Debug)]
184pub struct TrimeshFlags(u8);
185
186bitflags::bitflags! {
187 impl TrimeshFlags: u8 {
188 const HALF_EDGE_TOPOLOGY = 0b0000_0001;
190 const CONNECTED_COMPONENTS = 0b0000_0010;
196 const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
198 const ORIENTED = 0b0000_1000;
202 const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
207 const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
213 const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
219 const FIX_INTERNAL_EDGES = 0b1000_0000 | Self::ORIENTED.bits() | Self::MERGE_DUPLICATE_VERTICES.bits();
226 }
227}
228
229impl From<TrimeshFlags> for parry::shape::TriMeshFlags {
230 fn from(value: TrimeshFlags) -> Self {
231 Self::from_bits(value.bits().into()).unwrap()
232 }
233}
234
235pub type TrimeshBuilderError = parry::shape::TriMeshBuilderError;
237
238#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
246#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
247#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
252#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
253#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
265#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
266#[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")]
273#[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")]
274#[cfg_attr(
277 feature = "2d",
278 doc = " commands.spawn((RigidBody::Static, Collider::rectangle(5.0, 0.5)));"
279)]
280#[cfg_attr(
281 feature = "3d",
282 doc = " commands.spawn((RigidBody::Static, Collider::cuboid(5.0, 0.5, 5.0)));"
283)]
284#[cfg_attr(
295 feature = "3d",
296 doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
297)]
298#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
308#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
309#[cfg_attr(
315 feature = "2d",
316 doc = " .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
317)]
318#[cfg_attr(
319 feature = "3d",
320 doc = " .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
321)]
322#[cfg_attr(
325 feature = "2d",
326 doc = " children.spawn((Collider::circle(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
327 children.spawn((Collider::circle(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
328)]
329#[cfg_attr(
330 feature = "3d",
331 doc = " children.spawn((Collider::sphere(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
332 children.spawn((Collider::sphere(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
333)]
334#[cfg_attr(
354 feature = "3d",
355 doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
356)]
357#[derive(Clone, Component, Debug)]
373#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
374#[require(
375 ColliderMarker,
376 ColliderAabb,
377 CollisionLayers,
378 EnlargedAabb,
379 ColliderDensity,
380 ColliderMassProperties
381)]
382pub struct Collider {
383 shape: SharedShape,
385 scaled_shape: SharedShape,
390 scale: Vector,
392}
393
394impl From<SharedShape> for Collider {
395 fn from(value: SharedShape) -> Self {
396 Self {
397 shape: value.clone(),
398 scaled_shape: value,
399 scale: Vector::ONE,
400 }
401 }
402}
403
404impl Default for Collider {
405 fn default() -> Self {
406 #[cfg(feature = "2d")]
407 {
408 Self::rectangle(0.5, 0.5)
409 }
410 #[cfg(feature = "3d")]
411 {
412 Self::cuboid(0.5, 0.5, 0.5)
413 }
414 }
415}
416
417impl AnyCollider for Collider {
418 type Context = ();
419
420 fn aabb_with_context(
421 &self,
422 position: RVector,
423 rotation: impl Into<Rot>,
424 margin: f32,
425 _: ColliderContext<Self::Context>,
426 ) -> ColliderAabb {
427 let aabb = self
428 .shape_scaled()
429 .compute_aabb(&make_pose(position, rotation))
430 .loosened(margin.real());
431 ColliderAabb::from_min_max(aabb.mins, aabb.maxs)
432 }
433
434 fn contact_manifolds_with_context(
435 &self,
436 other: &Self,
437 position1: RVector,
438 rotation1: impl Into<Rot>,
439 position2: RVector,
440 rotation2: impl Into<Rot>,
441 prediction_distance: f32,
442 manifolds: &mut Vec<ContactManifold>,
443 _: ColliderPairContext<Self::Context>,
444 ) {
445 contact_query::contact_manifolds(
446 self,
447 position1,
448 rotation1,
449 other,
450 position2,
451 rotation2,
452 prediction_distance,
453 manifolds,
454 )
455 }
456
457 fn ccd_thickness_with_context(&self, _context: ColliderContext<Self::Context>) -> f32 {
458 if !shape_supports_sweeps(self.shape_scaled().as_ref()) {
459 return f32::INFINITY;
460 }
461
462 self.shape_scaled().ccd_thickness().f32()
463 }
464
465 fn max_distance_to_point_with_context(
466 &self,
467 point: RVector,
468 _context: ColliderContext<Self::Context>,
469 ) -> f32 {
470 let bounding_sphere = self.shape_scaled().compute_local_bounding_sphere();
471 point.distance(bounding_sphere.center).f32() + bounding_sphere.radius.f32()
472 }
473
474 fn bounding_radius_with_context(&self, _context: ColliderContext<Self::Context>) -> f32 {
475 let center_of_mass = self.center_of_mass();
476 self.max_distance_to_point(center_of_mass.real())
477 }
478}
479
480#[cfg(feature = "2d")]
483impl ComputeMassProperties for Collider {
484 fn mass(&self, density: f32) -> f32 {
485 let props = self.shape_scaled().mass_properties(density.real());
486 props.mass().f32()
487 }
488
489 fn unit_angular_inertia(&self) -> f32 {
490 self.angular_inertia(1.0)
491 }
492
493 fn angular_inertia(&self, mass: f32) -> f32 {
494 let props = self.shape_scaled().mass_properties(mass.real());
495 props.principal_inertia().f32()
496 }
497
498 fn center_of_mass(&self) -> Vec2 {
499 let props = self.shape_scaled().mass_properties(1.0);
500 props.local_com.f32()
501 }
502
503 fn mass_properties(&self, density: f32) -> MassProperties {
504 let props = self.shape_scaled().mass_properties(density.real());
505
506 MassProperties {
507 mass: props.mass().f32(),
508 #[cfg(feature = "2d")]
509 angular_inertia: props.principal_inertia().f32(),
510 #[cfg(feature = "3d")]
511 principal_angular_inertia: props.principal_inertia().f32(),
512 #[cfg(feature = "3d")]
513 local_inertial_frame: props.principal_inertia_local_frame.f32(),
514 center_of_mass: props.local_com.f32(),
515 }
516 }
517}
518
519#[cfg(feature = "3d")]
520impl ComputeMassProperties for Collider {
521 fn mass(&self, density: f32) -> f32 {
522 let props = self.shape_scaled().mass_properties(density.real());
523 props.mass().f32()
524 }
525
526 fn unit_principal_angular_inertia(&self) -> Vec3 {
527 self.principal_angular_inertia(1.0)
528 }
529
530 fn principal_angular_inertia(&self, mass: f32) -> Vec3 {
531 let props = self.shape_scaled().mass_properties(mass.real());
532 props.principal_inertia().f32()
533 }
534
535 fn local_inertial_frame(&self) -> Quat {
536 let props = self.shape_scaled().mass_properties(1.0);
537 props.principal_inertia_local_frame.f32()
538 }
539
540 fn center_of_mass(&self) -> Vec3 {
541 let props = self.shape_scaled().mass_properties(1.0);
542 props.local_com.f32()
543 }
544
545 fn mass_properties(&self, density: f32) -> MassProperties {
546 let props = self.shape_scaled().mass_properties(density.real());
547
548 MassProperties {
549 mass: props.mass().f32(),
550 #[cfg(feature = "2d")]
551 angular_inertia: props.principal_inertia().f32(),
552 #[cfg(feature = "3d")]
553 principal_angular_inertia: props.principal_inertia().f32(),
554 #[cfg(feature = "3d")]
555 local_inertial_frame: props.principal_inertia_local_frame.f32(),
556 center_of_mass: props.local_com.f32(),
557 }
558 }
559}
560
561impl ScalableCollider for Collider {
562 fn scale(&self) -> Vector {
563 self.scale()
564 }
565
566 fn set_scale(&mut self, scale: Vector, detail: u32) {
567 self.set_scale(scale, detail)
568 }
569}
570
571impl Collider {
572 pub fn shape(&self) -> &SharedShape {
574 &self.shape
575 }
576
577 pub fn shape_mut(&mut self) -> &mut SharedShape {
579 &mut self.shape
580 }
581
582 pub fn shape_scaled(&self) -> &SharedShape {
584 &self.scaled_shape
585 }
586
587 pub fn set_shape(&mut self, shape: SharedShape) {
589 self.shape = shape;
590
591 if let Ok(scaled) = scale_shape(&self.shape, self.scale, 10) {
593 self.scaled_shape = scaled;
594 } else {
595 log::error!("Failed to create convex hull for scaled collider.");
596 }
597 }
598
599 pub fn scale(&self) -> Vector {
601 self.scale
602 }
603
604 pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
612 if scale == self.scale {
613 return;
614 }
615
616 if scale == Vector::ONE {
617 self.scaled_shape = self.shape.clone();
619 self.scale = Vector::ONE;
620 return;
621 }
622
623 if let Ok(scaled) = scale_shape(&self.shape, scale, num_subdivisions) {
624 self.scaled_shape = scaled;
625 self.scale = scale;
626 } else {
627 log::error!("Failed to create convex hull for scaled collider.");
628 }
629 }
630
631 pub fn project_point(
637 &self,
638 translation: RVector,
639 rotation: impl Into<Rot>,
640 point: RVector,
641 solid: bool,
642 ) -> (RVector, bool) {
643 let projection =
644 self.shape_scaled()
645 .project_point(&make_pose(translation, rotation), point, solid);
646 (projection.point, projection.is_inside)
647 }
648
649 pub fn distance_to_point(
655 &self,
656 translation: RVector,
657 rotation: impl Into<Rot>,
658 point: RVector,
659 solid: bool,
660 ) -> f32 {
661 self.shape_scaled()
662 .distance_to_point(&make_pose(translation, rotation), point, solid)
663 .f32()
664 }
665
666 pub fn contains_point(
668 &self,
669 translation: RVector,
670 rotation: impl Into<Rot>,
671 point: RVector,
672 ) -> bool {
673 self.shape_scaled()
674 .contains_point(&make_pose(translation, rotation), point)
675 }
676
677 pub fn cast_ray(
690 &self,
691 translation: RVector,
692 rotation: impl Into<Rot>,
693 ray_origin: RVector,
694 ray_direction: Vector,
695 max_distance: f32,
696 solid: bool,
697 ) -> Option<(f32, Vector)> {
698 let hit = self.shape_scaled().cast_ray_and_get_normal(
699 &make_pose(translation, rotation),
700 &parry::query::Ray::new(ray_origin, ray_direction.real()),
701 max_distance.real(),
702 solid,
703 );
704 hit.map(|hit| (hit.time_of_impact.f32(), hit.normal.f32()))
705 }
706
707 pub fn intersects_ray(
715 &self,
716 translation: RVector,
717 rotation: impl Into<Rot>,
718 ray_origin: RVector,
719 ray_direction: Vector,
720 max_distance: f32,
721 ) -> bool {
722 self.shape_scaled().intersects_ray(
723 &make_pose(translation, rotation),
724 &parry::query::Ray::new(ray_origin, ray_direction.real()),
725 max_distance.real(),
726 )
727 }
728
729 pub fn compound(
737 shapes: Vec<(
738 impl Into<Position>,
739 impl Into<Rotation>,
740 impl Into<Collider>,
741 )>,
742 ) -> Self {
743 let shapes = shapes
744 .into_iter()
745 .map(|(p, r, c)| {
746 (
747 make_pose(p.into(), r.into()),
748 c.into().shape_scaled().clone(),
749 )
750 })
751 .collect::<Vec<_>>();
752 SharedShape::compound(shapes).into()
753 }
754
755 #[cfg(feature = "2d")]
757 pub fn circle(radius: f32) -> Self {
758 SharedShape::ball(radius.real()).into()
759 }
760
761 #[cfg(feature = "3d")]
763 pub fn sphere(radius: f32) -> Self {
764 SharedShape::ball(radius.real()).into()
765 }
766
767 #[cfg(feature = "2d")]
769 pub fn ellipse(half_width: f32, half_height: f32) -> Self {
770 SharedShape::new(EllipseColliderShape(Ellipse::new(
771 half_width as f32,
772 half_height as f32,
773 )))
774 .into()
775 }
776
777 #[cfg(feature = "2d")]
779 pub fn rectangle(x_length: f32, y_length: f32) -> Self {
780 SharedShape::cuboid(x_length.real() * 0.5, y_length.real() * 0.5).into()
781 }
782
783 #[cfg(feature = "3d")]
785 pub fn cuboid(x_length: f32, y_length: f32, z_length: f32) -> Self {
786 SharedShape::cuboid(
787 x_length.real() * 0.5,
788 y_length.real() * 0.5,
789 z_length.real() * 0.5,
790 )
791 .into()
792 }
793
794 #[cfg(feature = "2d")]
796 pub fn round_rectangle(x_length: f32, y_length: f32, border_radius: f32) -> Self {
797 SharedShape::round_cuboid(
798 x_length.real() * 0.5,
799 y_length.real() * 0.5,
800 border_radius.real(),
801 )
802 .into()
803 }
804
805 #[cfg(feature = "3d")]
807 pub fn round_cuboid(x_length: f32, y_length: f32, z_length: f32, border_radius: f32) -> Self {
808 SharedShape::round_cuboid(
809 x_length.real() * 0.5,
810 y_length.real() * 0.5,
811 z_length.real() * 0.5,
812 border_radius.real(),
813 )
814 .into()
815 }
816
817 #[cfg(feature = "3d")]
820 pub fn cylinder(radius: f32, height: f32) -> Self {
821 SharedShape::cylinder(height.real() * 0.5, radius.real()).into()
822 }
823
824 #[cfg(feature = "3d")]
827 pub fn cone(radius: f32, height: f32) -> Self {
828 SharedShape::cone(height.real() * 0.5, radius.real()).into()
829 }
830
831 pub fn capsule(radius: f32, length: f32) -> Self {
834 SharedShape::capsule(
835 RVector::Y * length.real() * 0.5,
836 RVector::NEG_Y * length.real() * 0.5,
837 radius.real(),
838 )
839 .into()
840 }
841
842 pub fn capsule_endpoints(radius: f32, a: Vector, b: Vector) -> Self {
844 SharedShape::capsule(a.real(), b.real(), radius.real()).into()
845 }
846
847 pub fn half_space(outward_normal: Vector) -> Self {
850 SharedShape::halfspace(outward_normal.normalize_or_zero().real()).into()
851 }
852
853 pub fn segment(a: Vector, b: Vector) -> Self {
855 SharedShape::segment(a.real(), b.real()).into()
856 }
857
858 #[cfg(feature = "2d")]
866 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
867 let mut triangle = parry::shape::Triangle::new(a.real(), b.real(), c.real());
868
869 if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
871 triangle.reverse();
872 }
873
874 SharedShape::new(triangle).into()
875 }
876
877 #[cfg(feature = "2d")]
884 pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
885 SharedShape::triangle(a.real(), b.real(), c.real()).into()
886 }
887
888 #[cfg(feature = "3d")]
890 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
891 SharedShape::triangle(a.real(), b.real(), c.real()).into()
892 }
893
894 #[cfg(feature = "2d")]
896 pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
897 RegularPolygon::new(circumradius, sides).collider()
898 }
899
900 pub fn polyline(vertices: Vec<RVector>, indices: Option<Vec<[u32; 2]>>) -> Self {
902 SharedShape::polyline(vertices, indices).into()
903 }
904
905 pub fn trimesh(vertices: Vec<RVector>, indices: Vec<[u32; 3]>) -> Self {
918 Self::try_trimesh(vertices, indices)
919 .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
920 }
921
922 pub fn try_trimesh(
935 vertices: Vec<RVector>,
936 indices: Vec<[u32; 3]>,
937 ) -> Result<Self, TrimeshBuilderError> {
938 SharedShape::trimesh(vertices, indices).map(|trimesh| trimesh.into())
939 }
940
941 pub fn trimesh_with_config(
955 vertices: Vec<RVector>,
956 indices: Vec<[u32; 3]>,
957 flags: TrimeshFlags,
958 ) -> Self {
959 Self::try_trimesh_with_config(vertices, indices, flags)
960 .unwrap_or_else(|error| panic!("Trimesh creation failed: {error:?}"))
961 }
962
963 pub fn try_trimesh_with_config(
977 vertices: Vec<RVector>,
978 indices: Vec<[u32; 3]>,
979 flags: TrimeshFlags,
980 ) -> Result<Self, TrimeshBuilderError> {
981 SharedShape::trimesh_with_flags(vertices, indices, flags.into())
982 .map(|trimesh| trimesh.into())
983 }
984
985 #[cfg(feature = "2d")]
988 pub fn convex_decomposition(vertices: &[RVector], indices: &[[u32; 2]]) -> Self {
989 SharedShape::convex_decomposition(vertices, indices).into()
990 }
991
992 #[cfg(feature = "3d")]
995 pub fn convex_decomposition(vertices: &[RVector], indices: &[[u32; 3]]) -> Self {
996 SharedShape::convex_decomposition(vertices, indices).into()
997 }
998
999 #[cfg(feature = "2d")]
1003 pub fn convex_decomposition_with_config(
1004 vertices: &[RVector],
1005 indices: &[[u32; 2]],
1006 params: &VhacdParameters,
1007 ) -> Self {
1008 SharedShape::convex_decomposition_with_params(vertices, indices, ¶ms.clone().into())
1009 .into()
1010 }
1011
1012 #[cfg(feature = "3d")]
1016 pub fn convex_decomposition_with_config(
1017 vertices: &[RVector],
1018 indices: &[[u32; 3]],
1019 params: &VhacdParameters,
1020 ) -> Self {
1021 SharedShape::convex_decomposition_with_params(vertices, indices, ¶ms.clone().into())
1022 .into()
1023 }
1024
1025 #[cfg(feature = "2d")]
1028 pub fn convex_hull(points: &[RVector]) -> Option<Self> {
1029 SharedShape::convex_hull(points).map(Into::into)
1030 }
1031
1032 #[cfg(feature = "3d")]
1035 pub fn convex_hull(points: &[RVector]) -> Option<Self> {
1036 SharedShape::convex_hull(points).map(Into::into)
1037 }
1038
1039 #[cfg(feature = "2d")]
1043 pub fn convex_polyline(points: Vec<RVector>) -> Option<Self> {
1044 SharedShape::convex_polyline(points).map(Into::into)
1045 }
1046
1047 pub fn voxels(voxel_size: Vector, grid_coordinates: &[IVector]) -> Self {
1051 #[cfg(all(feature = "2d", feature = "f64"))]
1052 let grid_coordinates = &grid_coordinates
1053 .iter()
1054 .map(|c| c.as_i64vec2())
1055 .collect::<Vec<_>>();
1056 #[cfg(all(feature = "3d", feature = "f64"))]
1057 let grid_coordinates = &grid_coordinates
1058 .iter()
1059 .map(|c| c.as_i64vec3())
1060 .collect::<Vec<_>>();
1061 let shape = Voxels::new(voxel_size.real(), grid_coordinates);
1062 SharedShape::new(shape).into()
1063 }
1064
1065 pub fn voxels_from_points(voxel_size: Vector, points: &[RVector]) -> Self {
1069 SharedShape::voxels_from_points(voxel_size.real(), points).into()
1070 }
1071
1072 #[cfg(feature = "2d")]
1074 pub fn voxelized_polyline(
1075 vertices: &[RVector],
1076 indices: &[[u32; 2]],
1077 voxel_size: f32,
1078 fill_mode: FillMode,
1079 ) -> Self {
1080 SharedShape::voxelized_mesh(vertices, indices, voxel_size.real(), fill_mode.into()).into()
1081 }
1082
1083 #[cfg(feature = "3d")]
1085 pub fn voxelized_trimesh(
1086 vertices: &[RVector],
1087 indices: &[[u32; 3]],
1088 voxel_size: f32,
1089 fill_mode: FillMode,
1090 ) -> Self {
1091 SharedShape::voxelized_mesh(vertices, indices, voxel_size.real(), fill_mode.into()).into()
1092 }
1093
1094 #[cfg(feature = "collider-from-mesh")]
1098 pub fn voxelized_trimesh_from_mesh(
1099 mesh: &Mesh,
1100 voxel_size: f32,
1101 fill_mode: FillMode,
1102 ) -> Option<Self> {
1103 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1104 SharedShape::voxelized_mesh(&vertices, &indices, voxel_size.real(), fill_mode.into())
1105 .into()
1106 })
1107 }
1108
1109 #[cfg_attr(
1110 feature = "2d",
1111 doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1112 )]
1113 #[cfg_attr(
1114 feature = "3d",
1115 doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1116 )]
1117 pub fn voxelized_convex_decomposition(
1118 vertices: &[RVector],
1119 indices: &[[u32; DIM]],
1120 ) -> Vec<Self> {
1121 Self::voxelized_convex_decomposition_with_config(
1122 vertices,
1123 indices,
1124 &VhacdParameters::default(),
1125 )
1126 }
1127
1128 #[cfg_attr(
1129 feature = "2d",
1130 doc = "Creates a collider with a compound shape obtained from the decomposition of the given polyline into voxelized convex parts."
1131 )]
1132 #[cfg_attr(
1133 feature = "3d",
1134 doc = "Creates a collider with a compound shape obtained from the decomposition of the given trimesh into voxelized convex parts."
1135 )]
1136 pub fn voxelized_convex_decomposition_with_config(
1137 vertices: &[RVector],
1138 indices: &[[u32; DIM]],
1139 parameters: &VhacdParameters,
1140 ) -> Vec<Self> {
1141 SharedShape::voxelized_convex_decomposition_with_params(
1142 vertices,
1143 indices,
1144 ¶meters.clone().into(),
1145 )
1146 .into_iter()
1147 .map(|c| c.into())
1148 .collect()
1149 }
1150
1151 #[cfg(feature = "2d")]
1158 pub fn heightfield(heights: Vec<Real>, scale: Vector) -> Self {
1159 SharedShape::heightfield(heights, scale.real()).into()
1160 }
1161
1162 #[cfg(feature = "3d")]
1172 pub fn heightfield(heights: Vec<Vec<Real>>, scale: Vector) -> Self {
1173 let row_count = heights.len();
1174 let column_count = heights[0].len();
1175 let data: Vec<Real> = heights.into_iter().flatten().collect();
1176
1177 assert_eq!(
1178 data.len(),
1179 row_count * column_count,
1180 "Each row in `heights` must have the same amount of points"
1181 );
1182
1183 let heights = parry::utils::Array2::new(row_count, column_count, data);
1184 SharedShape::heightfield(heights, scale.real()).into()
1185 }
1186
1187 #[cfg(feature = "collider-from-mesh")]
1218 pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
1219 extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1220 SharedShape::trimesh_with_flags(
1221 vertices,
1222 indices,
1223 TrimeshFlags::MERGE_DUPLICATE_VERTICES.into(),
1224 )
1225 .map(|trimesh| trimesh.into())
1226 .ok()
1227 })
1228 }
1229
1230 #[cfg(feature = "collider-from-mesh")]
1262 pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TrimeshFlags) -> Option<Self> {
1263 extract_mesh_vertices_indices(mesh).and_then(|(vertices, indices)| {
1264 SharedShape::trimesh_with_flags(vertices, indices, flags.into())
1265 .map(|trimesh| trimesh.into())
1266 .ok()
1267 })
1268 }
1269
1270 #[cfg(feature = "collider-from-mesh")]
1287 pub fn convex_hull_from_mesh(mesh: &Mesh) -> Option<Self> {
1288 extract_mesh_vertices_indices(mesh)
1289 .and_then(|(vertices, _)| SharedShape::convex_hull(&vertices).map(|shape| shape.into()))
1290 }
1291
1292 #[cfg(feature = "collider-from-mesh")]
1309 pub fn convex_decomposition_from_mesh(mesh: &Mesh) -> Option<Self> {
1310 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1311 SharedShape::convex_decomposition(&vertices, &indices).into()
1312 })
1313 }
1314
1315 #[cfg(feature = "collider-from-mesh")]
1337 pub fn convex_decomposition_from_mesh_with_config(
1338 mesh: &Mesh,
1339 parameters: &VhacdParameters,
1340 ) -> Option<Self> {
1341 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1342 SharedShape::convex_decomposition_with_params(
1343 &vertices,
1344 &indices,
1345 ¶meters.clone().into(),
1346 )
1347 .into()
1348 })
1349 }
1350
1351 #[cfg_attr(
1356 feature = "collider-from-mesh",
1357 doc = "Returns `None` in the following cases:
1358- The given [`ColliderConstructor`] requires a mesh, but none was provided.
1359- Creating the collider from the given [`ColliderConstructor`] failed."
1360 )]
1361 #[cfg_attr(
1362 not(feature = "collider-from-mesh"),
1363 doc = "Returns `None` if creating the collider from the given [`ColliderConstructor`] failed."
1364 )]
1365 pub fn try_from_constructor(
1366 collider_constructor: ColliderConstructor,
1367 #[cfg(feature = "collider-from-mesh")] mesh: Option<&Mesh>,
1368 ) -> Option<Self> {
1369 match collider_constructor {
1370 #[cfg(feature = "2d")]
1371 ColliderConstructor::Circle { radius } => Some(Self::circle(radius)),
1372 #[cfg(feature = "3d")]
1373 ColliderConstructor::Sphere { radius } => Some(Self::sphere(radius)),
1374 #[cfg(feature = "2d")]
1375 ColliderConstructor::Ellipse {
1376 half_width,
1377 half_height,
1378 } => Some(Self::ellipse(half_width, half_height)),
1379 #[cfg(feature = "2d")]
1380 ColliderConstructor::Rectangle { x_length, y_length } => {
1381 Some(Self::rectangle(x_length, y_length))
1382 }
1383 #[cfg(feature = "3d")]
1384 ColliderConstructor::Cuboid {
1385 x_length,
1386 y_length,
1387 z_length,
1388 } => Some(Self::cuboid(x_length, y_length, z_length)),
1389 #[cfg(feature = "2d")]
1390 ColliderConstructor::RoundRectangle {
1391 x_length,
1392 y_length,
1393 border_radius,
1394 } => Some(Self::round_rectangle(x_length, y_length, border_radius)),
1395 #[cfg(feature = "3d")]
1396 ColliderConstructor::RoundCuboid {
1397 x_length,
1398 y_length,
1399 z_length,
1400 border_radius,
1401 } => Some(Self::round_cuboid(
1402 x_length,
1403 y_length,
1404 z_length,
1405 border_radius,
1406 )),
1407 #[cfg(feature = "3d")]
1408 ColliderConstructor::Cylinder { radius, height } => {
1409 Some(Self::cylinder(radius, height))
1410 }
1411 #[cfg(feature = "3d")]
1412 ColliderConstructor::Cone { radius, height } => Some(Self::cone(radius, height)),
1413 ColliderConstructor::Capsule { radius, height } => Some(Self::capsule(radius, height)),
1414 ColliderConstructor::CapsuleEndpoints { radius, a, b } => {
1415 Some(Self::capsule_endpoints(radius, a, b))
1416 }
1417 ColliderConstructor::HalfSpace { outward_normal } => {
1418 Some(Self::half_space(outward_normal))
1419 }
1420 ColliderConstructor::Segment { a, b } => Some(Self::segment(a, b)),
1421 ColliderConstructor::Triangle { a, b, c } => Some(Self::triangle(a, b, c)),
1422 #[cfg(feature = "2d")]
1423 ColliderConstructor::RegularPolygon {
1424 circumradius,
1425 sides,
1426 } => Some(Self::regular_polygon(circumradius, sides)),
1427 ColliderConstructor::Polyline { vertices, indices } => {
1428 Some(Self::polyline(vertices, indices))
1429 }
1430 ColliderConstructor::Trimesh { vertices, indices } => {
1431 Some(Self::trimesh(vertices, indices))
1432 }
1433 ColliderConstructor::TrimeshWithConfig {
1434 vertices,
1435 indices,
1436 flags,
1437 } => Some(Self::trimesh_with_config(vertices, indices, flags)),
1438 #[cfg(feature = "2d")]
1439 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1440 Some(Self::convex_decomposition(&vertices, &indices))
1441 }
1442 #[cfg(feature = "3d")]
1443 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1444 Some(Self::convex_decomposition(&vertices, &indices))
1445 }
1446 #[cfg(feature = "2d")]
1447 ColliderConstructor::ConvexDecompositionWithConfig {
1448 vertices,
1449 indices,
1450 params,
1451 } => Some(Self::convex_decomposition_with_config(
1452 &vertices, &indices, ¶ms,
1453 )),
1454 #[cfg(feature = "3d")]
1455 ColliderConstructor::ConvexDecompositionWithConfig {
1456 vertices,
1457 indices,
1458 params,
1459 } => Some(Self::convex_decomposition_with_config(
1460 &vertices, &indices, ¶ms,
1461 )),
1462 #[cfg(feature = "2d")]
1463 ColliderConstructor::ConvexHull { points } => Self::convex_hull(&points),
1464 #[cfg(feature = "3d")]
1465 ColliderConstructor::ConvexHull { points } => Self::convex_hull(&points),
1466 #[cfg(feature = "2d")]
1467 ColliderConstructor::ConvexPolyline { points } => Self::convex_polyline(points),
1468 ColliderConstructor::Voxels {
1469 voxel_size,
1470 grid_coordinates,
1471 } => Some(Self::voxels(voxel_size, &grid_coordinates)),
1472 #[cfg(feature = "2d")]
1473 ColliderConstructor::VoxelizedPolyline {
1474 vertices,
1475 indices,
1476 voxel_size,
1477 fill_mode,
1478 } => Some(Self::voxelized_polyline(
1479 &vertices, &indices, voxel_size, fill_mode,
1480 )),
1481 #[cfg(feature = "3d")]
1482 ColliderConstructor::VoxelizedTrimesh {
1483 vertices,
1484 indices,
1485 voxel_size,
1486 fill_mode,
1487 } => Some(Self::voxelized_trimesh(
1488 &vertices, &indices, voxel_size, fill_mode,
1489 )),
1490 #[cfg(feature = "2d")]
1491 ColliderConstructor::Heightfield { heights, scale } => {
1492 Some(Self::heightfield(heights, scale))
1493 }
1494 #[cfg(feature = "3d")]
1495 ColliderConstructor::Heightfield { heights, scale } => {
1496 Some(Self::heightfield(heights, scale))
1497 }
1498 #[cfg(feature = "collider-from-mesh")]
1499 ColliderConstructor::TrimeshFromMesh => Self::trimesh_from_mesh(mesh?),
1500 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1501 ColliderConstructor::TrimeshFromMeshWithConfig(flags) => {
1502 Self::trimesh_from_mesh_with_config(mesh?, flags)
1503 }
1504 #[cfg(feature = "collider-from-mesh")]
1505 ColliderConstructor::ConvexDecompositionFromMesh => {
1506 Self::convex_decomposition_from_mesh(mesh?)
1507 }
1508 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1509 ColliderConstructor::ConvexDecompositionFromMeshWithConfig(params) => {
1510 Self::convex_decomposition_from_mesh_with_config(mesh?, ¶ms)
1511 }
1512 #[cfg(feature = "collider-from-mesh")]
1513 ColliderConstructor::ConvexHullFromMesh => Self::convex_hull_from_mesh(mesh?),
1514 #[cfg(feature = "collider-from-mesh")]
1515 ColliderConstructor::VoxelizedTrimeshFromMesh {
1516 voxel_size,
1517 fill_mode,
1518 } => Self::voxelized_trimesh_from_mesh(mesh?, voxel_size, fill_mode),
1519 ColliderConstructor::Compound(compound_constructors) => {
1520 let shapes: Vec<_> =
1521 ColliderConstructor::flatten_compound_constructors(compound_constructors)
1522 .into_iter()
1523 .filter_map(|(position, rotation, collider_constructor)| {
1524 Self::try_from_constructor(
1525 collider_constructor,
1526 #[cfg(feature = "collider-from-mesh")]
1527 mesh,
1528 )
1529 .map(|collider| (position, rotation, collider))
1530 })
1531 .collect();
1532
1533 (!shapes.is_empty()).then(|| Self::compound(shapes))
1534 }
1535 }
1536 }
1537}
1538
1539#[cfg(feature = "collider-from-mesh")]
1540type VerticesIndices = (Vec<RVector>, Vec<[u32; 3]>);
1541
1542#[cfg(feature = "collider-from-mesh")]
1543fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<VerticesIndices> {
1544 let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
1545 let indices = mesh.indices()?;
1546
1547 let vtx: Vec<_> = match vertices {
1548 VertexAttributeValues::Float32(vtx) => Some(
1549 vtx.chunks(3)
1550 .map(|v| [v[0] as Real, v[1] as Real, v[2] as Real].into())
1551 .collect(),
1552 ),
1553 VertexAttributeValues::Float32x3(vtx) => Some(
1554 vtx.iter()
1555 .map(|v| [v[0] as Real, v[1] as Real, v[2] as Real].into())
1556 .collect(),
1557 ),
1558 _ => None,
1559 }?;
1560
1561 let idx = match indices {
1562 Indices::U16(idx) => idx
1563 .as_chunks::<3>()
1564 .0
1565 .iter()
1566 .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
1567 .collect(),
1568 Indices::U32(idx) => idx.as_chunks::<3>().0.to_vec(),
1569 };
1570
1571 Some((vtx, idx))
1572}
1573
1574fn scale_shape(
1575 shape: &SharedShape,
1576 scale: Vector,
1577 num_subdivisions: u32,
1578) -> Result<SharedShape, UnsupportedShape> {
1579 let scale = scale.abs().real();
1580 match shape.as_typed_shape() {
1581 TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(scale.abs()))),
1582 TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
1583 border_radius: s.border_radius,
1584 inner_shape: s.inner_shape.scaled(scale.abs()),
1585 })),
1586 TypedShape::Capsule(c) => match c.scaled(scale.abs(), num_subdivisions) {
1587 None => {
1588 log::error!("Failed to apply scale {} to Capsule shape.", scale);
1589 Ok(SharedShape::ball(0.0))
1590 }
1591 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1592 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1593 },
1594 TypedShape::Ball(b) => {
1595 #[cfg(feature = "2d")]
1596 {
1597 if scale.x == scale.y {
1598 Ok(SharedShape::ball(b.radius * scale.x.abs()))
1599 } else {
1600 Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1602 half_size: Vec2::splat(b.radius as f32) * scale.f32().abs(),
1603 })))
1604 }
1605 }
1606 #[cfg(feature = "3d")]
1607 match b.scaled(scale.abs(), num_subdivisions) {
1608 None => {
1609 log::error!("Failed to apply scale {} to Ball shape.", scale);
1610 Ok(SharedShape::ball(0.0))
1611 }
1612 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1613 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1614 }
1615 }
1616 TypedShape::Segment(s) => Ok(SharedShape::new(s.scaled(scale))),
1617 TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(scale))),
1618 TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
1619 border_radius: t.border_radius,
1620 inner_shape: t.inner_shape.scaled(scale),
1621 })),
1622 TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(scale))),
1623 TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(scale))),
1624 TypedShape::HalfSpace(h) => match h.scaled(scale) {
1625 None => {
1626 log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
1627 Ok(SharedShape::ball(0.0))
1628 }
1629 Some(scaled) => Ok(SharedShape::new(scaled)),
1630 },
1631 TypedShape::Voxels(v) => Ok(SharedShape::new(v.clone().scaled(scale))),
1632 TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(scale))),
1633 #[cfg(feature = "2d")]
1634 TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(scale) {
1635 None => {
1636 log::error!("Failed to apply scale {} to ConvexPolygon shape.", scale);
1637 Ok(SharedShape::ball(0.0))
1638 }
1639 Some(scaled) => Ok(SharedShape::new(scaled)),
1640 },
1641 #[cfg(feature = "2d")]
1642 TypedShape::RoundConvexPolygon(cp) => match cp.inner_shape.clone().scaled(scale) {
1643 None => {
1644 log::error!(
1645 "Failed to apply scale {} to RoundConvexPolygon 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 #[cfg(feature = "3d")]
1656 TypedShape::ConvexPolyhedron(cp) => match cp.clone().scaled(scale) {
1657 None => {
1658 log::error!("Failed to apply scale {} to ConvexPolyhedron shape.", scale);
1659 Ok(SharedShape::ball(0.0))
1660 }
1661 Some(scaled) => Ok(SharedShape::new(scaled)),
1662 },
1663 #[cfg(feature = "3d")]
1664 TypedShape::RoundConvexPolyhedron(cp) => match cp.clone().inner_shape.scaled(scale) {
1665 None => {
1666 log::error!(
1667 "Failed to apply scale {} to RoundConvexPolyhedron shape.",
1668 scale
1669 );
1670 Ok(SharedShape::ball(0.0))
1671 }
1672 Some(scaled) => Ok(SharedShape::new(RoundShape {
1673 border_radius: cp.border_radius,
1674 inner_shape: scaled,
1675 })),
1676 },
1677 #[cfg(feature = "3d")]
1678 TypedShape::Cylinder(c) => match c.scaled(scale.abs(), num_subdivisions) {
1679 None => {
1680 log::error!("Failed to apply scale {} to Cylinder shape.", scale);
1681 Ok(SharedShape::ball(0.0))
1682 }
1683 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1684 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1685 },
1686 #[cfg(feature = "3d")]
1687 TypedShape::RoundCylinder(c) => match c.inner_shape.scaled(scale.abs(), num_subdivisions) {
1688 None => {
1689 log::error!("Failed to apply scale {} to RoundCylinder shape.", scale);
1690 Ok(SharedShape::ball(0.0))
1691 }
1692 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1693 border_radius: c.border_radius,
1694 inner_shape: scaled,
1695 })),
1696 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1697 border_radius: c.border_radius,
1698 inner_shape: scaled,
1699 })),
1700 },
1701 #[cfg(feature = "3d")]
1702 TypedShape::Cone(c) => match c.scaled(scale, num_subdivisions) {
1703 None => {
1704 log::error!("Failed to apply scale {} to Cone shape.", scale);
1705 Ok(SharedShape::ball(0.0))
1706 }
1707 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1708 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1709 },
1710 #[cfg(feature = "3d")]
1711 TypedShape::RoundCone(c) => match c.inner_shape.scaled(scale, num_subdivisions) {
1712 None => {
1713 log::error!("Failed to apply scale {} to RoundCone shape.", scale);
1714 Ok(SharedShape::ball(0.0))
1715 }
1716 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1717 border_radius: c.border_radius,
1718 inner_shape: scaled,
1719 })),
1720 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1721 border_radius: c.border_radius,
1722 inner_shape: scaled,
1723 })),
1724 },
1725 TypedShape::Compound(c) => {
1726 let mut scaled = Vec::with_capacity(c.shapes().len());
1727
1728 for (pose, shape) in c.shapes() {
1729 #[cfg(feature = "2d")]
1730 scaled.push((
1731 make_pose(
1732 pose.translation * scale,
1733 Rot::from_sin_cos(pose.rotation.sin().f32(), pose.rotation.cos().f32()),
1734 ),
1735 scale_shape(shape, scale.f32(), num_subdivisions)?,
1736 ));
1737 #[cfg(feature = "3d")]
1738 scaled.push((
1739 make_pose(pose.translation * scale, pose.rotation.f32()),
1740 scale_shape(shape, scale.f32(), num_subdivisions)?,
1741 ));
1742 }
1743 Ok(SharedShape::compound(scaled))
1744 }
1745 TypedShape::Custom(_shape) => {
1746 #[cfg(feature = "2d")]
1747 {
1748 if let Some(ellipse) = _shape.as_shape::<EllipseColliderShape>() {
1749 return Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1750 half_size: ellipse.half_size * scale.f32().abs(),
1751 })));
1752 }
1753 if let Some(polygon) = _shape.as_shape::<RegularPolygonColliderShape>() {
1754 if scale.x == scale.y {
1755 return Ok(SharedShape::new(RegularPolygonColliderShape(
1756 RegularPolygon::new(
1757 polygon.circumradius() * scale.x.abs() as f32,
1758 polygon.sides,
1759 ),
1760 )));
1761 } else {
1762 let vertices = polygon
1763 .vertices(0.0)
1764 .into_iter()
1765 .map(|v| v.real())
1766 .collect::<Vec<_>>();
1767
1768 return scale_shape(
1769 &SharedShape::convex_hull(&vertices).unwrap(),
1770 scale.f32(),
1771 num_subdivisions,
1772 );
1773 }
1774 }
1775 }
1776 _shape
1777 .scale_dyn(scale, num_subdivisions)
1778 .map(|scaled| SharedShape(alloc::sync::Arc::from(scaled)))
1779 .ok_or(parry::query::Unsupported)
1780 }
1781 }
1782}
1783
1784#[cfg(all(test, feature = "3d"))]
1785mod tests {
1786 use super::*;
1787 use approx::assert_relative_eq;
1788 use core::f32::consts::PI;
1789
1790 #[test]
1791 fn test_flatten_compound_constructors() {
1792 let input = vec![
1793 (
1794 Position(RVector::new(10.0, 0.0, 0.0)),
1795 Rotation::default(),
1796 ColliderConstructor::Sphere { radius: 1.0 },
1797 ),
1798 (
1799 Position(RVector::new(5.0, 0.0, 0.0)),
1800 Rotation::from(Quat::from_rotation_z(PI / 2.0)),
1801 ColliderConstructor::Compound(vec![
1802 (
1803 Position(RVector::new(2.0, 0.0, 0.0)),
1804 Rotation::from(Quat::from_rotation_y(PI)),
1805 ColliderConstructor::Compound(vec![(
1806 Position(RVector::new(1.0, 0.0, 0.0)),
1807 Rotation::default(),
1808 ColliderConstructor::Sphere { radius: 0.5 },
1809 )]),
1810 ),
1811 (
1812 Position(RVector::new(0.0, 3.0, 0.0)),
1813 Rotation::default(),
1814 ColliderConstructor::Sphere { radius: 0.25 },
1815 ),
1816 ]),
1817 ),
1818 ];
1819
1820 let flattened = ColliderConstructor::flatten_compound_constructors(input);
1821 assert_eq!(flattened.len(), 3);
1822
1823 let unchanged_simple_sphere = &flattened[0];
1824 let flattened_grandchild = &flattened[1];
1825 let flattened_sibling = &flattened[2];
1826
1827 assert_eq!(
1829 unchanged_simple_sphere.0,
1830 Position(RVector::new(10.0, 0.0, 0.0))
1831 );
1832 assert_eq!(unchanged_simple_sphere.1, Rotation::default());
1833
1834 let expected_grandchild_world_pos = RVector::new(5.0, 1.0, 0.0);
1840 let actual_grandchild_world_pos = flattened_grandchild.0.0;
1841
1842 assert_relative_eq!(
1843 actual_grandchild_world_pos.x,
1844 expected_grandchild_world_pos.x,
1845 epsilon = 1e-6
1846 );
1847 assert_relative_eq!(
1848 actual_grandchild_world_pos.y,
1849 expected_grandchild_world_pos.y,
1850 epsilon = 1e-6
1851 );
1852 assert_relative_eq!(
1853 actual_grandchild_world_pos.z,
1854 expected_grandchild_world_pos.z,
1855 epsilon = 1e-6
1856 );
1857
1858 let expected_sibling_world_pos = RVector::new(2.0, 0.0, 0.0);
1862 let actual_sibling_world_pos = flattened_sibling.0.0;
1863
1864 assert_relative_eq!(
1865 actual_sibling_world_pos.x,
1866 expected_sibling_world_pos.x,
1867 epsilon = 1e-6
1868 );
1869 assert_relative_eq!(
1870 actual_sibling_world_pos.y,
1871 expected_sibling_world_pos.y,
1872 epsilon = 1e-6
1873 );
1874 assert_relative_eq!(
1875 actual_sibling_world_pos.z,
1876 expected_sibling_world_pos.z,
1877 epsilon = 1e-6
1878 );
1879 }
1880}