418 lines
17 KiB
Rust
418 lines
17 KiB
Rust
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<Film>,
|
|
pub medium: Option<Arc<Medium>>,
|
|
}
|
|
|
|
impl CameraBaseParameters {
|
|
pub fn new(
|
|
camera_transform: &CameraTransform,
|
|
film: Arc<Film>,
|
|
medium: Option<Arc<Medium>>,
|
|
params: &ParameterDictionary,
|
|
loc: &FileLoc,
|
|
) -> Result<Self> {
|
|
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<Arc<Medium>>,
|
|
film: Arc<Film>,
|
|
loc: &FileLoc,
|
|
arena: &Arena,
|
|
) -> Result<Self>
|
|
where
|
|
Self: Sized;
|
|
}
|
|
|
|
impl CameraFactory for Camera {
|
|
fn create(
|
|
name: &str,
|
|
params: &ParameterDictionary,
|
|
camera_transform: &CameraTransform,
|
|
medium: Option<Arc<Medium>>,
|
|
film: Arc<Film>,
|
|
loc: &FileLoc,
|
|
arena: &Arena,
|
|
) -> Result<Self>
|
|
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<HostImage> = 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)),
|
|
}
|
|
}
|
|
}
|