use crate::{ bvh_triangles::BVHTriangles, camera::Camera, cuboid::Cuboid, hitable::Hit, hitable_list::HitableList, material::{Dielectric, DiffuseLight, Isotropic, Lambertian, Material, Metal}, renderer::Scene, sphere::Sphere, texture::{EnvMap, Texture}, }; use chrono::IsoWeek; use serde::Deserialize; use std::{collections::HashMap, fs::File, io::BufReader, path::PathBuf, sync::Arc}; use stl::STL; use thiserror::Error; use vec3::Vec3; #[derive(Debug, Deserialize)] pub struct Config { scene: SceneConfig, camera: CameraConfig, materials: Vec, hitables: Vec, envmap: Option, } #[derive(Error, Debug)] pub enum ConfigError { #[error("failed to load image")] ImageError(#[from] image::ImageError), #[error("failed to parser STL")] STLError(#[from] stl::ParseError), #[error("I/O error")] IOError(#[from] std::io::Error), #[error("duplication material named '{0}'")] DuplicateMaterial(String), #[error("unkown material named '{0}'")] UnknownMaterial(String), } impl TryFrom for Scene { type Error = ConfigError; fn try_from(c: Config) -> Result { let mut materials = HashMap::new(); for mc in c.materials { let v: Arc = match mc.material { Materials::Metal { albedo, fuzzy } => Arc::new(Metal::new(albedo, fuzzy)), Materials::Dielectric { ref_idx } => Arc::new(Dielectric::new(ref_idx)), Materials::DiffuseLight { texture } => Arc::new(DiffuseLight::new(texture)), Materials::Isotropic { texture } => Arc::new(Isotropic::new(texture)), Materials::Lambertian { texture } => Arc::new(Lambertian::new(texture)), }; if materials.insert(mc.name.clone(), v).is_some() { return Err(ConfigError::DuplicateMaterial(mc.name)); } } let hitables: Result>, Self::Error> = c .hitables .into_iter() .map(|hc| -> Result, Self::Error> { match hc.hitable { Hitables::Sphere { center, radius } => Ok(Box::new(Sphere::new( center, radius, Arc::clone( materials .get(&hc.material_name) .ok_or(ConfigError::UnknownMaterial(hc.material_name))?, ), ))), Hitables::Cuboid { min, max } => Ok(Box::new(Cuboid::new( min.into(), max.into(), Arc::clone( materials .get(&hc.material_name) .ok_or(ConfigError::UnknownMaterial(hc.material_name))?, ), ))), Hitables::STL { path, scale } => { let r = BufReader::new(File::open(path)?); let stl = STL::parse(r, false)?; Ok(Box::new(BVHTriangles::new( &stl, Arc::clone( materials .get(&hc.material_name) .ok_or(ConfigError::UnknownMaterial(hc.material_name))?, ), scale.unwrap_or(1.), ))) } } }) .collect(); let hitables = hitables?; let world: Box = Box::new(HitableList::new(hitables)); let mut env_map: Option = None; if let Some(em) = c.envmap { let im = image::open(em.path)?.into_rgb(); env_map = Some(EnvMap::new(im)); }; let camera = make_camera(&c.camera, c.scene.width, c.scene.height); let scene = Scene { world, camera, env_map, subsamples: c.scene.subsamples.unwrap_or(8), adaptive_subsampling: c.scene.adaptive_subsampling, num_threads: c.scene.num_threads, width: c.scene.width, height: c.scene.height, global_illumination: c.scene.global_illumination.unwrap_or(true), }; Ok(scene) } } fn make_camera(cfg: &CameraConfig, width: usize, height: usize) -> Camera { Camera::new( cfg.lookfrom.into(), cfg.lookat.into(), Vec3::new(0., 1., 0.), cfg.fov, width as f32 / height as f32, cfg.aperture, cfg.focus_dist, cfg.time_min, cfg.time_max, ) } #[derive(Debug, Deserialize)] struct SceneConfig { subsamples: Option, adaptive_subsampling: Option, num_threads: Option, width: usize, height: usize, global_illumination: Option, } #[derive(Debug, Deserialize)] #[serde(tag = "type")] struct HitableConfig { material_name: String, #[serde(flatten)] hitable: Hitables, } #[derive(Debug, Deserialize)] #[serde(tag = "type")] enum Hitables { #[serde(rename = "sphere")] Sphere { center: [f32; 3], radius: f32 }, #[serde(rename = "cuboid")] Cuboid { min: [f32; 3], max: [f32; 3] }, #[serde(rename = "stl")] STL { path: PathBuf, scale: Option }, } #[derive(Debug, Deserialize)] struct MaterialConfig { name: String, #[serde(flatten)] material: Materials, } #[derive(Debug, Deserialize)] #[serde(tag = "type")] enum Materials { #[serde(rename = "metal")] Metal { albedo: [f32; 3], fuzzy: f32 }, #[serde(rename = "dielectric")] Dielectric { ref_idx: f32 }, // TODO(wathiede): these all take Textures, for now, only support RGB #[serde(rename = "diffuse_light")] DiffuseLight { texture: [f32; 3] }, #[serde(rename = "isotropic")] Isotropic { texture: [f32; 3] }, #[serde(rename = "lambertian")] Lambertian { texture: [f32; 3] }, } #[derive(Debug, Deserialize)] pub struct CameraConfig { lookfrom: [f32; 3], lookat: [f32; 3], fov: f32, aperture: f32, focus_dist: f32, time_min: f32, time_max: f32, } #[derive(Debug, Deserialize)] struct EnvMapConfig { path: PathBuf, }