1use serde::{Deserialize, Serialize};
4use std::f64::consts::PI;
5
6#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
8pub struct Point3D {
9 pub x: f64,
11 pub y: f64,
13 pub z: f64,
15}
16
17impl Point3D {
18 pub fn new(x: f64, y: f64, z: f64) -> Self {
20 Self { x, y, z }
21 }
22
23 pub fn zero() -> Self {
25 Self::new(0.0, 0.0, 0.0)
26 }
27
28 pub fn distance_to(&self, other: &Point3D) -> f64 {
30 let dx = self.x - other.x;
31 let dy = self.y - other.y;
32 let dz = self.z - other.z;
33 (dx * dx + dy * dy + dz * dz).sqrt()
34 }
35
36 pub fn to_spherical(&self) -> (f64, f64, f64) {
40 let r = (self.x * self.x + self.y * self.y + self.z * self.z).sqrt();
41 let theta = if r > 1e-10 { (self.z / r).acos() } else { 0.0 };
42 let phi = self.y.atan2(self.x);
43 (r, theta, phi)
44 }
45
46 pub fn from_spherical(r: f64, theta: f64, phi: f64) -> Self {
48 Self {
49 x: r * theta.sin() * phi.cos(),
50 y: r * theta.sin() * phi.sin(),
51 z: r * theta.cos(),
52 }
53 }
54
55 pub fn dot(&self, other: &Point3D) -> f64 {
57 self.x * other.x + self.y * other.y + self.z * other.z
58 }
59
60 pub fn cross(&self, other: &Point3D) -> Point3D {
62 Point3D {
63 x: self.y * other.z - self.z * other.y,
64 y: self.z * other.x - self.x * other.z,
65 z: self.x * other.y - self.y * other.x,
66 }
67 }
68
69 pub fn length(&self) -> f64 {
71 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
72 }
73
74 pub fn normalize(&self) -> Option<Point3D> {
76 let len = self.length();
77 if len > 1e-10 {
78 Some(Point3D {
79 x: self.x / len,
80 y: self.y / len,
81 z: self.z / len,
82 })
83 } else {
84 None
85 }
86 }
87
88 pub fn scale(&self, s: f64) -> Point3D {
90 Point3D {
91 x: self.x * s,
92 y: self.y * s,
93 z: self.z * s,
94 }
95 }
96
97 pub fn add(&self, other: &Point3D) -> Point3D {
99 Point3D {
100 x: self.x + other.x,
101 y: self.y + other.y,
102 z: self.z + other.z,
103 }
104 }
105
106 pub fn sub(&self, other: &Point3D) -> Point3D {
108 Point3D {
109 x: self.x - other.x,
110 y: self.y - other.y,
111 z: self.z - other.z,
112 }
113 }
114}
115
116impl std::ops::Add for Point3D {
117 type Output = Point3D;
118 fn add(self, other: Point3D) -> Point3D {
119 Point3D {
120 x: self.x + other.x,
121 y: self.y + other.y,
122 z: self.z + other.z,
123 }
124 }
125}
126
127impl std::ops::Sub for Point3D {
128 type Output = Point3D;
129 fn sub(self, other: Point3D) -> Point3D {
130 Point3D {
131 x: self.x - other.x,
132 y: self.y - other.y,
133 z: self.z - other.z,
134 }
135 }
136}
137
138impl std::ops::Mul<f64> for Point3D {
139 type Output = Point3D;
140 fn mul(self, s: f64) -> Point3D {
141 Point3D {
142 x: self.x * s,
143 y: self.y * s,
144 z: self.z * s,
145 }
146 }
147}
148
149pub type ListeningPosition = Point3D;
151
152#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct SurfaceElement {
155 pub nodes: Vec<usize>,
157}
158
159impl SurfaceElement {
160 pub fn triangle(n0: usize, n1: usize, n2: usize) -> Self {
162 Self {
163 nodes: vec![n0, n1, n2],
164 }
165 }
166
167 pub fn quad(n0: usize, n1: usize, n2: usize, n3: usize) -> Self {
169 Self {
170 nodes: vec![n0, n1, n2, n3],
171 }
172 }
173
174 pub fn is_triangle(&self) -> bool {
176 self.nodes.len() == 3
177 }
178
179 pub fn is_quad(&self) -> bool {
181 self.nodes.len() == 4
182 }
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct RoomMesh {
188 pub nodes: Vec<Point3D>,
190 pub elements: Vec<SurfaceElement>,
192}
193
194impl RoomMesh {
195 pub fn new() -> Self {
197 Self {
198 nodes: Vec::new(),
199 elements: Vec::new(),
200 }
201 }
202
203 pub fn num_nodes(&self) -> usize {
205 self.nodes.len()
206 }
207
208 pub fn num_elements(&self) -> usize {
210 self.elements.len()
211 }
212
213 pub fn element_centroid(&self, element_idx: usize) -> Point3D {
215 let element = &self.elements[element_idx];
216 let mut centroid = Point3D::zero();
217 for &node_idx in &element.nodes {
218 centroid = centroid + self.nodes[node_idx];
219 }
220 centroid * (1.0 / element.nodes.len() as f64)
221 }
222
223 pub fn element_normal(&self, element_idx: usize) -> Option<Point3D> {
225 let element = &self.elements[element_idx];
226 if element.nodes.len() < 3 {
227 return None;
228 }
229
230 let p0 = self.nodes[element.nodes[0]];
231 let p1 = self.nodes[element.nodes[1]];
232 let p2 = self.nodes[element.nodes[2]];
233
234 let v1 = p1 - p0;
235 let v2 = p2 - p0;
236 v1.cross(&v2).normalize()
237 }
238
239 pub fn element_area(&self, element_idx: usize) -> f64 {
241 let element = &self.elements[element_idx];
242 if element.nodes.len() < 3 {
243 return 0.0;
244 }
245
246 let p0 = self.nodes[element.nodes[0]];
247 let p1 = self.nodes[element.nodes[1]];
248 let p2 = self.nodes[element.nodes[2]];
249
250 let v1 = p1 - p0;
251 let v2 = p2 - p0;
252 v1.cross(&v2).length() * 0.5
253 }
254}
255
256impl Default for RoomMesh {
257 fn default() -> Self {
258 Self::new()
259 }
260}
261
262pub mod constants {
264 pub const SPEED_OF_SOUND_20C: f64 = 343.0;
266
267 pub const REFERENCE_PRESSURE: f64 = 20e-6;
269
270 pub const AIR_DENSITY_20C: f64 = 1.204;
272}
273
274pub fn wavenumber(frequency: f64, speed_of_sound: f64) -> f64 {
276 2.0 * PI * frequency / speed_of_sound
277}
278
279pub fn pressure_to_spl(pressure: num_complex::Complex64) -> f64 {
281 let magnitude = pressure.norm();
282 if magnitude > 1e-20 {
283 20.0 * (magnitude / constants::REFERENCE_PRESSURE).log10()
284 } else {
285 -120.0 }
287}
288
289pub fn log_space(start: f64, end: f64, num: usize) -> Vec<f64> {
291 if num < 2 {
292 return vec![start];
293 }
294 let log_start = start.ln();
295 let log_end = end.ln();
296 (0..num)
297 .map(|i| {
298 let log_val = log_start + (log_end - log_start) * i as f64 / (num - 1) as f64;
299 log_val.exp()
300 })
301 .collect()
302}
303
304pub fn lin_space(start: f64, end: f64, num: usize) -> Vec<f64> {
306 if num < 2 {
307 return vec![start];
308 }
309 (0..num)
310 .map(|i| start + (end - start) * i as f64 / (num - 1) as f64)
311 .collect()
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use approx::assert_relative_eq;
318
319 #[test]
320 fn test_point_distance() {
321 let p1 = Point3D::new(0.0, 0.0, 0.0);
322 let p2 = Point3D::new(3.0, 4.0, 0.0);
323 assert_relative_eq!(p1.distance_to(&p2), 5.0, epsilon = 1e-10);
324 }
325
326 #[test]
327 fn test_point_spherical() {
328 let p = Point3D::new(1.0, 0.0, 0.0);
329 let (r, theta, phi) = p.to_spherical();
330 assert_relative_eq!(r, 1.0, epsilon = 1e-10);
331 assert_relative_eq!(theta, std::f64::consts::FRAC_PI_2, epsilon = 1e-10);
332 assert_relative_eq!(phi, 0.0, epsilon = 1e-10);
333 }
334
335 #[test]
336 fn test_log_space() {
337 let freqs = log_space(20.0, 20000.0, 200);
338 assert_eq!(freqs.len(), 200);
339 assert_relative_eq!(freqs[0], 20.0, epsilon = 1e-6);
340 assert_relative_eq!(freqs[199], 20000.0, epsilon = 1e-6);
341 assert!(freqs[1] / freqs[0] > 1.0);
343 }
344
345 #[test]
346 fn test_wavenumber() {
347 let k = wavenumber(1000.0, 343.0);
348 assert_relative_eq!(
349 k,
350 2.0 * std::f64::consts::PI * 1000.0 / 343.0,
351 epsilon = 1e-10
352 );
353 }
354}