import { Vec3 } from '../math/Vec3' import type { Body } from '../objects/Body' /** * Smoothed-particle hydrodynamics system * @todo Make parameters customizable in the constructor */ export class SPHSystem { /** * The particles array. */ particles: Body[] /** * Density of the system (kg/m3). * @default 1 */ density: number /** * Distance below which two particles are considered to be neighbors. * It should be adjusted so there are about 15-20 neighbor particles within this radius. * @default 1 */ smoothingRadius: number /** * @default 1 */ speedOfSound: number /** * Viscosity of the system. * @default 0.01 */ viscosity: number /** * @default 0.000001 */ eps: number pressures: number[] densities: number[] neighbors: Body[][] constructor() { this.particles = [] this.density = 1 this.smoothingRadius = 1 this.speedOfSound = 1 this.viscosity = 0.01 this.eps = 0.000001 // Stuff Computed per particle this.pressures = [] this.densities = [] this.neighbors = [] } /** * Add a particle to the system. */ add(particle: Body): void { this.particles.push(particle) if (this.neighbors.length < this.particles.length) { this.neighbors.push([]) } } /** * Remove a particle from the system. */ remove(particle: Body): void { const idx = this.particles.indexOf(particle) if (idx !== -1) { this.particles.splice(idx, 1) if (this.neighbors.length > this.particles.length) { this.neighbors.pop() } } } /** * Get neighbors within smoothing volume, save in the array neighbors */ getNeighbors(particle: Body, neighbors: Body[]): void { const N = this.particles.length const id = particle.id const R2 = this.smoothingRadius * this.smoothingRadius const dist = SPHSystem_getNeighbors_dist for (let i = 0; i !== N; i++) { const p = this.particles[i] p.position.vsub(particle.position, dist) if (id !== p.id && dist.lengthSquared() < R2) { neighbors.push(p) } } } update(): void { const N = this.particles.length const dist = SPHSystem_update_dist const cs = this.speedOfSound const eps = this.eps for (let i = 0; i !== N; i++) { const p = this.particles[i] // Current particle const neighbors = this.neighbors[i] // Get neighbors neighbors.length = 0 this.getNeighbors(p, neighbors) neighbors.push(this.particles[i]) // Add current too const numNeighbors = neighbors.length // Accumulate density for the particle let sum = 0.0 for (let j = 0; j !== numNeighbors; j++) { //printf("Current particle has position %f %f %f\n",objects[id].pos.x(),objects[id].pos.y(),objects[id].pos.z()); p.position.vsub(neighbors[j].position, dist) const len = dist.length() const weight = this.w(len) sum += neighbors[j].mass * weight } // Save this.densities[i] = sum this.pressures[i] = cs * cs * (this.densities[i] - this.density) } // Add forces // Sum to these accelerations const a_pressure = SPHSystem_update_a_pressure const a_visc = SPHSystem_update_a_visc const gradW = SPHSystem_update_gradW const r_vec = SPHSystem_update_r_vec const u = SPHSystem_update_u for (let i = 0; i !== N; i++) { const particle = this.particles[i] a_pressure.set(0, 0, 0) a_visc.set(0, 0, 0) // Init vars let Pij let nabla let Vij // Sum up for all other neighbors const neighbors = this.neighbors[i] const numNeighbors = neighbors.length //printf("Neighbors: "); for (let j = 0; j !== numNeighbors; j++) { const neighbor = neighbors[j] //printf("%d ",nj); // Get r once for all.. particle.position.vsub(neighbor.position, r_vec) const r = r_vec.length() // Pressure contribution Pij = -neighbor.mass * (this.pressures[i] / (this.densities[i] * this.densities[i] + eps) + this.pressures[j] / (this.densities[j] * this.densities[j] + eps)) this.gradw(r_vec, gradW) // Add to pressure acceleration gradW.scale(Pij, gradW) a_pressure.vadd(gradW, a_pressure) // Viscosity contribution neighbor.velocity.vsub(particle.velocity, u) u.scale((1.0 / (0.0001 + this.densities[i] * this.densities[j])) * this.viscosity * neighbor.mass, u) nabla = this.nablaw(r) u.scale(nabla, u) // Add to viscosity acceleration a_visc.vadd(u, a_visc) } // Calculate force a_visc.scale(particle.mass, a_visc) a_pressure.scale(particle.mass, a_pressure) // Add force to particles particle.force.vadd(a_visc, particle.force) particle.force.vadd(a_pressure, particle.force) } } // Calculate the weight using the W(r) weightfunction w(r: number): number { // 315 const h = this.smoothingRadius return (315.0 / (64.0 * Math.PI * h ** 9)) * (h * h - r * r) ** 3 } // calculate gradient of the weight function gradw(rVec: Vec3, resultVec: Vec3): void { const r = rVec.length() const h = this.smoothingRadius rVec.scale((945.0 / (32.0 * Math.PI * h ** 9)) * (h * h - r * r) ** 2, resultVec) } // Calculate nabla(W) nablaw(r: number): number { const h = this.smoothingRadius const nabla = (945.0 / (32.0 * Math.PI * h ** 9)) * (h * h - r * r) * (7 * r * r - 3 * h * h) return nabla } } const SPHSystem_getNeighbors_dist = new Vec3() // Temp vectors for calculation const SPHSystem_update_dist = new Vec3() // Relative velocity const SPHSystem_update_a_pressure = new Vec3() const SPHSystem_update_a_visc = new Vec3() const SPHSystem_update_gradW = new Vec3() const SPHSystem_update_r_vec = new Vec3() const SPHSystem_update_u = new Vec3()