1use crate::{G, GravityParticle, GravitySolver};
11use phyz_math::Vec3;
12
13#[derive(Debug, Clone)]
15pub struct PoissonSolver {
16 pub resolution: usize,
18 pub bounds: (Vec3, Vec3),
20 pub density: Vec<f64>,
22 pub potential: Vec<f64>,
24 pub max_iter: usize,
26 pub tolerance: f64,
28}
29
30impl PoissonSolver {
31 pub fn new(resolution: usize, bounds: (Vec3, Vec3)) -> Self {
33 let n = resolution * resolution * resolution;
34 Self {
35 resolution,
36 bounds,
37 density: vec![0.0; n],
38 potential: vec![0.0; n],
39 max_iter: 100,
40 tolerance: 1e-6,
41 }
42 }
43
44 fn idx(&self, i: usize, j: usize, k: usize) -> usize {
46 i + j * self.resolution + k * self.resolution * self.resolution
47 }
48
49 fn dx(&self) -> f64 {
51 (self.bounds.1.x - self.bounds.0.x) / self.resolution as f64
52 }
53
54 pub fn deposit_density(&mut self, particles: &[GravityParticle]) {
56 self.density.fill(0.0);
57
58 let dx = self.dx();
59 let min = self.bounds.0;
60
61 for p in particles {
62 let gx = ((p.x.x - min.x) / dx).floor() as isize;
64 let gy = ((p.x.y - min.y) / dx).floor() as isize;
65 let gz = ((p.x.z - min.z) / dx).floor() as isize;
66
67 if gx >= 0
69 && gx < self.resolution as isize
70 && gy >= 0
71 && gy < self.resolution as isize
72 && gz >= 0
73 && gz < self.resolution as isize
74 {
75 let idx = self.idx(gx as usize, gy as usize, gz as usize);
76 self.density[idx] += p.m / (dx * dx * dx);
77 }
78 }
79 }
80
81 pub fn solve_poisson(&mut self) {
83 let n = self.resolution;
84 let dx = self.dx();
85 let dx2 = dx * dx;
86
87 let mut new_potential = self.potential.clone();
88
89 for _iter in 0..self.max_iter {
90 let mut max_delta: f64 = 0.0;
91
92 for i in 1..n - 1 {
93 for j in 1..n - 1 {
94 for k in 1..n - 1 {
95 let idx = self.idx(i, j, k);
96
97 let rhs = 4.0 * std::f64::consts::PI * G * self.density[idx];
99
100 new_potential[idx] = (self.potential[self.idx(i + 1, j, k)]
102 + self.potential[self.idx(i - 1, j, k)]
103 + self.potential[self.idx(i, j + 1, k)]
104 + self.potential[self.idx(i, j - 1, k)]
105 + self.potential[self.idx(i, j, k + 1)]
106 + self.potential[self.idx(i, j, k - 1)]
107 + dx2 * rhs)
108 / 6.0;
109
110 let delta = (new_potential[idx] - self.potential[idx]).abs();
111 max_delta = max_delta.max(delta);
112 }
113 }
114 }
115
116 std::mem::swap(&mut self.potential, &mut new_potential);
117
118 if max_delta < self.tolerance {
119 break;
120 }
121 }
122 }
123
124 pub fn force_at(&self, x: Vec3) -> Vec3 {
126 let dx = self.dx();
127 let min = self.bounds.0;
128
129 let gx = ((x.x - min.x) / dx).floor() as isize;
131 let gy = ((x.y - min.y) / dx).floor() as isize;
132 let gz = ((x.z - min.z) / dx).floor() as isize;
133
134 if gx < 1
135 || gx >= (self.resolution - 1) as isize
136 || gy < 1
137 || gy >= (self.resolution - 1) as isize
138 || gz < 1
139 || gz >= (self.resolution - 1) as isize
140 {
141 return Vec3::zeros();
142 }
143
144 let i = gx as usize;
145 let j = gy as usize;
146 let k = gz as usize;
147
148 let grad_x = (self.potential[self.idx(i + 1, j, k)]
150 - self.potential[self.idx(i - 1, j, k)])
151 / (2.0 * dx);
152 let grad_y = (self.potential[self.idx(i, j + 1, k)]
153 - self.potential[self.idx(i, j - 1, k)])
154 / (2.0 * dx);
155 let grad_z = (self.potential[self.idx(i, j, k + 1)]
156 - self.potential[self.idx(i, j, k - 1)])
157 / (2.0 * dx);
158
159 Vec3::new(-grad_x, -grad_y, -grad_z)
160 }
161}
162
163impl GravitySolver for PoissonSolver {
164 fn compute_forces(&mut self, particles: &mut [GravityParticle]) {
165 self.deposit_density(particles);
167
168 self.solve_poisson();
170
171 for p in particles.iter_mut() {
173 p.reset_force();
174 let g = self.force_at(p.x);
175 p.add_force(g * p.m);
176 }
177 }
178
179 fn potential_energy(&self, particles: &[GravityParticle]) -> f64 {
180 let dx = self.dx();
182 let min = self.bounds.0;
183
184 particles
185 .iter()
186 .map(|p| {
187 let gx = ((p.x.x - min.x) / dx).floor() as isize;
188 let gy = ((p.x.y - min.y) / dx).floor() as isize;
189 let gz = ((p.x.z - min.z) / dx).floor() as isize;
190
191 if gx >= 0
192 && gx < self.resolution as isize
193 && gy >= 0
194 && gy < self.resolution as isize
195 && gz >= 0
196 && gz < self.resolution as isize
197 {
198 let idx = self.idx(gx as usize, gy as usize, gz as usize);
199 p.m * self.potential[idx]
200 } else {
201 0.0
202 }
203 })
204 .sum()
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_poisson_solver_creation() {
214 let solver =
215 PoissonSolver::new(10, (Vec3::new(-5.0, -5.0, -5.0), Vec3::new(5.0, 5.0, 5.0)));
216 assert_eq!(solver.resolution, 10);
217 assert_eq!(solver.density.len(), 1000);
218 }
219
220 #[test]
221 fn test_density_deposition() {
222 let mut solver =
223 PoissonSolver::new(10, (Vec3::new(-5.0, -5.0, -5.0), Vec3::new(5.0, 5.0, 5.0)));
224
225 let particles = vec![GravityParticle::new(Vec3::zeros(), Vec3::zeros(), 1000.0)];
226
227 solver.deposit_density(&particles);
228
229 let total_density: f64 = solver.density.iter().sum();
231 assert!(total_density > 0.0);
232 }
233}