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::{BaseSceneGraph, SceneGraphNode},
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};
52use fyrox_graph::constructor::ConstructorProvider;
53use rapier2d::geometry::ColliderHandle;
54use std::{
55 cell::Cell,
56 ops::{Deref, DerefMut},
57};
58use strum_macros::{AsRefStr, EnumString, VariantNames};
59
60#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
62pub struct BallShape {
63 #[reflect(min_value = 0.001, step = 0.05)]
65 pub radius: f32,
66}
67
68impl Default for BallShape {
69 fn default() -> Self {
70 Self { radius: 0.5 }
71 }
72}
73
74#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
76pub struct CuboidShape {
77 #[reflect(min_value = 0.001, step = 0.05)]
80 pub half_extents: Vector2<f32>,
81}
82
83impl Default for CuboidShape {
84 fn default() -> Self {
85 Self {
86 half_extents: Vector2::new(0.5, 0.5),
87 }
88 }
89}
90
91#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
93pub struct CapsuleShape {
94 pub begin: Vector2<f32>,
96 pub end: Vector2<f32>,
98 #[reflect(min_value = 0.001, step = 0.05)]
100 pub radius: f32,
101}
102
103impl Default for CapsuleShape {
104 fn default() -> Self {
106 Self {
107 begin: Default::default(),
108 end: Vector2::new(0.0, 1.0),
109 radius: 0.5,
110 }
111 }
112}
113
114#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
116pub struct SegmentShape {
117 pub begin: Vector2<f32>,
119 pub end: Vector2<f32>,
121}
122
123impl Default for SegmentShape {
124 fn default() -> Self {
125 Self {
126 begin: Default::default(),
127 end: Vector2::new(0.0, 1.0),
128 }
129 }
130}
131
132#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
134pub struct TriangleShape {
135 pub a: Vector2<f32>,
137 pub b: Vector2<f32>,
139 pub c: Vector2<f32>,
141}
142
143impl Default for TriangleShape {
144 fn default() -> Self {
145 Self {
146 a: Default::default(),
147 b: Vector2::new(1.0, 0.0),
148 c: Vector2::new(0.0, 1.0),
149 }
150 }
151}
152
153#[derive(Default, Clone, Copy, PartialEq, Hash, Debug, Visit, Reflect, Eq, TypeUuidProvider)]
159#[type_uuid(id = "1d451699-d76e-4774-87ea-dd3e2751cb39")]
160pub struct GeometrySource(pub Handle<Node>);
161
162#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
164pub struct TrimeshShape {
165 pub sources: Vec<GeometrySource>,
167}
168
169#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
171pub struct HeightfieldShape {
172 pub geometry_source: GeometrySource,
174}
175
176#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
178pub struct TileMapShape {
179 pub tile_map: GeometrySource,
181 pub layer_name: ImmutableString,
183}
184
185#[derive(Clone, Debug, Visit, Reflect, AsRefStr, PartialEq, EnumString, VariantNames)]
187pub enum ColliderShape {
188 Ball(BallShape),
190 Cuboid(CuboidShape),
192 Capsule(CapsuleShape),
194 Segment(SegmentShape),
196 Triangle(TriangleShape),
198 Trimesh(TrimeshShape),
200 Heightfield(HeightfieldShape),
202 TileMap(TileMapShape),
204}
205
206uuid_provider!(ColliderShape = "4615485f-f8db-4405-b4a5-437e74b3f5b8");
207
208impl Default for ColliderShape {
209 fn default() -> Self {
210 Self::Ball(Default::default())
211 }
212}
213
214impl ColliderShape {
215 pub fn ball(radius: f32) -> Self {
217 Self::Ball(BallShape { radius })
218 }
219
220 pub fn cuboid(hx: f32, hy: f32) -> Self {
222 Self::Cuboid(CuboidShape {
223 half_extents: Vector2::new(hx, hy),
224 })
225 }
226
227 pub fn capsule(begin: Vector2<f32>, end: Vector2<f32>, radius: f32) -> Self {
229 Self::Capsule(CapsuleShape { begin, end, radius })
230 }
231
232 pub fn capsule_x(half_height: f32, radius: f32) -> Self {
234 let p = Vector2::x() * half_height;
235 Self::capsule(-p, p, radius)
236 }
237
238 pub fn capsule_y(half_height: f32, radius: f32) -> Self {
240 let p = Vector2::y() * half_height;
241 Self::capsule(-p, p, radius)
242 }
243
244 pub fn segment(begin: Vector2<f32>, end: Vector2<f32>) -> Self {
246 Self::Segment(SegmentShape { begin, end })
247 }
248
249 pub fn triangle(a: Vector2<f32>, b: Vector2<f32>, c: Vector2<f32>) -> Self {
251 Self::Triangle(TriangleShape { a, b, c })
252 }
253
254 pub fn trimesh(geometry_sources: Vec<GeometrySource>) -> Self {
257 Self::Trimesh(TrimeshShape {
258 sources: geometry_sources,
259 })
260 }
261
262 pub fn heightfield(geometry_source: GeometrySource) -> Self {
264 Self::Heightfield(HeightfieldShape { geometry_source })
265 }
266}
267
268#[derive(Reflect, Visit, Debug, ComponentProvider)]
271pub struct Collider {
272 base: Base,
273
274 #[reflect(setter = "set_shape")]
275 pub(crate) shape: InheritableVariable<ColliderShape>,
276
277 #[reflect(min_value = 0.0, step = 0.05, setter = "set_friction")]
278 pub(crate) friction: InheritableVariable<f32>,
279
280 #[reflect(setter = "set_density")]
281 pub(crate) density: InheritableVariable<Option<f32>>,
282
283 #[reflect(min_value = 0.0, step = 0.05, setter = "set_restitution")]
284 pub(crate) restitution: InheritableVariable<f32>,
285
286 #[reflect(setter = "set_is_sensor")]
287 pub(crate) is_sensor: InheritableVariable<bool>,
288
289 #[reflect(setter = "set_collision_groups")]
290 pub(crate) collision_groups: InheritableVariable<InteractionGroups>,
291
292 #[reflect(setter = "set_solver_groups")]
293 pub(crate) solver_groups: InheritableVariable<InteractionGroups>,
294
295 #[reflect(setter = "set_friction_combine_rule")]
296 pub(crate) friction_combine_rule: InheritableVariable<CoefficientCombineRule>,
297
298 #[reflect(setter = "set_restitution_combine_rule")]
299 pub(crate) restitution_combine_rule: InheritableVariable<CoefficientCombineRule>,
300
301 #[visit(skip)]
302 #[reflect(hidden)]
303 pub(crate) native: Cell<ColliderHandle>,
304}
305
306impl Default for Collider {
307 fn default() -> Self {
308 Self {
309 base: Default::default(),
310 shape: Default::default(),
311 friction: Default::default(),
312 density: Default::default(),
313 restitution: Default::default(),
314 is_sensor: Default::default(),
315 collision_groups: Default::default(),
316 solver_groups: Default::default(),
317 friction_combine_rule: Default::default(),
318 restitution_combine_rule: Default::default(),
319 native: Cell::new(ColliderHandle::invalid()),
320 }
321 }
322}
323
324impl Deref for Collider {
325 type Target = Base;
326
327 fn deref(&self) -> &Self::Target {
328 &self.base
329 }
330}
331
332impl DerefMut for Collider {
333 fn deref_mut(&mut self) -> &mut Self::Target {
334 &mut self.base
335 }
336}
337
338impl Clone for Collider {
339 fn clone(&self) -> Self {
340 Self {
341 base: self.base.clone(),
342 shape: self.shape.clone(),
343 friction: self.friction.clone(),
344 density: self.density.clone(),
345 restitution: self.restitution.clone(),
346 is_sensor: self.is_sensor.clone(),
347 collision_groups: self.collision_groups.clone(),
348 solver_groups: self.solver_groups.clone(),
349 friction_combine_rule: self.friction_combine_rule.clone(),
350 restitution_combine_rule: self.restitution_combine_rule.clone(),
351 native: Cell::new(ColliderHandle::invalid()),
353 }
354 }
355}
356
357impl TypeUuidProvider for Collider {
358 fn type_uuid() -> Uuid {
359 uuid!("2b1659ea-a116-4224-bcd4-7931e3ae3b40")
360 }
361}
362
363impl Collider {
364 pub fn set_shape(&mut self, shape: ColliderShape) -> ColliderShape {
372 self.shape.set_value_and_mark_modified(shape)
373 }
374
375 pub fn shape(&self) -> &ColliderShape {
377 &self.shape
378 }
379
380 pub fn shape_value(&self) -> ColliderShape {
382 (*self.shape).clone()
383 }
384
385 pub fn shape_mut(&mut self) -> &mut ColliderShape {
393 self.shape.get_value_mut_and_mark_modified()
394 }
395
396 pub fn set_restitution(&mut self, restitution: f32) -> f32 {
406 self.restitution.set_value_and_mark_modified(restitution)
407 }
408
409 pub fn restitution(&self) -> f32 {
411 *self.restitution
412 }
413
414 pub fn set_density(&mut self, density: Option<f32>) -> Option<f32> {
429 self.density.set_value_and_mark_modified(density)
430 }
431
432 pub fn density(&self) -> Option<f32> {
434 *self.density
435 }
436
437 pub fn set_friction(&mut self, friction: f32) -> f32 {
447 self.friction.set_value_and_mark_modified(friction)
448 }
449
450 pub fn friction(&self) -> f32 {
452 *self.friction
453 }
454
455 pub fn set_collision_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
463 self.collision_groups.set_value_and_mark_modified(groups)
464 }
465
466 pub fn collision_groups(&self) -> InteractionGroups {
468 *self.collision_groups
469 }
470
471 pub fn set_solver_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
479 self.solver_groups.set_value_and_mark_modified(groups)
480 }
481
482 pub fn solver_groups(&self) -> InteractionGroups {
484 *self.solver_groups
485 }
486
487 pub fn set_is_sensor(&mut self, is_sensor: bool) -> bool {
496 self.is_sensor.set_value_and_mark_modified(is_sensor)
497 }
498
499 pub fn is_sensor(&self) -> bool {
501 *self.is_sensor
502 }
503
504 pub fn set_friction_combine_rule(
512 &mut self,
513 rule: CoefficientCombineRule,
514 ) -> CoefficientCombineRule {
515 self.friction_combine_rule.set_value_and_mark_modified(rule)
516 }
517
518 pub fn friction_combine_rule(&self) -> CoefficientCombineRule {
520 *self.friction_combine_rule
521 }
522
523 pub fn set_restitution_combine_rule(
531 &mut self,
532 rule: CoefficientCombineRule,
533 ) -> CoefficientCombineRule {
534 self.restitution_combine_rule
535 .set_value_and_mark_modified(rule)
536 }
537
538 pub fn restitution_combine_rule(&self) -> CoefficientCombineRule {
540 *self.restitution_combine_rule
541 }
542
543 pub fn contacts<'a>(
546 &self,
547 physics: &'a PhysicsWorld,
548 ) -> impl Iterator<Item = ContactPair> + 'a {
549 physics.contacts_with(self.native.get())
550 }
551
552 pub fn intersects<'a>(
555 &self,
556 physics: &'a PhysicsWorld,
557 ) -> impl Iterator<Item = IntersectionPair> + 'a {
558 physics.intersections_with(self.native.get())
559 }
560
561 pub(crate) fn needs_sync_model(&self) -> bool {
562 self.shape.need_sync()
563 || self.friction.need_sync()
564 || self.density.need_sync()
565 || self.restitution.need_sync()
566 || self.is_sensor.need_sync()
567 || self.collision_groups.need_sync()
568 || self.solver_groups.need_sync()
569 || self.friction_combine_rule.need_sync()
570 || self.restitution_combine_rule.need_sync()
571 }
572}
573
574impl ConstructorProvider<Node, Graph> for Collider {
575 fn constructor() -> NodeConstructor {
576 NodeConstructor::new::<Self>()
577 .with_variant("Collider", |_| {
578 ColliderBuilder::new(BaseBuilder::new().with_name("Collider 2D"))
579 .with_shape(ColliderShape::Cuboid(Default::default()))
580 .build_node()
581 .into()
582 })
583 .with_group("Physics 2D")
584 }
585}
586
587impl NodeTrait for Collider {
588 fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
589 self.base.local_bounding_box()
590 }
591
592 fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
593 self.base.world_bounding_box()
594 }
595
596 fn id(&self) -> Uuid {
597 Self::type_uuid()
598 }
599
600 fn on_removed_from_graph(&mut self, graph: &mut Graph) {
601 graph.physics2d.remove_collider(self.native.get());
602 self.native.set(ColliderHandle::invalid());
603
604 Log::info(format!(
605 "Native collider 2D was removed for node: {}",
606 self.name()
607 ));
608 }
609
610 fn on_unlink(&mut self, graph: &mut Graph) {
611 if graph.physics2d.remove_collider(self.native.get()) {
612 self.native.set(ColliderHandle::invalid());
614 }
615 }
616
617 fn on_local_transform_changed(&self, context: &mut SyncContext) {
618 if self.native.get() != ColliderHandle::invalid() {
619 if let Some(native) = context.physics2d.colliders.get_mut(self.native.get()) {
620 native.set_position_wrt_parent(Isometry2 {
621 rotation: UnitComplex::from_angle(
622 self.local_transform().rotation().euler_angles().2,
623 ),
624 translation: Translation2 {
625 vector: self.local_transform().position().xy(),
626 },
627 });
628 }
629 }
630 }
631
632 fn sync_native(&self, self_handle: Handle<Node>, context: &mut SyncContext) {
633 context
634 .physics2d
635 .sync_to_collider_node(context.nodes, self_handle, self);
636 }
637
638 fn validate(&self, scene: &Scene) -> Result<(), String> {
639 let mut message = String::new();
640
641 if scene
642 .graph
643 .try_get(self.parent())
644 .and_then(|p| p.component_ref::<RigidBody>())
645 .is_none()
646 {
647 message += "2D Collider must be a direct child of a 3D Rigid Body node, \
648 otherwise it will not have any effect!";
649 }
650
651 match &*self.shape {
652 ColliderShape::Trimesh(trimesh) => {
653 for source in trimesh.sources.iter() {
654 if !scene.graph.is_valid_handle(source.0) {
655 message += &format!("Trimesh shape data handle {} is invalid!", source.0);
656 }
657 }
658 }
659 ColliderShape::Heightfield(heightfield) => {
660 if !scene.graph.is_valid_handle(heightfield.geometry_source.0) {
661 message += &format!(
662 "Heightfield shape data handle {} is invalid!",
663 heightfield.geometry_source.0
664 );
665 }
666 }
667 ColliderShape::TileMap(tile_map) => {
668 if !scene.graph.is_valid_handle(tile_map.tile_map.0) {
669 message += &format!(
670 "Tile map shape data handle {} is invalid!",
671 tile_map.tile_map.0
672 );
673 }
674 }
675 _ => (),
676 }
677
678 if message.is_empty() {
679 Ok(())
680 } else {
681 Err(message)
682 }
683 }
684}
685
686pub struct ColliderBuilder {
688 base_builder: BaseBuilder,
689 shape: ColliderShape,
690 friction: f32,
691 density: Option<f32>,
692 restitution: f32,
693 is_sensor: bool,
694 collision_groups: InteractionGroups,
695 solver_groups: InteractionGroups,
696 friction_combine_rule: CoefficientCombineRule,
697 restitution_combine_rule: CoefficientCombineRule,
698}
699
700impl ColliderBuilder {
701 pub fn new(base_builder: BaseBuilder) -> Self {
703 Self {
704 base_builder,
705 shape: Default::default(),
706 friction: 0.0,
707 density: None,
708 restitution: 0.0,
709 is_sensor: false,
710 collision_groups: Default::default(),
711 solver_groups: Default::default(),
712 friction_combine_rule: Default::default(),
713 restitution_combine_rule: Default::default(),
714 }
715 }
716
717 pub fn with_shape(mut self, shape: ColliderShape) -> Self {
719 self.shape = shape;
720 self
721 }
722
723 pub fn with_density(mut self, density: Option<f32>) -> Self {
725 self.density = density;
726 self
727 }
728
729 pub fn with_restitution(mut self, restitution: f32) -> Self {
731 self.restitution = restitution;
732 self
733 }
734
735 pub fn with_friction(mut self, friction: f32) -> Self {
737 self.friction = friction;
738 self
739 }
740
741 pub fn with_sensor(mut self, sensor: bool) -> Self {
743 self.is_sensor = sensor;
744 self
745 }
746
747 pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
749 self.solver_groups = solver_groups;
750 self
751 }
752
753 pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
755 self.collision_groups = collision_groups;
756 self
757 }
758
759 pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
761 self.friction_combine_rule = rule;
762 self
763 }
764
765 pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
767 self.restitution_combine_rule = rule;
768 self
769 }
770
771 pub fn build_collider(self) -> Collider {
773 Collider {
774 base: self.base_builder.build_base(),
775 shape: self.shape.into(),
776 friction: self.friction.into(),
777 density: self.density.into(),
778 restitution: self.restitution.into(),
779 is_sensor: self.is_sensor.into(),
780 collision_groups: self.collision_groups.into(),
781 solver_groups: self.solver_groups.into(),
782 friction_combine_rule: self.friction_combine_rule.into(),
783 restitution_combine_rule: self.restitution_combine_rule.into(),
784 native: Cell::new(ColliderHandle::invalid()),
785 }
786 }
787
788 pub fn build_node(self) -> Node {
790 Node::new(self.build_collider())
791 }
792
793 pub fn build(self, graph: &mut Graph) -> Handle<Node> {
795 graph.add_node(self.build_node())
796 }
797}
798
799#[cfg(test)]
800mod test {
801
802 use crate::core::algebra::Vector2;
803 use crate::scene::{
804 base::BaseBuilder,
805 dim2::{
806 collider::{ColliderBuilder, ColliderShape},
807 rigidbody::RigidBodyBuilder,
808 },
809 graph::Graph,
810 rigidbody::RigidBodyType,
811 };
812
813 #[test]
814 fn test_collider_2d_intersect() {
815 let mut graph = Graph::new();
816
817 let mut create_rigid_body = |is_sensor| {
818 let cube_half_size = 0.5;
819 let collider_sensor = ColliderBuilder::new(BaseBuilder::new())
820 .with_shape(ColliderShape::cuboid(cube_half_size, cube_half_size))
821 .with_sensor(is_sensor)
822 .build(&mut graph);
823
824 RigidBodyBuilder::new(BaseBuilder::new().with_children(&[collider_sensor]))
825 .with_body_type(RigidBodyType::Static)
826 .build(&mut graph);
827
828 collider_sensor
829 };
830
831 let collider_sensor = create_rigid_body(true);
832 let collider_non_sensor = create_rigid_body(false);
833
834 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
836 graph.update(Vector2::new(800.0, 600.0), 1.0, Default::default());
837
838 assert_eq!(
840 0,
841 graph[collider_sensor]
842 .as_collider2d()
843 .contacts(&graph.physics2d)
844 .count()
845 );
846 assert_eq!(
847 0,
848 graph[collider_non_sensor]
849 .as_collider2d()
850 .contacts(&graph.physics2d)
851 .count()
852 );
853
854 assert_eq!(
856 1,
857 graph[collider_sensor]
858 .as_collider2d()
859 .intersects(&graph.physics2d)
860 .count()
861 );
862 assert_eq!(
863 1,
864 graph[collider_non_sensor]
865 .as_collider2d()
866 .intersects(&graph.physics2d)
867 .count()
868 );
869 }
870}