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 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::{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/// Ball is an idea sphere shape defined by a single parameters - its radius.
61#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
62pub struct BallShape {
63    /// Radius of the sphere.
64    #[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/// Cuboid shape (rectangle).
75#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
76pub struct CuboidShape {
77    /// Half extents of the box. X - half width, Y - half height.
78    /// Actual _size_ will be 2 times bigger.
79    #[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/// Arbitrary capsule shape defined by 2 points (which forms axis) and a radius.
92#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
93pub struct CapsuleShape {
94    /// Begin point of the capsule.
95    pub begin: Vector2<f32>,
96    /// End point of the capsule.
97    pub end: Vector2<f32>,
98    /// Radius of the capsule.
99    #[reflect(min_value = 0.001, step = 0.05)]
100    pub radius: f32,
101}
102
103impl Default for CapsuleShape {
104    // Y-capsule
105    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/// Arbitrary segment shape defined by two points.
115#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
116pub struct SegmentShape {
117    /// Begin point of the capsule.
118    pub begin: Vector2<f32>,
119    /// End point of the capsule.
120    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/// Arbitrary triangle shape.
133#[derive(Clone, Debug, Visit, PartialEq, Reflect)]
134pub struct TriangleShape {
135    /// First point of the triangle shape.
136    pub a: Vector2<f32>,
137    /// Second point of the triangle shape.
138    pub b: Vector2<f32>,
139    /// Third point of the triangle shape.
140    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/// Geometry source for colliders with complex geometry.
154///
155/// # Notes
156///
157/// Currently there is only one way to set geometry - using a scene node as a source of data.
158#[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/// Arbitrary triangle mesh shape.
163#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
164pub struct TrimeshShape {
165    /// Geometry sources for the shape.
166    pub sources: Vec<GeometrySource>,
167}
168
169/// Arbitrary height field shape.
170#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
171pub struct HeightfieldShape {
172    /// A handle to terrain scene node.
173    pub geometry_source: GeometrySource,
174}
175
176/// Arbitrary tile map shape.
177#[derive(Default, Clone, Debug, PartialEq, Visit, Reflect, Eq)]
178pub struct TileMapShape {
179    /// A handle to tile map scene node.
180    pub tile_map: GeometrySource,
181    /// Name of a collider layer in the tile map's tile set.
182    pub layer_name: ImmutableString,
183}
184
185/// Possible collider shapes.
186#[derive(Clone, Debug, Visit, Reflect, AsRefStr, PartialEq, EnumString, VariantNames)]
187pub enum ColliderShape {
188    /// See [`BallShape`] docs.
189    Ball(BallShape),
190    /// See [`CuboidShape`] docs.
191    Cuboid(CuboidShape),
192    /// See [`CapsuleShape`] docs.
193    Capsule(CapsuleShape),
194    /// See [`SegmentShape`] docs.
195    Segment(SegmentShape),
196    /// See [`TriangleShape`] docs.
197    Triangle(TriangleShape),
198    /// See [`TrimeshShape`] docs.
199    Trimesh(TrimeshShape),
200    /// See [`HeightfieldShape`] docs.
201    Heightfield(HeightfieldShape),
202    /// See [`TileMapShape`] docs.
203    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    /// Initializes a ball shape defined by its radius.
216    pub fn ball(radius: f32) -> Self {
217        Self::Ball(BallShape { radius })
218    }
219
220    /// Initializes a cuboid shape defined by its half-extents.
221    pub fn cuboid(hx: f32, hy: f32) -> Self {
222        Self::Cuboid(CuboidShape {
223            half_extents: Vector2::new(hx, hy),
224        })
225    }
226
227    /// Initializes a capsule shape from its endpoints and radius.
228    pub fn capsule(begin: Vector2<f32>, end: Vector2<f32>, radius: f32) -> Self {
229        Self::Capsule(CapsuleShape { begin, end, radius })
230    }
231
232    /// Initializes a new collider builder with a capsule shape aligned with the `x` axis.
233    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    /// Initializes a new collider builder with a capsule shape aligned with the `y` axis.
239    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    /// Initializes a segment shape from its endpoints.
245    pub fn segment(begin: Vector2<f32>, end: Vector2<f32>) -> Self {
246        Self::Segment(SegmentShape { begin, end })
247    }
248
249    /// Initializes a triangle shape.
250    pub fn triangle(a: Vector2<f32>, b: Vector2<f32>, c: Vector2<f32>) -> Self {
251        Self::Triangle(TriangleShape { a, b, c })
252    }
253
254    /// Initializes a triangle mesh shape defined by a set of handles to mesh nodes that will be
255    /// used to create physical shape.
256    pub fn trimesh(geometry_sources: Vec<GeometrySource>) -> Self {
257        Self::Trimesh(TrimeshShape {
258            sources: geometry_sources,
259        })
260    }
261
262    /// Initializes a heightfield shape defined by a handle to terrain node.
263    pub fn heightfield(geometry_source: GeometrySource) -> Self {
264        Self::Heightfield(HeightfieldShape { geometry_source })
265    }
266}
267
268/// Collider is a geometric entity that can be attached to a rigid body to allow participate it
269/// participate in contact generation, collision response and proximity queries.
270#[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            // Do not copy. The copy will have its own native representation.
352            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    /// Sets the new shape to the collider.
365    ///
366    /// # Performance
367    ///
368    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
369    /// perform collision response, etc. Try avoid calling this method each frame for better
370    /// performance.
371    pub fn set_shape(&mut self, shape: ColliderShape) -> ColliderShape {
372        self.shape.set_value_and_mark_modified(shape)
373    }
374
375    /// Returns shared reference to the collider shape.
376    pub fn shape(&self) -> &ColliderShape {
377        &self.shape
378    }
379
380    /// Returns a copy of the collider shape.
381    pub fn shape_value(&self) -> ColliderShape {
382        (*self.shape).clone()
383    }
384
385    /// Returns mutable reference to the current collider shape.
386    ///
387    /// # Performance
388    ///
389    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
390    /// perform collision response, etc. Try avoid calling this method each frame for better
391    /// performance.
392    pub fn shape_mut(&mut self) -> &mut ColliderShape {
393        self.shape.get_value_mut_and_mark_modified()
394    }
395
396    /// Sets the new restitution value. The exact meaning of possible values is somewhat complex,
397    /// check [Wikipedia page](https://en.wikipedia.org/wiki/Coefficient_of_restitution) for more
398    /// info.
399    ///
400    /// # Performance
401    ///
402    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
403    /// perform collision response, etc. Try avoid calling this method each frame for better
404    /// performance.
405    pub fn set_restitution(&mut self, restitution: f32) -> f32 {
406        self.restitution.set_value_and_mark_modified(restitution)
407    }
408
409    /// Returns current restitution value of the collider.
410    pub fn restitution(&self) -> f32 {
411        *self.restitution
412    }
413
414    /// Sets the new density value of the collider. Density defines actual mass of the rigid body to
415    /// which the collider is attached. Final mass will be a sum of `ColliderVolume * ColliderDensity`
416    /// of each collider. In case if density is undefined, the mass of the collider will be zero,
417    /// which will lead to two possible effects:
418    ///
419    /// 1) If a rigid body to which collider is attached have no additional mass, then the rigid body
420    ///    won't rotate, only move.
421    /// 2) If the rigid body have some additional mass, then the rigid body will have normal behaviour.
422    ///
423    /// # Performance
424    ///
425    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
426    /// perform collision response, etc. Try avoid calling this method each frame for better
427    /// performance.
428    pub fn set_density(&mut self, density: Option<f32>) -> Option<f32> {
429        self.density.set_value_and_mark_modified(density)
430    }
431
432    /// Returns current density of the collider.
433    pub fn density(&self) -> Option<f32> {
434        *self.density
435    }
436
437    /// Sets friction coefficient for the collider. The greater value is the more kinematic energy
438    /// will be converted to heat (in other words - lost), the parent rigid body will slowdown much
439    /// faster and so on.
440    ///
441    /// # Performance
442    ///
443    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
444    /// perform collision response, etc. Try avoid calling this method each frame for better
445    /// performance.
446    pub fn set_friction(&mut self, friction: f32) -> f32 {
447        self.friction.set_value_and_mark_modified(friction)
448    }
449
450    /// Return current friction of the collider.
451    pub fn friction(&self) -> f32 {
452        *self.friction
453    }
454
455    /// Sets the new collision filtering options. See [`InteractionGroups`] docs for more info.
456    ///
457    /// # Performance
458    ///
459    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
460    /// perform collision response, etc. Try avoid calling this method each frame for better
461    /// performance.
462    pub fn set_collision_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
463        self.collision_groups.set_value_and_mark_modified(groups)
464    }
465
466    /// Returns current collision filtering options.
467    pub fn collision_groups(&self) -> InteractionGroups {
468        *self.collision_groups
469    }
470
471    /// Sets the new joint solver filtering options. See [`InteractionGroups`] docs for more info.
472    ///
473    /// # Performance
474    ///
475    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
476    /// perform collision response, etc. Try avoid calling this method each frame for better
477    /// performance.
478    pub fn set_solver_groups(&mut self, groups: InteractionGroups) -> InteractionGroups {
479        self.solver_groups.set_value_and_mark_modified(groups)
480    }
481
482    /// Returns current solver groups.
483    pub fn solver_groups(&self) -> InteractionGroups {
484        *self.solver_groups
485    }
486
487    /// If true is passed, the method makes collider a sensor. Sensors will not participate in
488    /// collision response, but it is still possible to query contact information from them.
489    ///
490    /// # Performance
491    ///
492    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
493    /// perform collision response, etc. Try avoid calling this method each frame for better
494    /// performance.
495    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    /// Returns true if the collider is sensor, false - otherwise.
500    pub fn is_sensor(&self) -> bool {
501        *self.is_sensor
502    }
503
504    /// Sets the new friction combine rule. See [`CoefficientCombineRule`] docs for more info.
505    ///
506    /// # Performance
507    ///
508    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
509    /// perform collision response, etc. Try avoid calling this method each frame for better
510    /// performance.
511    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    /// Returns current friction combine rule of the collider.
519    pub fn friction_combine_rule(&self) -> CoefficientCombineRule {
520        *self.friction_combine_rule
521    }
522
523    /// Sets the new restitution combine rule. See [`CoefficientCombineRule`] docs for more info.
524    ///
525    /// # Performance
526    ///
527    /// This is relatively expensive operation - it forces the physics engine to recalculate contacts,
528    /// perform collision response, etc. Try avoid calling this method each frame for better
529    /// performance.
530    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    /// Returns current restitution combine rule of the collider.
539    pub fn restitution_combine_rule(&self) -> CoefficientCombineRule {
540        *self.restitution_combine_rule
541    }
542
543    /// Returns an iterator that yields contact information for the collider.
544    /// Contacts checks between two regular colliders
545    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    /// Returns an iterator that yields intersection information for the collider.
553    /// Intersections checks between regular colliders and sensor colliders
554    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            // Remove native collider when detaching a collider node from rigid body node.
613            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
686/// Collider builder allows you to build a collider node in declarative manner.
687pub 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    /// Creates new collider builder.
702    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    /// Sets desired shape of the collider.
718    pub fn with_shape(mut self, shape: ColliderShape) -> Self {
719        self.shape = shape;
720        self
721    }
722
723    /// Sets desired density value.
724    pub fn with_density(mut self, density: Option<f32>) -> Self {
725        self.density = density;
726        self
727    }
728
729    /// Sets desired restitution value.
730    pub fn with_restitution(mut self, restitution: f32) -> Self {
731        self.restitution = restitution;
732        self
733    }
734
735    /// Sets desired friction value.
736    pub fn with_friction(mut self, friction: f32) -> Self {
737        self.friction = friction;
738        self
739    }
740
741    /// Sets whether this collider will be used a sensor or not.
742    pub fn with_sensor(mut self, sensor: bool) -> Self {
743        self.is_sensor = sensor;
744        self
745    }
746
747    /// Sets desired solver groups.
748    pub fn with_solver_groups(mut self, solver_groups: InteractionGroups) -> Self {
749        self.solver_groups = solver_groups;
750        self
751    }
752
753    /// Sets desired collision groups.
754    pub fn with_collision_groups(mut self, collision_groups: InteractionGroups) -> Self {
755        self.collision_groups = collision_groups;
756        self
757    }
758
759    /// Sets desired friction combine rule.
760    pub fn with_friction_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
761        self.friction_combine_rule = rule;
762        self
763    }
764
765    /// Sets desired restitution combine rule.
766    pub fn with_restitution_combine_rule(mut self, rule: CoefficientCombineRule) -> Self {
767        self.restitution_combine_rule = rule;
768        self
769    }
770
771    /// Creates collider node, but does not add it to a graph.
772    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    /// Creates collider node, but does not add it to a graph.
789    pub fn build_node(self) -> Node {
790        Node::new(self.build_collider())
791    }
792
793    /// Creates collider node and adds it to the graph.
794    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        // need to call two times for the physics engine to execute
835        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        // we don't expect contact between regular body and sensor
839        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        // we expect intersection between regular body and sensor
855        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}