2023-09-21 10:25:23 -07:00

207 lines
6.3 KiB
Rust

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<MaterialConfig>,
hitables: Vec<HitableConfig>,
envmap: Option<EnvMapConfig>,
}
#[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<Config> for Scene {
type Error = ConfigError;
fn try_from(c: Config) -> Result<Scene, Self::Error> {
let mut materials = HashMap::new();
for mc in c.materials {
let v: Arc<dyn Material> = 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<Vec<Box<dyn Hit>>, Self::Error> = c
.hitables
.into_iter()
.map(|hc| -> Result<Box<dyn Hit>, 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<dyn Hit> = Box::new(HitableList::new(hitables));
let mut env_map: Option<EnvMap> = 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<usize>,
adaptive_subsampling: Option<f32>,
num_threads: Option<usize>,
width: usize,
height: usize,
global_illumination: Option<bool>,
}
#[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<f32> },
}
#[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,
}