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