207 lines
6.3 KiB
Rust
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,
|
|
}
|