1use glam::Vec2;
22
23const PRESSURE_ITERATIONS: usize = 20;
26const DIFFUSION_ITERATIONS: usize = 4;
27
28#[inline]
32fn clamp_idx(v: i32, max: i32) -> usize {
33 v.clamp(0, max - 1) as usize
34}
35
36fn bilinear(grid: &[f32], w: usize, h: usize, x: f32, y: f32) -> f32 {
38 let x0 = (x.floor() as i32).clamp(0, w as i32 - 2) as usize;
39 let y0 = (y.floor() as i32).clamp(0, h as i32 - 2) as usize;
40 let x1 = (x0 + 1).min(w - 1);
41 let y1 = (y0 + 1).min(h - 1);
42 let tx = x.fract().clamp(0.0, 1.0);
43 let ty = y.fract().clamp(0.0, 1.0);
44 let v00 = grid[y0 * w + x0];
45 let v10 = grid[y0 * w + x1];
46 let v01 = grid[y1 * w + x0];
47 let v11 = grid[y1 * w + x1];
48 let a = v00 + tx * (v10 - v00);
49 let b = v01 + tx * (v11 - v01);
50 a + ty * (b - a)
51}
52
53#[derive(Debug, Clone)]
57pub struct FluidGrid {
58 pub width: usize,
60 pub height: usize,
62 pub dx: f32,
64
65 pub vx: Vec<f32>,
67 pub vy: Vec<f32>,
69 pub density: Vec<f32>,
71 pub temp: Vec<f32>,
73 pub color_r: Vec<f32>,
75 pub color_g: Vec<f32>,
77 pub color_b: Vec<f32>,
79 pub pressure: Vec<f32>,
81 divergence: Vec<f32>,
83 pub obstacle: Vec<f32>,
85
86 vx0: Vec<f32>,
88 vy0: Vec<f32>,
89 dens0: Vec<f32>,
90 temp0: Vec<f32>,
91 col0_r: Vec<f32>,
92 col0_g: Vec<f32>,
93 col0_b: Vec<f32>,
94
95 pub viscosity: f32,
97 pub diffusion: f32,
99 pub buoyancy: f32,
101 pub ambient_temp: f32,
103 pub vorticity_strength: f32,
105 pub gravity: Vec2,
107 pub decay: f32,
109
110 pub time: f32,
112}
113
114impl FluidGrid {
115 pub fn new(width: usize, height: usize, dx: f32) -> Self {
117 let n = width * height;
118 let ones = vec![1.0_f32; n];
119 Self {
120 width, height, dx,
121 vx: vec![0.0; n],
122 vy: vec![0.0; n],
123 density: vec![0.0; n],
124 temp: vec![20.0; n], color_r: vec![0.0; n],
126 color_g: vec![0.0; n],
127 color_b: vec![0.0; n],
128 pressure: vec![0.0; n],
129 divergence: vec![0.0; n],
130 obstacle: ones,
131 vx0: vec![0.0; n],
132 vy0: vec![0.0; n],
133 dens0: vec![0.0; n],
134 temp0: vec![0.0; n],
135 col0_r: vec![0.0; n],
136 col0_g: vec![0.0; n],
137 col0_b: vec![0.0; n],
138 viscosity: 1e-4,
139 diffusion: 1e-5,
140 buoyancy: 0.15,
141 ambient_temp: 20.0,
142 vorticity_strength: 0.3,
143 gravity: Vec2::new(0.0, -9.8),
144 decay: 0.995,
145 time: 0.0,
146 }
147 }
148
149 #[inline]
152 pub fn idx(&self, x: usize, y: usize) -> usize {
153 y * self.width + x
154 }
155
156 #[inline]
157 fn ix(&self, x: i32, y: i32) -> usize {
158 let cx = x.clamp(0, self.width as i32 - 1) as usize;
159 let cy = y.clamp(0, self.height as i32 - 1) as usize;
160 cy * self.width + cx
161 }
162
163 pub fn add_velocity(&mut self, cx: usize, cy: usize, dvx: f32, dvy: f32) {
167 if cx < self.width && cy < self.height {
168 let i = self.idx(cx, cy);
169 self.vx[i] += dvx;
170 self.vy[i] += dvy;
171 }
172 }
173
174 pub fn add_density(&mut self, cx: usize, cy: usize, amount: f32) {
176 if cx < self.width && cy < self.height {
177 let i = self.idx(cx, cy);
178 self.density[i] = (self.density[i] + amount).clamp(0.0, 10.0);
179 }
180 }
181
182 pub fn add_temperature(&mut self, cx: usize, cy: usize, dt: f32) {
184 if cx < self.width && cy < self.height {
185 let i = self.idx(cx, cy);
186 self.temp[i] += dt;
187 }
188 }
189
190 pub fn add_color(&mut self, cx: usize, cy: usize, r: f32, g: f32, b: f32) {
192 if cx < self.width && cy < self.height {
193 let i = self.idx(cx, cy);
194 self.color_r[i] = (self.color_r[i] + r).clamp(0.0, 1.0);
195 self.color_g[i] = (self.color_g[i] + g).clamp(0.0, 1.0);
196 self.color_b[i] = (self.color_b[i] + b).clamp(0.0, 1.0);
197 }
198 }
199
200 pub fn add_obstacle_circle(&mut self, cx: f32, cy: f32, radius: f32) {
202 let w = self.width;
203 let h = self.height;
204 for y in 0..h {
205 for x in 0..w {
206 let dx = x as f32 - cx;
207 let dy = y as f32 - cy;
208 if dx * dx + dy * dy <= radius * radius {
209 let i = y * w + x;
210 self.obstacle[i] = 0.0;
211 self.vx[i] = 0.0;
212 self.vy[i] = 0.0;
213 }
214 }
215 }
216 }
217
218 pub fn add_obstacle_rect(&mut self, x0: usize, y0: usize, x1: usize, y1: usize) {
220 for y in y0.min(self.height)..y1.min(self.height) {
221 for x in x0.min(self.width)..x1.min(self.width) {
222 let i = y * self.width + x;
223 self.obstacle[i] = 0.0;
224 self.vx[i] = 0.0;
225 self.vy[i] = 0.0;
226 }
227 }
228 }
229
230 pub fn step(&mut self, dt: f32) {
234 self.time += dt;
235
236 self.apply_forces(dt);
238
239 if self.vorticity_strength > 0.0 {
241 self.apply_vorticity_confinement(dt);
242 }
243
244 if self.viscosity > 0.0 {
246 self.diffuse_velocity(dt);
247 }
248
249 self.project(dt);
251
252 self.advect_velocity(dt);
254
255 self.project(dt);
257
258 let _ = self.diffusion;
260
261 self.advect_scalars(dt);
263
264 let decay = self.decay.powf(dt);
266 for v in &mut self.density { *v *= decay; }
267 for v in &mut self.color_r { *v *= decay; }
268 for v in &mut self.color_g { *v *= decay; }
269 for v in &mut self.color_b { *v *= decay; }
270
271 let cool = (-0.5 * dt).exp();
273 let ambient = self.ambient_temp;
274 for v in &mut self.temp {
275 *v = ambient + (*v - ambient) * cool;
276 }
277
278 let w = self.width;
280 let h = self.height;
281 for y in 0..h {
282 for x in 0..w {
283 let i = y * w + x;
284 if self.obstacle[i] == 0.0 {
285 self.vx[i] = 0.0;
286 self.vy[i] = 0.0;
287 }
288 }
289 }
290 }
291
292 fn make_dens_swap_vec(v: Vec<f32>) -> Vec<f32> { v }
293
294 fn apply_forces(&mut self, dt: f32) {
297 let w = self.width;
298 let h = self.height;
299 let gx = self.gravity.x * dt;
300 let gy = self.gravity.y * dt;
301 let buoy = self.buoyancy * dt;
302 let amb = self.ambient_temp;
303 for y in 0..h {
304 for x in 0..w {
305 let i = y * w + x;
306 if self.obstacle[i] == 0.0 { continue; }
307 self.vx[i] += gx;
308 self.vy[i] += gy;
309 let delta_t = self.temp[i] - amb;
311 self.vy[i] += buoy * delta_t;
312 let drag = 1.0 - 0.01 * self.density[i].clamp(0.0, 5.0);
314 self.vx[i] *= drag;
315 self.vy[i] *= drag;
316 }
317 }
318 }
319
320 fn apply_vorticity_confinement(&mut self, dt: f32) {
323 let w = self.width;
324 let h = self.height;
325 let strength = self.vorticity_strength * dt;
326
327 let mut curl = vec![0.0_f32; w * h];
329 for y in 1..h - 1 {
330 for x in 1..w - 1 {
331 let dvx_dy = self.vx[self.ix(x as i32, y as i32 + 1)]
332 - self.vx[self.ix(x as i32, y as i32 - 1)];
333 let dvy_dx = self.vy[self.ix(x as i32 + 1, y as i32)]
334 - self.vy[self.ix(x as i32 - 1, y as i32)];
335 curl[y * w + x] = (dvy_dx - dvx_dy) * 0.5;
336 }
337 }
338
339 for y in 1..h - 1 {
341 for x in 1..w - 1 {
342 let i = y * w + x;
343 if self.obstacle[i] == 0.0 { continue; }
344 let dw_dx = curl[self.ix(x as i32 + 1, y as i32)].abs()
345 - curl[self.ix(x as i32 - 1, y as i32)].abs();
346 let dw_dy = curl[self.ix(x as i32, y as i32 + 1)].abs()
347 - curl[self.ix(x as i32, y as i32 - 1)].abs();
348 let len = (dw_dx * dw_dx + dw_dy * dw_dy).sqrt() + 1e-5;
349 let nx = dw_dx / len;
350 let ny = dw_dy / len;
351 let c = curl[i];
352 self.vx[i] += strength * ny * c;
353 self.vy[i] -= strength * nx * c;
354 }
355 }
356 }
357
358 fn diffuse_velocity(&mut self, dt: f32) {
361 let a = dt * self.viscosity * (self.width * self.height) as f32;
362 let w = self.width;
363 let h = self.height;
364 let obs = self.obstacle.clone();
365 let vx_src = self.vx.clone();
366 let vy_src = self.vy.clone();
367 for _ in 0..DIFFUSION_ITERATIONS {
368 for y in 1..h - 1 {
369 for x in 1..w - 1 {
370 let i = y * w + x;
371 if obs[i] == 0.0 { continue; }
372 let n = self.ix(x as i32, y as i32 + 1);
373 let s = self.ix(x as i32, y as i32 - 1);
374 let e = self.ix(x as i32 + 1, y as i32);
375 let ww = self.ix(x as i32 - 1, y as i32);
376 self.vx[i] = (vx_src[i] + a * (self.vx[n] + self.vx[s]
377 + self.vx[e] + self.vx[ww]))
378 / (1.0 + 4.0 * a);
379 self.vy[i] = (vy_src[i] + a * (self.vy[n] + self.vy[s]
380 + self.vy[e] + self.vy[ww]))
381 / (1.0 + 4.0 * a);
382 }
383 }
384 self.set_boundary_velocity();
385 }
386 }
387
388 fn project(&mut self, _dt: f32) {
391 let w = self.width;
392 let h = self.height;
393
394 for y in 1..h - 1 {
396 for x in 1..w - 1 {
397 let i = y * w + x;
398 if self.obstacle[i] == 0.0 {
399 self.divergence[i] = 0.0;
400 self.pressure[i] = 0.0;
401 continue;
402 }
403 let dvx = self.vx[self.ix(x as i32 + 1, y as i32)]
404 - self.vx[self.ix(x as i32 - 1, y as i32)];
405 let dvy = self.vy[self.ix(x as i32, y as i32 + 1)]
406 - self.vy[self.ix(x as i32, y as i32 - 1)];
407 self.divergence[i] = -0.5 * (dvx + dvy) / self.dx;
408 self.pressure[i] = 0.0;
409 }
410 }
411 self.set_boundary_scalar_inplace_pressure();
412
413 let div = self.divergence.clone();
415 let obs = self.obstacle.clone();
416 for _ in 0..PRESSURE_ITERATIONS {
417 for y in 1..h - 1 {
418 for x in 1..w - 1 {
419 let i = y * w + x;
420 if obs[i] == 0.0 { continue; }
421 let pn = self.pressure[self.ix(x as i32, y as i32 + 1)];
422 let ps = self.pressure[self.ix(x as i32, y as i32 - 1)];
423 let pe = self.pressure[self.ix(x as i32 + 1, y as i32)];
424 let pw = self.pressure[self.ix(x as i32 - 1, y as i32)];
425 self.pressure[i] = (div[i] + pn + ps + pe + pw) / 4.0;
426 }
427 }
428 self.set_boundary_scalar_inplace_pressure();
429 }
430
431 for y in 1..h - 1 {
433 for x in 1..w - 1 {
434 let i = y * w + x;
435 if obs[i] == 0.0 { continue; }
436 let dp_dx = self.pressure[self.ix(x as i32 + 1, y as i32)]
437 - self.pressure[self.ix(x as i32 - 1, y as i32)];
438 let dp_dy = self.pressure[self.ix(x as i32, y as i32 + 1)]
439 - self.pressure[self.ix(x as i32, y as i32 - 1)];
440 self.vx[i] -= 0.5 * dp_dx * self.dx;
441 self.vy[i] -= 0.5 * dp_dy * self.dx;
442 }
443 }
444 self.set_boundary_velocity();
445 }
446
447 fn advect_velocity(&mut self, dt: f32) {
450 let w = self.width;
451 let h = self.height;
452 let dt0 = dt / self.dx;
453
454 self.vx0.copy_from_slice(&self.vx);
455 self.vy0.copy_from_slice(&self.vy);
456
457 for y in 1..h - 1 {
458 for x in 1..w - 1 {
459 let i = y * w + x;
460 if self.obstacle[i] == 0.0 { continue; }
461 let bx = x as f32 - dt0 * self.vx0[i];
462 let by = y as f32 - dt0 * self.vy0[i];
463 self.vx[i] = bilinear(&self.vx0, w, h, bx, by);
464 self.vy[i] = bilinear(&self.vy0, w, h, bx, by);
465 }
466 }
467 self.set_boundary_velocity();
468 }
469
470 fn advect_scalars(&mut self, dt: f32) {
471 let w = self.width;
472 let h = self.height;
473 let dt0 = dt / self.dx;
474
475 self.dens0.copy_from_slice(&self.density);
476 self.temp0.copy_from_slice(&self.temp);
477 self.col0_r.copy_from_slice(&self.color_r);
478 self.col0_g.copy_from_slice(&self.color_g);
479 self.col0_b.copy_from_slice(&self.color_b);
480
481 for y in 1..h - 1 {
482 for x in 1..w - 1 {
483 let i = y * w + x;
484 if self.obstacle[i] == 0.0 { continue; }
485 let bx = x as f32 - dt0 * self.vx[i];
486 let by = y as f32 - dt0 * self.vy[i];
487 self.density[i] = bilinear(&self.dens0, w, h, bx, by);
488 self.temp[i] = bilinear(&self.temp0, w, h, bx, by);
489 self.color_r[i] = bilinear(&self.col0_r, w, h, bx, by);
490 self.color_g[i] = bilinear(&self.col0_g, w, h, bx, by);
491 self.color_b[i] = bilinear(&self.col0_b, w, h, bx, by);
492 }
493 }
494 }
495
496 fn diffuse_scalar(field: &mut Vec<f32>, diff: f32, dt: f32, obs: &[f32]) {
499 let _ = (field, diff, dt, obs);
501 }
502
503 fn set_boundary_velocity(&mut self) {
506 let w = self.width;
507 let h = self.height;
508 for x in 0..w {
510 let i00 = x; let i01 = w + x; let ih1 = (h-1)*w + x; let ih2 = (h-2)*w + x; self.vy[i00] = -self.vy[i01];
515 self.vy[ih1] = -self.vy[ih2];
516 self.vx[i00] = self.vx[i01];
517 self.vx[ih1] = self.vx[ih2];
518 }
519 for y in 0..h {
520 let i00 = y*w; let i01 = y*w + 1; let iw1 = y*w + w - 1; let iw2 = y*w + w - 2; self.vx[i00] = -self.vx[i01];
525 self.vx[iw1] = -self.vx[iw2];
526 self.vy[i00] = self.vy[i01];
527 self.vy[iw1] = self.vy[iw2];
528 }
529 }
530
531 fn set_boundary_scalar_inplace_pressure(&mut self) {
532 let w = self.width;
533 let h = self.height;
534 for x in 0..w {
535 let i00 = x;
536 let i01 = w + x;
537 let ih1 = (h-1)*w + x;
538 let ih2 = (h-2)*w + x;
539 self.pressure[i00] = self.pressure[i01];
540 self.pressure[ih1] = self.pressure[ih2];
541 }
542 for y in 0..h {
543 let i00 = y*w;
544 let i01 = y*w + 1;
545 let iw1 = y*w + w - 1;
546 let iw2 = y*w + w - 2;
547 self.pressure[i00] = self.pressure[i01];
548 self.pressure[iw1] = self.pressure[iw2];
549 }
550 }
551
552 pub fn sample_velocity(&self, wx: f32, wy: f32) -> Vec2 {
556 let cx = wx / self.dx;
557 let cy = wy / self.dx;
558 let vx = bilinear(&self.vx, self.width, self.height, cx, cy);
559 let vy = bilinear(&self.vy, self.width, self.height, cx, cy);
560 Vec2::new(vx, vy)
561 }
562
563 pub fn sample_density(&self, wx: f32, wy: f32) -> f32 {
565 let cx = wx / self.dx;
566 let cy = wy / self.dx;
567 bilinear(&self.density, self.width, self.height, cx, cy)
568 }
569
570 pub fn sample_color(&self, wx: f32, wy: f32) -> (f32, f32, f32) {
572 let cx = wx / self.dx;
573 let cy = wy / self.dx;
574 let r = bilinear(&self.color_r, self.width, self.height, cx, cy);
575 let g = bilinear(&self.color_g, self.width, self.height, cx, cy);
576 let b = bilinear(&self.color_b, self.width, self.height, cx, cy);
577 (r, g, b)
578 }
579
580 pub fn max_velocity(&self) -> f32 {
582 let w = self.width;
583 let h = self.height;
584 let mut max_sq = 0.0_f32;
585 for i in 0..w * h {
586 let sq = self.vx[i] * self.vx[i] + self.vy[i] * self.vy[i];
587 if sq > max_sq { max_sq = sq; }
588 }
589 max_sq.sqrt()
590 }
591
592 pub fn total_density(&self) -> f32 {
594 self.density.iter().sum()
595 }
596
597 pub fn clear(&mut self) {
599 let n = self.width * self.height;
600 self.vx.fill(0.0);
601 self.vy.fill(0.0);
602 self.density.fill(0.0);
603 self.color_r.fill(0.0);
604 self.color_g.fill(0.0);
605 self.color_b.fill(0.0);
606 self.pressure.fill(0.0);
607 self.divergence.fill(0.0);
608 let _ = n;
609 }
610
611 pub fn splash(&mut self, cx: f32, cy: f32, radius: f32, strength: f32) {
613 let w = self.width;
614 let h = self.height;
615 for y in 0..h {
616 for x in 0..w {
617 let dx = x as f32 - cx;
618 let dy = y as f32 - cy;
619 let dist = (dx * dx + dy * dy).sqrt();
620 if dist < radius && dist > 0.01 {
621 let i = y * w + x;
622 let factor = (1.0 - dist / radius) * strength;
623 self.vx[i] += dx / dist * factor;
624 self.vy[i] += dy / dist * factor;
625 }
626 }
627 }
628 }
629
630 pub fn swirl(&mut self, cx: f32, cy: f32, radius: f32, strength: f32) {
632 let w = self.width;
633 let h = self.height;
634 for y in 0..h {
635 for x in 0..w {
636 let dx = x as f32 - cx;
637 let dy = y as f32 - cy;
638 let dist = (dx * dx + dy * dy).sqrt();
639 if dist < radius && dist > 0.01 {
640 let i = y * w + x;
641 let factor = (1.0 - dist / radius) * strength;
642 self.vx[i] += -dy / dist * factor;
644 self.vy[i] += dx / dist * factor;
645 }
646 }
647 }
648 }
649
650 pub fn resize(&mut self, new_width: usize, new_height: usize) {
652 *self = FluidGrid::new(new_width, new_height, self.dx);
653 }
654
655 pub fn to_rgba_density(&self, tint: (u8, u8, u8)) -> Vec<u8> {
658 let n = self.width * self.height;
659 let mut out = vec![0u8; n * 4];
660 for i in 0..n {
661 let d = (self.density[i].clamp(0.0, 1.0) * 255.0) as u8;
662 out[i * 4 ] = (tint.0 as f32 * self.density[i].clamp(0.0, 1.0)) as u8;
663 out[i * 4 + 1] = (tint.1 as f32 * self.density[i].clamp(0.0, 1.0)) as u8;
664 out[i * 4 + 2] = (tint.2 as f32 * self.density[i].clamp(0.0, 1.0)) as u8;
665 out[i * 4 + 3] = d;
666 }
667 out
668 }
669
670 pub fn to_rgba_velocity(&self) -> Vec<u8> {
672 let n = self.width * self.height;
673 let max_v = self.max_velocity().max(0.01);
674 let mut out = vec![0u8; n * 4];
675 for i in 0..n {
676 let speed = (self.vx[i] * self.vx[i] + self.vy[i] * self.vy[i])
677 .sqrt() / max_v;
678 let angle = self.vy[i].atan2(self.vx[i]); let hue = (angle / std::f32::consts::TAU + 0.5).fract(); let (r, g, b) = hsv_to_rgb(hue, 1.0, speed);
682 out[i * 4 ] = (r * 255.0) as u8;
683 out[i * 4 + 1] = (g * 255.0) as u8;
684 out[i * 4 + 2] = (b * 255.0) as u8;
685 out[i * 4 + 3] = 255;
686 }
687 out
688 }
689}
690
691fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
692 let i = (h * 6.0) as u32;
693 let f = h * 6.0 - i as f32;
694 let p = v * (1.0 - s);
695 let q = v * (1.0 - f * s);
696 let t = v * (1.0 - (1.0 - f) * s);
697 match i % 6 {
698 0 => (v, t, p),
699 1 => (q, v, p),
700 2 => (p, v, t),
701 3 => (p, q, v),
702 4 => (t, p, v),
703 _ => (v, p, q),
704 }
705}
706
707pub struct FluidParams {
711 pub viscosity: f32,
712 pub diffusion: f32,
713 pub buoyancy: f32,
714 pub vorticity_strength: f32,
715 pub decay: f32,
716 pub gravity: Vec2,
717}
718
719impl FluidParams {
720 pub fn smoke() -> Self {
722 Self {
723 viscosity: 1e-5,
724 diffusion: 1e-6,
725 buoyancy: 0.3,
726 vorticity_strength: 0.5,
727 decay: 0.998,
728 gravity: Vec2::new(0.0, 0.0),
729 }
730 }
731
732 pub fn lava() -> Self {
734 Self {
735 viscosity: 0.01,
736 diffusion: 0.001,
737 buoyancy: 0.05,
738 vorticity_strength: 0.1,
739 decay: 0.9995,
740 gravity: Vec2::new(0.0, -2.0),
741 }
742 }
743
744 pub fn water() -> Self {
746 Self {
747 viscosity: 1e-4,
748 diffusion: 0.0,
749 buoyancy: 0.0,
750 vorticity_strength: 0.2,
751 decay: 1.0,
752 gravity: Vec2::new(0.0, -9.8),
753 }
754 }
755
756 pub fn aether() -> Self {
758 Self {
759 viscosity: 5e-5,
760 diffusion: 2e-5,
761 buoyancy: 0.1,
762 vorticity_strength: 1.0,
763 decay: 0.9999,
764 gravity: Vec2::ZERO,
765 }
766 }
767
768 pub fn apply(&self, grid: &mut FluidGrid) {
770 grid.viscosity = self.viscosity;
771 grid.diffusion = self.diffusion;
772 grid.buoyancy = self.buoyancy;
773 grid.vorticity_strength = self.vorticity_strength;
774 grid.decay = self.decay;
775 grid.gravity = self.gravity;
776 }
777}
778
779pub struct FluidRecorder {
783 pub frames: Vec<FluidFrame>,
784 pub max_frames: usize,
785}
786
787pub struct FluidFrame {
789 pub time: f32,
790 pub density: Vec<f32>,
791 pub vx: Vec<f32>,
792 pub vy: Vec<f32>,
793}
794
795impl FluidRecorder {
796 pub fn new(max_frames: usize) -> Self {
797 Self { frames: Vec::new(), max_frames }
798 }
799
800 pub fn record(&mut self, grid: &FluidGrid) {
802 if self.frames.len() >= self.max_frames {
803 self.frames.remove(0);
804 }
805 self.frames.push(FluidFrame {
806 time: grid.time,
807 density: grid.density.clone(),
808 vx: grid.vx.clone(),
809 vy: grid.vy.clone(),
810 });
811 }
812
813 pub fn len(&self) -> usize { self.frames.len() }
815 pub fn is_empty(&self) -> bool { self.frames.is_empty() }
816
817 pub fn mean_density_at(&self, x: usize, y: usize, width: usize) -> f32 {
819 if self.frames.is_empty() { return 0.0; }
820 let i = y * width + x;
821 let sum: f32 = self.frames.iter().map(|f| f.density.get(i).copied().unwrap_or(0.0)).sum();
822 sum / self.frames.len() as f32
823 }
824
825 pub fn restore(&self, grid: &mut FluidGrid, frame_idx: usize) -> bool {
827 if frame_idx >= self.frames.len() { return false; }
828 let f = &self.frames[frame_idx];
829 grid.density.copy_from_slice(&f.density);
830 grid.vx.copy_from_slice(&f.vx);
831 grid.vy.copy_from_slice(&f.vy);
832 grid.time = f.time;
833 true
834 }
835}
836
837#[cfg(test)]
840mod tests {
841 use super::*;
842
843 #[test]
844 fn test_new_grid() {
845 let g = FluidGrid::new(32, 32, 1.0 / 32.0);
846 assert_eq!(g.width, 32);
847 assert_eq!(g.height, 32);
848 assert_eq!(g.density.len(), 1024);
849 assert!(g.total_density() < 1e-6);
850 }
851
852 #[test]
853 fn test_add_density_and_velocity() {
854 let mut g = FluidGrid::new(32, 32, 1.0 / 32.0);
855 g.add_density(16, 16, 1.0);
856 g.add_velocity(16, 16, 1.0, 0.0);
857 assert!(g.density[g.idx(16, 16)] > 0.9);
858 assert!(g.vx[g.idx(16, 16)] > 0.5);
859 }
860
861 #[test]
862 fn test_step_runs() {
863 let mut g = FluidGrid::new(16, 16, 1.0 / 16.0);
864 g.add_density(8, 8, 1.0);
865 g.add_velocity(8, 8, 0.0, 2.0);
866 for _ in 0..10 {
867 g.step(0.016);
868 }
869 assert!(g.total_density() > 0.0);
871 }
872
873 #[test]
874 fn test_obstacle() {
875 let mut g = FluidGrid::new(32, 32, 1.0 / 32.0);
876 g.add_obstacle_rect(10, 10, 20, 20);
877 assert_eq!(g.obstacle[g.idx(15, 15)], 0.0);
878 assert_eq!(g.obstacle[g.idx(5, 5)], 1.0);
879 }
880
881 #[test]
882 fn test_max_velocity() {
883 let mut g = FluidGrid::new(16, 16, 1.0 / 16.0);
884 g.add_velocity(8, 8, 3.0, 4.0);
885 assert!((g.max_velocity() - 5.0).abs() < 0.01);
886 }
887
888 #[test]
889 fn test_rgba_density() {
890 let mut g = FluidGrid::new(4, 4, 0.25);
891 g.add_density(2, 2, 0.5);
892 let buf = g.to_rgba_density((255, 128, 0));
893 assert_eq!(buf.len(), 4 * 4 * 4);
894 }
895
896 #[test]
897 fn test_splash_and_swirl() {
898 let mut g = FluidGrid::new(32, 32, 1.0 / 32.0);
899 g.splash(16.0, 16.0, 8.0, 5.0);
900 g.swirl(16.0, 16.0, 8.0, 3.0);
901 assert!(g.max_velocity() > 0.0);
902 }
903
904 #[test]
905 fn test_clear() {
906 let mut g = FluidGrid::new(16, 16, 0.0625);
907 g.add_density(8, 8, 1.0);
908 g.add_velocity(8, 8, 2.0, 2.0);
909 g.clear();
910 assert!(g.total_density() < 1e-6);
911 assert!(g.max_velocity() < 1e-6);
912 }
913
914 #[test]
915 fn test_fluid_params_apply() {
916 let mut g = FluidGrid::new(16, 16, 0.0625);
917 FluidParams::smoke().apply(&mut g);
918 assert!(g.buoyancy > 0.2);
919 FluidParams::lava().apply(&mut g);
920 assert!(g.viscosity > 0.005);
921 }
922
923 #[test]
924 fn test_recorder() {
925 let mut g = FluidGrid::new(8, 8, 0.125);
926 let mut rec = FluidRecorder::new(10);
927 g.add_density(4, 4, 1.0);
928 rec.record(&g);
929 assert_eq!(rec.len(), 1);
930 g.clear();
931 assert!(g.total_density() < 1e-6);
932 let restored = rec.restore(&mut g, 0);
933 assert!(restored);
934 assert!(g.total_density() > 0.5);
935 }
936}