1use core::iter::once;
2
3use crate::prelude::*;
4use bevy::{platform::collections::HashMap, prelude::*};
5use itertools::Either;
6
7#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
35#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
36#[cfg_attr(
42 feature = "2d",
43 doc = " // Spawn the scene and automatically generate circle colliders"
44)]
45#[cfg_attr(
46 feature = "3d",
47 doc = " // Spawn the scene and automatically generate triangle mesh colliders"
48)]
49#[cfg_attr(
52 feature = "2d",
53 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::Circle { radius: 2.0 }),"
54)]
55#[cfg_attr(
56 feature = "3d",
57 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh),"
58)]
59#[cfg_attr(
65 feature = "2d",
66 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::Circle { radius: 2.0 })
67 .with_constructor_for_name(\"Tree\", ColliderConstructor::Rectangle { x_length: 1.0, y_length: 2.0 })"
68)]
69#[cfg_attr(
70 feature = "3d",
71 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh)
72 .with_constructor_for_name(\"Tree\", ColliderConstructor::ConvexHullFromMesh)"
73)]
74#[cfg_attr(
83 feature = "2d",
84 doc = " .with_constructor_for_name(\"Tree\", ColliderConstructor::Circle { radius: 2.0 }),"
85)]
86#[cfg_attr(
87 feature = "3d",
88 doc = " .with_constructor_for_name(\"Tree\", ColliderConstructor::ConvexHullFromMesh),"
89)]
90#[cfg_attr(
96 feature = "2d",
97 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::Circle { radius: 2.0 })
98 .without_constructor_for_name(\"Tree\"),"
99)]
100#[cfg_attr(
101 feature = "3d",
102 doc = " ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMeshWithConfig(
103 TrimeshFlags::MERGE_DUPLICATE_VERTICES
104 ))
105 .without_constructor_for_name(\"Tree\"),"
106)]
107#[derive(Component, Clone, Debug, Default, PartialEq, Reflect)]
111#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
112#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
113#[reflect(Component, Debug, PartialEq, Default)]
114pub struct ColliderConstructorHierarchy {
115 pub default_constructor: Option<ColliderConstructor>,
118 pub default_layers: CollisionLayers,
122 pub default_density: ColliderDensity,
126 pub config: HashMap<String, Option<ColliderConstructorHierarchyConfig>>,
132}
133
134#[derive(EntityEvent, Clone, Copy, Debug, PartialEq)]
139#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
140pub struct ColliderConstructorReady {
141 pub entity: Entity,
143}
144
145#[derive(EntityEvent, Clone, Copy, Debug, PartialEq)]
151#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
152pub struct ColliderConstructorHierarchyReady {
153 pub entity: Entity,
155}
156
157impl ColliderConstructorHierarchy {
158 pub fn new(default_constructor: impl Into<Option<ColliderConstructor>>) -> Self {
167 Self {
168 default_constructor: default_constructor.into(),
169 default_layers: CollisionLayers::default(),
170 default_density: ColliderDensity(1.0),
171 config: default(),
172 }
173 }
174
175 pub fn with_default_layers(mut self, layers: CollisionLayers) -> Self {
177 self.default_layers = layers;
178 self
179 }
180
181 pub fn with_default_density(mut self, density: impl Into<ColliderDensity>) -> Self {
183 self.default_density = density.into();
184 self
185 }
186
187 pub fn with_constructor_for_name(
189 mut self,
190 name: &str,
191 constructor: ColliderConstructor,
192 ) -> Self {
193 if let Some(Some(data)) = self.config.get_mut(name) {
194 data.constructor = Some(constructor);
195 } else {
196 self.config.insert(
197 name.to_string(),
198 Some(ColliderConstructorHierarchyConfig {
199 constructor: Some(constructor),
200 ..default()
201 }),
202 );
203 }
204 self
205 }
206
207 pub fn with_layers_for_name(self, name: &str, layers: CollisionLayers) -> Self {
209 self.with_config_for_name(name, |config| config.layers = Some(layers))
210 }
211
212 pub fn with_density_for_name(self, name: &str, density: impl Into<ColliderDensity>) -> Self {
214 let density = density.into();
215 self.with_config_for_name(name, |config| config.density = Some(density))
216 }
217
218 pub fn without_constructor_for_name(mut self, name: &str) -> Self {
221 self.config.insert(name.to_string(), None);
222 self
223 }
224
225 fn with_config_for_name(
226 mut self,
227 name: &str,
228 mut mutate_config: impl FnMut(&mut ColliderConstructorHierarchyConfig),
229 ) -> Self {
230 if let Some(Some(config)) = self.config.get_mut(name) {
231 mutate_config(config);
232 } else {
233 let mut config = ColliderConstructorHierarchyConfig::default();
234 mutate_config(&mut config);
235 self.config.insert(name.to_string(), Some(config));
236 }
237 self
238 }
239}
240
241#[derive(Clone, Debug, Default, PartialEq, Reflect)]
243#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
244#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
245#[reflect(Debug, Default, PartialEq)]
246pub struct ColliderConstructorHierarchyConfig {
247 pub constructor: Option<ColliderConstructor>,
251 pub layers: Option<CollisionLayers>,
255 pub density: Option<ColliderDensity>,
259}
260
261#[cfg_attr(feature = "2d", doc = "use avian2d::prelude::*;")]
281#[cfg_attr(feature = "3d", doc = "use avian3d::prelude::*;")]
282#[cfg_attr(feature = "2d", doc = " // Spawn a circle with radius 2")]
286#[cfg_attr(
287 feature = "3d",
288 doc = " // Spawn a cube with a convex hull collider generated from the mesh"
289)]
290#[cfg_attr(
292 feature = "2d",
293 doc = " ColliderConstructor::Circle { radius: 2.0 },"
294)]
295#[cfg_attr(
296 feature = "3d",
297 doc = " ColliderConstructor::ConvexHullFromMesh,"
298)]
299#[derive(Clone, Debug, PartialEq, Reflect, Component)]
304#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
305#[cfg_attr(feature = "serialize", reflect(Serialize, Deserialize))]
306#[cfg_attr(feature = "collider-from-mesh", derive(Default))]
307#[cfg_attr(feature = "collider-from-mesh", reflect(Default))]
308#[reflect(Debug, Component, PartialEq)]
309#[reflect(no_field_bounds)]
310#[non_exhaustive]
311#[allow(missing_docs)]
312pub enum ColliderConstructor {
313 #[cfg(feature = "2d")]
315 Circle { radius: Scalar },
316 #[cfg(feature = "3d")]
318 Sphere { radius: Scalar },
319 #[cfg(feature = "2d")]
321 Ellipse {
322 half_width: Scalar,
323 half_height: Scalar,
324 },
325 #[cfg(feature = "2d")]
327 Rectangle { x_length: Scalar, y_length: Scalar },
328 #[cfg(feature = "3d")]
330 Cuboid {
331 x_length: Scalar,
332 y_length: Scalar,
333 z_length: Scalar,
334 },
335 #[cfg(feature = "2d")]
337 RoundRectangle {
338 x_length: Scalar,
339 y_length: Scalar,
340 border_radius: Scalar,
341 },
342 #[cfg(feature = "3d")]
344 RoundCuboid {
345 x_length: Scalar,
346 y_length: Scalar,
347 z_length: Scalar,
348 border_radius: Scalar,
349 },
350 #[cfg(feature = "3d")]
352 Cylinder { radius: Scalar, height: Scalar },
353 #[cfg(feature = "3d")]
355 Cone { radius: Scalar, height: Scalar },
356 Capsule { radius: Scalar, height: Scalar },
358 CapsuleEndpoints {
360 radius: Scalar,
361 a: Vector,
362 b: Vector,
363 },
364 HalfSpace { outward_normal: Vector },
366 Segment { a: Vector, b: Vector },
368 Triangle { a: Vector, b: Vector, c: Vector },
370 #[cfg(feature = "2d")]
372 RegularPolygon { circumradius: f32, sides: u32 },
373 Polyline {
375 vertices: Vec<Vector>,
376 indices: Option<Vec<[u32; 2]>>,
377 },
378 Trimesh {
380 vertices: Vec<Vector>,
381 indices: Vec<[u32; 3]>,
382 },
383 TrimeshWithConfig {
385 vertices: Vec<Vector>,
386 indices: Vec<[u32; 3]>,
387 flags: TrimeshFlags,
388 },
389 #[cfg(feature = "2d")]
391 ConvexDecomposition {
392 vertices: Vec<Vector>,
393 indices: Vec<[u32; 2]>,
394 },
395 #[cfg(feature = "3d")]
397 ConvexDecomposition {
398 vertices: Vec<Vector>,
399 indices: Vec<[u32; 3]>,
400 },
401 #[cfg(feature = "2d")]
403 ConvexDecompositionWithConfig {
404 vertices: Vec<Vector>,
405 indices: Vec<[u32; 2]>,
406 params: VhacdParameters,
407 },
408 #[cfg(feature = "3d")]
410 ConvexDecompositionWithConfig {
411 vertices: Vec<Vector>,
412 indices: Vec<[u32; 3]>,
413 params: VhacdParameters,
414 },
415 #[cfg(feature = "2d")]
417 ConvexHull { points: Vec<Vector> },
418 #[cfg(feature = "3d")]
420 ConvexHull { points: Vec<Vector> },
421 #[cfg(feature = "2d")]
423 ConvexPolyline { points: Vec<Vector> },
424 Voxels {
426 voxel_size: Vector,
427 grid_coordinates: Vec<IVector>,
428 },
429 #[cfg(feature = "2d")]
431 VoxelizedPolyline {
432 vertices: Vec<Vector>,
433 indices: Vec<[u32; 2]>,
434 voxel_size: Scalar,
435 fill_mode: FillMode,
436 },
437 #[cfg(feature = "3d")]
439 VoxelizedTrimesh {
440 vertices: Vec<Vector>,
441 indices: Vec<[u32; 3]>,
442 voxel_size: Scalar,
443 fill_mode: FillMode,
444 },
445 #[cfg(feature = "2d")]
447 Heightfield { heights: Vec<Scalar>, scale: Vector },
448 #[cfg(feature = "3d")]
450 Heightfield {
451 heights: Vec<Vec<Scalar>>,
452 scale: Vector,
453 },
454 #[cfg(feature = "collider-from-mesh")]
456 #[default]
457 TrimeshFromMesh,
458 #[cfg(all(
460 feature = "3d",
461 feature = "collider-from-mesh",
462 feature = "default-collider"
463 ))]
464 TrimeshFromMeshWithConfig(TrimeshFlags),
465 #[cfg(feature = "collider-from-mesh")]
467 ConvexDecompositionFromMesh,
468 #[cfg(all(
470 feature = "3d",
471 feature = "collider-from-mesh",
472 feature = "default-collider"
473 ))]
474 ConvexDecompositionFromMeshWithConfig(VhacdParameters),
475 #[cfg(feature = "collider-from-mesh")]
477 ConvexHullFromMesh,
478 #[cfg(feature = "collider-from-mesh")]
480 VoxelizedTrimeshFromMesh {
481 voxel_size: Scalar,
482 fill_mode: FillMode,
483 },
484 Compound(Vec<(Position, Rotation, ColliderConstructor)>),
486}
487
488impl ColliderConstructor {
489 #[cfg(feature = "collider-from-mesh")]
491 pub fn requires_mesh(&self) -> bool {
492 matches!(
493 self,
494 Self::TrimeshFromMesh
495 | Self::TrimeshFromMeshWithConfig(_)
496 | Self::ConvexDecompositionFromMesh
497 | Self::ConvexDecompositionFromMeshWithConfig(_)
498 | Self::ConvexHullFromMesh
499 | Self::VoxelizedTrimeshFromMesh { .. }
500 )
501 }
502
503 pub fn compound<P, R>(shapes: Vec<(P, R, ColliderConstructor)>) -> Self
505 where
506 P: Into<Position>,
507 R: Into<Rotation>,
508 {
509 Self::Compound(
510 shapes
511 .into_iter()
512 .map(|(pos, rot, constructor)| (pos.into(), rot.into(), constructor))
513 .collect(),
514 )
515 }
516
517 pub(crate) fn flatten_compound_constructors(
518 constructors: Vec<(Position, Rotation, ColliderConstructor)>,
519 ) -> Vec<(Position, Rotation, ColliderConstructor)> {
520 constructors
521 .into_iter()
522 .flat_map(|(pos, rot, constructor)| match constructor {
523 ColliderConstructor::Compound(nested) => {
524 Either::Left(Self::flatten_compound_constructors(nested).into_iter().map(
525 move |(nested_pos, nested_rot, nested_constructor)| {
526 (
527 Position(pos.0 + rot * nested_pos.0),
528 rot * nested_rot,
529 nested_constructor,
530 )
531 },
532 ))
533 }
534 other => Either::Right(once((pos, rot, other))),
535 })
536 .collect()
537 }
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543 #[cfg(feature = "bevy_scene")]
544 use bevy::scene::ScenePlugin;
545 use bevy::{ecs::query::QueryData, mesh::MeshPlugin};
546
547 #[test]
548 fn collider_constructor_requires_no_mesh_on_primitive() {
549 let mut app = create_test_app();
550
551 let entity = app.world_mut().spawn(PRIMITIVE_COLLIDER.clone()).id();
552
553 app.update();
554
555 assert!(app.query_ok::<&Collider>(entity));
556 assert!(app.query_err::<&ColliderConstructor>(entity));
557 }
558
559 #[cfg(feature = "collider-from-mesh")]
560 #[test]
561 #[should_panic]
562 fn collider_constructor_requires_mesh_on_computed() {
563 let mut app = create_test_app();
564
565 app.world_mut().spawn(COMPUTED_COLLIDER.clone());
566
567 app.update();
568 }
569
570 #[cfg(feature = "collider-from-mesh")]
571 #[test]
572 fn collider_constructor_converts_mesh_on_computed() {
573 let mut app = create_test_app();
574
575 let mesh = app.add_mesh();
576 let entity = app
577 .world_mut()
578 .spawn((COMPUTED_COLLIDER.clone(), Mesh3d(mesh)))
579 .id();
580
581 app.update();
582
583 assert!(app.query_ok::<&Collider>(entity));
584 assert!(app.query_ok::<&Mesh3d>(entity));
585 assert!(app.query_err::<&ColliderConstructor>(entity));
586 }
587
588 #[test]
589 fn collider_constructor_hierarchy_does_nothing_on_self_with_primitive() {
590 let mut app = create_test_app();
591
592 let entity = app
593 .world_mut()
594 .spawn(ColliderConstructorHierarchy::new(
595 PRIMITIVE_COLLIDER.clone(),
596 ))
597 .id();
598
599 app.update();
600
601 assert!(app.query_err::<&ColliderConstructorHierarchy>(entity));
602 assert!(app.query_err::<&Collider>(entity));
603 }
604
605 #[cfg(feature = "collider-from-mesh")]
606 #[test]
607 fn collider_constructor_hierarchy_does_nothing_on_self_with_computed() {
608 let mut app = create_test_app();
609
610 let mesh = app.add_mesh();
611 let entity = app
612 .world_mut()
613 .spawn((
614 ColliderConstructorHierarchy::new(COMPUTED_COLLIDER.clone()),
615 Mesh3d(mesh),
616 ))
617 .id();
618
619 app.update();
620
621 assert!(app.query_ok::<&Mesh3d>(entity));
622 assert!(app.query_err::<&ColliderConstructorHierarchy>(entity));
623 assert!(app.query_err::<&Collider>(entity));
624 }
625
626 #[cfg(feature = "collider-from-mesh")]
627 #[test]
628 fn collider_constructor_hierarchy_does_not_require_mesh_on_self_with_computed() {
629 let mut app = create_test_app();
630
631 let entity = app
632 .world_mut()
633 .spawn(ColliderConstructorHierarchy::new(COMPUTED_COLLIDER.clone()))
634 .id();
635
636 app.update();
637
638 assert!(app.query_err::<&Collider>(entity));
639 assert!(app.query_err::<&ColliderConstructorHierarchy>(entity));
640 }
641
642 #[test]
643 fn collider_constructor_hierarchy_inserts_primitive_colliders_on_all_descendants() {
644 let mut app = create_test_app();
645
646 let parent = app
653 .world_mut()
654 .spawn(ColliderConstructorHierarchy::new(
655 PRIMITIVE_COLLIDER.clone(),
656 ))
657 .id();
658 let child1 = app.world_mut().spawn(()).id();
659 let child2 = app.world_mut().spawn(()).id();
660 let child3 = app.world_mut().spawn(()).id();
661
662 app.world_mut()
663 .entity_mut(parent)
664 .add_children(&[child1, child2]);
665 app.world_mut().entity_mut(child2).add_children(&[child3]);
666
667 app.update();
668
669 assert!(app.query_err::<&ColliderConstructorHierarchy>(parent));
671 assert!(app.query_err::<&ColliderConstructorHierarchy>(child1));
672 assert!(app.query_err::<&ColliderConstructorHierarchy>(child2));
673 assert!(app.query_err::<&ColliderConstructorHierarchy>(child3));
674
675 assert!(app.query_err::<&Collider>(parent));
676 assert!(app.query_ok::<&Collider>(child1));
677 assert!(app.query_ok::<&Collider>(child2));
678 assert!(app.query_ok::<&Collider>(child3));
679 }
680
681 #[cfg(feature = "collider-from-mesh")]
682 #[test]
683 fn collider_constructor_hierarchy_inserts_computed_colliders_only_on_descendants_with_mesh() {
684 let mut app = create_test_app();
685 let mesh = Mesh3d(app.add_mesh());
686
687 let parent = app
699 .world_mut()
700 .spawn(ColliderConstructorHierarchy::new(COMPUTED_COLLIDER.clone()))
701 .id();
702 let child1 = app.world_mut().spawn(()).id();
703 let child2 = app.world_mut().spawn(()).id();
704 let child3 = app.world_mut().spawn(mesh.clone()).id();
705 let child4 = app.world_mut().spawn(mesh.clone()).id();
706 let child5 = app.world_mut().spawn(()).id();
707 let child6 = app.world_mut().spawn(mesh.clone()).id();
708 let child7 = app.world_mut().spawn(mesh.clone()).id();
709 let child8 = app.world_mut().spawn(mesh.clone()).id();
710
711 app.world_mut()
712 .entity_mut(parent)
713 .add_children(&[child1, child2, child4, child6, child7]);
714 app.world_mut().entity_mut(child2).add_child(child3);
715 app.world_mut().entity_mut(child4).add_child(child5);
716 app.world_mut().entity_mut(child7).add_child(child8);
717
718 app.update();
719
720 assert!(app.query_err::<&ColliderConstructorHierarchy>(parent));
722 assert!(app.query_err::<&ColliderConstructorHierarchy>(child1));
723 assert!(app.query_err::<&ColliderConstructorHierarchy>(child2));
724 assert!(app.query_err::<&ColliderConstructorHierarchy>(child3));
725 assert!(app.query_err::<&ColliderConstructorHierarchy>(child4));
726 assert!(app.query_err::<&ColliderConstructorHierarchy>(child5));
727 assert!(app.query_err::<&ColliderConstructorHierarchy>(child6));
728 assert!(app.query_err::<&ColliderConstructorHierarchy>(child7));
729 assert!(app.query_err::<&ColliderConstructorHierarchy>(child8));
730
731 assert!(app.query_err::<&Collider>(parent));
732 assert!(app.query_err::<&Collider>(child1));
733 assert!(app.query_err::<&Collider>(child2));
734 assert!(app.query_ok::<&Collider>(child3));
735 assert!(app.query_ok::<&Collider>(child4));
736 assert!(app.query_err::<&Collider>(child5));
737 assert!(app.query_ok::<&Collider>(child6));
738 assert!(app.query_ok::<&Collider>(child7));
739 assert!(app.query_ok::<&Collider>(child8));
740 }
741
742 #[cfg(all(feature = "collider-from-mesh", feature = "bevy_scene"))]
743 #[test]
744 #[cfg_attr(
745 target_os = "linux",
746 ignore = "The plugin setup requires access to the GPU, which is not available in the linux test environment"
747 )]
748 fn collider_constructor_hierarchy_inserts_correct_configs_on_scene() {
749 use bevy::gltf::GltfMeshName;
750 use parry::shape::ShapeType;
751
752 #[derive(Resource)]
753 struct SceneReady;
754
755 let mut app = create_gltf_test_app();
756
757 app.add_observer(
758 |_trigger: On<bevy::scene::SceneInstanceReady>, mut commands: Commands| {
759 commands.insert_resource(SceneReady);
760 },
761 );
762
763 let scene_handle = app
764 .world_mut()
765 .resource_mut::<AssetServer>()
766 .load("ferris.glb#Scene0");
767
768 let hierarchy = app
769 .world_mut()
770 .spawn((
771 SceneRoot(scene_handle),
772 ColliderConstructorHierarchy::new(ColliderConstructor::ConvexDecompositionFromMesh)
773 .with_constructor_for_name("armL_mesh.ferris_material", PRIMITIVE_COLLIDER)
775 .with_density_for_name("armL_mesh.ferris_material", 2.0)
776 .without_constructor_for_name("armR_mesh.ferris_material"),
778 RigidBody::Dynamic,
779 ))
780 .id();
781
782 let mut counter = 0;
783 loop {
784 if app.world().contains_resource::<SceneReady>() {
785 break;
786 }
787 app.update();
788 counter += 1;
789 if counter > 1000 {
790 panic!("SceneInstanceReady was never triggered");
791 }
792 }
793 app.update();
794
795 assert!(app.query_err::<&ColliderConstructorHierarchy>(hierarchy));
796 assert!(app.query_err::<&Collider>(hierarchy));
797
798 let densities: HashMap<_, _> = app
800 .world_mut()
801 .query::<(&GltfMeshName, &ColliderDensity)>()
802 .iter(app.world())
803 .map(|(name, density)| (name.to_string(), density.0))
804 .collect();
805
806 assert_eq!(densities["eyes_mesh"], 1.0);
807 assert_eq!(densities["armL_mesh"], 2.0);
808 assert!(densities.get("armR_mesh").is_none());
809
810 let colliders: HashMap<_, _> = app
812 .world_mut()
813 .query::<(&GltfMeshName, &Collider)>()
814 .iter(app.world())
815 .map(|(name, collider)| (name.to_string(), collider))
816 .collect();
817
818 assert_eq!(
819 colliders["eyes_mesh"].shape().shape_type(),
820 ShapeType::Compound
821 );
822 assert_eq!(
823 colliders["armL_mesh"].shape().shape_type(),
824 ShapeType::Capsule
825 );
826 assert!(colliders.get("armR_mesh").is_none());
827 }
828
829 const PRIMITIVE_COLLIDER: ColliderConstructor = ColliderConstructor::Capsule {
830 height: 1.0,
831 radius: 0.5,
832 };
833
834 #[cfg(feature = "collider-from-mesh")]
835 const COMPUTED_COLLIDER: ColliderConstructor = ColliderConstructor::TrimeshFromMesh;
836
837 fn create_test_app() -> App {
838 let mut app = App::new();
839 app.add_plugins((
840 MinimalPlugins,
841 AssetPlugin::default(),
842 #[cfg(feature = "bevy_scene")]
843 ScenePlugin,
844 MeshPlugin,
845 PhysicsPlugins::default(),
846 ));
847
848 app
849 }
850
851 #[cfg(all(feature = "collider-from-mesh", feature = "bevy_scene"))]
852 fn create_gltf_test_app() -> App {
853 use bevy::{diagnostic::DiagnosticsPlugin, winit::WinitPlugin};
854
855 let mut app = App::new();
859 app.add_plugins((
860 DefaultPlugins
861 .build()
862 .disable::<WinitPlugin>()
863 .disable::<DiagnosticsPlugin>(),
864 PhysicsPlugins::default(),
865 ));
866 app.finish();
867 app.cleanup();
868 app
869 }
870
871 trait AppExt {
872 fn query_ok<D: QueryData>(&mut self, entity: Entity) -> bool;
873 fn query_err<D: QueryData>(&mut self, entity: Entity) -> bool {
874 !self.query_ok::<D>(entity)
875 }
876
877 #[cfg(feature = "collider-from-mesh")]
878 fn add_mesh(&mut self) -> Handle<Mesh>;
879 }
880
881 impl AppExt for App {
882 fn query_ok<D: QueryData>(&mut self, entity: Entity) -> bool {
883 let mut query = self.world_mut().query::<D>();
884 let component = query.get(self.world(), entity);
885 component.is_ok()
886 }
887
888 #[cfg(feature = "collider-from-mesh")]
889 fn add_mesh(&mut self) -> Handle<Mesh> {
890 self.world_mut()
891 .get_resource_mut::<Assets<Mesh>>()
892 .unwrap()
893 .add(Mesh::from(Cuboid::default()))
894 }
895 }
896}