alistair
9 months ago
4 changed files with 2051 additions and 1806 deletions
@ -0,0 +1,341 @@
@@ -0,0 +1,341 @@
|
||||
|
||||
#include "svector.h" |
||||
#include "type.h" |
||||
#include <span> |
||||
#include <tuple> |
||||
#include <vector> |
||||
|
||||
/**
|
||||
* Simplified SPH |
||||
*/ |
||||
|
||||
namespace msph { |
||||
|
||||
struct sph_settings { |
||||
float timestep = 0.00031; // * h / 90;
|
||||
float rest_density = 1000; |
||||
float visc_constant = 0.0007; |
||||
float stiffness_k = 1100; |
||||
float h = 0.051; // smoothing radius
|
||||
}; |
||||
|
||||
struct domain { |
||||
float room_width = 0.2; |
||||
float origin = 5; |
||||
}; |
||||
|
||||
struct sim_domain { |
||||
float origin; |
||||
float room_width; |
||||
}; |
||||
|
||||
class sph_data { |
||||
private: |
||||
std::vector<particle> particles; |
||||
struct cell_count { |
||||
unsigned count; |
||||
/* to ensure each cell only gets checked once */ |
||||
bool checked = 0; |
||||
}; |
||||
|
||||
// radix sorted
|
||||
std::vector<cell_count> cell_counts{}; |
||||
|
||||
// map from hash to particle index
|
||||
std::vector<unsigned> sorted_particles{}; |
||||
size_t backing_array_size; |
||||
|
||||
// grid diameter
|
||||
float diameter; |
||||
float inv_diameter; |
||||
|
||||
unsigned hash_func(Vec3 v) { |
||||
return abs((92837111 * (int)(v.x * inv_diameter)) ^ |
||||
(689287499 * (int)(v.y * inv_diameter)) ^ |
||||
(283923481 * (int)(v.z * inv_diameter))); |
||||
} |
||||
|
||||
unsigned cell_id(Vec3 v) { return hash_func(v) % backing_array_size; } |
||||
|
||||
public: |
||||
sph_data(float diameter, size_t num_particles) |
||||
: diameter(diameter), inv_diameter(1 / diameter), |
||||
backing_array_size(2 * num_particles) {} |
||||
std::vector<particle> &get_particles() { return particles; } |
||||
|
||||
int repair_map() { |
||||
// https://matthias-research.github.io/pages/tenMinutePhysics/11-hashing.pdf
|
||||
|
||||
cell_counts.clear(); |
||||
cell_counts.resize(backing_array_size + 1, {0, 0}); |
||||
sorted_particles.resize(backing_array_size); |
||||
|
||||
for (auto p : particles) { |
||||
cell_counts[cell_id(p.pos)].count++; |
||||
} |
||||
|
||||
/* extra + 1 to account for reading off the end when getting the
|
||||
* next cell */ |
||||
for (int i = 1; i < backing_array_size + 1; i++) { |
||||
cell_counts[i].count += cell_counts[i - 1].count; |
||||
} |
||||
|
||||
for (unsigned int i = 0; i < particles.size(); i++) { |
||||
unsigned x = --cell_counts[cell_id(particles[i].pos)].count; |
||||
sorted_particles[x] = i; |
||||
} |
||||
/* each element in cell_counts which corresponds to an existing cell
|
||||
* is equal to the cumulative sum of the particles before it, ie it |
||||
* is equal to the position this cell starts at in the sorted |
||||
* particle vector */ |
||||
|
||||
return 0; |
||||
} |
||||
|
||||
/*
|
||||
* Calls to this function are parallelised. |
||||
*/ |
||||
void get_locality(Vec3 pos, const float support_radius_squared, |
||||
std::vector<particle> &loc) { |
||||
loc.clear(); |
||||
Vec3 center; |
||||
|
||||
// only check each cell once
|
||||
std::vector<bool> cell_checked{}; |
||||
cell_checked.resize(backing_array_size + 1, false); |
||||
|
||||
center.x = ((int)(pos.x / diameter)) * diameter + diameter / 2; |
||||
center.y = ((int)(pos.y / diameter)) * diameter + diameter / 2; |
||||
center.z = ((int)(pos.z / diameter)) * diameter + diameter / 2; |
||||
|
||||
constexpr int pmo[] = {1, 0, -1}; |
||||
int discarded = 0; |
||||
|
||||
for (int i = 0; i < 3; i++) { |
||||
for (int j = 0; j < 3; j++) { |
||||
for (int k = 0; k < 3; k++) { |
||||
auto o = center + Vec3{diameter * pmo[i], diameter * pmo[j], |
||||
diameter * pmo[k]}; |
||||
unsigned int cell = (cell_id(o)); |
||||
|
||||
if (cell_checked[cell]) { |
||||
continue; |
||||
} |
||||
|
||||
cell_checked[cell] = true; |
||||
|
||||
auto start = cell_counts[cell].count; |
||||
auto end = cell_counts[cell + 1].count; |
||||
|
||||
for (auto i = start; i < end; i++) { |
||||
|
||||
auto p = particles[sorted_particles[i]]; |
||||
Vec3 xij = {pos.x - p.pos.x, pos.y - p.pos.y, pos.z - p.pos.z}; |
||||
if (Vec3_dot(xij, xij) <= support_radius_squared) { |
||||
loc.emplace_back(p); |
||||
} else { |
||||
discarded++; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
struct wcsph_solver { |
||||
private: |
||||
float timestep; // * h / 90;
|
||||
float rest_density; |
||||
float visc_constant; |
||||
float stiffness_k; |
||||
float h; // smoothing radius
|
||||
float particle_mass; |
||||
float invh; |
||||
float invmass; // 1/mass
|
||||
float inv_restdenssity; |
||||
float support_radius; |
||||
float support_radius_squared; |
||||
float rev = -1.0; // collision damping
|
||||
sim_domain domain; |
||||
|
||||
float spiky_kernel_dfdq(float q) { |
||||
if (0 <= q && q < 1) { |
||||
return (3.0 / (2 * M_PI)) * (-2 * q + 0.5 * 3 * q * q); |
||||
} else if (q <= 2) { |
||||
return (3.0 / (2 * M_PI)) * (-1.0 / 2 * (2 - q) * (2 - q)); |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
Vec3 gradW_spiky(Vec3 xi, Vec3 xj) { |
||||
Vec3 dir{xi.x - xj.x, xi.y - xj.y, xi.z - xj.z}; |
||||
|
||||
float omag = mod_vec3(dir); |
||||
float mag = spiky_kernel_dfdq(omag * invh) * (invh * invh * invh * invh); |
||||
if (omag != 0) { |
||||
mag /= omag; |
||||
} else { |
||||
mag = 0; |
||||
} |
||||
|
||||
dir.x = dir.x * mag; |
||||
dir.y = dir.y * mag; |
||||
dir.z = dir.z * mag; |
||||
|
||||
return dir; |
||||
} |
||||
|
||||
float pressure(float density) { |
||||
/* see test case "pow vs multiply" in sphash-test.cpp */ |
||||
float d = density * inv_restdenssity; |
||||
return stiffness_k * (d * d * d * d * d * d * d - 1); |
||||
} |
||||
|
||||
float spiky_kernel_f(float q) { |
||||
float s = 3.0 / (2 * M_PI); |
||||
if (0 <= q && q < 1) { |
||||
s *= 2.0 / 3 - q * q + 0.5 * q * q * q; |
||||
} else if (q >= 1 && q < 2) { |
||||
s *= 1.0 / 6 * (2 - q) * (2 - q) * (2 - q); |
||||
} else { |
||||
s *= 0; |
||||
} |
||||
return s; |
||||
} |
||||
|
||||
float W_spiky_kernel(Vec3 xi, Vec3 xj) { |
||||
float q = mod_vec3(Vec3{xi.x - xj.x, xi.y - xj.y, xi.z - xj.z}) * invh; |
||||
return spiky_kernel_f(q) * (invh * invh * invh); |
||||
} |
||||
|
||||
std::pair<Vec3, Vec3> |
||||
update_denspres_gradpres_laplvel(particle &i, |
||||
const std::vector<particle> &locals) { |
||||
/*
|
||||
* Calculates the density and pressure, then the pressure gradient and |
||||
* viscosity Laplacian in the same loop to hopefully improve computation |
||||
* time and take advantage of cache locality. |
||||
* |
||||
* Complexity: 2 * local.size() |
||||
* |
||||
* Returns a pair; pressure grad, velocity lapl. |
||||
*/ |
||||
float density = 0; |
||||
|
||||
for (const auto &j : locals) { |
||||
density += particle_mass * W_spiky_kernel(i.pos, j.pos); |
||||
} |
||||
|
||||
i.density = density; |
||||
i.pressure = pressure(density); |
||||
Vec3 lapl_vel{0, 0, 0}; |
||||
Vec3 grad_pres{0, 0, 0}; |
||||
|
||||
for (int j = 0; j < locals.size(); j++) { |
||||
float inv_dens_sq = 1.0 / (locals[j].density * locals[j].density); |
||||
auto gradW = gradW_spiky(i.pos, locals[j].pos); |
||||
float temp = particle_mass * (i.pressure * inv_dens_sq + |
||||
(locals[j].pressure * inv_dens_sq)); |
||||
// update
|
||||
grad_pres = grad_pres + gradW * temp; |
||||
|
||||
Vec3 vij = i.vel - locals[j].vel; |
||||
Vec3 xij = i.pos - locals[j].pos; |
||||
|
||||
// update
|
||||
lapl_vel = |
||||
lapl_vel + |
||||
2 * vij * |
||||
(((particle_mass / locals[j].density) * (Vec3_dot(xij, gradW))) / |
||||
(Vec3_dot(xij, xij) + 0.01 * h * h)); |
||||
} |
||||
|
||||
return {grad_pres, lapl_vel}; |
||||
} |
||||
|
||||
public: |
||||
wcsph_solver(const sph_settings s, const sim_domain d) |
||||
: timestep(s.timestep), rest_density(s.rest_density), |
||||
visc_constant(s.visc_constant), stiffness_k(s.stiffness_k), h(s.h), |
||||
particle_mass(h * h * h * rest_density), invh(1 / h), |
||||
invmass(1 / particle_mass), inv_restdenssity(1 / rest_density), |
||||
support_radius(2 * h), |
||||
support_radius_squared(support_radius * support_radius), domain(d) {} |
||||
|
||||
void update(sph_data &data) { |
||||
std::vector<std::vector<particle>> locals; |
||||
int rep = data.repair_map(); |
||||
|
||||
auto &particles = data.get_particles(); |
||||
|
||||
for (int i = 0; i < particles.size(); i++) { |
||||
auto &p = particles[i]; |
||||
thread_local std::vector<particle> locality; |
||||
data.get_locality(p.pos, support_radius_squared, locality); |
||||
|
||||
particle old_p = p; |
||||
particle new_p = old_p; |
||||
|
||||
std::pair<Vec3, Vec3> gradp_laplvel = |
||||
update_denspres_gradpres_laplvel(new_p, locality); |
||||
Vec3 Fp = -particle_mass * gradp_laplvel.first; |
||||
Vec3 Fvisc = particle_mass * visc_constant * gradp_laplvel.second; |
||||
|
||||
Vec3 Fother = Vec3{0, 0, (float)-9.8 * particle_mass}; |
||||
|
||||
Vec3 forces = { |
||||
Fvisc.x + Fp.x + Fother.x, |
||||
Fvisc.y + Fp.y + Fother.y, |
||||
Fvisc.z + Fp.z + Fother.z, |
||||
}; |
||||
|
||||
new_p.vel = new_p.vel + timestep * invmass * forces; |
||||
new_p.pos = new_p.pos + timestep * new_p.vel; |
||||
|
||||
// handle wall collisions
|
||||
if (new_p.pos.z < domain.origin) { |
||||
new_p.pos.z = domain.origin + (domain.origin - new_p.pos.z); |
||||
new_p.vel.z *= rev * 0.5; |
||||
} |
||||
|
||||
if (new_p.pos.x < domain.origin) { |
||||
new_p.vel.x *= rev; |
||||
new_p.pos.x = domain.origin + (domain.origin - new_p.pos.x); |
||||
} |
||||
if (new_p.pos.x > domain.room_width) { |
||||
new_p.pos.x = domain.room_width - (new_p.pos.x - domain.room_width); |
||||
new_p.vel.x *= rev; |
||||
} |
||||
if (new_p.pos.y < domain.origin) { |
||||
new_p.vel.y *= rev; |
||||
new_p.pos.y = domain.origin + (domain.origin - new_p.pos.y); |
||||
} |
||||
if (new_p.pos.y > domain.room_width) { |
||||
new_p.vel.y *= rev; |
||||
new_p.pos.y = domain.room_width - (new_p.pos.y - domain.room_width); |
||||
} |
||||
|
||||
float actwidth = domain.room_width - domain.origin; |
||||
float blocwidth = actwidth / 7; |
||||
float blocstart = domain.origin + actwidth / 2; |
||||
float blocend = blocstart + blocwidth; |
||||
|
||||
if (new_p.pos.y > blocend && new_p.pos.x > blocend) { |
||||
if (blocend - new_p.pos.x > blocend - p.pos.y) { |
||||
new_p.pos.x = blocend; |
||||
new_p.vel.x *= rev; |
||||
} else { |
||||
new_p.pos.y = blocend; |
||||
new_p.vel.y *= rev; |
||||
} |
||||
} |
||||
|
||||
p = new_p; |
||||
} |
||||
return; |
||||
} |
||||
}; |
||||
|
||||
}; // namespace msph
|
Loading…
Reference in new issue