use crate::ray::tracer::{Ray, Surface};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CardinalPoints {
pub front_focal_length: f64,
pub back_focal_length: f64,
pub front_principal_plane: f64,
pub back_principal_plane: f64,
pub front_focal_distance: f64,
pub back_focal_distance: f64,
}
#[derive(Debug, Clone, Copy)]
pub struct SystemMatrix {
pub a: f64,
pub b: f64,
pub c: f64,
pub d: f64,
}
impl SystemMatrix {
pub fn identity() -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
}
}
pub fn propagation(d: f64) -> Self {
Self {
a: 1.0,
b: d,
c: 0.0,
d: 1.0,
}
}
pub fn thin_lens(f: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: -1.0 / f,
d: 1.0,
}
}
pub fn curved_interface(r: f64, n1: f64, n2: f64) -> Self {
Self {
a: 1.0,
b: 0.0,
c: -(n2 - n1) / (r * n1),
d: n1 / n2,
}
}
pub fn then(self, next: SystemMatrix) -> SystemMatrix {
SystemMatrix {
a: next.a * self.a + next.b * self.c,
b: next.a * self.b + next.b * self.d,
c: next.c * self.a + next.d * self.c,
d: next.c * self.b + next.d * self.d,
}
}
pub fn apply(&self, ray: Ray) -> Ray {
Ray {
y: self.a * ray.y + self.b * ray.u,
u: self.c * ray.y + self.d * ray.u,
}
}
pub fn det(&self) -> f64 {
self.a * self.d - self.b * self.c
}
pub fn cardinal_points(&self) -> CardinalPoints {
self.cardinal_points_with_media(1.0, 1.0)
}
pub fn cardinal_points_with_media(&self, n1: f64, n2: f64) -> CardinalPoints {
let c = self.c;
let f_b = if c.abs() < 1e-30 {
f64::INFINITY
} else {
-n2 / c
};
let f_f = if c.abs() < 1e-30 {
f64::INFINITY
} else {
n1 / c
};
let bfd = if c.abs() < 1e-30 {
f64::INFINITY
} else {
-self.d * n2 / c
};
let ffd = if c.abs() < 1e-30 {
f64::INFINITY
} else {
self.a * n1 / c
};
let h_back = bfd - f_b; let h_front = ffd - f_f; CardinalPoints {
front_focal_length: f_f,
back_focal_length: f_b,
front_principal_plane: h_front,
back_principal_plane: h_back,
front_focal_distance: ffd,
back_focal_distance: bfd,
}
}
pub fn from_surfaces(surfaces: &[Surface]) -> Self {
surfaces.iter().fold(Self::identity(), |m, s| {
let sm = Self::from_surface(s);
m.then(sm)
})
}
fn from_surface(surface: &Surface) -> Self {
match *surface {
Surface::FreeSpace { d } => Self::propagation(d),
Surface::ThinLens { f } => Self::thin_lens(f),
Surface::CurvedInterface { r, n1, n2 } => Self::curved_interface(r, n1, n2),
Surface::FlatInterface { n1, n2 } => Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: n1 / n2,
},
Surface::Mirror { r } => {
if r.is_infinite() {
Self::identity()
} else {
Self::thin_lens(r / 2.0)
}
}
Surface::ApertureStop { .. } => Self::identity(),
Surface::DiffractionGrating { n1, n2, .. } => Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: n1 / n2,
},
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct ParaxialImager {
pub focal_length: f64,
}
impl ParaxialImager {
pub fn new(focal_length: f64) -> Self {
Self { focal_length }
}
pub fn image_distance(&self, object_distance: f64) -> f64 {
let inv_v = 1.0 / self.focal_length + 1.0 / object_distance;
if inv_v.abs() < 1e-30 {
f64::INFINITY
} else {
1.0 / inv_v
}
}
pub fn magnification(&self, object_distance: f64) -> f64 {
self.image_distance(object_distance) / object_distance
}
pub fn newtons_equation(&self, x_object: f64) -> f64 {
self.focal_length * self.focal_length / x_object
}
pub fn depth_of_focus(&self, depth_of_field: f64, object_distance: f64) -> f64 {
let m = self.magnification(object_distance);
m * m * depth_of_field
}
pub fn field_of_view_half(&self, sensor_half_size: f64, object_distance: f64) -> f64 {
let v = self.image_distance(object_distance);
(sensor_half_size / v).atan()
}
}
#[derive(Debug, Clone, Copy)]
pub struct ChromaticAnalysis {
pub abbe_number: f64,
pub focal_length: f64,
}
impl ChromaticAnalysis {
pub fn new(focal_length: f64, abbe_number: f64) -> Self {
Self {
abbe_number,
focal_length,
}
}
pub fn bk7(focal_length: f64) -> Self {
Self::new(focal_length, 64.2)
}
pub fn f2(focal_length: f64) -> Self {
Self::new(focal_length, 36.4)
}
pub fn lca(&self) -> f64 {
self.focal_length / self.abbe_number
}
pub fn achromat_crown_focal(&self, v2: f64) -> f64 {
self.focal_length * (self.abbe_number - v2) / self.abbe_number
}
pub fn achromat_flint_focal(&self, v2: f64) -> f64 {
-self.focal_length * (self.abbe_number - v2) / v2
}
}
#[derive(Debug, Clone, Copy)]
pub struct PupilAnalysis {
pub f_number: f64,
pub entrance_pupil_diameter: f64,
}
impl PupilAnalysis {
pub fn new(focal_length: f64, aperture_diameter: f64) -> Self {
Self {
f_number: focal_length / aperture_diameter,
entrance_pupil_diameter: aperture_diameter,
}
}
pub fn numerical_aperture(&self, n: f64) -> f64 {
n * self.entrance_pupil_diameter / (2.0 * self.f_number * self.entrance_pupil_diameter)
}
pub fn airy_radius(&self, wavelength: f64) -> f64 {
1.22 * wavelength * self.f_number
}
pub fn rayleigh_resolution(&self, wavelength: f64) -> f64 {
self.airy_radius(wavelength)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn thin_lens_cardinal_points() {
let f = 0.1;
let m = SystemMatrix::thin_lens(f);
let cp = m.cardinal_points();
assert!(
(cp.back_focal_length - f).abs() < 1e-12,
"BFL={}",
cp.back_focal_length
);
assert!(
(cp.front_focal_length + f).abs() < 1e-12,
"FFL={}",
cp.front_focal_length
);
}
#[test]
fn system_matrix_from_surfaces() {
let surfaces = vec![Surface::ThinLens { f: 0.1 }];
let m = SystemMatrix::from_surfaces(&surfaces);
assert!((m.c + 10.0).abs() < 1e-10, "C={}", m.c);
}
#[test]
fn paraxial_image_distance_thin_lens() {
let imager = ParaxialImager::new(0.1); let v = imager.image_distance(-0.2); assert!((v - 0.2).abs() < 1e-12, "image distance={v}");
}
#[test]
fn magnification_sign() {
let imager = ParaxialImager::new(0.1);
let m = imager.magnification(-0.2);
assert!((m + 1.0).abs() < 1e-12, "magnification={m}");
}
#[test]
fn newtons_equation() {
let imager = ParaxialImager::new(0.1);
let xp = imager.newtons_equation(0.1);
assert!((xp - 0.1).abs() < 1e-12, "x'={xp}");
}
#[test]
fn chromatic_aberration_achromat() {
let crown = ChromaticAnalysis::bk7(0.1); let v2 = 36.4; let f1 = crown.achromat_crown_focal(v2);
let f2 = crown.achromat_flint_focal(v2);
let combined = 1.0 / f1 + 1.0 / f2;
assert!((combined - 10.0).abs() < 1e-6, "combined power={combined}");
}
#[test]
fn matrix_multiply_identity() {
let m = SystemMatrix::identity();
let m2 = m.then(SystemMatrix::identity());
assert!((m2.a - 1.0).abs() < 1e-12);
assert!((m2.d - 1.0).abs() < 1e-12);
assert!(m2.b.abs() < 1e-12);
assert!(m2.c.abs() < 1e-12);
}
#[test]
fn propagation_then_lens() {
let f = 0.1;
let m = SystemMatrix::propagation(f).then(SystemMatrix::thin_lens(f));
assert!((m.a - 1.0).abs() < 1e-12, "A={}", m.a);
assert!((m.b - f).abs() < 1e-12, "B={}", m.b);
assert!((m.c + 1.0 / f).abs() < 1e-12, "C={}", m.c);
assert!(m.d.abs() < 1e-12, "D={}", m.d);
}
#[test]
fn airy_radius_f2() {
let p = PupilAnalysis::new(0.1, 0.05); let r = p.airy_radius(550e-9);
assert!((r - 1.22 * 550e-9 * 2.0).abs() < 1e-15, "airy={r}");
}
}