use std::fmt::Debug; use std::sync::Arc; use log::warn; use serde::Deserialize; use crate::{ camera::Camera, objects::{ cube::Cube, materials::{ dielectric::Dielectric, lambertian::{Lambertian, Metal}, traits::Material, }, quad::Quad, sphere::Sphere, traits::Hittable, triangle::Triangle, }, scenes::raw_camera::RawCamera, vec3::Vec3, }; pub struct Scene { pub camera: Camera, pub materials: Vec>, pub objects: Vec>, } impl Debug for Scene { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Scene") .field("camera", &self.camera) .field("materials", &self.materials) .field("objects", &self.objects) .finish() } } impl Scene { pub fn new(image_width: u32, image_height: u32) -> Self { Self { camera: Camera::new(image_width, image_height), materials: vec![], objects: vec![], } } pub fn camera(&mut self) -> &mut Camera { &mut self.camera } pub fn add_hittable(&mut self, hittable: Arc) { self.objects.push(hittable); } pub fn render(&mut self) { self.camera.render(&self.objects); } } #[derive(Deserialize)] struct SceneDef { pub camera: RawCamera, pub materials: Vec, pub objects: Vec, } impl<'de> Deserialize<'de> for Scene { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { let conc = SceneDef::deserialize(deserializer)?; let mats: Vec> = conc .materials .into_iter() .map(MaterialDef::into_arc) .collect(); let objs: Vec> = conc .objects .into_iter() .filter_map(|h| h.into_arc(&mats)) .collect(); Ok(Self { camera: Camera::from(conc.camera), materials: mats, objects: objs, }) } } #[derive(Deserialize)] #[serde(untagged)] enum RawMaterial { Ref(u32), Direct(MaterialDef), } impl RawMaterial { pub fn into_arc(self, materials: &Vec>) -> Option> { match self { RawMaterial::Ref(r) => { if r as usize >= materials.len() { warn!("Sphere specified nonexistent material {}; skipping...", r); return None; } Some(materials.get(r as usize).unwrap().clone()) } RawMaterial::Direct(m) => Some(m.into_arc()), } } } #[derive(Deserialize)] struct RawSphere { pub center: Vec3, pub radius: f32, pub material: RawMaterial, } #[derive(Deserialize)] struct RawTriangle { pub p1: Vec3, pub p2: Vec3, pub p3: Vec3, pub material: RawMaterial, } #[derive(Deserialize)] struct RawQuad { pub p1: Vec3, pub p2: Vec3, pub p3: Vec3, pub p4: Vec3, pub material: RawMaterial, } #[derive(Deserialize)] struct RawCube { pub p1: Vec3, pub p2: Vec3, pub p3: Vec3, pub p4: Vec3, pub p5: Vec3, pub p6: Vec3, pub p7: Vec3, pub p8: Vec3, pub material: RawMaterial, } #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum HittableDef { Sphere(RawSphere), Triangle(RawTriangle), Quad(RawQuad), Cube(RawCube), } impl HittableDef { fn into_arc(self, materials: &Vec>) -> Option> { // THOUGHT: i think this can be done better; in the map/filter call up there? match self { HittableDef::Sphere(s) => { let material = s.material.into_arc(materials); if let Some(m) = material { Some(Arc::new(Sphere::new(s.center, s.radius, m))) } else { None } } HittableDef::Triangle(t) => { let material = t.material.into_arc(materials); if let Some(m) = material { Some(Arc::new(Triangle::new(t.p1, t.p2, t.p3, m))) } else { None } } HittableDef::Quad(q) => { let material = q.material.into_arc(materials); if let Some(m) = material { Some(Arc::new(Quad::new(q.p1, q.p2, q.p3, q.p4, m))) } else { None } } HittableDef::Cube(c) => { let material = c.material.into_arc(materials); if let Some(m) = material { Some(Arc::new(Cube::new( c.p1, c.p2, c.p3, c.p4, c.p5, c.p6, c.p7, c.p8, m, ))) } else { None } } } } } #[derive(Deserialize)] #[serde(tag = "type", rename_all = "lowercase")] enum MaterialDef { Lambertian(Lambertian), Metal(Metal), Dielectric(Dielectric), } impl MaterialDef { fn into_arc(self) -> Arc { match self { MaterialDef::Lambertian(l) => Arc::new(l), MaterialDef::Metal(m) => Arc::new(m), MaterialDef::Dielectric(d) => Arc::new(d), } } }