vox_geometry_rust/
particle_system_solver3.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::vector3::Vector3D;
10use crate::particle_system_data3::*;
11use crate::collider3::Collider3Ptr;
12use crate::particle_emitter3::ParticleEmitter3Ptr;
13use crate::vector_field3::VectorField3Ptr;
14use crate::constant_vector_field3::ConstantVectorField3;
15use crate::animation::{Animation, Frame};
16use crate::physics_animation::{PhysicsAnimation, PhysicsAnimationData};
17use log::info;
18use std::time::SystemTime;
19use std::sync::{RwLock, Arc};
20use rayon::prelude::*;
21
22///
23/// # Basic 3-D particle system solver.
24///
25/// This class implements basic particle system solver. It includes gravity,
26/// air drag, and collision. But it does not compute particle-to-particle
27/// interaction. Thus, this solver is suitable for performing simple spray-like
28/// simulations with low computational cost. This class can be further extend
29/// to add more sophisticated simulations, such as SPH, to handle
30/// particle-to-particle intersection.
31///
32/// \see        SphSolver3
33///
34pub struct ParticleSystemSolver3 {
35    pub _drag_coefficient: f64,
36    pub _restitution_coefficient: f64,
37    pub _gravity: Vector3D,
38
39    pub _particle_system_data: ParticleSystemData3Ptr,
40    pub _new_positions: Vec<Vector3D>,
41    pub _new_velocities: Vec<Vector3D>,
42    pub _collider: Option<Collider3Ptr>,
43    pub _emitter: Option<ParticleEmitter3Ptr>,
44    pub _wind: VectorField3Ptr,
45
46    pub _animation_data: PhysicsAnimationData,
47}
48
49impl ParticleSystemSolver3 {
50    /// Constructs an empty solver.
51    pub fn new_default() -> ParticleSystemSolver3 {
52        return ParticleSystemSolver3::new(1.0e-3, 1.0e-3);
53    }
54
55    /// Constructs a solver with particle parameters.
56    pub fn new(radius: f64,
57               mass: f64) -> ParticleSystemSolver3 {
58        let data = Arc::new(RwLock::new(ParticleSystemData3::new_default()));
59        data.write().unwrap().set_radius(radius);
60        data.write().unwrap().set_mass(mass);
61        let wind = Arc::new(RwLock::new(ConstantVectorField3::new(Some(Vector3D::new_default()))));
62        return ParticleSystemSolver3 {
63            _drag_coefficient: 1.0e-4,
64            _restitution_coefficient: 0.0,
65            _gravity: Vector3D::new(0.0, crate::constants::K_GRAVITY, 0.0),
66            _particle_system_data: data,
67            _new_positions: vec![],
68            _new_velocities: vec![],
69            _collider: None,
70            _emitter: None,
71            _wind: wind,
72            _animation_data: PhysicsAnimationData::new(),
73        };
74    }
75
76    /// Returns builder fox ParticleSystemSolver3.
77    pub fn builder() -> Builder {
78        return Builder::new();
79    }
80}
81
82impl ParticleSystemSolver3 {
83    /// Returns the drag coefficient.
84    pub fn drag_coefficient(&self) -> f64 {
85        return self._drag_coefficient;
86    }
87
88    ///
89    /// \brief      Sets the drag coefficient.
90    ///
91    /// The drag coefficient controls the amount of air-drag. The coefficient
92    /// should be a positive number and 0 means no drag force.
93    ///
94    /// - parameter:  new_drag_coefficient The new drag coefficient.
95    ///
96    pub fn set_drag_coefficient(&mut self, new_drag_coefficient: f64) {
97        self._drag_coefficient = f64::max(new_drag_coefficient, 0.0);
98    }
99
100    /// Sets the restitution coefficient.
101    pub fn restitution_coefficient(&self) -> f64 {
102        return self._restitution_coefficient;
103    }
104
105    ///
106    /// \brief      Sets the restitution coefficient.
107    ///
108    /// The restitution coefficient controls the bouncy-ness of a particle when
109    /// it hits a collider surface. The range of the coefficient should be 0 to
110    /// 1 -- 0 means no bounce back and 1 means perfect reflection.
111    ///
112    /// - parameter:  new_restitution_coefficient The new restitution coefficient.
113    ///
114    pub fn set_restitution_coefficient(&mut self, new_restitution_coefficient: f64) {
115        self._restitution_coefficient = crate::math_utils::clamp(new_restitution_coefficient, 0.0, 1.0);
116    }
117
118    /// Returns the gravity.
119    pub fn gravity(&self) -> &Vector3D {
120        return &self._gravity;
121    }
122
123    /// Sets the gravity.
124    pub fn set_gravity(&mut self, new_gravity: &Vector3D) {
125        self._gravity = *new_gravity;
126    }
127
128    ///
129    /// \brief      Returns the particle system data.
130    ///
131    /// This function returns the particle system data. The data is created when
132    /// this solver is constructed and also owned by the solver.
133    ///
134    /// \return     The particle system data.
135    ///
136    pub fn particle_system_data(&self) -> &ParticleSystemData3Ptr {
137        return &self._particle_system_data;
138    }
139
140    /// Returns the collider.
141    pub fn collider(&self) -> &Option<Collider3Ptr> {
142        return &self._collider;
143    }
144
145    /// Sets the collider.
146    pub fn set_collider(&mut self, new_collider: &Collider3Ptr) {
147        self._collider = Some(new_collider.clone());
148    }
149
150    /// Returns the emitter.
151    pub fn emitter(&self) -> &Option<ParticleEmitter3Ptr> {
152        return &self._emitter;
153    }
154
155    /// Sets the emitter.
156    pub fn set_emitter(&mut self, new_emitter: &ParticleEmitter3Ptr) {
157        self._emitter = Some(new_emitter.clone());
158        new_emitter.write().unwrap().set_target(self._particle_system_data.clone());
159    }
160
161    /// Returns the wind field.
162    pub fn wind(&self) -> &VectorField3Ptr {
163        return &self._wind;
164    }
165
166    ///
167    /// \brief      Sets the wind.
168    ///
169    /// Wind can be applied to the particle system by setting a vector field to
170    /// the solver.
171    ///
172    /// - parameter:  new_wind The new wind.
173    ///
174    pub fn set_wind(&mut self, new_wind: &VectorField3Ptr) {
175        self._wind = new_wind.clone();
176    }
177}
178
179impl Animation for ParticleSystemSolver3 {
180    fn on_update(&mut self, frame: &Frame) {
181        PhysicsAnimation::on_update(self, frame);
182    }
183}
184
185impl PhysicsAnimation for ParticleSystemSolver3 {
186    fn on_advance_time_step(&mut self, time_step_in_seconds: f64) {
187        self.begin_advance_time_step(time_step_in_seconds);
188
189        let mut timer = SystemTime::now();
190        self.accumulate_forces(time_step_in_seconds);
191        info!("Accumulating forces took {} seconds", timer.elapsed().unwrap().as_secs_f64());
192
193        timer = SystemTime::now();
194        self.time_integration(time_step_in_seconds);
195        info!("Time integration took {} seconds", timer.elapsed().unwrap().as_secs_f64());
196
197        timer = SystemTime::now();
198        self.resolve_collision();
199        info!("Resolving collision took {} seconds", timer.elapsed().unwrap().as_secs_f64());
200
201        self.end_advance_time_step(time_step_in_seconds);
202    }
203
204    fn on_initialize(&mut self) {
205        // When initializing the solver, update the collider and emitter state as
206        // well since they also affects the initial condition of the simulation.
207        let mut timer = SystemTime::now();
208        self.update_collider(0.0);
209        info!("Update collider took {} seconds", timer.elapsed().unwrap().as_secs_f64());
210
211        timer = SystemTime::now();
212        self.update_emitter(0.0);
213        info!("Update emitter took {} seconds", timer.elapsed().unwrap().as_secs_f64());
214    }
215
216    fn view(&self) -> &PhysicsAnimationData {
217        return &self._animation_data;
218    }
219
220    fn view_mut(&mut self) -> &mut PhysicsAnimationData {
221        return &mut self._animation_data;
222    }
223}
224
225impl ParticleSystemSolver3 {
226    /// Accumulates forces applied to the particles.
227    fn accumulate_forces(&mut self, _: f64) {
228        // Add external forces
229        self.accumulate_external_forces();
230    }
231
232    /// Called when a time-step is about to begin.
233    fn on_begin_advance_time_step(&mut self, _: f64) {}
234
235    /// Called after a time-step is completed.
236    fn on_end_advance_time_step(&mut self, _: f64) {}
237
238    /// Resolves any collisions occurred by the particles.
239    fn resolve_collision(&mut self) {
240        if let Some(collider) = &self._collider {
241            let radius = self._particle_system_data.read().unwrap().radius();
242            let restitution = self._restitution_coefficient;
243            (&mut self._new_positions, &mut self._new_velocities).into_par_iter().for_each(|(pos, vel)| {
244                collider.read().unwrap().resolve_collision(
245                    radius,
246                    restitution,
247                    pos,
248                    vel);
249            });
250        }
251    }
252
253    fn begin_advance_time_step(&mut self, time_step_in_seconds: f64) {
254        // Clear forces
255        self._particle_system_data.write().unwrap().forces_mut().fill(Vector3D::new_default());
256
257        // Update collider and emitter
258        let mut timer = SystemTime::now();
259        self.update_collider(time_step_in_seconds);
260        info!("Update collider took {} seconds", timer.elapsed().unwrap().as_secs_f64());
261
262        timer = SystemTime::now();
263        self.update_emitter(time_step_in_seconds);
264        info!("Update emitter took {} seconds", timer.elapsed().unwrap().as_secs_f64());
265
266        // Allocate buffers
267        let n = self._particle_system_data.read().unwrap().number_of_particles();
268        self._new_positions.resize(n, Vector3D::new_default());
269        self._new_velocities.resize(n, Vector3D::new_default());
270
271        self.on_begin_advance_time_step(time_step_in_seconds);
272    }
273
274    fn end_advance_time_step(&mut self, time_step_in_seconds: f64) {
275        // Update data
276        (self._particle_system_data.write().unwrap().positions_mut(),
277         self._particle_system_data.write().unwrap().velocities_mut(),
278         &self._new_positions, &self._new_velocities).into_par_iter().for_each(|(pos, vel, pos_new, vel_new)| {
279            *pos = *pos_new;
280            *vel = *vel_new;
281        });
282
283        self.on_end_advance_time_step(time_step_in_seconds);
284    }
285
286    fn accumulate_external_forces(&mut self) {
287        let mass = self._particle_system_data.read().unwrap().mass();
288
289        (self._particle_system_data.write().unwrap().forces_mut(),
290         self._particle_system_data.read().unwrap().positions(),
291         self._particle_system_data.read().unwrap().velocities()).into_par_iter().for_each(|(force, pos, vel)| {
292            // Gravity
293            let mut f = self._gravity * mass;
294
295            // Wind forces
296            let relative_vel = *vel - self._wind.read().unwrap().sample(pos);
297            f += relative_vel * -self._drag_coefficient;
298
299            *force += f;
300        });
301    }
302
303    fn time_integration(&mut self, time_step_in_seconds: f64) {
304        let mass = self._particle_system_data.read().unwrap().mass();
305
306        (&mut self._new_positions, &mut self._new_velocities,
307         self._particle_system_data.read().unwrap().forces(),
308         self._particle_system_data.read().unwrap().positions(),
309         self._particle_system_data.read().unwrap().velocities()).into_par_iter().for_each(|(new_pos, new_vel, force, pos, vel)| {
310            // Integrate velocity first
311            *new_vel = *vel + *force * time_step_in_seconds / mass;
312
313            // Integrate position.
314            *new_pos = *pos + *new_vel * time_step_in_seconds;
315        });
316    }
317
318    fn update_collider(&mut self, time_step_in_seconds: f64) {
319        if let Some(collider) = &self._collider {
320            collider.write().unwrap().update(self.current_time_in_seconds(),
321                                             time_step_in_seconds);
322        }
323    }
324
325    fn update_emitter(&mut self, time_step_in_seconds: f64) {
326        if let Some(emitter) = &self._emitter {
327            emitter.write().unwrap().update(self.current_time_in_seconds(),
328                                            time_step_in_seconds);
329        }
330    }
331}
332
333/// Shared pointer type for the ParticleSystemSolver3.
334pub type ParticleSystemSolver3Ptr = Arc<RwLock<ParticleSystemSolver3>>;
335
336//--------------------------------------------------------------------------------------------------
337///
338/// # Base class for particle-based solver builder.
339///
340pub trait ParticleSystemSolverBuilderBase3 {
341    /// Returns builder with particle radius.
342    fn with_radius(&mut self, radius: f64) -> &mut Self;
343
344    /// Returns builder with mass per particle.
345    fn with_mass(&mut self, mass: f64) -> &mut Self;
346}
347
348///
349/// # Front-end to create ParticleSystemSolver3 objects step by step.
350///
351pub struct Builder {
352    _radius: f64,
353    _mass: f64,
354}
355
356impl Builder {
357    /// Builds ParticleSystemSolver3.
358    pub fn build(&mut self) -> ParticleSystemSolver3 {
359        return ParticleSystemSolver3::new(self._radius, self._mass);
360    }
361
362    /// Builds shared pointer of ParticleSystemSolver3 instance.
363    pub fn make_shared(&mut self) -> ParticleSystemSolver3Ptr {
364        return ParticleSystemSolver3Ptr::new(RwLock::new(self.build()));
365    }
366
367    /// constructor
368    pub fn new() -> Builder {
369        return Builder {
370            _radius: 1.0e-3,
371            _mass: 1.0e-3,
372        };
373    }
374}
375
376impl ParticleSystemSolverBuilderBase3 for Builder {
377    fn with_radius(&mut self, radius: f64) -> &mut Self {
378        self._radius = radius;
379        return self;
380    }
381
382    fn with_mass(&mut self, mass: f64) -> &mut Self {
383        self._mass = mass;
384        return self;
385    }
386}