Skip to main content

pfx_physics/
fluid_inspect.rs

1use super::{Fluid, FluidState, Particle};
2use std::collections::BTreeMap;
3
4impl Fluid {
5    pub fn inspect(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> FluidState {
6        self.inspect_with_row(device, queue, None)
7    }
8
9    pub fn inspect_row(&self, device: &wgpu::Device, queue: &wgpu::Queue, row: u32) -> FluidState {
10        self.inspect_with_row(device, queue, Some(row))
11    }
12
13    fn inspect_with_row(
14        &self,
15        device: &wgpu::Device,
16        queue: &wgpu::Queue,
17        row: Option<u32>,
18    ) -> FluidState {
19        let particle_bytes = self.high as u64 * std::mem::size_of::<Particle>() as u64;
20        let padded = (self.width * 8).div_ceil(256) * 256;
21        let height_bytes = padded as u64 * self.height as u64;
22        let particles = device.create_buffer(&wgpu::BufferDescriptor {
23            label: Some("Fluid particle inspection"),
24            size: particle_bytes.max(4),
25            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
26            mapped_at_creation: false,
27        });
28        let height = device.create_buffer(&wgpu::BufferDescriptor {
29            label: Some("Fluid height inspection"),
30            size: height_bytes,
31            usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
32            mapped_at_creation: false,
33        });
34        let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
35            label: Some("Fluid inspection"),
36        });
37        if particle_bytes > 0 {
38            encoder.copy_buffer_to_buffer(&self.particles, 0, &particles, 0, particle_bytes);
39        }
40        encoder.copy_texture_to_buffer(
41            self.height_texture.as_image_copy(),
42            wgpu::TexelCopyBufferInfo {
43                buffer: &height,
44                layout: wgpu::TexelCopyBufferLayout {
45                    offset: 0,
46                    bytes_per_row: Some(padded),
47                    rows_per_image: Some(self.height),
48                },
49            },
50            self.height_texture.size(),
51        );
52        queue.submit(Some(encoder.finish()));
53        let particle_data = if particle_bytes > 0 {
54            mapped(device, &particles)
55        } else {
56            Vec::new()
57        };
58        let height_data = mapped(device, &height);
59        let mut state = FluidState {
60            live: 0,
61            asleep: self.asleep(),
62            bounds: None,
63            height_min: f32::INFINITY,
64            height_max: f32::NEG_INFINITY,
65            height_mean: 0.0,
66            channel_histogram: std::array::from_fn(|_| BTreeMap::new()),
67            resting: self.still > self.settle,
68            row: row
69                .filter(|r| *r < self.height)
70                .map(|_| Vec::with_capacity(self.width as usize)),
71        };
72        for particle in bytemuck::cast_slice::<u8, Particle>(&particle_data) {
73            if particle.born < 0.0 || !particle.pos.iter().all(|v| v.is_finite()) {
74                continue;
75            }
76            state.live += 1;
77            match &mut state.bounds {
78                Some((lo, hi)) => {
79                    for axis in 0..2 {
80                        lo[axis] = lo[axis].min(particle.pos[axis]);
81                        hi[axis] = hi[axis].max(particle.pos[axis]);
82                    }
83                }
84                None => state.bounds = Some((particle.pos, particle.pos)),
85            }
86        }
87        let mut sum = 0.0f64;
88        for y in 0..self.height as usize {
89            for x in 0..self.width as usize {
90                let at = y * padded as usize + x * 8;
91                let channels = std::array::from_fn::<_, 4, _>(|channel| {
92                    half(u16::from_le_bytes([
93                        height_data[at + channel * 2],
94                        height_data[at + channel * 2 + 1],
95                    ]))
96                });
97                let h = channels[0];
98                state.height_min = state.height_min.min(h);
99                state.height_max = state.height_max.max(h);
100                sum += f64::from(h);
101                if row == Some(y as u32)
102                    && let Some(samples) = &mut state.row
103                {
104                    samples.push(h);
105                }
106                if h > 0.01 {
107                    for channel in 0..3 {
108                        let value = (channels[channel + 1] / h * 16.0).round() as i32;
109                        *state.channel_histogram[channel].entry(value).or_default() += 1;
110                    }
111                }
112            }
113        }
114        state.height_mean = (sum / f64::from(self.width * self.height)) as f32;
115        state
116    }
117}
118
119pub(super) fn mapped(device: &wgpu::Device, buffer: &wgpu::Buffer) -> Vec<u8> {
120    let slice = buffer.slice(..);
121    let (send, recv) = std::sync::mpsc::channel();
122    slice.map_async(wgpu::MapMode::Read, move |result| {
123        let _ = send.send(result);
124    });
125    device
126        .poll(wgpu::PollType::wait_indefinitely())
127        .expect("fluid inspection poll");
128    recv.recv()
129        .expect("fluid inspection callback")
130        .expect("fluid inspection map");
131    slice.get_mapped_range().expect("a mapped range").to_vec()
132}
133
134fn half(h: u16) -> f32 {
135    let sign = ((h >> 15) & 1) as u32;
136    let exp = ((h >> 10) & 0x1f) as u32;
137    let man = (h & 0x3ff) as u32;
138    let bits = if exp == 0 {
139        if man == 0 {
140            sign << 31
141        } else {
142            let mut frac = man;
143            let mut exponent = 113u32;
144            while frac & 0x400 == 0 {
145                frac <<= 1;
146                exponent -= 1;
147            }
148            frac &= 0x3ff;
149            (sign << 31) | (exponent << 23) | (frac << 13)
150        }
151    } else if exp == 31 {
152        (sign << 31) | (255 << 23) | (man << 13)
153    } else {
154        (sign << 31) | ((exp + 112) << 23) | (man << 13)
155    };
156    f32::from_bits(bits)
157}