use indicatif::{ProgressBar, ProgressStyle}; use crate::calculus::calculus::{deg2rad, sample_square, Point3, Ray, Vec3}; use crate::common::{get_image_height, Color, DisplayBuffer, Pixel, ASPECT_RATIO, DEFOCUS_ANGLE, FOCUS_DIST, IMG_WIDTH, LOOK_AT, LOOK_FROM, MAX_DEPTH, SAMPLES_PER_PIXEL, VFOV, VUP}; use crate::calculus::Interval; use crate::object::{HitRecord, Hittable, HittableList}; pub struct Camera { pub aspect_ratio: f32, pub image_width: usize, pub samples_per_pixel: usize, pub vfov: f32, pub look_from: Point3, pub look_at: Point3, pub vup: Vec3, image_height: usize, center: Point3, pixel_upper_left: Point3, delta_pixel_u: Vec3, delta_pixel_v: Vec3, pixel_samples_scale: f32, max_depth: usize, pub defocus_angle: f32, pub focus_dist: f32, pub defocus_disk_u: Vec3, pub defocus_disk_v: Vec3, } impl Default for Camera { fn default() -> Self { Self::new( ASPECT_RATIO, IMG_WIDTH, VFOV, LOOK_FROM, LOOK_AT, VUP, SAMPLES_PER_PIXEL, FOCUS_DIST, DEFOCUS_ANGLE, MAX_DEPTH ) } } impl Camera { pub fn new( aspect_ratio: f32, image_width: usize, vfov: f32, look_from: Point3, look_at: Point3, vup: Vec3, samples_per_pixel: usize, focus_dist: f32, defocus_angle: f32, max_depth: usize, ) -> Self { let mut new_camera = Self { aspect_ratio, image_width, image_height: 1, center: Point3::new(0.0, 0.0, 0.0), pixel_upper_left: Point3::new(0.0, 0.0, 0.0), delta_pixel_u: Vec3::new(0.0, 0.0, 0.0), delta_pixel_v: Vec3::new(0.0, 0.0, 0.0), samples_per_pixel, pixel_samples_scale: 1.0, max_depth, vfov, look_from, look_at, vup, defocus_angle, focus_dist, defocus_disk_u: Vec3::new(0.0, 0.0, 0.0), defocus_disk_v: Vec3::new(0.0, 0.0, 0.0), }; new_camera.compute_dependent_fields(); new_camera } fn compute_dependent_fields(&mut self) { self.image_height = get_image_height(self.image_width, self.aspect_ratio); self.center = self.look_from; self.pixel_samples_scale = 1.0 / self.samples_per_pixel as f32; let theta = deg2rad(self.vfov); let h = (theta / 2.0).tan(); let w = self.look_from.sub(&self.look_at).unit(); let u = self.vup.cross_prod(&w).unit(); let v = w.cross_prod(&u).unit(); let viewport_height = 2.0 * h * self.focus_dist; let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32); let viewport_u = u.scalar_mul(viewport_width); let viewport_v = v.scalar_mul(-1.0).scalar_mul(viewport_height); self.delta_pixel_u = viewport_u.scalar_mul(1.0 / self.image_width as f32); self.delta_pixel_v = viewport_v.scalar_mul(1.0 / self.image_height as f32); let viewport_upper_left = self.center .sub(&w.scalar_mul(self.focus_dist)) .sub(&viewport_u.scalar_mul(0.5)) .sub(&viewport_v.scalar_mul(0.5)); self.pixel_upper_left = viewport_upper_left.add( &self.delta_pixel_u.add(&self.delta_pixel_v).scalar_mul(0.5) ); let defocus_radius = self.focus_dist * deg2rad(self.defocus_angle / 2.0).tan(); self.defocus_disk_u = u.scalar_mul(defocus_radius); self.defocus_disk_v = v.scalar_mul(defocus_radius); } fn get_ray(&self, i: usize, j: usize) -> Ray { let offset = sample_square(); let pixel_sample = self.pixel_upper_left .add(&self.delta_pixel_u.scalar_mul(i as f32 + offset.x)) .add(&self.delta_pixel_v.scalar_mul(j as f32 + offset.y)); let ray_origin = if self.defocus_angle <= 0.0 { self.center } else { self.defocus_disk_sample() }; let ray_direction = pixel_sample.sub(&ray_origin); Ray { origin: ray_origin, direction: ray_direction, } } fn defocus_disk_sample(&self) -> Point3 { let p = Vec3::random_in_unit_disk(); self.center .add(&self.defocus_disk_u.scalar_mul(p.x)) .add(&self.defocus_disk_v.scalar_mul(p.y)) } fn ray_color(&self, ray: &Ray, world: &HittableList, depth: usize) -> Color { if depth <= 0 { return Color::new(0.0, 0.0, 0.0); } let mut rec = HitRecord::default(); let ray_t = Interval::new(0.001, f32::INFINITY); if world.hit(ray, ray_t, &mut rec) { let mut scattered: Ray = Ray { origin: Vec3::random_unit(), direction: Vec3::random_unit(), }; let mut attenuation: Color = Color::new(0.0, 0.0, 0.0); if rec.material.scatter(ray, &rec, &mut attenuation, &mut scattered) { return attenuation.elem_prod(&self.ray_color(&mut scattered, &world, depth - 1)); } return Color::new(0.0, 0.0, 0.0); } let unit_direction = ray.direction.unit(); let a = 0.5 * (unit_direction.y + 1.0); let color1 = Color::new(1.0, 1.0, 1.0).mul_scalar(1.0 - a); let color2 = Color::new(0.5, 0.7, 1.0).mul_scalar(a); color1.add(&color2) } pub fn render(&self, display_buffer: &mut DisplayBuffer, world: &HittableList) { let progress_bar = ProgressBar::new(100); let one_percent: u64 = ((self.image_width * self.image_height) / 100) as u64; progress_bar.set_style(ProgressStyle::default_bar() .template("Rendering {spinner:.green} {wide_bar} {percent}% ") .unwrap() .tick_chars("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ ") ); let mut delta: u64 = 0; (0..self.image_height).for_each(|j| { (0..self.image_width).for_each(|i| { let mut pixel_color = Color::new(0.0, 0.0, 0.0); (0..self.samples_per_pixel).for_each(|_| { let r = self.get_ray(i, j); let ray_color = self.ray_color(&r, world, self.max_depth); pixel_color = pixel_color.add(&ray_color); }); delta += 1; if delta >= one_percent { progress_bar.inc(1); delta = 0; } pixel_color = pixel_color.mul_scalar(self.pixel_samples_scale); display_buffer[j][i] = Pixel::from_color(&pixel_color); }) }); progress_bar.finish_with_message("Done rendering to display buffer"); } }