use crate::{color::*, hittable::*, ray::*, vec3::*};
use rand::{thread_rng, Rng};
pub struct Camera {
pub aspect_ratio: f64,
pub image_width: u32,
pub image_height: u32,
pub samples: u32,
pub bounces: u32,
pub vfov: f64,
pub lookfrom: Point3,
pub lookat: Point3,
pub vup: Vec3,
pub defocus_angle: f64,
pub focus_dist: f64,
sample_scale: f64,
center: Point3,
pixel00_loc: Point3,
pixel_delta_u: Vec3,
pixel_delta_v: Vec3,
u: Vec3,
v: Vec3,
w: Vec3,
defocus_disc_u: Vec3,
defocus_disc_v: Vec3,
pub sky: Box<dyn Sky>,
}
impl Camera {
pub fn new() -> Camera {
let mut cam = Camera::default();
cam.initialize();
cam
}
pub fn render_to_string<F>(&mut self, world: HittableList, mut progress: F) -> String
where
F: FnMut(u32),
{
self.initialize();
let mut buffer =
String::with_capacity((self.image_width * self.image_height * 12) as usize);
for j in 0..self.image_height {
for i in 0..self.image_width {
let mut pixel_color = Color::from(0.0);
for _ in 0..self.samples {
let r = self.get_ray(i, j);
pixel_color += self.ray_color(r, self.bounces, &world);
}
let rgb = (pixel_color * self.sample_scale).to_rgb_bytes();
buffer.push_str(&format!("{} {} {}\n", rgb[0], rgb[1], rgb[2]));
}
progress(j);
}
buffer
}
pub fn render_to_bytes<F>(&mut self, world: HittableList, mut progress: F) -> Vec<u8>
where
F: FnMut(u32),
{
self.initialize();
let mut buffer = Vec::new();
for j in 0..=self.image_height - 1 {
for i in 0..=self.image_width - 1 {
let mut pixel_color = Color::from(0.0);
for _ in 0..self.samples {
let r = self.get_ray(i, j);
pixel_color += self.ray_color(r, self.bounces, &world);
}
buffer.extend_from_slice(&(pixel_color * self.sample_scale).to_rgb_bytes());
}
progress(j);
}
buffer
}
pub fn initialize(&mut self) {
self.aspect_ratio = self.image_width as f64 / self.image_height as f64;
self.sample_scale = 1.0 / self.samples as f64;
self.center = self.lookfrom;
let theta = self.vfov.to_radians();
let h = (theta / 2.0).tan();
let viewport_height = 2.0 * h * self.focus_dist;
let viewport_width = viewport_height * (self.image_width as f64 / self.image_height as f64);
self.w = (self.lookfrom - self.lookat).normalized();
self.u = cross(&self.vup, &self.w).normalized();
self.v = cross(&self.w, &self.u);
let viewport_u = viewport_width * self.u;
let viewport_v = viewport_height * -self.v;
self.pixel_delta_u = viewport_u / self.image_width as f64;
self.pixel_delta_v = viewport_v / self.image_height as f64;
let viewport_upper_left =
self.center - (self.focus_dist * self.w) - viewport_u / 2.0 - viewport_v / 2.0;
self.pixel00_loc = viewport_upper_left + 0.5 * (self.pixel_delta_u + self.pixel_delta_v);
let defocus_radius = self.focus_dist * (self.defocus_angle / 2.0).to_radians().tan();
self.defocus_disc_u = self.u * defocus_radius;
self.defocus_disc_v = self.v * defocus_radius;
}
pub fn get_ray(&self, i: u32, j: u32) -> Ray {
let offset = sample_square();
let pixel_sample = self.pixel00_loc
+ ((i as f64 + offset.x) * self.pixel_delta_u)
+ ((j as f64 + offset.y) * self.pixel_delta_v);
let ray_origin = defocus_disk_sample(&self);
let ray_direction = pixel_sample - ray_origin;
Ray::new(ray_origin, ray_direction)
}
pub fn get_distance(&self, target: Point3) -> f64 {
(target - self.lookfrom).length()
}
pub fn get_sample_scale(&self) -> f64 {
self.sample_scale
}
pub fn ray_color(&self, r: Ray, bounces: u32, world: &HittableList) -> Color {
if bounces == 0 {
return Color::from(0.);
}
let mut rec: HitRecord = Default::default();
if world.hit(&r, 0.001..f64::INFINITY, &mut rec) {
let mut scattered = Ray::new(Vec3::from(0.), Vec3::from(0.));
let mut attenuation = Color::from(1.);
if rec.mat.scatter(&r, &rec, &mut attenuation, &mut scattered) {
return attenuation * self.ray_color(scattered, bounces - 1, world);
}
return Color::new(0., 0., 0.); }
return self.sky.color(r);
}
pub fn get_height(&self) -> u32 {
self.image_height
}
}
impl Default for Camera {
fn default() -> Self {
Self {
aspect_ratio: 1.0,
image_width: 600,
samples: 100,
bounces: 10,
image_height: 600,
sample_scale: 1.0,
vfov: 90.0,
lookfrom: Point3::from(0.),
lookat: Point3::new(0., 0., -1.),
vup: Vec3::new(0., 1., 0.),
defocus_angle: 0.,
focus_dist: 10.,
center: Vec3::from(0.0),
pixel00_loc: Vec3::from(0.0),
pixel_delta_u: Vec3::from(0.0),
pixel_delta_v: Vec3::from(0.0),
u: Vec3::from(0.0),
v: Vec3::from(0.0),
w: Vec3::from(0.0),
defocus_disc_u: Vec3::from(0.0),
defocus_disc_v: Vec3::from(0.0),
sky: Box::new(GradientSky {
start: Color::new(0.5, 0.7, 1.0),
end: Color::new(1.0, 1.0, 1.0),
}),
}
}
}
fn sample_square() -> Vec3 {
let mut rng = thread_rng();
Vec3::new(rng.gen_range(0.0..1.0), rng.gen_range(0.0..1.0), 0.)
}
fn defocus_disk_sample(cam: &Camera) -> Point3 {
let p = Vec3::random_in_unit_disk();
cam.center + (p.x * cam.defocus_disc_u) + (p.y * cam.defocus_disc_v)
}
pub trait Sky: SkyClone {
fn color(&self, ray: Ray) -> Vec3;
}
pub trait SkyClone {
fn clone_box(&self) -> Box<dyn Sky>;
}
impl<T> SkyClone for T
where
T: 'static + Sky + Clone,
{
fn clone_box(&self) -> Box<dyn Sky> {
Box::new(self.clone())
}
}
impl Clone for Box<dyn Sky> {
fn clone(&self) -> Box<dyn Sky> {
self.clone_box()
}
}
#[derive(Clone)]
pub struct GradientSky {
pub start: Color,
pub end: Color,
}
impl Sky for Color {
fn color(&self, _direction: Ray) -> Vec3 {
*self
}
}
impl Sky for GradientSky {
fn color(&self, ray: Ray) -> Vec3 {
let t = 0.5 * (ray.direction.normalized().y + 1.0);
self.start * (1.0 - t) + self.end * t
}
}