1mod solver;
8mod config;
9
10pub use solver::*;
11pub use config::*;
12
13use ndarray::Array2;
14use serde::{Deserialize, Serialize};
15use std::f64::consts::PI;
16
17#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
19pub struct Point3D {
20 pub x: f64,
22 pub y: f64,
24 pub z: f64,
26}
27
28impl Point3D {
29 pub fn new(x: f64, y: f64, z: f64) -> Self {
31 Self { x, y, z }
32 }
33
34 pub fn distance_to(&self, other: &Point3D) -> f64 {
36 let dx = self.x - other.x;
37 let dy = self.y - other.y;
38 let dz = self.z - other.z;
39 (dx * dx + dy * dy + dz * dz).sqrt()
40 }
41
42 pub fn to_spherical(&self) -> (f64, f64, f64) {
44 let r = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt();
45 let theta = (self.z / r).acos(); let phi = self.y.atan2(self.x); (r, theta, phi)
48 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
53pub enum RoomGeometry {
54 Rectangular(RectangularRoom),
55 LShaped(LShapedRoom),
56}
57
58impl RoomGeometry {
59 pub fn generate_mesh(&self, elements_per_meter: usize) -> RoomMesh {
60 match self {
61 RoomGeometry::Rectangular(room) => room.generate_mesh(elements_per_meter),
62 RoomGeometry::LShaped(room) => room.generate_mesh(elements_per_meter),
63 }
64 }
65
66 pub fn generate_adaptive_mesh(
73 &self,
74 base_elements_per_meter: usize,
75 frequency: f64,
76 sources: &[Source],
77 speed_of_sound: f64,
78 ) -> RoomMesh {
79 match self {
80 RoomGeometry::Rectangular(room) => {
81 room.generate_adaptive_mesh(base_elements_per_meter, frequency, sources, speed_of_sound)
82 }
83 RoomGeometry::LShaped(room) => {
84 room.generate_adaptive_mesh(base_elements_per_meter, frequency, sources, speed_of_sound)
85 }
86 }
87 }
88
89 pub fn get_edges(&self) -> Vec<(Point3D, Point3D)> {
90 match self {
91 RoomGeometry::Rectangular(room) => room.get_edges(),
92 RoomGeometry::LShaped(room) => room.get_edges(),
93 }
94 }
95}
96
97struct SurfaceInfo {
99 origin: Point3D,
100 u_dir: Point3D,
101 v_dir: Point3D,
102 u_length: f64,
103 v_length: f64,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct RectangularRoom {
109 pub width: f64, pub depth: f64, pub height: f64, }
113
114impl RectangularRoom {
115 pub fn new(width: f64, depth: f64, height: f64) -> Self {
116 Self {
117 width,
118 depth,
119 height,
120 }
121 }
122
123 pub fn generate_mesh(&self, elements_per_meter: usize) -> RoomMesh {
125 let nx = (self.width * elements_per_meter as f64).ceil() as usize;
126 let ny = (self.depth * elements_per_meter as f64).ceil() as usize;
127 let nz = (self.height * elements_per_meter as f64).ceil() as usize;
128
129 let mut nodes = Vec::new();
130 let mut elements = Vec::new();
131
132 self.add_surface_mesh(
134 &mut nodes,
135 &mut elements,
136 Point3D::new(0.0, 0.0, 0.0),
137 Point3D::new(self.width, 0.0, 0.0),
138 Point3D::new(0.0, self.depth, 0.0),
139 nx,
140 ny,
141 );
142
143 self.add_surface_mesh(
145 &mut nodes,
146 &mut elements,
147 Point3D::new(0.0, 0.0, self.height),
148 Point3D::new(self.width, 0.0, self.height),
149 Point3D::new(0.0, self.depth, self.height),
150 nx,
151 ny,
152 );
153
154 self.add_surface_mesh(
156 &mut nodes,
157 &mut elements,
158 Point3D::new(0.0, 0.0, 0.0),
159 Point3D::new(self.width, 0.0, 0.0),
160 Point3D::new(0.0, 0.0, self.height),
161 nx,
162 nz,
163 );
164
165 self.add_surface_mesh(
167 &mut nodes,
168 &mut elements,
169 Point3D::new(0.0, self.depth, 0.0),
170 Point3D::new(self.width, self.depth, 0.0),
171 Point3D::new(0.0, self.depth, self.height),
172 nx,
173 nz,
174 );
175
176 self.add_surface_mesh(
178 &mut nodes,
179 &mut elements,
180 Point3D::new(0.0, 0.0, 0.0),
181 Point3D::new(0.0, self.depth, 0.0),
182 Point3D::new(0.0, 0.0, self.height),
183 ny,
184 nz,
185 );
186
187 self.add_surface_mesh(
189 &mut nodes,
190 &mut elements,
191 Point3D::new(self.width, 0.0, 0.0),
192 Point3D::new(self.width, self.depth, 0.0),
193 Point3D::new(self.width, 0.0, self.height),
194 ny,
195 nz,
196 );
197
198 RoomMesh { nodes, elements }
199 }
200
201 pub fn generate_adaptive_mesh(
203 &self,
204 base_elements_per_meter: usize,
205 frequency: f64,
206 sources: &[Source],
207 speed_of_sound: f64,
208 ) -> RoomMesh {
209 let wavelength = speed_of_sound / frequency;
210
211 let target_element_size = wavelength / 8.0;
213
214 let mut nodes = Vec::new();
215 let mut elements = Vec::new();
216
217 let surfaces = vec![
219 SurfaceInfo {
221 origin: Point3D::new(0.0, 0.0, 0.0),
222 u_dir: Point3D::new(self.width, 0.0, 0.0),
223 v_dir: Point3D::new(0.0, self.depth, 0.0),
224 u_length: self.width,
225 v_length: self.depth,
226 },
227 SurfaceInfo {
229 origin: Point3D::new(0.0, 0.0, self.height),
230 u_dir: Point3D::new(self.width, 0.0, self.height),
231 v_dir: Point3D::new(0.0, self.depth, self.height),
232 u_length: self.width,
233 v_length: self.depth,
234 },
235 SurfaceInfo {
237 origin: Point3D::new(0.0, 0.0, 0.0),
238 u_dir: Point3D::new(self.width, 0.0, 0.0),
239 v_dir: Point3D::new(0.0, 0.0, self.height),
240 u_length: self.width,
241 v_length: self.height,
242 },
243 SurfaceInfo {
245 origin: Point3D::new(0.0, self.depth, 0.0),
246 u_dir: Point3D::new(self.width, self.depth, 0.0),
247 v_dir: Point3D::new(0.0, self.depth, self.height),
248 u_length: self.width,
249 v_length: self.height,
250 },
251 SurfaceInfo {
253 origin: Point3D::new(0.0, 0.0, 0.0),
254 u_dir: Point3D::new(0.0, self.depth, 0.0),
255 v_dir: Point3D::new(0.0, 0.0, self.height),
256 u_length: self.depth,
257 v_length: self.height,
258 },
259 SurfaceInfo {
261 origin: Point3D::new(self.width, 0.0, 0.0),
262 u_dir: Point3D::new(self.width, self.depth, 0.0),
263 v_dir: Point3D::new(self.width, 0.0, self.height),
264 u_length: self.depth,
265 v_length: self.height,
266 },
267 ];
268
269 for surface in surfaces {
270 self.add_adaptive_surface_mesh(
271 &mut nodes,
272 &mut elements,
273 &surface,
274 target_element_size,
275 base_elements_per_meter,
276 sources,
277 );
278 }
279
280 RoomMesh { nodes, elements }
281 }
282
283 fn add_adaptive_surface_mesh(
284 &self,
285 nodes: &mut Vec<Point3D>,
286 elements: &mut Vec<SurfaceElement>,
287 surface: &SurfaceInfo,
288 target_element_size: f64,
289 base_elements_per_meter: usize,
290 sources: &[Source],
291 ) {
292 let nu_base = (surface.u_length / target_element_size).ceil() as usize;
299 let nv_base = (surface.v_length / target_element_size).ceil() as usize;
300
301 let nu_min = (surface.u_length * base_elements_per_meter as f64).ceil() as usize;
303 let nv_min = (surface.v_length * base_elements_per_meter as f64).ceil() as usize;
304
305 let nu = nu_base.max(nu_min);
307 let nv = nv_base.max(nv_min);
308
309 let near_source = sources.iter().any(|source| {
311 let dist_to_surface = self.distance_point_to_surface(&source.position, surface);
312 dist_to_surface < target_element_size * 2.0
313 });
314
315 let (nu_final, nv_final) = if near_source {
317 (nu * 2, nv * 2)
318 } else {
319 (nu, nv)
320 };
321
322 let base_idx = nodes.len();
324
325 for j in 0..=nv_final {
327 for i in 0..=nu_final {
328 let u = self.graded_parameter(i as f64 / nu_final as f64);
330 let v = self.graded_parameter(j as f64 / nv_final as f64);
331
332 let u_vec = Point3D::new(
333 surface.u_dir.x - surface.origin.x,
334 surface.u_dir.y - surface.origin.y,
335 surface.u_dir.z - surface.origin.z,
336 );
337 let v_vec = Point3D::new(
338 surface.v_dir.x - surface.origin.x,
339 surface.v_dir.y - surface.origin.y,
340 surface.v_dir.z - surface.origin.z,
341 );
342
343 let node = Point3D::new(
344 surface.origin.x + u * u_vec.x + v * v_vec.x,
345 surface.origin.y + u * u_vec.y + v * v_vec.y,
346 surface.origin.z + u * u_vec.z + v * v_vec.z,
347 );
348
349 nodes.push(node);
350 }
351 }
352
353 for j in 0..nv_final {
355 for i in 0..nu_final {
356 let i0 = base_idx + j * (nu_final + 1) + i;
357 let i1 = i0 + 1;
358 let i2 = i0 + (nu_final + 1);
359 let i3 = i2 + 1;
360
361 elements.push(SurfaceElement {
363 nodes: vec![i0, i1, i2],
364 });
365
366 elements.push(SurfaceElement {
368 nodes: vec![i1, i3, i2],
369 });
370 }
371 }
372 }
373
374 fn graded_parameter(&self, t: f64) -> f64 {
377 let grading = 0.15;
380
381 if t < grading {
382 0.5 * (t / grading).powi(2) * grading
384 } else if t > 1.0 - grading {
385 let t_rel = (t - (1.0 - grading)) / grading;
387 1.0 - 0.5 * grading * (1.0 - t_rel).powi(2)
388 } else {
389 let t_mid = (t - grading) / (1.0 - 2.0 * grading);
391 grading + t_mid * (1.0 - 2.0 * grading)
392 }
393 }
394
395 fn distance_point_to_surface(&self, point: &Point3D, surface: &SurfaceInfo) -> f64 {
397 let u_vec = Point3D::new(
399 surface.u_dir.x - surface.origin.x,
400 surface.u_dir.y - surface.origin.y,
401 surface.u_dir.z - surface.origin.z,
402 );
403 let v_vec = Point3D::new(
404 surface.v_dir.x - surface.origin.x,
405 surface.v_dir.y - surface.origin.y,
406 surface.v_dir.z - surface.origin.z,
407 );
408
409 let normal = Point3D::new(
411 u_vec.y * v_vec.z - u_vec.z * v_vec.y,
412 u_vec.z * v_vec.x - u_vec.x * v_vec.z,
413 u_vec.x * v_vec.y - u_vec.y * v_vec.x,
414 );
415
416 let normal_length = (normal.x * normal.x + normal.y * normal.y + normal.z * normal.z).sqrt();
417
418 if normal_length < 1e-10 {
419 return point.distance_to(&surface.origin);
420 }
421
422 let nx = normal.x / normal_length;
424 let ny = normal.y / normal_length;
425 let nz = normal.z / normal_length;
426
427 let dx = point.x - surface.origin.x;
429 let dy = point.y - surface.origin.y;
430 let dz = point.z - surface.origin.z;
431
432 (dx * nx + dy * ny + dz * nz).abs()
433 }
434
435 pub fn get_edges(&self) -> Vec<(Point3D, Point3D)> {
437 vec![
438 (Point3D::new(0.0, 0.0, 0.0), Point3D::new(self.width, 0.0, 0.0)),
440 (Point3D::new(self.width, 0.0, 0.0), Point3D::new(self.width, self.depth, 0.0)),
441 (Point3D::new(self.width, self.depth, 0.0), Point3D::new(0.0, self.depth, 0.0)),
442 (Point3D::new(0.0, self.depth, 0.0), Point3D::new(0.0, 0.0, 0.0)),
443 (Point3D::new(0.0, 0.0, self.height), Point3D::new(self.width, 0.0, self.height)),
445 (Point3D::new(self.width, 0.0, self.height), Point3D::new(self.width, self.depth, self.height)),
446 (Point3D::new(self.width, self.depth, self.height), Point3D::new(0.0, self.depth, self.height)),
447 (Point3D::new(0.0, self.depth, self.height), Point3D::new(0.0, 0.0, self.height)),
448 (Point3D::new(0.0, 0.0, 0.0), Point3D::new(0.0, 0.0, self.height)),
450 (Point3D::new(self.width, 0.0, 0.0), Point3D::new(self.width, 0.0, self.height)),
451 (Point3D::new(self.width, self.depth, 0.0), Point3D::new(self.width, self.depth, self.height)),
452 (Point3D::new(0.0, self.depth, 0.0), Point3D::new(0.0, self.depth, self.height)),
453 ]
454 }
455
456 fn add_surface_mesh(
457 &self,
458 nodes: &mut Vec<Point3D>,
459 elements: &mut Vec<SurfaceElement>,
460 origin: Point3D,
461 u_dir: Point3D,
462 v_dir: Point3D,
463 nu: usize,
464 nv: usize,
465 ) {
466 let base_idx = nodes.len();
467
468 for j in 0..=nv {
470 for i in 0..=nu {
471 let u = i as f64 / nu as f64;
472 let v = j as f64 / nv as f64;
473
474 let node = Point3D::new(
475 origin.x + u * (u_dir.x - origin.x) + v * (v_dir.x - origin.x),
476 origin.y + u * (u_dir.y - origin.y) + v * (v_dir.y - origin.y),
477 origin.z + u * (u_dir.z - origin.z) + v * (v_dir.z - origin.z),
478 );
479 nodes.push(node);
480 }
481 }
482
483 for j in 0..nv {
485 for i in 0..nu {
486 let n0 = base_idx + j * (nu + 1) + i;
487 let n1 = base_idx + j * (nu + 1) + i + 1;
488 let n2 = base_idx + (j + 1) * (nu + 1) + i + 1;
489 let n3 = base_idx + (j + 1) * (nu + 1) + i;
490
491 elements.push(SurfaceElement {
492 nodes: vec![n0, n1, n2, n3],
493 });
494 }
495 }
496 }
497}
498
499#[derive(Debug, Clone, Serialize, Deserialize)]
503pub struct LShapedRoom {
504 pub width1: f64, pub depth1: f64, pub width2: f64, pub depth2: f64, pub height: f64, }
510
511impl LShapedRoom {
512 pub fn new(width1: f64, depth1: f64, width2: f64, depth2: f64, height: f64) -> Self {
513 Self {
514 width1,
515 depth1,
516 width2,
517 depth2,
518 height,
519 }
520 }
521
522 pub fn generate_mesh(&self, elements_per_meter: usize) -> RoomMesh {
524 let mut nodes = Vec::new();
525 let mut elements = Vec::new();
526
527 let nx1 = (self.width1 * elements_per_meter as f64).ceil() as usize;
529 let ny1 = (self.depth1 * elements_per_meter as f64).ceil() as usize;
530
531 let nx2 = (self.width2 * elements_per_meter as f64).ceil() as usize;
533 let ny2 = (self.depth2 * elements_per_meter as f64).ceil() as usize;
534
535 let nz = (self.height * elements_per_meter as f64).ceil() as usize;
536
537 self.add_surface_mesh_lshaped(
539 &mut nodes,
540 &mut elements,
541 Point3D::new(0.0, 0.0, 0.0),
542 Point3D::new(self.width1, 0.0, 0.0),
543 Point3D::new(0.0, self.depth1, 0.0),
544 nx1,
545 ny1,
546 );
547
548 self.add_surface_mesh_lshaped(
550 &mut nodes,
551 &mut elements,
552 Point3D::new(0.0, self.depth1, 0.0),
553 Point3D::new(self.width2, self.depth1, 0.0),
554 Point3D::new(0.0, self.depth1 + self.depth2, 0.0),
555 nx2,
556 ny2,
557 );
558
559 self.add_surface_mesh_lshaped(
561 &mut nodes,
562 &mut elements,
563 Point3D::new(0.0, 0.0, self.height),
564 Point3D::new(self.width1, 0.0, self.height),
565 Point3D::new(0.0, self.depth1, self.height),
566 nx1,
567 ny1,
568 );
569
570 self.add_surface_mesh_lshaped(
572 &mut nodes,
573 &mut elements,
574 Point3D::new(0.0, self.depth1, self.height),
575 Point3D::new(self.width2, self.depth1, self.height),
576 Point3D::new(0.0, self.depth1 + self.depth2, self.height),
577 nx2,
578 ny2,
579 );
580
581 self.add_surface_mesh_lshaped(
584 &mut nodes,
585 &mut elements,
586 Point3D::new(0.0, 0.0, 0.0),
587 Point3D::new(self.width1, 0.0, 0.0),
588 Point3D::new(0.0, 0.0, self.height),
589 nx1,
590 nz,
591 );
592
593 self.add_surface_mesh_lshaped(
595 &mut nodes,
596 &mut elements,
597 Point3D::new(self.width1, 0.0, 0.0),
598 Point3D::new(self.width1, self.depth1, 0.0),
599 Point3D::new(self.width1, 0.0, self.height),
600 ny1,
601 nz,
602 );
603
604 let total_depth = self.depth1 + self.depth2;
606 let ny_total = ((total_depth) * elements_per_meter as f64).ceil() as usize;
607 self.add_surface_mesh_lshaped(
608 &mut nodes,
609 &mut elements,
610 Point3D::new(0.0, 0.0, 0.0),
611 Point3D::new(0.0, total_depth, 0.0),
612 Point3D::new(0.0, 0.0, self.height),
613 ny_total,
614 nz,
615 );
616
617 self.add_surface_mesh_lshaped(
619 &mut nodes,
620 &mut elements,
621 Point3D::new(0.0, total_depth, 0.0),
622 Point3D::new(self.width2, total_depth, 0.0),
623 Point3D::new(0.0, total_depth, self.height),
624 nx2,
625 nz,
626 );
627
628 self.add_surface_mesh_lshaped(
630 &mut nodes,
631 &mut elements,
632 Point3D::new(self.width2, self.depth1, 0.0),
633 Point3D::new(self.width2, total_depth, 0.0),
634 Point3D::new(self.width2, self.depth1, self.height),
635 ny2,
636 nz,
637 );
638
639 let internal_width = self.width1 - self.width2;
642 let nx_internal = (internal_width * elements_per_meter as f64).ceil() as usize;
643 self.add_surface_mesh_lshaped(
644 &mut nodes,
645 &mut elements,
646 Point3D::new(self.width2, self.depth1, 0.0),
647 Point3D::new(self.width1, self.depth1, 0.0),
648 Point3D::new(self.width2, self.depth1, self.height),
649 nx_internal,
650 nz,
651 );
652
653 RoomMesh { nodes, elements }
654 }
655
656 pub fn get_edges(&self) -> Vec<(Point3D, Point3D)> {
658 let total_depth = self.depth1 + self.depth2;
659 vec![
660 (Point3D::new(0.0, 0.0, 0.0), Point3D::new(self.width1, 0.0, 0.0)),
662 (Point3D::new(self.width1, 0.0, 0.0), Point3D::new(self.width1, self.depth1, 0.0)),
663 (Point3D::new(self.width1, self.depth1, 0.0), Point3D::new(self.width2, self.depth1, 0.0)),
664 (Point3D::new(self.width2, self.depth1, 0.0), Point3D::new(self.width2, total_depth, 0.0)),
665 (Point3D::new(self.width2, total_depth, 0.0), Point3D::new(0.0, total_depth, 0.0)),
666 (Point3D::new(0.0, total_depth, 0.0), Point3D::new(0.0, 0.0, 0.0)),
667
668 (Point3D::new(0.0, 0.0, self.height), Point3D::new(self.width1, 0.0, self.height)),
670 (Point3D::new(self.width1, 0.0, self.height), Point3D::new(self.width1, self.depth1, self.height)),
671 (Point3D::new(self.width1, self.depth1, self.height), Point3D::new(self.width2, self.depth1, self.height)),
672 (Point3D::new(self.width2, self.depth1, self.height), Point3D::new(self.width2, total_depth, self.height)),
673 (Point3D::new(self.width2, total_depth, self.height), Point3D::new(0.0, total_depth, self.height)),
674 (Point3D::new(0.0, total_depth, self.height), Point3D::new(0.0, 0.0, self.height)),
675
676 (Point3D::new(0.0, 0.0, 0.0), Point3D::new(0.0, 0.0, self.height)),
678 (Point3D::new(self.width1, 0.0, 0.0), Point3D::new(self.width1, 0.0, self.height)),
679 (Point3D::new(self.width1, self.depth1, 0.0), Point3D::new(self.width1, self.depth1, self.height)),
680 (Point3D::new(self.width2, self.depth1, 0.0), Point3D::new(self.width2, self.depth1, self.height)),
681 (Point3D::new(self.width2, total_depth, 0.0), Point3D::new(self.width2, total_depth, self.height)),
682 (Point3D::new(0.0, total_depth, 0.0), Point3D::new(0.0, total_depth, self.height)),
683 ]
684 }
685
686 fn add_surface_mesh_lshaped(
687 &self,
688 nodes: &mut Vec<Point3D>,
689 elements: &mut Vec<SurfaceElement>,
690 origin: Point3D,
691 u_dir: Point3D,
692 v_dir: Point3D,
693 nu: usize,
694 nv: usize,
695 ) {
696 let base_idx = nodes.len();
697
698 for j in 0..=nv {
700 for i in 0..=nu {
701 let u = i as f64 / nu as f64;
702 let v = j as f64 / nv as f64;
703
704 let node = Point3D::new(
705 origin.x + u * (u_dir.x - origin.x) + v * (v_dir.x - origin.x),
706 origin.y + u * (u_dir.y - origin.y) + v * (v_dir.y - origin.y),
707 origin.z + u * (u_dir.z - origin.z) + v * (v_dir.z - origin.z),
708 );
709 nodes.push(node);
710 }
711 }
712
713 for j in 0..nv {
715 for i in 0..nu {
716 let n0 = base_idx + j * (nu + 1) + i;
717 let n1 = base_idx + j * (nu + 1) + i + 1;
718 let n2 = base_idx + (j + 1) * (nu + 1) + i + 1;
719 let n3 = base_idx + (j + 1) * (nu + 1) + i;
720
721 elements.push(SurfaceElement {
722 nodes: vec![n0, n1, n2, n3],
723 });
724 }
725 }
726 }
727
728 pub fn generate_adaptive_mesh(
730 &self,
731 base_elements_per_meter: usize,
732 _frequency: f64,
733 _sources: &[Source],
734 _speed_of_sound: f64,
735 ) -> RoomMesh {
736 self.generate_mesh(base_elements_per_meter)
739 }
740}
741
742#[derive(Debug, Clone, Serialize, Deserialize)]
744pub struct SurfaceElement {
745 pub nodes: Vec<usize>,
746}
747
748#[derive(Debug, Clone, Serialize, Deserialize)]
750pub struct RoomMesh {
751 pub nodes: Vec<Point3D>,
752 pub elements: Vec<SurfaceElement>,
753}
754
755#[derive(Debug, Clone, Serialize, Deserialize)]
757pub struct DirectivityPattern {
758 pub horizontal_angles: Vec<f64>,
760 pub vertical_angles: Vec<f64>,
762 pub magnitude: Array2<f64>,
765}
766
767impl DirectivityPattern {
768 pub fn omnidirectional() -> Self {
770 let horizontal_angles: Vec<f64> = (0..36).map(|i| i as f64 * 10.0).collect();
771 let vertical_angles: Vec<f64> = (0..19).map(|i| i as f64 * 10.0).collect();
772
773 let magnitude = Array2::ones((vertical_angles.len(), horizontal_angles.len()));
774
775 Self {
776 horizontal_angles,
777 vertical_angles,
778 magnitude,
779 }
780 }
781
782 pub fn interpolate(&self, theta: f64, phi: f64) -> f64 {
784 let theta_deg = theta.to_degrees();
786 let mut phi_deg = phi.to_degrees();
787
788 while phi_deg < 0.0 {
790 phi_deg += 360.0;
791 }
792 while phi_deg >= 360.0 {
793 phi_deg -= 360.0;
794 }
795
796 let h_idx = (phi_deg / 10.0).floor() as usize;
798 let v_idx = (theta_deg / 10.0).floor() as usize;
799
800 let h_idx = h_idx.min(self.horizontal_angles.len() - 1);
801 let v_idx = v_idx.min(self.vertical_angles.len() - 1);
802
803 let h_next = (h_idx + 1) % self.horizontal_angles.len();
804 let v_next = (v_idx + 1).min(self.vertical_angles.len() - 1);
805
806 let h_frac = (phi_deg / 10.0) - h_idx as f64;
808 let v_frac = (theta_deg / 10.0) - v_idx as f64;
809
810 let m00 = self.magnitude[[v_idx, h_idx]];
811 let m01 = self.magnitude[[v_idx, h_next]];
812 let m10 = self.magnitude[[v_next, h_idx]];
813 let m11 = self.magnitude[[v_next, h_next]];
814
815 let m0 = m00 * (1.0 - h_frac) + m01 * h_frac;
816 let m1 = m10 * (1.0 - h_frac) + m11 * h_frac;
817
818 m0 * (1.0 - v_frac) + m1 * v_frac
819 }
820}
821
822#[derive(Debug, Clone, Serialize, Deserialize)]
824pub enum CrossoverFilter {
825 FullRange,
827 Lowpass {
829 cutoff_freq: f64,
830 order: u32, },
832 Highpass {
834 cutoff_freq: f64,
835 order: u32,
836 },
837 Bandpass {
839 low_cutoff: f64,
840 high_cutoff: f64,
841 order: u32,
842 },
843}
844
845impl CrossoverFilter {
846 pub fn amplitude_at_frequency(&self, frequency: f64) -> f64 {
848 match self {
849 CrossoverFilter::FullRange => 1.0,
850 CrossoverFilter::Lowpass { cutoff_freq, order } => {
851 let ratio = frequency / cutoff_freq;
852 let response = 1.0 / (1.0 + ratio.powi(*order as i32 * 2)).sqrt();
853 response
854 }
855 CrossoverFilter::Highpass { cutoff_freq, order } => {
856 let ratio = cutoff_freq / frequency;
857 let response = 1.0 / (1.0 + ratio.powi(*order as i32 * 2)).sqrt();
858 response
859 }
860 CrossoverFilter::Bandpass {
861 low_cutoff,
862 high_cutoff,
863 order,
864 } => {
865 let high_ratio = low_cutoff / frequency;
867 let low_ratio = frequency / high_cutoff;
868 let hp_response = 1.0 / (1.0 + high_ratio.powi(*order as i32 * 2)).sqrt();
869 let lp_response = 1.0 / (1.0 + low_ratio.powi(*order as i32 * 2)).sqrt();
870 hp_response * lp_response
871 }
872 }
873 }
874}
875
876#[derive(Debug, Clone, Serialize, Deserialize)]
878pub struct Source {
879 pub position: Point3D,
880 pub directivity: DirectivityPattern,
881 pub amplitude: f64, pub crossover: CrossoverFilter,
883 pub name: String, }
885
886impl Source {
887 pub fn new(position: Point3D, directivity: DirectivityPattern, amplitude: f64) -> Self {
888 Self {
889 position,
890 directivity,
891 amplitude,
892 crossover: CrossoverFilter::FullRange,
893 name: String::from("Source"),
894 }
895 }
896
897 pub fn with_crossover(mut self, crossover: CrossoverFilter) -> Self {
898 self.crossover = crossover;
899 self
900 }
901
902 pub fn with_name(mut self, name: String) -> Self {
903 self.name = name;
904 self
905 }
906
907 pub fn amplitude_towards(&self, point: &Point3D, frequency: f64) -> f64 {
909 let dx = point.x - self.position.x;
910 let dy = point.y - self.position.y;
911 let dz = point.z - self.position.z;
912
913 let r = (dx * dx + dy * dy + dz * dz).sqrt();
914 if r < 1e-10 {
915 return self.amplitude * self.crossover.amplitude_at_frequency(frequency);
916 }
917
918 let theta = (dz / r).acos();
919 let phi = dy.atan2(dx);
920
921 let directivity_factor = self.directivity.interpolate(theta, phi);
922 let crossover_factor = self.crossover.amplitude_at_frequency(frequency);
923 self.amplitude * directivity_factor * crossover_factor
924 }
925}
926
927pub type ListeningPosition = Point3D;
929
930#[derive(Debug, Clone, Serialize, Deserialize)]
932pub struct RoomSimulation {
933 pub room: RoomGeometry,
934 pub sources: Vec<Source>,
935 pub listening_positions: Vec<ListeningPosition>,
936 pub frequencies: Vec<f64>,
937 pub speed_of_sound: f64,
938}
939
940impl RoomSimulation {
941 pub fn new(
942 room: RoomGeometry,
943 sources: Vec<Source>,
944 listening_positions: Vec<ListeningPosition>,
945 ) -> Self {
946 let frequencies = Self::log_space(20.0, 20000.0, 200);
948
949 Self {
950 room,
951 sources,
952 listening_positions,
953 frequencies,
954 speed_of_sound: 343.0, }
956 }
957
958 pub fn with_frequencies(
960 room: RoomGeometry,
961 sources: Vec<Source>,
962 listening_positions: Vec<ListeningPosition>,
963 min_freq: f64,
964 max_freq: f64,
965 num_points: usize,
966 ) -> Self {
967 let frequencies = Self::log_space(min_freq, max_freq, num_points);
968
969 Self {
970 room,
971 sources,
972 listening_positions,
973 frequencies,
974 speed_of_sound: 343.0,
975 }
976 }
977
978 fn log_space(start: f64, end: f64, num: usize) -> Vec<f64> {
979 let log_start = start.ln();
980 let log_end = end.ln();
981 (0..num)
982 .map(|i| {
983 let log_val = log_start + (log_end - log_start) * i as f64 / (num - 1) as f64;
984 log_val.exp()
985 })
986 .collect()
987 }
988
989 pub fn wavenumber(&self, frequency: f64) -> f64 {
991 2.0 * PI * frequency / self.speed_of_sound
992 }
993}
994
995#[derive(Debug, Clone, Serialize, Deserialize)]
997pub struct FrequencyResult {
998 pub frequency: f64,
999 pub spl_at_lp: Vec<f64>, }
1001
1002#[derive(Debug, Clone, Serialize, Deserialize)]
1004pub struct SimulationResults {
1005 pub frequencies: Vec<f64>,
1006 pub lp_frequency_responses: Vec<Vec<f64>>, pub horizontal_slice: Option<SliceData>,
1008 pub vertical_slice: Option<SliceData>,
1009}
1010
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1013pub struct SliceData {
1014 pub x: Vec<f64>,
1015 pub y: Vec<f64>,
1016 pub spl: Array2<f64>, pub frequency: f64,
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022 use super::*;
1023
1024 #[test]
1025 fn test_rectangular_room() {
1026 let room = RectangularRoom::new(5.0, 4.0, 3.0);
1027 assert_eq!(room.width, 5.0);
1028 assert_eq!(room.depth, 4.0);
1029 assert_eq!(room.height, 3.0);
1030 }
1031
1032 #[test]
1033 fn test_omnidirectional_pattern() {
1034 let pattern = DirectivityPattern::omnidirectional();
1035 assert!((pattern.interpolate(0.0, 0.0) - 1.0).abs() < 1e-6);
1037 assert!((pattern.interpolate(PI / 2.0, PI) - 1.0).abs() < 1e-6);
1038 assert!((pattern.interpolate(PI, 0.0) - 1.0).abs() < 1e-6);
1039 }
1040
1041 #[test]
1042 fn test_log_space() {
1043 let freqs = RoomSimulation::log_space(20.0, 20000.0, 200);
1044 assert_eq!(freqs.len(), 200);
1045 assert!((freqs[0] - 20.0).abs() < 1e-6);
1046 assert!((freqs[199] - 20000.0).abs() < 1e-6);
1047 assert!(freqs[1] / freqs[0] > 1.0);
1049 }
1050
1051 #[test]
1052 fn test_point_distance() {
1053 let p1 = Point3D::new(0.0, 0.0, 0.0);
1054 let p2 = Point3D::new(3.0, 4.0, 0.0);
1055 assert!((p1.distance_to(&p2) - 5.0).abs() < 1e-6);
1056 }
1057}