use crate::{G, GravityParticle, GravitySolver};
use phyz_math::Vec3;
#[derive(Debug, Clone)]
pub struct PoissonSolver {
pub resolution: usize,
pub bounds: (Vec3, Vec3),
pub density: Vec<f64>,
pub potential: Vec<f64>,
pub max_iter: usize,
pub tolerance: f64,
}
impl PoissonSolver {
pub fn new(resolution: usize, bounds: (Vec3, Vec3)) -> Self {
let n = resolution * resolution * resolution;
Self {
resolution,
bounds,
density: vec![0.0; n],
potential: vec![0.0; n],
max_iter: 100,
tolerance: 1e-6,
}
}
fn idx(&self, i: usize, j: usize, k: usize) -> usize {
i + j * self.resolution + k * self.resolution * self.resolution
}
fn dx(&self) -> f64 {
(self.bounds.1.x - self.bounds.0.x) / self.resolution as f64
}
pub fn deposit_density(&mut self, particles: &[GravityParticle]) {
self.density.fill(0.0);
let dx = self.dx();
let min = self.bounds.0;
for p in particles {
let gx = ((p.x.x - min.x) / dx).floor() as isize;
let gy = ((p.x.y - min.y) / dx).floor() as isize;
let gz = ((p.x.z - min.z) / dx).floor() as isize;
if gx >= 0
&& gx < self.resolution as isize
&& gy >= 0
&& gy < self.resolution as isize
&& gz >= 0
&& gz < self.resolution as isize
{
let idx = self.idx(gx as usize, gy as usize, gz as usize);
self.density[idx] += p.m / (dx * dx * dx);
}
}
}
pub fn solve_poisson(&mut self) {
let n = self.resolution;
let dx = self.dx();
let dx2 = dx * dx;
let mut new_potential = self.potential.clone();
for _iter in 0..self.max_iter {
let mut max_delta: f64 = 0.0;
for i in 1..n - 1 {
for j in 1..n - 1 {
for k in 1..n - 1 {
let idx = self.idx(i, j, k);
let rhs = 4.0 * std::f64::consts::PI * G * self.density[idx];
new_potential[idx] = (self.potential[self.idx(i + 1, j, k)]
+ self.potential[self.idx(i - 1, j, k)]
+ self.potential[self.idx(i, j + 1, k)]
+ self.potential[self.idx(i, j - 1, k)]
+ self.potential[self.idx(i, j, k + 1)]
+ self.potential[self.idx(i, j, k - 1)]
+ dx2 * rhs)
/ 6.0;
let delta = (new_potential[idx] - self.potential[idx]).abs();
max_delta = max_delta.max(delta);
}
}
}
std::mem::swap(&mut self.potential, &mut new_potential);
if max_delta < self.tolerance {
break;
}
}
}
pub fn force_at(&self, x: Vec3) -> Vec3 {
let dx = self.dx();
let min = self.bounds.0;
let gx = ((x.x - min.x) / dx).floor() as isize;
let gy = ((x.y - min.y) / dx).floor() as isize;
let gz = ((x.z - min.z) / dx).floor() as isize;
if gx < 1
|| gx >= (self.resolution - 1) as isize
|| gy < 1
|| gy >= (self.resolution - 1) as isize
|| gz < 1
|| gz >= (self.resolution - 1) as isize
{
return Vec3::zeros();
}
let i = gx as usize;
let j = gy as usize;
let k = gz as usize;
let grad_x = (self.potential[self.idx(i + 1, j, k)]
- self.potential[self.idx(i - 1, j, k)])
/ (2.0 * dx);
let grad_y = (self.potential[self.idx(i, j + 1, k)]
- self.potential[self.idx(i, j - 1, k)])
/ (2.0 * dx);
let grad_z = (self.potential[self.idx(i, j, k + 1)]
- self.potential[self.idx(i, j, k - 1)])
/ (2.0 * dx);
Vec3::new(-grad_x, -grad_y, -grad_z)
}
}
impl GravitySolver for PoissonSolver {
fn compute_forces(&mut self, particles: &mut [GravityParticle]) {
self.deposit_density(particles);
self.solve_poisson();
for p in particles.iter_mut() {
p.reset_force();
let g = self.force_at(p.x);
p.add_force(g * p.m);
}
}
fn potential_energy(&self, particles: &[GravityParticle]) -> f64 {
let dx = self.dx();
let min = self.bounds.0;
particles
.iter()
.map(|p| {
let gx = ((p.x.x - min.x) / dx).floor() as isize;
let gy = ((p.x.y - min.y) / dx).floor() as isize;
let gz = ((p.x.z - min.z) / dx).floor() as isize;
if gx >= 0
&& gx < self.resolution as isize
&& gy >= 0
&& gy < self.resolution as isize
&& gz >= 0
&& gz < self.resolution as isize
{
let idx = self.idx(gx as usize, gy as usize, gz as usize);
p.m * self.potential[idx]
} else {
0.0
}
})
.sum()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_poisson_solver_creation() {
let solver =
PoissonSolver::new(10, (Vec3::new(-5.0, -5.0, -5.0), Vec3::new(5.0, 5.0, 5.0)));
assert_eq!(solver.resolution, 10);
assert_eq!(solver.density.len(), 1000);
}
#[test]
fn test_density_deposition() {
let mut solver =
PoissonSolver::new(10, (Vec3::new(-5.0, -5.0, -5.0), Vec3::new(5.0, 5.0, 5.0)));
let particles = vec![GravityParticle::new(Vec3::zeros(), Vec3::zeros(), 1000.0)];
solver.deposit_density(&particles);
let total_density: f64 = solver.density.iter().sum();
assert!(total_density > 0.0);
}
}