vox_geometry_rust/
collider2.rs

1/*
2 * // Copyright (c) 2021 Feng Yang
3 * //
4 * // I am making my contributions/submissions to this project solely in my
5 * // personal capacity and am not conveying any rights to any intellectual
6 * // property of any third parties.
7 */
8
9use crate::vector2::Vector2D;
10use crate::surface2::*;
11use std::sync::{RwLock, Arc};
12
13/// Internal query result structure.
14pub struct ColliderQueryResult {
15    distance: f64,
16    point: Vector2D,
17    normal: Vector2D,
18    velocity: Vector2D,
19}
20
21impl ColliderQueryResult {
22    pub fn new() -> ColliderQueryResult {
23        return ColliderQueryResult {
24            distance: 0.0,
25            point: Vector2D::new_default(),
26            normal: Vector2D::new_default(),
27            velocity: Vector2D::new_default(),
28        };
29    }
30}
31
32pub struct Collider2Data {
33    _surface: Option<Surface2Ptr>,
34    _friction_coefficient: f64,
35}
36
37impl Collider2Data {
38    pub fn new(surface: Option<Surface2Ptr>) -> Collider2Data {
39        return Collider2Data {
40            _surface: surface,
41            _friction_coefficient: 0.0,
42        };
43    }
44}
45
46///
47/// # Abstract base class for generic collider object.
48///
49/// This class contains basic interfaces for colliders. Most of the
50/// functionalities are implemented within this class, except the member
51/// function Collider2::velocity_at. This class also let the subclasses to
52/// provide a Surface2 instance to define collider surface using
53/// Collider2::set_surface function.
54///
55pub trait Collider2 {
56    /// Returns the velocity of the collider at given \p point.
57    fn velocity_at(&self, point: &Vector2D) -> Vector2D;
58
59    /// Resolves collision for given point.
60    /// - Parameters:
61    ///   - radius: Radius of the colliding point.
62    ///   - restitution_coefficient:  Defines the restitution effect.
63    ///   - position: Input and output position of the point.
64    ///   - velocity: Input and output velocity of the point.
65    fn resolve_collision(&self, radius: f64,
66                         restitution_coefficient: f64,
67                         new_position: &mut Vector2D,
68                         new_velocity: &mut Vector2D) {
69        if !self.surface().read().unwrap().is_valid_geometry() {
70            return;
71        }
72
73        let mut collider_point = ColliderQueryResult::new();
74
75        self.get_closest_point(self.surface(), new_position, &mut collider_point);
76
77        // Check if the new position is penetrating the surface
78        if self.is_penetrating(&collider_point, new_position, radius) {
79            // Target point is the closest non-penetrating position from the
80            // new position.
81            let target_normal = collider_point.normal;
82            let target_point = collider_point.point + target_normal * radius;
83            let collider_vel_at_target_point = collider_point.velocity;
84
85            // Get new candidate relative velocity from the target point.
86            let relative_vel = *new_velocity - collider_vel_at_target_point;
87            let normal_dot_relative_vel = target_normal.dot(&relative_vel);
88            let mut relative_vel_n = target_normal * normal_dot_relative_vel;
89            let mut relative_vel_t = relative_vel - relative_vel_n;
90
91            // Check if the velocity is facing opposite direction of the surface
92            // normal
93            if normal_dot_relative_vel < 0.0 {
94                // Apply restitution coefficient to the surface normal component of
95                // the velocity
96                let delta_relative_vel_n = relative_vel_n * (-restitution_coefficient - 1.0);
97                relative_vel_n *= -restitution_coefficient;
98
99                // Apply friction to the tangential component of the velocity
100                // From Bridson et al., Robust Treatment of Collisions, Contact and
101                // Friction for Cloth Animation, 2002
102                // http://graphics.stanford.edu/papers/cloth-sig02/cloth.pdf
103                if relative_vel_t.length_squared() > 0.0 {
104                    let friction_scale = f64::max(
105                        1.0 - self.friction_coefficient() * delta_relative_vel_n.length() /
106                            relative_vel_t.length(),
107                        0.0);
108                    relative_vel_t *= friction_scale;
109                }
110
111                // Reassemble the components
112                *new_velocity = relative_vel_n + relative_vel_t + collider_vel_at_target_point;
113            }
114
115            // Geometric fix
116            *new_position = target_point;
117        }
118    }
119
120    //----------------------------------------------------------------------------------------------
121    /// Returns friction coefficient.
122    fn friction_coefficient(&self) -> f64 {
123        return self.view()._friction_coefficient;
124    }
125
126    /// Sets the friction coefficient.
127    ///
128    /// This function assigns the friction coefficient to the collider. Any
129    /// negative inputs will be clamped to zero.
130    fn set_friction_coefficient(&mut self, new_friction_coefficient: f64) {
131        self.view_mut()._friction_coefficient = new_friction_coefficient;
132    }
133
134    /// Returns the surface instance.
135    fn surface(&self) -> Surface2Ptr {
136        return self.view()._surface.as_ref().unwrap().clone();
137    }
138
139    /// Updates the collider state.
140    fn update(&mut self, current_time_in_seconds: f64,
141              time_interval_in_seconds: f64);
142
143    /// Assigns the surface instance from the subclass.
144    fn set_surface(&mut self, new_surface: Surface2Ptr) {
145        self.view_mut()._surface = Some(new_surface);
146    }
147
148    /// Outputs closest point's information.
149    fn get_closest_point(&self, surface: Surface2Ptr,
150                         query_point: &Vector2D,
151                         result: &mut ColliderQueryResult) {
152        result.distance = surface.read().unwrap().closest_distance(query_point);
153        result.point = surface.read().unwrap().closest_point(query_point);
154        result.normal = surface.read().unwrap().closest_normal(query_point);
155        result.velocity = self.velocity_at(query_point);
156    }
157
158    /// Returns true if given point is in the opposite side of the surface.
159    fn is_penetrating(&self, collider_point: &ColliderQueryResult,
160                      position: &Vector2D,
161                      radius: f64) -> bool {
162        // If the new candidate position of the particle is inside
163        // the volume defined by the surface OR the new distance to the surface is
164        // less than the particle's radius, this particle is in colliding state.
165        return self.surface().read().unwrap().is_inside(position) || collider_point.distance < radius;
166    }
167
168    //----------------------------------------------------------------------------------------------
169    fn view(&self) -> &Collider2Data;
170
171    fn view_mut(&mut self) -> &mut Collider2Data;
172}
173
174/// Shared pointer type for the Collider2.
175pub type Collider2Ptr = Arc<RwLock<dyn Collider2 + Send + Sync>>;