rust_tracer/
lib.rs

1use std::io::{stderr, Write};
2
3use hittables::HittableList;
4use math::{Color, Ray};
5use rand::{rngs::ThreadRng, thread_rng, Rng};
6
7use crate::{camera::Camera, utils::write_color};
8
9pub mod camera;
10pub mod hittables;
11pub mod materials;
12pub mod math;
13pub mod utils;
14
15fn ray_color(ray: Ray, world: &HittableList, rng: &mut ThreadRng, depth: usize) -> Color {
16    if depth <= 0 {
17        return Color::new(0.0, 0.0, 0.0);
18    }
19
20    if let Some(rec) = world.hit(ray, 0.001, f64::INFINITY) {
21        return if let Some((attenuation, scattered)) = rec.material.scatter(ray, rec, rng) {
22            attenuation * ray_color(scattered, world, rng, depth - 1)
23        } else {
24            Color::new(0.0, 0.0, 0.0)
25        };
26    }
27
28    let unit_direction = ray.direction.unit_vector();
29    let t = 0.5 * (unit_direction.y() + 1.0);
30
31    (1.0 - t) * Color::new(1.0, 1.0, 1.0) + Color::new(0.5, 0.7, 1.0) * t
32}
33
34/// Renders a scene given an image width, number of samples, max recursion depth, a world, and a camera
35///
36/// # Examples
37/// ```
38/// use rust_tracer::{
39///     camera::Camera,
40///     hittables::{self, Sphere},
41///     materials::{Dielectric, Lambertian, Metal},
42///     math::{Color, Point3, Vector3},
43/// };
44///
45/// // World
46/// let mut world = hittables::HittableList::new();
47///
48/// let ground_material = Lambertian::new(Color::new(0.8, 0.8, 0.0));
49/// let center_material = Lambertian::new(Color::new(0.1, 0.2, 0.5));
50/// let left_material = Dielectric::new(1.5);
51/// let right_material = Metal::new(Color::new(0.8, 0.6, 0.2), 0.0);
52///
53/// world.add(Sphere::new(
54///     Point3::new(0.0, -100.5, -1.0),
55///     100.0,
56///     ground_material,
57/// ));
58/// world.add(Sphere::new(
59///     Point3::new(0.0, 0.0, -1.0),
60///     0.5,
61///     center_material,
62/// ));
63/// world.add(Sphere::new(
64///     Point3::new(-1.0, 0.0, -1.0),
65///     0.5,
66///     left_material.clone(),
67/// ));
68/// world.add(Sphere::new(
69///     Point3::new(-1.0, 0.0, -1.0),
70///     -0.45,
71///     left_material,
72/// ));
73/// world.add(Sphere::new(
74///     Point3::new(1.0, 0.0, -1.0),
75///     0.5,
76///     right_material,
77/// ));
78///
79/// // Camera
80/// let camera = Camera::new(
81///     Point3::new(-2.0, 2.0, 1.0),
82///     Point3::new(0.0, 0.0, -1.0),
83///     Vector3(0.0, 1.0, 0.0),
84///     20.0,
85///     16.0 / 9.0,
86/// );
87///
88/// // Render
89/// rust_tracer::render(400, 100, 50, &world, &camera);
90/// ```
91pub fn render(
92    image_width: u32,
93    samples_per_pixel: u32,
94    max_depth: usize,
95    world: &HittableList,
96    camera: &Camera,
97) {
98    // Image
99    let aspect_ratio = camera.aspect_ratio;
100    let image_height = (image_width as f64 / aspect_ratio) as i32;
101
102    // Render
103    println!("P3\n{image_width} {image_height}\n255");
104
105    let mut rng = thread_rng();
106
107    for j in (0..image_height).rev() {
108        eprint!("\rScanlines remaining: {j}  ");
109        stderr().flush().unwrap_or_default();
110
111        for i in 0..image_width {
112            let mut pixel_color = Color::new(0.0, 0.0, 0.0);
113
114            for _ in 0..samples_per_pixel {
115                let u = (i as f64 + rng.gen::<f64>()) / (image_width - 1) as f64;
116                let v = (j as f64 + rng.gen::<f64>()) / (image_height - 1) as f64;
117
118                pixel_color += ray_color(camera.ray(u, v, &mut rng), &world, &mut rng, max_depth);
119            }
120
121            println!("{}", write_color(pixel_color, samples_per_pixel));
122        }
123    }
124
125    eprintln!("\nDone");
126}