use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use crate::core::{
math::vec3::Vec3,
sequential_model::{Step, Surface},
Float, PI,
};
const TOL: Float = Float::EPSILON;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Ray {
pos: Vec3,
dir: Vec3,
terminated: bool,
field_id: usize,
}
impl Ray {
pub fn new(pos: Vec3, dir: Vec3, field_id: usize) -> Result<Self> {
if !dir.is_unit() {
bail!("Ray direction must be a unit vector");
}
Ok(Self {
pos,
dir,
terminated: false,
field_id,
})
}
pub fn intersect(&self, surf: &Surface, max_iter: usize) -> Result<(Vec3, Vec3)> {
let mut s_1 = 0.0;
let mut s = -self.pos.z() / self.dir.m();
let mut p: Vec3;
let mut sag: Float;
let mut norm: Vec3;
for ctr in 0..max_iter {
p = self.pos + self.dir * s;
(sag, norm) = surf.sag_norm(p);
s -= (p.z() - sag) / norm.dot(self.dir);
if (s - s_1).abs() / Float::max(s, s_1) < TOL {
break;
}
if ctr == max_iter - 1 {
bail!("Ray intersection did not converge");
}
s_1 = s;
}
p = self.pos + self.dir * s;
(_, norm) = surf.sag_norm(p);
Ok((p, norm))
}
pub fn redirect(&mut self, step: &Step, norm: Vec3) {
let (gap_0, surf, gap_1) = step;
let n_0 = gap_0.refractive_index.n();
let n_1 = if let Some(gap_1) = gap_1 {
gap_1.refractive_index.n()
} else {
n_0
};
match surf {
Surface::Conic(_) => {
let mu = n_0 / n_1;
let cos_theta_1 = self.dir.dot(norm);
let term_1 = norm * (1.0 - mu * mu * (1.0 - cos_theta_1 * cos_theta_1)).sqrt();
let term_2 = (self.dir - norm * cos_theta_1) * mu;
self.dir = term_1 + term_2;
}
Surface::Image(_) => {}
Surface::Object(_) => {}
Surface::Probe(_) => {}
Surface::Stop(_) => {}
}
}
pub fn displace(&mut self, pos: Vec3) {
self.pos = pos;
}
pub fn transform(&mut self, surf: &Surface) {
self.pos = surf.rot_mat() * (self.pos - surf.pos());
self.dir = surf.rot_mat() * self.dir;
}
pub fn i_transform(&mut self, surf: &Surface) {
self.pos = surf.rot_mat().transpose() * (self.pos + surf.pos());
self.dir = surf.rot_mat().transpose() * self.dir;
}
pub fn terminate(&mut self) {
self.terminated = true;
}
#[inline]
pub fn is_terminated(&self) -> bool {
self.terminated
}
#[allow(clippy::too_many_arguments)]
pub fn fan(
n: usize,
r: Float,
theta: Float,
z: Float,
phi: Float,
radial_offset_x: Float,
radial_offset_y: Float,
field_id: usize,
) -> Vec<Ray> {
let pos = Vec3::fan(n, r, theta, z, radial_offset_x, radial_offset_y);
let dir: Vec<Vec3> = pos
.iter()
.map(|_| {
let l = phi.sin() * theta.cos();
let m = phi.sin() * theta.sin();
let n = phi.cos();
Vec3::new(l, m, n)
})
.collect();
pos.iter()
.zip(dir.iter())
.map(|(p, d)| Ray::new(*p, *d, field_id).unwrap())
.collect()
}
pub(crate) fn sq_grid_in_circ(
radius: Float,
spacing: Float,
z: Float,
phi: Float,
radial_offset_x: Float,
radial_offset_y: Float,
field_id: usize,
) -> Vec<Ray> {
let theta = PI / 2.0;
let pos: Vec<Vec3> =
Vec3::sq_grid_in_circ(radius, spacing, z, radial_offset_x, radial_offset_y);
let dir: Vec<Vec3> = pos
.iter()
.map(|_| {
let l = phi.sin() * theta.cos();
let m = phi.sin() * theta.sin();
let n = phi.cos();
Vec3::new(l, m, n)
})
.collect();
pos.iter()
.zip(dir.iter())
.map(|(p, d)| Ray::new(*p, *d, field_id).unwrap())
.collect()
}
}
#[cfg(test)]
mod test {
use crate::specs::surfaces::{SurfaceSpec, SurfaceType};
use super::*;
#[test]
fn test_rays_new() {
let pos = Vec3::new(0.0, 0.0, 0.0);
let dir = Vec3::new(0.0, 0.0, 1.0);
let field_id = 0;
let rays = Ray::new(pos, dir, field_id);
assert!(rays.is_ok());
}
#[test]
fn test_rays_new_non_unit_dir() {
let pos = Vec3::new(0.0, 0.0, 0.0);
let dir = Vec3::new(0.0, 0.0, 2.0);
let field_id = 0;
let rays = Ray::new(pos, dir, field_id);
assert!(rays.is_err());
}
#[test]
fn test_ray_intersection_flat_surface() {
let field_id = 0;
let pos = Vec3::new(0.0, 0.0, 0.0);
let ray = Ray::new(
Vec3::new(0.0, 0.0, -1.0),
Vec3::new(0.0, 0.0, 1.0),
field_id,
)
.unwrap();
let surf_spec = SurfaceSpec::Conic {
semi_diameter: 4.0,
radius_of_curvature: Float::INFINITY,
conic_constant: 0.0,
surf_type: SurfaceType::Refracting,
};
let surf = Surface::from_spec(&surf_spec, pos);
let max_iter = 1000;
let (p, _) = ray.intersect(&surf, max_iter).unwrap();
assert_eq!(p, Vec3::new(0.0, 0.0, 0.0));
}
#[test]
fn test_ray_intersection_conic() {
let field_id = 0;
let pos = Vec3::new(0.0, 0.0, 0.0);
let l = 0.7071;
let m = ((1.0 as Float) - 0.7071 * 0.7071).sqrt();
let ray = Ray::new(Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, l, m), field_id).unwrap();
let surf_spec = SurfaceSpec::Conic {
semi_diameter: 4.0,
radius_of_curvature: -1.0,
conic_constant: 0.0,
surf_type: SurfaceType::Refracting,
};
let surf = Surface::from_spec(&surf_spec, pos);
let max_iter = 1000;
let (p, _) = ray.intersect(&surf, max_iter).unwrap();
assert!((p.x() - 0.0).abs() < 1e-4);
assert!((p.y() - (PI / 4.0).sin()).abs() < 1e-4);
assert!((p.z() - ((PI / 4.0).cos() - 1.0)).abs() < 1e-4);
}
}