Skip to main content

fyrox_impl/scene/dim2/
collider.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Collider is a geometric entity that can be attached to a rigid body to allow to participate it
22//! participate in contact generation, collision response and proximity queries.
23
24use 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/// Ball is an idea sphere shape defined by a single parameters - its radius.
62#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
63pub struct BallShape {
64    /// Radius of the sphere.
65    #[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/// Cuboid shape (rectangle).
76#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
77pub struct CuboidShape {
78    /// Half extents of the box. X - half width, Y - half height.
79    /// Actual _size_ will be 2 times bigger.
80    #[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/// Arbitrary capsule shape defined by 2 points (which forms axis) and a radius.
93#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
94pub struct CapsuleShape {
95    /// Begin point of the capsule.
96    pub begin: Vector2<f32>,
97    /// End point of the capsule.
98    pub end: Vector2<f32>,
99    /// Radius of the capsule.
100    #[reflect(min_value = 0.001, step = 0.05)]
101    pub radius: f32,
102}
103
104impl Default for CapsuleShape {
105    // Y-capsule
106    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/// Arbitrary segment shape defined by two points.
116#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
117pub struct SegmentShape {
118    /// Begin point of the capsule.
119    pub begin: Vector2<f32>,
120    /// End point of the capsule.
121    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/// Arbitrary triangle shape.
134#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
135pub struct TriangleShape {
136    /// First point of the triangle shape.
137    pub a: Vector2<f32>,
138    /// Second point of the triangle shape.
139    pub b: Vector2<f32>,
140    /// Third point of the triangle shape.
141    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/// Geometry source for colliders with complex geometry.
155///
156/// # Notes
157///
158/// Currently, there is only one way to set geometry - using a scene node as a source of data.
159#[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/// Arbitrary triangle mesh shape.
164#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
165pub struct TrimeshShape {
166    /// Geometry sources for the shape.
167    pub sources: Vec<GeometrySource>,
168}
169
170/// Arbitrary height field shape.
171#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
172pub struct HeightfieldShape {
173    /// A handle to terrain scene node.
174    pub geometry_source: GeometrySource,
175}
176
177/// Arbitrary tile map shape.
178#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
179pub struct TileMapShape {
180    /// A handle to tile map scene node.
181    pub tile_map: GeometrySource,
182    /// Name of a collider layer in the tile map's tile set.
183    pub layer_name: ImmutableString,
184}
185
186/// Possible collider shapes.
187#[derive(Clone, Debug, Visit, Reflect, AsRefStr, PartialEq, EnumString, VariantNames)]
188pub enum ColliderShape {
189    /// See [`BallShape`] docs.
190    Ball(BallShape),
191    /// See [`CuboidShape`] docs.
192    Cuboid(CuboidShape),
193    /// See [`CapsuleShape`] docs.
194    Capsule(CapsuleShape),
195    /// See [`SegmentShape`] docs.
196    Segment(SegmentShape),
197    /// See [`TriangleShape`] docs.
198    Triangle(TriangleShape),
199    /// See [`TrimeshShape`] docs.
200    Trimesh(TrimeshShape),
201    /// See [`HeightfieldShape`] docs.
202    Heightfield(HeightfieldShape),
203    /// See [`TileMapShape`] docs.
204    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    /// Initializes a ball shape defined by its radius.
217    pub fn ball(radius: f32) -> Self {
218        Self::Ball(BallShape { radius })
219    }
220
221    /// Initializes a cuboid shape defined by its half-extents.
222    pub fn cuboid(hx: f32, hy: f32) -> Self {
223        Self::Cuboid(CuboidShape {
224            half_extents: Vector2::new(hx, hy),
225        })
226    }
227
228    /// Initializes a capsule shape from its endpoints and radius.
229    pub fn capsule(begin: Vector2<f32>, end: Vector2<f32>, radius: f32) -> Self {
230        Self::Capsule(CapsuleShape { begin, end, radius })
231    }
232
233    /// Initializes a new collider builder with a capsule shape aligned with the `x` axis.
234    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    /// Initializes a new collider builder with a capsule shape aligned with the `y` axis.
240    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    /// Initializes a segment shape from its endpoints.
246    pub fn segment(begin: Vector2<f32>, end: Vector2<f32>) -> Self {
247        Self::Segment(SegmentShape { begin, end })
248    }
249
250    /// Initializes a triangle shape.
251    pub fn triangle(a: Vector2<f32>, b: Vector2<f32>, c: Vector2<f32>) -> Self {
252        Self::Triangle(TriangleShape { a, b, c })
253    }
254
255    /// Initializes a triangle mesh shape defined by a set of handles to mesh nodes that will be
256    /// used to create physical shape.
257    pub fn trimesh(geometry_sources: Vec<GeometrySource>) -> Self {
258        Self::Trimesh(TrimeshShape {
259            sources: geometry_sources,
260        })
261    }
262
263    /// Initializes a heightfield shape defined by a handle to terrain node.
264    pub fn heightfield(geometry_source: GeometrySource) -> Self {
265        Self::Heightfield(HeightfieldShape { geometry_source })
266    }
267}
268
269/// Collider is a geometric entity that can be attached to a rigid body to allow to participate in
270/// contact generation, collision response and proximity queries.
271#[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            // Do not copy. The copy will have its own native representation.
354            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    /// Sets the new shape to the collider.
367    ///
368    /// # Performance
369    ///
370    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
371    /// perform collision response, etc. Try to avoid calling this method each frame for better
372    /// performance.
373    pub fn set_shape(&mut self, shape: ColliderShape) -> ColliderShape {
374        self.shape.set_value_and_mark_modified(shape)
375    }
376
377    /// Returns shared reference to the collider shape.
378    pub fn shape(&self) -> &ColliderShape {
379        &self.shape
380    }
381
382    /// Returns a copy of the collider shape.
383    pub fn shape_value(&self) -> ColliderShape {
384        (*self.shape).clone()
385    }
386
387    /// Returns mutable reference to the current collider shape.
388    ///
389    /// # Performance
390    ///
391    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
392    /// perform collision response, etc. Try to avoid calling this method each frame for better
393    /// performance.
394    pub fn shape_mut(&mut self) -> &mut ColliderShape {
395        self.shape.get_value_mut_and_mark_modified()
396    }
397
398    /// Sets the new restitution value. The exact meaning of possible values is somewhat complex,
399    /// check [Wikipedia page](https://en.wikipedia.org/wiki/Coefficient_of_restitution) for more
400    /// info.
401    ///
402    /// # Performance
403    ///
404    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
405    /// perform collision response, etc. Try to avoid calling this method each frame for better
406    /// performance.
407    pub fn set_restitution(&mut self, restitution: f32) -> f32 {
408        self.restitution.set_value_and_mark_modified(restitution)
409    }
410
411    /// Returns current restitution value of the collider.
412    pub fn restitution(&self) -> f32 {
413        *self.restitution
414    }
415
416    /// Sets the new density value of the collider. Density defines actual mass of the rigid body to
417    /// which the collider is attached. Final mass will be a sum of `ColliderVolume * ColliderDensity`
418    /// of each collider. In case if density is undefined, the mass of the collider will be zero,
419    /// which will lead to two possible effects:
420    ///
421    /// 1) If a rigid body to which collider is attached have no additional mass, then the rigid body
422    ///    won't rotate, only move.
423    /// 2) If the rigid body have some additional mass, then the rigid body will have normal behavior.
424    ///
425    /// # Performance
426    ///
427    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
428    /// perform collision response, etc. Try to avoid calling this method each frame for better
429    /// performance.
430    pub fn set_density(&mut self, density: Option<f32>) -> Option<f32> {
431        self.density.set_value_and_mark_modified(density)
432    }
433
434    /// Returns current density of the collider.
435    pub fn density(&self) -> Option<f32> {
436        *self.density
437    }
438
439    /// Sets friction coefficient for the collider. The greater value is the more kinematic energy
440    /// will be converted to heat (in other words - lost), the parent rigid body will slowdown much
441    /// faster and so on.
442    ///
443    /// # Performance
444    ///
445    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
446    /// perform collision response, etc. Try avoid calling this method each frame for better
447    /// performance.
448    pub fn set_friction(&mut self, friction: f32) -> f32 {
449        self.friction.set_value_and_mark_modified(friction)
450    }
451
452    /// Return current friction of the collider.
453    pub fn friction(&self) -> f32 {
454        *self.friction
455    }
456
457    /// Sets the new collision filtering options. See [`InteractionGroups`] docs for more info.
458    ///
459    /// # Performance
460    ///
461    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
462    /// perform collision response, etc. Try avoid calling this method each frame for better
463    /// performance.
464    pub fn set_collision_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
465        self.collision_groups.set_value_and_mark_modified(groups)
466    }
467
468    /// Returns current collision filtering options.
469    pub fn collision_groups(&self) -> InteractionGroups {
470        *self.collision_groups
471    }
472
473    /// Sets the new joint solver filtering options. See [`InteractionGroups`] docs for more info.
474    ///
475    /// # Performance
476    ///
477    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
478    /// perform collision response, etc. Try avoid calling this method each frame for better
479    /// performance.
480    pub fn set_solver_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
481        self.solver_groups.set_value_and_mark_modified(groups)
482    }
483
484    /// Returns current solver groups.
485    pub fn solver_groups(&self) -> InteractionGroups {
486        *self.solver_groups
487    }
488
489    /// If true is passed, the method makes collider a sensor. Sensors will not participate in
490    /// collision response, but it is still possible to query contact information from them.
491    ///
492    /// # Performance
493    ///
494    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
495    /// perform collision response, etc. Try avoid calling this method each frame for better
496    /// performance.
497    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    /// Returns true if the collider is sensor, false - otherwise.
502    pub fn is_sensor(&self) -> bool {
503        *self.is_sensor
504    }
505
506    /// Sets the new friction combine rule. See [`CoefficientCombineRule`] docs for more info.
507    ///
508    /// # Performance
509    ///
510    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
511    /// perform collision response, etc. Try avoid calling this method each frame for better
512    /// performance.
513    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    /// Returns current friction combine rule of the collider.
521    pub fn friction_combine_rule(&self) -> CoefficientCombineRule {
522        *self.friction_combine_rule
523    }
524
525    /// Sets the new restitution combine rule. See [`CoefficientCombineRule`] docs for more info.
526    ///
527    /// # Performance
528    ///
529    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
530    /// perform collision response, etc. Try avoid calling this method each frame for better
531    /// performance.
532    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    /// Returns current restitution combine rule of the collider.
541    pub fn restitution_combine_rule(&self) -> CoefficientCombineRule {
542        *self.restitution_combine_rule
543    }
544
545    /// Returns an iterator that yields contact information for the collider.
546    /// Contacts checks between two non-sensor colliders.
547    /// This includes only cases where two colliders are pressing against each other,
548    /// and only if [`ContactPair::has_any_active_contact`] is true.
549    /// When `has_any_active_contact` is false, the colliders may merely have overlapping
550    /// bounding boxes. See [`Collider::active_contacts`] for an integrator that yields
551    /// only pairs where `has_any_active_contact` is true.
552    ///
553    /// When a collider is passing through a sensor collider, that goes into the
554    /// [`Collider::intersects`] list. Those intersections will not be produced by
555    /// this iterator.
556    ///
557    /// Each pair produced by this iterator includes the handles of two colliders,
558    /// this collider and some other collider. There is no guarantee about whether
559    /// this collider will be the first member of the pair or the second,
560    /// but [`ContactPair::other`] can be used to get the handle of the other collider
561    /// given the handle of this collider.
562    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    /// Returns an iterator that yields contact information for the collider.
570    /// Contacts checks between two non-sensor colliders that are actually touching.
571    /// This includes only cases where two colliders are pressing against each other.
572    /// When a collider is passing through a sensor collider, that goes into the
573    /// [`Collider::intersects`] list.
574    ///
575    /// [`ContactPair::has_any_active_contact`] is guaranteed to be true for every pair
576    /// produced by this iterator.
577    ///
578    /// Each pair produced by this iterator includes the handles of two colliders,
579    /// this collider and some other collider. There is no guarantee about whether
580    /// this collider will be the first member of the pair or the second,
581    /// but [`ContactPair::other`] can be used to get the handle of the other collider
582    /// given the handle of this collider.
583    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    /// Returns an iterator that yields intersection information for the collider.
592    /// Intersections checks for colliders passing through each other due to at least
593    /// one of the colliders being a sensor.
594    /// If [`IntersectionPair::has_any_active_contact`] is true, that means the colliders are actually touching.
595    /// When `has_any_active_contact` is false, the colliders may merely have overlapping
596    /// bounding boxes. See [`Collider::active_intersects`] for an integrator that yields
597    /// only colliders that actually overlap this collider.
598    ///
599    /// Each pair produced by this iterator includes the handles of two colliders,
600    /// this collider and some other collider. There is no guarantee about whether
601    /// this collider will be the first member of the pair or the second,
602    /// but [`IntersectionPair::other`] can be used to get the handle of the other collider
603    /// given the handle of this collider.
604    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    /// Returns an iterator that yields intersection information for the collider.
612    /// Intersections checks for colliders passing through each other due to at least
613    /// one of the colliders being a sensor.
614    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            // Remove native collider when detaching a collider node from rigid body node.
676            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
748/// Collider builder allows you to build a collider node in declarative manner.
749pub 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    /// Creates new collider builder.
764    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    /// Sets desired shape of the collider.
780    pub fn with_shape(mut self, shape: ColliderShape) -> Self {
781        self.shape = shape;
782        self
783    }
784
785    /// Sets desired density value.
786    pub fn with_density(mut self, density: Option<f32>) -> Self {
787        self.density = density;
788        self
789    }
790
791    /// Sets desired restitution value.
792    pub fn with_restitution(mut self, restitution: f32) -> Self {
793        self.restitution = restitution;
794        self
795    }
796
797    /// Sets desired friction value.
798    pub fn with_friction(mut self, friction: f32) -> Self {
799        self.friction = friction;
800        self
801    }
802
803    /// Sets whether this collider will be used a sensor or not.
804    pub fn with_sensor(mut self, sensor: bool) -> Self {
805        self.is_sensor = sensor;
806        self
807    }
808
809    /// Sets desired solver groups.
810    pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
811        self.solver_groups = solver_groups;
812        self
813    }
814
815    /// Sets desired collision groups.
816    pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
817        self.collision_groups = collision_groups;
818        self
819    }
820
821    /// Sets desired friction combine rule.
822    pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
823        self.friction_combine_rule = rule;
824        self
825    }
826
827    /// Sets desired restitution combine rule.
828    pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
829        self.restitution_combine_rule = rule;
830        self
831    }
832
833    /// Creates collider node, but does not add it to a graph.
834    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    /// Creates collider node, but does not add it to a graph.
851    pub fn build_node(self) -> Node {
852        Node::new(self.build_collider())
853    }
854
855    /// Creates collider node and adds it to the graph.
856    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        // need to call two times for the physics engine to execute
897        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        // we don't expect contact between regular body and sensor
901        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        // we expect intersection between regular body and sensor
910        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}