use crate::core::image::{HostImage, ImageIO, ImageMetadata}; use crate::globals::get_options; use crate::utils::read_float_file; use crate::{Arena, FileLoc, ParameterDictionary, ArenaUpload}; use anyhow::{anyhow, Result}; use shared::cameras::*; use shared::core::camera::{Camera, CameraBase, CameraTrait, CameraTransform}; use shared::core::color::SRGB; use shared::core::film::Film; use shared::core::geometry::{Bounds2f, Point2f, Point2i, Vector2f, Vector3f}; use shared::core::image::PixelFormat; use shared::core::medium::Medium; use shared::utils::math::square; use shared::{Float, Ptr, PI}; use std::path::Path; use std::sync::Arc; #[repr(C)] #[derive(Clone, Debug)] pub struct CameraBaseParameters { pub camera_transform: CameraTransform, pub shutter_open: Float, pub shutter_close: Float, pub film: Arc, pub medium: Option>, } impl CameraBaseParameters { pub fn new( camera_transform: &CameraTransform, film: Arc, medium: Option>, params: &ParameterDictionary, loc: &FileLoc, ) -> Result { let mut shutter_open = params.get_one_float("shutteropen", 0.)?; let mut shutter_close = params.get_one_float("shutterclose", 1.)?; if shutter_close < shutter_open { eprint!( "{}: Shutter close time {} < shutter open {}. Swapping", loc, shutter_close, shutter_open ); std::mem::swap(&mut shutter_open, &mut shutter_close); } Ok(CameraBaseParameters { camera_transform: camera_transform.clone(), shutter_open, shutter_close, film, medium, }) } } pub trait CameraBaseFactory { fn create(p: CameraBaseParameters) -> CameraBase { CameraBase { camera_transform: p.camera_transform.clone(), shutter_open: p.shutter_open, shutter_close: p.shutter_close, film: Ptr::from(p.film.clone().as_ref()), medium: match p.medium { Some(ref m) => Ptr::from(m.as_ref()), None => Ptr::null(), }, min_pos_differential_x: Vector3f::default(), min_pos_differential_y: Vector3f::default(), min_dir_differential_x: Vector3f::default(), min_dir_differential_y: Vector3f::default(), } } } impl CameraBaseFactory for CameraBase {} pub trait InitMetadata { fn init_metadata(&self, metadata: &mut ImageMetadata); } impl InitMetadata for CameraBase { fn init_metadata(&self, metadata: &mut ImageMetadata) { let camera_from_world = self.camera_transform.camera_from_world(self.shutter_open); metadata.camera_from_world = Some(camera_from_world.get_matrix()); } } impl InitMetadata for Camera { fn init_metadata(&self, metadata: &mut ImageMetadata) { self.base().init_metadata(metadata); } } pub trait CameraFactory { fn create( name: &str, params: &ParameterDictionary, camera_transform: &CameraTransform, medium: Option>, film: Arc, loc: &FileLoc, arena: &Arena, ) -> Result where Self: Sized; } impl CameraFactory for Camera { fn create( name: &str, params: &ParameterDictionary, camera_transform: &CameraTransform, medium: Option>, film: Arc, loc: &FileLoc, arena: &Arena, ) -> Result where Self: Sized, { match name { "perspective" => { let full_res = film.full_resolution(); let camera_params = CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let lens_radius = params.get_one_float("lensradius", 0.)?; let focal_distance = params.get_one_float("focaldistance", 1e6)?; let frame = params.get_one_float( "frameaspectratio", full_res.x() as Float / full_res.y() as Float, )?; let mut screen = if frame > 1. { Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) } else { Bounds2f::from_points( Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame), ) }; let sw = params.get_float_array("screenwindow")?; if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); } else { if sw.len() == 4 { screen = Bounds2f::from_points( Point2f::new(sw[0], sw[2]), Point2f::new(sw[1], sw[3]), ); } else { return Err(anyhow!( "{}: screenwindow param must have four values", loc )); } } } let fov = params.get_one_float("fov", 90.)?; let camera = PerspectiveCamera::new(base, fov, screen, lens_radius, focal_distance); arena.alloc(camera); Ok(Camera::Perspective(camera)) } "orthographic" => { let full_res = film.full_resolution(); let camera_params = CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let lens_radius = params.get_one_float("lensradius", 0.)?; let focal_distance = params.get_one_float("focaldistance", 1e6)?; let frame = params.get_one_float( "frameaspectratio", full_res.x() as Float / full_res.y() as Float, )?; let mut screen = if frame > 1. { Bounds2f::from_points(Point2f::new(-frame, -1.), Point2f::new(frame, 1.)) } else { Bounds2f::from_points( Point2f::new(-1., -1. / frame), Point2f::new(1., 1. / frame), ) }; let sw = params.get_float_array("screenwindow")?; if !sw.is_empty() { if get_options().fullscreen { eprint!("Screenwindow is ignored in fullscreen mode"); } else { if sw.len() == 4 { screen = Bounds2f::from_points( Point2f::new(sw[0], sw[2]), Point2f::new(sw[1], sw[3]), ); } else { return Err(anyhow!( "{}: screenwindow param must have four values", loc )); } } } let camera = OrthographicCamera::new(base, screen, lens_radius, focal_distance); arena.alloc(camera); Ok(Camera::Orthographic(camera)) } "realistic" => { let camera_params = CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let aperture_diameter = params.get_one_float("aperturediameter", 1.)?; let focal_distance = params.get_one_float("focaldistance", 10.)?; let lens_file = params.get_one_string("lensfile", "")?; if lens_file.is_empty() { return Err(anyhow!("{}: No lens file supplied", loc)); } let lens_params = read_float_file(lens_file.as_str()).map_err(|e| anyhow!(e))?; if lens_params.len() % 4 != 0 { return Err(anyhow!( "{}: excess values in lens specification file; must be multiple-of-four values, read {}", loc, lens_params.len() )); } let builtin_res = 256; let rasterize = |vert: &[Point2f]| -> HostImage { let mut image = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], SRGB.into(), ); let res = image.resolution(); for y in 0..res.y() { for x in 0..res.x() { // Map pixel to [-1, 1] range let p = Point2f::new( -1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float, -1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float, ); let mut winding_number = 0; // Winding number test against edges for i in 0..vert.len() { let i1 = (i + 1) % vert.len(); let v_i = vert[i]; let v_i1 = vert[i1]; let e = (p.x() - v_i.x()) * (v_i1.y() - v_i.y()) - (p.y() - v_i.y()) * (v_i1.x() - v_i.x()); if v_i.y() <= p.y() { if v_i1.y() > p.y() && e > 0.0 { winding_number += 1; } } else if v_i1.y() <= p.y() && e < 0.0 { winding_number -= 1; } } image.set_channel( Point2i::new(x, y), 0, if winding_number == 0 { 0.0 } else { 1.0 }, ); } } image }; let aperture_name = params.get_one_string("aperture", "")?; let mut aperture_image: Option = None; if !aperture_name.is_empty() { match aperture_name.as_str() { "gaussian" => { let mut img = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], SRGB.into(), ); let res = img.resolution(); for y in 0..res.y() { for x in 0..res.x() { let uv = Point2f::new( -1.0 + 2.0 * (x as Float + 0.5) / res.x() as Float, -1.0 + 2.0 * (y as Float + 0.5) / res.y() as Float, ); let r2 = square(uv.x()) + square(uv.y()); let sigma2 = 1.0; let v = ((-r2 / sigma2).exp() - (-1.0 / sigma2).exp()).max(0.0); img.set_channel(Point2i::new(x, y), 0, v); } } aperture_image = Some(img); } "square" => { let mut img = HostImage::new( PixelFormat::F32, Point2i::new(builtin_res, builtin_res), &["Y"], SRGB.into(), ); let low = (0.25 * builtin_res as Float) as i32; let high = (0.75 * builtin_res as Float) as i32; for y in low..high { for x in low..high { img.inner.set_channel(Point2i::new(x, y), 0, 4.0); } } aperture_image = Some(img); } "pentagon" => { let c1 = (5.0f32.sqrt() - 1.0) / 4.0; let c2 = (5.0f32.sqrt() + 1.0) / 4.0; let s1 = (10.0 + 2.0 * 5.0f32.sqrt()).sqrt() / 4.0; let s2 = (10.0 - 2.0 * 5.0f32.sqrt()).sqrt() / 4.0; let mut vert = [ Point2f::new(0.0, 1.0), Point2f::new(s1, c1), Point2f::new(s2, -c2), Point2f::new(-s2, -c2), Point2f::new(-s1, c1), ]; for v in vert.iter_mut() { *v = Point2f::from(Vector2f::from(*v) * 0.8); } aperture_image = Some(rasterize(&vert)); } "star" => { let mut vert = Vec::with_capacity(10); for i in 0..10 { let r = if i % 2 == 1 { 1.0 } else { (72.0f32.to_radians().cos()) / (36.0f32.to_radians().cos()) }; let angle = PI * i as Float / 5.0; vert.push(Point2f::new(r * angle.cos(), r * angle.sin())); } vert.reverse(); aperture_image = Some(rasterize(&vert)); } _ => { if let Ok(im) = HostImage::read(Path::new(&aperture_name), None) { if im.image.n_channels() > 1 { let mut mono = HostImage::new( PixelFormat::F32, im.image.resolution(), &["Y"], SRGB.into(), ); let res = mono.resolution(); for y in 0..res.y() { for x in 0..res.x() { let avg = im.image.get_channels(Point2i::new(x, y)).average(); mono.inner.set_channel(Point2i::new(x, y), 0, avg); } } aperture_image = Some(mono); } else { aperture_image = Some(im.image); } } } } } let camera = RealisticCamera::new( base, &lens_params, focal_distance, aperture_diameter, arena.upload(aperture_image) ); arena.alloc(camera); Ok(Camera::Realistic(camera)) } "spherical" => { let _full_res = film.full_resolution(); let camera_params = CameraBaseParameters::new(camera_transform, film, medium, params, loc)?; let base = CameraBase::create(camera_params); let m = params.get_one_string("mapping", "equalarea")?; let mapping = match m.as_str() { "equalarea" => Mapping::EqualArea, "equirectangular" => Mapping::EquiRectangular, _ => { return Err(anyhow!( "{}: unknown mapping for spherical camera at {}", m, loc )); } }; let camera = SphericalCamera { mapping, base }; arena.alloc(camera); Ok(Camera::Spherical(camera)) } _ => Err(anyhow!("Camera type '{}' unknown at {}", name, loc)), } } }