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}