salva3d/
liquid_world.rs

1use crate::counters::Counters;
2use crate::coupling::CouplingManager;
3use crate::geometry::{self, ContactManager, HGrid, HGridEntry};
4use crate::math::{Real, Vector};
5use crate::object::{Boundary, BoundaryHandle, BoundarySet};
6use crate::object::{Fluid, FluidHandle, FluidSet};
7use crate::solver::PressureSolver;
8use crate::TimestepManager;
9#[cfg(feature = "parry")]
10use {
11    crate::math::Isometry,
12    crate::object::ParticleId,
13    parry::{bounding_volume::Aabb, query::PointQuery, shape::Shape},
14};
15
16/// The physics world for simulating fluids with boundaries.
17pub struct LiquidWorld {
18    /// Performance counters of the whole fluid simulation engine.
19    pub counters: Counters,
20    nsubsteps_since_sort: usize,
21    particle_radius: Real,
22    h: Real,
23    fluids: FluidSet,
24    boundaries: BoundarySet,
25    solver: Box<dyn PressureSolver + Send + Sync>,
26    contact_manager: ContactManager,
27    timestep_manager: TimestepManager,
28    hgrid: HGrid<HGridEntry>,
29}
30
31impl LiquidWorld {
32    /// Initialize a new liquid world.
33    ///
34    /// # Parameters
35    ///
36    /// - `particle_radius`: the radius of every particle on this world.
37    /// - `smoothing_factor`: the smoothing factor used to compute the SPH kernel radius.
38    ///    The kernel radius will be computed as `particle_radius * smoothing_factor * 2.0.
39    pub fn new(
40        solver: impl PressureSolver + Send + Sync + 'static,
41        particle_radius: Real,
42        smoothing_factor: Real,
43    ) -> Self {
44        let h = particle_radius * smoothing_factor * na::convert::<_, Real>(2.0);
45        Self {
46            counters: Counters::new(),
47            nsubsteps_since_sort: 0,
48            particle_radius,
49            h,
50            fluids: FluidSet::new(),
51            boundaries: BoundarySet::new(),
52            solver: Box::new(solver),
53            contact_manager: ContactManager::new(),
54            timestep_manager: TimestepManager::new(particle_radius),
55            hgrid: HGrid::new(h),
56        }
57    }
58
59    /// Advances the simulation by `dt` milliseconds.
60    ///
61    /// All the fluid particles will be affected by an acceleration equal to `gravity`.
62    pub fn step(&mut self, dt: Real, gravity: &Vector<Real>) {
63        self.step_with_coupling(dt, gravity, &mut ())
64    }
65
66    /// Advances the simulation by `dt` milliseconds, taking into account coupling with an external rigid-body engine.
67    pub fn step_with_coupling(
68        &mut self,
69        dt: Real,
70        gravity: &Vector<Real>,
71        coupling: &mut impl CouplingManager,
72    ) {
73        self.counters.reset();
74        self.counters.step_time.start();
75        self.timestep_manager.reset(dt);
76
77        self.solver.init_with_fluids(self.fluids.as_slice());
78
79        for fluid in self.fluids.as_mut_slice() {
80            fluid.apply_particles_removal();
81        }
82
83        // Perform substeps.
84        while !self.timestep_manager.is_done() {
85            self.nsubsteps_since_sort += 1;
86            self.counters.nsubsteps += 1;
87
88            self.counters.stages.collision_detection_time.resume();
89            self.counters.cd.grid_insertion_time.resume();
90            self.hgrid.clear();
91            geometry::insert_fluids_to_grid(self.fluids.as_slice(), &mut self.hgrid);
92            self.counters.cd.grid_insertion_time.pause();
93
94            self.counters.cd.boundary_update_time.resume();
95            coupling.update_boundaries(
96                &self.timestep_manager,
97                self.h,
98                self.particle_radius,
99                &self.hgrid,
100                self.fluids.as_mut_slice(),
101                &mut self.boundaries,
102            );
103            self.counters.cd.boundary_update_time.pause();
104
105            self.counters.cd.grid_insertion_time.resume();
106            geometry::insert_boundaries_to_grid(self.boundaries.as_slice(), &mut self.hgrid);
107            self.counters.cd.grid_insertion_time.pause();
108
109            self.solver.init_with_boundaries(self.boundaries.as_slice());
110
111            self.contact_manager.update_contacts(
112                &mut self.counters,
113                self.h,
114                self.fluids.as_slice(),
115                self.boundaries.as_slice(),
116                &self.hgrid,
117            );
118
119            self.counters.cd.ncontacts = self.contact_manager.ncontacts();
120            self.counters.stages.collision_detection_time.pause();
121
122            self.counters.stages.solver_time.resume();
123            self.solver.evaluate_kernels(
124                self.h,
125                &mut self.contact_manager,
126                self.fluids.as_slice(),
127                self.boundaries.as_slice(),
128            );
129
130            self.solver.compute_densities(
131                &self.contact_manager,
132                self.fluids.as_slice(),
133                self.boundaries.as_mut_slice(),
134            );
135
136            self.solver.step(
137                &mut self.counters,
138                &mut self.timestep_manager,
139                gravity,
140                &mut self.contact_manager,
141                self.h,
142                self.fluids.as_mut_slice(),
143                self.boundaries.as_slice(),
144            );
145
146            coupling.transmit_forces(&self.timestep_manager, &self.boundaries);
147            self.counters.stages.solver_time.pause();
148        }
149
150        //        if self.nsubsteps_since_sort >= 100 {
151        //            self.nsubsteps_since_sort = 0;
152        //            println!("Performing z-sort of particles.");
153        //            par_iter_mut!(self.fluids.as_mut_slice()).for_each(|fluid| fluid.z_sort())
154        //        }
155
156        self.counters.step_time.pause();
157        //        println!("Counters: {}", self.counters);
158    }
159
160    /// Add a fluid to the liquid world.
161    pub fn add_fluid(&mut self, fluid: Fluid) -> FluidHandle {
162        self.fluids.insert(fluid)
163    }
164
165    /// Add a boundary to the liquid world.
166    pub fn add_boundary(&mut self, boundary: Boundary) -> BoundaryHandle {
167        self.boundaries.insert(boundary)
168    }
169
170    /// Add a fluid to the liquid world.
171    pub fn remove_fluid(&mut self, handle: FluidHandle) -> Option<Fluid> {
172        self.fluids.remove(handle)
173    }
174
175    /// Add a boundary to the liquid world.
176    pub fn remove_boundary(&mut self, handle: BoundaryHandle) -> Option<Boundary> {
177        self.boundaries.remove(handle)
178    }
179
180    /// The set of fluids on this liquid world.
181    pub fn fluids(&self) -> &FluidSet {
182        &self.fluids
183    }
184
185    /// The mutable set of fluids on this liquid world.
186    pub fn fluids_mut(&mut self) -> &mut FluidSet {
187        &mut self.fluids
188    }
189
190    /// The set of boundaries on this liquid world.
191    pub fn boundaries(&self) -> &BoundarySet {
192        &self.boundaries
193    }
194
195    /// The mutable set of boundaries on this liquid world.
196    pub fn boundaries_mut(&mut self) -> &mut BoundarySet {
197        &mut self.boundaries
198    }
199
200    /// The SPH kernel radius.
201    pub fn h(&self) -> Real {
202        self.h
203    }
204
205    /// The radius of every particle on this liquid world.
206    pub fn particle_radius(&self) -> Real {
207        self.particle_radius
208    }
209
210    /// The set of particles potentially intersecting the given AABB.
211    #[cfg(feature = "parry")]
212    pub fn particles_intersecting_aabb<'a>(
213        &'a self,
214        aabb: Aabb,
215    ) -> impl Iterator<Item = ParticleId> + 'a {
216        self.hgrid
217            .cells_intersecting_aabb(&aabb.mins, &aabb.maxs)
218            .flat_map(|e| e.1)
219            .filter_map(move |entry| match entry {
220                HGridEntry::FluidParticle(fid, pid) => {
221                    let (fluid, handle) = self.fluids.get_from_contiguous_index(*fid)?;
222                    let pt = fluid.positions[*pid];
223
224                    // FIXME: use `distance_to_local_point` once it's supported.
225                    let id = &Isometry::identity();
226                    if aabb.distance_to_point(id, &pt, true) < self.particle_radius {
227                        Some(ParticleId::FluidParticle(handle, *pid))
228                    } else {
229                        None
230                    }
231                }
232                HGridEntry::BoundaryParticle(bid, pid) => {
233                    let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?;
234                    let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported.
235                    let id = &Isometry::identity();
236                    if aabb.distance_to_point(id, &pt, true) < self.particle_radius {
237                        Some(ParticleId::BoundaryParticle(handle, *pid))
238                    } else {
239                        None
240                    }
241                }
242            })
243    }
244
245    /// The set of particles potentially intersecting the given shape.
246    #[cfg(feature = "parry")]
247    pub fn particles_intersecting_shape<'a, S: ?Sized>(
248        &'a self,
249        pos: &'a Isometry<Real>,
250        shape: &'a S,
251    ) -> impl Iterator<Item = ParticleId> + 'a
252    where
253        S: Shape,
254    {
255        let aabb = shape.compute_aabb(pos);
256        self.hgrid
257            .cells_intersecting_aabb(&aabb.mins, &aabb.maxs)
258            .flat_map(|e| e.1)
259            .filter_map(move |entry| match entry {
260                HGridEntry::FluidParticle(fid, pid) => {
261                    let (fluid, handle) = self.fluids.get_from_contiguous_index(*fid)?;
262                    let pt = fluid.positions[*pid];
263
264                    if shape.distance_to_point(pos, &pt, true) <= self.particle_radius {
265                        Some(ParticleId::FluidParticle(handle, *pid))
266                    } else {
267                        None
268                    }
269                }
270                HGridEntry::BoundaryParticle(bid, pid) => {
271                    let (boundary, handle) = self.boundaries.get_from_contiguous_index(*bid)?;
272                    let pt = boundary.positions[*pid]; // FIXME: use `distance_to_local_point` once it's supported.
273                    if shape.distance_to_point(pos, &pt, true) <= self.particle_radius {
274                        Some(ParticleId::BoundaryParticle(handle, *pid))
275                    } else {
276                        None
277                    }
278                }
279            })
280    }
281}
282
283#[test]
284fn world_is_send_and_sync() {
285    fn check<T: Send + Sync>() {}
286    check::<LiquidWorld>();
287}