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(
684 Isometry2 {
685 rotation: UnitComplex::from_angle(
686 self.local_transform().rotation().euler_angles().2,
687 ),
688 translation: Translation2 {
689 vector: self.local_transform().position().xy(),
690 },
691 }
692 .into(),
693 );
694 }
695 }
696 }
697
698 fn sync_native(&self, self_handle: Handle<Node>, context: &mut SyncContext) {
699 context
700 .physics2d
701 .sync_to_collider_node(context.nodes, self_handle, self);
702 }
703
704 fn validate(&self, scene: &Scene) -> Result<(), String> {
705 let mut message = String::new();
706
707 if scene
708 .graph
709 .try_get_of_type::<RigidBody>(self.parent())
710 .is_err()
711 {
712 message += "2D Collider must be a direct child of a 3D Rigid Body node, \
713 otherwise it will not have any effect!";
714 }
715
716 match &*self.shape {
717 ColliderShape::Trimesh(trimesh) => {
718 for source in trimesh.sources.iter() {
719 if !scene.graph.is_valid_handle(source.0) {
720 message += &format!("Trimesh shape data handle {} is invalid!", source.0);
721 }
722 }
723 }
724 ColliderShape::Heightfield(heightfield) => {
725 if !scene.graph.is_valid_handle(heightfield.geometry_source.0) {
726 message += &format!(
727 "Heightfield shape data handle {} is invalid!",
728 heightfield.geometry_source.0
729 );
730 }
731 }
732 ColliderShape::TileMap(tile_map) => {
733 if !scene.graph.is_valid_handle(tile_map.tile_map.0) {
734 message += &format!(
735 "Tile map shape data handle {} is invalid!",
736 tile_map.tile_map.0
737 );
738 }
739 }
740 _ => (),
741 }
742
743 if message.is_empty() {
744 Ok(())
745 } else {
746 Err(message)
747 }
748 }
749}
750
751pub struct ColliderBuilder {
753 base_builder: BaseBuilder,
754 shape: ColliderShape,
755 friction: f32,
756 density: Option<f32>,
757 restitution: f32,
758 is_sensor: bool,
759 collision_groups: InteractionGroups,
760 solver_groups: InteractionGroups,
761 friction_combine_rule: CoefficientCombineRule,
762 restitution_combine_rule: CoefficientCombineRule,
763}
764
765impl ColliderBuilder {
766 pub fn new(base_builder: BaseBuilder) -> Self {
768 Self {
769 base_builder,
770 shape: Default::default(),
771 friction: 0.0,
772 density: None,
773 restitution: 0.0,
774 is_sensor: false,
775 collision_groups: Default::default(),
776 solver_groups: Default::default(),
777 friction_combine_rule: Default::default(),
778 restitution_combine_rule: Default::default(),
779 }
780 }
781
782 pub fn with_shape(mut self, shape: ColliderShape) -> Self {
784 self.shape = shape;
785 self
786 }
787
788 pub fn with_density(mut self, density: Option<f32>) -> Self {
790 self.density = density;
791 self
792 }
793
794 pub fn with_restitution(mut self, restitution: f32) -> Self {
796 self.restitution = restitution;
797 self
798 }
799
800 pub fn with_friction(mut self, friction: f32) -> Self {
802 self.friction = friction;
803 self
804 }
805
806 pub fn with_sensor(mut self, sensor: bool) -> Self {
808 self.is_sensor = sensor;
809 self
810 }
811
812 pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
814 self.solver_groups = solver_groups;
815 self
816 }
817
818 pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
820 self.collision_groups = collision_groups;
821 self
822 }
823
824 pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
826 self.friction_combine_rule = rule;
827 self
828 }
829
830 pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
832 self.restitution_combine_rule = rule;
833 self
834 }
835
836 pub fn build_collider(self) -> Collider {
838 Collider {
839 base: self.base_builder.build_base(),
840 shape: self.shape.into(),
841 friction: self.friction.into(),
842 density: self.density.into(),
843 restitution: self.restitution.into(),
844 is_sensor: self.is_sensor.into(),
845 collision_groups: self.collision_groups.into(),
846 solver_groups: self.solver_groups.into(),
847 friction_combine_rule: self.friction_combine_rule.into(),
848 restitution_combine_rule: self.restitution_combine_rule.into(),
849 native: Cell::new(ColliderHandle::invalid()),
850 }
851 }
852
853 pub fn build_node(self) -> Node {
855 Node::new(self.build_collider())
856 }
857
858 pub fn build(self, graph: &mut Graph) -> Handle<Collider> {
860 graph.add_node(self.build_node()).to_variant()
861 }
862}
863
864#[cfg(test)]
865mod test {
866
867 use crate::core::algebra::Vector2;
868 use crate::scene::{
869 base::BaseBuilder,
870 dim2::{
871 collider::{ColliderBuilder, ColliderShape},
872 rigidbody::RigidBodyBuilder,
873 },
874 graph::Graph,
875 rigidbody::RigidBodyType,
876 };
877
878 #[test]
879 fn test_collider_2d_intersect() {
880 let mut graph = Graph::new();
881
882 let mut create_rigid_body = |is_sensor| {
883 let cube_half_size = 0.5;
884 let collider_sensor = ColliderBuilder::new(BaseBuilder::new())
885 .with_shape(ColliderShape::cuboid(cube_half_size, cube_half_size))
886 .with_sensor(is_sensor)
887 .build(&mut graph);
888
889 RigidBodyBuilder::new(BaseBuilder::new().with_child(collider_sensor))
890 .with_body_type(RigidBodyType::Static)
891 .build(&mut graph);
892
893 collider_sensor
894 };
895
896 let collider_sensor = create_rigid_body(true);
897 let collider_non_sensor = create_rigid_body(false);
898
899 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
901 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
902
903 assert_eq!(0, graph[collider_sensor].contacts(&graph.physics2d).count());
905 assert_eq!(
906 0,
907 graph[collider_non_sensor]
908 .contacts(&graph.physics2d)
909 .count()
910 );
911
912 assert_eq!(
914 1,
915 graph[collider_sensor].intersects(&graph.physics2d).count()
916 );
917 assert_eq!(
918 1,
919 graph[collider_non_sensor]
920 .intersects(&graph.physics2d)
921 .count()
922 );
923 }
924}