1use crate::scene::node::constructor::NodeConstructor;
25use crate::{
26 core::{
27 algebra::{Isometry2, Translation2, UnitComplex, Vector2},
28 log::Log,
29 math::aabb::AxisAlignedBoundingBox,
30 pool::Handle,
31 reflect::prelude::*,
32 type_traits::prelude::*,
33 uuid::{uuid, Uuid},
34 uuid_provider,
35 variable::InheritableVariable,
36 visitor::prelude::*,
37 ImmutableString, TypeUuidProvider,
38 },
39 graph::SceneGraph,
40 scene::{
41 base::{Base, BaseBuilder},
42 collider::InteractionGroups,
43 dim2::{
44 physics::{ContactPair, IntersectionPair, PhysicsWorld},
45 rigidbody::RigidBody,
46 },
47 graph::{physics::CoefficientCombineRule, Graph},
48 node::{Node, NodeTrait, SyncContext},
49 Scene,
50 },
51};
52
53use fyrox_graph::constructor::ConstructorProvider;
54use rapier2d::geometry::ColliderHandle;
55use std::{
56 cell::Cell,
57 ops::{Deref, DerefMut},
58};
59use strum_macros::{AsRefStr, EnumString, VariantNames};
60
61#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
63pub struct BallShape {
64 #[reflect(min_value = 0.001, step = 0.05)]
66 pub radius: f32,
67}
68
69impl Default for BallShape {
70 fn default() -> Self {
71 Self { radius: 0.5 }
72 }
73}
74
75#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
77pub struct CuboidShape {
78 #[reflect(min_value = 0.001, step = 0.05)]
81 pub half_extents: Vector2<f32>,
82}
83
84impl Default for CuboidShape {
85 fn default() -> Self {
86 Self {
87 half_extents: Vector2::new(0.5, 0.5),
88 }
89 }
90}
91
92#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
94pub struct CapsuleShape {
95 pub begin: Vector2<f32>,
97 pub end: Vector2<f32>,
99 #[reflect(min_value = 0.001, step = 0.05)]
101 pub radius: f32,
102}
103
104impl Default for CapsuleShape {
105 fn default() -> Self {
107 Self {
108 begin: Default::default(),
109 end: Vector2::new(0.0, 1.0),
110 radius: 0.5,
111 }
112 }
113}
114
115#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
117pub struct SegmentShape {
118 pub begin: Vector2<f32>,
120 pub end: Vector2<f32>,
122}
123
124impl Default for SegmentShape {
125 fn default() -> Self {
126 Self {
127 begin: Default::default(),
128 end: Vector2::new(0.0, 1.0),
129 }
130 }
131}
132
133#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
135pub struct TriangleShape {
136 pub a: Vector2<f32>,
138 pub b: Vector2<f32>,
140 pub c: Vector2<f32>,
142}
143
144impl Default for TriangleShape {
145 fn default() -> Self {
146 Self {
147 a: Default::default(),
148 b: Vector2::new(1.0, 0.0),
149 c: Vector2::new(0.0, 1.0),
150 }
151 }
152}
153
154#[derive(Default, Clone, Copy, PartialEq, Hash, Debug, Visit, Reflect, Eq, TypeUuidProvider)]
160#[type_uuid(id = "1d451699-d76e-4774-87ea-dd3e2751cb39")]
161pub struct GeometrySource(pub Handle<Node>);
162
163#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
165pub struct TrimeshShape {
166 pub sources: Vec<GeometrySource>,
168}
169
170#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
172pub struct HeightfieldShape {
173 pub geometry_source: GeometrySource,
175}
176
177#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
179pub struct TileMapShape {
180 pub tile_map: GeometrySource,
182 pub layer_name: ImmutableString,
184}
185
186#[derive(Clone, Debug, Visit, Reflect, AsRefStr, PartialEq, EnumString, VariantNames)]
188pub enum ColliderShape {
189 Ball(BallShape),
191 Cuboid(CuboidShape),
193 Capsule(CapsuleShape),
195 Segment(SegmentShape),
197 Triangle(TriangleShape),
199 Trimesh(TrimeshShape),
201 Heightfield(HeightfieldShape),
203 TileMap(TileMapShape),
205}
206
207uuid_provider!(ColliderShape = "4615485f-f8db-4405-b4a5-437e74b3f5b8");
208
209impl Default for ColliderShape {
210 fn default() -> Self {
211 Self::Ball(Default::default())
212 }
213}
214
215impl ColliderShape {
216 pub fn ball(radius: f32) -> Self {
218 Self::Ball(BallShape { radius })
219 }
220
221 pub fn cuboid(hx: f32, hy: f32) -> Self {
223 Self::Cuboid(CuboidShape {
224 half_extents: Vector2::new(hx, hy),
225 })
226 }
227
228 pub fn capsule(begin: Vector2<f32>, end: Vector2<f32>, radius: f32) -> Self {
230 Self::Capsule(CapsuleShape { begin, end, radius })
231 }
232
233 pub fn capsule_x(half_height: f32, radius: f32) -> Self {
235 let p = Vector2::x() * half_height;
236 Self::capsule(-p, p, radius)
237 }
238
239 pub fn capsule_y(half_height: f32, radius: f32) -> Self {
241 let p = Vector2::y() * half_height;
242 Self::capsule(-p, p, radius)
243 }
244
245 pub fn segment(begin: Vector2<f32>, end: Vector2<f32>) -> Self {
247 Self::Segment(SegmentShape { begin, end })
248 }
249
250 pub fn triangle(a: Vector2<f32>, b: Vector2<f32>, c: Vector2<f32>) -> Self {
252 Self::Triangle(TriangleShape { a, b, c })
253 }
254
255 pub fn trimesh(geometry_sources: Vec<GeometrySource>) -> Self {
258 Self::Trimesh(TrimeshShape {
259 sources: geometry_sources,
260 })
261 }
262
263 pub fn heightfield(geometry_source: GeometrySource) -> Self {
265 Self::Heightfield(HeightfieldShape { geometry_source })
266 }
267}
268
269#[derive(Reflect, Visit, Debug, ComponentProvider)]
272#[reflect(derived_type = "Node")]
273pub struct Collider {
274 base: Base,
275
276 #[reflect(setter = "set_shape")]
277 pub(crate) shape: InheritableVariable<ColliderShape>,
278
279 #[reflect(min_value = 0.0, step = 0.05, setter = "set_friction")]
280 pub(crate) friction: InheritableVariable<f32>,
281
282 #[reflect(setter = "set_density")]
283 pub(crate) density: InheritableVariable<Option<f32>>,
284
285 #[reflect(min_value = 0.0, step = 0.05, setter = "set_restitution")]
286 pub(crate) restitution: InheritableVariable<f32>,
287
288 #[reflect(setter = "set_is_sensor")]
289 pub(crate) is_sensor: InheritableVariable<bool>,
290
291 #[reflect(setter = "set_collision_groups")]
292 pub(crate) collision_groups: InheritableVariable<InteractionGroups>,
293
294 #[reflect(setter = "set_solver_groups")]
295 pub(crate) solver_groups: InheritableVariable<InteractionGroups>,
296
297 #[reflect(setter = "set_friction_combine_rule")]
298 pub(crate) friction_combine_rule: InheritableVariable<CoefficientCombineRule>,
299
300 #[reflect(setter = "set_restitution_combine_rule")]
301 pub(crate) restitution_combine_rule: InheritableVariable<CoefficientCombineRule>,
302
303 #[visit(skip)]
304 #[reflect(hidden)]
305 pub(crate) native: Cell<ColliderHandle>,
306}
307
308impl Default for Collider {
309 fn default() -> Self {
310 Self {
311 base: Default::default(),
312 shape: Default::default(),
313 friction: Default::default(),
314 density: Default::default(),
315 restitution: Default::default(),
316 is_sensor: Default::default(),
317 collision_groups: Default::default(),
318 solver_groups: Default::default(),
319 friction_combine_rule: Default::default(),
320 restitution_combine_rule: Default::default(),
321 native: Cell::new(ColliderHandle::invalid()),
322 }
323 }
324}
325
326impl Deref for Collider {
327 type Target = Base;
328
329 fn deref(&self) -> &Self::Target {
330 &self.base
331 }
332}
333
334impl DerefMut for Collider {
335 fn deref_mut(&mut self) -> &mut Self::Target {
336 &mut self.base
337 }
338}
339
340impl Clone for Collider {
341 fn clone(&self) -> Self {
342 Self {
343 base: self.base.clone(),
344 shape: self.shape.clone(),
345 friction: self.friction.clone(),
346 density: self.density.clone(),
347 restitution: self.restitution.clone(),
348 is_sensor: self.is_sensor.clone(),
349 collision_groups: self.collision_groups.clone(),
350 solver_groups: self.solver_groups.clone(),
351 friction_combine_rule: self.friction_combine_rule.clone(),
352 restitution_combine_rule: self.restitution_combine_rule.clone(),
353 native: Cell::new(ColliderHandle::invalid()),
355 }
356 }
357}
358
359impl TypeUuidProvider for Collider {
360 fn type_uuid() -> Uuid {
361 uuid!("2b1659ea-a116-4224-bcd4-7931e3ae3b40")
362 }
363}
364
365impl Collider {
366 pub fn set_shape(&mut self, shape: ColliderShape) -> ColliderShape {
374 self.shape.set_value_and_mark_modified(shape)
375 }
376
377 pub fn shape(&self) -> &ColliderShape {
379 &self.shape
380 }
381
382 pub fn shape_value(&self) -> ColliderShape {
384 (*self.shape).clone()
385 }
386
387 pub fn shape_mut(&mut self) -> &mut ColliderShape {
395 self.shape.get_value_mut_and_mark_modified()
396 }
397
398 pub fn set_restitution(&mut self, restitution: f32) -> f32 {
408 self.restitution.set_value_and_mark_modified(restitution)
409 }
410
411 pub fn restitution(&self) -> f32 {
413 *self.restitution
414 }
415
416 pub fn set_density(&mut self, density: Option<f32>) -> Option<f32> {
431 self.density.set_value_and_mark_modified(density)
432 }
433
434 pub fn density(&self) -> Option<f32> {
436 *self.density
437 }
438
439 pub fn set_friction(&mut self, friction: f32) -> f32 {
449 self.friction.set_value_and_mark_modified(friction)
450 }
451
452 pub fn friction(&self) -> f32 {
454 *self.friction
455 }
456
457 pub fn set_collision_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
465 self.collision_groups.set_value_and_mark_modified(groups)
466 }
467
468 pub fn collision_groups(&self) -> InteractionGroups {
470 *self.collision_groups
471 }
472
473 pub fn set_solver_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
481 self.solver_groups.set_value_and_mark_modified(groups)
482 }
483
484 pub fn solver_groups(&self) -> InteractionGroups {
486 *self.solver_groups
487 }
488
489 pub fn set_is_sensor(&mut self, is_sensor: bool) -> bool {
498 self.is_sensor.set_value_and_mark_modified(is_sensor)
499 }
500
501 pub fn is_sensor(&self) -> bool {
503 *self.is_sensor
504 }
505
506 pub fn set_friction_combine_rule(
514 &mut self,
515 rule: CoefficientCombineRule,
516 ) -> CoefficientCombineRule {
517 self.friction_combine_rule.set_value_and_mark_modified(rule)
518 }
519
520 pub fn friction_combine_rule(&self) -> CoefficientCombineRule {
522 *self.friction_combine_rule
523 }
524
525 pub fn set_restitution_combine_rule(
533 &mut self,
534 rule: CoefficientCombineRule,
535 ) -> CoefficientCombineRule {
536 self.restitution_combine_rule
537 .set_value_and_mark_modified(rule)
538 }
539
540 pub fn restitution_combine_rule(&self) -> CoefficientCombineRule {
542 *self.restitution_combine_rule
543 }
544
545 pub fn contacts<'a>(
563 &self,
564 physics: &'a PhysicsWorld,
565 ) -> impl Iterator<Item = ContactPair> + 'a {
566 physics.contacts_with(self.native.get())
567 }
568
569 pub fn active_contacts<'a>(
584 &self,
585 physics: &'a PhysicsWorld,
586 ) -> impl Iterator<Item = ContactPair> + 'a {
587 self.contacts(physics)
588 .filter(|pair| pair.has_any_active_contact)
589 }
590
591 pub fn intersects<'a>(
605 &self,
606 physics: &'a PhysicsWorld,
607 ) -> impl Iterator<Item = IntersectionPair> + 'a {
608 physics.intersections_with(self.native.get())
609 }
610
611 pub fn active_intersects<'a>(
615 &self,
616 physics: &'a PhysicsWorld,
617 ) -> impl Iterator<Item = Handle<Self>> + 'a {
618 let self_handle = self.handle().to_variant();
619 self.intersects(physics)
620 .filter(|pair| pair.has_any_active_contact)
621 .map(move |pair| pair.other(self_handle))
622 }
623
624 pub(crate) fn needs_sync_model(&self) -> bool {
625 self.shape.need_sync()
626 || self.friction.need_sync()
627 || self.density.need_sync()
628 || self.restitution.need_sync()
629 || self.is_sensor.need_sync()
630 || self.collision_groups.need_sync()
631 || self.solver_groups.need_sync()
632 || self.friction_combine_rule.need_sync()
633 || self.restitution_combine_rule.need_sync()
634 }
635}
636
637impl ConstructorProvider<Node, Graph> for Collider {
638 fn constructor() -> NodeConstructor {
639 NodeConstructor::new::<Self>()
640 .with_variant("Collider", |_| {
641 ColliderBuilder::new(BaseBuilder::new().with_name("Collider 2D"))
642 .with_shape(ColliderShape::Cuboid(Default::default()))
643 .build_node()
644 .into()
645 })
646 .with_group("Physics 2D")
647 }
648}
649
650impl NodeTrait for Collider {
651 fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
652 self.base.local_bounding_box()
653 }
654
655 fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
656 self.base.world_bounding_box()
657 }
658
659 fn id(&self) -> Uuid {
660 Self::type_uuid()
661 }
662
663 fn on_removed_from_graph(&mut self, graph: &mut Graph) {
664 graph.physics2d.remove_collider(self.native.get());
665 self.native.set(ColliderHandle::invalid());
666
667 Log::info(format!(
668 "Native collider 2D was removed for node: {}",
669 self.name()
670 ));
671 }
672
673 fn on_unlink(&mut self, graph: &mut Graph) {
674 if graph.physics2d.remove_collider(self.native.get()) {
675 self.native.set(ColliderHandle::invalid());
677 }
678 }
679
680 fn on_local_transform_changed(&self, context: &mut SyncContext) {
681 if self.native.get() != ColliderHandle::invalid() {
682 if let Some(native) = context.physics2d.colliders.get_mut(self.native.get()) {
683 native.set_position_wrt_parent(Isometry2 {
684 rotation: UnitComplex::from_angle(
685 self.local_transform().rotation().euler_angles().2,
686 ),
687 translation: Translation2 {
688 vector: self.local_transform().position().xy(),
689 },
690 });
691 }
692 }
693 }
694
695 fn sync_native(&self, self_handle: Handle<Node>, context: &mut SyncContext) {
696 context
697 .physics2d
698 .sync_to_collider_node(context.nodes, self_handle, self);
699 }
700
701 fn validate(&self, scene: &Scene) -> Result<(), String> {
702 let mut message = String::new();
703
704 if scene
705 .graph
706 .try_get_of_type::<RigidBody>(self.parent())
707 .is_err()
708 {
709 message += "2D Collider must be a direct child of a 3D Rigid Body node, \
710 otherwise it will not have any effect!";
711 }
712
713 match &*self.shape {
714 ColliderShape::Trimesh(trimesh) => {
715 for source in trimesh.sources.iter() {
716 if !scene.graph.is_valid_handle(source.0) {
717 message += &format!("Trimesh shape data handle {} is invalid!", source.0);
718 }
719 }
720 }
721 ColliderShape::Heightfield(heightfield) => {
722 if !scene.graph.is_valid_handle(heightfield.geometry_source.0) {
723 message += &format!(
724 "Heightfield shape data handle {} is invalid!",
725 heightfield.geometry_source.0
726 );
727 }
728 }
729 ColliderShape::TileMap(tile_map) => {
730 if !scene.graph.is_valid_handle(tile_map.tile_map.0) {
731 message += &format!(
732 "Tile map shape data handle {} is invalid!",
733 tile_map.tile_map.0
734 );
735 }
736 }
737 _ => (),
738 }
739
740 if message.is_empty() {
741 Ok(())
742 } else {
743 Err(message)
744 }
745 }
746}
747
748pub struct ColliderBuilder {
750 base_builder: BaseBuilder,
751 shape: ColliderShape,
752 friction: f32,
753 density: Option<f32>,
754 restitution: f32,
755 is_sensor: bool,
756 collision_groups: InteractionGroups,
757 solver_groups: InteractionGroups,
758 friction_combine_rule: CoefficientCombineRule,
759 restitution_combine_rule: CoefficientCombineRule,
760}
761
762impl ColliderBuilder {
763 pub fn new(base_builder: BaseBuilder) -> Self {
765 Self {
766 base_builder,
767 shape: Default::default(),
768 friction: 0.0,
769 density: None,
770 restitution: 0.0,
771 is_sensor: false,
772 collision_groups: Default::default(),
773 solver_groups: Default::default(),
774 friction_combine_rule: Default::default(),
775 restitution_combine_rule: Default::default(),
776 }
777 }
778
779 pub fn with_shape(mut self, shape: ColliderShape) -> Self {
781 self.shape = shape;
782 self
783 }
784
785 pub fn with_density(mut self, density: Option<f32>) -> Self {
787 self.density = density;
788 self
789 }
790
791 pub fn with_restitution(mut self, restitution: f32) -> Self {
793 self.restitution = restitution;
794 self
795 }
796
797 pub fn with_friction(mut self, friction: f32) -> Self {
799 self.friction = friction;
800 self
801 }
802
803 pub fn with_sensor(mut self, sensor: bool) -> Self {
805 self.is_sensor = sensor;
806 self
807 }
808
809 pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
811 self.solver_groups = solver_groups;
812 self
813 }
814
815 pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
817 self.collision_groups = collision_groups;
818 self
819 }
820
821 pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
823 self.friction_combine_rule = rule;
824 self
825 }
826
827 pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
829 self.restitution_combine_rule = rule;
830 self
831 }
832
833 pub fn build_collider(self) -> Collider {
835 Collider {
836 base: self.base_builder.build_base(),
837 shape: self.shape.into(),
838 friction: self.friction.into(),
839 density: self.density.into(),
840 restitution: self.restitution.into(),
841 is_sensor: self.is_sensor.into(),
842 collision_groups: self.collision_groups.into(),
843 solver_groups: self.solver_groups.into(),
844 friction_combine_rule: self.friction_combine_rule.into(),
845 restitution_combine_rule: self.restitution_combine_rule.into(),
846 native: Cell::new(ColliderHandle::invalid()),
847 }
848 }
849
850 pub fn build_node(self) -> Node {
852 Node::new(self.build_collider())
853 }
854
855 pub fn build(self, graph: &mut Graph) -> Handle<Collider> {
857 graph.add_node(self.build_node()).to_variant()
858 }
859}
860
861#[cfg(test)]
862mod test {
863
864 use crate::core::algebra::Vector2;
865 use crate::scene::{
866 base::BaseBuilder,
867 dim2::{
868 collider::{ColliderBuilder, ColliderShape},
869 rigidbody::RigidBodyBuilder,
870 },
871 graph::Graph,
872 rigidbody::RigidBodyType,
873 };
874
875 #[test]
876 fn test_collider_2d_intersect() {
877 let mut graph = Graph::new();
878
879 let mut create_rigid_body = |is_sensor| {
880 let cube_half_size = 0.5;
881 let collider_sensor = ColliderBuilder::new(BaseBuilder::new())
882 .with_shape(ColliderShape::cuboid(cube_half_size, cube_half_size))
883 .with_sensor(is_sensor)
884 .build(&mut graph);
885
886 RigidBodyBuilder::new(BaseBuilder::new().with_child(collider_sensor))
887 .with_body_type(RigidBodyType::Static)
888 .build(&mut graph);
889
890 collider_sensor
891 };
892
893 let collider_sensor = create_rigid_body(true);
894 let collider_non_sensor = create_rigid_body(false);
895
896 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
898 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
899
900 assert_eq!(0, graph[collider_sensor].contacts(&graph.physics2d).count());
902 assert_eq!(
903 0,
904 graph[collider_non_sensor]
905 .contacts(&graph.physics2d)
906 .count()
907 );
908
909 assert_eq!(
911 1,
912 graph[collider_sensor].intersects(&graph.physics2d).count()
913 );
914 assert_eq!(
915 1,
916 graph[collider_non_sensor]
917 .intersects(&graph.physics2d)
918 .count()
919 );
920 }
921}