extern crate image; mod camera; mod color; mod hittable; mod hittable_list; mod ray; mod utility; mod vec3; mod material; use camera::Camera; use hittable::{HitRecord, Hittable, Sphere}; use hittable_list::HittableList; use image::{Rgb, RgbImage}; use ray::Ray; use rayon::prelude::*; use std::env; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use vec3::{Color, Point3, Vec3}; use material::{Material, Lambertian, Metal, Dielectric}; fn ray_color(r: &Ray, world: &HittableList, depth: u32) -> Color { let mut rec = HitRecord::empty(); if depth <= 0 { return Color::null(); } if world.hit(r, 0.001, f64::INFINITY, &mut rec) { let mut scattered = Ray::new(Point3::null(), Vec3::null()); let mut attenuation = Color::null(); if rec.mat_ptr.scatter(r, &rec, &mut attenuation, &mut scattered) { return attenuation * ray_color(&scattered, world, depth-1); } return Color::null(); //let target = rec.p + rec.normal + Vec3::random_unit_vector(); // rec.p + rec.normal.random_in_hemisphere(); //return 0.5 * ray_color(&Ray::new(rec.p, target - rec.p), world, depth - 1); } let unit_direction = r.direction(); let t = 0.5 * (unit_direction.y() + 1.0); return (1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0); } fn random_world() -> HittableList { let mut world = HittableList::new(); let material_ground = Arc::new(Lambertian::new(&Color::new(0.1, 0.1, 0.1))); //let material_center = Arc::new(Lambertian::new(&Color::new(0.7, 0.1, 0.2))); //let material_center = Arc::new(Dielectric::new(1.5)); //let material_blue = Arc::new(Lambertian::new(&Color::new(0.2, 0.1, 0.7))); //let material_metal = Arc::new(Metal::new(&Color::new(0.8, 0.8, 0.8), 0.1)); //let material_metal_fuzz = Arc::new(Metal::new(&Color::new(0.8, 0.8, 0.8), 1.0)); //let material_mirror = Arc::new(Metal::new(&Color::new(0.8, 0.8, 0.8), 0.0)); //let material_dielectric = Arc::new(Dielectric::new(1.5)); //let material_light = Arc::new(Lambertian::new(&Color::new(2.0, 1.0, 0.0))); world.add(Box::::new(Sphere::new( Point3::new(0.0, -1000.0, 0.0), 1000.0, material_ground.clone(), ))); (-6..5).into_iter().for_each(|a| { (-6..5).into_iter().for_each(|b| { let choose_mat = utility::random_f64(); let center = Point3::new(a as f64 + 0.9 * utility::random_f64(), 0.2, b as f64 + 0.9 * utility::random_f64()); if (center - Point3::new(4.0, 0.2, 0.0)).length() > 0.9 { if choose_mat < 0.8 { // diffuse let sphere_material = Arc::new(Lambertian::new(&(Color::random_f64() * Color::random_f64()))); world.add(Box::::new(Sphere::new( center, 0.2, sphere_material.clone() ))); } else if choose_mat < 0.95 { // metal let sphere_material = Arc::new(Metal::new(&Color::random_rng(0.5, 1.0), utility::random_rng(0.0, 0.5))); world.add(Box::::new(Sphere::new( center, 0.2, sphere_material.clone() ))); } else { // glass let sphere_material = Arc::new(Dielectric::new(1.5)); world.add(Box::::new(Sphere::new( center, 0.2, sphere_material.clone() ))); } } }); }); let material1 = Arc::new(Dielectric::new(1.5)); let material3 = Arc::new(Metal::new(&Color::new(0.7, 0.6, 0.5), 0.0)); let material2 = Arc::new(Lambertian::new(&Color::new(0.4, 0.2, 0.1))); world.add(Box::::new(Sphere::new( Point3::new(0.0, 1.0, 0.0), 1.0, material2.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(-4.0, 1.0, 0.0), 1.0, material1.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(4.0, 1.0, 0.0), 1.0, material3.clone(), ))); /* world.add(Box::::new(Sphere::new( Point3::new(-2.0*r, 0.1, -2.0), r, material_dielectric.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(r, 0.0, -1.0), r, material_dielectric.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(-1.5, 1.3, -1.7), 0.4, material_mirror.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(-0.5, 0.5, 1.0), 0.4, material_blue.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(0.5, 1.0, 0.3), 0.3, material_light.clone(), ))); */ /* for i in -15..15 { for j in -15..15 { world.add(Box::::new(Sphere::new(Point3::new(j as f64/5.0 as f64, i as f64/5.0 as f64, -1.5), 0.08))); } } */ return world; } fn main() { // File let mut default_file = "image.ppm"; // Image let aspect_ratio = 16.0 / 9.0; let image_width = 1920; let image_height = (image_width as f64 / aspect_ratio) as u32; let samples_per_pixel = 100_u32; let max_depth = 50; let vfov = 20.0; let lookfrom = Point3::new(10.0, 10.0, 13.0); let lookat = Point3::new(0.0, 0.0, 0.0); let vup = Vec3::new(0.0, 1.0, 0.0); let dist_to_focus = 15.0; let aperture = 0.1; // World let world = random_world(); // Camera let cam = Camera::new(lookfrom, lookat, vup, vfov, aspect_ratio, aperture, dist_to_focus); // Render let args: Vec = env::args().collect(); if args.len() > 1 && args[1] != "" { default_file = &args[1]; } let mut image = RgbImage::new(image_width, image_height); let atomic_counter = Arc::new(AtomicU32::new(0)); let color_lines: Vec<_> = (0..image_height) .into_par_iter() .rev() .map(|j| { let v = atomic_counter.fetch_add(1, Ordering::Relaxed); eprint!("\rScanlines remaining: {:5}", image_height - v); let mut colors = Vec::new(); for i in 0..image_width { let mut pixel_color = Color::new(0.0, 0.0, 0.0); for _ in 0..samples_per_pixel { let u = (i as f64 + utility::random_f64()) / (image_width - 1) as f64; let v = (j as f64 + utility::random_f64()) / (image_height - 1) as f64; let r = cam.get_ray(u, v); pixel_color += ray_color(&r, &world, max_depth); } colors.push(pixel_color); } return colors; }) .collect(); eprint!("\rScanlines remaining: {:5}", 0); (0..image_height).into_iter().rev().for_each(|j| { (0..image_width).into_iter().for_each(|i| { color::put_color( &mut image, &color_lines[(image_height - j - 1) as usize][i as usize], i, image_height - j - 1, samples_per_pixel, ); }) }); image.save(default_file).unwrap(); eprintln!("\nDone!"); }