#[macro_use] extern crate tracing; use std::fs::File; use std::path::PathBuf; use anyhow::Result; use assignment_1d::{image::Image, ray::Ray, scene::Scene}; use clap::{ArgAction, Parser}; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use tracing::metadata::LevelFilter; use tracing_subscriber::{ fmt::Layer, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, }; /// Simple raytracer with Blinn-Phong illumination and shadowing. #[derive(Parser)] #[clap(author, version, about, long_about = None)] struct Opt { /// Path to the input file to use. #[clap()] input_path: PathBuf, /// Path to the output (defaults to the same file name as the input except /// with an extension of .ppm) #[clap(short = 'o', long = "output")] output_path: Option, /// Force parallel projection to be used #[clap(long = "parallel")] force_parallel: bool, /// Override distance from eye #[clap(long = "distance", default_value = "1.0")] distance: f64, /// Verbosity #[clap(short, long, action = ArgAction::Count)] verbosity: u8, } fn main() -> Result<()> { let opt = Opt::parse(); let level_filter = match opt.verbosity { 0 => LevelFilter::ERROR, 1 => LevelFilter::WARN, 2 => LevelFilter::INFO, 3 => LevelFilter::DEBUG, _ => LevelFilter::TRACE, }; // Set up logging let layer = Layer::default() .json() .with_target(false) .with_timer(tracing_subscriber::fmt::time::uptime()) .with_level(true); tracing_subscriber::registry() .with(layer) .with(level_filter) .init(); // Rename the output file if it's not provided let out_file = opt .output_path .unwrap_or_else(|| opt.input_path.with_extension("ppm")); let mut scene = Scene::from_input_file(&opt.input_path)?; let distance = opt.distance; // Force-override parallel projection if opt.force_parallel { scene.parallel_projection = true; } // Translate image pixels to real-world 3d coords let translate_pixel = scene.pixel_translation_function(distance); // Generate a parallel iterator for pixels // The iterator preserves order and uses row-major order let pixels_iter = (0..scene.image_height) .into_par_iter() .flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y))); // Loop through every single pixel of the output file let pixels = pixels_iter .map(|(px, py)| { let span = trace_span!("main_loop", px = px, py = py); let _enter = span.enter(); let pixel_in_space = translate_pixel(px, py); let ray_start = if scene.parallel_projection { // For a parallel projection, we'll just take the view direction and // subtract it from the target point. This means every single // ray will be viewed from a point at infinity, rather than a single eye // position. let n = scene.view_dir.normalize(); let view_dir = n * distance; pixel_in_space - view_dir } else { scene.eye_pos }; let ray = Ray::from_endpoints(ray_start, pixel_in_space); // let res= rayon::spawn(|| scene.trace_single_ray(ray, 0)); scene.trace_single_ray(ray, 0) }) .collect::>>()?; // Construct and emit image let image = Image { width: scene.image_width, height: scene.image_height, data: pixels, }; { let file = File::create(out_file)?; image.write(file)?; } Ok(()) }