185 lines
5.2 KiB
Rust
185 lines
5.2 KiB
Rust
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<T: Hittable>(&mut self, hittables: &Vec<T>) {
|
|
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<T: Hittable>(&self, hittables: &Vec<T>, 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)
|
|
}
|
|
}
|