cherry_rs/views/ray_trace_3d/
rays.rs1use anyhow::{bail, Result};
2use serde::{Deserialize, Serialize};
3
4use crate::core::{
5 math::vec3::Vec3,
6 sequential_model::{Step, Surface},
7 Float, PI,
8};
9
10const TOL: Float = Float::EPSILON;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Ray {
21 pos: Vec3,
22 dir: Vec3,
23 terminated: bool,
24 field_id: usize,
25}
26
27impl Ray {
28 pub fn new(pos: Vec3, dir: Vec3, field_id: usize) -> Result<Self> {
29 if !dir.is_unit() {
30 bail!("Ray direction must be a unit vector");
31 }
32 Ok(Self {
33 pos,
34 dir,
35 terminated: false,
36 field_id,
37 })
38 }
39
40 pub fn intersect(&self, surf: &Surface, max_iter: usize) -> Result<(Vec3, Vec3)> {
49 let mut s_1 = 0.0;
51
52 let mut s = -self.pos.z() / self.dir.m();
55
56 let mut p: Vec3;
57 let mut sag: Float;
58 let mut norm: Vec3;
59 for ctr in 0..max_iter {
60 p = self.pos + self.dir * s;
62
63 (sag, norm) = surf.sag_norm(p);
65 s -= (p.z() - sag) / norm.dot(self.dir);
66
67 if (s - s_1).abs() / Float::max(s, s_1) < TOL {
69 break;
70 }
71
72 if ctr == max_iter - 1 {
73 bail!("Ray intersection did not converge");
74 }
75
76 s_1 = s;
77 }
78
79 p = self.pos + self.dir * s;
81 (_, norm) = surf.sag_norm(p);
82
83 Ok((p, norm))
84 }
85
86 pub fn redirect(&mut self, step: &Step, norm: Vec3) {
92 let (gap_0, surf, gap_1) = step;
95 let n_0 = gap_0.refractive_index.n();
96 let n_1 = if let Some(gap_1) = gap_1 {
97 gap_1.refractive_index.n()
98 } else {
99 n_0
100 };
101
102 match surf {
103 Surface::Conic(_) => {
106 let mu = n_0 / n_1;
107 let cos_theta_1 = self.dir.dot(norm);
108 let term_1 = norm * (1.0 - mu * mu * (1.0 - cos_theta_1 * cos_theta_1)).sqrt();
109 let term_2 = (self.dir - norm * cos_theta_1) * mu;
110
111 self.dir = term_1 + term_2;
112 }
113 Surface::Image(_) => {}
115 Surface::Object(_) => {}
116 Surface::Probe(_) => {}
117 Surface::Stop(_) => {}
118 }
119 }
120
121 pub fn displace(&mut self, pos: Vec3) {
123 self.pos = pos;
124 }
125
126 pub fn transform(&mut self, surf: &Surface) {
129 self.pos = surf.rot_mat() * (self.pos - surf.pos());
130 self.dir = surf.rot_mat() * self.dir;
131 }
132
133 pub fn i_transform(&mut self, surf: &Surface) {
136 self.pos = surf.rot_mat().transpose() * (self.pos + surf.pos());
137 self.dir = surf.rot_mat().transpose() * self.dir;
138 }
139
140 pub fn terminate(&mut self) {
141 self.terminated = true;
142 }
143
144 #[inline]
145 pub fn is_terminated(&self) -> bool {
146 self.terminated
147 }
148
149 #[allow(clippy::too_many_arguments)]
167 pub fn fan(
168 n: usize,
169 r: Float,
170 theta: Float,
171 z: Float,
172 phi: Float,
173 radial_offset_x: Float,
174 radial_offset_y: Float,
175 field_id: usize,
176 ) -> Vec<Ray> {
177 let pos = Vec3::fan(n, r, theta, z, radial_offset_x, radial_offset_y);
178 let dir: Vec<Vec3> = pos
179 .iter()
180 .map(|_| {
181 let l = phi.sin() * theta.cos();
182 let m = phi.sin() * theta.sin();
183 let n = phi.cos();
184 Vec3::new(l, m, n)
185 })
186 .collect();
187
188 pos.iter()
189 .zip(dir.iter())
190 .map(|(p, d)| Ray::new(*p, *d, field_id).unwrap())
191 .collect()
192 }
193
194 pub(crate) fn sq_grid_in_circ(
208 radius: Float,
209 spacing: Float,
210 z: Float,
211 phi: Float,
212 radial_offset_x: Float,
213 radial_offset_y: Float,
214 field_id: usize,
215 ) -> Vec<Ray> {
216 let theta = PI / 2.0; let pos: Vec<Vec3> =
219 Vec3::sq_grid_in_circ(radius, spacing, z, radial_offset_x, radial_offset_y);
220 let dir: Vec<Vec3> = pos
221 .iter()
222 .map(|_| {
223 let l = phi.sin() * theta.cos();
224 let m = phi.sin() * theta.sin();
225 let n = phi.cos();
226 Vec3::new(l, m, n)
227 })
228 .collect();
229
230 pos.iter()
231 .zip(dir.iter())
232 .map(|(p, d)| Ray::new(*p, *d, field_id).unwrap())
233 .collect()
234 }
235}
236
237#[cfg(test)]
238mod test {
239 use crate::specs::surfaces::{SurfaceSpec, SurfaceType};
240
241 use super::*;
242 #[test]
244 fn test_rays_new() {
245 let pos = Vec3::new(0.0, 0.0, 0.0);
246 let dir = Vec3::new(0.0, 0.0, 1.0);
247 let field_id = 0;
248
249 let rays = Ray::new(pos, dir, field_id);
250
251 assert!(rays.is_ok());
252 }
253
254 #[test]
256 fn test_rays_new_non_unit_dir() {
257 let pos = Vec3::new(0.0, 0.0, 0.0);
258 let dir = Vec3::new(0.0, 0.0, 2.0);
259 let field_id = 0;
260
261 let rays = Ray::new(pos, dir, field_id);
262
263 assert!(rays.is_err());
264 }
265
266 #[test]
268 fn test_ray_intersection_flat_surface() {
269 let field_id = 0;
270 let pos = Vec3::new(0.0, 0.0, 0.0);
271 let ray = Ray::new(
272 Vec3::new(0.0, 0.0, -1.0),
273 Vec3::new(0.0, 0.0, 1.0),
274 field_id,
275 )
276 .unwrap();
277 let surf_spec = SurfaceSpec::Conic {
278 semi_diameter: 4.0,
279 radius_of_curvature: Float::INFINITY,
280 conic_constant: 0.0,
281 surf_type: SurfaceType::Refracting,
282 };
283 let surf = Surface::from_spec(&surf_spec, pos);
284 let max_iter = 1000;
285
286 let (p, _) = ray.intersect(&surf, max_iter).unwrap();
287
288 assert_eq!(p, Vec3::new(0.0, 0.0, 0.0));
289 }
290
291 #[test]
293 fn test_ray_intersection_conic() {
294 let field_id = 0;
295 let pos = Vec3::new(0.0, 0.0, 0.0);
296
297 let l = 0.7071;
299 let m = ((1.0 as Float) - 0.7071 * 0.7071).sqrt();
300 let ray = Ray::new(Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, l, m), field_id).unwrap();
301
302 let surf_spec = SurfaceSpec::Conic {
304 semi_diameter: 4.0,
305 radius_of_curvature: -1.0,
306 conic_constant: 0.0,
307 surf_type: SurfaceType::Refracting,
308 };
309 let surf = Surface::from_spec(&surf_spec, pos);
310 let max_iter = 1000;
311
312 let (p, _) = ray.intersect(&surf, max_iter).unwrap();
313
314 assert!((p.x() - 0.0).abs() < 1e-4);
315 assert!((p.y() - (PI / 4.0).sin()).abs() < 1e-4);
316 assert!((p.z() - ((PI / 4.0).cos() - 1.0)).abs() < 1e-4);
317 }
318}