use std::f32::consts::PI; use log::{info, warn}; use crate::{ objects::{hit::Hit, traits::Hittable}, ray::Ray, vec3::{Colour, Vec3}, }; #[derive(Debug)] pub struct Camera { // output image_width: u32, image_height: u32, // raytracing anti_alias_rate: u32, max_depth: u32, pixel00_loc: Vec3, pixel_delta_u: Vec3, pixel_delta_v: Vec3, // camera dirty: bool, fov: f32, look_from: Vec3, look_at: Vec3, vup: Vec3, // camera helpers u: Vec3, v: Vec3, w: Vec3, } // FIXME: kinda out of place in this file. fn deg_to_rad(deg: f32) -> f32 { deg * PI / 180. } impl Camera { pub fn new(image_width: u32, image_height: u32) -> Self { Self { image_width: image_width, image_height: image_height, anti_alias_rate: 1, max_depth: 10, dirty: true, fov: 60., pixel00_loc: Vec3::nil(), pixel_delta_u: Vec3::nil(), pixel_delta_v: Vec3::nil(), look_from: Vec3::nil(), look_at: Vec3::new(0., 0., -1.), vup: Vec3::new(0., 1., 0.), u: Vec3::nil(), v: Vec3::nil(), w: Vec3::nil(), } } fn init(&mut self) { // camera let focal_length = (self.look_from - self.look_at).length(); let theta = deg_to_rad(self.fov); let h = (theta / 2.).tan(); self.w = (self.look_from - self.look_at).get_unit(); self.u = self.vup.cross(&self.w).get_unit(); self.v = self.w.cross(&self.u); // viewport let viewport_height = 2. * h * focal_length; let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32); let viewport_u = viewport_width * self.u; let viewport_v = viewport_height * -self.v; // variables self.pixel_delta_u = viewport_u / self.image_width as f32; self.pixel_delta_v = viewport_v / self.image_height as f32; self.pixel00_loc = self.look_from - (focal_length * self.w) - viewport_u / 2. - viewport_v / 2.; self.dirty = false; } pub fn set_fov(&mut self, fov: f32) { if self.fov != fov { self.fov = fov; self.dirty = true; } } pub fn set_max_depth(&mut self, depth: u32) { if self.max_depth != depth { self.max_depth = depth; self.dirty = true; } } pub fn set_anti_alias_rate(&mut self, rate: u32) { if self.anti_alias_rate != rate { self.anti_alias_rate = rate; self.dirty = true; } } pub fn set_look_from(&mut self, look_from: Vec3) { if self.look_from != look_from { self.look_from = look_from; self.dirty = true; } } pub fn set_look_at(&mut self, look_at: Vec3) { if self.look_at != look_at { self.look_at = look_at; self.dirty = true; } } pub fn set_vup(&mut self, vup: Vec3) { if self.vup != vup { self.vup = vup; self.dirty = true; } } pub fn render(&mut self, hittables: &Vec) { if self.dirty { self.init() } warn!("{}", format!("{self:#?}")); let mut imgbuf = image::ImageBuffer::new(self.image_width, self.image_height); // render for j in 0..self.image_height { info!("{}\tScanlines remaining.", (self.image_height - j)); for i in 0..self.image_width { let pixel_tl = self.pixel00_loc + (i * self.pixel_delta_u) + (j * self.pixel_delta_v); let mut pixel_colour = Colour::nil(); for y in 1..(self.anti_alias_rate + 1) { for x in 1..(self.anti_alias_rate + 1) { let pixel_loc = pixel_tl + (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32) + (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32); let ray_dir = pixel_loc - self.look_from; let r = Ray::new(self.look_from, ray_dir); pixel_colour += self.ray_colour(&hittables, &r, self.max_depth); } } let pixel = imgbuf.get_pixel_mut(i, j); *pixel = (pixel_colour / (self.anti_alias_rate * self.anti_alias_rate) as f32).output(); } } info!("Writing image file..."); imgbuf.save("output.png").unwrap(); } fn ray_colour(&self, hittables: &Vec, r: &Ray, depth: u32) -> Colour { if depth <= 0 { return Colour::nil(); } let closest = Hit::hit_list(hittables, r); if let Some(hit) = closest { if let Some((scattered, att)) = hit.mat().scatter(&hit, r) { return att * self.ray_colour(hittables, &scattered, depth - 1); } return Colour::nil(); } // background let unit_dir = r.dir().get_unit(); let a = 0.5 * (unit_dir.y() + 1.); (1.0 - a) * Colour::new(1., 1., 1.) + a * Colour::new(0.5, 0.7, 1.0) } }