sparkl2d_kernels/cuda/
particle_updater.rs

1use crate::{cuda::InterpolatedParticleData, GpuColliderSet, GpuParticleModel};
2use sparkl_core::math::{Matrix, Real, Vector};
3use sparkl_core::prelude::{
4    ActiveTimestepBounds, ParticlePhase, ParticlePosition, ParticleStatus, ParticleVelocity,
5    ParticleVolume,
6};
7
8#[cfg(not(feature = "std"))]
9use na::ComplexField;
10
11pub trait ParticleUpdater {
12    fn artificial_pressure_stiffness(&self) -> f32 {
13        0.0
14    }
15
16    unsafe fn estimate_particle_timestep_length(
17        &self,
18        cell_width: Real,
19        particle_id: u32,
20        particle_status: &ParticleStatus,
21        particle_volume: &ParticleVolume,
22        particle_vel: &ParticleVelocity,
23    ) -> (ActiveTimestepBounds, Real);
24
25    unsafe fn update_particle_and_compute_kirchhoff_stress(
26        &self,
27        dt: Real,
28        cell_width: Real,
29        colliders: &GpuColliderSet,
30        particle_id: u32,
31        particle_status: &mut ParticleStatus,
32        particle_pos: &mut ParticlePosition,
33        particle_vel: &mut ParticleVelocity,
34        particle_volume: &mut ParticleVolume,
35        particle_phase: &mut ParticlePhase,
36        interpolated_data: &mut InterpolatedParticleData,
37    ) -> Option<(Matrix<Real>, Vector<Real>)>;
38}
39
40pub struct DefaultParticleUpdater {
41    pub models: *mut GpuParticleModel,
42}
43
44impl ParticleUpdater for DefaultParticleUpdater {
45    #[inline(always)]
46    unsafe fn estimate_particle_timestep_length(
47        &self,
48        cell_width: Real,
49        particle_id: u32,
50        particle_status: &ParticleStatus,
51        particle_volume: &ParticleVolume,
52        particle_vel: &ParticleVelocity,
53    ) -> (ActiveTimestepBounds, Real) {
54        let model = &*self.models.add(particle_status.model_index);
55        let active_timestep_bounds = model.constitutive_model.active_timestep_bounds();
56
57        if active_timestep_bounds.contains(ActiveTimestepBounds::CONSTITUTIVE_MODEL_BOUND) {
58            (
59                active_timestep_bounds,
60                model.constitutive_model.timestep_bound(
61                    particle_id,
62                    particle_volume,
63                    particle_vel,
64                    cell_width,
65                ),
66            )
67        } else {
68            (active_timestep_bounds, Real::MAX)
69        }
70    }
71
72    #[inline(always)]
73    unsafe fn update_particle_and_compute_kirchhoff_stress(
74        &self,
75        dt: Real,
76        cell_width: Real,
77        colliders: &GpuColliderSet,
78        particle_id: u32,
79        particle_status: &mut ParticleStatus,
80        particle_pos: &mut ParticlePosition,
81        particle_vel: &mut ParticleVelocity,
82        particle_volume: &mut ParticleVolume,
83        particle_phase: &mut ParticlePhase,
84        interpolated_data: &mut InterpolatedParticleData,
85    ) -> Option<(Matrix<Real>, Vector<Real>)> {
86        let model = &*self.models.add(particle_status.model_index);
87
88        particle_vel.vector = interpolated_data.velocity;
89
90        let is_fluid = model.constitutive_model.is_fluid();
91
92        /*
93         * Modified Eigenerosion.
94         */
95        // TODO: move this to a new failure model?
96        // if damage_model == DamageModel::ModifiedEigenerosion
97        //     && particle.crack_propagation_factor != 0.0
98        //     && particle_phase.phase > 0.0
99        // {
100        //     let crack_energy = particle.crack_propagation_factor * cell_width * psi_pos_momentum;
101        //     if crack_energy > particle.crack_threshold {
102        //         particle_phase.phase = 0.0;
103        //     }
104        // }
105
106        /*
107         * Advection.
108         */
109        if particle_status.kinematic_vel_enabled {
110            particle_vel.vector = particle_status.kinematic_vel;
111        }
112
113        if particle_vel
114            .vector
115            .iter()
116            .any(|x| x.abs() * dt >= cell_width)
117        {
118            particle_vel
119                .vector
120                .apply(|x| *x = x.signum() * cell_width / dt);
121        }
122
123        particle_pos.point += particle_vel.vector * dt;
124
125        /*
126         * Deformation gradient update.
127         */
128        if !is_fluid {
129            particle_volume.deformation_gradient +=
130                (interpolated_data.velocity_gradient * dt) * particle_volume.deformation_gradient;
131        } else {
132            particle_volume.deformation_gradient[(0, 0)] +=
133                (interpolated_data.velocity_gradient_det * dt)
134                    * particle_volume.deformation_gradient[(0, 0)];
135            model
136                .constitutive_model
137                .update_internal_energy_and_pressure(
138                    particle_id,
139                    particle_volume,
140                    dt,
141                    cell_width,
142                    &interpolated_data.velocity_gradient,
143                );
144        }
145
146        if let Some(plasticity) = &model.plastic_model {
147            plasticity.update_particle(particle_id, particle_volume, particle_phase.phase);
148        }
149
150        if particle_status.is_static {
151            particle_vel.vector.fill(0.0);
152            interpolated_data.velocity_gradient.fill(0.0);
153        }
154
155        if particle_volume.density_def_grad() == 0.0
156        || particle_status.failed
157        // Isolated particles tend to accumulate a huge amount of numerical
158        // error, leading to completely broken deformation gradients.
159        // Don’t let them destroy the whole simulation by setting them as failed.
160        || (!is_fluid && particle_volume.deformation_gradient[(0, 0)].abs() > 1.0e4)
161        {
162            particle_status.failed = true;
163            particle_volume.deformation_gradient = Matrix::identity();
164            return None;
165        }
166
167        /*
168         * Update Pos energy.
169         * TODO: refactor to its own function.
170         * TODO: should the crack propagation be part of its own kernel?
171         */
172        {
173            let energy = model.constitutive_model.pos_energy(
174                particle_id,
175                particle_volume,
176                particle_phase.phase,
177            );
178            particle_phase.psi_pos = particle_phase.psi_pos.max(energy);
179        }
180
181        /*
182         * Apply failure model.
183         * FIXME: refactor to its own function.
184         */
185        {
186            if let Some(failure_model) = &model.failure_model {
187                let stress = model.constitutive_model.kirchhoff_stress(
188                    particle_id,
189                    particle_volume,
190                    particle_phase.phase,
191                    &interpolated_data.velocity_gradient,
192                );
193                if failure_model.particle_failed(&stress) {
194                    particle_phase.phase = 0.0;
195                }
196            }
197        }
198
199        /*
200         * Particle projection.
201         * TODO: refactor to its own function.
202         */
203        let mut penalty_force = Vector::zeros();
204        if false {
205            // enable_boundary_particle_projection {
206            for collider in colliders.iter() {
207                if collider.penalty_stiffness > 0.0 {
208                    if let Some(proj) = collider.shape.project_point_with_max_dist(
209                        &collider.position,
210                        &particle_pos.point,
211                        false,
212                        100.0 * cell_width,
213                    ) {
214                        if proj.is_inside {
215                            penalty_force +=
216                                (proj.point - particle_pos.point) * collider.penalty_stiffness;
217                        }
218                    }
219                }
220            }
221        }
222
223        // MPM-MLS: the APIC affine matrix and the velocity gradient are the same.
224        if !particle_status.failed {
225            let stress = model.constitutive_model.kirchhoff_stress(
226                particle_id,
227                particle_volume,
228                particle_phase.phase,
229                &interpolated_data.velocity_gradient,
230            );
231
232            Some((stress, penalty_force))
233        } else {
234            Some((Matrix::zeros(), Vector::zeros()))
235        }
236    }
237}