lumiere 0.2.0

A ray-tracing implementation in Rust, based on Peter Shirley's "Ray Tracing in One Weekend" series.
Documentation
use rand::{rngs, Rng};

use crate::{ray::Ray, vec3::Vec3, Point3};

pub struct CameraBuilder {
    origin: Point3,
    aspect_ratio: f64,
    aperture: f64,
    focus_dist: f64,
    fov: f64,
    look_dir: Vec3,
    v_up: Vec3,
}

impl CameraBuilder {
    pub fn new() -> Self {
        CameraBuilder {
            origin: Point3::new(0., 0., -1.),
            aspect_ratio: 16. / 9.,
            aperture: 0.,
            focus_dist: 10.,
            fov: 40.,
            look_dir: Vec3::new(0., 0., 1.).unit(),
            v_up: Vec3::new(0., 1., 0.),
        }
    }

    pub fn origin(&mut self, origin: Point3) -> &mut Self {
        self.origin = origin;
        self
    }

    pub fn look_dir(&mut self, look_dir: Vec3) -> &mut Self {
        self.look_dir = look_dir;
        self
    }

    pub fn v_up(&mut self, v_up: Vec3) -> &mut Self {
        self.v_up = v_up;
        self
    }

    pub fn aspect_ratio(&mut self, aspect_ratio: f64) -> &mut Self {
        self.aspect_ratio = aspect_ratio;
        self
    }

    pub fn aperture(&mut self, aperture: f64) -> &mut Self {
        self.aperture = aperture;
        self
    }

    pub fn focus_dist(&mut self, focus_dist: f64) -> &mut Self {
        self.focus_dist = focus_dist;
        self
    }

    /// Sets the vertical field of view of the camera in degrees.
    pub fn fov(&mut self, fov: f64) -> &mut Self {
        self.fov = fov;
        self
    }

    pub fn build(&mut self) -> Camera {
        let theta = self.fov.to_radians();
        let h = (theta / 2.).tan();
        let viewport_height = 2.0 * h;
        let viewport_width = self.aspect_ratio * viewport_height;

        let w = -self.look_dir.unit();
        let u = self.v_up.cross(w);
        let v = w.cross(u);

        let horizontal = u * viewport_width * self.focus_dist;
        let vertical = v * viewport_height * self.focus_dist;
        let upper_left_corner = self.origin - horizontal / 2. + vertical / 2. - w * self.focus_dist;

        Camera {
            origin: self.origin,
            upper_left_corner,
            horizontal,
            vertical,
            lens_radius: self.aperture / 2.,
            u,
            v,
            w,
        }
    }
}

impl Default for CameraBuilder {
    fn default() -> Self {
        Self::new()
    }
}

pub struct Camera {
    origin: Point3,
    upper_left_corner: Point3,
    horizontal: Vec3,
    vertical: Vec3,
    lens_radius: f64,
    u: Vec3,
    v: Vec3,
    #[allow(dead_code)]
    w: Vec3,
}

impl Camera {
    pub fn get_ray(&self, s: f64, t: f64, rng: &mut rngs::ThreadRng) -> Ray {
        let rd = Vec3::random_in_unit_disk(rng) * self.lens_radius;
        let offset = self.u * rd.x + self.v * rd.y;
        Ray::new(
            self.origin + offset,
            self.upper_left_corner + self.horizontal * s - self.vertical * t - self.origin - offset,
            rng.gen(),
        )
    }

    pub fn builder() -> CameraBuilder {
        CameraBuilder::new()
    }
}