use std::sync::{Arc, Mutex}; use std::time::Instant; use crate::camera::Camera; use crate::hittable::{Sphere, HittableList, HittableObject}; use crate::output::{Output, PNG}; use crate::ray::Ray; use crate::vec3::{Color, Point3, Vec3}; use hittable::MovableSphere; use rand::Rng; use rand::distributions::{Distribution, Uniform}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use crate::material::{Dielectric, Lambertian, Material, Metal}; use crate::aabb::Aabb; use crate::image_texture::ImageTexture; use crate::noise::NoiseTexture; use crate::perlin::Perlin; use crate::texture::CheckerTexture; mod vec3; mod ray; mod hittable; mod material; mod camera; mod output; mod aabb; mod bvh; mod texture; mod perlin; mod noise; mod image_texture; fn earth() -> HittableList { let mut world:HittableList = Vec::new(); let earth_texture = ImageTexture::new("textures/earthmap.jpg"); let earth_material = Arc::new( Material::Lambertian(Lambertian::textured(Arc::new(earth_texture)))); world.push(Arc::new(Sphere { center: Point3::new(0.0, 0.0, 0.0), radius: 2.0, material: earth_material })); world } fn two_spheres() -> HittableList { let mut world:HittableList = Vec::new(); let checker = CheckerTexture::colored( Color::new(0.2, 0.3, 0.1), Color::new(0.9, 0.9, 0.9)); let checker_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker)))); world.push(Arc::new(Sphere { center: Point3::new(0.0, -10.0, 0.0), radius: 10.0, material: Arc::clone(&checker_material) })); world.push(Arc::new(Sphere { center: Point3::new(0.0, 10.0, 0.0), radius: 10.0, material: checker_material })); world } fn two_perlin_spheres() -> HittableList { let mut world:HittableList = Vec::new(); let noise = NoiseTexture { noise: Perlin::new(), scale: 4.0 }; let noise_material = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(noise)))); world.push(Arc::new(Sphere { center: Point3::new(0.0, -1000.0, 0.0), radius: 1000.0, material: Arc::clone(&noise_material) })); world.push(Arc::new(Sphere { center: Point3::new(1.0, 2.0, 1.0), radius: 2.0, material: noise_material })); world } fn random_scene() -> HittableList { let mut world: HittableList = Vec::new(); let checker = CheckerTexture::colored( Color::new(0.2, 0.3, 0.1), Color::new(0.9, 0.9, 0.9)); let material_ground = Arc::new(Material::Lambertian(Lambertian::textured(Arc::new(checker)))); let ground = Sphere { center: Point3::new(0.0, -1000.0, 0.0), radius: 1000.0, material: material_ground }; world.push(Arc::new(ground)); let unit_range = Uniform::from(0.0..1.0); let fuzz_range = Uniform::from(0.0..0.5); let mut rng = rand::thread_rng(); let p = Point3::new(4.0, 0.2, 0.0); for a in -1..11 { for b in -11..11 { let choose_material = unit_range.sample(&mut rng); let center = Point3::new( (a as f64) + 0.9*unit_range.sample(&mut rng), 0.2, (b as f64) + 0.9*unit_range.sample(&mut rng)); if (center - p).length() < 0.9 { continue; } let material = match choose_material { _ if choose_material < 0.8 => Arc::new(Material::Lambertian(Lambertian::new( Color::random(0.0, 1.0) * Color::random(0.0, 1.0)))), _ if choose_material < 0.95 => Arc::new(Material::Metal(Metal::new( Color::random(0.5, 1.0), fuzz_range.sample(&mut rng)))), _ => Arc::new(Material::Dielectric(Dielectric::new(1.5))), }; let sphere: HittableObject = match rng.gen_bool(1.0 / 3.0) { true => { let center1 = center + Vec3::new(0.0, fuzz_range.sample(&mut rng) / 2.0, 0.0); Arc::new(MovableSphere { center0: center, center1, radius: 0.2, material, time0: 0.0, time1: 1.0 }) } false => Arc::new(Sphere { center, radius: 0.2, material }) }; world.push(sphere); } } let material1 = Arc::new(Material::Dielectric(Dielectric::new(1.5))); world.push(Arc::new(Sphere { center: Point3::new(0.0, 1.0, 0.0), radius: 1.0, material: material1 })); let material2 = Arc::new(Material::Lambertian(Lambertian::new(Color::new(0.4, 0.2, 0.1)))); world.push(Arc::new(Sphere { center: Point3::new(-4.0, 1.0, 0.0), radius: 1.0, material: material2 })); let material3 = Arc::new(Material::Metal(Metal::new(Color::new(0.7, 0.6, 0.5), 0.0))); world.push(Arc::new(Sphere { center: Point3::new(4.0, 1.0, 0.0), radius: 1.0, material: material3 })); world } fn main() { // Image const ASPECT_RATIO: f64 = 3.0 / 2.0; const IMAGE_WIDTH: usize = 400; const IMAGE_HEIGHT: usize = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as usize; const SAMPLES_PER_PIXEL: i32 = 1; const MAX_DEPTH: i32 = 50; let look_from = Point3::new(13.0, 2.0, 3.0); let look_at = Point3::new(0.0, 0.0, 0.0); let focus_dist = 2.0; // Camera let cam = Camera::new( look_from, look_at, Vec3::new(0.0, 1.0, 0.0), ASPECT_RATIO, 20.0, 0.0, focus_dist, 0.0, 1.0); // World let scene: u8 = 2; let world = match scene { 0 => two_spheres(), 1 => two_perlin_spheres(), 2 => earth(), _ => random_scene() }; let between = Uniform::from(0.0..1.0); let start = Instant::now(); let mut pixels = vec![0; IMAGE_WIDTH * IMAGE_HEIGHT * 3]; let bands: Vec<(usize, &mut [u8])> = pixels.chunks_mut(3).enumerate().collect(); let count = Mutex::new(0); bands.into_par_iter().for_each(|(i, pixel)| { let row = IMAGE_HEIGHT - (i / IMAGE_WIDTH) - 1; let col = i % IMAGE_WIDTH; let mut rng = rand::thread_rng(); let mut color = Color::default(); (0..SAMPLES_PER_PIXEL).for_each(|_s| { let random_number = between.sample(&mut rng); let u = (col as f64 + random_number) / (IMAGE_WIDTH - 1) as f64; let v = (row as f64 + random_number) / (IMAGE_HEIGHT - 1) as f64; let ray = cam.get_ray(u, v); color += ray.pixel_color(&world, MAX_DEPTH); }); let bytes = color.into_bytes(SAMPLES_PER_PIXEL); pixel[0] = bytes[0]; pixel[1] = bytes[1]; pixel[2] = bytes[2]; if i % 100 == 0 { let mut rem = count.lock().unwrap(); let percent_done_before = 100 * *rem / (IMAGE_WIDTH * IMAGE_HEIGHT); *rem += 100; let percent_done_after = 100 * *rem / (IMAGE_WIDTH * IMAGE_HEIGHT); if percent_done_before != percent_done_after { eprint!("\rProgress: {}% ", percent_done_after); } } }); PNG::write("imc.png", &pixels, IMAGE_WIDTH, IMAGE_HEIGHT).expect("Error writing image: {}"); eprintln!("\nDone. Time: {}ms", start.elapsed().as_millis()); } fn world_bounding_box(time0: f64, time1: f64, world: &HittableList) -> Option<Aabb> { if world.is_empty() { return None; } let mut is_first = true; let mut output_box: Aabb = Aabb { minimum: Point3::default(), maximum: Point3::default() }; for object in world { if let Some(bb) = object.bounding_box(time0, time1) { output_box = match is_first { true => bb, false => output_box.surrounding(&bb) }; is_first = false; } else { return None; } } Some(output_box) }