diff --git a/.env b/.env new file mode 100644 index 0000000..984208b --- /dev/null +++ b/.env @@ -0,0 +1 @@ +RUST_LOG=info \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 005a724..bfac7a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.105" png = "0.17.10" wavefront_obj = "10.0.0" +dotenv = "0.15.0" [dev-dependencies] test-log = "0.2.12" diff --git a/src/config.rs b/src/config.rs index 202284a..ff45414 100644 --- a/src/config.rs +++ b/src/config.rs @@ -36,7 +36,7 @@ impl Default for Config { } } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct JSONScene { pub camera: JSONCamera, pub elements: Vec, diff --git a/src/main.rs b/src/main.rs index 86f97da..521c697 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ +use dotenv::dotenv; use std::env; -use std::fs::File; -use std::io::{BufReader, BufWriter}; -use std::path::Path; - -use raytracer::render::integrator::RenderIntegrator; +use raytracer::render::RenderIntegrator; // App main function -// load the JSON files for the Scene and for the Configuration, and calls the main raytracer function render from lib.rsß +// load the JSON files for the Scene and for the Configuration, and calls the main raytracer function render from lib.rs fn main() { + dotenv().ok(); + env_logger::init(); log::info!( @@ -15,64 +14,24 @@ fn main() { env::current_dir().unwrap() ); - // Open the Scene file - let scene_file = - File::open("input/scene.json").expect("Error reading input/scene.json, quitting"); - let scene_reader = BufReader::new(scene_file); - - // Read the JSON contents of the file as an instance of `Scene`. - let scene: raytracer::config::JSONScene = - serde_json::from_reader(scene_reader).expect("Error parsing input/scene.json, quitting"); - - // Open the Config file - let config_file = - File::open("input/config.json").expect("Error reading input/config.json, quitting"); - let config_reader = BufReader::new(config_file); - - // Read the JSON contents of the file as an instance of `Config`. - let config: raytracer::config::Config = - serde_json::from_reader(config_reader).expect("Error parsing input/config.json, quitting"); - - let r: RenderIntegrator = RenderIntegrator::new(scene, config); + let mut r: RenderIntegrator = RenderIntegrator::new_from_json("input/scene.json", "input/config.json"); + // execute the main render match r.render() { - Ok(i) => { - //i.save("renders/render.png").unwrap(); - - let mut raw_pixels: Vec = Vec::new(); - - for p in i { - let rgb = p.to_rgb(); - raw_pixels.push(rgb.r); - raw_pixels.push(rgb.g); - raw_pixels.push(rgb.b); + Ok(()) => { + + // success, so save to png + match r.save_to_png("renders/render.png") { + Ok(()) => { + log::info!("File saved, closing program"); + } + Err(e) => { + log::error!("Error in saving file: {}", e); + } } - - let path = Path::new(r"renders/render.png"); - let file = File::create(path).unwrap(); - let w = &mut BufWriter::new(file); - - let mut encoder = - png::Encoder::new(w, config.img_width as u32, config.img_height as u32); // Width x heigth - encoder.set_color(png::ColorType::Rgb); - encoder.set_depth(png::BitDepth::Eight); - encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); // 1.0 / 2.2, unscaled, but rounded - let source_chromaticities = png::SourceChromaticities::new( - // Using unscaled instantiation here - (0.31270, 0.32900), - (0.64000, 0.33000), - (0.30000, 0.60000), - (0.15000, 0.06000), - ); - encoder.set_source_chromaticities(source_chromaticities); - let mut writer = encoder.write_header().unwrap(); - - let data = raw_pixels; // An array containing a RGB sequence - writer.write_image_data(&data).unwrap(); // Save - log::info!("File saved, closing program"); } Err(e) => { - panic!("Error in render function: {}", e); + log::error!("Error in render function: {}", e); } } } //fn main diff --git a/src/render/camera.rs b/src/render/camera.rs index 476efb4..1ef79a1 100644 --- a/src/render/camera.rs +++ b/src/render/camera.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{config::Config, linalg::Vec3, render::ray::Ray}; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Copy, Clone)] pub struct JSONCamera { pub camera_center: Vec3, pub camera_look_at: Vec3, diff --git a/src/render/integrator.rs b/src/render/integrator.rs index e1362f9..db5e6a7 100644 --- a/src/render/integrator.rs +++ b/src/render/integrator.rs @@ -1,4 +1,4 @@ -use std::{error::Error, time::Instant}; +use std::{error::Error, time::Instant, fs::File, io::{BufReader, BufWriter}, path::Path}; use indicatif::ProgressBar; use rand::{rngs::SmallRng, SeedableRng}; @@ -16,20 +16,86 @@ use crate::{ render::ray::Ray, }; +#[derive(Debug, Clone)] pub struct RenderIntegrator { json_scene: JSONScene, config: Config, + pixels: Vec, } impl RenderIntegrator { pub fn new(json_scene: JSONScene, config: Config) -> Self { - RenderIntegrator { json_scene, config } + // create a 1-d vector holding all the pixels, and + let pixels = vec![ + Color::new(0.0, 0.0, 0.0); + (config.img_width * config.img_height) as usize + ]; + + RenderIntegrator { json_scene, config, pixels } + } + + pub fn new_from_json(scene_path: &str, config_path: &str) -> Self { + + // Open the Scene file + let scene_file = + File::open(scene_path).expect("Error reading input/scene.json, quitting"); + let scene_reader = BufReader::new(scene_file); + + // Read the JSON contents of the file as an instance of `Scene`. + let scene: JSONScene = + serde_json::from_reader(scene_reader).expect("Error parsing input/scene.json, quitting"); + + // Open the Config file + let config_file = + File::open(config_path).expect("Error reading input/config.json, quitting"); + let config_reader = BufReader::new(config_file); + + // Read the JSON contents of the file as an instance of `Config`. + let config: Config = + serde_json::from_reader(config_reader).expect("Error parsing input/config.json, quitting"); + + return RenderIntegrator::new(scene, config); + } + + pub fn save_to_png(&self, png_path: &str) -> Result<(), Box> { + let mut raw_pixels: Vec = Vec::new(); + + for p in &self.pixels { + let rgb = p.to_rgb(); + raw_pixels.push(rgb.r); + raw_pixels.push(rgb.g); + raw_pixels.push(rgb.b); + } + + let path = Path::new(png_path); + let file = File::create(path).unwrap(); + let w = &mut BufWriter::new(file); + + let mut encoder = + png::Encoder::new(w, self.config.img_width as u32, self.config.img_height as u32); // Width x heigth + encoder.set_color(png::ColorType::Rgb); + encoder.set_depth(png::BitDepth::Eight); + encoder.set_source_gamma(png::ScaledFloat::new(1.0 / 2.2)); // 1.0 / 2.2, unscaled, but rounded + let source_chromaticities = png::SourceChromaticities::new( + // Using unscaled instantiation here + (0.31270, 0.32900), + (0.64000, 0.33000), + (0.30000, 0.60000), + (0.15000, 0.06000), + ); + encoder.set_source_chromaticities(source_chromaticities); + let mut writer = encoder.write_header().unwrap(); + + let data = raw_pixels; // An array containing a RGB sequence + writer.write_image_data(&data).unwrap(); // Save + + Ok(()) } // fn render // the main render function that sets up the camera, creates an 1d vector for the pixels, splits it into bands, calls the band render function and writes to an image file // applies parallel rendering using Rayon - pub fn render(self) -> Result, Box> { + pub fn render(&mut self) -> Result<(), Box> { // create a new camera object let camera: Camera = Camera::new(&self.config, &self.json_scene.camera); @@ -37,7 +103,7 @@ impl RenderIntegrator { // this allows us to pre-calculate various things like the bbox or commonly used numbers in the cconstructor let mut objects: Vec = Vec::new(); - for json_element in self.json_scene.elements { + for json_element in &self.json_scene.elements { match json_element { JSONElement::JSONQuad(q) => q.add_as_element(&mut objects), JSONElement::JSONSphere(s) => s.add_as_element(&mut objects), @@ -56,12 +122,9 @@ impl RenderIntegrator { // create a refrence to the elements that are marked as an attractor let attractors: Vec<&Element> = objects.iter().filter(|e| e.is_attractor()).collect(); - // create a 1-d vector holding all the pixels, and split into bands for parallel rendering - let mut pixels = vec![ - Color::new(0.0, 0.0, 0.0); - (self.config.img_width * self.config.img_height) as usize - ]; - let bands: Vec<(usize, &mut [Color])> = pixels + // split into bands for parallel rendering + + let bands: Vec<(usize, &mut [Color])> = self.pixels .chunks_mut(self.config.img_width as usize) .enumerate() .collect(); @@ -86,8 +149,8 @@ impl RenderIntegrator { log::info!("Render finished in {}ms", start.elapsed().as_millis()); - // return the pixels - Ok(pixels) + // return an OK + Ok(()) } // fn render_line diff --git a/src/render/mod.rs b/src/render/mod.rs index 168c487..5b84942 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,5 +1,6 @@ pub mod camera; -pub mod integrator; +mod integrator; +pub use integrator::RenderIntegrator; mod pdf; pub use pdf::{CosinePDF, HittablePDF, MixedPDF, PDFTrait, Pdf};