1use super::{ColorMap, Vertex3D};
6use crate::simulation::SimulationGrid3D;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum SliceAxis {
11 #[default]
13 XY,
14 XZ,
16 YZ,
18}
19
20#[derive(Debug, Clone)]
22pub struct SliceConfig {
23 pub axis: SliceAxis,
25 pub position: f32,
27 pub opacity: f32,
29 pub color_map: ColorMap,
31 pub show_grid: bool,
33}
34
35impl Default for SliceConfig {
36 fn default() -> Self {
37 Self {
38 axis: SliceAxis::XY,
39 position: 0.5,
40 opacity: 0.8,
41 color_map: ColorMap::BlueWhiteRed,
42 show_grid: false,
43 }
44 }
45}
46
47impl SliceConfig {
48 pub fn new(axis: SliceAxis, position: f32) -> Self {
49 Self {
50 axis,
51 position: position.clamp(0.0, 1.0),
52 ..Default::default()
53 }
54 }
55}
56
57pub struct SliceRenderer {
59 pub slices: Vec<SliceConfig>,
61 max_pressure: f32,
63}
64
65impl Default for SliceRenderer {
66 fn default() -> Self {
67 Self::new()
68 }
69}
70
71impl SliceRenderer {
72 pub fn new() -> Self {
73 Self {
74 slices: vec![SliceConfig::default()],
75 max_pressure: 1.0,
76 }
77 }
78
79 pub fn add_slice(&mut self, config: SliceConfig) {
81 self.slices.push(config);
82 }
83
84 pub fn remove_slice(&mut self, index: usize) {
86 if index < self.slices.len() {
87 self.slices.remove(index);
88 }
89 }
90
91 pub fn clear(&mut self) {
93 self.slices.clear();
94 }
95
96 pub fn set_max_pressure(&mut self, max: f32) {
98 self.max_pressure = max.max(0.001);
99 }
100
101 pub fn generate_vertices(
103 &self,
104 grid: &SimulationGrid3D,
105 grid_physical_size: (f32, f32, f32),
106 ) -> Vec<Vertex3D> {
107 let mut vertices = Vec::new();
108
109 for config in &self.slices {
110 vertices.extend(self.generate_slice_vertices(grid, grid_physical_size, config));
111 }
112
113 vertices
114 }
115
116 pub fn generate_slice_vertices(
118 &self,
119 grid: &SimulationGrid3D,
120 grid_physical_size: (f32, f32, f32),
121 config: &SliceConfig,
122 ) -> Vec<Vertex3D> {
123 let (phys_w, phys_h, phys_d) = grid_physical_size;
124 let (w, h, d) = (grid.width, grid.height, grid.depth);
125
126 match config.axis {
127 SliceAxis::XY => {
128 let z_idx = ((d as f32 - 1.0) * config.position) as usize;
129 let z_pos = phys_d * config.position;
130 let slice_data = grid.get_xy_slice(z_idx);
131
132 self.create_quad_grid(
133 &slice_data,
134 w,
135 h,
136 |x, y| [x * phys_w / w as f32, y * phys_h / h as f32, z_pos],
137 [0.0, 0.0, 1.0],
138 config,
139 )
140 }
141 SliceAxis::XZ => {
142 let y_idx = ((h as f32 - 1.0) * config.position) as usize;
143 let y_pos = phys_h * config.position;
144 let slice_data = grid.get_xz_slice(y_idx);
145
146 self.create_quad_grid(
147 &slice_data,
148 w,
149 d,
150 |x, z| [x * phys_w / w as f32, y_pos, z * phys_d / d as f32],
151 [0.0, 1.0, 0.0],
152 config,
153 )
154 }
155 SliceAxis::YZ => {
156 let x_idx = ((w as f32 - 1.0) * config.position) as usize;
157 let x_pos = phys_w * config.position;
158 let slice_data = grid.get_yz_slice(x_idx);
159
160 self.create_quad_grid(
161 &slice_data,
162 h,
163 d,
164 |y, z| [x_pos, y * phys_h / h as f32, z * phys_d / d as f32],
165 [1.0, 0.0, 0.0],
166 config,
167 )
168 }
169 }
170 }
171
172 fn create_quad_grid<F>(
174 &self,
175 data: &[f32],
176 width: usize,
177 height: usize,
178 pos_fn: F,
179 normal: [f32; 3],
180 config: &SliceConfig,
181 ) -> Vec<Vertex3D>
182 where
183 F: Fn(f32, f32) -> [f32; 3],
184 {
185 let mut vertices = Vec::with_capacity((width - 1) * (height - 1) * 6);
186
187 for y in 0..height - 1 {
188 for x in 0..width - 1 {
189 let idx00 = y * width + x;
191 let idx10 = y * width + x + 1;
192 let idx01 = (y + 1) * width + x;
193 let idx11 = (y + 1) * width + x + 1;
194
195 let p00 = data.get(idx00).copied().unwrap_or(0.0);
197 let p10 = data.get(idx10).copied().unwrap_or(0.0);
198 let p01 = data.get(idx01).copied().unwrap_or(0.0);
199 let p11 = data.get(idx11).copied().unwrap_or(0.0);
200
201 let mut c00 = config.color_map.map(p00, self.max_pressure);
202 let mut c10 = config.color_map.map(p10, self.max_pressure);
203 let mut c01 = config.color_map.map(p01, self.max_pressure);
204 let mut c11 = config.color_map.map(p11, self.max_pressure);
205
206 c00[3] *= config.opacity;
208 c10[3] *= config.opacity;
209 c01[3] *= config.opacity;
210 c11[3] *= config.opacity;
211
212 let pos00 = pos_fn(x as f32, y as f32);
214 let pos10 = pos_fn(x as f32 + 1.0, y as f32);
215 let pos01 = pos_fn(x as f32, y as f32 + 1.0);
216 let pos11 = pos_fn(x as f32 + 1.0, y as f32 + 1.0);
217
218 vertices.push(Vertex3D::new(pos00, c00).with_normal(normal));
220 vertices.push(Vertex3D::new(pos10, c10).with_normal(normal));
221 vertices.push(Vertex3D::new(pos01, c01).with_normal(normal));
222
223 vertices.push(Vertex3D::new(pos10, c10).with_normal(normal));
224 vertices.push(Vertex3D::new(pos11, c11).with_normal(normal));
225 vertices.push(Vertex3D::new(pos01, c01).with_normal(normal));
226 }
227 }
228
229 vertices
230 }
231
232 pub fn generate_grid_lines(
234 &self,
235 grid_physical_size: (f32, f32, f32),
236 divisions: u32,
237 ) -> Vec<Vertex3D> {
238 let mut vertices = Vec::new();
239 let (phys_w, phys_h, phys_d) = grid_physical_size;
240 let line_color = [0.5, 0.5, 0.5, 0.3];
241
242 for config in &self.slices {
243 if !config.show_grid {
244 continue;
245 }
246
247 match config.axis {
248 SliceAxis::XY => {
249 let z = phys_d * config.position;
250 for i in 0..=divisions {
251 let t = i as f32 / divisions as f32;
252 vertices.push(Vertex3D::new([0.0, t * phys_h, z], line_color));
254 vertices.push(Vertex3D::new([phys_w, t * phys_h, z], line_color));
255 vertices.push(Vertex3D::new([t * phys_w, 0.0, z], line_color));
257 vertices.push(Vertex3D::new([t * phys_w, phys_h, z], line_color));
258 }
259 }
260 SliceAxis::XZ => {
261 let y = phys_h * config.position;
262 for i in 0..=divisions {
263 let t = i as f32 / divisions as f32;
264 vertices.push(Vertex3D::new([0.0, y, t * phys_d], line_color));
266 vertices.push(Vertex3D::new([phys_w, y, t * phys_d], line_color));
267 vertices.push(Vertex3D::new([t * phys_w, y, 0.0], line_color));
269 vertices.push(Vertex3D::new([t * phys_w, y, phys_d], line_color));
270 }
271 }
272 SliceAxis::YZ => {
273 let x = phys_w * config.position;
274 for i in 0..=divisions {
275 let t = i as f32 / divisions as f32;
276 vertices.push(Vertex3D::new([x, 0.0, t * phys_d], line_color));
278 vertices.push(Vertex3D::new([x, phys_h, t * phys_d], line_color));
279 vertices.push(Vertex3D::new([x, t * phys_h, 0.0], line_color));
281 vertices.push(Vertex3D::new([x, t * phys_h, phys_d], line_color));
282 }
283 }
284 }
285 }
286
287 vertices
288 }
289}
290
291pub struct MultiSliceView {
293 pub xy_position: f32,
295 pub xz_position: f32,
297 pub yz_position: f32,
299 pub show_xy: bool,
301 pub show_xz: bool,
303 pub show_yz: bool,
305 pub color_map: ColorMap,
307 pub opacity: f32,
309}
310
311impl Default for MultiSliceView {
312 fn default() -> Self {
313 Self {
314 xy_position: 0.5,
315 xz_position: 0.5,
316 yz_position: 0.5,
317 show_xy: true,
318 show_xz: false,
319 show_yz: false,
320 color_map: ColorMap::BlueWhiteRed,
321 opacity: 0.8,
322 }
323 }
324}
325
326impl MultiSliceView {
327 pub fn to_slice_configs(&self) -> Vec<SliceConfig> {
329 let mut configs = Vec::new();
330
331 if self.show_xy {
332 configs.push(SliceConfig {
333 axis: SliceAxis::XY,
334 position: self.xy_position,
335 opacity: self.opacity,
336 color_map: self.color_map,
337 show_grid: false,
338 });
339 }
340
341 if self.show_xz {
342 configs.push(SliceConfig {
343 axis: SliceAxis::XZ,
344 position: self.xz_position,
345 opacity: self.opacity,
346 color_map: self.color_map,
347 show_grid: false,
348 });
349 }
350
351 if self.show_yz {
352 configs.push(SliceConfig {
353 axis: SliceAxis::YZ,
354 position: self.yz_position,
355 opacity: self.opacity,
356 color_map: self.color_map,
357 show_grid: false,
358 });
359 }
360
361 configs
362 }
363}
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use crate::simulation::physics::{AcousticParams3D, Environment};
369
370 fn create_test_grid() -> SimulationGrid3D {
371 let params = AcousticParams3D::new(Environment::default(), 0.1);
372 SimulationGrid3D::new(16, 16, 16, params)
373 }
374
375 #[test]
376 fn test_slice_config() {
377 let config = SliceConfig::default();
378 assert_eq!(config.axis, SliceAxis::XY);
379 assert!((config.position - 0.5).abs() < 0.001);
380 }
381
382 #[test]
383 fn test_slice_renderer() {
384 let grid = create_test_grid();
385 let renderer = SliceRenderer::new();
386
387 let vertices = renderer.generate_vertices(&grid, (1.6, 1.6, 1.6));
388 assert!(!vertices.is_empty());
389 }
390
391 #[test]
392 fn test_multi_slice_view() {
393 let view = MultiSliceView::default();
394 let configs = view.to_slice_configs();
395
396 assert_eq!(configs.len(), 1);
398 assert_eq!(configs[0].axis, SliceAxis::XY);
399 }
400
401 #[test]
402 fn test_all_axes() {
403 let grid = create_test_grid();
404 let mut renderer = SliceRenderer::new();
405
406 renderer.clear();
407 renderer.add_slice(SliceConfig::new(SliceAxis::XY, 0.5));
408 renderer.add_slice(SliceConfig::new(SliceAxis::XZ, 0.5));
409 renderer.add_slice(SliceConfig::new(SliceAxis::YZ, 0.5));
410
411 let vertices = renderer.generate_vertices(&grid, (1.6, 1.6, 1.6));
412 assert!(vertices.len() > 100); }
414}