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::render::mesh::{Indices, VertexAttributeValues};
16use bevy::{log, prelude::*};
17use contact_query::UnsupportedShape;
18use itertools::Either;
19use parry::shape::{RoundShape, SharedShape, TypedShape};
20
21impl<T: IntoCollider<Collider>> From<T> for Collider {
22 fn from(value: T) -> Self {
23 value.collider()
24 }
25}
26
27#[derive(Clone, PartialEq, Debug, Reflect)]
31#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
33#[reflect(PartialEq, Debug)]
34pub struct VhacdParameters {
35 pub concavity: Scalar,
40 pub alpha: Scalar,
45 pub beta: Scalar,
50 pub resolution: u32,
54 pub plane_downsampling: u32,
59 pub convex_hull_downsampling: u32,
64 pub fill_mode: FillMode,
69 pub convex_hull_approximation: bool,
75 pub max_convex_hulls: u32,
79}
80
81impl Default for VhacdParameters {
82 fn default() -> Self {
83 Self {
84 #[cfg(feature = "3d")]
85 resolution: 64,
86 #[cfg(feature = "3d")]
87 concavity: 0.01,
88 #[cfg(feature = "2d")]
89 resolution: 256,
90 #[cfg(feature = "2d")]
91 concavity: 0.1,
92 plane_downsampling: 4,
93 convex_hull_downsampling: 4,
94 alpha: 0.05,
95 beta: 0.05,
96 convex_hull_approximation: true,
97 max_convex_hulls: 1024,
98 fill_mode: FillMode::FloodFill {
99 detect_cavities: false,
100 #[cfg(feature = "2d")]
101 detect_self_intersections: false,
102 },
103 }
104 }
105}
106
107impl From<VhacdParameters> for parry::transformation::vhacd::VHACDParameters {
108 fn from(value: VhacdParameters) -> Self {
109 Self {
110 concavity: value.concavity,
111 alpha: value.alpha,
112 beta: value.beta,
113 resolution: value.resolution,
114 plane_downsampling: value.plane_downsampling,
115 convex_hull_downsampling: value.convex_hull_downsampling,
116 fill_mode: value.fill_mode.into(),
117 convex_hull_approximation: value.convex_hull_approximation,
118 max_convex_hulls: value.max_convex_hulls,
119 }
120 }
121}
122
123#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
126#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
127#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
128#[reflect(Hash, PartialEq, Debug)]
129pub enum FillMode {
130 SurfaceOnly,
133 FloodFill {
137 detect_cavities: bool,
139 #[cfg(feature = "2d")]
141 detect_self_intersections: bool,
142 },
143}
144
145impl From<FillMode> for parry::transformation::voxelization::FillMode {
146 fn from(value: FillMode) -> Self {
147 match value {
148 FillMode::SurfaceOnly => Self::SurfaceOnly,
149 FillMode::FloodFill {
150 detect_cavities,
151 #[cfg(feature = "2d")]
152 detect_self_intersections,
153 } => Self::FloodFill {
154 detect_cavities,
155 #[cfg(feature = "2d")]
156 detect_self_intersections,
157 },
158 }
159 }
160}
161
162#[repr(transparent)]
164#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
165#[derive(Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
166#[reflect(opaque, Hash, PartialEq, Debug)]
167pub struct TrimeshFlags(u8);
168
169bitflags::bitflags! {
170 impl TrimeshFlags: u8 {
171 const HALF_EDGE_TOPOLOGY = 0b0000_0001;
173 const CONNECTED_COMPONENTS = 0b0000_0010;
179 const DELETE_BAD_TOPOLOGY_TRIANGLES = 0b0000_0100;
181 const ORIENTED = 0b0000_1000;
185 const MERGE_DUPLICATE_VERTICES = 0b0001_0000;
190 const DELETE_DEGENERATE_TRIANGLES = 0b0010_0000;
196 const DELETE_DUPLICATE_TRIANGLES = 0b0100_0000;
202 const FIX_INTERNAL_EDGES = 0b1000_0000 | Self::ORIENTED.bits() | Self::MERGE_DUPLICATE_VERTICES.bits();
209 }
210}
211
212impl From<TrimeshFlags> for parry::shape::TriMeshFlags {
213 fn from(value: TrimeshFlags) -> Self {
214 Self::from_bits(value.bits().into()).unwrap()
215 }
216}
217
218#[cfg_attr(feature = "2d", doc = "# use avian2d::prelude::*;")]
226#[cfg_attr(feature = "3d", doc = "# use avian3d::prelude::*;")]
227#[cfg_attr(feature = "2d", doc = "commands.spawn(Collider::circle(0.5));")]
232#[cfg_attr(feature = "3d", doc = "commands.spawn(Collider::sphere(0.5));")]
233#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
245#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
246#[cfg_attr(feature = "2d", doc = " Collider::circle(0.5),")]
253#[cfg_attr(feature = "3d", doc = " Collider::sphere(0.5),")]
254#[cfg_attr(
257 feature = "2d",
258 doc = " commands.spawn((RigidBody::Static, Collider::rectangle(5.0, 0.5)));"
259)]
260#[cfg_attr(
261 feature = "3d",
262 doc = " commands.spawn((RigidBody::Static, Collider::cuboid(5.0, 0.5, 5.0)));"
263)]
264#[cfg_attr(
275 feature = "3d",
276 doc = "Colliders can also be generated automatically for meshes and scenes. See [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]."
277)]
278#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
288#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
289#[cfg_attr(
295 feature = "2d",
296 doc = " .spawn((RigidBody::Dynamic, Collider::circle(0.5)))"
297)]
298#[cfg_attr(
299 feature = "3d",
300 doc = " .spawn((RigidBody::Dynamic, Collider::sphere(0.5)))"
301)]
302#[cfg_attr(
305 feature = "2d",
306 doc = " children.spawn((Collider::circle(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
307 children.spawn((Collider::circle(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
308)]
309#[cfg_attr(
310 feature = "3d",
311 doc = " children.spawn((Collider::sphere(0.5), Transform::from_xyz(2.0, 0.0, 0.0)));
312 children.spawn((Collider::sphere(0.5), Transform::from_xyz(-2.0, 0.0, 0.0)));"
313)]
314#[cfg_attr(
334 feature = "3d",
335 doc = "- Generating colliders for meshes and scenes with [`ColliderConstructor`] and [`ColliderConstructorHierarchy`]"
336)]
337#[derive(Clone, Component, Debug)]
353#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
354#[require(
355 ColliderMarker,
356 ColliderAabb,
357 CollisionLayers,
358 ColliderDensity,
359 ColliderMassProperties
360)]
361pub struct Collider {
362 shape: SharedShape,
364 scaled_shape: SharedShape,
369 scale: Vector,
371}
372
373impl From<SharedShape> for Collider {
374 fn from(value: SharedShape) -> Self {
375 Self {
376 shape: value.clone(),
377 scaled_shape: value,
378 scale: Vector::ONE,
379 }
380 }
381}
382
383impl Default for Collider {
384 fn default() -> Self {
385 #[cfg(feature = "2d")]
386 {
387 Self::rectangle(0.5, 0.5)
388 }
389 #[cfg(feature = "3d")]
390 {
391 Self::cuboid(0.5, 0.5, 0.5)
392 }
393 }
394}
395
396impl AnyCollider for Collider {
397 type Context = ();
398
399 fn aabb_with_context(
400 &self,
401 position: Vector,
402 rotation: impl Into<Rotation>,
403 _: AabbContext<Self::Context>,
404 ) -> ColliderAabb {
405 let aabb = self
406 .shape_scaled()
407 .compute_aabb(&make_isometry(position, rotation));
408 ColliderAabb {
409 min: aabb.mins.into(),
410 max: aabb.maxs.into(),
411 }
412 }
413
414 fn contact_manifolds_with_context(
415 &self,
416 other: &Self,
417 position1: Vector,
418 rotation1: impl Into<Rotation>,
419 position2: Vector,
420 rotation2: impl Into<Rotation>,
421 prediction_distance: Scalar,
422 manifolds: &mut Vec<ContactManifold>,
423 _: ContactManifoldContext<Self::Context>,
424 ) {
425 contact_query::contact_manifolds(
426 self,
427 position1,
428 rotation1,
429 other,
430 position2,
431 rotation2,
432 prediction_distance,
433 manifolds,
434 )
435 }
436}
437
438#[cfg(feature = "2d")]
441impl ComputeMassProperties for Collider {
442 fn mass(&self, density: f32) -> f32 {
443 let props = self.shape_scaled().mass_properties(density as Scalar);
444 props.mass() as f32
445 }
446
447 fn unit_angular_inertia(&self) -> f32 {
448 self.angular_inertia(1.0)
449 }
450
451 fn angular_inertia(&self, mass: f32) -> f32 {
452 let props = self.shape_scaled().mass_properties(mass as Scalar);
453 props.principal_inertia() as f32
454 }
455
456 fn center_of_mass(&self) -> Vec2 {
457 let props = self.shape_scaled().mass_properties(1.0);
458 Vector::from(props.local_com).f32()
459 }
460
461 fn mass_properties(&self, density: f32) -> MassProperties {
462 let props = self.shape_scaled().mass_properties(density as Scalar);
463
464 MassProperties {
465 mass: props.mass() as f32,
466 #[cfg(feature = "2d")]
467 angular_inertia: props.principal_inertia() as f32,
468 #[cfg(feature = "3d")]
469 principal_angular_inertia: Vector::from(props.principal_inertia()).f32(),
470 #[cfg(feature = "3d")]
471 local_inertial_frame: Quaternion::from(props.principal_inertia_local_frame).f32(),
472 center_of_mass: Vector::from(props.local_com).f32(),
473 }
474 }
475}
476
477#[cfg(feature = "3d")]
478impl ComputeMassProperties for Collider {
479 fn mass(&self, density: f32) -> f32 {
480 let props = self.shape_scaled().mass_properties(density as Scalar);
481 props.mass() as f32
482 }
483
484 fn unit_principal_angular_inertia(&self) -> Vec3 {
485 self.principal_angular_inertia(1.0)
486 }
487
488 fn principal_angular_inertia(&self, mass: f32) -> Vec3 {
489 let props = self.shape_scaled().mass_properties(mass as Scalar);
490 Vector::from(props.principal_inertia()).f32()
491 }
492
493 fn local_inertial_frame(&self) -> Quat {
494 let props = self.shape_scaled().mass_properties(1.0);
495 Quaternion::from(props.principal_inertia_local_frame).f32()
496 }
497
498 fn center_of_mass(&self) -> Vec3 {
499 let props = self.shape_scaled().mass_properties(1.0);
500 Vector::from(props.local_com).f32()
501 }
502
503 fn mass_properties(&self, density: f32) -> MassProperties {
504 let props = self.shape_scaled().mass_properties(density as Scalar);
505
506 MassProperties {
507 mass: props.mass() as f32,
508 #[cfg(feature = "2d")]
509 angular_inertia: props.principal_inertia() as f32,
510 #[cfg(feature = "3d")]
511 principal_angular_inertia: Vector::from(props.principal_inertia()).f32(),
512 #[cfg(feature = "3d")]
513 local_inertial_frame: Quaternion::from(props.principal_inertia_local_frame).f32(),
514 center_of_mass: Vector::from(props.local_com).f32(),
515 }
516 }
517}
518
519impl ScalableCollider for Collider {
520 fn scale(&self) -> Vector {
521 self.scale()
522 }
523
524 fn set_scale(&mut self, scale: Vector, detail: u32) {
525 self.set_scale(scale, detail)
526 }
527}
528
529impl Collider {
530 pub fn shape(&self) -> &SharedShape {
532 &self.shape
533 }
534
535 pub fn shape_scaled(&self) -> &SharedShape {
537 &self.scaled_shape
538 }
539
540 pub fn set_shape(&mut self, shape: SharedShape) {
542 self.shape = shape;
543
544 if let Ok(scaled) = scale_shape(&self.shape, self.scale, 10) {
546 self.scaled_shape = scaled;
547 } else {
548 log::error!("Failed to create convex hull for scaled collider.");
549 }
550 }
551
552 pub fn scale(&self) -> Vector {
554 self.scale
555 }
556
557 pub fn set_scale(&mut self, scale: Vector, num_subdivisions: u32) {
565 if scale == self.scale {
566 return;
567 }
568
569 if scale == Vector::ONE {
570 self.scaled_shape = self.shape.clone();
572 self.scale = Vector::ONE;
573 return;
574 }
575
576 if let Ok(scaled) = scale_shape(&self.shape, scale, num_subdivisions) {
577 self.scaled_shape = scaled;
578 self.scale = scale;
579 } else {
580 log::error!("Failed to create convex hull for scaled collider.");
581 }
582 }
583
584 pub fn project_point(
590 &self,
591 translation: impl Into<Position>,
592 rotation: impl Into<Rotation>,
593 point: Vector,
594 solid: bool,
595 ) -> (Vector, bool) {
596 let projection = self.shape_scaled().project_point(
597 &make_isometry(translation, rotation),
598 &point.into(),
599 solid,
600 );
601 (projection.point.into(), projection.is_inside)
602 }
603
604 pub fn distance_to_point(
610 &self,
611 translation: impl Into<Position>,
612 rotation: impl Into<Rotation>,
613 point: Vector,
614 solid: bool,
615 ) -> Scalar {
616 self.shape_scaled().distance_to_point(
617 &make_isometry(translation, rotation),
618 &point.into(),
619 solid,
620 )
621 }
622
623 pub fn contains_point(
625 &self,
626 translation: impl Into<Position>,
627 rotation: impl Into<Rotation>,
628 point: Vector,
629 ) -> bool {
630 self.shape_scaled()
631 .contains_point(&make_isometry(translation, rotation), &point.into())
632 }
633
634 pub fn cast_ray(
647 &self,
648 translation: impl Into<Position>,
649 rotation: impl Into<Rotation>,
650 ray_origin: Vector,
651 ray_direction: Vector,
652 max_distance: Scalar,
653 solid: bool,
654 ) -> Option<(Scalar, Vector)> {
655 let hit = self.shape_scaled().cast_ray_and_get_normal(
656 &make_isometry(translation, rotation),
657 &parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
658 max_distance,
659 solid,
660 );
661 hit.map(|hit| (hit.time_of_impact, hit.normal.into()))
662 }
663
664 pub fn intersects_ray(
672 &self,
673 translation: impl Into<Position>,
674 rotation: impl Into<Rotation>,
675 ray_origin: Vector,
676 ray_direction: Vector,
677 max_distance: Scalar,
678 ) -> bool {
679 self.shape_scaled().intersects_ray(
680 &make_isometry(translation, rotation),
681 &parry::query::Ray::new(ray_origin.into(), ray_direction.into()),
682 max_distance,
683 )
684 }
685
686 pub fn compound(
694 shapes: Vec<(
695 impl Into<Position>,
696 impl Into<Rotation>,
697 impl Into<Collider>,
698 )>,
699 ) -> Self {
700 let shapes = shapes
701 .into_iter()
702 .map(|(p, r, c)| {
703 (
704 make_isometry(*p.into(), r.into()),
705 c.into().shape_scaled().clone(),
706 )
707 })
708 .collect::<Vec<_>>();
709 SharedShape::compound(shapes).into()
710 }
711
712 #[cfg(feature = "2d")]
714 pub fn circle(radius: Scalar) -> Self {
715 SharedShape::ball(radius).into()
716 }
717
718 #[cfg(feature = "3d")]
720 pub fn sphere(radius: Scalar) -> Self {
721 SharedShape::ball(radius).into()
722 }
723
724 #[cfg(feature = "2d")]
726 pub fn ellipse(half_width: Scalar, half_height: Scalar) -> Self {
727 SharedShape::new(EllipseColliderShape(Ellipse::new(
728 half_width as f32,
729 half_height as f32,
730 )))
731 .into()
732 }
733
734 #[cfg(feature = "2d")]
736 pub fn rectangle(x_length: Scalar, y_length: Scalar) -> Self {
737 SharedShape::cuboid(x_length * 0.5, y_length * 0.5).into()
738 }
739
740 #[cfg(feature = "3d")]
742 pub fn cuboid(x_length: Scalar, y_length: Scalar, z_length: Scalar) -> Self {
743 SharedShape::cuboid(x_length * 0.5, y_length * 0.5, z_length * 0.5).into()
744 }
745
746 #[cfg(feature = "2d")]
748 pub fn round_rectangle(x_length: Scalar, y_length: Scalar, border_radius: Scalar) -> Self {
749 SharedShape::round_cuboid(x_length * 0.5, y_length * 0.5, border_radius).into()
750 }
751
752 #[cfg(feature = "3d")]
754 pub fn round_cuboid(
755 x_length: Scalar,
756 y_length: Scalar,
757 z_length: Scalar,
758 border_radius: Scalar,
759 ) -> Self {
760 SharedShape::round_cuboid(
761 x_length * 0.5,
762 y_length * 0.5,
763 z_length * 0.5,
764 border_radius,
765 )
766 .into()
767 }
768
769 #[cfg(feature = "3d")]
772 pub fn cylinder(radius: Scalar, height: Scalar) -> Self {
773 SharedShape::cylinder(height * 0.5, radius).into()
774 }
775
776 #[cfg(feature = "3d")]
779 pub fn cone(radius: Scalar, height: Scalar) -> Self {
780 SharedShape::cone(height * 0.5, radius).into()
781 }
782
783 pub fn capsule(radius: Scalar, length: Scalar) -> Self {
786 SharedShape::capsule(
787 (Vector::Y * length * 0.5).into(),
788 (Vector::NEG_Y * length * 0.5).into(),
789 radius,
790 )
791 .into()
792 }
793
794 pub fn capsule_endpoints(radius: Scalar, a: Vector, b: Vector) -> Self {
796 SharedShape::capsule(a.into(), b.into(), radius).into()
797 }
798
799 pub fn half_space(outward_normal: Vector) -> Self {
802 SharedShape::halfspace(nalgebra::Unit::new_normalize(outward_normal.into())).into()
803 }
804
805 pub fn segment(a: Vector, b: Vector) -> Self {
807 SharedShape::segment(a.into(), b.into()).into()
808 }
809
810 #[cfg(feature = "2d")]
818 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
819 let mut triangle = parry::shape::Triangle::new(a.into(), b.into(), c.into());
820
821 if triangle.orientation(1e-8) == parry::shape::TriangleOrientation::Clockwise {
823 triangle.reverse();
824 }
825
826 SharedShape::new(triangle).into()
827 }
828
829 #[cfg(feature = "2d")]
836 pub fn triangle_unchecked(a: Vector, b: Vector, c: Vector) -> Self {
837 SharedShape::triangle(a.into(), b.into(), c.into()).into()
838 }
839
840 #[cfg(feature = "3d")]
842 pub fn triangle(a: Vector, b: Vector, c: Vector) -> Self {
843 SharedShape::triangle(a.into(), b.into(), c.into()).into()
844 }
845
846 #[cfg(feature = "2d")]
848 pub fn regular_polygon(circumradius: f32, sides: u32) -> Self {
849 RegularPolygon::new(circumradius, sides).collider()
850 }
851
852 pub fn polyline(vertices: Vec<Vector>, indices: Option<Vec<[u32; 2]>>) -> Self {
854 let vertices = vertices.into_iter().map(|v| v.into()).collect();
855 SharedShape::polyline(vertices, indices).into()
856 }
857
858 pub fn trimesh(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
865 let vertices = vertices.into_iter().map(|v| v.into()).collect();
866 SharedShape::trimesh(vertices, indices).into()
867 }
868
869 pub fn trimesh_with_config(
877 vertices: Vec<Vector>,
878 indices: Vec<[u32; 3]>,
879 flags: TrimeshFlags,
880 ) -> Self {
881 let vertices = vertices.into_iter().map(|v| v.into()).collect();
882 SharedShape::trimesh_with_flags(vertices, indices, flags.into()).into()
883 }
884
885 #[cfg(feature = "2d")]
888 pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 2]>) -> Self {
889 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
890 SharedShape::convex_decomposition(&vertices, &indices).into()
891 }
892
893 #[cfg(feature = "3d")]
896 pub fn convex_decomposition(vertices: Vec<Vector>, indices: Vec<[u32; 3]>) -> Self {
897 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
898 SharedShape::convex_decomposition(&vertices, &indices).into()
899 }
900
901 #[cfg(feature = "2d")]
905 pub fn convex_decomposition_with_config(
906 vertices: Vec<Vector>,
907 indices: Vec<[u32; 2]>,
908 params: &VhacdParameters,
909 ) -> Self {
910 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
911 SharedShape::convex_decomposition_with_params(&vertices, &indices, ¶ms.clone().into())
912 .into()
913 }
914
915 #[cfg(feature = "3d")]
919 pub fn convex_decomposition_with_config(
920 vertices: Vec<Vector>,
921 indices: Vec<[u32; 3]>,
922 params: VhacdParameters,
923 ) -> Self {
924 let vertices = vertices.iter().map(|v| (*v).into()).collect::<Vec<_>>();
925 SharedShape::convex_decomposition_with_params(&vertices, &indices, ¶ms.clone().into())
926 .into()
927 }
928
929 #[cfg(feature = "2d")]
932 pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
933 let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
934 SharedShape::convex_hull(&points).map(Into::into)
935 }
936
937 #[cfg(feature = "3d")]
940 pub fn convex_hull(points: Vec<Vector>) -> Option<Self> {
941 let points = points.iter().map(|v| (*v).into()).collect::<Vec<_>>();
942 SharedShape::convex_hull(&points).map(Into::into)
943 }
944
945 #[cfg(feature = "2d")]
952 pub fn heightfield(heights: Vec<Scalar>, scale: Vector) -> Self {
953 SharedShape::heightfield(heights.into(), scale.into()).into()
954 }
955
956 #[cfg(feature = "3d")]
966 pub fn heightfield(heights: Vec<Vec<Scalar>>, scale: Vector) -> Self {
967 let row_count = heights.len();
968 let column_count = heights[0].len();
969 let data: Vec<Scalar> = heights.into_iter().flatten().collect();
970
971 assert_eq!(
972 data.len(),
973 row_count * column_count,
974 "Each row in `heights` must have the same amount of points"
975 );
976
977 let heights = nalgebra::DMatrix::from_vec(row_count, column_count, data);
978 SharedShape::heightfield(heights, scale.into()).into()
979 }
980
981 #[cfg(feature = "collider-from-mesh")]
1003 pub fn trimesh_from_mesh(mesh: &Mesh) -> Option<Self> {
1004 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1005 SharedShape::trimesh_with_flags(
1006 vertices,
1007 indices,
1008 TrimeshFlags::MERGE_DUPLICATE_VERTICES.into(),
1009 )
1010 .into()
1011 })
1012 }
1013
1014 #[cfg(feature = "collider-from-mesh")]
1037 pub fn trimesh_from_mesh_with_config(mesh: &Mesh, flags: TrimeshFlags) -> Option<Self> {
1038 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1039 SharedShape::trimesh_with_flags(vertices, indices, flags.into()).into()
1040 })
1041 }
1042
1043 #[cfg(feature = "collider-from-mesh")]
1060 pub fn convex_hull_from_mesh(mesh: &Mesh) -> Option<Self> {
1061 extract_mesh_vertices_indices(mesh)
1062 .and_then(|(vertices, _)| SharedShape::convex_hull(&vertices).map(|shape| shape.into()))
1063 }
1064
1065 #[cfg(feature = "collider-from-mesh")]
1082 pub fn convex_decomposition_from_mesh(mesh: &Mesh) -> Option<Self> {
1083 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1084 SharedShape::convex_decomposition(&vertices, &indices).into()
1085 })
1086 }
1087
1088 #[cfg(feature = "collider-from-mesh")]
1110 pub fn convex_decomposition_from_mesh_with_config(
1111 mesh: &Mesh,
1112 parameters: &VhacdParameters,
1113 ) -> Option<Self> {
1114 extract_mesh_vertices_indices(mesh).map(|(vertices, indices)| {
1115 SharedShape::convex_decomposition_with_params(
1116 &vertices,
1117 &indices,
1118 ¶meters.clone().into(),
1119 )
1120 .into()
1121 })
1122 }
1123
1124 #[cfg_attr(
1129 feature = "collider-from-mesh",
1130 doc = "Returns `None` in the following cases:
1131- The given [`ColliderConstructor`] requires a mesh, but none was provided.
1132- Creating the collider from the given [`ColliderConstructor`] failed."
1133 )]
1134 #[cfg_attr(
1135 not(feature = "collider-from-mesh"),
1136 doc = "Returns `None` if creating the collider from the given [`ColliderConstructor`] failed."
1137 )]
1138 pub fn try_from_constructor(
1139 collider_constructor: ColliderConstructor,
1140 #[cfg(feature = "collider-from-mesh")] mesh: Option<&Mesh>,
1141 ) -> Option<Self> {
1142 match collider_constructor {
1143 #[cfg(feature = "2d")]
1144 ColliderConstructor::Circle { radius } => Some(Self::circle(radius)),
1145 #[cfg(feature = "3d")]
1146 ColliderConstructor::Sphere { radius } => Some(Self::sphere(radius)),
1147 #[cfg(feature = "2d")]
1148 ColliderConstructor::Ellipse {
1149 half_width,
1150 half_height,
1151 } => Some(Self::ellipse(half_width, half_height)),
1152 #[cfg(feature = "2d")]
1153 ColliderConstructor::Rectangle { x_length, y_length } => {
1154 Some(Self::rectangle(x_length, y_length))
1155 }
1156 #[cfg(feature = "3d")]
1157 ColliderConstructor::Cuboid {
1158 x_length,
1159 y_length,
1160 z_length,
1161 } => Some(Self::cuboid(x_length, y_length, z_length)),
1162 #[cfg(feature = "2d")]
1163 ColliderConstructor::RoundRectangle {
1164 x_length,
1165 y_length,
1166 border_radius,
1167 } => Some(Self::round_rectangle(x_length, y_length, border_radius)),
1168 #[cfg(feature = "3d")]
1169 ColliderConstructor::RoundCuboid {
1170 x_length,
1171 y_length,
1172 z_length,
1173 border_radius,
1174 } => Some(Self::round_cuboid(
1175 x_length,
1176 y_length,
1177 z_length,
1178 border_radius,
1179 )),
1180 #[cfg(feature = "3d")]
1181 ColliderConstructor::Cylinder { radius, height } => {
1182 Some(Self::cylinder(radius, height))
1183 }
1184 #[cfg(feature = "3d")]
1185 ColliderConstructor::Cone { radius, height } => Some(Self::cone(radius, height)),
1186 ColliderConstructor::Capsule { radius, height } => Some(Self::capsule(radius, height)),
1187 ColliderConstructor::CapsuleEndpoints { radius, a, b } => {
1188 Some(Self::capsule_endpoints(radius, a, b))
1189 }
1190 ColliderConstructor::HalfSpace { outward_normal } => {
1191 Some(Self::half_space(outward_normal))
1192 }
1193 ColliderConstructor::Segment { a, b } => Some(Self::segment(a, b)),
1194 ColliderConstructor::Triangle { a, b, c } => Some(Self::triangle(a, b, c)),
1195 #[cfg(feature = "2d")]
1196 ColliderConstructor::RegularPolygon {
1197 circumradius,
1198 sides,
1199 } => Some(Self::regular_polygon(circumradius, sides)),
1200 ColliderConstructor::Polyline { vertices, indices } => {
1201 Some(Self::polyline(vertices, indices))
1202 }
1203 ColliderConstructor::Trimesh { vertices, indices } => {
1204 Some(Self::trimesh(vertices, indices))
1205 }
1206 ColliderConstructor::TrimeshWithConfig {
1207 vertices,
1208 indices,
1209 flags,
1210 } => Some(Self::trimesh_with_config(vertices, indices, flags)),
1211 #[cfg(feature = "2d")]
1212 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1213 Some(Self::convex_decomposition(vertices, indices))
1214 }
1215 #[cfg(feature = "3d")]
1216 ColliderConstructor::ConvexDecomposition { vertices, indices } => {
1217 Some(Self::convex_decomposition(vertices, indices))
1218 }
1219 #[cfg(feature = "2d")]
1220 ColliderConstructor::ConvexDecompositionWithConfig {
1221 vertices,
1222 indices,
1223 params,
1224 } => Some(Self::convex_decomposition_with_config(
1225 vertices, indices, ¶ms,
1226 )),
1227 #[cfg(feature = "3d")]
1228 ColliderConstructor::ConvexDecompositionWithConfig {
1229 vertices,
1230 indices,
1231 params,
1232 } => Some(Self::convex_decomposition_with_config(
1233 vertices, indices, params,
1234 )),
1235 #[cfg(feature = "2d")]
1236 ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
1237 #[cfg(feature = "3d")]
1238 ColliderConstructor::ConvexHull { points } => Self::convex_hull(points),
1239 #[cfg(feature = "2d")]
1240 ColliderConstructor::Heightfield { heights, scale } => {
1241 Some(Self::heightfield(heights, scale))
1242 }
1243 #[cfg(feature = "3d")]
1244 ColliderConstructor::Heightfield { heights, scale } => {
1245 Some(Self::heightfield(heights, scale))
1246 }
1247 #[cfg(feature = "collider-from-mesh")]
1248 ColliderConstructor::TrimeshFromMesh => Self::trimesh_from_mesh(mesh?),
1249 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1250 ColliderConstructor::TrimeshFromMeshWithConfig(flags) => {
1251 Self::trimesh_from_mesh_with_config(mesh?, flags)
1252 }
1253 #[cfg(feature = "collider-from-mesh")]
1254 ColliderConstructor::ConvexDecompositionFromMesh => {
1255 Self::convex_decomposition_from_mesh(mesh?)
1256 }
1257 #[cfg(all(feature = "collider-from-mesh", feature = "default-collider"))]
1258 ColliderConstructor::ConvexDecompositionFromMeshWithConfig(params) => {
1259 Self::convex_decomposition_from_mesh_with_config(mesh?, ¶ms)
1260 }
1261 #[cfg(feature = "collider-from-mesh")]
1262 ColliderConstructor::ConvexHullFromMesh => Self::convex_hull_from_mesh(mesh?),
1263 }
1264 }
1265}
1266
1267#[cfg(feature = "collider-from-mesh")]
1268type VerticesIndices = (Vec<nalgebra::Point3<Scalar>>, Vec<[u32; 3]>);
1269
1270#[cfg(feature = "collider-from-mesh")]
1271fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<VerticesIndices> {
1272 let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
1273 let indices = mesh.indices()?;
1274
1275 let vtx: Vec<_> = match vertices {
1276 VertexAttributeValues::Float32(vtx) => Some(
1277 vtx.chunks(3)
1278 .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
1279 .collect(),
1280 ),
1281 VertexAttributeValues::Float32x3(vtx) => Some(
1282 vtx.iter()
1283 .map(|v| [v[0] as Scalar, v[1] as Scalar, v[2] as Scalar].into())
1284 .collect(),
1285 ),
1286 _ => None,
1287 }?;
1288
1289 let idx = match indices {
1290 Indices::U16(idx) => idx
1291 .chunks_exact(3)
1292 .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
1293 .collect(),
1294 Indices::U32(idx) => idx.chunks_exact(3).map(|i| [i[0], i[1], i[2]]).collect(),
1295 };
1296
1297 Some((vtx, idx))
1298}
1299
1300fn scale_shape(
1301 shape: &SharedShape,
1302 scale: Vector,
1303 num_subdivisions: u32,
1304) -> Result<SharedShape, UnsupportedShape> {
1305 let scale = scale.abs();
1306 match shape.as_typed_shape() {
1307 TypedShape::Cuboid(s) => Ok(SharedShape::new(s.scaled(&scale.abs().into()))),
1308 TypedShape::RoundCuboid(s) => Ok(SharedShape::new(RoundShape {
1309 border_radius: s.border_radius,
1310 inner_shape: s.inner_shape.scaled(&scale.abs().into()),
1311 })),
1312 TypedShape::Capsule(c) => match c.scaled(&scale.abs().into(), num_subdivisions) {
1313 None => {
1314 log::error!("Failed to apply scale {} to Capsule shape.", scale);
1315 Ok(SharedShape::ball(0.0))
1316 }
1317 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1318 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1319 },
1320 TypedShape::Ball(b) => {
1321 #[cfg(feature = "2d")]
1322 {
1323 if scale.x == scale.y {
1324 Ok(SharedShape::ball(b.radius * scale.x.abs()))
1325 } else {
1326 Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1328 half_size: Vec2::splat(b.radius as f32) * scale.f32().abs(),
1329 })))
1330 }
1331 }
1332 #[cfg(feature = "3d")]
1333 match b.scaled(&scale.abs().into(), num_subdivisions) {
1334 None => {
1335 log::error!("Failed to apply scale {} to Ball shape.", scale);
1336 Ok(SharedShape::ball(0.0))
1337 }
1338 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1339 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1340 }
1341 }
1342 TypedShape::Segment(s) => Ok(SharedShape::new(s.scaled(&scale.into()))),
1343 TypedShape::Triangle(t) => Ok(SharedShape::new(t.scaled(&scale.into()))),
1344 TypedShape::RoundTriangle(t) => Ok(SharedShape::new(RoundShape {
1345 border_radius: t.border_radius,
1346 inner_shape: t.inner_shape.scaled(&scale.into()),
1347 })),
1348 TypedShape::TriMesh(t) => Ok(SharedShape::new(t.clone().scaled(&scale.into()))),
1349 TypedShape::Polyline(p) => Ok(SharedShape::new(p.clone().scaled(&scale.into()))),
1350 TypedShape::HalfSpace(h) => match h.scaled(&scale.into()) {
1351 None => {
1352 log::error!("Failed to apply scale {} to HalfSpace shape.", scale);
1353 Ok(SharedShape::ball(0.0))
1354 }
1355 Some(scaled) => Ok(SharedShape::new(scaled)),
1356 },
1357 TypedShape::HeightField(h) => Ok(SharedShape::new(h.clone().scaled(&scale.into()))),
1358 #[cfg(feature = "2d")]
1359 TypedShape::ConvexPolygon(cp) => match cp.clone().scaled(&scale.into()) {
1360 None => {
1361 log::error!("Failed to apply scale {} to ConvexPolygon shape.", scale);
1362 Ok(SharedShape::ball(0.0))
1363 }
1364 Some(scaled) => Ok(SharedShape::new(scaled)),
1365 },
1366 #[cfg(feature = "2d")]
1367 TypedShape::RoundConvexPolygon(cp) => match cp.inner_shape.clone().scaled(&scale.into()) {
1368 None => {
1369 log::error!(
1370 "Failed to apply scale {} to RoundConvexPolygon shape.",
1371 scale
1372 );
1373 Ok(SharedShape::ball(0.0))
1374 }
1375 Some(scaled) => Ok(SharedShape::new(RoundShape {
1376 border_radius: cp.border_radius,
1377 inner_shape: scaled,
1378 })),
1379 },
1380 #[cfg(feature = "3d")]
1381 TypedShape::ConvexPolyhedron(cp) => match cp.clone().scaled(&scale.into()) {
1382 None => {
1383 log::error!("Failed to apply scale {} to ConvexPolyhedron shape.", scale);
1384 Ok(SharedShape::ball(0.0))
1385 }
1386 Some(scaled) => Ok(SharedShape::new(scaled)),
1387 },
1388 #[cfg(feature = "3d")]
1389 TypedShape::RoundConvexPolyhedron(cp) => {
1390 match cp.clone().inner_shape.scaled(&scale.into()) {
1391 None => {
1392 log::error!(
1393 "Failed to apply scale {} to RoundConvexPolyhedron shape.",
1394 scale
1395 );
1396 Ok(SharedShape::ball(0.0))
1397 }
1398 Some(scaled) => Ok(SharedShape::new(RoundShape {
1399 border_radius: cp.border_radius,
1400 inner_shape: scaled,
1401 })),
1402 }
1403 }
1404 #[cfg(feature = "3d")]
1405 TypedShape::Cylinder(c) => match c.scaled(&scale.abs().into(), num_subdivisions) {
1406 None => {
1407 log::error!("Failed to apply scale {} to Cylinder shape.", scale);
1408 Ok(SharedShape::ball(0.0))
1409 }
1410 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1411 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1412 },
1413 #[cfg(feature = "3d")]
1414 TypedShape::RoundCylinder(c) => {
1415 match c.inner_shape.scaled(&scale.abs().into(), num_subdivisions) {
1416 None => {
1417 log::error!("Failed to apply scale {} to RoundCylinder shape.", scale);
1418 Ok(SharedShape::ball(0.0))
1419 }
1420 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1421 border_radius: c.border_radius,
1422 inner_shape: scaled,
1423 })),
1424 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1425 border_radius: c.border_radius,
1426 inner_shape: scaled,
1427 })),
1428 }
1429 }
1430 #[cfg(feature = "3d")]
1431 TypedShape::Cone(c) => match c.scaled(&scale.into(), num_subdivisions) {
1432 None => {
1433 log::error!("Failed to apply scale {} to Cone shape.", scale);
1434 Ok(SharedShape::ball(0.0))
1435 }
1436 Some(Either::Left(b)) => Ok(SharedShape::new(b)),
1437 Some(Either::Right(b)) => Ok(SharedShape::new(b)),
1438 },
1439 #[cfg(feature = "3d")]
1440 TypedShape::RoundCone(c) => match c.inner_shape.scaled(&scale.into(), num_subdivisions) {
1441 None => {
1442 log::error!("Failed to apply scale {} to RoundCone shape.", scale);
1443 Ok(SharedShape::ball(0.0))
1444 }
1445 Some(Either::Left(scaled)) => Ok(SharedShape::new(RoundShape {
1446 border_radius: c.border_radius,
1447 inner_shape: scaled,
1448 })),
1449 Some(Either::Right(scaled)) => Ok(SharedShape::new(RoundShape {
1450 border_radius: c.border_radius,
1451 inner_shape: scaled,
1452 })),
1453 },
1454 TypedShape::Compound(c) => {
1455 let mut scaled = Vec::with_capacity(c.shapes().len());
1456
1457 for (iso, shape) in c.shapes() {
1458 scaled.push((
1459 #[cfg(feature = "2d")]
1460 make_isometry(
1461 Vector::from(iso.translation) * scale,
1462 Rotation::radians(iso.rotation.angle()),
1463 ),
1464 #[cfg(feature = "3d")]
1465 make_isometry(
1466 Vector::from(iso.translation) * scale,
1467 Quaternion::from(iso.rotation),
1468 ),
1469 scale_shape(shape, scale, num_subdivisions)?,
1470 ));
1471 }
1472 Ok(SharedShape::compound(scaled))
1473 }
1474 TypedShape::Custom(_shape) => {
1475 #[cfg(feature = "2d")]
1476 {
1477 if let Some(ellipse) = _shape.as_shape::<EllipseColliderShape>() {
1478 return Ok(SharedShape::new(EllipseColliderShape(Ellipse {
1479 half_size: ellipse.half_size * scale.f32().abs(),
1480 })));
1481 }
1482 if let Some(polygon) = _shape.as_shape::<RegularPolygonColliderShape>() {
1483 if scale.x == scale.y {
1484 return Ok(SharedShape::new(RegularPolygonColliderShape(
1485 RegularPolygon::new(
1486 polygon.circumradius() * scale.x.abs() as f32,
1487 polygon.sides,
1488 ),
1489 )));
1490 } else {
1491 let vertices = polygon
1492 .vertices(0.0)
1493 .into_iter()
1494 .map(|v| v.adjust_precision().into())
1495 .collect::<Vec<_>>();
1496
1497 return scale_shape(
1498 &SharedShape::convex_hull(&vertices).unwrap(),
1499 scale,
1500 num_subdivisions,
1501 );
1502 }
1503 }
1504 }
1505 Err(parry::query::Unsupported)
1506 }
1507 }
1508}