extern crate image; mod camera; mod color; mod hittable; mod hittable_list; mod material; mod ray; mod utility; mod vec3; use camera::Camera; use hittable::{HitRecord, Hittable, Sphere, Triangle}; use hittable_list::HittableList; use image::{Rgb, RgbImage}; use material::{Dielectric, Lambertian, Material, Metal}; use ray::Ray; use rayon::prelude::*; use std::env; use std::fs::File; use std::io::{self, BufRead}; use std::path::Path; use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::Arc; use tobj; use vec3::{Color, Point3, Vec3}; 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.05, 0.05, 0.05))); world.add(Box::::new(Sphere::new( Point3::new(0.0, -50000.0, 0.0), 50000.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 rad = utility::random_rng(0.1, 0.5); let center = Point3::new( 1.5 * a as f64 + 1.3 * utility::random_f64(), rad, 1.5 * b as f64 + 1.3 * utility::random_f64(), ); if (center - Point3::new(4.0, rad, 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, rad, 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 material2 = Arc::new(Lambertian::new(&Color::new(0.4, 0.2, 0.1))); let material3 = Arc::new(Metal::new(&Color::new(0.7, 0.6, 0.5), 0.0)); let material4 = Arc::new(Dielectric::new(2.0)); let material5 = Arc::new(Metal::new(&Color::new(0.9, 0.9, 0.7), 0.0)); 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, 2.0, -5.0), 2.0, material4.clone(), ))); world.add(Box::::new(Sphere::new( Point3::new(-3.6, 2.0, -2.0), 0.6, material5.clone(), ))); world.add(Box::::new(Triangle::new( Point3::new(0.0, 1.0, 5.0), Point3::new(3.0, 2.0, 0.0), Point3::new(0.0, 4.0, 0.0), material2.clone(), ))); world.add(Box::::new(Triangle::new( Point3::new(5.0, 1.0, -6.0), Point3::new(1.0, 3.0, -5.0), Point3::new(6.0, 4.0, -6.0), material5.clone(), ))); world.add(Box::::new(Triangle::new( Point3::new(5.0, 1.0, -6.0), Point3::new(8.0, 1.0, -7.0), Point3::new(6.0, 4.0, -6.0), material5.clone(), ))); world.add(Box::::new(Triangle::new( Point3::new(8.0, 4.0, -5.0), Point3::new(8.0, 1.0, -7.0), Point3::new(6.0, 4.0, -6.0), material5.clone(), ))); return world; } fn from_obj(path: &str) -> HittableList { let mut world = HittableList::new(); let material_ground = Arc::new(Lambertian::new(&Color::new(0.05, 0.05, 0.05))); world.add(Box::::new(Sphere::new( Point3::new(0.0, -50000.0, 0.0), 50000.0, material_ground.clone(), ))); /* if let Ok(lines) = read_lines(path) { for line in lines { if let Ok(text) = line { dbg!(text); } } }*/ let material = Arc::new(Lambertian::new(&Color::new(0.4, 0.2, 0.1))); let cornell_box = tobj::load_obj(path, &tobj::GPU_LOAD_OPTIONS); let (models, materials) = cornell_box.expect("Failed to load OBJ file"); let materials = materials.expect("Failed to load MTL file"); for (i, m) in models.iter().enumerate() { let mesh = &m.mesh; let mut next_face = 0; for f in 0..mesh.face_arities.len() { let end = next_face + mesh.face_arities[f] as usize; let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect(); println!(" face[{}] = {:?}", f, face_indices); next_face = end; } // todo!("find out how to get triangular faces and build world"); // https://docs.rs/tobj/3.2.3/tobj/struct.Mesh.html for v in 0..mesh.indices.len() / 3 { let indexA = mesh.indices[3 * v]; let indexB = mesh.indices[3 * v + 1]; let indexC = mesh.indices[3 * v + 2]; world.add(Box::::new(Triangle::new( Point3::new( mesh.positions[3 * indexA] as f64, mesh.positions[3 * indexA + 1] as f64, mesh.positions[3 * indexA + 2] as f64 ), Point3::new( mesh.positions[3 * indexB] as f64, mesh.positions[3 * indexB + 1] as f64, mesh.positions[3 * indexB + 2] as f64 ), Point3::new( mesh.positions[3 * indexC] as f64, mesh.positions[3 * indexC + 1] as f64, mesh.positions[3 * indexC + 2] as f64 ), material ))); for v in 0..mesh.positions.len() / 3 { println!( " v[{}] = ({}, {}, {})", v, mesh.positions[3 * v], mesh.positions[3 * v + 1], mesh.positions[3 * v + 2] ); } } return world; } // The output is wrapped in a Result to allow matching on errors // Returns an Iterator to the Reader of the lines of the file. fn read_lines

(filename: P) -> io::Result>> where P: AsRef, { let file = File::open(filename)?; Ok(io::BufReader::new(file).lines()) } /* Current world view: I y I I I / \ / \ / \ / z \ x */ fn main() { // File let mut default_file = "image.ppm"; // Image let aspect_ratio = 16.0 / 9.0; let image_width = 200; let image_height = (image_width as f64 / aspect_ratio) as u32; let samples_per_pixel = 100_u32; let max_depth = 50; let vfov = 40.0; let lookfrom = Point3::new(10.0, 4.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 = 17.0; let aperture = 0.1; let world = from_obj("obj/baum.obj"); // 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() // threadded/parallel variant //.into_iter() // iterative variant .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!"); }