crystal_ball 0.3.0

A path tracing library written in Rust.
Documentation
use std::f64::consts::{FRAC_PI_4, PI};

use nanorand::tls::TlsWyRand;

use crystal_ball_derive::Transformable;

use crate::math::{Bounds2, Point2, Point3, Ray, Transform, Vec2, Vec3};
use crate::util::IOR;

/// A camera with precalculated matrices.
#[derive(Copy, Clone, Debug)]
pub(crate) struct PrecalculatedCamera {
    pub camera_to_world: Transform,
    pub raster_to_camera: Transform,
    pub lens_radius: f64,
    pub focal_distance: f64,
    pub width: u32,
    pub height: u32,
}

impl From<Camera> for PrecalculatedCamera {
    fn from(c: Camera) -> Self {
        let lens_radius = c.aperture;
        let focal_distance = c.focal_distance;

        let aspect = c.width as f64 / c.height as f64;

        let screen_window = if aspect > 1.0 {
            Bounds2::new(Point2::new(-aspect, -1.0), Point2::new(aspect, 1.0))
        } else {
            Bounds2::new(
                Point2::new(-1.0, -1.0 / aspect),
                Point2::new(1.0, 1.0 / aspect),
            )
        };

        let camera_to_screen = Transform::perspective(c.fov, 1e-2, 1e3);
        let screen_to_raster =
            Transform::scale(
                Point3::ZERO,
                Vec3::new(c.width as f64, c.height as f64, 1.0),
            ) * Transform::scale(
                Point3::ZERO,
                Vec3::new(
                    1.0 / (screen_window.min.x - screen_window.max.x),
                    1.0 / (screen_window.max.y - screen_window.min.y),
                    1.0,
                ),
            ) * Transform::translate(Vec3::new(-screen_window.max.x, -screen_window.min.y, 0.0));

        let raster_to_screen = screen_to_raster.inverse();

        let raster_to_camera = camera_to_screen.inverse() * raster_to_screen;

        Self {
            camera_to_world: c.transform * Transform::rotate_y(Point3::ZERO, PI),
            raster_to_camera,
            lens_radius,
            focal_distance,
            width: c.width,
            height: c.height,
        }
    }
}

impl PrecalculatedCamera {
    /// Generate a new ray from the camera's position.
    pub fn get_ray(&self, x: f64, y: f64, rng: &mut TlsWyRand) -> Ray {
        let p_film = Point3::new(x, y, 0.0);
        let p_camera = self.raster_to_camera.mat4 * p_film;

        let mut ray = Ray::new(Point3::ZERO, Vec3::from(p_camera), IOR::AIR);

        if self.lens_radius > 0.0 {
            let lens_pos = self.lens_radius * Vec2::random_in_unit_disk(rng);

            let ft = self.focal_distance / ray.direction.z;
            let focus_point = ray.get(ft);

            ray.origin = Point3::new(lens_pos.x, lens_pos.y, 0.0);
            ray.direction = (focus_point - ray.origin).normalize()
        }

        self.camera_to_world.transform_ray(ray)
    }

    pub fn dimensions(&self) -> (u32, u32) {
        (self.width, self.height)
    }
}

/// The camera used for rendering the [`Scene`](crate::rendering::Scene).
#[derive(Copy, Clone, Debug, Transformable)]
#[internal]
pub struct Camera {
    /// The camera's transform.
    ///
    /// Defaults to a position and rotation of `0` and a scale of `1`.
    /// A rotation of `0` means the +X axis is to the right, +Y is up and the camera if looking towards the -Z axis.
    #[transform]
    pub transform: Transform,
    /// The width of the rendered image.
    ///
    /// Defaults to `1920`.
    pub width: u32,
    /// The height of the rendered image.
    ///
    /// Defaults to `1080`.
    pub height: u32,
    /// The camera's field of view in radians.
    ///
    /// Defaults to `π/4`.
    pub fov: f64,
    /// The camera's focal distance, i.e. the distance from the camera at which a point is in full focus.
    ///
    /// This has no effect, if `lens_radius` is set to `0.0`.
    ///
    /// Defaults to `10.0`.
    pub focal_distance: f64,
    /// The camera's aperture size.
    ///
    /// The higher the value, the more blur (bokeh).
    /// `0.0` corresponds to no blur at all.
    ///
    /// Defaults to `0.0`.
    pub aperture: f64,
}

impl Default for Camera {
    fn default() -> Self {
        Self {
            transform: Transform::default(),
            width: 1920,
            height: 1080,
            fov: FRAC_PI_4,
            focal_distance: 10.0,
            aperture: 0.0,
        }
    }
}