use crate::vector3::Vector3D;
use crate::particle_system_solver3::ParticleSystemSolver3;
use crate::sph_system_data3::*;
use crate::collider3::Collider3Ptr;
use crate::particle_emitter3::ParticleEmitter3Ptr;
use crate::vector_field3::VectorField3Ptr;
use crate::sph_kernels3::SphSpikyKernel3;
use crate::animation::*;
use crate::physics_animation::*;
use std::sync::{RwLock, Arc};
use std::time::SystemTime;
use log::info;
use rayon::prelude::*;
pub struct SphSolver3 {
_eos_exponent: f64,
_negative_pressure_scale: f64,
_viscosity_coefficient: f64,
_pseudo_viscosity_coefficient: f64,
_speed_of_sound: f64,
_time_step_limit_scale: f64,
_base_solver: ParticleSystemSolver3,
_particle_system_data: SphSystemData3Ptr,
}
impl SphSolver3 {
pub fn new_default() -> SphSolver3 {
let mut solver = SphSolver3 {
_eos_exponent: 7.0,
_negative_pressure_scale: 0.0,
_viscosity_coefficient: 0.01,
_pseudo_viscosity_coefficient: 10.0,
_speed_of_sound: 100.0,
_time_step_limit_scale: 1.0,
_base_solver: ParticleSystemSolver3::new_default(),
_particle_system_data: SphSystemData3Ptr::new(RwLock::new(SphSystemData3::new_default())),
};
solver.set_is_using_fixed_sub_time_steps(false);
return solver;
}
pub fn new(target_density: f64,
target_spacing: f64,
relative_kernel_radius: f64) -> SphSolver3 {
let sph_particles = SphSystemData3Ptr::new(RwLock::new(SphSystemData3::new_default()));
sph_particles.write().unwrap().set_target_density(target_density);
sph_particles.write().unwrap().set_target_spacing(target_spacing);
sph_particles.write().unwrap().set_relative_kernel_radius(relative_kernel_radius);
let mut solver = SphSolver3 {
_eos_exponent: 7.0,
_negative_pressure_scale: 0.0,
_viscosity_coefficient: 0.01,
_pseudo_viscosity_coefficient: 10.0,
_speed_of_sound: 100.0,
_time_step_limit_scale: 1.0,
_base_solver: ParticleSystemSolver3::new_default(),
_particle_system_data: sph_particles,
};
solver.set_is_using_fixed_sub_time_steps(false);
return solver;
}
}
impl SphSolver3 {
pub fn drag_coefficient(&self) -> f64 {
return self._base_solver.drag_coefficient();
}
pub fn set_drag_coefficient(&mut self, new_drag_coefficient: f64) {
self._base_solver.set_drag_coefficient(f64::max(new_drag_coefficient, 0.0));
}
pub fn restitution_coefficient(&self) -> f64 {
return self._base_solver.restitution_coefficient();
}
pub fn set_restitution_coefficient(&mut self, new_restitution_coefficient: f64) {
self._base_solver.set_restitution_coefficient(crate::math_utils::clamp(new_restitution_coefficient, 0.0, 1.0));
}
pub fn gravity(&self) -> &Vector3D {
return self._base_solver.gravity();
}
pub fn set_gravity(&mut self, new_gravity: &Vector3D) {
self._base_solver.set_gravity(new_gravity);
}
pub fn particle_system_data(&self) -> &SphSystemData3Ptr {
return &self._particle_system_data;
}
pub fn collider(&self) -> &Option<Collider3Ptr> {
return self._base_solver.collider();
}
pub fn set_collider(&mut self, new_collider: &Collider3Ptr) {
self._base_solver.set_collider(new_collider);
}
pub fn emitter(&self) -> &Option<ParticleEmitter3Ptr> {
return self._base_solver.emitter();
}
pub fn set_emitter(&mut self, new_emitter: &ParticleEmitter3Ptr) {
self._base_solver.set_emitter(new_emitter);
}
pub fn wind(&self) -> &VectorField3Ptr {
return self._base_solver.wind();
}
pub fn set_wind(&mut self, new_wind: &VectorField3Ptr) {
self._base_solver.set_wind(new_wind);
}
}
impl SphSolver3 {
pub fn eos_exponent(&self) -> f64 {
return self._eos_exponent;
}
pub fn set_eos_exponent(&mut self, new_eos_exponent: f64) {
self._eos_exponent = f64::max(new_eos_exponent, 1.0);
}
pub fn negative_pressure_scale(&self) -> f64 {
return self._negative_pressure_scale;
}
pub fn set_negative_pressure_scale(&mut self, new_negative_pressure_scale: f64) {
self._negative_pressure_scale = crate::math_utils::clamp(new_negative_pressure_scale, 0.0, 1.0);
}
pub fn viscosity_coefficient(&self) -> f64 {
return self._viscosity_coefficient;
}
pub fn set_viscosity_coefficient(&mut self, new_viscosity_coefficient: f64) {
self._viscosity_coefficient = f64::max(new_viscosity_coefficient, 0.0);
}
pub fn pseudo_viscosity_coefficient(&self) -> f64 {
return self._pseudo_viscosity_coefficient;
}
pub fn set_pseudo_viscosity_coefficient(&mut self, new_pseudo_viscosity_coefficient: f64) {
self._pseudo_viscosity_coefficient = f64::max(new_pseudo_viscosity_coefficient, 0.0);
}
pub fn speed_of_sound(&self) -> f64 {
return self._speed_of_sound;
}
pub fn set_speed_of_sound(&mut self, new_speed_of_sound: f64) {
self._speed_of_sound = f64::max(new_speed_of_sound, f64::EPSILON);
}
pub fn time_step_limit_scale(&self) -> f64 {
return self._time_step_limit_scale;
}
pub fn set_time_step_limit_scale(&mut self, new_scale: f64) {
self._time_step_limit_scale = f64::max(new_scale, 0.0);
}
}
impl Animation for SphSolver3 {
fn on_update(&mut self, frame: &Frame) {
PhysicsAnimation::on_update(self, frame);
}
}
impl PhysicsAnimation for SphSolver3 {
fn on_advance_time_step(&mut self, time_step_in_seconds: f64) {
self.begin_advance_time_step(time_step_in_seconds);
let mut timer = SystemTime::now();
self.accumulate_forces(time_step_in_seconds);
info!("Accumulating forces took {} seconds", timer.elapsed().unwrap().as_secs_f64());
timer = SystemTime::now();
self.time_integration(time_step_in_seconds);
info!("Time integration took {} seconds", timer.elapsed().unwrap().as_secs_f64());
timer = SystemTime::now();
self.resolve_collision();
info!("Resolving collision took {} seconds", timer.elapsed().unwrap().as_secs_f64());
self.end_advance_time_step(time_step_in_seconds);
}
fn number_of_sub_time_steps(&self,
time_interval_in_seconds: f64) -> usize {
let number_of_particles = self._particle_system_data.read().unwrap().number_of_particles();
let kernel_radius = self._particle_system_data.read().unwrap().kernel_radius();
let mass = self._particle_system_data.read().unwrap().mass();
let mut max_force_magnitude = 0.0;
for i in 0..number_of_particles {
max_force_magnitude = f64::max(max_force_magnitude,
self._particle_system_data.read().unwrap().forces()[i].length());
}
let time_step_limit_by_speed = K_TIME_STEP_LIMIT_BY_SPEED_FACTOR * kernel_radius / self._speed_of_sound;
let time_step_limit_by_force = K_TIME_STEP_LIMIT_BY_FORCE_FACTOR * f64::sqrt(kernel_radius * mass / max_force_magnitude);
let desired_time_step = self._time_step_limit_scale * f64::min(time_step_limit_by_speed, time_step_limit_by_force);
return f64::ceil(time_interval_in_seconds / desired_time_step) as usize;
}
fn on_initialize(&mut self) {
let mut timer = SystemTime::now();
self.update_collider(0.0);
info!("Update collider took {} seconds", timer.elapsed().unwrap().as_secs_f64());
timer = SystemTime::now();
self.update_emitter(0.0);
info!("Update emitter took {} seconds", timer.elapsed().unwrap().as_secs_f64());
}
fn view(&self) -> &PhysicsAnimationData {
return self._base_solver.view();
}
fn view_mut(&mut self) -> &mut PhysicsAnimationData {
return self._base_solver.view_mut();
}
}
impl SphSolver3 {
fn resolve_collision(&mut self) {
if let Some(collider) = &self._base_solver.collider() {
let collider = collider.clone();
let radius = self._particle_system_data.read().unwrap().radius();
let restitution = self._base_solver.restitution_coefficient();
(&mut self._base_solver._new_positions, &mut self._base_solver._new_velocities).into_par_iter().for_each(|(pos, vel)| {
collider.read().unwrap().resolve_collision(
radius,
restitution,
pos,
vel);
});
}
}
fn begin_advance_time_step(&mut self, time_step_in_seconds: f64) {
self._particle_system_data.write().unwrap().forces_mut().fill(Vector3D::new_default());
let mut timer = SystemTime::now();
self.update_collider(time_step_in_seconds);
info!("Update collider took {} seconds", timer.elapsed().unwrap().as_secs_f64());
timer = SystemTime::now();
self.update_emitter(time_step_in_seconds);
info!("Update emitter took {} seconds", timer.elapsed().unwrap().as_secs_f64());
let n = self._particle_system_data.read().unwrap().number_of_particles();
self._base_solver._new_positions.resize(n, Vector3D::new_default());
self._base_solver._new_velocities.resize(n, Vector3D::new_default());
self.on_begin_advance_time_step(time_step_in_seconds);
}
fn end_advance_time_step(&mut self, time_step_in_seconds: f64) {
(self._particle_system_data.write().unwrap().positions_mut(),
self._particle_system_data.write().unwrap().velocities_mut(),
&self._base_solver._new_positions, &self._base_solver._new_velocities).into_par_iter().for_each(|(pos, vel, pos_new, vel_new)| {
*pos = *pos_new;
*vel = *vel_new;
});
self.on_end_advance_time_step(time_step_in_seconds);
}
fn accumulate_external_forces(&mut self) {
let mass = self._particle_system_data.read().unwrap().mass();
(self._particle_system_data.write().unwrap().forces_mut(),
self._particle_system_data.read().unwrap().positions(),
self._particle_system_data.read().unwrap().velocities()).into_par_iter().for_each(|(force, pos, vel)| {
let mut f = self._base_solver._gravity * mass;
let relative_vel = *vel - self._base_solver._wind.read().unwrap().sample(pos);
f += relative_vel * -self._base_solver._drag_coefficient;
*force += f;
});
}
fn time_integration(&mut self, time_step_in_seconds: f64) {
let mass = self._particle_system_data.read().unwrap().mass();
(&mut self._base_solver._new_positions, &mut self._base_solver._new_velocities,
self._particle_system_data.read().unwrap().forces(),
self._particle_system_data.read().unwrap().positions(),
self._particle_system_data.read().unwrap().velocities()).into_par_iter().for_each(|(new_pos, new_vel, force, pos, vel)| {
*new_vel = *vel + *force * time_step_in_seconds / mass;
*new_pos = *pos + *new_vel * time_step_in_seconds;
});
}
fn update_collider(&mut self, time_step_in_seconds: f64) {
if let Some(collider) = &self._base_solver._collider {
collider.write().unwrap().update(self.current_time_in_seconds(),
time_step_in_seconds);
}
}
fn update_emitter(&mut self, time_step_in_seconds: f64) {
if let Some(emitter) = &self._base_solver._emitter {
emitter.write().unwrap().update(self.current_time_in_seconds(),
time_step_in_seconds);
}
}
}
const K_TIME_STEP_LIMIT_BY_SPEED_FACTOR: f64 = 0.4;
const K_TIME_STEP_LIMIT_BY_FORCE_FACTOR: f64 = 0.35;
impl SphSolver3 {
fn accumulate_forces(&mut self, time_step_in_seconds: f64) {
self.accumulate_non_pressure_forces(time_step_in_seconds);
self.accumulate_pressure_force(time_step_in_seconds);
}
fn on_begin_advance_time_step(&self, _: f64) {
let timer = SystemTime::now();
self._particle_system_data.write().unwrap().build_neighbor_searcher();
self._particle_system_data.write().unwrap().build_neighbor_lists();
self._particle_system_data.write().unwrap().update_densities();
info!("Building neighbor lists and updating densities took {} seconds", timer.elapsed().unwrap().as_secs_f64());
}
fn on_end_advance_time_step(&self, time_step_in_seconds: f64) {
self.compute_pseudo_viscosity(time_step_in_seconds);
let number_of_particles = self._particle_system_data.read().unwrap().number_of_particles();
let mut max_density = 0.0;
for i in 0..number_of_particles {
max_density = f64::max(max_density, self._particle_system_data.read().unwrap().densities()[i]);
}
info!("Max density: {} Max density / target density ratio: {}", max_density,
max_density / self._particle_system_data.read().unwrap().target_density());
}
fn accumulate_non_pressure_forces(&mut self, _: f64) {
self.accumulate_external_forces();
self.accumulate_viscosity_force();
}
fn accumulate_pressure_force(&self, _: f64) {
self.compute_pressure();
self.accumulate_pressure_force_new(
self._particle_system_data.read().unwrap().positions(),
self._particle_system_data.read().unwrap().densities(),
self._particle_system_data.read().unwrap().pressures(),
self._particle_system_data.write().unwrap().forces_mut());
}
fn compute_pressure(&self) {
let target_density = self._particle_system_data.read().unwrap().target_density();
let eos_scale = target_density * crate::math_utils::square(self._speed_of_sound);
(self._particle_system_data.write().unwrap().pressures_mut(),
self._particle_system_data.read().unwrap().densities()).into_par_iter().for_each(|(p, d)| {
*p = crate::physics_helpers::compute_pressure_from_eos(
*d,
target_density,
eos_scale,
self.eos_exponent(),
self.negative_pressure_scale());
});
}
fn accumulate_pressure_force_new(&self, positions: &Vec<Vector3D>,
densities: &Vec<f64>,
pressures: &Vec<f64>,
pressure_forces: &mut Vec<Vector3D>) {
let number_of_particles = self._particle_system_data.read().unwrap().number_of_particles();
let mass_squared = crate::math_utils::square(self._particle_system_data.read().unwrap().mass());
let kernel = SphSpikyKernel3::new(self._particle_system_data.read().unwrap().kernel_radius());
(pressure_forces,
self._particle_system_data.read().unwrap().neighbor_lists(),
0..number_of_particles).into_par_iter().for_each(|(f, neighbors, index)| {
for j in neighbors {
let dist = positions[index].distance_to(positions[*j]);
if dist > 0.0 {
let dir = (positions[*j] - positions[index]) / dist;
*f -= kernel.gradient_dir(dist, &dir)
* mass_squared
* (pressures[index] / (densities[index] * densities[index]) + pressures[*j] / (densities[*j] * densities[*j]));
}
}
});
}
fn accumulate_viscosity_force(&self) {
let number_of_particles = self._particle_system_data.read().unwrap().number_of_particles();
let x = self._particle_system_data.read().unwrap().positions().clone();
let v = self._particle_system_data.read().unwrap().velocities().clone();
let d = self._particle_system_data.read().unwrap().densities().clone();
let mass_squared = crate::math_utils::square(self._particle_system_data.read().unwrap().mass());
let kernel = SphSpikyKernel3::new(self._particle_system_data.read().unwrap().kernel_radius());
(self._particle_system_data.write().unwrap().forces_mut(),
self._particle_system_data.read().unwrap().neighbor_lists(),
0..number_of_particles).into_par_iter().for_each(|(f, neighbors, index)| {
for j in neighbors {
let dist = x[index].distance_to(x[*j]);
*f += (v[*j] - v[index]) * self.viscosity_coefficient() * mass_squared / d[*j]
* kernel.second_derivative(dist);
}
});
}
fn compute_pseudo_viscosity(&self, time_step_in_seconds: f64) {
let number_of_particles = self._particle_system_data.read().unwrap().number_of_particles();
let x = self._particle_system_data.read().unwrap().positions().clone();
let v = self._particle_system_data.read().unwrap().velocities().clone();
let d = self._particle_system_data.read().unwrap().densities().clone();
let mass = self._particle_system_data.read().unwrap().mass();
let kernel = SphSpikyKernel3::new(self._particle_system_data.read().unwrap().kernel_radius());
let mut smoothed_velocities: Vec<Vector3D> = Vec::new();
smoothed_velocities.resize(number_of_particles, Vector3D::new_default());
(&mut smoothed_velocities,
self._particle_system_data.read().unwrap().neighbor_lists(),
0..number_of_particles).into_par_iter().for_each(|(vel, neighbors, index)| {
let mut weight_sum = 0.0;
let mut smoothed_velocity = Vector3D::new_default();
for j in neighbors {
let dist = x[index].distance_to(x[*j]);
let wj = mass / d[*j] * kernel.apply(dist);
weight_sum += wj;
smoothed_velocity += v[*j] * wj;
}
let wi = mass / d[index];
weight_sum += wi;
smoothed_velocity += v[index] * wi;
if weight_sum > 0.0 {
smoothed_velocity /= weight_sum;
}
*vel = smoothed_velocity;
});
let mut factor = time_step_in_seconds * self._pseudo_viscosity_coefficient;
factor = crate::math_utils::clamp(factor, 0.0, 1.0);
(self._particle_system_data.write().unwrap().velocities_mut(),
&mut smoothed_velocities).into_par_iter().for_each(|(vel, smooth_vel)| {
*vel = *vel * (1.0 - factor) + *smooth_vel * factor;
});
}
}
pub type SphSolver3Ptr = Arc<RwLock<SphSolver3>>;