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;
#[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 {
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)
}
}
#[derive(Copy, Clone, Debug, Transformable)]
#[internal]
pub struct Camera {
#[transform]
pub transform: Transform,
pub width: u32,
pub height: u32,
pub fov: f64,
pub focal_distance: f64,
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,
}
}
}