Added shapes, starting work on interactions and materials
This commit is contained in:
parent
21a2c0c674
commit
d58203e97a
53 changed files with 17210 additions and 5004 deletions
|
|
@ -1,18 +1,18 @@
|
||||||
mod perspective;
|
|
||||||
mod orthographic;
|
mod orthographic;
|
||||||
mod spherical;
|
mod perspective;
|
||||||
mod realistic;
|
mod realistic;
|
||||||
|
mod spherical;
|
||||||
|
|
||||||
pub use perspective::PerspectiveCamera;
|
|
||||||
pub use orthographic::OrthographicCamera;
|
pub use orthographic::OrthographicCamera;
|
||||||
pub use spherical::SphericalCamera;
|
pub use perspective::PerspectiveCamera;
|
||||||
pub use realistic::RealisticCamera;
|
pub use realistic::RealisticCamera;
|
||||||
|
pub use spherical::SphericalCamera;
|
||||||
|
|
||||||
use crate::core::film::Film;
|
use crate::core::film::Film;
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::pbrt::{Float, lerp, RenderingCoordinateSystem};
|
use crate::core::pbrt::{Float, RenderingCoordinateSystem, lerp};
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::utils::geometry::{Ray, RayDifferential, Vector3f, Point3f, Normal3f, Dot, Normed};
|
use crate::geometry::{Normal3f, Point3f, Ray, RayDifferential, Vector3f, VectorLike};
|
||||||
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::transform::{AnimatedTransform, Transform};
|
use crate::utils::transform::{AnimatedTransform, Transform};
|
||||||
|
|
||||||
|
|
@ -30,7 +30,10 @@ pub struct CameraTransform {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CameraTransform {
|
impl CameraTransform {
|
||||||
pub fn from_world(world_from_camera: AnimatedTransform, rendering_space: RenderingCoordinateSystem) -> Self {
|
pub fn from_world(
|
||||||
|
world_from_camera: AnimatedTransform,
|
||||||
|
rendering_space: RenderingCoordinateSystem,
|
||||||
|
) -> Self {
|
||||||
let world_from_render = match rendering_space {
|
let world_from_render = match rendering_space {
|
||||||
RenderingCoordinateSystem::Camera => {
|
RenderingCoordinateSystem::Camera => {
|
||||||
let t_mid = (world_from_camera.start_time + world_from_camera.end_time) / 2.;
|
let t_mid = (world_from_camera.start_time + world_from_camera.end_time) / 2.;
|
||||||
|
|
@ -44,10 +47,20 @@ impl CameraTransform {
|
||||||
RenderingCoordinateSystem::World => Transform::identity(),
|
RenderingCoordinateSystem::World => Transform::identity(),
|
||||||
};
|
};
|
||||||
let render_from_world = world_from_render.inverse();
|
let render_from_world = world_from_render.inverse();
|
||||||
let rfc = [render_from_world * world_from_camera.start_transform, render_from_world * world_from_camera.end_transform];
|
let rfc = [
|
||||||
let render_from_camera = AnimatedTransform::new(&rfc[0], world_from_camera.start_time, &rfc[1], world_from_camera.end_time).expect("Render wrong");
|
render_from_world * world_from_camera.start_transform,
|
||||||
Self { render_from_camera, world_from_render }
|
render_from_world * world_from_camera.end_transform,
|
||||||
|
];
|
||||||
|
let render_from_camera = AnimatedTransform::new(
|
||||||
|
&rfc[0],
|
||||||
|
world_from_camera.start_time,
|
||||||
|
&rfc[1],
|
||||||
|
world_from_camera.end_time,
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
render_from_camera,
|
||||||
|
world_from_render,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_from_camera(&self, p: Point3f, time: Float) -> Point3f {
|
pub fn render_from_camera(&self, p: Point3f, time: Float) -> Point3f {
|
||||||
|
|
@ -90,7 +103,11 @@ pub struct CameraBase {
|
||||||
pub trait CameraTrait {
|
pub trait CameraTrait {
|
||||||
fn base(&self) -> &CameraBase;
|
fn base(&self) -> &CameraBase;
|
||||||
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
|
fn generate_ray(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay>;
|
||||||
fn generate_ray_differential(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray_differential(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
let mut central_cam_ray = match self.generate_ray(sample, lambda) {
|
let mut central_cam_ray = match self.generate_ray(sample, lambda) {
|
||||||
Some(cr) => cr,
|
Some(cr) => cr,
|
||||||
None => return None,
|
None => return None,
|
||||||
|
|
@ -105,8 +122,10 @@ pub trait CameraTrait {
|
||||||
s_shift.p_film[0] += eps;
|
s_shift.p_film[0] += eps;
|
||||||
|
|
||||||
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
if let Some(rx_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||||
rd.rx_origin = central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
rd.rx_origin =
|
||||||
rd.rx_direction = central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
central_cam_ray.ray.o + (rx_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||||
|
rd.rx_direction =
|
||||||
|
central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||||
rx_found = true;
|
rx_found = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -117,10 +136,12 @@ pub trait CameraTrait {
|
||||||
s_shift.p_film[1] += eps;
|
s_shift.p_film[1] += eps;
|
||||||
|
|
||||||
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
if let Some(ry_cam_ray) = self.generate_ray(s_shift, lambda) {
|
||||||
rd.ry_origin = central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
rd.ry_origin =
|
||||||
rd.ry_direction = central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
central_cam_ray.ray.o + (ry_cam_ray.ray.o - central_cam_ray.ray.o) / eps;
|
||||||
|
rd.ry_direction =
|
||||||
|
central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
|
||||||
ry_found = true;
|
ry_found = true;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,32 +156,61 @@ pub trait CameraTrait {
|
||||||
lerp(u, self.base().shutter_open, self.base().shutter_close)
|
lerp(u, self.base().shutter_open, self.base().shutter_close)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn approximate_dp_dxy(&self, p: Point3f, n: Normal3f, time: Float, samples_per_pixel: i32, dpdx: &mut Vector3f, dpdy: &mut Vector3f) {
|
fn approximate_dp_dxy(
|
||||||
|
&self,
|
||||||
|
p: Point3f,
|
||||||
|
n: Normal3f,
|
||||||
|
time: Float,
|
||||||
|
samples_per_pixel: i32,
|
||||||
|
dpdx: &mut Vector3f,
|
||||||
|
dpdy: &mut Vector3f,
|
||||||
|
) {
|
||||||
let p_camera = self.base().camera_transform.camera_from_render(p, time);
|
let p_camera = self.base().camera_transform.camera_from_render(p, time);
|
||||||
let p_camera_vec = p_camera - Point3f::new(0., 0., 0.);
|
let p_camera_vec = p_camera - Point3f::new(0., 0., 0.);
|
||||||
let down_z_from_camera = Transform::rotate_from_to(p_camera_vec.normalize(), Vector3f::new(1., 0., 0.));
|
let down_z_from_camera =
|
||||||
|
Transform::rotate_from_to(p_camera_vec.normalize(), Vector3f::new(1., 0., 0.));
|
||||||
let p_down_z = down_z_from_camera.apply_to_point(p_camera);
|
let p_down_z = down_z_from_camera.apply_to_point(p_camera);
|
||||||
let n_down_z = down_z_from_camera.apply_to_normal(self.base().camera_transform.camera_from_render_normal(n, time));
|
let n_down_z = down_z_from_camera.apply_to_normal(
|
||||||
|
self.base()
|
||||||
|
.camera_transform
|
||||||
|
.camera_from_render_normal(n, time),
|
||||||
|
);
|
||||||
let d = n_down_z.z() * p_down_z.z();
|
let d = n_down_z.z() * p_down_z.z();
|
||||||
let x_ray = Ray::new(Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
|
let x_ray = Ray::new(
|
||||||
|
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_x,
|
||||||
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
|
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_x,
|
||||||
None, None);
|
None,
|
||||||
let y_ray = Ray::new(Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
|
None,
|
||||||
|
);
|
||||||
|
let y_ray = Ray::new(
|
||||||
|
Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
|
||||||
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
|
Vector3f::new(0., 0., 1.) + self.base().min_dir_differential_y,
|
||||||
None, None);
|
None,
|
||||||
let tx = -(n_down_z.dot(y_ray.o)) / n_down_z.dot(x_ray.d);
|
None,
|
||||||
let ty = -(n_down_z.dot(x_ray.o) - d) / n_down_z.dot(y_ray.d);
|
);
|
||||||
|
let n_down = Vector3f::from(n_down_z);
|
||||||
|
let tx = -(n_down.dot(y_ray.o.into())) / n_down.dot(x_ray.d);
|
||||||
|
let ty = -(n_down.dot(x_ray.o.into()) - d) / n_down.dot(y_ray.d);
|
||||||
let px = x_ray.evaluate(tx);
|
let px = x_ray.evaluate(tx);
|
||||||
let py = y_ray.evaluate(ty);
|
let py = y_ray.evaluate(ty);
|
||||||
let spp_scale = 0.125_f32.max((samples_per_pixel as Float).sqrt());
|
let spp_scale = 0.125_f32.max((samples_per_pixel as Float).sqrt());
|
||||||
*dpdx = spp_scale * self.base().camera_transform.render_from_camera_vector(down_z_from_camera.apply_inverse_vector(px - p_down_z), time);
|
*dpdx = spp_scale
|
||||||
*dpdy = spp_scale * self.base().camera_transform.render_from_camera_vector(down_z_from_camera.apply_inverse_vector(py - p_down_z), time);
|
* self.base().camera_transform.render_from_camera_vector(
|
||||||
|
down_z_from_camera.apply_inverse_vector(px - p_down_z),
|
||||||
|
time,
|
||||||
|
);
|
||||||
|
*dpdy = spp_scale
|
||||||
|
* self.base().camera_transform.render_from_camera_vector(
|
||||||
|
down_z_from_camera.apply_inverse_vector(py - p_down_z),
|
||||||
|
time,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
|
fn render_from_camera(&self, r: &Ray, t_max: &mut Option<Float>) -> Ray {
|
||||||
self.base().camera_transform.render_from_camera_ray(r, t_max)
|
self.base()
|
||||||
|
.camera_transform
|
||||||
|
.render_from_camera_ray(r, t_max)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Camera {
|
pub enum Camera {
|
||||||
|
|
@ -188,7 +238,11 @@ impl CameraTrait for Camera {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray_differential(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray_differential(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
match self {
|
match self {
|
||||||
Camera::Perspective(c) => c.generate_ray_differential(sample, lambda),
|
Camera::Perspective(c) => c.generate_ray_differential(sample, lambda),
|
||||||
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
|
Camera::Orthographic(c) => c.generate_ray_differential(sample, lambda),
|
||||||
|
|
@ -205,7 +259,7 @@ pub struct LensElementInterface {
|
||||||
pub aperture_radius: Float,
|
pub aperture_radius: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ExitPupilSample {
|
pub struct ExitPupilSample {
|
||||||
pub p_pupil: Point3f,
|
pub p_pupil: Point3f,
|
||||||
pub pdf: Float,
|
pub pdf: Float,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use super::{CameraBase, CameraRay, CameraTrait};
|
use super::{CameraBase, CameraRay, CameraTrait};
|
||||||
use crate::core::sampler::CameraSample;
|
|
||||||
use crate::core::pbrt::{Float, sample_uniform_disk_concentric};
|
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::FilmTrait;
|
||||||
use crate::utils::transform::Transform;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::geometry::{Bounds2f, Point3f, Vector3f, Ray, RayDifferential, Normed};
|
use crate::core::sampler::CameraSample;
|
||||||
|
use crate::geometry::{
|
||||||
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
||||||
|
use crate::utils::transform::Transform;
|
||||||
|
|
||||||
pub struct OrthographicCamera {
|
pub struct OrthographicCamera {
|
||||||
pub base: CameraBase,
|
pub base: CameraBase,
|
||||||
|
|
@ -19,12 +22,26 @@ pub struct OrthographicCamera {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrthographicCamera {
|
impl OrthographicCamera {
|
||||||
pub fn new(base: CameraBase, screen_window: Bounds2f, lens_radius: Float, focal_distance: Float) -> Self {
|
pub fn new(
|
||||||
|
base: CameraBase,
|
||||||
|
screen_window: Bounds2f,
|
||||||
|
lens_radius: Float,
|
||||||
|
focal_distance: Float,
|
||||||
|
) -> Self {
|
||||||
let ndc_from_screen: Transform<Float> = Transform::scale(
|
let ndc_from_screen: Transform<Float> = Transform::scale(
|
||||||
1./(screen_window.p_max.x() - screen_window.p_min.x()),
|
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
||||||
1. / (screen_window.p_max.y() - screen_window.p_min.y()), 1.) *
|
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
||||||
Transform::translate(Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.));
|
1.,
|
||||||
let raster_from_ndc = Transform::scale(base.film.full_resolution().x() as Float, -base.film.full_resolution().y() as Float, 1.);
|
) * Transform::translate(Vector3f::new(
|
||||||
|
-screen_window.p_min.x(),
|
||||||
|
-screen_window.p_max.y(),
|
||||||
|
0.,
|
||||||
|
));
|
||||||
|
let raster_from_ndc = Transform::scale(
|
||||||
|
base.film.full_resolution().x() as Float,
|
||||||
|
-base.film.full_resolution().y() as Float,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
||||||
let screen_from_raster = raster_from_screen.inverse();
|
let screen_from_raster = raster_from_screen.inverse();
|
||||||
let screen_from_camera = Transform::orthographic(0., 1.);
|
let screen_from_camera = Transform::orthographic(0., 1.);
|
||||||
|
|
@ -36,25 +53,45 @@ impl OrthographicCamera {
|
||||||
base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.);
|
base_ortho.min_dir_differential_y = Vector3f::new(0., 0., 0.);
|
||||||
base_ortho.min_pos_differential_x = dx_camera;
|
base_ortho.min_pos_differential_x = dx_camera;
|
||||||
base_ortho.min_pos_differential_y = dy_camera;
|
base_ortho.min_pos_differential_y = dy_camera;
|
||||||
Self { base: base_ortho, screen_from_camera, camera_from_raster, raster_from_screen, screen_from_raster, lens_radius, focal_distance, dx_camera, dy_camera }
|
Self {
|
||||||
|
base: base_ortho,
|
||||||
|
screen_from_camera,
|
||||||
|
camera_from_raster,
|
||||||
|
raster_from_screen,
|
||||||
|
screen_from_raster,
|
||||||
|
lens_radius,
|
||||||
|
focal_distance,
|
||||||
|
dx_camera,
|
||||||
|
dy_camera,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl CameraTrait for OrthographicCamera {
|
impl CameraTrait for OrthographicCamera {
|
||||||
fn base(&self) -> &CameraBase {
|
fn base(&self) -> &CameraBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
fn generate_ray(&self, sample: CameraSample, _lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
_lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
// Compute raster and camera sample positions
|
// Compute raster and camera sample positions
|
||||||
let p_film = Point3f::new(sample.p_film.x(), sample.p_film.y(), 0.);
|
let p_film = Point3f::new(sample.p_film.x(), sample.p_film.y(), 0.);
|
||||||
let p_camera = self.camera_from_raster.apply_to_point(p_film);
|
let p_camera = self.camera_from_raster.apply_to_point(p_film);
|
||||||
|
|
||||||
let mut ray = Ray::new(p_camera, Vector3f::new(0., 0., 1.), Some(self.sample_time(sample.time)), self.base().medium.clone());
|
let mut ray = Ray::new(
|
||||||
|
p_camera,
|
||||||
|
Vector3f::new(0., 0., 1.),
|
||||||
|
Some(self.sample_time(sample.time)),
|
||||||
|
self.base().medium.clone(),
|
||||||
|
);
|
||||||
// Modify ray for depth of field
|
// Modify ray for depth of field
|
||||||
if self.lens_radius > 0. {
|
if self.lens_radius > 0. {
|
||||||
// Sample point on lens
|
// Sample point on lens
|
||||||
let p_lens = self.lens_radius * sample_uniform_disk_concentric(sample.p_lens);
|
let p_lens_vec =
|
||||||
|
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
|
||||||
|
let p_lens = Point2f::from(p_lens_vec);
|
||||||
|
|
||||||
// Compute point on plane of focus
|
// Compute point on plane of focus
|
||||||
let ft = self.focal_distance / ray.d.z();
|
let ft = self.focal_distance / ray.d.z();
|
||||||
|
|
@ -67,10 +104,17 @@ impl CameraTrait for OrthographicCamera {
|
||||||
|
|
||||||
let camera_ray = self.render_from_camera(&ray, &mut None);
|
let camera_ray = self.render_from_camera(&ray, &mut None);
|
||||||
|
|
||||||
Some(CameraRay{ray: camera_ray, weight: SampledSpectrum::default()})
|
Some(CameraRay {
|
||||||
|
ray: camera_ray,
|
||||||
|
weight: SampledSpectrum::default(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray_differential(&self, sample: CameraSample, lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray_differential(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
let mut central_cam_ray = match self.generate_ray(sample, lambda) {
|
let mut central_cam_ray = match self.generate_ray(sample, lambda) {
|
||||||
Some(cr) => cr,
|
Some(cr) => cr,
|
||||||
None => return None,
|
None => return None,
|
||||||
|
|
@ -81,8 +125,14 @@ impl CameraTrait for OrthographicCamera {
|
||||||
return self.generate_ray_differential(sample, lambda);
|
return self.generate_ray_differential(sample, lambda);
|
||||||
} else {
|
} else {
|
||||||
let time = self.sample_time(sample.time);
|
let time = self.sample_time(sample.time);
|
||||||
let world_dx = self.base.camera_transform.render_from_camera_vector(self.dx_camera, time);
|
let world_dx = self
|
||||||
let world_dy = self.base.camera_transform.render_from_camera_vector(self.dy_camera, time);
|
.base
|
||||||
|
.camera_transform
|
||||||
|
.render_from_camera_vector(self.dx_camera, time);
|
||||||
|
let world_dy = self
|
||||||
|
.base
|
||||||
|
.camera_transform
|
||||||
|
.render_from_camera_vector(self.dy_camera, time);
|
||||||
|
|
||||||
rd.rx_origin = central_cam_ray.ray.o + world_dx;
|
rd.rx_origin = central_cam_ray.ray.o + world_dx;
|
||||||
rd.ry_origin = central_cam_ray.ray.o + world_dy;
|
rd.ry_origin = central_cam_ray.ray.o + world_dy;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
use super::{CameraRay, CameraBase, CameraTrait};
|
use super::{CameraBase, CameraRay, CameraTrait};
|
||||||
use crate::camera;
|
use crate::camera;
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::FilmTrait;
|
||||||
use crate::core::filter::FilterTrait;
|
use crate::core::filter::FilterTrait;
|
||||||
use crate::core::pbrt::{Float, sample_uniform_disk_concentric};
|
use crate::core::pbrt::Float;
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::utils::geometry::{Point2f, Point3f, Bounds2f, Vector3f, Normed, Ray, RayDifferential};
|
use crate::geometry::{
|
||||||
|
Bounds2f, Point2f, Point3f, Ray, RayDifferential, Vector2f, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::Transform;
|
||||||
|
|
||||||
|
|
@ -22,23 +25,42 @@ pub struct PerspectiveCamera {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PerspectiveCamera {
|
impl PerspectiveCamera {
|
||||||
pub fn new(base: CameraBase, screen_from_camera: &Transform<Float>, screen_window: Bounds2f, lens_radius: Float, focal_distance: Float) -> Self {
|
pub fn new(
|
||||||
|
base: CameraBase,
|
||||||
|
screen_from_camera: &Transform<Float>,
|
||||||
|
screen_window: Bounds2f,
|
||||||
|
lens_radius: Float,
|
||||||
|
focal_distance: Float,
|
||||||
|
) -> Self {
|
||||||
let ndc_from_screen: Transform<Float> = Transform::scale(
|
let ndc_from_screen: Transform<Float> = Transform::scale(
|
||||||
1./(screen_window.p_max.x() - screen_window.p_min.x()),
|
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
|
||||||
1. / (screen_window.p_max.y() - screen_window.p_min.y()), 1.) *
|
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
|
||||||
Transform::translate(Vector3f::new(-screen_window.p_min.x(), -screen_window.p_max.y(), 0.));
|
1.,
|
||||||
let raster_from_ndc = Transform::scale(base.film.full_resolution().x() as Float, -base.film.full_resolution().y() as Float, 1.);
|
) * Transform::translate(Vector3f::new(
|
||||||
|
-screen_window.p_min.x(),
|
||||||
|
-screen_window.p_max.y(),
|
||||||
|
0.,
|
||||||
|
));
|
||||||
|
let raster_from_ndc = Transform::scale(
|
||||||
|
base.film.full_resolution().x() as Float,
|
||||||
|
-base.film.full_resolution().y() as Float,
|
||||||
|
1.,
|
||||||
|
);
|
||||||
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
let raster_from_screen = raster_from_ndc * ndc_from_screen;
|
||||||
let screen_from_raster = raster_from_screen.inverse();
|
let screen_from_raster = raster_from_screen.inverse();
|
||||||
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
|
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster.clone();
|
||||||
let dx_camera = camera_from_raster.apply_to_point(Point3f::new(1., 0., 0.)) - camera_from_raster.apply_to_point(Point3f::new(0., 0., 0.));
|
let dx_camera = camera_from_raster.apply_to_point(Point3f::new(1., 0., 0.))
|
||||||
let dy_camera = camera_from_raster.apply_to_point(Point3f::new(0., 1., 0.)) - camera_from_raster.apply_to_point(Point3f::new(0., 0., 0.));
|
- camera_from_raster.apply_to_point(Point3f::new(0., 0., 0.));
|
||||||
|
let dy_camera = camera_from_raster.apply_to_point(Point3f::new(0., 1., 0.))
|
||||||
|
- camera_from_raster.apply_to_point(Point3f::new(0., 0., 0.));
|
||||||
let radius = base.film.get_filter().radius();
|
let radius = base.film.get_filter().radius();
|
||||||
let p_corner = Point3f::new(-radius.x(), -radius.y(), 0.);
|
let p_corner = Point3f::new(-radius.x(), -radius.y(), 0.);
|
||||||
let w_corner_camera = (camera_from_raster.apply_to_point(p_corner) - Point3f::new(0., 0., 0.)).normalize();
|
let w_corner_camera =
|
||||||
|
(camera_from_raster.apply_to_point(p_corner) - Point3f::new(0., 0., 0.)).normalize();
|
||||||
let cos_total_width = w_corner_camera.z();
|
let cos_total_width = w_corner_camera.z();
|
||||||
Self { base,
|
Self {
|
||||||
screen_from_camera: *screen_from_camera,
|
base,
|
||||||
|
screen_from_camera: screen_from_camera.clone(),
|
||||||
camera_from_raster,
|
camera_from_raster,
|
||||||
raster_from_screen,
|
raster_from_screen,
|
||||||
screen_from_raster,
|
screen_from_raster,
|
||||||
|
|
@ -55,17 +77,27 @@ impl CameraTrait for PerspectiveCamera {
|
||||||
fn base(&self) -> &CameraBase {
|
fn base(&self) -> &CameraBase {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
fn generate_ray(&self, sample: CameraSample, _lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
_lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
// Compute raster and camera sample positions
|
// Compute raster and camera sample positions
|
||||||
let p_film = Point3f::new(sample.p_film.x(), sample.p_film.y(), 0.);
|
let p_film = Point3f::new(sample.p_film.x(), sample.p_film.y(), 0.);
|
||||||
let p_camera = self.camera_from_raster.apply_to_point(p_film);
|
let p_camera = self.camera_from_raster.apply_to_point(p_film);
|
||||||
let p_vector = p_camera - Point3f::new(0., 0., 0.);
|
let p_vector = p_camera - Point3f::new(0., 0., 0.);
|
||||||
|
|
||||||
let mut r = Ray::new(Point3f::new(0., 0., 0.), p_vector.normalize(), Some(self.sample_time(sample.time)), self.base().medium.clone());
|
let mut r = Ray::new(
|
||||||
|
Point3f::new(0., 0., 0.),
|
||||||
|
p_vector.normalize(),
|
||||||
|
Some(self.sample_time(sample.time)),
|
||||||
|
self.base().medium.clone(),
|
||||||
|
);
|
||||||
// Modify ray for depth of field
|
// Modify ray for depth of field
|
||||||
if self.lens_radius > 0. {
|
if self.lens_radius > 0. {
|
||||||
// Sample point on lens
|
// Sample point on lens
|
||||||
let p_lens = self.lens_radius * sample_uniform_disk_concentric(sample.p_lens);
|
let p_lens =
|
||||||
|
self.lens_radius * Vector2f::from(sample_uniform_disk_concentric(sample.p_lens));
|
||||||
|
|
||||||
// Compute point on plane of focus
|
// Compute point on plane of focus
|
||||||
let ft = self.focal_distance / r.d.z();
|
let ft = self.focal_distance / r.d.z();
|
||||||
|
|
@ -77,6 +109,9 @@ impl CameraTrait for PerspectiveCamera {
|
||||||
}
|
}
|
||||||
|
|
||||||
let ray = self.render_from_camera(&r, &mut None);
|
let ray = self.render_from_camera(&r, &mut None);
|
||||||
Some(CameraRay{ray, weight: SampledSpectrum::default()})
|
Some(CameraRay {
|
||||||
|
ray,
|
||||||
|
weight: SampledSpectrum::default(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,41 @@
|
||||||
use super::{CameraBase, LensElementInterface, CameraRay, CameraTrait, ExitPupilSample};
|
use super::{CameraBase, CameraRay, CameraTrait, ExitPupilSample, LensElementInterface};
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::FilmTrait;
|
||||||
use crate::core::pbrt::{Float, square, lerp};
|
use crate::core::pbrt::{Float, lerp};
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
use crate::utils::math::quadratic;
|
use crate::geometry::{
|
||||||
use crate::utils::geometry::{Bounds2f, Point2f, Point3f, Point2i, Normal3f, Vector2i, Vector3f, Ray, Normed, Dot};
|
Bounds2f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector2i, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
|
use crate::utils::image::Image;
|
||||||
|
use crate::utils::math::{quadratic, square};
|
||||||
use crate::utils::scattering::refract;
|
use crate::utils::scattering::refract;
|
||||||
use crate::utils::spectrum::{SampledWavelengths, SampledSpectrum};
|
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
||||||
|
|
||||||
pub struct RealisticCamera {
|
pub struct RealisticCamera {
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
// aperture_image: Image,
|
aperture_image: Image,
|
||||||
element_interface: Vec<LensElementInterface>,
|
element_interface: Vec<LensElementInterface>,
|
||||||
physical_extent: Bounds2f,
|
physical_extent: Bounds2f,
|
||||||
exit_pupil_bounds: Vec<Bounds2f>,
|
exit_pupil_bounds: Vec<Bounds2f>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RealisticCamera {
|
impl RealisticCamera {
|
||||||
pub fn new(&self,
|
pub fn new(
|
||||||
|
&self,
|
||||||
base: CameraBase,
|
base: CameraBase,
|
||||||
lens_params: Vec<Float>,
|
lens_params: Vec<Float>,
|
||||||
focus_distance: Float,
|
focus_distance: Float,
|
||||||
set_aperture_diameter: Float,
|
set_aperture_diameter: Float,
|
||||||
|
aperture_image: Image,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
|
let aspect =
|
||||||
let aspect = base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
|
||||||
let diagonal = base.film.diagonal();
|
let diagonal = base.film.diagonal();
|
||||||
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
|
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
|
||||||
let y = x * aspect;
|
let y = x * aspect;
|
||||||
let physical_extent = Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
|
let physical_extent =
|
||||||
|
Bounds2f::from_points(Point2f::new(-x / 2., -y / 2.), Point2f::new(x / 2., y / 2.));
|
||||||
let mut element_interface: Vec<LensElementInterface> = Vec::new();
|
let mut element_interface: Vec<LensElementInterface> = Vec::new();
|
||||||
|
|
||||||
for i in (0..lens_params.len()).step_by(4) {
|
for i in (0..lens_params.len()).step_by(4) {
|
||||||
|
|
@ -46,7 +52,12 @@ impl RealisticCamera {
|
||||||
aperture_diameter = set_aperture_diameter;
|
aperture_diameter = set_aperture_diameter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let el_int = LensElementInterface { curvature_radius, thickness, eta, aperture_radius: aperture_diameter / 2.0 };
|
let el_int = LensElementInterface {
|
||||||
|
curvature_radius,
|
||||||
|
thickness,
|
||||||
|
eta,
|
||||||
|
aperture_radius: aperture_diameter / 2.0,
|
||||||
|
};
|
||||||
element_interface.push(el_int);
|
element_interface.push(el_int);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,10 +70,20 @@ impl RealisticCamera {
|
||||||
exit_pupil_bounds[i] = self.bound_exit_pupil(r0, r1);
|
exit_pupil_bounds[i] = self.bound_exit_pupil(r0, r1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { base, focus_distance, element_interface, physical_extent, set_aperture_diameter, exit_pupil_bounds }
|
Self {
|
||||||
|
base,
|
||||||
|
focus_distance,
|
||||||
|
element_interface,
|
||||||
|
physical_extent,
|
||||||
|
set_aperture_diameter,
|
||||||
|
aperture_image,
|
||||||
|
exit_pupil_bounds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn lens_rear_z(&self) -> Float { self.element_interface.last().unwrap().thickness }
|
pub fn lens_rear_z(&self) -> Float {
|
||||||
|
self.element_interface.last().unwrap().thickness
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lens_front_z(&self) -> Float {
|
pub fn lens_front_z(&self) -> Float {
|
||||||
let mut z_sum = 0.;
|
let mut z_sum = 0.;
|
||||||
|
|
@ -72,48 +93,66 @@ impl RealisticCamera {
|
||||||
z_sum
|
z_sum
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rear_element_radius(&self) -> Float { self.element_interface.last().unwrap().aperture_radius }
|
pub fn rear_element_radius(&self) -> Float {
|
||||||
|
self.element_interface.last().unwrap().aperture_radius
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
pub fn bound_exit_pupil(&self, film_x_0: Float, film_x_1: Float) -> Bounds2f {
|
||||||
let mut pupil_bounds = Bounds2f::default();
|
let mut pupil_bounds = Bounds2f::default();
|
||||||
let n_samples = 1024 * 1024;
|
let n_samples = 1024 * 1024;
|
||||||
let rear_radius = self.rear_element_radius();
|
let rear_radius = self.rear_element_radius();
|
||||||
let proj_rear_bounds = Bounds2f::from_points(Point2f::new(-1.5 * rear_radius, -1.5 * rear_radius),
|
let proj_rear_bounds = Bounds2f::from_points(
|
||||||
Point2f::new(1.5 * rear_radius, 1.5 * rear_radius));
|
Point2f::new(-1.5 * rear_radius, -1.5 * rear_radius),
|
||||||
|
Point2f::new(1.5 * rear_radius, 1.5 * rear_radius),
|
||||||
|
);
|
||||||
|
|
||||||
let radical_inverse = |x: i32, _y: i64| x as Float;
|
let radical_inverse = |x: i32, _y: i64| x as Float;
|
||||||
let lens_rear_z = || 1. ;
|
let lens_rear_z = || 1.;
|
||||||
let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true;
|
let trace_lenses_from_film = |_ray: Ray, _place: Option<Ray>| true;
|
||||||
for i in 0..n_samples {
|
for i in 0..n_samples {
|
||||||
// Find location of sample points on $x$ segment and rear lens element
|
// Find location of sample points on $x$ segment and rear lens element
|
||||||
//
|
//
|
||||||
let p_film = Point3f::new(lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1), 0., 0.);
|
let p_film = Point3f::new(
|
||||||
|
lerp((i as Float + 0.5) / n_samples as Float, film_x_0, film_x_1),
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
);
|
||||||
let u: [Float; 2] = [radical_inverse(0, i), radical_inverse(1, i)];
|
let u: [Float; 2] = [radical_inverse(0, i), radical_inverse(1, i)];
|
||||||
let p_rear = Point3f::new(lerp(u[0], proj_rear_bounds.p_min.x(), proj_rear_bounds.p_max.x()),
|
let p_rear = Point3f::new(
|
||||||
|
lerp(u[0], proj_rear_bounds.p_min.x(), proj_rear_bounds.p_max.x()),
|
||||||
lerp(u[1], proj_rear_bounds.p_min.y(), proj_rear_bounds.p_max.y()),
|
lerp(u[1], proj_rear_bounds.p_min.y(), proj_rear_bounds.p_max.y()),
|
||||||
lens_rear_z());
|
lens_rear_z(),
|
||||||
|
);
|
||||||
|
|
||||||
// Expand pupil bounds if ray makes it through the lens system
|
// Expand pupil bounds if ray makes it through the lens system
|
||||||
if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y())) &&
|
if !pupil_bounds.contains(Point2f::new(p_rear.x(), p_rear.y()))
|
||||||
trace_lenses_from_film(Ray::new(p_film, p_rear - p_film, None, None), None) {
|
&& trace_lenses_from_film(Ray::new(p_film, p_rear - p_film, None, None), None)
|
||||||
|
{
|
||||||
pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y()));
|
pupil_bounds = pupil_bounds.union_point(Point2f::new(p_rear.x(), p_rear.y()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return degenerate bounds if no rays made it through the lens system
|
// Return degenerate bounds if no rays made it through the lens system
|
||||||
if pupil_bounds.is_degenerate() {
|
if pupil_bounds.is_degenerate() {
|
||||||
print!("Unable to find exit pupil in x = {},{} on film.", film_x_0, film_x_1);
|
print!(
|
||||||
|
"Unable to find exit pupil in x = {},{} on film.",
|
||||||
|
film_x_0, film_x_1
|
||||||
|
);
|
||||||
return pupil_bounds;
|
return pupil_bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt())
|
pupil_bounds.expand(2. * proj_rear_bounds.diagonal().norm() / (n_samples as Float).sqrt())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn intersect_spherical_element(radius: Float, z_center: Float, ray: &Ray) -> Option<(Float, Normal3f)> {
|
pub fn intersect_spherical_element(
|
||||||
|
radius: Float,
|
||||||
|
z_center: Float,
|
||||||
|
ray: &Ray,
|
||||||
|
) -> Option<(Float, Normal3f)> {
|
||||||
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
|
let o = ray.o - Vector3f::new(0.0, 0.0, z_center);
|
||||||
|
|
||||||
let a = ray.d.norm_squared();
|
let a = ray.d.norm_squared();
|
||||||
let b = 2.0 * ray.d.dot(o);
|
let b = 2.0 * ray.d.dot(o.into());
|
||||||
let c = Vector3f::from(o).norm_squared() - radius * radius;
|
let c = Vector3f::from(o).norm_squared() - radius * radius;
|
||||||
|
|
||||||
let (t0, t1) = quadratic(a, b, c)?;
|
let (t0, t1) = quadratic(a, b, c)?;
|
||||||
|
|
@ -128,7 +167,9 @@ impl RealisticCamera {
|
||||||
|
|
||||||
let p_hit_relative = o + ray.d * t;
|
let p_hit_relative = o + ray.d * t;
|
||||||
// Ensures the normal points towards the incident ray.
|
// Ensures the normal points towards the incident ray.
|
||||||
let n = Normal3f::from(Vector3f::from(p_hit_relative)).normalize().face_forward(-ray.d);
|
let n = Normal3f::from(Vector3f::from(p_hit_relative))
|
||||||
|
.normalize()
|
||||||
|
.face_forward(-ray.d);
|
||||||
|
|
||||||
Some((t, n))
|
Some((t, n))
|
||||||
}
|
}
|
||||||
|
|
@ -137,8 +178,12 @@ impl RealisticCamera {
|
||||||
let mut element_z = 0.;
|
let mut element_z = 0.;
|
||||||
let weight = 1.;
|
let weight = 1.;
|
||||||
// Transform _r_camera_ from camera to lens system space
|
// Transform _r_camera_ from camera to lens system space
|
||||||
let mut r_lens = Ray::new(Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()),
|
let mut r_lens = Ray::new(
|
||||||
Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()), Some(r_camera.time), None);
|
Point3f::new(r_camera.o.x(), r_camera.o.y(), -r_camera.o.z()),
|
||||||
|
Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()),
|
||||||
|
Some(r_camera.time),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
for i in (0..self.element_interface.len() - 1).rev() {
|
for i in (0..self.element_interface.len() - 1).rev() {
|
||||||
let element: &LensElementInterface = &self.element_interface[i];
|
let element: &LensElementInterface = &self.element_interface[i];
|
||||||
|
|
@ -155,7 +200,9 @@ impl RealisticCamera {
|
||||||
// Intersect ray with element to compute _t_ and _n_
|
// Intersect ray with element to compute _t_ and _n_
|
||||||
let radius = element.curvature_radius;
|
let radius = element.curvature_radius;
|
||||||
let z_center = element_z + element.curvature_radius;
|
let z_center = element_z + element.curvature_radius;
|
||||||
if let Some((intersect_t, intersect_n)) = RealisticCamera::intersect_spherical_element(radius, z_center, &r_lens) {
|
if let Some((intersect_t, intersect_n)) =
|
||||||
|
RealisticCamera::intersect_spherical_element(radius, z_center, &r_lens)
|
||||||
|
{
|
||||||
t = intersect_t;
|
t = intersect_t;
|
||||||
n = intersect_n;
|
n = intersect_n;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -178,7 +225,11 @@ impl RealisticCamera {
|
||||||
// Update ray path for element interface interaction
|
// Update ray path for element interface interaction
|
||||||
if !is_stop {
|
if !is_stop {
|
||||||
let eta_i = element.eta;
|
let eta_i = element.eta;
|
||||||
let eta_t = if i > 0 && self.element_interface[i - 1].eta != 0. { self.element_interface[i - 1].eta } else { 1. };
|
let eta_t = if i > 0 && self.element_interface[i - 1].eta != 0. {
|
||||||
|
self.element_interface[i - 1].eta
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
};
|
||||||
let wi = -r_lens.d.normalize();
|
let wi = -r_lens.d.normalize();
|
||||||
let eta_ratio = eta_t / eta_i;
|
let eta_ratio = eta_t / eta_i;
|
||||||
|
|
||||||
|
|
@ -193,8 +244,12 @@ impl RealisticCamera {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform lens system space ray back to camera space
|
// Transform lens system space ray back to camera space
|
||||||
let r_out = Ray::new(Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()),
|
let r_out = Ray::new(
|
||||||
Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()), Some(r_lens.time), None);
|
Point3f::new(r_lens.o.x(), r_lens.o.y(), -r_lens.o.z()),
|
||||||
|
Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()),
|
||||||
|
Some(r_lens.time),
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
|
||||||
Some((weight, r_out))
|
Some((weight, r_out))
|
||||||
}
|
}
|
||||||
|
|
@ -202,7 +257,8 @@ impl RealisticCamera {
|
||||||
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
|
||||||
// Find exit pupil bound for sample distance from film center
|
// Find exit pupil bound for sample distance from film center
|
||||||
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
|
let r_film = (square(p_film.x()) + square(p_film.y())).sqrt();
|
||||||
let mut r_index = (r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
|
let mut r_index =
|
||||||
|
(r_film / (self.base.film.diagonal() / 2.)) as usize * self.exit_pupil_bounds.len();
|
||||||
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
r_index = (self.exit_pupil_bounds.len() - 1).min(r_index);
|
||||||
|
|
||||||
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
let pupil_bounds = self.exit_pupil_bounds[r_index];
|
||||||
|
|
@ -215,12 +271,23 @@ impl RealisticCamera {
|
||||||
let pdf = 1. / pupil_bounds.area();
|
let pdf = 1. / pupil_bounds.area();
|
||||||
|
|
||||||
// Return sample point rotated by angle of _pFilm_ with $+x$ axis
|
// Return sample point rotated by angle of _pFilm_ with $+x$ axis
|
||||||
let sin_theta = if r_film != 0. { p_film.y() / r_film } else { 0. };
|
let sin_theta = if r_film != 0. {
|
||||||
let cos_theta = if r_film != 0. { p_film.x() / r_film } else { 1. };
|
p_film.y() / r_film
|
||||||
let p_pupil = Point3f::new(cos_theta * p_lens.x() - sin_theta * p_lens.y(),
|
} else {
|
||||||
sin_theta * p_lens.x() + cos_theta * p_lens.y(), self.lens_rear_z());
|
0.
|
||||||
|
};
|
||||||
|
let cos_theta = if r_film != 0. {
|
||||||
|
p_film.x() / r_film
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
};
|
||||||
|
let p_pupil = Point3f::new(
|
||||||
|
cos_theta * p_lens.x() - sin_theta * p_lens.y(),
|
||||||
|
sin_theta * p_lens.x() + cos_theta * p_lens.y(),
|
||||||
|
self.lens_rear_z(),
|
||||||
|
);
|
||||||
|
|
||||||
Some(ExitPupilSample{p_pupil, pdf})
|
Some(ExitPupilSample { p_pupil, pdf })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,10 +296,16 @@ impl CameraTrait for RealisticCamera {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray(&self, sample: CameraSample, _lambda: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
_lambda: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
// Find point on film, _pFilm_, corresponding to _sample.pFilm_
|
||||||
let s = Point2f::new(sample.p_film.x() / self.base.film.full_resolution().x() as Float,
|
let s = Point2f::new(
|
||||||
sample.p_film.y() / self.base.film.full_resolution().y() as Float);
|
sample.p_film.x() / self.base.film.full_resolution().x() as Float,
|
||||||
|
sample.p_film.y() / self.base.film.full_resolution().y() as Float,
|
||||||
|
);
|
||||||
let p_film2 = self.physical_extent.lerp(s);
|
let p_film2 = self.physical_extent.lerp(s);
|
||||||
let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.);
|
let p_film = Point3f::new(-p_film2.x(), p_film2.y(), 0.);
|
||||||
|
|
||||||
|
|
@ -243,7 +316,7 @@ impl CameraTrait for RealisticCamera {
|
||||||
let r_film = Ray::new(p_film, p_pupil - p_film, None, None);
|
let r_film = Ray::new(p_film, p_pupil - p_film, None, None);
|
||||||
let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
|
let (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
|
||||||
if weight == 0. {
|
if weight == 0. {
|
||||||
return None
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finish initialization of _RealisticCamera_ ray
|
// Finish initialization of _RealisticCamera_ ray
|
||||||
|
|
@ -254,8 +327,12 @@ impl CameraTrait for RealisticCamera {
|
||||||
|
|
||||||
// Compute weighting for _RealisticCamera_ ray
|
// Compute weighting for _RealisticCamera_ ray
|
||||||
let cos_theta = r_film.d.normalize().z();
|
let cos_theta = r_film.d.normalize().z();
|
||||||
let final_weight = weight * cos_theta.powf(4.) / (eps.pdf as Float * square(self.lens_rear_z()));
|
let final_weight =
|
||||||
|
weight * cos_theta.powf(4.) / (eps.pdf as Float * square(self.lens_rear_z()));
|
||||||
|
|
||||||
Some(CameraRay{ray, weight: SampledSpectrum::new(final_weight)})
|
Some(CameraRay {
|
||||||
|
ray,
|
||||||
|
weight: SampledSpectrum::new(final_weight),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{CameraRay, CameraTrait, CameraBase};
|
use super::{CameraBase, CameraRay, CameraTrait};
|
||||||
use crate::utils::geometry::{Bounds2f, Point2f, Point3f, Vector3f, Ray, spherical_direction};
|
|
||||||
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
|
||||||
use crate::utils::math::{wrap_equal_area_square, equal_area_square_to_sphere};
|
|
||||||
use crate::core::film::FilmTrait;
|
use crate::core::film::FilmTrait;
|
||||||
use crate::core::pbrt::{Float, PI};
|
use crate::core::pbrt::{Float, PI};
|
||||||
use crate::core::sampler::CameraSample;
|
use crate::core::sampler::CameraSample;
|
||||||
|
use crate::geometry::{Bounds2f, Point2f, Point3f, Ray, Vector3f, spherical_direction};
|
||||||
|
use crate::utils::math::{equal_area_square_to_sphere, wrap_equal_area_square};
|
||||||
|
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub struct EquiRectangularMapping;
|
pub struct EquiRectangularMapping;
|
||||||
|
|
@ -27,17 +27,22 @@ impl CameraTrait for SphericalCamera {
|
||||||
&self.base
|
&self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_ray(&self, sample: CameraSample, _lamdba: &SampledWavelengths) -> Option<CameraRay> {
|
fn generate_ray(
|
||||||
|
&self,
|
||||||
|
sample: CameraSample,
|
||||||
|
_lamdba: &SampledWavelengths,
|
||||||
|
) -> Option<CameraRay> {
|
||||||
// Compute spherical camera ray direction
|
// Compute spherical camera ray direction
|
||||||
let mut uv = Point2f::new(sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
let mut uv = Point2f::new(
|
||||||
sample.p_film.y() / self.base().film.full_resolution().y() as Float);
|
sample.p_film.x() / self.base().film.full_resolution().x() as Float,
|
||||||
|
sample.p_film.y() / self.base().film.full_resolution().y() as Float,
|
||||||
|
);
|
||||||
let dir: Vector3f;
|
let dir: Vector3f;
|
||||||
if self.mapping == Mapping::EquiRectangular(EquiRectangularMapping) {
|
if self.mapping == Mapping::EquiRectangular(EquiRectangularMapping) {
|
||||||
// Compute ray direction using equirectangular mapping
|
// Compute ray direction using equirectangular mapping
|
||||||
let theta = PI * uv[1];
|
let theta = PI * uv[1];
|
||||||
let phi = 2. * PI * uv[0];
|
let phi = 2. * PI * uv[0];
|
||||||
dir = spherical_direction(theta.sin(), theta.cos(), phi);
|
dir = spherical_direction(theta.sin(), theta.cos(), phi);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Compute ray direction using equal area mapping
|
// Compute ray direction using equal area mapping
|
||||||
uv = wrap_equal_area_square(&mut uv);
|
uv = wrap_equal_area_square(&mut uv);
|
||||||
|
|
@ -45,7 +50,15 @@ impl CameraTrait for SphericalCamera {
|
||||||
}
|
}
|
||||||
std::mem::swap(&mut dir.y(), &mut dir.z());
|
std::mem::swap(&mut dir.y(), &mut dir.z());
|
||||||
|
|
||||||
let ray = Ray::new(Point3f::new(0., 0., 0.), dir, Some(self.sample_time(sample.time)), self.base().medium.clone());
|
let ray = Ray::new(
|
||||||
Some(CameraRay{ray: self.render_from_camera(&ray, &mut None), weight: SampledSpectrum::default() })
|
Point3f::new(0., 0., 0.),
|
||||||
|
dir,
|
||||||
|
Some(self.sample_time(sample.time)),
|
||||||
|
self.base().medium.clone(),
|
||||||
|
);
|
||||||
|
Some(CameraRay {
|
||||||
|
ray: self.render_from_camera(&ray, &mut None),
|
||||||
|
weight: SampledSpectrum::default(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1149
src/core/bxdf.rs
1149
src/core/bxdf.rs
File diff suppressed because it is too large
Load diff
7516
src/core/cie.rs
7516
src/core/cie.rs
File diff suppressed because it is too large
Load diff
643
src/core/film.rs
643
src/core/film.rs
|
|
@ -1,58 +1,464 @@
|
||||||
|
use crate::core::filter::{Filter, FilterTrait};
|
||||||
|
use crate::core::interaction::SurfaceInteraction;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::color::RGB;
|
use crate::geometry::{
|
||||||
use crate::core::filter::Filter;
|
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Vector2f, Vector2fi,
|
||||||
use crate::utils::geometry::{Point2i, Point2f, Bounds2f, Bounds2fi};
|
Vector2i, Vector3f,
|
||||||
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
|
};
|
||||||
|
use crate::utils::color::{RGB, SRGBEncoding, Triplet, XYZ, white_balance};
|
||||||
|
use crate::utils::colorspace::RGBColorspace;
|
||||||
|
use crate::utils::containers::Array2D;
|
||||||
|
use crate::utils::image::{
|
||||||
|
Image, ImageChannelDesc, ImageChannelValues, ImageMetadata, PixelFormat,
|
||||||
|
};
|
||||||
|
use crate::utils::math::SquareMatrix;
|
||||||
|
use crate::utils::math::linear_least_squares;
|
||||||
|
use crate::utils::sampling::VarianceEstimator;
|
||||||
|
use crate::utils::spectrum::{
|
||||||
|
ConstantSpectrum, DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES,
|
||||||
|
SampledSpectrum, SampledWavelengths, Spectrum, inner_product, spectra,
|
||||||
|
};
|
||||||
|
use crate::utils::transform::AnimatedTransform;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::sync::{Arc, atomic::AtomicUsize, atomic::Ordering};
|
||||||
|
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
pub struct RGBFilm {
|
pub struct RGBFilm {
|
||||||
pub base: FilmBase,
|
pub base: FilmBase,
|
||||||
|
pub max_component_value: Float,
|
||||||
|
pub write_fp16: bool,
|
||||||
|
pub filter_integral: Float,
|
||||||
|
pub output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
|
pub pixels: Array2D<RGBPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct RGBPixel {
|
||||||
|
rgb_sum: [f64; 3],
|
||||||
|
weight_sum: f64,
|
||||||
|
rgb_splat: Mutex<[f64; 3]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RGBPixel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
rgb_sum: [0., 0., 0.],
|
||||||
|
weight_sum: 0.,
|
||||||
|
rgb_splat: Mutex::new([0., 0., 0.]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RGBFilm {
|
||||||
|
pub fn new(
|
||||||
|
base: FilmBase,
|
||||||
|
colorspace: RGBColorspace,
|
||||||
|
max_component_value: Float,
|
||||||
|
write_fp16: bool,
|
||||||
|
) -> Self {
|
||||||
|
let pixels = Array2D::new(base.pixel_bounds);
|
||||||
|
let filter_integral = base.filter.integral();
|
||||||
|
let sensor_matrix = base
|
||||||
|
.sensor
|
||||||
|
.as_ref()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.xyz_from_sensor_rgb;
|
||||||
|
let output_rgbf_from_sensor_rgb = colorspace.rgb_from_xyz * sensor_matrix;
|
||||||
|
Self {
|
||||||
|
base,
|
||||||
|
max_component_value,
|
||||||
|
write_fp16,
|
||||||
|
filter_integral,
|
||||||
|
output_rgbf_from_sensor_rgb,
|
||||||
|
pixels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct GBufferBFilm {
|
pub struct GBufferBFilm {
|
||||||
pub base: FilmBase,
|
pub base: FilmBase,
|
||||||
|
output_from_render: AnimatedTransform,
|
||||||
|
apply_inverse: bool,
|
||||||
|
pixels: Array2D<GBufferPixel>,
|
||||||
|
colorspace: RGBColorspace,
|
||||||
|
max_component_value: Float,
|
||||||
|
write_fp16: bool,
|
||||||
|
filter_integral: Float,
|
||||||
|
output_rgbf_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct GBufferPixel {
|
||||||
|
rgb_sum: [f64; 3],
|
||||||
|
weight_sum: f64,
|
||||||
|
g_bugger_weight_sum: f64,
|
||||||
|
rgb_splat: Mutex<[f64; 3]>,
|
||||||
|
p_sum: Point3f,
|
||||||
|
dz_dx_sum: Float,
|
||||||
|
dz_dy_sum: Float,
|
||||||
|
n_sum: Normal3f,
|
||||||
|
ns_sum: Normal3f,
|
||||||
|
uv_sum: Point2f,
|
||||||
|
rgb_albedo_sum: [f64; 3],
|
||||||
|
rgb_variance: VarianceEstimator,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct SpectralFilm {
|
pub struct SpectralFilm {
|
||||||
pub base: FilmBase,
|
pub base: FilmBase,
|
||||||
|
output_from_render: AnimatedTransform,
|
||||||
|
pixels: Array2D<SpectralPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VisibleSurface;
|
struct SpectralPixel;
|
||||||
pub struct PixelSensor;
|
|
||||||
pub struct ImageMetadata;
|
|
||||||
|
|
||||||
|
const N_SWATCH_REFLECTANCES: usize = 24;
|
||||||
|
|
||||||
|
static SWATCH_REFLECTANCES: Lazy<[Spectrum; N_SWATCH_REFLECTANCES]> =
|
||||||
|
Lazy::new(|| std::array::from_fn(|_| Spectrum::Constant(ConstantSpectrum::new(0.8))));
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PixelSensor {
|
||||||
|
pub xyz_from_sensor_rgb: SquareMatrix<Float, 3>,
|
||||||
|
r_bar: DenselySampledSpectrum,
|
||||||
|
g_bar: DenselySampledSpectrum,
|
||||||
|
b_bar: DenselySampledSpectrum,
|
||||||
|
imaging_ratio: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PixelSensor {
|
||||||
|
pub fn new(
|
||||||
|
r: &Spectrum,
|
||||||
|
g: &Spectrum,
|
||||||
|
b: &Spectrum,
|
||||||
|
output_colorspace: RGBColorspace,
|
||||||
|
sensor_illum: &Spectrum,
|
||||||
|
imaging_ratio: Float,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let r_bar = DenselySampledSpectrum::from_spectrum(&r, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let g_bar = DenselySampledSpectrum::from_spectrum(&g, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let b_bar = DenselySampledSpectrum::from_spectrum(&b, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let mut rgb_camera = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
||||||
|
|
||||||
|
for i in 0..N_SWATCH_REFLECTANCES {
|
||||||
|
let rgb = Self::project_reflectance::<RGB>(
|
||||||
|
&SWATCH_REFLECTANCES[i],
|
||||||
|
sensor_illum,
|
||||||
|
&Spectrum::DenselySampled(r_bar.clone()),
|
||||||
|
&Spectrum::DenselySampled(g_bar.clone()),
|
||||||
|
&Spectrum::DenselySampled(b_bar.clone()),
|
||||||
|
);
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb_camera[i][c] = rgb[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut xyz_output = [[0.; 3]; N_SWATCH_REFLECTANCES];
|
||||||
|
let sensor_white_g = inner_product(sensor_illum, &Spectrum::DenselySampled(g_bar.clone()));
|
||||||
|
let sensor_white_y = inner_product(sensor_illum, &spectra::Y);
|
||||||
|
for i in 0..N_SWATCH_REFLECTANCES {
|
||||||
|
let s = SWATCH_REFLECTANCES[i].clone();
|
||||||
|
let xyz = Self::project_reflectance::<XYZ>(
|
||||||
|
&s,
|
||||||
|
&output_colorspace.illuminant,
|
||||||
|
&spectra::X,
|
||||||
|
&spectra::Y,
|
||||||
|
&spectra::Z,
|
||||||
|
) * (sensor_white_y / sensor_white_g);
|
||||||
|
for c in 0..3 {
|
||||||
|
xyz_output[i][c] = xyz[c];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let xyz_from_sensor_rgb = linear_least_squares(rgb_camera, xyz_output)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
xyz_from_sensor_rgb,
|
||||||
|
r_bar,
|
||||||
|
g_bar,
|
||||||
|
b_bar,
|
||||||
|
imaging_ratio,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_white_balance(
|
||||||
|
output_colorspace: RGBColorspace,
|
||||||
|
sensor_illum: Option<Spectrum>,
|
||||||
|
imaging_ratio: Float,
|
||||||
|
) -> Self {
|
||||||
|
let r_bar = DenselySampledSpectrum::from_spectrum(&spectra::X, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let g_bar = DenselySampledSpectrum::from_spectrum(&spectra::Y, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let b_bar = DenselySampledSpectrum::from_spectrum(&spectra::Z, LAMBDA_MIN, LAMBDA_MAX);
|
||||||
|
let xyz_from_sensor_rgb: SquareMatrix<Float, 3>;
|
||||||
|
|
||||||
|
if let Some(illum) = sensor_illum {
|
||||||
|
let source_white = illum.to_xyz().xy();
|
||||||
|
let target_white = output_colorspace.w;
|
||||||
|
xyz_from_sensor_rgb = white_balance(source_white, target_white);
|
||||||
|
} else {
|
||||||
|
xyz_from_sensor_rgb = SquareMatrix::<Float, 3>::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
xyz_from_sensor_rgb,
|
||||||
|
r_bar,
|
||||||
|
g_bar,
|
||||||
|
b_bar,
|
||||||
|
imaging_ratio,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn project_reflectance<T: Triplet>(
|
||||||
|
refl: &Spectrum,
|
||||||
|
illum: &Spectrum,
|
||||||
|
b1: &Spectrum,
|
||||||
|
b2: &Spectrum,
|
||||||
|
b3: &Spectrum,
|
||||||
|
) -> T {
|
||||||
|
let mut result = [0.; 3];
|
||||||
|
let mut g_integral = 0.;
|
||||||
|
|
||||||
|
for lambda_ind in LAMBDA_MIN..=LAMBDA_MAX {
|
||||||
|
let lambda = lambda_ind as Float;
|
||||||
|
let illum_val = illum.sample_at(lambda);
|
||||||
|
|
||||||
|
g_integral += b2.sample_at(lambda) * illum_val;
|
||||||
|
let refl_illum = refl.sample_at(lambda) * illum_val;
|
||||||
|
result[0] += b1.sample_at(lambda) * refl_illum;
|
||||||
|
result[1] += b2.sample_at(lambda) * refl_illum;
|
||||||
|
result[2] += b3.sample_at(lambda) * refl_illum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if g_integral > 0. {
|
||||||
|
let inv_g = 1. / g_integral;
|
||||||
|
result[0] *= inv_g;
|
||||||
|
result[1] *= inv_g;
|
||||||
|
result[2] *= inv_g;
|
||||||
|
}
|
||||||
|
|
||||||
|
T::from_triplet(result[0], result[1], result[2])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_sensor_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||||
|
let l_norm = l.safe_div(lambda.pdf());
|
||||||
|
self.imaging_ratio
|
||||||
|
* RGB::new(
|
||||||
|
(self.r_bar.sample(lambda) * l_norm).average(),
|
||||||
|
(self.g_bar.sample(lambda) * l_norm).average(),
|
||||||
|
(self.b_bar.sample(lambda) * l_norm).average(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct VisibleSurface {
|
||||||
|
pub p: Point3f,
|
||||||
|
pub n: Normal3f,
|
||||||
|
pub ns: Normal3f,
|
||||||
|
pub uv: Point2f,
|
||||||
|
pub time: Float,
|
||||||
|
pub dpdx: Vector3f,
|
||||||
|
pub dpdy: Vector3f,
|
||||||
|
pub albedo: SampledSpectrum,
|
||||||
|
pub set: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VisibleSurface {
|
||||||
|
pub fn new(
|
||||||
|
_si: SurfaceInteraction,
|
||||||
|
albedo: SampledSpectrum,
|
||||||
|
_lambda: SampledWavelengths,
|
||||||
|
) -> Self {
|
||||||
|
let mut vs = VisibleSurface::default();
|
||||||
|
vs.albedo = albedo;
|
||||||
|
vs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VisibleSurface {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
p: Point3f::default(),
|
||||||
|
n: Normal3f::default(),
|
||||||
|
ns: Normal3f::default(),
|
||||||
|
uv: Point2f::default(),
|
||||||
|
time: 0.,
|
||||||
|
dpdx: Vector3f::default(),
|
||||||
|
dpdy: Vector3f::default(),
|
||||||
|
albedo: SampledSpectrum::default(),
|
||||||
|
set: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct FilmBase {
|
pub struct FilmBase {
|
||||||
pub full_resolution: Point2i,
|
pub full_resolution: Point2i,
|
||||||
pub pixel_bounds: Bounds2fi,
|
pub pixel_bounds: Bounds2i,
|
||||||
pub filter: Filter,
|
pub filter: Filter,
|
||||||
pub diagonal: Float,
|
pub diagonal: Float,
|
||||||
pub sensor: Option<PixelSensor>,
|
pub sensor: Option<PixelSensor>,
|
||||||
pub filename: String,
|
pub filename: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait FilmTrait {
|
impl FilmBase {
|
||||||
// Must be implemented
|
pub fn new(
|
||||||
|
full_resolution: Point2i,
|
||||||
|
pixel_bounds: Bounds2i,
|
||||||
|
filter: Filter,
|
||||||
|
diagonal: Float,
|
||||||
|
sensor: Option<PixelSensor>,
|
||||||
|
filename: String,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
full_resolution,
|
||||||
|
pixel_bounds,
|
||||||
|
filter,
|
||||||
|
diagonal: 0.001 * diagonal,
|
||||||
|
sensor,
|
||||||
|
filename,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FilmTrait: Sync {
|
||||||
fn base(&self) -> &FilmBase;
|
fn base(&self) -> &FilmBase;
|
||||||
fn base_mut(&mut self) -> &mut FilmBase;
|
fn base_mut(&mut self) -> &mut FilmBase;
|
||||||
fn add_sample(&mut self,
|
fn add_sample(
|
||||||
|
&mut self,
|
||||||
_p_filme: Point2i,
|
_p_filme: Point2i,
|
||||||
_l: SampledSpectrum,
|
_l: SampledSpectrum,
|
||||||
_lambda: &SampledWavelengths,
|
_lambda: &SampledWavelengths,
|
||||||
_visible_surface: Option<&VisibleSurface>,
|
_visible_surface: Option<&VisibleSurface>,
|
||||||
_weight: Float) {todo!()}
|
_weight: Float,
|
||||||
fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) { todo!() }
|
) {
|
||||||
fn write_image(&self, _splat_scale: Float) { todo!() }
|
todo!()
|
||||||
fn get_image(&self, _metadata: &ImageMetadata, _splat_scale: Float) { todo!() }
|
}
|
||||||
fn get_pixel_rgb(&self, _p: Point2i) -> RGB { todo!() }
|
fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) {
|
||||||
fn reset_pixel(&mut self, _p: Point2i) { todo!() }
|
todo!()
|
||||||
fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB { todo!() }
|
}
|
||||||
fn sample_wavelengths(&self) -> &SampledWavelengths { todo!() }
|
|
||||||
fn uses_visible_surface(&self) -> bool { todo!() }
|
|
||||||
|
|
||||||
// Sensible defaults
|
fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) {
|
||||||
fn full_resolution(&self) -> Point2i { self.base().full_resolution }
|
let image = self.get_image(metadata, splat_scale);
|
||||||
fn sample_bounds(&self) -> Bounds2f { Bounds2f::default() }
|
image
|
||||||
fn diagonal(&self) -> Float { self.base().diagonal }
|
.write(self.get_filename(), metadata)
|
||||||
fn get_filter(&self) -> &Filter { &self.base().filter }
|
.expect("Something")
|
||||||
fn get_pixel_sensor(&self) -> Option<&PixelSensor> { self.base().sensor.as_ref() }
|
}
|
||||||
|
|
||||||
|
fn get_image(&self, _metadata: &ImageMetadata, splat_scale: Float) -> Image {
|
||||||
|
let write_fp16 = true;
|
||||||
|
let format = if write_fp16 {
|
||||||
|
PixelFormat::F16
|
||||||
|
} else {
|
||||||
|
PixelFormat::F32
|
||||||
|
};
|
||||||
|
|
||||||
|
let channel_names = vec!["R".to_string(), "G".to_string(), "B".to_string()];
|
||||||
|
|
||||||
|
let pixel_bounds = self.base().pixel_bounds;
|
||||||
|
let resolution = Point2i::from(pixel_bounds.diagonal());
|
||||||
|
|
||||||
|
let n_clamped = Arc::new(AtomicUsize::new(0));
|
||||||
|
let processed_rows: Vec<Vec<Float>> = (pixel_bounds.p_min.y()..pixel_bounds.p_max.y())
|
||||||
|
.into_par_iter()
|
||||||
|
.map(|y| {
|
||||||
|
let n_clamped = Arc::clone(&n_clamped);
|
||||||
|
let mut row_data = Vec::with_capacity(resolution.x() as usize * 3);
|
||||||
|
for x in pixel_bounds.p_min.x()..pixel_bounds.p_max.x() {
|
||||||
|
let p = Point2i::new(x, y);
|
||||||
|
let mut rgb = self.get_pixel_rgb(p, Some(splat_scale));
|
||||||
|
let mut was_clamped = false;
|
||||||
|
if write_fp16 {
|
||||||
|
if rgb.r > 65504.0 {
|
||||||
|
rgb.r = 65504.0;
|
||||||
|
was_clamped = true;
|
||||||
|
}
|
||||||
|
if rgb.g > 65504.0 {
|
||||||
|
rgb.g = 65504.0;
|
||||||
|
was_clamped = true;
|
||||||
|
}
|
||||||
|
if rgb.b > 65504.0 {
|
||||||
|
rgb.b = 65504.0;
|
||||||
|
was_clamped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if was_clamped {
|
||||||
|
n_clamped.fetch_add(1, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
row_data.push(rgb.r);
|
||||||
|
row_data.push(rgb.g);
|
||||||
|
row_data.push(rgb.b);
|
||||||
|
}
|
||||||
|
row_data
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut image = Image::new(format, resolution, channel_names, Arc::new(SRGBEncoding));
|
||||||
|
let rgb_desc = ImageChannelDesc::new(&[0, 1, 2]);
|
||||||
|
|
||||||
|
for (iy, row_data) in processed_rows.into_iter().enumerate() {
|
||||||
|
for (ix, rgb_chunk) in row_data.chunks_exact(3).enumerate() {
|
||||||
|
let p_offset = Point2i::new(ix as i32, iy as i32);
|
||||||
|
let values = ImageChannelValues::from(rgb_chunk);
|
||||||
|
image.set_channels(p_offset, &rgb_desc, &values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let clamped_count = n_clamped.load(Ordering::SeqCst);
|
||||||
|
if clamped_count > 0 {
|
||||||
|
println!(
|
||||||
|
"{} pixel values clamped to maximum fp16 value.",
|
||||||
|
clamped_count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// self.base().pixel_bounds = pixel_bounds;
|
||||||
|
// self.base().full_resolution = resolution;
|
||||||
|
// self.colorspace = colorspace;
|
||||||
|
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pixel_rgb(&self, _p: Point2i, _splat_scale: Option<Float>) -> RGB {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn reset_pixel(&mut self, _p: Point2i) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn to_output_rgb(&self, _v: SampledSpectrum, _lambda: &SampledWavelengths) -> RGB {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
fn sample_wavelengths(&self, u: Float) -> SampledWavelengths {
|
||||||
|
SampledWavelengths::sample_visible(u)
|
||||||
|
}
|
||||||
|
fn uses_visible_surface(&self) -> bool;
|
||||||
|
|
||||||
|
fn full_resolution(&self) -> Point2i {
|
||||||
|
self.base().full_resolution
|
||||||
|
}
|
||||||
|
fn pixel_bounds(&self) -> Bounds2i {
|
||||||
|
self.base().pixel_bounds
|
||||||
|
}
|
||||||
|
fn sample_bounds(&self) -> Bounds2f {
|
||||||
|
let pixel_bounds = self.pixel_bounds();
|
||||||
|
let radius = self.get_filter().radius();
|
||||||
|
Bounds2f::from_points(
|
||||||
|
Point2f::from(pixel_bounds.p_min) - radius + Vector2f::new(0.5, 0.5),
|
||||||
|
Point2f::from(pixel_bounds.p_max) + radius - Vector2f::new(0.5, 0.5),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagonal(&self) -> Float {
|
||||||
|
self.base().diagonal
|
||||||
|
}
|
||||||
|
fn get_filter(&self) -> &Filter {
|
||||||
|
&self.base().filter
|
||||||
|
}
|
||||||
|
fn get_pixel_sensor(&self) -> Option<&PixelSensor> {
|
||||||
|
self.base().sensor.as_ref()
|
||||||
|
}
|
||||||
|
fn get_filename(&self) -> &String {
|
||||||
|
&self.base().filename
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Film {
|
pub enum Film {
|
||||||
|
|
@ -69,6 +475,108 @@ impl FilmTrait for RGBFilm {
|
||||||
fn base_mut(&mut self) -> &mut FilmBase {
|
fn base_mut(&mut self) -> &mut FilmBase {
|
||||||
&mut self.base
|
&mut self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_sample(
|
||||||
|
&mut self,
|
||||||
|
p_film: Point2i,
|
||||||
|
l: SampledSpectrum,
|
||||||
|
lambda: &SampledWavelengths,
|
||||||
|
_vi: Option<&VisibleSurface>,
|
||||||
|
weight: Float,
|
||||||
|
) {
|
||||||
|
let mut rgb = self
|
||||||
|
.get_pixel_sensor()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.to_sensor_rgb(l, lambda);
|
||||||
|
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
if m > self.max_component_value {
|
||||||
|
rgb *= self.max_component_value / m;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pixel = &mut self.pixels[p_film];
|
||||||
|
for c in 0..3 {
|
||||||
|
pixel.rgb_sum[c] += (weight * rgb[c]) as f64;
|
||||||
|
}
|
||||||
|
pixel.weight_sum += weight as f64;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
||||||
|
let mut rgb = self
|
||||||
|
.get_pixel_sensor()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.to_sensor_rgb(l, lambda);
|
||||||
|
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
if m > self.max_component_value {
|
||||||
|
rgb *= self.max_component_value / m;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p_discrete = p + Vector2f::new(0.5, 0.5);
|
||||||
|
let radius = self.get_filter().radius();
|
||||||
|
|
||||||
|
let splat_bounds = Bounds2i::from_points(
|
||||||
|
(p_discrete - radius).floor(),
|
||||||
|
(p_discrete + radius).floor() + Vector2i::new(1, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let splat_intersect = splat_bounds.union(self.pixel_bounds());
|
||||||
|
for pi in &splat_intersect {
|
||||||
|
let pi_f: Point2f = (*pi).into();
|
||||||
|
let wt = self
|
||||||
|
.get_filter()
|
||||||
|
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
|
||||||
|
if wt != 0. {
|
||||||
|
let pixel = &self.pixels[*pi];
|
||||||
|
for i in 0..3 {
|
||||||
|
let mut rgb_splat = pixel.rgb_splat.lock().unwrap();
|
||||||
|
rgb_splat[i] += wt as f64 * rgb_splat[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
|
||||||
|
let pixel = &self.pixels[p];
|
||||||
|
let mut rgb = RGB::new(
|
||||||
|
pixel.rgb_sum[0] as Float,
|
||||||
|
pixel.rgb_sum[1] as Float,
|
||||||
|
pixel.rgb_sum[2] as Float,
|
||||||
|
);
|
||||||
|
let weight_sum = pixel.weight_sum;
|
||||||
|
if weight_sum != 0. {
|
||||||
|
rgb /= weight_sum as Float
|
||||||
|
}
|
||||||
|
|
||||||
|
let rgb_splat = pixel.rgb_splat.lock().unwrap();
|
||||||
|
if let Some(splat) = splat_scale {
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] += splat * rgb_splat[c] as Float / self.filter_integral;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] += rgb_splat[c] as Float / self.filter_integral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.output_rgbf_from_sensor_rgb * rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||||
|
let sensor_rgb = self
|
||||||
|
.get_pixel_sensor()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.to_sensor_rgb(l, lambda);
|
||||||
|
self.output_rgbf_from_sensor_rgb * sensor_rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uses_visible_surface(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_image(&self, metadata: &ImageMetadata, splat_scale: Float) {
|
||||||
|
let image = self.get_image(metadata, splat_scale);
|
||||||
|
image
|
||||||
|
.write(self.get_filename(), metadata)
|
||||||
|
.expect("Something please")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilmTrait for GBufferBFilm {
|
impl FilmTrait for GBufferBFilm {
|
||||||
|
|
@ -79,6 +587,77 @@ impl FilmTrait for GBufferBFilm {
|
||||||
fn base_mut(&mut self) -> &mut FilmBase {
|
fn base_mut(&mut self) -> &mut FilmBase {
|
||||||
&mut self.base
|
&mut self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn add_splat(&mut self, p: Point2f, l: SampledSpectrum, lambda: &SampledWavelengths) {
|
||||||
|
let mut rgb = self
|
||||||
|
.get_pixel_sensor()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.to_sensor_rgb(l, lambda);
|
||||||
|
let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max);
|
||||||
|
if m > self.max_component_value {
|
||||||
|
rgb *= self.max_component_value / m;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p_discrete = p + Vector2f::new(0.5, 0.5);
|
||||||
|
let radius = self.get_filter().radius();
|
||||||
|
|
||||||
|
let splat_bounds = Bounds2i::from_points(
|
||||||
|
(p_discrete - radius).floor(),
|
||||||
|
(p_discrete + radius).floor() + Vector2i::new(1, 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let splat_intersect = splat_bounds.union(self.pixel_bounds());
|
||||||
|
for pi in &splat_intersect {
|
||||||
|
let pi_f: Point2f = (*pi).into();
|
||||||
|
let wt = self
|
||||||
|
.get_filter()
|
||||||
|
.evaluate((p - pi_f - Vector2f::new(0.5, 0.5)).into());
|
||||||
|
if wt != 0. {
|
||||||
|
let pixel = &self.pixels[*pi];
|
||||||
|
for i in 0..3 {
|
||||||
|
let mut rgb_splat = pixel.rgb_splat.lock().unwrap();
|
||||||
|
rgb_splat[i] += wt as f64 * rgb_splat[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_output_rgb(&self, l: SampledSpectrum, lambda: &SampledWavelengths) -> RGB {
|
||||||
|
let sensor_rgb = self
|
||||||
|
.get_pixel_sensor()
|
||||||
|
.expect("Sensor must exist")
|
||||||
|
.to_sensor_rgb(l, lambda);
|
||||||
|
self.output_rgbf_from_sensor_rgb * sensor_rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pixel_rgb(&self, p: Point2i, splat_scale: Option<Float>) -> RGB {
|
||||||
|
let pixel = &self.pixels[p];
|
||||||
|
let mut rgb = RGB::new(
|
||||||
|
pixel.rgb_sum[0] as Float,
|
||||||
|
pixel.rgb_sum[1] as Float,
|
||||||
|
pixel.rgb_sum[2] as Float,
|
||||||
|
);
|
||||||
|
let weight_sum = pixel.weight_sum;
|
||||||
|
if weight_sum != 0. {
|
||||||
|
rgb /= weight_sum as Float
|
||||||
|
}
|
||||||
|
|
||||||
|
let rgb_splat = pixel.rgb_splat.lock().unwrap();
|
||||||
|
if let Some(splat) = splat_scale {
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] += splat * rgb_splat[c] as Float / self.filter_integral;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for c in 0..3 {
|
||||||
|
rgb[c] += rgb_splat[c] as Float / self.filter_integral;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.output_rgbf_from_sensor_rgb * rgb
|
||||||
|
}
|
||||||
|
|
||||||
|
fn uses_visible_surface(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilmTrait for SpectralFilm {
|
impl FilmTrait for SpectralFilm {
|
||||||
|
|
@ -89,6 +668,10 @@ impl FilmTrait for SpectralFilm {
|
||||||
fn base_mut(&mut self) -> &mut FilmBase {
|
fn base_mut(&mut self) -> &mut FilmBase {
|
||||||
&mut self.base
|
&mut self.base
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn uses_visible_surface(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilmTrait for Film {
|
impl FilmTrait for Film {
|
||||||
|
|
@ -107,4 +690,12 @@ impl FilmTrait for Film {
|
||||||
Film::Spectral(film) => film.base_mut(),
|
Film::Spectral(film) => film.base_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn uses_visible_surface(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Film::RGB(film) => film.uses_visible_surface(),
|
||||||
|
Film::GBuffer(film) => film.uses_visible_surface(),
|
||||||
|
Film::Spectral(film) => film.uses_visible_surface(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::utils::geometry::{Vector2f, Point2f, Point2i, Bounds2i, Bounds2f};
|
|
||||||
use crate::core::pbrt::{Float, lerp};
|
use crate::core::pbrt::{Float, lerp};
|
||||||
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
|
|
||||||
use crate::core::sampler::PiecewiseConstant2D;
|
use crate::core::sampler::PiecewiseConstant2D;
|
||||||
|
use crate::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
|
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
|
||||||
|
|
||||||
use std::hash::Hash;
|
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
pub struct FilterSample {
|
pub struct FilterSample {
|
||||||
p: Point2f,
|
p: Point2f,
|
||||||
weight: Float,
|
weight: Float,
|
||||||
}
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct FilterSampler {
|
pub struct FilterSampler {
|
||||||
domain: Bounds2f,
|
domain: Bounds2f,
|
||||||
distrib: PiecewiseConstant2D,
|
distrib: PiecewiseConstant2D,
|
||||||
|
|
@ -18,12 +19,14 @@ pub struct FilterSampler {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterSampler {
|
impl FilterSampler {
|
||||||
/// A redesigned constructor that takes a closure to avoid initialization issues.
|
|
||||||
pub fn new<F>(radius: Vector2f, resolution: Point2i, evaluate_fn: F) -> Self
|
pub fn new<F>(radius: Vector2f, resolution: Point2i, evaluate_fn: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(Point2f) -> Float,
|
F: Fn(Point2f) -> Float,
|
||||||
{
|
{
|
||||||
let domain = Bounds2f::from_points(Point2f::new(-radius.x(), -radius.y()), Point2f::new(radius.x(), radius.y()));
|
let domain = Bounds2f::from_points(
|
||||||
|
Point2f::new(-radius.x(), -radius.y()),
|
||||||
|
Point2f::new(radius.x(), radius.y()),
|
||||||
|
);
|
||||||
let array_bounds = Bounds2i::from_points(Point2i::new(0, 0), resolution);
|
let array_bounds = Bounds2i::from_points(Point2i::new(0, 0), resolution);
|
||||||
let mut f = Array2D::new(array_bounds);
|
let mut f = Array2D::new(array_bounds);
|
||||||
for j in 0..resolution.y() {
|
for j in 0..resolution.y() {
|
||||||
|
|
@ -35,7 +38,7 @@ impl FilterSampler {
|
||||||
f[Point2i::new(i, j)] = evaluate_fn(p);
|
f[Point2i::new(i, j)] = evaluate_fn(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let distrib = PiecewiseConstant2D::new(&f, domain);
|
let distrib = PiecewiseConstant2D::new_with_bounds(&f, domain);
|
||||||
Self { domain, f, distrib }
|
Self { domain, f, distrib }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -57,6 +60,7 @@ pub trait FilterTrait {
|
||||||
fn sample(&self, u: Point2f) -> FilterSample;
|
fn sample(&self, u: Point2f) -> FilterSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum Filter {
|
pub enum Filter {
|
||||||
Box(BoxFilter),
|
Box(BoxFilter),
|
||||||
Gaussian(GaussianFilter),
|
Gaussian(GaussianFilter),
|
||||||
|
|
@ -107,6 +111,7 @@ impl FilterTrait for Filter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct BoxFilter {
|
pub struct BoxFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
}
|
}
|
||||||
|
|
@ -135,11 +140,15 @@ impl FilterTrait for BoxFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample(&self, u: Point2f) -> FilterSample {
|
fn sample(&self, u: Point2f) -> FilterSample {
|
||||||
let p = Point2f::new(lerp(u[0], -self.radius.x(), self.radius.x()), lerp(u[1], -self.radius.y(), self.radius.y()));
|
let p = Point2f::new(
|
||||||
|
lerp(u[0], -self.radius.x(), self.radius.x()),
|
||||||
|
lerp(u[1], -self.radius.y(), self.radius.y()),
|
||||||
|
);
|
||||||
FilterSample { p, weight: 1.0 }
|
FilterSample { p, weight: 1.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct GaussianFilter {
|
pub struct GaussianFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
pub sigma: Float,
|
pub sigma: Float,
|
||||||
|
|
@ -157,16 +166,16 @@ impl GaussianFilter {
|
||||||
radius,
|
radius,
|
||||||
Point2i::new((32.0 * radius.x()) as i32, (32.0 * radius.y()) as i32),
|
Point2i::new((32.0 * radius.x()) as i32, (32.0 * radius.y()) as i32),
|
||||||
|p: Point2f| {
|
|p: Point2f| {
|
||||||
(gaussian(p.x(), 0., sigma) - exp_x).max(0.) *
|
(gaussian(p.x(), 0., sigma) - exp_x).max(0.)
|
||||||
(gaussian(p.y(), 0., sigma) - exp_y).max(0.)
|
* (gaussian(p.y(), 0., sigma) - exp_y).max(0.)
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
radius,
|
radius,
|
||||||
sigma,
|
sigma,
|
||||||
exp_x: gaussian(radius.x(), 0., sigma),
|
exp_x: gaussian(radius.x(), 0., sigma),
|
||||||
exp_y: gaussian(radius.y(), 0., sigma),
|
exp_y: gaussian(radius.y(), 0., sigma),
|
||||||
sampler
|
sampler,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -177,13 +186,15 @@ impl FilterTrait for GaussianFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, p: Point2f) -> Float {
|
fn evaluate(&self, p: Point2f) -> Float {
|
||||||
(gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0) *
|
(gaussian(p.x(), 0.0, self.sigma) - self.exp_x).max(0.0)
|
||||||
(gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
|
* (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn integral(&self) -> Float {
|
fn integral(&self) -> Float {
|
||||||
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma) - 2.0 * self.radius.x() * self.exp_x) *
|
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
|
||||||
(gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma) - 2.0 * self.radius.y() * self.exp_y)
|
- 2.0 * self.radius.x() * self.exp_x)
|
||||||
|
* (gaussian_integral(-self.radius.y(), self.radius.y(), 0.0, self.sigma)
|
||||||
|
- 2.0 * self.radius.y() * self.exp_y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample(&self, u: Point2f) -> FilterSample {
|
fn sample(&self, u: Point2f) -> FilterSample {
|
||||||
|
|
@ -191,6 +202,7 @@ impl FilterTrait for GaussianFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct MitchellFilter {
|
pub struct MitchellFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
pub b: Float,
|
pub b: Float,
|
||||||
|
|
@ -207,14 +219,16 @@ impl MitchellFilter {
|
||||||
let mitchell_1d = |x: Float| {
|
let mitchell_1d = |x: Float| {
|
||||||
let x = x.abs();
|
let x = x.abs();
|
||||||
if x <= 1.0 {
|
if x <= 1.0 {
|
||||||
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) +
|
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
|
||||||
(-18.0 + 12.0 * b + 6.0 * c) * x.powi(2) +
|
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
|
||||||
(6.0 - 2.0 * b)) * (1.0 / 6.0)
|
+ (6.0 - 2.0 * b))
|
||||||
|
* (1.0 / 6.0)
|
||||||
} else if x <= 2.0 {
|
} else if x <= 2.0 {
|
||||||
((-b - 6.0 * c) * x.powi(3) +
|
((-b - 6.0 * c) * x.powi(3)
|
||||||
(6.0 * b + 30.0 * c) * x.powi(2) +
|
+ (6.0 * b + 30.0 * c) * x.powi(2)
|
||||||
(-12.0 * b - 48.0 * c) * x +
|
+ (-12.0 * b - 48.0 * c) * x
|
||||||
(8.0 * b + 24.0 * c)) * (1.0 / 6.0)
|
+ (8.0 * b + 24.0 * c))
|
||||||
|
* (1.0 / 6.0)
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
|
|
@ -222,20 +236,27 @@ impl MitchellFilter {
|
||||||
mitchell_1d(2.0 * p.x() / radius.x()) * mitchell_1d(2.0 * p.y() / radius.y())
|
mitchell_1d(2.0 * p.x() / radius.x()) * mitchell_1d(2.0 * p.y() / radius.y())
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self { radius, b, c, sampler }
|
Self {
|
||||||
|
radius,
|
||||||
|
b,
|
||||||
|
c,
|
||||||
|
sampler,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mitchell_1d(&self, x: Float) -> Float {
|
fn mitchell_1d(&self, x: Float) -> Float {
|
||||||
let x = x.abs();
|
let x = x.abs();
|
||||||
if x <= 1.0 {
|
if x <= 1.0 {
|
||||||
((12.0 - 9.0 * self.b - 6.0 * self.c) * x.powi(3) +
|
((12.0 - 9.0 * self.b - 6.0 * self.c) * x.powi(3)
|
||||||
(-18.0 + 12.0 * self.b + 6.0 * self.c) * x.powi(2) +
|
+ (-18.0 + 12.0 * self.b + 6.0 * self.c) * x.powi(2)
|
||||||
(6.0 - 2.0 * self.b)) * (1.0 / 6.0)
|
+ (6.0 - 2.0 * self.b))
|
||||||
|
* (1.0 / 6.0)
|
||||||
} else if x <= 2.0 {
|
} else if x <= 2.0 {
|
||||||
((-self.b - 6.0 * self.c) * x.powi(3) +
|
((-self.b - 6.0 * self.c) * x.powi(3)
|
||||||
(6.0 * self.b + 30.0 * self.c) * x.powi(2) +
|
+ (6.0 * self.b + 30.0 * self.c) * x.powi(2)
|
||||||
(-12.0 * self.b - 48.0 * self.c) * x +
|
+ (-12.0 * self.b - 48.0 * self.c) * x
|
||||||
(8.0 * self.b + 24.0 * self.c)) * (1.0 / 6.0)
|
+ (8.0 * self.b + 24.0 * self.c))
|
||||||
|
* (1.0 / 6.0)
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
|
|
@ -243,10 +264,13 @@ impl MitchellFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for MitchellFilter {
|
impl FilterTrait for MitchellFilter {
|
||||||
fn radius(&self) -> Vector2f { self.radius }
|
fn radius(&self) -> Vector2f {
|
||||||
|
self.radius
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate(&self, p: Point2f) -> Float {
|
fn evaluate(&self, p: Point2f) -> Float {
|
||||||
self.mitchell_1d(2.0 * p.x() / self.radius.x()) * self.mitchell_1d(2.0 * p.y() / self.radius.y())
|
self.mitchell_1d(2.0 * p.x() / self.radius.x())
|
||||||
|
* self.mitchell_1d(2.0 * p.y() / self.radius.y())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn integral(&self) -> Float {
|
fn integral(&self) -> Float {
|
||||||
|
|
@ -258,6 +282,7 @@ impl FilterTrait for MitchellFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct LanczosSincFilter {
|
pub struct LanczosSincFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
pub tau: Float,
|
pub tau: Float,
|
||||||
|
|
@ -273,15 +298,22 @@ impl LanczosSincFilter {
|
||||||
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
|
windowed_sinc(p.x(), radius.x(), tau) * windowed_sinc(p.y(), radius.y(), tau)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Self { radius, tau, sampler }
|
Self {
|
||||||
|
radius,
|
||||||
|
tau,
|
||||||
|
sampler,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for LanczosSincFilter {
|
impl FilterTrait for LanczosSincFilter {
|
||||||
fn radius(&self) -> Vector2f { self.radius }
|
fn radius(&self) -> Vector2f {
|
||||||
|
self.radius
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate(&self, p: Point2f) -> Float {
|
fn evaluate(&self, p: Point2f) -> Float {
|
||||||
windowed_sinc(p.x(), self.radius.x(), self.tau) * windowed_sinc(p.y(), self.radius.y(), self.tau)
|
windowed_sinc(p.x(), self.radius.x(), self.tau)
|
||||||
|
* windowed_sinc(p.y(), self.radius.y(), self.tau)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn integral(&self) -> Float {
|
fn integral(&self) -> Float {
|
||||||
|
|
@ -312,6 +344,7 @@ impl FilterTrait for LanczosSincFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct TriangleFilter {
|
pub struct TriangleFilter {
|
||||||
pub radius: Vector2f,
|
pub radius: Vector2f,
|
||||||
}
|
}
|
||||||
|
|
@ -323,11 +356,12 @@ impl TriangleFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FilterTrait for TriangleFilter {
|
impl FilterTrait for TriangleFilter {
|
||||||
fn radius(&self) -> Vector2f { self.radius }
|
fn radius(&self) -> Vector2f {
|
||||||
|
self.radius
|
||||||
|
}
|
||||||
|
|
||||||
fn evaluate(&self, p: Point2f) -> Float {
|
fn evaluate(&self, p: Point2f) -> Float {
|
||||||
(self.radius.x() - p.x().abs()).max(0.0) *
|
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
|
||||||
(self.radius.y() - p.y().abs()).max(0.0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn integral(&self) -> Float {
|
fn integral(&self) -> Float {
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,4 @@ pub enum Integrator {
|
||||||
Sampler(SamplerIntegrator),
|
Sampler(SamplerIntegrator),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Integrator {
|
impl Integrator {}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,45 @@
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::bxdf::BSDF;
|
||||||
|
use crate::core::material::MaterialTrait;
|
||||||
use crate::core::medium::{Medium, MediumInterface};
|
use crate::core::medium::{Medium, MediumInterface};
|
||||||
use crate::core::light::Light;
|
use crate::core::pbrt::Float;
|
||||||
use crate::utils::geometry::{Vector3f, Normal3f, Point2f, Point3f, Point3fi, Normed, Ray, Dot};
|
use crate::geometry::{Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike};
|
||||||
|
use crate::lights::Light;
|
||||||
|
use crate::shapes::ShapeTrait;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct InteractionData {
|
pub struct InteractionData {
|
||||||
pub pi: Point3fi,
|
pub pi: Point3fi,
|
||||||
|
pub n: Normal3f,
|
||||||
pub time: Float,
|
pub time: Float,
|
||||||
pub wo: Vector3f, // Outgoing direction
|
pub wo: Vector3f,
|
||||||
|
pub medium_interface: Option<Arc<MediumInterface>>,
|
||||||
|
pub medium: Option<Arc<Medium>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Interaction: Send + Sync {
|
pub trait Interaction: Send + Sync {
|
||||||
fn get_common(&self) -> &InteractionData;
|
fn get_common(&self) -> &InteractionData;
|
||||||
|
fn get_common_mut(&mut self) -> &mut InteractionData;
|
||||||
fn as_any(&self) -> &dyn Any;
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
|
||||||
fn p(&self) -> Point3f { self.get_common().pi.into() }
|
fn p(&self) -> Point3f {
|
||||||
fn pi(&self) -> Point3fi { self.get_common().pi }
|
self.get_common().pi.into()
|
||||||
fn time(&self) -> Float { self.get_common().time }
|
}
|
||||||
fn wo(&self) -> Vector3f { self.get_common().wo }
|
fn pi(&self) -> Point3fi {
|
||||||
|
self.get_common().pi
|
||||||
|
}
|
||||||
|
fn time(&self) -> Float {
|
||||||
|
self.get_common().time
|
||||||
|
}
|
||||||
|
fn wo(&self) -> Vector3f {
|
||||||
|
self.get_common().wo
|
||||||
|
}
|
||||||
|
|
||||||
|
fn n(&self) -> Normal3f {
|
||||||
|
self.get_common().n
|
||||||
|
}
|
||||||
|
|
||||||
fn is_surface_interaction(&self) -> bool {
|
fn is_surface_interaction(&self) -> bool {
|
||||||
self.as_any().is::<SurfaceInteraction>()
|
self.as_any().is::<SurfaceInteraction>()
|
||||||
|
|
@ -29,13 +49,10 @@ pub trait Interaction: Send + Sync {
|
||||||
self.as_any().is::<MediumInteraction>()
|
self.as_any().is::<MediumInteraction>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determines the medium for a ray starting at this interaction.
|
|
||||||
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>>;
|
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>>;
|
||||||
|
|
||||||
/// Spawns a new ray starting from this interaction point.
|
|
||||||
fn spawn_ray(&self, d: Vector3f) -> Ray;
|
fn spawn_ray(&self, d: Vector3f) -> Ray;
|
||||||
|
|
||||||
/// Spawns a ray from this interaction to a specified point.
|
|
||||||
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
|
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
|
||||||
let origin = self.p();
|
let origin = self.p();
|
||||||
let direction = p2 - origin;
|
let direction = p2 - origin;
|
||||||
|
|
@ -48,13 +65,20 @@ pub trait Interaction: Send + Sync {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a ray from this interaction to another one.
|
|
||||||
/// The default implementation simply targets the other interaction's point.
|
|
||||||
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
|
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
|
||||||
self.spawn_ray_to_point(other.p())
|
self.spawn_ray_to_point(other.p())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn offset_ray_vector(&self, w: Vector3f) -> Point3f {
|
||||||
|
Ray::offset_origin(&self.pi(), &self.n(), &w)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn offset_ray_point(&self, pt: Point3f) -> Point3f {
|
||||||
|
self.offset_ray_vector(pt - self.p())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, Debug)]
|
||||||
pub struct ShadingGeometry {
|
pub struct ShadingGeometry {
|
||||||
pub n: Normal3f,
|
pub n: Normal3f,
|
||||||
pub dpdu: Vector3f,
|
pub dpdu: Vector3f,
|
||||||
|
|
@ -63,9 +87,9 @@ pub struct ShadingGeometry {
|
||||||
pub dndv: Normal3f,
|
pub dndv: Normal3f,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct SurfaceInteraction {
|
pub struct SurfaceInteraction {
|
||||||
pub common: InteractionData,
|
pub common: InteractionData,
|
||||||
pub n: Normal3f,
|
|
||||||
pub uv: Point2f,
|
pub uv: Point2f,
|
||||||
pub dpdu: Vector3f,
|
pub dpdu: Vector3f,
|
||||||
pub dpdv: Vector3f,
|
pub dpdv: Vector3f,
|
||||||
|
|
@ -73,16 +97,35 @@ pub struct SurfaceInteraction {
|
||||||
pub dndv: Normal3f,
|
pub dndv: Normal3f,
|
||||||
pub shading: ShadingGeometry,
|
pub shading: ShadingGeometry,
|
||||||
pub medium_interface: Option<Arc<MediumInterface>>,
|
pub medium_interface: Option<Arc<MediumInterface>>,
|
||||||
pub face_index: bool,
|
pub face_index: usize,
|
||||||
pub area_light: Option<Arc<Light>>,
|
pub area_light: Option<Arc<Light>>,
|
||||||
|
pub material: Option<Arc<dyn MaterialTrait>>,
|
||||||
pub dpdx: Vector3f,
|
pub dpdx: Vector3f,
|
||||||
pub dpdy: Vector3f,
|
pub dpdy: Vector3f,
|
||||||
pub dudx: Float,
|
pub dudx: Float,
|
||||||
pub dvdx: Float,
|
pub dvdx: Float,
|
||||||
pub dudy: Float,
|
pub dudy: Float,
|
||||||
pub dvdy: Float,
|
pub dvdy: Float,
|
||||||
// pub shape: Option<Arc<dyn Shape>>,
|
pub shape: Option<Arc<dyn ShapeTrait>>,
|
||||||
// pub bsdf: Option<BSDF>,
|
pub bsdf: Option<BSDF>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SurfaceInteraction {
|
||||||
|
pub fn set_intersection_properties(
|
||||||
|
&mut self,
|
||||||
|
mtl: Arc<dyn MaterialTrait>,
|
||||||
|
area: Arc<Light>,
|
||||||
|
prim_medium_interface: Option<Arc<MediumInterface>>,
|
||||||
|
ray_medium: Arc<Medium>,
|
||||||
|
) {
|
||||||
|
self.material = Some(mtl);
|
||||||
|
self.area_light = Some(area);
|
||||||
|
if prim_medium_interface.as_ref().map_or(false, |mi| mi.is_medium_transition()) {
|
||||||
|
self.common.medium_interface = prim_medium_interface;
|
||||||
|
} else {
|
||||||
|
self.common.medium = Some(ray_medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PhaseFunction;
|
pub struct PhaseFunction;
|
||||||
|
|
@ -94,12 +137,21 @@ pub struct MediumInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interaction for SurfaceInteraction {
|
impl Interaction for SurfaceInteraction {
|
||||||
fn get_common(&self) -> &InteractionData { &self.common }
|
fn get_common(&self) -> &InteractionData {
|
||||||
fn as_any(&self) -> &dyn Any { self }
|
&self.common
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_common_mut(&mut self) -> &mut InteractionData {
|
||||||
|
&mut self.common
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
|
fn get_medium(&self, w: Vector3f) -> Option<Arc<Medium>> {
|
||||||
self.medium_interface.as_ref().and_then(|interface| {
|
self.medium_interface.as_ref().and_then(|interface| {
|
||||||
if self.n.dot(w) > 0.0 {
|
if self.n().dot(w.into()) > 0.0 {
|
||||||
interface.outside.clone()
|
interface.outside.clone()
|
||||||
} else {
|
} else {
|
||||||
interface.inside.clone()
|
interface.inside.clone()
|
||||||
|
|
@ -108,13 +160,13 @@ impl Interaction for SurfaceInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_ray(&self, d: Vector3f) -> Ray {
|
fn spawn_ray(&self, d: Vector3f) -> Ray {
|
||||||
let mut ray = Ray::spawn(&self.pi(), &self.n, self.time(), d);
|
let mut ray = Ray::spawn(&self.pi(), &self.n(), self.time(), d);
|
||||||
ray.medium = self.get_medium(d);
|
ray.medium = self.get_medium(d);
|
||||||
ray
|
ray
|
||||||
}
|
}
|
||||||
|
|
||||||
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
|
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
|
||||||
let mut ray = Ray::spawn_to_point(&self.pi(), &self.n, self.time(), p2);
|
let mut ray = Ray::spawn_to_point(&self.pi(), &self.n(), self.time(), p2);
|
||||||
ray.medium = self.get_medium(ray.d);
|
ray.medium = self.get_medium(ray.d);
|
||||||
ray
|
ray
|
||||||
}
|
}
|
||||||
|
|
@ -122,7 +174,13 @@ impl Interaction for SurfaceInteraction {
|
||||||
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
|
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
|
||||||
// Check if the other interaction is a surface to use the robust spawn method
|
// Check if the other interaction is a surface to use the robust spawn method
|
||||||
if let Some(si_to) = other.as_any().downcast_ref::<SurfaceInteraction>() {
|
if let Some(si_to) = other.as_any().downcast_ref::<SurfaceInteraction>() {
|
||||||
let mut ray = Ray::spawn_to_interaction(&self.pi(), &self.n, self.time(), &si_to.pi(), &si_to.n);
|
let mut ray = Ray::spawn_to_interaction(
|
||||||
|
&self.pi(),
|
||||||
|
&self.n(),
|
||||||
|
self.time(),
|
||||||
|
&si_to.pi(),
|
||||||
|
&si_to.n(),
|
||||||
|
);
|
||||||
ray.medium = self.get_medium(ray.d);
|
ray.medium = self.get_medium(ray.d);
|
||||||
ray
|
ray
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -133,7 +191,17 @@ impl Interaction for SurfaceInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SurfaceInteraction {
|
impl SurfaceInteraction {
|
||||||
pub fn new(pi: Point3fi, uv: Point2f, wo: Vector3f, dpdu: Vector3f, dpdv: Vector3f, dndu: Normal3f, dndv: Normal3f, time: Float, flip: bool) -> Self {
|
pub fn new(
|
||||||
|
pi: Point3fi,
|
||||||
|
uv: Point2f,
|
||||||
|
wo: Vector3f,
|
||||||
|
dpdu: Vector3f,
|
||||||
|
dpdv: Vector3f,
|
||||||
|
dndu: Normal3f,
|
||||||
|
dndv: Normal3f,
|
||||||
|
time: Float,
|
||||||
|
flip: bool,
|
||||||
|
) -> Self {
|
||||||
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
|
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
|
||||||
let mut shading_n = n;
|
let mut shading_n = n;
|
||||||
|
|
||||||
|
|
@ -143,16 +211,22 @@ impl SurfaceInteraction {
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
common: InteractionData { pi, time, wo },
|
common: InteractionData { pi, n, time, wo, medium_interface: None, medium: None },
|
||||||
n,
|
|
||||||
uv,
|
uv,
|
||||||
dpdu,
|
dpdu,
|
||||||
dpdv,
|
dpdv,
|
||||||
dndu,
|
dndu,
|
||||||
dndv,
|
dndv,
|
||||||
shading: ShadingGeometry { n: shading_n, dpdu, dpdv, dndu, dndv },
|
shading: ShadingGeometry {
|
||||||
|
n: shading_n,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
dndu,
|
||||||
|
dndv,
|
||||||
|
},
|
||||||
medium_interface: None,
|
medium_interface: None,
|
||||||
face_index: false,
|
material: None,
|
||||||
|
face_index: 0,
|
||||||
area_light: None,
|
area_light: None,
|
||||||
dpdx: Vector3f::zero(),
|
dpdx: Vector3f::zero(),
|
||||||
dpdy: Vector3f::zero(),
|
dpdy: Vector3f::zero(),
|
||||||
|
|
@ -160,24 +234,74 @@ impl SurfaceInteraction {
|
||||||
dudy: 0.0,
|
dudy: 0.0,
|
||||||
dvdx: 0.0,
|
dvdx: 0.0,
|
||||||
dvdy: 0.0,
|
dvdy: 0.0,
|
||||||
|
shape: None,
|
||||||
|
bsdf: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_shading_geometry(&mut self, ns: Normal3f, dpdus: Vector3f, dpdvs: Vector3f, dndus: Normal3f, dndvs: Normal3f, orientation: bool) {
|
pub fn new_with_face(
|
||||||
|
pi: Point3fi,
|
||||||
|
uv: Point2f,
|
||||||
|
wo: Vector3f,
|
||||||
|
dpdu: Vector3f,
|
||||||
|
dpdv: Vector3f,
|
||||||
|
dndu: Normal3f,
|
||||||
|
dndv: Normal3f,
|
||||||
|
time: Float,
|
||||||
|
flip: bool,
|
||||||
|
face_index: usize,
|
||||||
|
) -> Self {
|
||||||
|
let mut si = Self::new(pi, uv, wo, dpdu, dpdv, dndu, dndv, time, flip);
|
||||||
|
si.face_index = face_index;
|
||||||
|
si
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_shading_geometry(
|
||||||
|
&mut self,
|
||||||
|
ns: Normal3f,
|
||||||
|
dpdus: Vector3f,
|
||||||
|
dpdvs: Vector3f,
|
||||||
|
dndus: Normal3f,
|
||||||
|
dndvs: Normal3f,
|
||||||
|
orientation: bool,
|
||||||
|
) {
|
||||||
self.shading.n = ns;
|
self.shading.n = ns;
|
||||||
if orientation {
|
if orientation {
|
||||||
self.n = self.n.face_forward(self.shading.n.into());
|
self.common.n = self.n().face_forward(self.shading.n.into());
|
||||||
}
|
}
|
||||||
self.shading.dpdu = dpdus;
|
self.shading.dpdu = dpdus;
|
||||||
self.shading.dpdv = dpdvs;
|
self.shading.dpdv = dpdvs;
|
||||||
self.shading.dndu = dndus.into();
|
self.shading.dndu = dndus.into();
|
||||||
self.shading.dndv = dndvs.into();
|
self.shading.dndv = dndvs.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self {
|
||||||
|
let mut si = Self::default();
|
||||||
|
si.common = InteractionData {
|
||||||
|
pi,
|
||||||
|
n,
|
||||||
|
time: 0.,
|
||||||
|
wo: Vector3f::zero(),
|
||||||
|
medium_interface: None,
|
||||||
|
medium: None,
|
||||||
|
};
|
||||||
|
si.uv = uv;
|
||||||
|
si
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Interaction for MediumInteraction {
|
impl Interaction for MediumInteraction {
|
||||||
fn get_common(&self) -> &InteractionData { &self.common }
|
fn get_common(&self) -> &InteractionData {
|
||||||
fn as_any(&self) -> &dyn Any { self }
|
&self.common
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_common_mut(&mut self) -> &mut InteractionData {
|
||||||
|
&mut self.common
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any(&self) -> &dyn Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
fn get_medium(&self, _w: Vector3f) -> Option<Arc<Medium>> {
|
fn get_medium(&self, _w: Vector3f) -> Option<Arc<Medium>> {
|
||||||
Some(self.medium.clone())
|
Some(self.medium.clone())
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
pub struct DiffuseAreaLight;
|
|
||||||
pub struct DistantLight;
|
|
||||||
pub struct GonioPhotometricLight;
|
|
||||||
pub struct InfiniteAreaLight;
|
|
||||||
pub struct PointLight;
|
|
||||||
pub struct ProjectionLight;
|
|
||||||
pub struct SpotLight;
|
|
||||||
|
|
||||||
pub enum Light {
|
|
||||||
DiffuseArea(Box<DiffuseAreaLight>),
|
|
||||||
Distant(Box<DistantLight>),
|
|
||||||
GonioPhotometric(Box<GonioPhotometricLight>),
|
|
||||||
InfiniteArea(Box<InfiniteAreaLight>),
|
|
||||||
Point(Box<PointLight>),
|
|
||||||
Projection(Box<ProjectionLight>),
|
|
||||||
Spot(Box<SpotLight>),
|
|
||||||
}
|
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct CoatedDiffuseMaterial;
|
pub struct CoatedDiffuseMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct CoatedConductorMaterial;
|
pub struct CoatedConductorMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct ConductorMaterial;
|
pub struct ConductorMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct DielectricMaterial;
|
pub struct DielectricMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct DiffuseMaterial;
|
pub struct DiffuseMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct DiffuseTransmissionMaterial;
|
pub struct DiffuseTransmissionMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct HairMaterial;
|
pub struct HairMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct MeasuredMaterial;
|
pub struct MeasuredMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct SubsurfaceMaterial;
|
pub struct SubsurfaceMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct ThinDielectricMaterial;
|
pub struct ThinDielectricMaterial;
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub struct MixMaterial;
|
pub struct MixMaterial;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum Material {
|
pub enum Material {
|
||||||
CoatedDiffuse(CoatedDiffuseMaterial),
|
CoatedDiffuse(CoatedDiffuseMaterial),
|
||||||
CoatedConductor(CoatedConductorMaterial),
|
CoatedConductor(CoatedConductorMaterial),
|
||||||
|
|
@ -24,5 +36,6 @@ pub enum Material {
|
||||||
Mix(MixMaterial),
|
Mix(MixMaterial),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Material {
|
impl Material {}
|
||||||
}
|
|
||||||
|
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,7 @@ pub enum Medium {
|
||||||
NanoVDB(NanoVDBMedium),
|
NanoVDB(NanoVDBMedium),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone)]
|
||||||
#[derive(Default, Clone)]
|
|
||||||
pub struct MediumInterface {
|
pub struct MediumInterface {
|
||||||
pub inside: Option<Arc<Medium>>,
|
pub inside: Option<Arc<Medium>>,
|
||||||
pub outside: Option<Arc<Medium>>,
|
pub outside: Option<Arc<Medium>>,
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,10 @@ pub mod cie;
|
||||||
pub mod film;
|
pub mod film;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod integrator;
|
pub mod integrator;
|
||||||
pub mod light;
|
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
pub mod material;
|
pub mod material;
|
||||||
pub mod medium;
|
pub mod medium;
|
||||||
pub mod pbrt;
|
pub mod pbrt;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod shape;
|
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
|
|
|
||||||
133
src/core/pbrt.rs
133
src/core/pbrt.rs
|
|
@ -1,7 +1,7 @@
|
||||||
|
use crate::geometry::{Lerp, Point2f, Vector2f, Vector3f};
|
||||||
use num_traits::Num;
|
use num_traits::Num;
|
||||||
use std::ops::{Add, Mul};
|
use std::ops::{Add, Mul};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||||
use crate::utils::geometry::{Point2f, Vector2f, Vector3f};
|
|
||||||
|
|
||||||
pub type Float = f32;
|
pub type Float = f32;
|
||||||
|
|
||||||
|
|
@ -17,11 +17,12 @@ pub const PI_OVER_4: Float = 0.785_398_163_397_448_309_61;
|
||||||
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
|
pub const SQRT_2: Float = 1.414_213_562_373_095_048_80;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn lerp<T>(x: T, a: T, b: T) -> T
|
pub fn lerp<Value, Factor>(t: Factor, a: Value, b: Value) -> Value
|
||||||
where
|
where
|
||||||
T: Num + Copy,
|
Factor: Copy + Num,
|
||||||
|
Value: Copy + Lerp<Factor>,
|
||||||
{
|
{
|
||||||
(T::one() - x) * a + x * b
|
Value::lerp(t, a, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
|
pub fn linear_pdf<T>(x: T, a: T, b: T) -> T
|
||||||
|
|
@ -45,34 +46,6 @@ where
|
||||||
u * (a + b)
|
u * (a + b)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_uniform_disk_polar(u: Point2f) -> Point2f {
|
|
||||||
let r = u[0].sqrt();
|
|
||||||
let theta = 2. * PI * u[1];
|
|
||||||
Point2f::new(r * theta.cos(), r * theta.sin())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample_uniform_disk_concentric(u: Point2f) -> Vector2f {
|
|
||||||
// Map _u_ to $[-1,1]^2$ and handle degeneracy at the origin
|
|
||||||
let u_vector = u - Point2f::new(0., 0.);
|
|
||||||
let u_offset = 2. * u_vector - Vector2f::new(1., 1.);
|
|
||||||
if u_offset.x() == 0. && u_offset.y() == 0. {
|
|
||||||
return Vector2f::new(0., 0.)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply concentric mapping to point
|
|
||||||
let theta: Float;
|
|
||||||
let r: Float;
|
|
||||||
if u_offset.x().abs() > u_offset.y().abs() {
|
|
||||||
r = u_offset.x();
|
|
||||||
theta = PI_OVER_4 * (u_offset.y() / u_offset.x());
|
|
||||||
} else {
|
|
||||||
r = u_offset.y();
|
|
||||||
theta = PI_OVER_2 - PI_OVER_4 * (u_offset.x() / u_offset.y());
|
|
||||||
}
|
|
||||||
let s_vector = Point2f::new(theta.cos(), theta.sin()) - Point2f::new(0., 0.);
|
|
||||||
return r * s_vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clamp_t<T>(val: T, low: T, high: T) -> T
|
pub fn clamp_t<T>(val: T, low: T, high: T) -> T
|
||||||
where
|
where
|
||||||
T: PartialOrd,
|
T: PartialOrd,
|
||||||
|
|
@ -88,54 +61,6 @@ where
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn float_to_bits(f: Float) -> u32 {
|
|
||||||
f.to_bits()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn bits_to_float(ui: u32) -> Float {
|
|
||||||
f32::from_bits(ui)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Corresponding types for 64-bit floats
|
|
||||||
#[inline]
|
|
||||||
pub fn f64_to_bits(f: f64) -> u64 {
|
|
||||||
f.to_bits()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn next_float_up(v: Float) -> Float {
|
|
||||||
if v.is_infinite() && v > 0.0 {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
let v = if v == -0.0 { 0.0 } else { v };
|
|
||||||
|
|
||||||
let mut ui = float_to_bits(v);
|
|
||||||
if v >= 0.0 {
|
|
||||||
ui += 1;
|
|
||||||
} else {
|
|
||||||
ui -= 1;
|
|
||||||
}
|
|
||||||
bits_to_float(ui)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn next_float_down(v: Float) -> Float {
|
|
||||||
if v.is_infinite() && v < 0.0 {
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
let v = if v == 0.0 { -0.0 } else { v };
|
|
||||||
|
|
||||||
let mut ui = float_to_bits(v);
|
|
||||||
if v > 0.0 {
|
|
||||||
ui -= 1;
|
|
||||||
} else {
|
|
||||||
ui += 1;
|
|
||||||
}
|
|
||||||
bits_to_float(ui)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
|
||||||
if coeffs.is_empty() {
|
if coeffs.is_empty() {
|
||||||
|
|
@ -174,22 +99,52 @@ where
|
||||||
clamp_t(result, 0, sz - 2)
|
clamp_t(result, 0, sz - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn gamma(n: i32) -> Float {
|
pub fn gamma(n: i32) -> Float {
|
||||||
return (n as Float * MACHINE_EPSILON) / (1. - n as Float * MACHINE_EPSILON);
|
return (n as Float * MACHINE_EPSILON) / (1. - n as Float * MACHINE_EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn square<T>(n: T) -> T
|
|
||||||
where
|
|
||||||
T: Mul<Output = T> + Copy { n * n }
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RenderingCoordinateSystem {
|
pub enum RenderingCoordinateSystem {
|
||||||
Camera,
|
Camera,
|
||||||
CameraWorld,
|
CameraWorld,
|
||||||
World,
|
World,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the static counters. These are thread-safe.
|
||||||
|
pub static RARE_EVENT_TOTAL_CALLS: AtomicU64 = AtomicU64::new(0);
|
||||||
|
pub static RARE_EVENT_CONDITION_MET: AtomicU64 = AtomicU64::new(0);
|
||||||
|
|
||||||
|
// A macro to encapsulate the logic
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! check_rare {
|
||||||
|
($frequency_threshold:expr, $condition:expr) => {
|
||||||
|
// We only run the check periodically to reduce overhead.
|
||||||
|
// PBRT's implementation does something similar.
|
||||||
|
const CHECK_INTERVAL: u64 = 4096;
|
||||||
|
|
||||||
|
// Atomically increment the total call counter.
|
||||||
|
// Ordering::Relaxed is fine because we don't need to synchronize memory.
|
||||||
|
let total_calls = RARE_EVENT_TOTAL_CALLS.fetch_add(1, SyncOrdering::Relaxed);
|
||||||
|
|
||||||
|
if $condition {
|
||||||
|
// If the rare condition is met, increment that counter.
|
||||||
|
RARE_EVENT_CONDITION_MET.fetch_add(1, SyncOrdering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only perform the expensive division and check periodically.
|
||||||
|
if (total_calls + 1) % CHECK_INTERVAL == 0 {
|
||||||
|
let met_count = RARE_EVENT_CONDITION_MET.load(SyncOrdering::Relaxed);
|
||||||
|
if met_count > 0 {
|
||||||
|
let frequency = met_count as f64 / (total_calls + 1) as f64;
|
||||||
|
if frequency > $frequency_threshold {
|
||||||
|
// In a real scenario, you might log an error or panic.
|
||||||
|
panic!(
|
||||||
|
"Rare event occurred with frequency {} which is > threshold {}",
|
||||||
|
frequency, $frequency_threshold
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,69 @@
|
||||||
// use crate::core::medium::MediumInterface;
|
use crate::core::interaction::Interaction;
|
||||||
// use crate::core::light::Light;
|
use crate::core::pbrt::Float;
|
||||||
// use crate::core::geometry::Bounds3f;
|
use crate::geometry::{Bounds3f, Ray};
|
||||||
//
|
use crate::shapes::{ShapeIntersection, ShapeTrait};
|
||||||
//
|
use crate::core::material::MaterialTrait;
|
||||||
pub struct GeometricPrimitive;
|
use crate::core::medium::MediumInterface;
|
||||||
|
use crate::lights::Light;
|
||||||
|
use crate::core::texture::{FloatTextureTrait, TextureEvalContext};
|
||||||
|
use crate::utils::hash::hash_float;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub trait PrimitiveTrait: Send + Sync + std::fmt::Debug {
|
||||||
|
fn bounds(&self) -> Bounds3f;
|
||||||
|
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
|
||||||
|
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GeometricPrimitive {
|
||||||
|
shape: Arc<dyn ShapeTrait>,
|
||||||
|
material: Arc<dyn MaterialTrait>,
|
||||||
|
area_light: Arc<Light>,
|
||||||
|
medium_interface: Arc<MediumInterface>,
|
||||||
|
alpha: Option<Arc<dyn FloatTextureTrait>>,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrimitiveTrait for GeometricPrimitive {
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
self.shape.bounds()
|
||||||
|
}
|
||||||
|
fn intersect(&self, r: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
let mut si = self.shape.intersect(r, t_max)?;
|
||||||
|
let ctx: TextureEvalContext = si.intr().into();
|
||||||
|
let Some(ref alpha) = self.alpha else { return None };
|
||||||
|
let a = alpha.evaluate(&ctx);
|
||||||
|
if a < 1. {
|
||||||
|
let u = if a <= 0. { 1. } else { hash_float((r.o, r.d)) };
|
||||||
|
if u > a {
|
||||||
|
let r_next = si.intr().spawn_ray(r.d);
|
||||||
|
let new_t_max = t_max.map(|t| t - si.t_hit())?;
|
||||||
|
let mut si_next = self.intersect(&r_next, Some(new_t_max - si.t_hit()))?;
|
||||||
|
si_next.set_t_hit(si_next.t_hit() + si.t_hit());
|
||||||
|
return Some(si_next)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
si.intr_mut().set_intersection_properties(self.material.clone(), self.area_light.clone(), Some(self.medium_interface.clone()), r.medium.clone()?);
|
||||||
|
Some(si)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, r: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
if self.alpha.is_some() {
|
||||||
|
self.intersect(r, t_max).is_some()
|
||||||
|
} else {
|
||||||
|
self.shape.intersect_p(r, t_max)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SimplePrimitive {
|
||||||
|
shape: Arc<dyn ShapeTrait>,
|
||||||
|
material: Arc<dyn MaterialTrait>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct TransformedPrimitive;
|
pub struct TransformedPrimitive;
|
||||||
pub struct AnimatedPrimitive;
|
pub struct AnimatedPrimitive;
|
||||||
pub struct BVHAggregatePrimitive;
|
pub struct BVHAggregatePrimitive;
|
||||||
|
|
@ -14,7 +74,7 @@ pub enum Primitive {
|
||||||
Transformed(TransformedPrimitive),
|
Transformed(TransformedPrimitive),
|
||||||
Animated(AnimatedPrimitive),
|
Animated(AnimatedPrimitive),
|
||||||
BVH(BVHAggregatePrimitive),
|
BVH(BVHAggregatePrimitive),
|
||||||
KdTree(KdTreeAggregate)
|
KdTree(KdTreeAggregate),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Primitive {
|
impl Primitive {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::utils::geometry::{Point2f, Point2i, Vector2f, Bounds2f};
|
use crate::core::pbrt::{Float, PI, PI_OVER_2, PI_OVER_4, find_interval, lerp};
|
||||||
use crate::core::pbrt::{find_interval, Float, PI, PI_OVER_2, PI_OVER_4, lerp};
|
use crate::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
|
||||||
use crate::utils::containers::Array2D;
|
use crate::utils::containers::Array2D;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -12,10 +12,16 @@ pub struct CameraSample {
|
||||||
|
|
||||||
impl Default for CameraSample {
|
impl Default for CameraSample {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { p_film: Point2f::default(), p_lens: Point2f::default(), time: 0.0, filter_weight: 1.0 }
|
Self {
|
||||||
|
p_film: Point2f::default(),
|
||||||
|
p_lens: Point2f::default(),
|
||||||
|
time: 0.0,
|
||||||
|
filter_weight: 1.0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct PiecewiseConstant1D {
|
pub struct PiecewiseConstant1D {
|
||||||
pub func: Vec<Float>,
|
pub func: Vec<Float>,
|
||||||
pub cdf: Vec<Float>,
|
pub cdf: Vec<Float>,
|
||||||
|
|
@ -27,6 +33,7 @@ impl PiecewiseConstant1D {
|
||||||
pub fn new(f: &[Float]) -> Self {
|
pub fn new(f: &[Float]) -> Self {
|
||||||
Self::new_with_bounds(f, 0., 1.)
|
Self::new_with_bounds(f, 0., 1.)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
|
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
|
||||||
assert!(max > min);
|
assert!(max > min);
|
||||||
let n = f.len();
|
let n = f.len();
|
||||||
|
|
@ -34,11 +41,13 @@ impl PiecewiseConstant1D {
|
||||||
for &val in f {
|
for &val in f {
|
||||||
func.push(val.abs());
|
func.push(val.abs());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut cdf = vec![0.; n + 1];
|
let mut cdf = vec![0.; n + 1];
|
||||||
for i in 1..=n {
|
for i in 1..=n {
|
||||||
debug_assert!(func[i - 1] >= 0.);
|
debug_assert!(func[i - 1] >= 0.);
|
||||||
cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float;
|
cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float;
|
||||||
}
|
}
|
||||||
|
|
||||||
let func_integral = cdf[n];
|
let func_integral = cdf[n];
|
||||||
if func_integral == 0. {
|
if func_integral == 0. {
|
||||||
for i in 1..=n {
|
for i in 1..=n {
|
||||||
|
|
@ -49,14 +58,24 @@ impl PiecewiseConstant1D {
|
||||||
cdf[i] /= func_integral;
|
cdf[i] /= func_integral;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Self { func, cdf, func_integral, min, max }
|
|
||||||
|
Self {
|
||||||
|
func,
|
||||||
|
cdf,
|
||||||
|
func_integral,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn integral(&self) -> Float {
|
pub fn integral(&self) -> Float {
|
||||||
self.func_integral
|
self.func_integral
|
||||||
}
|
}
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
self.func.len()
|
self.func.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(&self, u: Float) -> (Float, Float, usize) {
|
pub fn sample(&self, u: Float) -> (Float, Float, usize) {
|
||||||
let o = find_interval(self.cdf.len(), |idx| self.cdf[idx] <= u);
|
let o = find_interval(self.cdf.len(), |idx| self.cdf[idx] <= u);
|
||||||
let mut du = u - self.cdf[o];
|
let mut du = u - self.cdf[o];
|
||||||
|
|
@ -64,11 +83,16 @@ impl PiecewiseConstant1D {
|
||||||
du /= self.cdf[o + 1] - self.cdf[o];
|
du /= self.cdf[o + 1] - self.cdf[o];
|
||||||
}
|
}
|
||||||
debug_assert!(!du.is_nan());
|
debug_assert!(!du.is_nan());
|
||||||
let value = lerp((o as Float + du) / self.len() as Float, self.min, self.max);
|
let value = lerp((o as Float + du) / self.size() as Float, self.min, self.max);
|
||||||
let pdf_val = if self.func_integral > 0. { self.func[o] / self.func_integral } else { 0. };
|
let pdf_val = if self.func_integral > 0. {
|
||||||
|
self.func[o] / self.func_integral
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
};
|
||||||
(value, pdf_val, o)
|
(value, pdf_val, o)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct PiecewiseConstant2D {
|
pub struct PiecewiseConstant2D {
|
||||||
pub p_conditional_v: Vec<PiecewiseConstant1D>,
|
pub p_conditional_v: Vec<PiecewiseConstant1D>,
|
||||||
pub p_marginal: PiecewiseConstant1D,
|
pub p_marginal: PiecewiseConstant1D,
|
||||||
|
|
@ -76,10 +100,7 @@ pub struct PiecewiseConstant2D {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PiecewiseConstant2D {
|
impl PiecewiseConstant2D {
|
||||||
pub fn new(data: &Array2D<f32>, domain: Bounds2f) -> Self {
|
pub fn new(data: &Array2D<Float>, nu: usize, nv: usize, domain: Bounds2f) -> Self {
|
||||||
let resolution = data.size();
|
|
||||||
let nu = resolution.x as usize;
|
|
||||||
let nv = resolution.y as usize;
|
|
||||||
let mut p_conditional_v = Vec::with_capacity(nv);
|
let mut p_conditional_v = Vec::with_capacity(nv);
|
||||||
for v in 0..nv {
|
for v in 0..nv {
|
||||||
let start = v * nu;
|
let start = v * nu;
|
||||||
|
|
@ -90,19 +111,45 @@ impl PiecewiseConstant2D {
|
||||||
domain.p_max.x(),
|
domain.p_max.x(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
// Compute marginal sampling distribution from the integrals of the conditional distributions
|
|
||||||
let marginal_func: Vec<f32> = p_conditional_v.iter().map(|p| p.integral()).collect();
|
let marginal_func: Vec<Float> = p_conditional_v.iter().map(|p| p.integral()).collect();
|
||||||
let p_marginal = PiecewiseConstant1D::new_with_bounds(
|
let p_marginal = PiecewiseConstant1D::new_with_bounds(
|
||||||
&marginal_func,
|
&marginal_func,
|
||||||
domain.p_min.y(),
|
domain.p_min.y(),
|
||||||
domain.p_max.y(),
|
domain.p_max.y(),
|
||||||
);
|
);
|
||||||
Self { p_conditional_v, p_marginal, domain }
|
|
||||||
|
Self {
|
||||||
|
p_conditional_v,
|
||||||
|
p_marginal,
|
||||||
|
domain,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_bounds(data: &Array2D<Float>, domain: Bounds2f) -> Self {
|
||||||
|
Self::new(data, data.x_size() as usize, data.y_size() as usize, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_data(data: &Array2D<Float>, nx: usize, ny: usize) -> Self {
|
||||||
|
Self::new(
|
||||||
|
data,
|
||||||
|
nx,
|
||||||
|
ny,
|
||||||
|
Bounds2f::from_points(Point2f::new(0., 0.), Point2f::new(1., 1.)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolution(&self) -> Point2i {
|
||||||
|
Point2i::new(
|
||||||
|
self.p_conditional_v[0].size() as i32,
|
||||||
|
self.p_conditional_v[1].size() as i32,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn integral(&self) -> f32 {
|
pub fn integral(&self) -> f32 {
|
||||||
self.p_marginal.integral()
|
self.p_marginal.integral()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
|
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
|
||||||
let (d1, pdf1, off_y) = self.p_marginal.sample(u.y());
|
let (d1, pdf1, off_y) = self.p_marginal.sample(u.y());
|
||||||
let (d0, pdf0, off_x) = self.p_conditional_v[off_y].sample(u.x());
|
let (d0, pdf0, off_x) = self.p_conditional_v[off_y].sample(u.x());
|
||||||
|
|
@ -110,14 +157,15 @@ impl PiecewiseConstant2D {
|
||||||
let offset = Point2i::new(off_x as i32, off_y as i32);
|
let offset = Point2i::new(off_x as i32, off_y as i32);
|
||||||
(Point2f::new(d0, d1), pdf, offset)
|
(Point2f::new(d0, d1), pdf, offset)
|
||||||
}
|
}
|
||||||
/// Calculates the PDF at a given point.
|
|
||||||
pub fn pdf(&self, p: Point2f) -> f32 {
|
pub fn pdf(&self, p: Point2f) -> f32 {
|
||||||
let p_offset = self.domain.offset(&p);
|
let p_offset = self.domain.offset(&p);
|
||||||
let nu = self.p_conditional_v[0].len();
|
let nu = self.p_conditional_v[0].size();
|
||||||
let nv = self.p_marginal.len();
|
let nv = self.p_marginal.size();
|
||||||
|
|
||||||
let iu = (p_offset.x() * nu as f32).clamp(0.0, nu as f32 - 1.0) as usize;
|
let iu = (p_offset.x() * nu as f32).clamp(0.0, nu as f32 - 1.0) as usize;
|
||||||
let iv = (p_offset.y() * nv as f32).clamp(0.0, nv as f32 - 1.0) as usize;
|
let iv = (p_offset.y() * nv as f32).clamp(0.0, nv as f32 - 1.0) as usize;
|
||||||
|
|
||||||
let integral = self.p_marginal.integral();
|
let integral = self.p_marginal.integral();
|
||||||
if integral == 0.0 {
|
if integral == 0.0 {
|
||||||
0.0
|
0.0
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,122 @@
|
||||||
pub struct TextureEvalContext;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::geometry::{Point3f, Vector3f, Normal3f, Point2f};
|
||||||
|
use crate::core::interaction::SurfaceInteraction;
|
||||||
|
|
||||||
|
pub struct TextureEvalContext {
|
||||||
|
p: Point3f,
|
||||||
|
dpdx: Vector3f,
|
||||||
|
dpdy: Vector3f,
|
||||||
|
n: Normal3f,
|
||||||
|
uv: Point2f,
|
||||||
|
// All 0
|
||||||
|
dudx: Float,
|
||||||
|
dudy: Float,
|
||||||
|
dvdx: Float,
|
||||||
|
dvdy: Float,
|
||||||
|
face_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextureEvalContext {
|
||||||
|
pub fn new(p: Point3f,
|
||||||
|
dpdx: Vector3f,
|
||||||
|
dpdy: Vector3f,
|
||||||
|
n: Normal3f,
|
||||||
|
uv: Point2f,
|
||||||
|
dudx: Float,
|
||||||
|
dudy: Float,
|
||||||
|
dvdx: Float,
|
||||||
|
dvdy: Float,
|
||||||
|
face_index: usize,
|
||||||
|
) -> Self {
|
||||||
|
Self {p, dpdx, dpdy, n, uv, dudx, dudy, dvdx, dvdy , face_index }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&SurfaceInteraction> for TextureEvalContext {
|
||||||
|
fn from(si: &SurfaceInteraction) -> Self {
|
||||||
|
Self {
|
||||||
|
p: si.common.pi.into(),
|
||||||
|
dpdx: si.dpdx,
|
||||||
|
dpdy: si.dpdy,
|
||||||
|
n: si.common.n,
|
||||||
|
uv: si.uv,
|
||||||
|
dudx: si.dudx,
|
||||||
|
dudy: si.dudy,
|
||||||
|
dvdx: si.dvdx,
|
||||||
|
dvdy: si.dvdy,
|
||||||
|
face_index: si.face_index,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait FloatTextureTrait: Send + Sync + std::fmt::Debug {
|
||||||
|
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct FloatImageTexture;
|
pub struct FloatImageTexture;
|
||||||
pub struct GPUFloatImageTexture;
|
|
||||||
pub struct FloatMixTexture;
|
|
||||||
pub struct FloatDirectionMixTexture;
|
|
||||||
pub struct FloatScaledTexture;
|
|
||||||
pub struct FloatConstantTexture;
|
|
||||||
pub struct FloatBilerpTexture;
|
|
||||||
pub struct FloatCheckerboardTexture;
|
|
||||||
pub struct FloatDotsTexture;
|
|
||||||
pub struct FBmTexture;
|
|
||||||
pub struct FloatPtexTexture;
|
|
||||||
pub struct GPUFloatPtex;
|
|
||||||
pub struct WindyTexture;
|
|
||||||
pub struct WrinkledTexture;
|
|
||||||
|
|
||||||
|
impl FloatTextureTrait for FloatImageTexture {
|
||||||
|
fn evaluate(&self, _ctx: &TextureEvalContext) -> Float {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GPUFloatImageTexture;
|
||||||
|
impl FloatTextureTrait for GPUFloatImageTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatMixTexture;
|
||||||
|
impl FloatTextureTrait for FloatMixTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatDirectionMixTexture;
|
||||||
|
impl FloatTextureTrait for FloatDirectionMixTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatScaledTexture;
|
||||||
|
impl FloatTextureTrait for FloatScaledTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatConstantTexture;
|
||||||
|
impl FloatTextureTrait for FloatConstantTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatBilerpTexture;
|
||||||
|
impl FloatTextureTrait for FloatBilerpTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatCheckerboardTexture;
|
||||||
|
impl FloatTextureTrait for FloatCheckerboardTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatDotsTexture;
|
||||||
|
impl FloatTextureTrait for FloatDotsTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FBmTexture;
|
||||||
|
impl FloatTextureTrait for FBmTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct FloatPtexTexture;
|
||||||
|
impl FloatTextureTrait for FloatPtexTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GPUFloatPtex;
|
||||||
|
impl FloatTextureTrait for GPUFloatPtex {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WindyTexture;
|
||||||
|
impl FloatTextureTrait for WindyTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct WrinkledTexture;
|
||||||
|
impl FloatTextureTrait for WrinkledTexture {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum FloatTexture {
|
pub enum FloatTexture {
|
||||||
FloatImage(FloatImageTexture),
|
FloatImage(FloatImageTexture),
|
||||||
GPUFloatImage(GPUFloatImageTexture),
|
GPUFloatImage(GPUFloatImageTexture),
|
||||||
|
|
@ -32,25 +134,25 @@ pub enum FloatTexture {
|
||||||
Wrinkled(WrinkledTexture),
|
Wrinkled(WrinkledTexture),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FloatTexture {
|
impl FloatTextureTrait for FloatTexture {
|
||||||
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
|
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
|
||||||
// match self {
|
match self {
|
||||||
// FloatTexture::FloatImage(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatImage(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
|
FloatTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatMix(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatMix(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatScaled(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatScaled(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatConstant(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatConstant(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatBilerp(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatBilerp(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatDots(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatDots(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FBm(texture) => texture.evaluate(ctx),
|
FloatTexture::FBm(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::FloatPtex(texture) => texture.evaluate(ctx),
|
FloatTexture::FloatPtex(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
|
FloatTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::Windy(texture) => texture.evaluate(ctx),
|
FloatTexture::Windy(texture) => texture.evaluate(ctx),
|
||||||
// FloatTexture::Wrinkled(texture) => texture.evaluate(ctx),
|
FloatTexture::Wrinkled(texture) => texture.evaluate(ctx),
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RGBConstantTexture;
|
pub struct RGBConstantTexture;
|
||||||
|
|
@ -86,22 +188,22 @@ pub enum SpectrumTexture {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SpectrumTexture {
|
impl SpectrumTexture {
|
||||||
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
|
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
|
||||||
// match self {
|
// match self {
|
||||||
// SpectrumTexture::FloatImage(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatImage(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatMix(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatMix(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatScaled(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatScaled(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatConstant(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatConstant(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatBilerp(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatBilerp(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatDots(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatDots(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FBm(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FBm(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::FloatPtex(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::FloatPtex(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::Windy(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::Windy(texture) => texture.evaluate(ctx),
|
||||||
// SpectrumTexture::Wrinkled(texture) => texture.evaluate(ctx),
|
// SpectrumTexture::Wrinkled(texture) => texture.evaluate(ctx),
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use super::{Float, NumFloat};
|
use super::{Float, NumFloat};
|
||||||
use super::{Point, Point3, Point2f, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
|
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
|
||||||
use crate::geometry::traits::VectorLike;
|
|
||||||
use crate::core::pbrt::lerp;
|
use crate::core::pbrt::lerp;
|
||||||
|
use crate::geometry::traits::{Sqrt, VectorLike};
|
||||||
use crate::geometry::{max, min};
|
use crate::geometry::{max, min};
|
||||||
use crate::utils::interval::Interval;
|
use crate::utils::interval::Interval;
|
||||||
use num_traits::{Num, Bounded};
|
use num_traits::{Bounded, Num};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Add, Div, DivAssign, Mul, Sub};
|
use std::ops::{Add, Div, DivAssign, Mul, Sub};
|
||||||
|
|
||||||
|
|
@ -131,6 +131,27 @@ where
|
||||||
o
|
o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn corner(&self, corner_index: usize) -> Point<T, N> {
|
||||||
|
let mut p_arr = [self.p_min[0]; N];
|
||||||
|
for i in 0..N {
|
||||||
|
p_arr[i] = if ((corner_index >> i) & 1) == 1 {
|
||||||
|
self.p_max[i]
|
||||||
|
} else {
|
||||||
|
self.p_min[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Point(p_arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn overlaps(&self, rhs: &Self) -> bool {
|
||||||
|
for i in 0..N {
|
||||||
|
if self.p_max[i] < rhs.p_min[i] || self.p_min[i] > rhs.p_max[i] {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, p: Point<T, N>) -> bool {
|
pub fn contains(&self, p: Point<T, N>) -> bool {
|
||||||
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
|
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
|
||||||
}
|
}
|
||||||
|
|
@ -182,10 +203,9 @@ where
|
||||||
|
|
||||||
impl<T> Bounds3<T>
|
impl<T> Bounds3<T>
|
||||||
where
|
where
|
||||||
T: NumFloat + PartialOrd + Copy + Default
|
T: NumFloat + PartialOrd + Copy + Default + Sqrt,
|
||||||
{
|
{
|
||||||
pub fn bounding_sphere(&self) -> (Point3<T>, T)
|
pub fn bounding_sphere(&self) -> (Point3<T>, T) {
|
||||||
{
|
|
||||||
let two = T::one() + T::one();
|
let two = T::one() + T::one();
|
||||||
let center = self.p_min + self.diagonal() / two;
|
let center = self.p_min + self.diagonal() / two;
|
||||||
let radius = if self.contains(center) {
|
let radius = if self.contains(center) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
|
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
|
||||||
use crate::core::pbrt::square;
|
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
|
||||||
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt};
|
|
||||||
use crate::utils::transform::Transform;
|
use crate::utils::transform::Transform;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,16 @@ pub mod ray;
|
||||||
pub mod traits;
|
pub mod traits;
|
||||||
|
|
||||||
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
|
pub use self::bounds::{Bounds, Bounds2f, Bounds2fi, Bounds2i, Bounds3f, Bounds3fi, Bounds3i};
|
||||||
pub use self::primitives::{
|
|
||||||
Frame, Normal, Normal3f, Point, Point2f, Point2i, Point2fi, Point3, Point3i, Point3f, Point3fi, Vector, Vector2, Vector2f,
|
|
||||||
Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i
|
|
||||||
};
|
|
||||||
pub use self::cone::DirectionCone;
|
pub use self::cone::DirectionCone;
|
||||||
|
pub use self::primitives::{
|
||||||
|
Frame, Normal, Normal3f, Point, Point2f, Point2fi, Point2i, Point3, Point3f, Point3fi, Point3i,
|
||||||
|
Vector, Vector2, Vector2f, Vector2fi, Vector2i, Vector3, Vector3f, Vector3fi, Vector3i,
|
||||||
|
};
|
||||||
pub use self::ray::{Ray, RayDifferential};
|
pub use self::ray::{Ray, RayDifferential};
|
||||||
pub use self::traits::{FloatVectorLike, VectorLike};
|
pub use self::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, PI, clamp_t, square};
|
use crate::core::pbrt::{Float, PI, clamp_t};
|
||||||
|
use crate::utils::math::square;
|
||||||
use num_traits::Float as NumFloat;
|
use num_traits::Float as NumFloat;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
@ -88,6 +89,31 @@ pub fn spherical_triangle_area<T: NumFloat>(a: Vector3f, b: Vector3f, c: Vector3
|
||||||
(2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
|
(2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn spherical_quad_area(a: Vector3f, b: Vector3f, c: Vector3f, d: Vector3f) -> Float {
|
||||||
|
let mut axb = a.cross(b);
|
||||||
|
let mut bxc = b.cross(c);
|
||||||
|
let mut cxd = c.cross(d);
|
||||||
|
let mut dxa = d.cross(a);
|
||||||
|
if axb.norm_squared() == 0.
|
||||||
|
|| bxc.norm_squared() == 0.
|
||||||
|
|| cxd.norm_squared() == 0.
|
||||||
|
|| dxa.norm_squared() == 0.
|
||||||
|
{
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
axb = axb.normalize();
|
||||||
|
bxc = bxc.normalize();
|
||||||
|
cxd = cxd.normalize();
|
||||||
|
dxa = dxa.normalize();
|
||||||
|
|
||||||
|
let alpha = dxa.angle_between(-axb);
|
||||||
|
let beta = axb.angle_between(-bxc);
|
||||||
|
let gamma = bxc.angle_between(-cxd);
|
||||||
|
let delta = cxd.angle_between(-dxa);
|
||||||
|
|
||||||
|
(alpha + beta + gamma + delta - 2. * PI).abs()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spherical_theta<T: NumFloat>(v: Vector3f) -> Float {
|
pub fn spherical_theta<T: NumFloat>(v: Vector3f) -> Float {
|
||||||
clamp_t(v.z(), -1.0, 1.0).acos()
|
clamp_t(v.z(), -1.0, 1.0).acos()
|
||||||
}
|
}
|
||||||
|
|
@ -96,5 +122,3 @@ pub fn spherical_phi<T: NumFloat>(v: Vector3f) -> Float {
|
||||||
let p = v.y().atan2(v.x());
|
let p = v.y().atan2(v.x());
|
||||||
if p < 0.0 { p + 2.0 * PI } else { p }
|
if p < 0.0 { p + 2.0 * PI } else { p }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,22 @@
|
||||||
use super::traits::Tuple;
|
use super::traits::{Lerp, Sqrt, Tuple, VectorLike};
|
||||||
use super::{Float, NumFloat, PI};
|
use super::{Float, NumFloat, PI};
|
||||||
use crate::utils::interval::Interval;
|
use crate::utils::interval::Interval;
|
||||||
use crate::utils::math::safe_asin;
|
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
|
||||||
use num_traits::{Num, Signed, Zero, FloatConst};
|
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
use std::iter::Sum;
|
use std::iter::Sum;
|
||||||
use std::ops::{
|
use std::ops::{
|
||||||
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||||
};
|
};
|
||||||
|
|
||||||
// N-dimensional displacement
|
// N-dimensional displacement
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Vector<T, const N: usize>(pub [T; N]);
|
pub struct Vector<T, const N: usize>(pub [T; N]);
|
||||||
// N-dimensional location
|
// N-dimensional location
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Point<T, const N: usize>(pub [T; N]);
|
pub struct Point<T, const N: usize>(pub [T; N]);
|
||||||
// N-dimensional surface normal
|
// N-dimensional surface normal
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
pub struct Normal<T, const N: usize>(pub [T; N]);
|
pub struct Normal<T, const N: usize>(pub [T; N]);
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
|
|
@ -38,6 +39,18 @@ macro_rules! impl_tuple_core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, Factor, const N: usize> Lerp<Factor> for $Struct<T, N>
|
||||||
|
where
|
||||||
|
Factor: Copy + Num,
|
||||||
|
T: Copy + Mul<Factor, Output = T> + Add<Output = T>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn lerp(t: Factor, a: Self, b: Self) -> Self {
|
||||||
|
let result = std::array::from_fn(|i| a[i] * (Factor::one() - t) + b[i] * t);
|
||||||
|
Self::from_array(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Default + Copy, const N: usize> Default for $Struct<T, N> {
|
impl<T: Default + Copy, const N: usize> Default for $Struct<T, N> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self([T::default(); N])
|
Self([T::default(); N])
|
||||||
|
|
@ -54,6 +67,7 @@ macro_rules! impl_tuple_core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<const N: usize> $Struct<f32, N> {
|
impl<const N: usize> $Struct<f32, N> {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn floor(&self) -> $Struct<i32, N> {
|
pub fn floor(&self) -> $Struct<i32, N> {
|
||||||
|
|
@ -69,6 +83,15 @@ macro_rules! impl_tuple_core {
|
||||||
pub fn fill(value: T) -> Self {
|
pub fn fill(value: T) -> Self {
|
||||||
Self([value; N])
|
Self([value; N])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn cast<U>(&self) -> $Struct<U, N>
|
||||||
|
where
|
||||||
|
U: 'static + Copy,
|
||||||
|
T: 'static + Copy + AsPrimitive<U>,
|
||||||
|
{
|
||||||
|
$Struct(self.0.map(|c| c.as_()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, const N: usize> Index<usize> for $Struct<T, N> {
|
impl<T, const N: usize> Index<usize> for $Struct<T, N> {
|
||||||
|
|
@ -196,61 +219,29 @@ macro_rules! impl_op_assign {
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! impl_float_vector_ops {
|
macro_rules! impl_float_vector_ops {
|
||||||
($Struct:ident) => {
|
($Struct:ident) => {
|
||||||
// This impl block is constrained to only apply when the scalar type `T` is a float.
|
impl<T, const N: usize> VectorLike for $Struct<T, N>
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: Copy
|
||||||
|
+ Zero
|
||||||
|
+ Add<Output = T>
|
||||||
|
+ Mul<Output = T>
|
||||||
|
+ Sub<Output = T>
|
||||||
|
+ Div<Output = T>
|
||||||
|
+ Sqrt,
|
||||||
{
|
{
|
||||||
pub fn dot(self, rhs: Self) -> T {
|
type Scalar = T;
|
||||||
|
fn dot(self, rhs: Self) -> T {
|
||||||
let mut sum = T::zero();
|
let mut sum = T::zero();
|
||||||
for i in 0..N {
|
for i in 0..N {
|
||||||
sum = sum + self[i] * rhs[i];
|
sum = sum + self[i] * rhs[i];
|
||||||
}
|
}
|
||||||
sum
|
sum
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn norm_squared(&self) -> T {
|
|
||||||
let mut sum = T::zero();
|
|
||||||
for i in 0..N {
|
|
||||||
sum = sum + self[i] * self[i];
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn norm(&self) -> T {
|
|
||||||
self.norm_squared().sqrt()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn normalize(self) -> Self {
|
|
||||||
let n = self.norm();
|
|
||||||
if n.is_zero() {
|
|
||||||
self
|
|
||||||
} else {
|
|
||||||
self / n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn angle_between(self, rhs: Self) -> T {
|
|
||||||
let dot_product = self.normalize().dot(rhs.normalize());
|
|
||||||
let clamped_dot = dot_product.min(T::one()).max(-T::one());
|
|
||||||
clamped_dot.acos()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn project_on(self, rhs: Self) -> Self {
|
|
||||||
let rhs_norm_sq = rhs.norm_squared();
|
|
||||||
if rhs_norm_sq.is_zero() {
|
|
||||||
// This now calls the inherent `zero()` method from `impl_tuple_core!`
|
|
||||||
Self::zero()
|
|
||||||
} else {
|
|
||||||
// Note: This requires Mul<T> to be implemented for the struct,
|
|
||||||
// which your `impl_scalar_ops!` macro already does.
|
|
||||||
rhs * (self.dot(rhs) / rhs_norm_sq)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! impl_abs {
|
macro_rules! impl_abs {
|
||||||
($Struct:ident) => {
|
($Struct:ident) => {
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
impl<T, const N: usize> $Struct<T, N>
|
||||||
|
|
@ -325,12 +316,11 @@ impl_float_vector_ops!(Vector);
|
||||||
impl_float_vector_ops!(Normal);
|
impl_float_vector_ops!(Normal);
|
||||||
impl_abs!(Vector);
|
impl_abs!(Vector);
|
||||||
impl_abs!(Normal);
|
impl_abs!(Normal);
|
||||||
|
impl_abs!(Point);
|
||||||
impl_accessors!(Vector);
|
impl_accessors!(Vector);
|
||||||
impl_accessors!(Point);
|
impl_accessors!(Point);
|
||||||
impl_accessors!(Normal);
|
impl_accessors!(Normal);
|
||||||
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
||||||
fn from(v: Vector<T, N>) -> Self {
|
fn from(v: Vector<T, N>) -> Self {
|
||||||
Self(v.0)
|
Self(v.0)
|
||||||
|
|
@ -356,7 +346,7 @@ impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
|
||||||
|
|
||||||
impl<T, const N: usize> Point<T, N>
|
impl<T, const N: usize> Point<T, N>
|
||||||
where
|
where
|
||||||
T: NumFloat,
|
T: NumFloat + Sqrt,
|
||||||
{
|
{
|
||||||
pub fn distance(self, other: Self) -> T {
|
pub fn distance(self, other: Self) -> T {
|
||||||
(self - other).norm()
|
(self - other).norm()
|
||||||
|
|
@ -367,6 +357,50 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Point2f {
|
||||||
|
pub fn invert_bilinear(p: Point2f, vert: &[Point2f]) -> Point2f {
|
||||||
|
let a = vert[0];
|
||||||
|
let b = vert[1];
|
||||||
|
let c = vert[3];
|
||||||
|
let d = vert[2];
|
||||||
|
let e = b - a;
|
||||||
|
let f = d - a;
|
||||||
|
let g = (a - b) + (c - d);
|
||||||
|
let h = p - a;
|
||||||
|
|
||||||
|
let cross2d = |a: Vector2f, b: Vector2f| difference_of_products(a.x(), b.y(), a.y(), b.x());
|
||||||
|
|
||||||
|
let k2 = cross2d(g, f);
|
||||||
|
let k1 = cross2d(e, f) + cross2d(h, g);
|
||||||
|
let k0 = cross2d(h, e);
|
||||||
|
|
||||||
|
// if edges are parallel, this is a linear equation
|
||||||
|
if k2.abs() < 0.001 {
|
||||||
|
if (e.x() * k1 - g.x() * k0).abs() < 1e-5 {
|
||||||
|
return Point2f::new(
|
||||||
|
(h.y() * k1 + f.y() * k0) / (e.y() * k1 - g.y() * k0),
|
||||||
|
-k0 / k1,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Point2f::new(
|
||||||
|
(h.x() * k1 + f.x() * k0) / (e.x() * k1 - g.x() * k0),
|
||||||
|
-k0 / k1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((v0, v1)) = quadratic(k2, k1, k0) {
|
||||||
|
let u = (h.x() - f.x() * v0) / (e.x() + g.x() * v0);
|
||||||
|
if u < 0. || u > 1. || v0 < 0. || v0 > 1. {
|
||||||
|
return Point2f::new((h.x() - f.x() * v1) / (e.x() + g.x() * v1), v1);
|
||||||
|
}
|
||||||
|
return Point2f::new(u, v0);
|
||||||
|
} else {
|
||||||
|
return Point2f::zero();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utility aliases and functions
|
// Utility aliases and functions
|
||||||
pub type Point2<T> = Point<T, 2>;
|
pub type Point2<T> = Point<T, 2>;
|
||||||
pub type Point2f = Point2<Float>;
|
pub type Point2f = Point2<Float>;
|
||||||
|
|
@ -428,6 +462,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Normal3<T>
|
||||||
|
where
|
||||||
|
T: Num + Copy + Neg<Output = T>,
|
||||||
|
{
|
||||||
|
pub fn cross(self, rhs: Self) -> Self {
|
||||||
|
Self([
|
||||||
|
self[1] * rhs[2] - self[2] * rhs[1],
|
||||||
|
self[2] * rhs[0] - self[0] * rhs[2],
|
||||||
|
self[0] * rhs[1] - self[1] * rhs[0],
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T> Vector3<T>
|
impl<T> Vector3<T>
|
||||||
where
|
where
|
||||||
T: Num + NumFloat + Copy + Neg<Output = T>,
|
T: Num + NumFloat + Copy + Neg<Output = T>,
|
||||||
|
|
@ -443,8 +490,76 @@ where
|
||||||
};
|
};
|
||||||
(v2, self.cross(v2))
|
(v2, self.cross(v2))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
|
||||||
|
let sign = self.z().copysign(T::one());
|
||||||
|
let a = -T::one() / (sign + self.z());
|
||||||
|
let b = self.x() * self.y() * a;
|
||||||
|
let v2 = Self::new(
|
||||||
|
T::one() + sign * self.x().powi(2) * a,
|
||||||
|
sign * b,
|
||||||
|
-sign * self.x(),
|
||||||
|
);
|
||||||
|
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
|
||||||
|
(v2, v3)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Normal3<T>
|
||||||
|
where
|
||||||
|
T: Num + NumFloat + Copy + Neg<Output = T>,
|
||||||
|
{
|
||||||
|
pub fn coordinate_system(&self) -> (Self, Self)
|
||||||
|
where
|
||||||
|
T: NumFloat,
|
||||||
|
{
|
||||||
|
let v2 = if self[0].abs() > self[1].abs() {
|
||||||
|
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
|
||||||
|
} else {
|
||||||
|
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
|
||||||
|
};
|
||||||
|
(v2, self.cross(v2))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn coordinate_system_from_cpp(&self) -> (Self, Self) {
|
||||||
|
let sign = self.z().copysign(T::one());
|
||||||
|
let a = -T::one() / (sign + self.z());
|
||||||
|
let b = self.x() * self.y() * a;
|
||||||
|
let v2 = Self::new(
|
||||||
|
T::one() + sign * self.x().powi(2) * a,
|
||||||
|
sign * b,
|
||||||
|
-sign * self.x(),
|
||||||
|
);
|
||||||
|
let v3 = Self::new(b, sign + self.y().powi(2) * a, -self.y());
|
||||||
|
(v2, v3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Hash for Vector<Float, N> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
for item in self.0.iter() {
|
||||||
|
item.to_bits().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Hash for Point<Float, N> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
for item in self.0.iter() {
|
||||||
|
item.to_bits().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> Hash for Normal<Float, N> {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
for item in self.0.iter() {
|
||||||
|
item.to_bits().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERVAL STUFF
|
||||||
impl<const N: usize> Point<Interval, N> {
|
impl<const N: usize> Point<Interval, N> {
|
||||||
pub fn new_from_point(p: Point<Float, N>) -> Self {
|
pub fn new_from_point(p: Point<Float, N>) -> Self {
|
||||||
let mut arr = [Interval::default(); N];
|
let mut arr = [Interval::default(); N];
|
||||||
|
|
@ -531,6 +646,16 @@ impl<const N: usize> From<Point<Interval, N>> for Point<Float, N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> From<Vector<Interval, N>> for Vector<Float, N> {
|
||||||
|
fn from(pi: Vector<Interval, N>) -> Self {
|
||||||
|
let mut arr = [0.0; N];
|
||||||
|
for i in 0..N {
|
||||||
|
arr[i] = pi[i].midpoint();
|
||||||
|
}
|
||||||
|
Vector(arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const N: usize> Mul<Vector<Interval, N>> for Interval {
|
impl<const N: usize> Mul<Vector<Interval, N>> for Interval {
|
||||||
type Output = Vector<Interval, N>;
|
type Output = Vector<Interval, N>;
|
||||||
fn mul(self, rhs: Vector<Interval, N>) -> Self::Output {
|
fn mul(self, rhs: Vector<Interval, N>) -> Self::Output {
|
||||||
|
|
@ -563,10 +688,14 @@ impl<const N: usize> From<Point<i32, N>> for Point<Float, N> {
|
||||||
|
|
||||||
impl<T> Normal3<T>
|
impl<T> Normal3<T>
|
||||||
where
|
where
|
||||||
T: Num + PartialOrd + Copy + Neg<Output = T>,
|
T: Num + PartialOrd + Copy + Neg<Output = T> + Sqrt,
|
||||||
{
|
{
|
||||||
pub fn face_forward(self, v: Vector3<T>) -> Self {
|
pub fn face_forward(self, v: Vector3<T>) -> Self {
|
||||||
if Vector3::<T>::from(self).dot(v.into()) < T::zero() { -self } else { self }
|
if Vector3::<T>::from(self).dot(v.into()) < T::zero() {
|
||||||
|
-self
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -595,6 +724,22 @@ impl Frame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_xz(x: Vector3f, z: Vector3f) -> Self {
|
||||||
|
Self {
|
||||||
|
x,
|
||||||
|
y: z.cross(x),
|
||||||
|
z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_xy(x: Vector3f, y: Vector3f) -> Self {
|
||||||
|
Self {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z: x.cross(y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_y(y: Vector3f) -> Self {
|
pub fn from_y(y: Vector3f) -> Self {
|
||||||
let (z, x) = y.normalize().coordinate_system();
|
let (z, x) = y.normalize().coordinate_system();
|
||||||
Self {
|
Self {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
|
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
|
||||||
use crate::core::medium::Medium;
|
use crate::core::medium::Medium;
|
||||||
use crate::core::pbrt::{Float, next_float_down, next_float_up};
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::utils::interval::Interval;
|
||||||
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
|
use num_integer::Roots;
|
||||||
|
use num_traits::{Float as NumFloat, FloatConst, Num, One, Signed, Zero};
|
||||||
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
|
use std::ops::{Add, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub};
|
||||||
|
|
||||||
use num_traits::{Float as NumFloat, Num, Zero, One, FloatConst};
|
|
||||||
|
|
||||||
pub trait Tuple<T, const N: usize>:
|
pub trait Tuple<T, const N: usize>:
|
||||||
Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
|
Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
|
||||||
{
|
{
|
||||||
|
|
@ -10,6 +13,61 @@ pub trait Tuple<T, const N: usize>:
|
||||||
fn data_mut(&mut self) -> &mut [T; N];
|
fn data_mut(&mut self) -> &mut [T; N];
|
||||||
|
|
||||||
fn from_array(arr: [T; N]) -> Self;
|
fn from_array(arr: [T; N]) -> Self;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn permute(&self, p: [usize; N]) -> Self
|
||||||
|
where
|
||||||
|
T: Copy,
|
||||||
|
{
|
||||||
|
let new_data = p.map(|index| self[index]);
|
||||||
|
Self::from_array(new_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_component_value(&self) -> T
|
||||||
|
where
|
||||||
|
T: PartialOrd + Copy,
|
||||||
|
{
|
||||||
|
self.data()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.reduce(|a, b| if a > b { a } else { b })
|
||||||
|
.expect("Cannot get max component of a zero-length tuple")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_component_value(&self) -> T
|
||||||
|
where
|
||||||
|
T: PartialOrd + Copy,
|
||||||
|
{
|
||||||
|
self.data()
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.reduce(|a, b| if a < b { a } else { b })
|
||||||
|
.expect("Cannot get min component of a zero-length tuple")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_component_index(&self) -> usize
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
self.data()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn min_component_index(&self) -> usize
|
||||||
|
where
|
||||||
|
T: PartialOrd,
|
||||||
|
{
|
||||||
|
self.data()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
|
||||||
|
.map(|(index, _)| index)
|
||||||
|
.unwrap_or(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait VectorLike:
|
pub trait VectorLike:
|
||||||
|
|
@ -18,41 +76,106 @@ pub trait VectorLike:
|
||||||
+ Add<Output = Self>
|
+ Add<Output = Self>
|
||||||
+ Sub<Output = Self>
|
+ Sub<Output = Self>
|
||||||
+ Div<Self::Scalar, Output = Self>
|
+ Div<Self::Scalar, Output = Self>
|
||||||
|
+ Mul<Self::Scalar, Output = Self>
|
||||||
{
|
{
|
||||||
type Scalar: NumFloat;
|
type Scalar: Copy + Zero + Add<Output = Self::Scalar> + Mul<Output = Self::Scalar> + Sqrt;
|
||||||
|
|
||||||
fn dot(self, rhs: Self) -> Self::Scalar;
|
fn dot(self, rhs: Self) -> Self::Scalar;
|
||||||
fn norm_squared(&self) -> Self::Scalar;
|
fn norm_squared(self) -> Self::Scalar {
|
||||||
|
self.dot(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn abs_dot(self, rhs: Self) -> Self::Scalar
|
||||||
|
where
|
||||||
|
Self::Scalar: Signed,
|
||||||
|
{
|
||||||
|
self.dot(rhs).abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gram_schmidt(self, rhs: Self) -> Self {
|
||||||
|
self - rhs * self.dot(rhs)
|
||||||
|
}
|
||||||
|
|
||||||
fn norm(&self) -> Self::Scalar {
|
fn norm(&self) -> Self::Scalar {
|
||||||
self.norm_squared().sqrt()
|
self.norm_squared().sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize(self) -> Self {
|
fn normalize(self) -> Self
|
||||||
|
where
|
||||||
|
Self::Scalar: NumFloat,
|
||||||
|
{
|
||||||
let n = self.norm();
|
let n = self.norm();
|
||||||
if n.is_zero() {
|
if n.is_zero() { self } else { self / n }
|
||||||
self
|
|
||||||
} else {
|
|
||||||
self / n
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn angle_between(self, rhs: Self) -> Self::Scalar {
|
fn angle_between(self, rhs: Self) -> Self::Scalar
|
||||||
|
where
|
||||||
|
Self::Scalar: NumFloat,
|
||||||
|
{
|
||||||
let dot_product = self.normalize().dot(rhs.normalize());
|
let dot_product = self.normalize().dot(rhs.normalize());
|
||||||
let clamped_dot = dot_product.min(Self::Scalar::one()).max(-Self::Scalar::one());
|
let clamped_dot = dot_product
|
||||||
|
.min(Self::Scalar::one())
|
||||||
|
.max(-Self::Scalar::one());
|
||||||
clamped_dot.acos()
|
clamped_dot.acos()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn project_on(self, rhs: Self) -> Self {
|
|
||||||
let rhs_norm_sq = rhs.norm_squared();
|
|
||||||
if rhs_norm_sq.is_zero() {
|
|
||||||
Self::zero()
|
|
||||||
} else {
|
|
||||||
let scale = self.dot(rhs) / rhs_norm_sq;
|
|
||||||
rhs * scale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn zero() -> Self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait Sqrt {
|
||||||
|
fn sqrt(self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sqrt for Float {
|
||||||
|
fn sqrt(self) -> Self {
|
||||||
|
self.sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sqrt for f64 {
|
||||||
|
fn sqrt(self) -> Self {
|
||||||
|
self.sqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sqrt for i32 {
|
||||||
|
fn sqrt(self) -> Self {
|
||||||
|
self.isqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sqrt for u32 {
|
||||||
|
fn sqrt(self) -> Self {
|
||||||
|
self.isqrt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sqrt for Interval {
|
||||||
|
fn sqrt(self) -> Self {
|
||||||
|
let low = if self.low < 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
next_float_down(self.low.sqrt())
|
||||||
|
};
|
||||||
|
|
||||||
|
let high = if self.high < 0.0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
next_float_up(self.high.sqrt())
|
||||||
|
};
|
||||||
|
|
||||||
|
Self { low, high }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Lerp<Factor: Copy + Num>: Sized + Copy {
|
||||||
|
fn lerp(t: Factor, a: Self, b: Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Lerp<T> for T
|
||||||
|
where
|
||||||
|
T: Num + Copy + Mul<T, Output = T> + Add<T, Output = T>,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn lerp(t: T, a: Self, b: Self) -> Self {
|
||||||
|
a * (T::one() - t) + b * t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
#![allow(unused_imports, dead_code)]
|
#![allow(unused_imports, dead_code)]
|
||||||
#![feature(float_erf)]
|
#![feature(float_erf)]
|
||||||
|
#![feature(f16)]
|
||||||
|
#![feature(generic_const_exprs)]
|
||||||
|
|
||||||
mod camera;
|
mod camera;
|
||||||
mod core;
|
mod core;
|
||||||
|
mod geometry;
|
||||||
mod lights;
|
mod lights;
|
||||||
|
mod shapes;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
use crate::core::medium::MediumInterface;
|
use crate::core::medium::MediumInterface;
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::shapes::ShapeTrait;
|
||||||
use crate::utils::spectrum::Spectrum;
|
use crate::utils::spectrum::Spectrum;
|
||||||
|
use crate::utils::transform::Transform;
|
||||||
|
|
||||||
pub struct DiffuseAreaLight {
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub struct DiffuseAreaLight<'a> {
|
||||||
pub l_emit: Spectrum,
|
pub l_emit: Spectrum,
|
||||||
// pub shape: Arc<Shape>,
|
pub shape: Arc<&'a dyn ShapeTrait>,
|
||||||
pub two_sided: bool,
|
pub two_sided: bool,
|
||||||
pub area: Float,
|
pub area: Float,
|
||||||
// inherited from class Light (see light.h)
|
|
||||||
pub flags: u8,
|
pub flags: u8,
|
||||||
pub n_samples: i32,
|
pub n_samples: i32,
|
||||||
pub medium_interface: MediumInterface,
|
pub medium_interface: MediumInterface,
|
||||||
// light_to_world: Transform,
|
light_to_world: Transform<Float>,
|
||||||
// world_to_light: Transform,
|
world_to_light: Transform<Float>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1,27 @@
|
||||||
pub mod diffuse;
|
pub mod diffuse;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiffuseAreaLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DistantLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct GonioPhotometricLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct InfiniteAreaLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PointLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ProjectionLight;
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SpotLight;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Light {
|
||||||
|
DiffuseArea(Box<DiffuseAreaLight>),
|
||||||
|
Distant(Box<DistantLight>),
|
||||||
|
GonioPhotometric(Box<GonioPhotometricLight>),
|
||||||
|
InfiniteArea(Box<InfiniteAreaLight>),
|
||||||
|
Point(Box<PointLight>),
|
||||||
|
Projection(Box<ProjectionLight>),
|
||||||
|
Spot(Box<SpotLight>),
|
||||||
|
}
|
||||||
|
|
|
||||||
713
src/shapes/bilinear.rs
Normal file
713
src/shapes/bilinear.rs
Normal file
|
|
@ -0,0 +1,713 @@
|
||||||
|
use super::{
|
||||||
|
BilinearIntersection, BilinearPatchShape, Bounds3f, DirectionCone, Interaction, Normal3f,
|
||||||
|
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
|
ShapeTrait, SurfaceInteraction, Vector3f,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{Float, clamp_t, gamma, lerp};
|
||||||
|
use crate::geometry::{Tuple, VectorLike, spherical_quad_area};
|
||||||
|
use crate::utils::math::{SquareMatrix, difference_of_products, quadratic};
|
||||||
|
use crate::utils::mesh::BilinearPatchMesh;
|
||||||
|
use crate::utils::sampling::{
|
||||||
|
bilinear_pdf, invert_spherical_rectangle_sample, sample_bilinear, sample_spherical_rectangle,
|
||||||
|
};
|
||||||
|
use crate::utils::transform::Transform;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
struct PatchData<'a> {
|
||||||
|
mesh: &'a BilinearPatchMesh,
|
||||||
|
p00: Point3f,
|
||||||
|
p10: Point3f,
|
||||||
|
p01: Point3f,
|
||||||
|
p11: Point3f,
|
||||||
|
n: Option<[Normal3f; 4]>,
|
||||||
|
uv: Option<[Point2f; 4]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IntersectionData {
|
||||||
|
t: Float,
|
||||||
|
u: Float,
|
||||||
|
v: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TextureDerivative {
|
||||||
|
duds: Float,
|
||||||
|
dvds: Float,
|
||||||
|
dudt: Float,
|
||||||
|
dvdt: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
static BILINEAR_MESHES: OnceLock<Vec<Arc<BilinearPatchMesh>>> = OnceLock::new();
|
||||||
|
impl BilinearPatchShape {
|
||||||
|
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 1e-4;
|
||||||
|
|
||||||
|
pub fn new(_mesh: BilinearPatchMesh, mesh_index: usize, blp_index: usize) -> Self {
|
||||||
|
let mut bp = BilinearPatchShape {
|
||||||
|
mesh_index,
|
||||||
|
blp_index,
|
||||||
|
area: 0.,
|
||||||
|
rectangle: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let (p00, p10, p01, p11) = {
|
||||||
|
let data = bp.get_data();
|
||||||
|
(data.p00, data.p10, data.p01, data.p11)
|
||||||
|
};
|
||||||
|
|
||||||
|
bp.rectangle = bp.is_rectangle(p00, p10, p01, p11);
|
||||||
|
|
||||||
|
if bp.rectangle {
|
||||||
|
bp.area = p00.distance(p01) + p00.distance(p10);
|
||||||
|
} else {
|
||||||
|
const NA: usize = 3;
|
||||||
|
let mut p = [[Point3f::default(); NA + 1]; NA + 1];
|
||||||
|
for i in 0..=NA {
|
||||||
|
let u = i as Float / NA as Float;
|
||||||
|
for j in 0..=NA {
|
||||||
|
let v = j as Float / NA as Float;
|
||||||
|
p[i][j] = lerp(u, lerp(v, p00, p01), lerp(v, p10, p11));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut area = 0.;
|
||||||
|
for i in 0..NA {
|
||||||
|
for j in 0..NA {
|
||||||
|
let diag1 = p[i + 1][j + 1] - p[i][j];
|
||||||
|
let diag2 = p[i + 1][j] - p[i][j + 1];
|
||||||
|
let quad_area = 0.5 * diag1.cross(diag2).norm();
|
||||||
|
area += quad_area;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bp.area = area;
|
||||||
|
}
|
||||||
|
bp
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mesh(&self) -> &Arc<BilinearPatchMesh> {
|
||||||
|
let meshes = BILINEAR_MESHES
|
||||||
|
.get()
|
||||||
|
.expect("Mesh has not been initialized");
|
||||||
|
&meshes[self.mesh_index]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&self) -> PatchData {
|
||||||
|
let mesh = self.mesh();
|
||||||
|
let start_index = 4 * self.blp_index;
|
||||||
|
let v = &mesh.vertex_indices[start_index..start_index + 4];
|
||||||
|
let p00: Point3f = mesh.p[v[0] as usize];
|
||||||
|
let p10: Point3f = mesh.p[v[1] as usize];
|
||||||
|
let p01: Point3f = mesh.p[v[2] as usize];
|
||||||
|
let p11: Point3f = mesh.p[v[3] as usize];
|
||||||
|
let n = mesh
|
||||||
|
.n
|
||||||
|
.as_ref()
|
||||||
|
.map(|normals| [normals[v[0]], normals[v[1]], normals[v[2]], normals[v[3]]]);
|
||||||
|
let uv = mesh
|
||||||
|
.uv
|
||||||
|
.as_ref()
|
||||||
|
.map(|uvs| [uvs[v[0]], uvs[v[1]], uvs[v[2]], uvs[v[3]]]);
|
||||||
|
PatchData {
|
||||||
|
mesh,
|
||||||
|
p00,
|
||||||
|
p10,
|
||||||
|
p01,
|
||||||
|
p11,
|
||||||
|
n,
|
||||||
|
uv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_rectangle(&self, p00: Point3f, p10: Point3f, p01: Point3f, p11: Point3f) -> bool {
|
||||||
|
if p00 == p01 || p01 == p11 || p11 == p10 || p10 == p00 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let n = Normal3f::from((p10 - p00).cross(p01 - p00).normalize());
|
||||||
|
if (p11 - p00).normalize().dot(n.into()).abs() > 1e-5 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p_center_vec = Vector3f::from(p00 + p01.into() + p10.into() + p11.into()) / 4.;
|
||||||
|
let p_center = Point3f::from(p_center_vec);
|
||||||
|
let d2 = [
|
||||||
|
p00.distance_squared(p_center),
|
||||||
|
p01.distance_squared(p_center),
|
||||||
|
p10.distance_squared(p_center),
|
||||||
|
p11.distance_squared(p_center),
|
||||||
|
];
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
if (d2[i] - d2[0]).abs() / d2[0] > 1e-4 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_bilinear_patch(
|
||||||
|
&self,
|
||||||
|
ray: &Ray,
|
||||||
|
t_max: Float,
|
||||||
|
data: &PatchData,
|
||||||
|
) -> Option<BilinearIntersection> {
|
||||||
|
let a = (data.p10 - data.p00).cross(data.p01 - data.p11).dot(ray.d);
|
||||||
|
let c = (data.p00 - ray.o).cross(ray.d).dot(data.p01 - data.p00);
|
||||||
|
let b = (data.p10 - ray.o).cross(ray.d).dot(data.p11 - data.p10) - (a + c);
|
||||||
|
|
||||||
|
let (u1, u2) = quadratic(a, b, c)?;
|
||||||
|
|
||||||
|
let eps = gamma(10)
|
||||||
|
* (ray.o.abs().max_component_value()
|
||||||
|
+ ray.d.abs().max_component_value()
|
||||||
|
+ data.p00.abs().max_component_value()
|
||||||
|
+ data.p10.abs().max_component_value()
|
||||||
|
+ data.p01.abs().max_component_value()
|
||||||
|
+ data.p11.abs().max_component_value());
|
||||||
|
|
||||||
|
let hit1 = self.check_candidate(u1, ray, data);
|
||||||
|
let hit2 = if u1 != u2 {
|
||||||
|
self.check_candidate(u2, ray, data)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find best hit from candidates
|
||||||
|
[hit1, hit2]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.filter(|hit| hit.t < t_max && hit.t > eps)
|
||||||
|
.min_by(|a, b| a.t.partial_cmp(&b.t).unwrap())
|
||||||
|
.map(|best_hit| {
|
||||||
|
BilinearIntersection::new(Point2f::new(best_hit.u, best_hit.v), best_hit.t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_candidate(&self, u: Float, ray: &Ray, data: &PatchData) -> Option<IntersectionData> {
|
||||||
|
if !(0.0..=1.0).contains(&u) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let uo: Point3f = lerp(u, data.p00, data.p10);
|
||||||
|
let ud: Point3f = Point3f::from(lerp(u, data.p01, data.p11) - uo);
|
||||||
|
let deltao = uo - ray.o;
|
||||||
|
let perp = ray.d.cross(ud.into());
|
||||||
|
let p2 = perp.norm_squared();
|
||||||
|
if p2 == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let v_det = SquareMatrix::<Float, 3>::new([
|
||||||
|
[deltao.x(), ray.d.x(), perp.x()],
|
||||||
|
[deltao.y(), ray.d.y(), perp.y()],
|
||||||
|
[deltao.z(), ray.d.z(), perp.z()],
|
||||||
|
])
|
||||||
|
.determinant();
|
||||||
|
|
||||||
|
if !(0.0..=p2).contains(&v_det) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let t_det = SquareMatrix::<Float, 3>::new([
|
||||||
|
[deltao.x(), ud.x(), perp.x()],
|
||||||
|
[deltao.y(), ud.y(), perp.y()],
|
||||||
|
[deltao.z(), ud.z(), perp.z()],
|
||||||
|
])
|
||||||
|
.determinant();
|
||||||
|
|
||||||
|
Some(IntersectionData {
|
||||||
|
t: t_det / p2,
|
||||||
|
u,
|
||||||
|
v: v_det / p2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interaction_from_intersection(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
uv: Point2f,
|
||||||
|
time: Float,
|
||||||
|
wo: Vector3f,
|
||||||
|
) -> SurfaceInteraction {
|
||||||
|
// Base geometry and derivatives
|
||||||
|
let p = lerp(
|
||||||
|
uv[0],
|
||||||
|
lerp(uv[1], data.p00, data.p01),
|
||||||
|
lerp(uv[1], data.p10, data.p11),
|
||||||
|
);
|
||||||
|
let mut dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
|
||||||
|
let mut dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
|
||||||
|
|
||||||
|
// If textured, apply coordinates
|
||||||
|
let (st, derivatives) = self.apply_texture_coordinates(data, uv, &mut dpdu, &mut dpdv);
|
||||||
|
|
||||||
|
// Compute second moments
|
||||||
|
let n = Normal3f::from(dpdu.cross(dpdv).normalize());
|
||||||
|
let (mut dndu, mut dndv) = self.calculate_surface_curvature(data, &dpdu, &dpdv, n);
|
||||||
|
if let Some(ref deriv) = derivatives {
|
||||||
|
let dnds = Normal3f::from(dndu * deriv.duds + dndv * deriv.dvds);
|
||||||
|
let dndt = Normal3f::from(dndu * deriv.dudt + dndv * deriv.dvdt);
|
||||||
|
dndu = dnds;
|
||||||
|
dndv = dndt;
|
||||||
|
}
|
||||||
|
|
||||||
|
let p_abs_sum = data.p00.abs()
|
||||||
|
+ Vector3f::from(data.p01.abs())
|
||||||
|
+ Vector3f::from(data.p10.abs())
|
||||||
|
+ Vector3f::from(data.p11.abs());
|
||||||
|
let p_error = gamma(6) * Vector3f::from(p_abs_sum);
|
||||||
|
|
||||||
|
let flip_normal = data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness;
|
||||||
|
let pi = Point3fi::new_with_error(p, p_error);
|
||||||
|
let mut isect =
|
||||||
|
SurfaceInteraction::new(pi, st, wo, dpdu, dpdv, dndu, dndv, time, flip_normal);
|
||||||
|
if data.n.is_some() {
|
||||||
|
self.apply_shading_normals(&mut isect, data, uv, derivatives);
|
||||||
|
}
|
||||||
|
isect
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_texture_coordinates(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
uv: Point2f,
|
||||||
|
dpdu: &mut Vector3f,
|
||||||
|
dpdv: &mut Vector3f,
|
||||||
|
) -> (Point2f, Option<TextureDerivative>) {
|
||||||
|
let Some(uvs) = data.uv else {
|
||||||
|
return (uv, None);
|
||||||
|
};
|
||||||
|
let uv00 = uvs[0];
|
||||||
|
let uv01 = uvs[1];
|
||||||
|
let uv10 = uvs[2];
|
||||||
|
let uv11 = uvs[3];
|
||||||
|
// Compute partial derivatives of (u, v) with respect to (s, t)
|
||||||
|
let st = lerp(uv[0], lerp(uv[1], uv00, uv01), lerp(uv[1], uv10, uv11));
|
||||||
|
let dstdu = lerp(uv[1], uv10, uv11) - lerp(uv[1], uv00, uv01);
|
||||||
|
let dstdv = lerp(uv[0], uv01, uv11) - lerp(uv[0], uv00, uv10);
|
||||||
|
|
||||||
|
let det = difference_of_products(dstdu[0], dstdv[1], dstdu[1], dstdv[0]);
|
||||||
|
let inv_det = if det == 0.0 { 0.0 } else { 1.0 / det };
|
||||||
|
let duds = dstdv[1] * inv_det;
|
||||||
|
let dvds = -dstdv[0] * inv_det;
|
||||||
|
let dudt = -dstdu[1] * inv_det;
|
||||||
|
let dvdt = dstdu[0] * inv_det;
|
||||||
|
|
||||||
|
let dpds = *dpdu * duds + *dpdv * dvds;
|
||||||
|
let mut dpdt = *dpdu * dudt + *dpdv * dvdt;
|
||||||
|
|
||||||
|
if dpdu.cross(*dpdv).dot(dpds.cross(dpdt)) < 0. {
|
||||||
|
dpdt = -dpdt;
|
||||||
|
}
|
||||||
|
*dpdu = dpds;
|
||||||
|
*dpdv = dpdt;
|
||||||
|
|
||||||
|
let factors = TextureDerivative {
|
||||||
|
duds,
|
||||||
|
dvds,
|
||||||
|
dudt,
|
||||||
|
dvdt,
|
||||||
|
};
|
||||||
|
(st, Some(factors))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_base_derivatives(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
uv: Point2f,
|
||||||
|
) -> (Point3f, Vector3f, Vector3f) {
|
||||||
|
let p = lerp(
|
||||||
|
uv[0],
|
||||||
|
lerp(uv[1], data.p00, data.p01),
|
||||||
|
lerp(uv[1], data.p10, data.p11),
|
||||||
|
);
|
||||||
|
let dpdu = lerp(uv[1], data.p10, data.p11) - lerp(uv[1], data.p00, data.p01);
|
||||||
|
let dpdv = lerp(uv[0], data.p01, data.p11) - lerp(uv[0], data.p00, data.p10);
|
||||||
|
(p, dpdu, dpdv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_surface_curvature(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
dpdu: &Vector3f,
|
||||||
|
dpdv: &Vector3f,
|
||||||
|
n: Normal3f,
|
||||||
|
) -> (Normal3f, Normal3f) {
|
||||||
|
let e = dpdu.dot(*dpdu);
|
||||||
|
let f = dpdu.dot(*dpdv);
|
||||||
|
let g = dpdv.dot(*dpdv);
|
||||||
|
let d2pduv = (data.p00 - data.p01) + (data.p11 - data.p10);
|
||||||
|
let d2pduu = Vector3f::zero();
|
||||||
|
let d2pdvv = Vector3f::zero();
|
||||||
|
|
||||||
|
let e_min = n.dot(d2pduu.into());
|
||||||
|
let f_min = n.dot(d2pduv.into());
|
||||||
|
let g_min = n.dot(d2pdvv.into());
|
||||||
|
|
||||||
|
let egf2 = difference_of_products(e, g, f, f);
|
||||||
|
let inv_egf2 = if egf2 == 0. { 0. } else { 1. / egf2 };
|
||||||
|
|
||||||
|
let dndu = Normal3f::from(
|
||||||
|
(f_min * f - e_min * g) * inv_egf2 * *dpdu + (e_min * f - f_min * e) * inv_egf2 * *dpdv,
|
||||||
|
);
|
||||||
|
let dndv = Normal3f::from(
|
||||||
|
(g_min * f - f_min * g) * inv_egf2 * *dpdu + (f_min * f - g_min * e) * inv_egf2 * *dpdv,
|
||||||
|
);
|
||||||
|
(dndu, dndv)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_shading_normals(
|
||||||
|
&self,
|
||||||
|
isect: &mut SurfaceInteraction,
|
||||||
|
data: &PatchData,
|
||||||
|
uv: Point2f,
|
||||||
|
derivatives: Option<TextureDerivative>,
|
||||||
|
) {
|
||||||
|
let Some(normals) = data.n else { return };
|
||||||
|
let n00 = normals[1];
|
||||||
|
let n10 = normals[1];
|
||||||
|
let n01 = normals[2];
|
||||||
|
let n11 = normals[3];
|
||||||
|
let ns = lerp(uv[0], lerp(uv[1], n00, n01), lerp(uv[1], n10, n11)).normalize();
|
||||||
|
let mut dndu = lerp(uv[1], n10, n11) - lerp(uv[1], n00, n01);
|
||||||
|
let mut dndv = lerp(uv[0], n01, n11) - lerp(uv[0], n00, n10);
|
||||||
|
|
||||||
|
if let Some(deriv) = derivatives {
|
||||||
|
let dnds = dndu * deriv.duds + dndv * deriv.dvds;
|
||||||
|
let dndt = dndu * deriv.dudt + dndv * deriv.dvdt;
|
||||||
|
dndu = dnds;
|
||||||
|
dndv = dndt;
|
||||||
|
}
|
||||||
|
let r = Transform::rotate_from_to(isect.n().normalize().into(), ns.into());
|
||||||
|
|
||||||
|
isect.set_shading_geometry(
|
||||||
|
ns,
|
||||||
|
r.apply_to_vector(isect.dpdu),
|
||||||
|
r.apply_to_vector(isect.dpdv),
|
||||||
|
dndu,
|
||||||
|
r.apply_to_normal(dndv),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_area_and_pdf(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let mut ss = self.sample(u)?;
|
||||||
|
|
||||||
|
let mut intr_clone = (*ss.intr).clone();
|
||||||
|
intr_clone.common.time = ctx.time;
|
||||||
|
ss.intr = Arc::new(intr_clone);
|
||||||
|
|
||||||
|
let mut wi = ss.intr.p() - ctx.p();
|
||||||
|
let dist_sq = wi.norm_squared();
|
||||||
|
if dist_sq == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
wi = wi.normalize();
|
||||||
|
let abs_dot = Vector3f::from(ss.intr.n()).abs_dot(-wi);
|
||||||
|
if abs_dot == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
ss.pdf *= dist_sq / abs_dot;
|
||||||
|
|
||||||
|
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_parametric_coords(&self, data: &PatchData, u: Point2f) -> (Point2f, Float) {
|
||||||
|
if let Some(image_distrib) = &data.mesh.image_distribution {
|
||||||
|
let (uv, pdf, _) = image_distrib.sample(u);
|
||||||
|
(uv, pdf)
|
||||||
|
} else if !self.rectangle {
|
||||||
|
let w = [
|
||||||
|
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(),
|
||||||
|
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(),
|
||||||
|
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(),
|
||||||
|
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(),
|
||||||
|
];
|
||||||
|
let uv = sample_bilinear(u, &w);
|
||||||
|
let pdf = bilinear_pdf(uv, &w);
|
||||||
|
(uv, pdf)
|
||||||
|
} else {
|
||||||
|
(u, 1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_solid_angle(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
ctx: &ShapeSampleContext,
|
||||||
|
u: Point2f,
|
||||||
|
corner_dirs: &[Vector3f; 4],
|
||||||
|
) -> Option<ShapeSample> {
|
||||||
|
let mut pdf = 1.;
|
||||||
|
if ctx.ns != Normal3f::zero() {
|
||||||
|
let w = [
|
||||||
|
0.01_f32.max(corner_dirs[0].dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(corner_dirs[1].dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(corner_dirs[2].dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(corner_dirs[3].dot(ctx.ns.into()).abs()),
|
||||||
|
];
|
||||||
|
let u = sample_bilinear(u, &w);
|
||||||
|
pdf *= bilinear_pdf(u, &w);
|
||||||
|
}
|
||||||
|
|
||||||
|
let eu = data.p10 - data.p00;
|
||||||
|
let ev = data.p01 - data.p00;
|
||||||
|
let (p, quad_pdf) = sample_spherical_rectangle(ctx.p(), data.p00, eu, ev, u);
|
||||||
|
pdf *= quad_pdf?;
|
||||||
|
|
||||||
|
// Compute (u, v) and surface normal for sampled points on rectangle
|
||||||
|
let uv = Point2f::new(
|
||||||
|
(p - data.p00).dot(eu) / data.p10.distance_squared(data.p00),
|
||||||
|
(p - data.p00).dot(ev) / data.p01.distance_squared(data.p00),
|
||||||
|
);
|
||||||
|
|
||||||
|
let n = self.compute_sampled_normal(data, &eu, &ev, uv);
|
||||||
|
let st = data.uv.map_or(uv, |uvs| {
|
||||||
|
lerp(
|
||||||
|
uv[0],
|
||||||
|
lerp(uv[1], uvs[0], uvs[1]),
|
||||||
|
lerp(uv[1], uvs[2], uvs[3]),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let pi = Point3fi::new_from_point(p);
|
||||||
|
let mut intr = SurfaceInteraction::new_simple(pi, n, st);
|
||||||
|
intr.common.time = ctx.time;
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(intr),
|
||||||
|
pdf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_sampled_normal(
|
||||||
|
&self,
|
||||||
|
data: &PatchData,
|
||||||
|
dpdu: &Vector3f,
|
||||||
|
dpdv: &Vector3f,
|
||||||
|
uv: Point2f,
|
||||||
|
) -> Normal3f {
|
||||||
|
let mut n = Normal3f::from(dpdu.cross(*dpdv).normalize());
|
||||||
|
|
||||||
|
if let Some(normals) = data.n {
|
||||||
|
// Apply interpolated shading normal to orient the geometric normal
|
||||||
|
let ns = lerp(
|
||||||
|
uv[0],
|
||||||
|
lerp(uv[1], normals[0], normals[2]),
|
||||||
|
lerp(uv[1], normals[1], normals[3]),
|
||||||
|
);
|
||||||
|
n = n.face_forward(ns.into());
|
||||||
|
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for BilinearPatchShape {
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
self.area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
let data = self.get_data();
|
||||||
|
if data.p00 == data.p10
|
||||||
|
|| data.p10 == data.p11
|
||||||
|
|| data.p11 == data.p01
|
||||||
|
|| data.p01 == data.p00
|
||||||
|
{
|
||||||
|
let dpdu = lerp(0.5, data.p10, data.p11) - lerp(0.5, data.p00, data.p01);
|
||||||
|
let dpdv = lerp(0.5, data.p01, data.p11) - lerp(0.5, data.p00, data.p10);
|
||||||
|
let mut n = Normal3f::from(dpdu.cross(dpdv).normalize());
|
||||||
|
if let Some(normals) = data.n {
|
||||||
|
let interp_n = (normals[0] + normals[1] + normals[2] + normals[3]) / 4.;
|
||||||
|
n = n.face_forward(interp_n.into());
|
||||||
|
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||||
|
n *= -1.;
|
||||||
|
}
|
||||||
|
return DirectionCone::new_from_vector(Vector3f::from(n));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute bilinear patch normals n10, n01, and n11
|
||||||
|
let mut n00 = Normal3f::from((data.p10 - data.p00).cross(data.p01 - data.p00).normalize());
|
||||||
|
let mut n10 = Normal3f::from((data.p11 - data.p10).cross(data.p00 - data.p10).normalize());
|
||||||
|
let mut n01 = Normal3f::from((data.p00 - data.p01).cross(data.p11 - data.p01).normalize());
|
||||||
|
let mut n11 = Normal3f::from((data.p01 - data.p11).cross(data.p10 - data.p11).normalize());
|
||||||
|
|
||||||
|
if let Some(normals) = data.n {
|
||||||
|
n00 = n00.face_forward(normals[0].into());
|
||||||
|
n10 = n10.face_forward(normals[1].into());
|
||||||
|
n01 = n01.face_forward(normals[2].into());
|
||||||
|
n11 = n11.face_forward(normals[3].into());
|
||||||
|
} else if data.mesh.reverse_orientation ^ data.mesh.transform_swaps_handedness {
|
||||||
|
n00 = -n00;
|
||||||
|
n10 = -n10;
|
||||||
|
n01 = -n01;
|
||||||
|
n11 = -n11;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute average normal and return normal bounds for patch
|
||||||
|
let n_avg = (n00 + n10 + n01 + n11).normalize();
|
||||||
|
let cos_theta = n_avg
|
||||||
|
.dot(n00)
|
||||||
|
.min(n_avg.dot(n10))
|
||||||
|
.min(n_avg.dot(n01))
|
||||||
|
.min(n_avg.dot(n11));
|
||||||
|
DirectionCone::new(n_avg.into(), clamp_t(cos_theta, -1., 1.))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
let data = self.get_data();
|
||||||
|
Bounds3f::from_points(data.p00, data.p01).union(Bounds3f::from_points(data.p10, data.p11))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
let t_max_val = t_max?;
|
||||||
|
let data = self.get_data();
|
||||||
|
if let Some(bilinear_hit) = self.intersect_bilinear_patch(ray, t_max_val, &data) {
|
||||||
|
let intr = self.interaction_from_intersection(&data, bilinear_hit.uv, ray.time, -ray.d);
|
||||||
|
|
||||||
|
Some(ShapeIntersection {
|
||||||
|
intr,
|
||||||
|
t_hit: bilinear_hit.t,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
let t_max_val = t_max.unwrap_or(Float::INFINITY);
|
||||||
|
let data = self.get_data();
|
||||||
|
self.intersect_bilinear_patch(ray, t_max_val, &data)
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let data = self.get_data();
|
||||||
|
// Sample bilinear patch parametric coordinate (u, v)
|
||||||
|
let (uv, pdf) = self.sample_parametric_coords(&data, u);
|
||||||
|
// Compute bilinear patch geometric quantities at sampled (u, v)
|
||||||
|
let (p, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv);
|
||||||
|
if dpdu.norm_squared() == 0. || dpdv.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute surface normal for sampled bilinear patch (u, v)
|
||||||
|
let n = self.compute_sampled_normal(&data, &dpdu, &dpdv, uv);
|
||||||
|
let st = data.uv.map_or(uv, |patch_uvs| {
|
||||||
|
lerp(
|
||||||
|
uv[0],
|
||||||
|
lerp(uv[1], patch_uvs[0], patch_uvs[1]),
|
||||||
|
lerp(uv[1], patch_uvs[2], patch_uvs[3]),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let p_abs_sum = data.p00.abs()
|
||||||
|
+ Vector3f::from(data.p01.abs())
|
||||||
|
+ Vector3f::from(data.p10.abs())
|
||||||
|
+ Vector3f::from(data.p11.abs());
|
||||||
|
let p_error = gamma(6) * Vector3f::from(p_abs_sum);
|
||||||
|
let pi = Point3fi::new_with_error(p, p_error);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, st)),
|
||||||
|
pdf: pdf / dpdu.cross(dpdv).norm(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let data = self.get_data();
|
||||||
|
let v00 = (data.p00 - ctx.p()).normalize();
|
||||||
|
let v10 = (data.p10 - ctx.p()).normalize();
|
||||||
|
let v01 = (data.p01 - ctx.p()).normalize();
|
||||||
|
let v11 = (data.p11 - ctx.p()).normalize();
|
||||||
|
|
||||||
|
let use_area_sampling = self.rectangle
|
||||||
|
|| data.mesh.image_distribution.is_some()
|
||||||
|
|| spherical_quad_area(v00, v10, v11, v01) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
|
||||||
|
if use_area_sampling {
|
||||||
|
self.sample_area_and_pdf(ctx, u)
|
||||||
|
} else {
|
||||||
|
self.sample_solid_angle(&data, ctx, u, &[v00, v10, v01, v11])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, intr: Arc<&dyn Interaction>) -> Float {
|
||||||
|
let Some(si) = intr.as_any().downcast_ref::<SurfaceInteraction>() else {
|
||||||
|
return 0.;
|
||||||
|
};
|
||||||
|
let data = self.get_data();
|
||||||
|
let uv = if let Some(uvs) = &data.mesh.uv {
|
||||||
|
Point2f::invert_bilinear(si.uv, &uvs)
|
||||||
|
} else {
|
||||||
|
si.uv
|
||||||
|
};
|
||||||
|
|
||||||
|
let param_pdf = if let Some(image_distrib) = &data.mesh.image_distribution {
|
||||||
|
image_distrib.pdf(uv)
|
||||||
|
} else if self.rectangle {
|
||||||
|
let w = [
|
||||||
|
(data.p10 - data.p00).cross(data.p01 - data.p00).norm(),
|
||||||
|
(data.p10 - data.p00).cross(data.p11 - data.p10).norm(),
|
||||||
|
(data.p01 - data.p00).cross(data.p11 - data.p01).norm(),
|
||||||
|
(data.p11 - data.p10).cross(data.p11 - data.p01).norm(),
|
||||||
|
];
|
||||||
|
bilinear_pdf(uv, &w)
|
||||||
|
} else {
|
||||||
|
1.
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, dpdu, dpdv) = self.calculate_base_derivatives(&data, uv);
|
||||||
|
let cross = dpdu.cross(dpdv).norm();
|
||||||
|
if cross == 0. { 0. } else { param_pdf / cross }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
|
let ray = ctx.spawn_ray(wi);
|
||||||
|
let Some(isect) = self.intersect(&ray, None) else {
|
||||||
|
return 0.;
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = self.get_data();
|
||||||
|
|
||||||
|
let v00 = (data.p00 - ctx.p()).normalize();
|
||||||
|
let v10 = (data.p10 - ctx.p()).normalize();
|
||||||
|
let v01 = (data.p01 - ctx.p()).normalize();
|
||||||
|
let v11 = (data.p11 - ctx.p()).normalize();
|
||||||
|
|
||||||
|
let use_area_sampling = !self.rectangle
|
||||||
|
|| data.mesh.image_distribution.is_some()
|
||||||
|
|| spherical_quad_area(v00, v10, v01, v11) <= Self::MIN_SPHERICAL_SAMPLE_AREA;
|
||||||
|
if use_area_sampling {
|
||||||
|
let isect_pdf = self.pdf(Arc::new(&isect.intr));
|
||||||
|
let distsq = ctx.p().distance_squared(isect.intr.p());
|
||||||
|
let absdot = Vector3f::from(isect.intr.n()).abs_dot(-wi);
|
||||||
|
if absdot == 0. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
let pdf = isect_pdf * distsq / absdot;
|
||||||
|
if pdf.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
} else {
|
||||||
|
return pdf;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mut pdf = 1. / spherical_quad_area(v00, v10, v01, v11);
|
||||||
|
if ctx.ns != Normal3f::zero() {
|
||||||
|
let w = [
|
||||||
|
0.01_f32.max(v00.dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(v10.dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(v01.dot(ctx.ns.into()).abs()),
|
||||||
|
0.01_f32.max(v11.dot(ctx.ns.into()).abs()),
|
||||||
|
];
|
||||||
|
let u = invert_spherical_rectangle_sample(
|
||||||
|
ctx.p(),
|
||||||
|
data.p00,
|
||||||
|
data.p10 - data.p00,
|
||||||
|
data.p01 - data.p00,
|
||||||
|
isect.intr.p(),
|
||||||
|
);
|
||||||
|
pdf *= bilinear_pdf(u, &w);
|
||||||
|
}
|
||||||
|
pdf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
316
src/shapes/curves.rs
Normal file
316
src/shapes/curves.rs
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
use crate::core::pbrt::{clamp_t, lerp};
|
||||||
|
use crate::utils::math::square;
|
||||||
|
use crate::utils::splines::{
|
||||||
|
bound_cubic_bezier, cubic_bezier_control_points, evaluate_cubic_bezier, subdivide_cubic_bezier,
|
||||||
|
};
|
||||||
|
use crate::utils::transform::look_at;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
Bounds3f, CurveCommon, CurveShape, CurveType, DirectionCone, Float, Interaction, Normal3f,
|
||||||
|
Point2f, Point3f, Point3fi, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
|
ShapeTrait, SurfaceInteraction, Transform, Vector2f, Vector3f, VectorLike,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
struct IntersectionContext<'a> {
|
||||||
|
ray: &'a Ray,
|
||||||
|
object_from_ray: &'a Transform<Float>,
|
||||||
|
common: &'a CurveCommon<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CurveShape<'a> {
|
||||||
|
pub fn new(common: CurveCommon<'a>, u_min: Float, u_max: Float) -> Self {
|
||||||
|
Self {
|
||||||
|
common,
|
||||||
|
u_min,
|
||||||
|
u_max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_ray(&self, r: &Ray, t_max: Float) -> Option<ShapeIntersection> {
|
||||||
|
let ray = self
|
||||||
|
.common
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_ray(r, &mut Some(t_max));
|
||||||
|
// Get object-space control points for curve segment, cpObj
|
||||||
|
let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max);
|
||||||
|
// Project curve control points to plane perpendicular to ray
|
||||||
|
let mut dx = ray.d.cross(cp_obj[3] - cp_obj[0]);
|
||||||
|
if dx.norm_squared() == 0. {
|
||||||
|
(dx, _) = ray.d.coordinate_system();
|
||||||
|
}
|
||||||
|
|
||||||
|
let ray_from_object = look_at(ray.o, ray.o + ray.d, dx.into()).expect("Inversion error");
|
||||||
|
let cp = [0; 4].map(|i| ray_from_object.apply_to_point(cp_obj[i]));
|
||||||
|
|
||||||
|
// Test ray against bound of projected control points
|
||||||
|
let max_width = lerp(self.u_min, self.common.width[0], self.common.width[1]).max(lerp(
|
||||||
|
self.u_max,
|
||||||
|
self.common.width[0],
|
||||||
|
self.common.width[1],
|
||||||
|
));
|
||||||
|
let curve_bounds = Bounds3f::from_points(cp[0], cp[1])
|
||||||
|
.union(Bounds3f::from_points(cp[2], cp[3]))
|
||||||
|
.expand(0.5 * max_width);
|
||||||
|
let ray_bounds =
|
||||||
|
Bounds3f::from_points(Point3f::zero(), Point3f::new(0., 0., ray.d.norm() * t_max));
|
||||||
|
if !ray_bounds.overlaps(&curve_bounds) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let l0 = (0..2)
|
||||||
|
.map(|i| {
|
||||||
|
(cp[i].x() - 2.0 * cp[i + 1].x() + cp[i + 2].x())
|
||||||
|
.abs()
|
||||||
|
.max((cp[i].y() - 2.0 * cp[i + 1].y() + cp[i + 2].y()).abs())
|
||||||
|
.max((cp[i].z() - 2.0 * cp[i + 1].z() + cp[i + 2].z()).abs())
|
||||||
|
})
|
||||||
|
.fold(0.0, Float::max);
|
||||||
|
|
||||||
|
let max_depth = if l0 > 0. {
|
||||||
|
let eps = self.common.width[0].max(self.common.width[1]) * 0.05;
|
||||||
|
let r0: i32 = (1.41421356237 * 6. * l0 / (8. * eps)).log2() as i32 / 2;
|
||||||
|
clamp_t(r0, 0, 10)
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
let context = IntersectionContext {
|
||||||
|
ray: &ray,
|
||||||
|
object_from_ray: &ray_from_object.inverse(),
|
||||||
|
common: &self.common,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.recursive_intersect(&context, t_max, &cp, self.u_min, self.u_max, max_depth)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recursive_intersect(
|
||||||
|
&self,
|
||||||
|
context: &IntersectionContext,
|
||||||
|
mut t_max: Float,
|
||||||
|
cp: &[Point3f],
|
||||||
|
u0: Float,
|
||||||
|
u1: Float,
|
||||||
|
depth: i32,
|
||||||
|
) -> Option<ShapeIntersection> {
|
||||||
|
if depth > 0 {
|
||||||
|
let cp_split = subdivide_cubic_bezier(cp);
|
||||||
|
let u = [u0, (u0 + u1) / 2., u1];
|
||||||
|
let mut best_hit: Option<ShapeIntersection> = None;
|
||||||
|
|
||||||
|
for seg in 0..2 {
|
||||||
|
let cps: &[Point3f] = &cp_split[3 * seg..3 * seg + 4];
|
||||||
|
|
||||||
|
let max_width = lerp(u[seg], self.common.width[0], self.common.width[1]).max(lerp(
|
||||||
|
u[seg + 1],
|
||||||
|
self.common.width[0],
|
||||||
|
self.common.width[1],
|
||||||
|
));
|
||||||
|
let curve_bounds = Bounds3f::from_points(cps[0], cps[1])
|
||||||
|
.union(Bounds3f::from_points(cps[2], cps[3]))
|
||||||
|
.expand(0.5 * max_width);
|
||||||
|
let ray_bounds = Bounds3f::from_points(
|
||||||
|
Point3f::zero(),
|
||||||
|
Point3f::new(0., 0., context.ray.d.norm() * t_max),
|
||||||
|
);
|
||||||
|
|
||||||
|
if !ray_bounds.overlaps(&curve_bounds) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(hit) =
|
||||||
|
self.recursive_intersect(context, t_max, cps, u[seg], u[seg + 1], depth - 1)
|
||||||
|
{
|
||||||
|
best_hit = Some(hit);
|
||||||
|
t_max = best_hit.as_ref().unwrap().t_hit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
best_hit
|
||||||
|
} else {
|
||||||
|
self.intersect_segment(context, t_max, cp, u0, u1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_segment(
|
||||||
|
&self,
|
||||||
|
context: &IntersectionContext,
|
||||||
|
t_max: Float,
|
||||||
|
cp: &[Point3f],
|
||||||
|
u0: Float,
|
||||||
|
u1: Float,
|
||||||
|
) -> Option<ShapeIntersection> {
|
||||||
|
let edge1 = (cp[1].y() - cp[0].y()) * -cp[0].y() + cp[0].x() * (cp[0].x() - cp[1].x());
|
||||||
|
let edge2 = (cp[2].y() - cp[3].y()) * -cp[3].y() + cp[3].x() * (cp[3].x() - cp[2].x());
|
||||||
|
if edge1 <= 0.0 || edge2 <= 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let segment_dir = Point2f::new(cp[3].x(), cp[3].y()) - Point2f::new(cp[0].x(), cp[0].y());
|
||||||
|
let denom = segment_dir.norm_squared();
|
||||||
|
if denom == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let w = Vector2f::new(cp[0].x(), cp[0].y()).dot(-segment_dir) / denom;
|
||||||
|
let u = clamp_t(lerp(w, u0, u1), u0, u1);
|
||||||
|
let ray_length = context.ray.d.norm();
|
||||||
|
|
||||||
|
let mut hit_width = lerp(u, self.common.width[0], self.common.width[1]);
|
||||||
|
let mut n_hit = Normal3f::zero();
|
||||||
|
if let CurveType::Ribbon = context.common.curve_type {
|
||||||
|
n_hit = if context.common.normal_angle == 0. {
|
||||||
|
context.common.n[0]
|
||||||
|
} else {
|
||||||
|
let sin0 =
|
||||||
|
((1. - u) * self.common.normal_angle).sin() * self.common.inv_sin_normal_angle;
|
||||||
|
let sin1 = (u * self.common.normal_angle).sin() * self.common.inv_sin_normal_angle;
|
||||||
|
sin0 * self.common.n[0] + sin1 * self.common.n[1]
|
||||||
|
};
|
||||||
|
hit_width = hit_width * n_hit.dot(context.ray.d.into()).abs() / ray_length;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (pc, dpcdw) = evaluate_cubic_bezier(cp, clamp_t(w, 0., 1.));
|
||||||
|
let ray_length = context.ray.d.norm();
|
||||||
|
|
||||||
|
if !self.valid_hit(pc, hit_width, t_max, ray_length) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hit is valid, obtain normals, and interaction differentials
|
||||||
|
self.intersection_result(context, pc, dpcdw, u, hit_width, n_hit, ray_length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hit_ribbon(
|
||||||
|
&self,
|
||||||
|
u: Float,
|
||||||
|
hit_width: Float,
|
||||||
|
context: &IntersectionContext,
|
||||||
|
) -> (Float, Normal3f) {
|
||||||
|
let n_hit = if context.common.normal_angle == 0. {
|
||||||
|
context.common.n[0]
|
||||||
|
} else {
|
||||||
|
let sin0 =
|
||||||
|
((1. - u) * self.common.normal_angle).sin() * self.common.inv_sin_normal_angle;
|
||||||
|
let sin1 = (u * self.common.normal_angle).sin() * self.common.inv_sin_normal_angle;
|
||||||
|
sin0 * self.common.n[0] + sin1 * self.common.n[1]
|
||||||
|
};
|
||||||
|
let new_hit_width =
|
||||||
|
hit_width * n_hit.dot(context.ray.d.into()).abs() / context.ray.d.norm();
|
||||||
|
|
||||||
|
(new_hit_width, n_hit)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn valid_hit(&self, pc: Point3f, hit_width: Float, t_max: Float, ray_length: Float) -> bool {
|
||||||
|
let pt_curve_dist_sq = square(pc.x()) + square(pc.y());
|
||||||
|
if pt_curve_dist_sq > square(hit_width * 0.5) || pc.z() < 0.0 || pc.z() > ray_length * t_max
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersection_result(
|
||||||
|
&self,
|
||||||
|
context: &IntersectionContext,
|
||||||
|
pc: Point3f,
|
||||||
|
dpcdw: Vector3f,
|
||||||
|
u: Float,
|
||||||
|
hit_width: Float,
|
||||||
|
n_hit: Normal3f,
|
||||||
|
ray_length: Float,
|
||||||
|
) -> Option<ShapeIntersection> {
|
||||||
|
let t_hit = pc.z() / ray_length;
|
||||||
|
let pt_curve_dist = (square(pc.x()) + square(pc.y())).sqrt();
|
||||||
|
let edge_func = dpcdw.x() * -pc.y() + dpcdw.y() * pc.x();
|
||||||
|
let v = if edge_func > 0. {
|
||||||
|
0.5 + pt_curve_dist / hit_width
|
||||||
|
} else {
|
||||||
|
0.5 - pt_curve_dist / hit_width
|
||||||
|
};
|
||||||
|
let (_, dpdu) = evaluate_cubic_bezier(&self.common.cp_obj, u);
|
||||||
|
let dpdv = match context.common.curve_type {
|
||||||
|
CurveType::Ribbon => Vector3f::from(n_hit).cross(dpdu).normalize() * hit_width,
|
||||||
|
_ => {
|
||||||
|
let dpdu_plane = context.object_from_ray.apply_inverse_vector(dpdu);
|
||||||
|
let mut dpdv_plane =
|
||||||
|
Vector3f::new(-dpdu_plane.y(), dpdu_plane.x(), 0.).normalize() * hit_width;
|
||||||
|
if context.common.curve_type == CurveType::Cylinder {
|
||||||
|
let theta = lerp(v, -90., 90.);
|
||||||
|
let rot = Transform::rotate_around_axis(-theta, dpdu_plane);
|
||||||
|
dpdv_plane = rot.apply_to_vector(dpdv_plane);
|
||||||
|
}
|
||||||
|
context.object_from_ray.apply_to_vector(dpdv_plane)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let p_error = Vector3f::fill(hit_width);
|
||||||
|
let flip_normal = self.common.reverse_orientation ^ self.common.transform_swap_handedness;
|
||||||
|
let pi = Point3fi::new_with_error(context.ray.evaluate(t_hit), p_error);
|
||||||
|
let intr = SurfaceInteraction::new(
|
||||||
|
pi,
|
||||||
|
Point2f::new(u, v),
|
||||||
|
-context.ray.d,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
Normal3f::default(),
|
||||||
|
Normal3f::default(),
|
||||||
|
context.ray.time,
|
||||||
|
flip_normal,
|
||||||
|
);
|
||||||
|
|
||||||
|
Some(ShapeIntersection { intr, t_hit })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for CurveShape<'_> {
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
let cs_span = self.common.cp_obj;
|
||||||
|
let obj_bounds = bound_cubic_bezier(&cs_span, self.u_min, self.u_max);
|
||||||
|
let width0 = lerp(self.u_min, self.common.width[0], self.common.width[1]);
|
||||||
|
let width1 = lerp(self.u_max, self.common.width[0], self.common.width[1]);
|
||||||
|
let obj_bounds_expand = obj_bounds.expand(width0.max(width1) * 0.5);
|
||||||
|
self.common
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_bounds(obj_bounds_expand)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
DirectionCone::entire_sphere()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
let cp_obj = cubic_bezier_control_points(&self.common.cp_obj, self.u_min, self.u_max);
|
||||||
|
let width0 = lerp(self.u_min, self.common.width[0], self.common.width[1]);
|
||||||
|
let width1 = lerp(self.u_max, self.common.width[0], self.common.width[1]);
|
||||||
|
let avg_width = (width0 + width1) / 2.;
|
||||||
|
let mut approx_length = 0.;
|
||||||
|
for i in 0..3 {
|
||||||
|
approx_length += cp_obj[i].distance(cp_obj[i + 1]);
|
||||||
|
}
|
||||||
|
approx_length * avg_width
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
self.intersect_ray(ray, t_max.unwrap_or(Float::INFINITY))
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
self.intersect_ray(ray, t_max.unwrap_or(Float::INFINITY))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, _interaction: Arc<&dyn Interaction>) -> Float {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, _ctx: &ShapeSampleContext, _wi: Vector3f) -> Float {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, _u: Point2f) -> Option<ShapeSample> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, _ctx: &ShapeSampleContext, _u: Point2f) -> Option<ShapeSample> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
271
src/shapes/cylinder.rs
Normal file
271
src/shapes/cylinder.rs
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
use super::{
|
||||||
|
Bounds3f, CylinderShape, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f,
|
||||||
|
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
|
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{gamma, lerp};
|
||||||
|
use crate::geometry::{Sqrt, Tuple, VectorLike};
|
||||||
|
use crate::utils::interval::Interval;
|
||||||
|
use crate::utils::math::{difference_of_products, square};
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl<'a> CylinderShape<'a> {
|
||||||
|
pub fn new(
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
radius: Float,
|
||||||
|
z_min: Float,
|
||||||
|
z_max: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
radius,
|
||||||
|
z_min,
|
||||||
|
z_max,
|
||||||
|
phi_max,
|
||||||
|
render_from_object,
|
||||||
|
object_from_render,
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swap_handedness: render_from_object.swaps_handedness(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> {
|
||||||
|
// Transform Ray origin and direction to object space
|
||||||
|
let oi = self
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_interval(&Point3fi::new_from_point(r.o));
|
||||||
|
let di = self
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_vector_interval(&Vector3fi::new_from_vector(r.d));
|
||||||
|
// Solve quadratic equation to find cylinder t0 and t1 values>>
|
||||||
|
let a: Interval = square(di.x()) + square(di.y()) + square(di.z());
|
||||||
|
let b: Interval = 2. * (di.x() * oi.x() + di.y() * oi.y() + di.z() * oi.z());
|
||||||
|
let c: Interval =
|
||||||
|
square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius));
|
||||||
|
let f = b / (2. * a);
|
||||||
|
let vx: Interval = oi.x() - f * di.x();
|
||||||
|
let vy: Interval = oi.y() - f * di.y();
|
||||||
|
let length: Interval = (square(vx) + square(vy)).sqrt();
|
||||||
|
let discrim: Interval =
|
||||||
|
4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length);
|
||||||
|
if discrim.low < 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let root_discrim = discrim.sqrt();
|
||||||
|
let q: Interval;
|
||||||
|
if Float::from(b) < 0. {
|
||||||
|
q = -0.5 * (b - root_discrim);
|
||||||
|
} else {
|
||||||
|
q = -0.5 * (b + root_discrim);
|
||||||
|
}
|
||||||
|
let mut t0 = q / a;
|
||||||
|
let mut t1 = c / q;
|
||||||
|
if t0.low > t1.low {
|
||||||
|
mem::swap(&mut t0, &mut t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check quadric shape t0 and t1 for nearest intersection
|
||||||
|
if t0.high > t_max || t1.low < 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut t_shape_hit: Interval = t0;
|
||||||
|
if t_shape_hit.low <= 0. {
|
||||||
|
t_shape_hit = t1;
|
||||||
|
if t_shape_hit.high > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute cylinder hit point and phi
|
||||||
|
let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di);
|
||||||
|
let hit_rad = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
||||||
|
p_hit[0] *= self.radius / hit_rad;
|
||||||
|
p_hit[1] *= self.radius / hit_rad;
|
||||||
|
|
||||||
|
let mut phi = p_hit.y().atan2(p_hit.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.z_min > -self.radius && p_hit.z() < self.z_min
|
||||||
|
|| self.z_max < self.radius && p_hit.z() > self.z_max
|
||||||
|
|| phi > self.phi_max
|
||||||
|
{
|
||||||
|
if t_shape_hit == t1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if t1.high > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
t_shape_hit = t1;
|
||||||
|
let mut p_hit =
|
||||||
|
Vector3f::from(Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di));
|
||||||
|
let hit_rad = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
||||||
|
p_hit[0] *= self.radius / hit_rad;
|
||||||
|
p_hit[1] *= self.radius / hit_rad;
|
||||||
|
phi = p_hit.y().atan2(p_hit.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if p_hit.z() < self.z_min || p_hit.z() > self.z_max || phi > self.phi_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(QuadricIntersection::new(t_shape_hit.into(), p_hit, phi))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interaction_from_intersection(
|
||||||
|
&self,
|
||||||
|
isect: QuadricIntersection,
|
||||||
|
wo: Vector3f,
|
||||||
|
time: Float,
|
||||||
|
) -> SurfaceInteraction {
|
||||||
|
let p_hit = isect.p_obj;
|
||||||
|
let phi = isect.phi;
|
||||||
|
let u = phi / self.phi_max;
|
||||||
|
let v = (p_hit.z() - self.z_min) / (self.z_max - self.z_min);
|
||||||
|
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
|
||||||
|
let dpdv = Vector3f::new(0., 0., self.z_max - self.z_min);
|
||||||
|
let d2pduu = -self.phi_max * self.phi_max * Vector3f::new(p_hit.x(), p_hit.y(), 0.);
|
||||||
|
let d2pduv = Vector3f::zero();
|
||||||
|
let d2pdvv = Vector3f::zero();
|
||||||
|
let e = dpdu.dot(dpdu);
|
||||||
|
let f = dpdu.dot(dpdv);
|
||||||
|
let g = dpdv.dot(dpdv);
|
||||||
|
let n: Vector3f = dpdu.cross(dpdv).normalize();
|
||||||
|
let e_min = n.dot(d2pduu);
|
||||||
|
let f_min = n.dot(d2pduv);
|
||||||
|
let g_min = n.dot(d2pdvv);
|
||||||
|
// Compute dn/du and dn/dv from fundamental form coefficients
|
||||||
|
let efg2 = difference_of_products(e, f, f, f);
|
||||||
|
let inv_efg2 = if efg2 == 0. { 0. } else { 1. / efg2 };
|
||||||
|
let dndu = Normal3f::from(
|
||||||
|
(f_min * f - e_min * g) * inv_efg2 * dpdu + (e_min * f - f_min * e) * inv_efg2 * dpdv,
|
||||||
|
);
|
||||||
|
let dndv = Normal3f::from(
|
||||||
|
(g_min * f - f_min * g) * inv_efg2 * dpdu + (f_min * f - g_min * e) * inv_efg2 * dpdv,
|
||||||
|
);
|
||||||
|
|
||||||
|
let p_error = gamma(3) * Vector3f::new(p_hit.x(), p_hit.y(), 0.).abs();
|
||||||
|
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
|
||||||
|
let wo_object = self.object_from_render.apply_to_vector(wo);
|
||||||
|
// (*renderFromObject)
|
||||||
|
let surf_point = SurfaceInteraction::new(
|
||||||
|
Point3fi::new_with_error(p_hit, p_error),
|
||||||
|
Point2f::new(u, v),
|
||||||
|
wo_object,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
dndu,
|
||||||
|
dndv,
|
||||||
|
time,
|
||||||
|
flip_normal,
|
||||||
|
);
|
||||||
|
surf_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for CylinderShape<'_> {
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
(self.z_max - self.z_min) * self.radius * self.phi_max
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
self.render_from_object
|
||||||
|
.apply_to_bounds(Bounds3f::from_points(
|
||||||
|
Point3f::new(-self.radius, -self.radius, self.z_min),
|
||||||
|
Point3f::new(self.radius, self.radius, self.z_max),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
DirectionCone::entire_sphere()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
let t = t_max.unwrap_or(Float::INFINITY);
|
||||||
|
if let Some(isect) = self.basic_intersect(ray, t) {
|
||||||
|
let intr = self.interaction_from_intersection(isect.clone(), -ray.d, ray.time);
|
||||||
|
return Some(ShapeIntersection::new(intr, isect.t_hit));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
if let Some(t) = t_max {
|
||||||
|
self.basic_intersect(ray, t).is_some()
|
||||||
|
} else {
|
||||||
|
self.basic_intersect(ray, Float::INFINITY).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, _interaction: Arc<&dyn Interaction>) -> Float {
|
||||||
|
1. / self.area()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
|
let ray = ctx.spawn_ray(wi);
|
||||||
|
if let Some(isect) = self.intersect(&ray, None) {
|
||||||
|
let n = isect.intr.n();
|
||||||
|
let absdot = Vector3f::from(n).dot(-wi).abs();
|
||||||
|
let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
|
||||||
|
if pdf.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
return pdf;
|
||||||
|
} else {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let z = lerp(u[0], self.z_min, self.z_max);
|
||||||
|
let phi = u[1] * self.phi_max;
|
||||||
|
let mut p_obj = Point3f::new(self.radius * phi.cos(), self.radius * phi.sin(), z);
|
||||||
|
let hit_rad = (square(p_obj.x()) + square(p_obj.y())).sqrt();
|
||||||
|
p_obj[0] *= self.radius / hit_rad;
|
||||||
|
p_obj[1] *= self.radius / hit_rad;
|
||||||
|
let p_obj_error = gamma(3) * Vector3f::new(p_obj.x(), p_obj.y(), 0.).abs();
|
||||||
|
let pi = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_interval(&Point3fi::new_with_error(p_obj, p_obj_error));
|
||||||
|
let mut n = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_normal(Normal3f::new(p_obj.x(), p_obj.y(), 0.))
|
||||||
|
.normalize();
|
||||||
|
if self.reverse_orientation {
|
||||||
|
n *= -1.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uv = Point2f::new(
|
||||||
|
phi / self.phi_max,
|
||||||
|
(p_obj.z() - self.z_min) / (self.z_max - self.z_min),
|
||||||
|
);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||||
|
pdf: 1. / self.area(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let mut ss = self.sample(u)?;
|
||||||
|
let intr = Arc::make_mut(&mut ss.intr);
|
||||||
|
intr.get_common_mut().time = ctx.time;
|
||||||
|
let mut wi = ss.intr.p() - ctx.p();
|
||||||
|
if wi.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
wi = wi.normalize();
|
||||||
|
ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
|
||||||
|
if ss.pdf.is_infinite() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(ss);
|
||||||
|
}
|
||||||
|
}
|
||||||
208
src/shapes/disk.rs
Normal file
208
src/shapes/disk.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
use super::{
|
||||||
|
Bounds3f, DirectionCone, DiskShape, Float, Interaction, Normal3f, PI, Point2f, Point3f,
|
||||||
|
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
|
||||||
|
ShapeTrait, SurfaceInteraction, Transform, Vector3f,
|
||||||
|
};
|
||||||
|
use crate::geometry::VectorLike;
|
||||||
|
use crate::utils::math::square;
|
||||||
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl<'a> DiskShape<'a> {
|
||||||
|
pub fn new(
|
||||||
|
radius: Float,
|
||||||
|
inner_radius: Float,
|
||||||
|
height: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
radius,
|
||||||
|
inner_radius,
|
||||||
|
height,
|
||||||
|
phi_max,
|
||||||
|
render_from_object,
|
||||||
|
object_from_render,
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swap_handedness: render_from_object.swaps_handedness(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> {
|
||||||
|
let oi = self.object_from_render.apply_to_point(r.o);
|
||||||
|
let di = self.object_from_render.apply_to_vector(r.d);
|
||||||
|
// Reject disk intersections for rays parallel to the disk’s plane
|
||||||
|
if di.z() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let t_shape_hit = (self.height - oi.z()) / di.z();
|
||||||
|
if t_shape_hit == 0. || t_shape_hit >= t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See if hit point is inside disk radii and phi_max
|
||||||
|
let p_hit: Point3f = oi + t_shape_hit * di;
|
||||||
|
let dist2 = square(p_hit.x()) + square(p_hit.y());
|
||||||
|
if dist2 > square(self.radius) || dist2 < square(self.inner_radius) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut phi = p_hit.y().atan2(p_hit.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
if phi > self.phi_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(QuadricIntersection {
|
||||||
|
t_hit: t_shape_hit,
|
||||||
|
p_obj: p_hit,
|
||||||
|
phi,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interaction_from_intersection(
|
||||||
|
&self,
|
||||||
|
isect: QuadricIntersection,
|
||||||
|
wo: Vector3f,
|
||||||
|
time: Float,
|
||||||
|
) -> SurfaceInteraction {
|
||||||
|
let mut p_hit = isect.p_obj;
|
||||||
|
let phi = isect.phi;
|
||||||
|
// Find parametric representation of disk hit
|
||||||
|
let u = phi / self.phi_max;
|
||||||
|
let r_hit = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
||||||
|
let v = (self.radius - r_hit) / (self.radius - self.inner_radius);
|
||||||
|
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
|
||||||
|
let dpdv =
|
||||||
|
Vector3f::new(p_hit.x(), p_hit.y(), 0.) * (self.inner_radius - self.radius) / r_hit;
|
||||||
|
let dndu = Normal3f::zero();
|
||||||
|
let dndv = Normal3f::zero();
|
||||||
|
|
||||||
|
p_hit[2] = self.height;
|
||||||
|
|
||||||
|
let p_error = Vector3f::zero();
|
||||||
|
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
|
||||||
|
let wo_object = self.object_from_render.apply_to_vector(wo);
|
||||||
|
let surf_point = SurfaceInteraction::new(
|
||||||
|
Point3fi::new_with_error(p_hit, p_error),
|
||||||
|
Point2f::new(u, v),
|
||||||
|
wo_object,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
dndu,
|
||||||
|
dndv,
|
||||||
|
time,
|
||||||
|
flip_normal,
|
||||||
|
);
|
||||||
|
surf_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for DiskShape<'_> {
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
self.phi_max * 0.5 * (square(self.radius) - square(self.inner_radius))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
self.render_from_object
|
||||||
|
.apply_to_bounds(Bounds3f::from_points(
|
||||||
|
Point3f::new(-self.radius, -self.radius, self.height),
|
||||||
|
Point3f::new(self.radius, self.radius, self.height),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
let mut n = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_normal(Normal3f::new(0., 0., 1.));
|
||||||
|
if self.reverse_orientation {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
DirectionCone::new_from_vector(Vector3f::from(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
let t = t_max.unwrap_or(Float::INFINITY);
|
||||||
|
if let Some(isect) = self.basic_intersect(ray, t) {
|
||||||
|
let intr = self.interaction_from_intersection(isect.clone(), -ray.d, ray.time);
|
||||||
|
return Some(ShapeIntersection::new(intr, isect.t_hit));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let pd = sample_uniform_disk_concentric(u);
|
||||||
|
let p_obj = Point3f::new(pd.x() * self.radius, pd.y() * self.radius, self.height);
|
||||||
|
let pi = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_interval(&Point3fi::new_from_point(p_obj));
|
||||||
|
let mut n: Normal3f = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_normal(Normal3f::new(0., 0., 1.))
|
||||||
|
.normalize();
|
||||||
|
if self.reverse_orientation {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
let mut phi = pd.y().atan2(pd.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
let radius_sample = (square(p_obj.x()) + square(p_obj.y())).sqrt();
|
||||||
|
let uv = Point2f::new(
|
||||||
|
phi / self.phi_max,
|
||||||
|
(self.radius - radius_sample) / (self.radius - self.inner_radius),
|
||||||
|
);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)),
|
||||||
|
pdf: 1. / self.area(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
if let Some(t) = t_max {
|
||||||
|
self.basic_intersect(ray, t).is_some()
|
||||||
|
} else {
|
||||||
|
self.basic_intersect(ray, Float::INFINITY).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let mut ss = self.sample(u)?;
|
||||||
|
let intr = Arc::make_mut(&mut ss.intr);
|
||||||
|
intr.get_common_mut().time = ctx.time;
|
||||||
|
let mut wi = ss.intr.p() - ctx.p();
|
||||||
|
if wi.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
wi = wi.normalize();
|
||||||
|
ss.pdf = Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
|
||||||
|
if ss.pdf.is_infinite() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(ss);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, _interaction: Arc<&dyn Interaction>) -> Float {
|
||||||
|
1. / self.area()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
|
let ray = ctx.spawn_ray(wi);
|
||||||
|
if let Some(isect) = self.intersect(&ray, None) {
|
||||||
|
let n = isect.intr.n();
|
||||||
|
let absdot = Vector3f::from(n).dot(-wi).abs();
|
||||||
|
let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
|
||||||
|
if pdf.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
return pdf;
|
||||||
|
} else {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
289
src/shapes/mod.rs
Normal file
289
src/shapes/mod.rs
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
pub mod bilinear;
|
||||||
|
pub mod curves;
|
||||||
|
pub mod cylinder;
|
||||||
|
pub mod disk;
|
||||||
|
pub mod sphere;
|
||||||
|
pub mod triangle;
|
||||||
|
|
||||||
|
use crate::core::interaction::{Interaction, MediumInteraction, SurfaceInteraction};
|
||||||
|
use crate::core::pbrt::{Float, PI};
|
||||||
|
use crate::geometry::{
|
||||||
|
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
||||||
|
Vector3fi, VectorLike,
|
||||||
|
};
|
||||||
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
|
use crate::utils::transform::Transform;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SphereShape<'a> {
|
||||||
|
radius: Float,
|
||||||
|
z_min: Float,
|
||||||
|
z_max: Float,
|
||||||
|
theta_z_min: Float,
|
||||||
|
theta_z_max: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
transform_swap_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CylinderShape<'a> {
|
||||||
|
radius: Float,
|
||||||
|
z_min: Float,
|
||||||
|
z_max: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
transform_swap_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DiskShape<'a> {
|
||||||
|
radius: Float,
|
||||||
|
inner_radius: Float,
|
||||||
|
height: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
transform_swap_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TriangleShape {
|
||||||
|
pub mesh_ind: usize,
|
||||||
|
pub tri_index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BilinearPatchShape {
|
||||||
|
mesh_index: usize,
|
||||||
|
blp_index: usize,
|
||||||
|
area: Float,
|
||||||
|
rectangle: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum CurveType {
|
||||||
|
Flat,
|
||||||
|
Cylinder,
|
||||||
|
Ribbon,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CurveCommon<'a> {
|
||||||
|
curve_type: CurveType,
|
||||||
|
cp_obj: [Point3f; 4],
|
||||||
|
width: [Float; 2],
|
||||||
|
n: [Normal3f; 2],
|
||||||
|
normal_angle: Float,
|
||||||
|
inv_sin_normal_angle: Float,
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
transform_swap_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> CurveCommon<'a> {
|
||||||
|
pub fn new(
|
||||||
|
c: &[Point3f],
|
||||||
|
w0: Float,
|
||||||
|
w1: Float,
|
||||||
|
curve_type: CurveType,
|
||||||
|
norm: &[Vector3f],
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
) -> Self {
|
||||||
|
let transform_swap_handedness = render_from_object.swaps_handedness();
|
||||||
|
let width = [w0, w1];
|
||||||
|
assert_eq!(c.len(), 4);
|
||||||
|
let mut cp_obj = [Point3f::default(); 4];
|
||||||
|
for i in 0..4 {
|
||||||
|
cp_obj[i] = c[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut n = [Normal3f::default(); 2];
|
||||||
|
let mut normal_angle: Float = 0.;
|
||||||
|
let mut inv_sin_normal_angle: Float = 0.;
|
||||||
|
if norm.len() == 2 {
|
||||||
|
n[0] = norm[0].normalize().into();
|
||||||
|
n[1] = norm[1].normalize().into();
|
||||||
|
normal_angle = n[0].angle_between(n[1]);
|
||||||
|
inv_sin_normal_angle = 1. / normal_angle.sin();
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
curve_type,
|
||||||
|
cp_obj,
|
||||||
|
width,
|
||||||
|
n,
|
||||||
|
normal_angle,
|
||||||
|
inv_sin_normal_angle,
|
||||||
|
render_from_object,
|
||||||
|
object_from_render,
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swap_handedness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CurveShape<'a> {
|
||||||
|
common: CurveCommon<'a>,
|
||||||
|
u_min: Float,
|
||||||
|
u_max: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define Intersection objects. This only varies for
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ShapeIntersection {
|
||||||
|
intr: SurfaceInteraction,
|
||||||
|
t_hit: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeIntersection {
|
||||||
|
pub fn new(intr: SurfaceInteraction, t_hit: Float) -> Self {
|
||||||
|
Self { intr, t_hit }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intr(&self) -> &SurfaceInteraction {
|
||||||
|
&self.intr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intr_mut(&mut self) -> &mut SurfaceInteraction {
|
||||||
|
&mut self.intr
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn t_hit(&self) -> Float {
|
||||||
|
self.t_hit
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_t_hit(&mut self, new_t: Float) {
|
||||||
|
self.t_hit = new_t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct QuadricIntersection {
|
||||||
|
t_hit: Float,
|
||||||
|
p_obj: Point3f,
|
||||||
|
phi: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuadricIntersection {
|
||||||
|
pub fn new(t_hit: Float, p_obj: Point3f, phi: Float) -> Self {
|
||||||
|
Self { t_hit, p_obj, phi }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct TriangleIntersection {
|
||||||
|
b0: Float,
|
||||||
|
b1: Float,
|
||||||
|
b2: Float,
|
||||||
|
t: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriangleIntersection {
|
||||||
|
pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self {
|
||||||
|
Self { b0, b1, b2, t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BilinearIntersection {
|
||||||
|
uv: Point2f,
|
||||||
|
t: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BilinearIntersection {
|
||||||
|
pub fn new(uv: Point2f, t: Float) -> Self {
|
||||||
|
Self { uv, t }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ShapeSample {
|
||||||
|
intr: Arc<SurfaceInteraction>,
|
||||||
|
pdf: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct ShapeSampleContext {
|
||||||
|
pi: Point3fi,
|
||||||
|
n: Normal3f,
|
||||||
|
ns: Normal3f,
|
||||||
|
time: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeSampleContext {
|
||||||
|
pub fn new(pi: Point3fi, n: Normal3f, ns: Normal3f, time: Float) -> Self {
|
||||||
|
Self { pi, n, ns, time }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_from_interaction(si: &SurfaceInteraction) -> Self {
|
||||||
|
Self {
|
||||||
|
pi: si.pi(),
|
||||||
|
n: si.n(),
|
||||||
|
ns: si.shading.n,
|
||||||
|
time: si.time(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn p(&self) -> Point3f {
|
||||||
|
Point3f::from(self.pi)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_ray_origin(&self, w: Vector3f) -> Point3f {
|
||||||
|
let d = self.n.abs().dot(self.pi.error().into());
|
||||||
|
let mut offset = d * Vector3f::from(self.n);
|
||||||
|
if w.dot(self.n.into()) < 0.0 {
|
||||||
|
offset = -offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut po = Point3f::from(self.pi) + offset;
|
||||||
|
for i in 0..3 {
|
||||||
|
if offset[i] > 0.0 {
|
||||||
|
po[i] = next_float_up(po[i]);
|
||||||
|
} else {
|
||||||
|
po[i] = next_float_down(po[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
po
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset_ray_origin_from_point(&self, pt: Point3f) -> Point3f {
|
||||||
|
self.offset_ray_origin(pt - self.p())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn_ray(&self, w: Vector3f) -> Ray {
|
||||||
|
Ray::new(self.offset_ray_origin(w), w, Some(self.time), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Shape<'a> {
|
||||||
|
Sphere(SphereShape<'a>),
|
||||||
|
Cylinder(CylinderShape<'a>),
|
||||||
|
Disk(DiskShape<'a>),
|
||||||
|
Triangle(TriangleShape),
|
||||||
|
BilinearPatch(BilinearPatchShape),
|
||||||
|
Curve(CurveShape<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ShapeTrait: Send + Sync + std::fmt::Debug {
|
||||||
|
fn bounds(&self) -> Bounds3f;
|
||||||
|
fn normal_bounds(&self) -> DirectionCone;
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection>;
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool;
|
||||||
|
fn area(&self) -> Float;
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample>;
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample>;
|
||||||
|
fn pdf(&self, interaction: Arc<&dyn Interaction>) -> Float;
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float;
|
||||||
|
}
|
||||||
360
src/shapes/sphere.rs
Normal file
360
src/shapes/sphere.rs
Normal file
|
|
@ -0,0 +1,360 @@
|
||||||
|
use super::{
|
||||||
|
Bounds3f, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Point3fi,
|
||||||
|
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
||||||
|
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{clamp_t, gamma};
|
||||||
|
use crate::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
|
||||||
|
use crate::utils::interval::Interval;
|
||||||
|
use crate::utils::math::{difference_of_products, radians, safe_acos, safe_sqrt, square};
|
||||||
|
use crate::utils::sampling::sample_uniform_sphere;
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl<'a> SphereShape<'a> {
|
||||||
|
pub fn new(
|
||||||
|
render_from_object: &'a Transform<Float>,
|
||||||
|
object_from_render: &'a Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
radius: Float,
|
||||||
|
z_min: Float,
|
||||||
|
z_max: Float,
|
||||||
|
phi_max: Float,
|
||||||
|
) -> Self {
|
||||||
|
let theta_z_min = clamp_t(z_min.min(z_max) / radius, -1., 1.).acos();
|
||||||
|
let theta_z_max = clamp_t(z_max.min(z_max) / radius, -1., 1.).acos();
|
||||||
|
let phi_max = radians(clamp_t(phi_max, 0., 360.0));
|
||||||
|
Self {
|
||||||
|
render_from_object,
|
||||||
|
object_from_render,
|
||||||
|
radius,
|
||||||
|
z_min: clamp_t(z_min.min(z_max), -radius, radius),
|
||||||
|
z_max: clamp_t(z_min.max(z_max), -radius, radius),
|
||||||
|
theta_z_max,
|
||||||
|
theta_z_min,
|
||||||
|
phi_max,
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swap_handedness: render_from_object.swaps_handedness(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn basic_intersect(&self, ray: &Ray, t_max: Float) -> Option<QuadricIntersection> {
|
||||||
|
let oi: Point3fi = self
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_interval(&Point3fi::new_from_point(ray.o));
|
||||||
|
let di: Vector3fi = self
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_interval(&Point3fi::new_from_point(Point3f::from(ray.d)))
|
||||||
|
.into();
|
||||||
|
let a: Interval = square(di.x()) + square(di.y()) + square(di.z());
|
||||||
|
let b: Interval = 2. * (di.x() * oi.x() + di.y() * oi.y() + di.z() * oi.z());
|
||||||
|
let c: Interval =
|
||||||
|
square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius));
|
||||||
|
|
||||||
|
let v: Vector3fi = (oi - b / Vector3fi::from((2. * a) * di)).into();
|
||||||
|
let length: Interval = v.norm();
|
||||||
|
let discrim =
|
||||||
|
4. * a * (Interval::new(self.radius) + length) * (Interval::new(self.radius) - length);
|
||||||
|
if discrim.low < 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let root_discrim = discrim.sqrt();
|
||||||
|
let q: Interval;
|
||||||
|
|
||||||
|
if Float::from(b) < 0. {
|
||||||
|
q = -0.5 * (b - root_discrim);
|
||||||
|
} else {
|
||||||
|
q = -0.5 * (b + root_discrim);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut t0 = q / a;
|
||||||
|
let mut t1 = c / q;
|
||||||
|
if t0.low > t1.low {
|
||||||
|
mem::swap(&mut t0, &mut t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if t0.high > t_max || t1.low < 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut t_shape_hit = t0;
|
||||||
|
if t_shape_hit.low <= 0. {
|
||||||
|
t_shape_hit = t1;
|
||||||
|
if t_shape_hit.high > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di);
|
||||||
|
if p_hit.x() == 0. && p_hit.y() == 0. {
|
||||||
|
p_hit[0] = 1e-5 * self.radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut phi = p_hit.y().atan2(p_hit.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.z_min > -self.radius && p_hit.z() < self.z_min
|
||||||
|
|| self.z_max < self.radius && p_hit.z() > self.z_max
|
||||||
|
|| phi > self.phi_max
|
||||||
|
{
|
||||||
|
if t_shape_hit == t1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if t1.high > t_max {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
t_shape_hit = t1;
|
||||||
|
let mut p_hit_vec =
|
||||||
|
Vector3f::from(Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di));
|
||||||
|
p_hit_vec *= self.radius / p_hit.distance(Point3f::zero());
|
||||||
|
p_hit = Point3f::from(p_hit_vec);
|
||||||
|
|
||||||
|
if p_hit.x() == 0. && p_hit.y() == 0. {
|
||||||
|
p_hit[0] = 1e-5 * self.radius;
|
||||||
|
}
|
||||||
|
phi = p_hit.y().atan2(p_hit.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.z_min > -self.radius && p_hit.z() < self.z_min
|
||||||
|
|| self.z_max < self.radius && p_hit.z() > self.z_max
|
||||||
|
|| phi > self.phi_max
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(QuadricIntersection::new(t_shape_hit.into(), p_hit, phi))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interaction_from_intersection(
|
||||||
|
&self,
|
||||||
|
isect: QuadricIntersection,
|
||||||
|
wo: Vector3f,
|
||||||
|
time: Float,
|
||||||
|
) -> SurfaceInteraction {
|
||||||
|
let p_hit = isect.p_obj;
|
||||||
|
let phi = isect.phi;
|
||||||
|
let u = phi / self.phi_max;
|
||||||
|
let cos_theta = p_hit.z() / self.radius;
|
||||||
|
let theta = safe_acos(cos_theta);
|
||||||
|
let v = (theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min);
|
||||||
|
let z_radius = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
||||||
|
let cos_phi = p_hit.x() / z_radius;
|
||||||
|
let sin_phi = p_hit.y() / z_radius;
|
||||||
|
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
|
||||||
|
let sin_theta = safe_sqrt(1. - square(cos_theta));
|
||||||
|
let dpdv = (self.theta_z_max - self.theta_z_min)
|
||||||
|
* Vector3f::new(
|
||||||
|
p_hit.z() * cos_phi,
|
||||||
|
p_hit.z() * sin_phi,
|
||||||
|
-self.radius * sin_theta,
|
||||||
|
);
|
||||||
|
let d2pduu = -self.phi_max * self.phi_max * Vector3f::new(p_hit.x(), p_hit.y(), 0.);
|
||||||
|
let d2pduv = (self.theta_z_max - self.theta_z_min)
|
||||||
|
* p_hit.z()
|
||||||
|
* self.phi_max
|
||||||
|
* Vector3f::new(-sin_phi, cos_phi, 0.);
|
||||||
|
let d2pdvv = -square(self.theta_z_max - self.theta_z_min) * Vector3f::from(p_hit);
|
||||||
|
let e = dpdu.dot(dpdu);
|
||||||
|
let f = dpdu.dot(dpdv);
|
||||||
|
let g = dpdv.dot(dpdv);
|
||||||
|
let n = dpdu.cross(dpdv).normalize();
|
||||||
|
let e_min = n.dot(d2pduu);
|
||||||
|
let f_min = n.dot(d2pduv);
|
||||||
|
let g_min = n.dot(d2pdvv);
|
||||||
|
|
||||||
|
let efg2 = difference_of_products(e, g, f, f);
|
||||||
|
let inv_efg2 = if efg2 == 0. { 0. } else { 1. / efg2 };
|
||||||
|
let dndu = Normal3f::from(
|
||||||
|
(f_min * f - e_min * g) * inv_efg2 * dpdu + (e_min * f - f_min * e) * inv_efg2 * dpdv,
|
||||||
|
);
|
||||||
|
let dndv = Normal3f::from(
|
||||||
|
(g_min * f - f_min * g) * inv_efg2 * dpdu + (f_min * f - g_min * e) * inv_efg2 * dpdv,
|
||||||
|
);
|
||||||
|
let p_error = gamma(5) * Vector3f::from(p_hit).abs();
|
||||||
|
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
|
||||||
|
let wo_object = self.object_from_render.apply_to_vector(wo);
|
||||||
|
let surf_point = SurfaceInteraction::new(
|
||||||
|
Point3fi::new_with_error(p_hit, p_error),
|
||||||
|
Point2f::new(u, v),
|
||||||
|
wo_object,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
dndu,
|
||||||
|
dndv,
|
||||||
|
time,
|
||||||
|
flip_normal,
|
||||||
|
);
|
||||||
|
// self.render_from_object.apply_to_point(surf_point)
|
||||||
|
surf_point
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for SphereShape<'_> {
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
self.render_from_object
|
||||||
|
.apply_to_bounds(Bounds3f::from_points(
|
||||||
|
Point3f::new(-self.radius, -self.radius, self.z_min),
|
||||||
|
Point3f::new(self.radius, self.radius, self.z_max),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
DirectionCone::entire_sphere()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
self.phi_max * self.radius * (self.z_max - self.z_min)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, _interaction: Arc<&dyn Interaction>) -> Float {
|
||||||
|
1. / self.area()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
let t = t_max.unwrap_or(Float::INFINITY);
|
||||||
|
if let Some(isect) = self.basic_intersect(ray, t) {
|
||||||
|
let intr = self.interaction_from_intersection(isect.clone(), -ray.d, ray.time);
|
||||||
|
return Some(ShapeIntersection::new(intr, isect.t_hit));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
if let Some(t) = t_max {
|
||||||
|
self.basic_intersect(ray, t).is_some()
|
||||||
|
} else {
|
||||||
|
self.basic_intersect(ray, Float::INFINITY).is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
|
let p_center = self
|
||||||
|
.object_from_render
|
||||||
|
.apply_to_point(Point3f::new(0., 0., 0.));
|
||||||
|
let p_origin = ctx.offset_ray_origin(p_center.into());
|
||||||
|
// Return solid angle PDF for point inside sphere
|
||||||
|
if p_origin.distance_squared(p_center) <= square(self.radius) {
|
||||||
|
let ray = ctx.spawn_ray(wi);
|
||||||
|
let isect = self.intersect(&ray, None).expect("Return 0");
|
||||||
|
let absdot = isect.intr.n().dot(-Normal3f::from(wi));
|
||||||
|
// Compute PDF in solid angle measure from shape intersection point
|
||||||
|
let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
|
||||||
|
if pdf.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
return pdf;
|
||||||
|
}
|
||||||
|
let sin2_theta_max = self.radius * self.radius / ctx.p().distance_squared(p_center);
|
||||||
|
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
|
||||||
|
let mut one_minus_cos_theta_max = 1. - cos_theta_max;
|
||||||
|
// Compute more accurate cos theta max for small solid angle
|
||||||
|
if sin2_theta_max < 0.00068523 {
|
||||||
|
one_minus_cos_theta_max = sin2_theta_max / 2.;
|
||||||
|
}
|
||||||
|
|
||||||
|
1. / (2. * PI * one_minus_cos_theta_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let p_obj = Point3f::new(0., 0., 0.) + self.radius * sample_uniform_sphere(u);
|
||||||
|
let mut p_obj_vec = Vector3f::from(p_obj);
|
||||||
|
p_obj_vec *= self.radius / p_obj.distance(Point3f::zero());
|
||||||
|
let p_obj_error = gamma(5) * p_obj_vec.abs();
|
||||||
|
let n_obj = Normal3f::from(p_obj_vec);
|
||||||
|
let mut n = self.render_from_object.apply_to_normal(n_obj).normalize();
|
||||||
|
if self.reverse_orientation {
|
||||||
|
n *= -1.;
|
||||||
|
}
|
||||||
|
let theta = safe_acos(p_obj_vec.z() / self.radius);
|
||||||
|
let mut phi = p_obj_vec.y().atan2(p_obj_vec.x());
|
||||||
|
if phi < 0. {
|
||||||
|
phi += 2. * PI;
|
||||||
|
}
|
||||||
|
let uv = Point2f::new(
|
||||||
|
phi / self.phi_max,
|
||||||
|
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
|
||||||
|
);
|
||||||
|
let pi = self
|
||||||
|
.render_from_object
|
||||||
|
.apply_to_interval(&Point3fi::new_with_error(
|
||||||
|
Point3f::from(p_obj_vec),
|
||||||
|
p_obj_error,
|
||||||
|
));
|
||||||
|
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(si),
|
||||||
|
pdf: 1. / self.area(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let p_center = self.render_from_object.apply_to_point(Point3f::zero());
|
||||||
|
let p_origin = ctx.offset_ray_origin_from_point(p_center);
|
||||||
|
if p_origin.distance_squared(p_center) <= square(self.radius) {
|
||||||
|
let mut ss = self.sample(u)?;
|
||||||
|
let intr = Arc::make_mut(&mut ss.intr);
|
||||||
|
intr.get_common_mut().time = ctx.time;
|
||||||
|
let mut wi = ss.intr.p() - ctx.p();
|
||||||
|
if wi.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
wi = wi.normalize();
|
||||||
|
ss.pdf =
|
||||||
|
Vector3f::from(ss.intr.n()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
|
||||||
|
if ss.pdf.is_infinite() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
return Some(ss);
|
||||||
|
}
|
||||||
|
let sin_theta_max = self.radius / ctx.p().distance(p_center);
|
||||||
|
let sin2_theta_max = square(sin_theta_max);
|
||||||
|
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
|
||||||
|
let mut one_minus_cos_theta_max = 1. - cos_theta_max;
|
||||||
|
let mut cos_theta = (cos_theta_max - 1.) * u[0] + 1.;
|
||||||
|
let mut sin2_theta = 1. - square(cos_theta);
|
||||||
|
|
||||||
|
// Compute more accurate cos theta max for small solid angle
|
||||||
|
if sin2_theta_max < 0.00068523 {
|
||||||
|
sin2_theta = sin2_theta_max * u[0];
|
||||||
|
cos_theta = (1. - sin2_theta).sqrt();
|
||||||
|
one_minus_cos_theta_max = sin2_theta_max / 2.;
|
||||||
|
}
|
||||||
|
// Compute angle alpha from center of sphere to sampled point on surface
|
||||||
|
let cos_alpha = sin2_theta / sin_theta_max
|
||||||
|
+ cos_theta * safe_sqrt(1. - sin2_theta / square(sin_theta_max));
|
||||||
|
let sin_alpha = safe_sqrt(1. - square(cos_alpha));
|
||||||
|
let phi = u[1] * 2. * PI;
|
||||||
|
let w = spherical_direction(sin_alpha, cos_alpha, phi);
|
||||||
|
let sampling_frame = Frame::from_z((p_center - ctx.p()).normalize());
|
||||||
|
let mut n: Normal3f = sampling_frame.from_local(-w).into();
|
||||||
|
let p = p_center + self.radius * Vector3f::from(n);
|
||||||
|
if self.reverse_orientation {
|
||||||
|
n *= -1.;
|
||||||
|
}
|
||||||
|
let p_error = gamma(5) * Vector3f::from(p).abs();
|
||||||
|
// Compute (u, v) coordinates for sampled point on sphere
|
||||||
|
let p_obj = self.object_from_render.apply_to_point(Point3f::from(p));
|
||||||
|
let theta = safe_acos(p_obj.z() / self.radius);
|
||||||
|
let mut sphere_phi = p_obj.y().atan2(p_obj.x());
|
||||||
|
if sphere_phi < 0. {
|
||||||
|
sphere_phi += 2. * PI;
|
||||||
|
}
|
||||||
|
let uv = Point2f::new(
|
||||||
|
sphere_phi / self.phi_max,
|
||||||
|
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
|
||||||
|
);
|
||||||
|
let pi = Point3fi::new_with_error(p_obj, p_error);
|
||||||
|
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(si),
|
||||||
|
pdf: 1. / (2. * PI * one_minus_cos_theta_max),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
518
src/shapes/triangle.rs
Normal file
518
src/shapes/triangle.rs
Normal file
|
|
@ -0,0 +1,518 @@
|
||||||
|
use super::{
|
||||||
|
Bounds3f, DirectionCone, Float, Interaction, Normal3f, Point2f, Point3f, Point3fi, Ray,
|
||||||
|
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
|
||||||
|
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::gamma;
|
||||||
|
use crate::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
|
||||||
|
use crate::utils::math::{difference_of_products, square};
|
||||||
|
use crate::utils::mesh::TriangleMesh;
|
||||||
|
use crate::utils::sampling::{
|
||||||
|
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
|
||||||
|
sample_uniform_triangle,
|
||||||
|
};
|
||||||
|
use std::mem;
|
||||||
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
|
pub static TRIANGLE_MESHES: OnceLock<Vec<Arc<TriangleMesh>>> = OnceLock::new();
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct TriangleData {
|
||||||
|
vertices: [Point3f; 3],
|
||||||
|
uvs: [Point2f; 3],
|
||||||
|
normals: Option<[Normal3f; 3]>,
|
||||||
|
area: Float,
|
||||||
|
normal: Normal3f,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
transform_swaps_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriangleShape {
|
||||||
|
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
|
||||||
|
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
|
||||||
|
|
||||||
|
fn mesh(&self) -> &Arc<TriangleMesh> {
|
||||||
|
let meshes = TRIANGLE_MESHES
|
||||||
|
.get()
|
||||||
|
.expect("Mesh has not been initialized");
|
||||||
|
&meshes[self.mesh_ind]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data(&self) -> TriangleData {
|
||||||
|
let mesh = self.mesh();
|
||||||
|
let start = 3 * self.tri_index;
|
||||||
|
let indices = &mesh.vertex_indices[start..start + 3];
|
||||||
|
let vertices = [mesh.p[indices[0]], mesh.p[indices[1]], mesh.p[indices[2]]];
|
||||||
|
let uvs = mesh.uv.as_ref().map_or(
|
||||||
|
[
|
||||||
|
Point2f::zero(),
|
||||||
|
Point2f::new(1.0, 0.0),
|
||||||
|
Point2f::new(1.0, 1.0),
|
||||||
|
],
|
||||||
|
|uv| [uv[indices[0]], uv[indices[1]], uv[indices[2]]],
|
||||||
|
);
|
||||||
|
let normals = mesh
|
||||||
|
.n
|
||||||
|
.as_ref()
|
||||||
|
.map(|n| [n[indices[0]], n[indices[1]], n[indices[2]]]);
|
||||||
|
let dp1 = vertices[1] - vertices[0];
|
||||||
|
let dp2 = vertices[2] - vertices[0];
|
||||||
|
let normal = Normal3f::from(dp1.cross(dp2).normalize());
|
||||||
|
let area = 0.5 * dp1.cross(dp2).norm();
|
||||||
|
|
||||||
|
TriangleData {
|
||||||
|
vertices,
|
||||||
|
uvs,
|
||||||
|
normals,
|
||||||
|
area,
|
||||||
|
normal,
|
||||||
|
reverse_orientation: mesh.reverse_orientation,
|
||||||
|
transform_swaps_handedness: mesh.transform_swaps_handedness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solid_angle(&self, p: Point3f) -> Float {
|
||||||
|
let data = self.get_data();
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
spherical_triangle_area::<Float>(
|
||||||
|
(p0 - p).normalize(),
|
||||||
|
(p1 - p).normalize(),
|
||||||
|
(p2 - p).normalize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_triangle(&self, ray: &Ray, t_max: Float) -> Option<TriangleIntersection> {
|
||||||
|
let data = self.get_data();
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut p0t = p0 - Vector3f::from(ray.o);
|
||||||
|
let mut p1t = p1 - Vector3f::from(ray.o);
|
||||||
|
let mut p2t = p2 - Vector3f::from(ray.o);
|
||||||
|
|
||||||
|
let kz = ray.d.abs().max_component_index();
|
||||||
|
let kx = if kz == 3 { 0 } else { kz + 1 };
|
||||||
|
let ky = if kz == 3 { 0 } else { kx + 1 };
|
||||||
|
let d = ray.d.permute([kx, ky, kz]);
|
||||||
|
p0t = p0t.permute([kx, ky, kz]);
|
||||||
|
p1t = p1t.permute([kx, ky, kz]);
|
||||||
|
p2t = p2t.permute([kx, ky, kz]);
|
||||||
|
// Apply shear transformation to translated vertex positions
|
||||||
|
let sx = -d.x() / d.z();
|
||||||
|
let sy = -d.y() / d.z();
|
||||||
|
let sz = 1. / d.z();
|
||||||
|
p0t[0] += sx * p0t.z();
|
||||||
|
p0t[1] += sy * p0t.z();
|
||||||
|
p1t[0] += sx * p1t.z();
|
||||||
|
p1t[1] += sy * p1t.z();
|
||||||
|
p2t[0] += sx * p2t.z();
|
||||||
|
p2t[0] += sy * p2t.z();
|
||||||
|
|
||||||
|
// Compute edge function coefficients e0, e1, and e2
|
||||||
|
let mut e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x());
|
||||||
|
let mut e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x());
|
||||||
|
let mut e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x());
|
||||||
|
|
||||||
|
// if mem::size_of::<Float>() == mem::size_of::<f32>() && (e0 == 0.0 || e1 == 0.0 || e2 == 0.0)
|
||||||
|
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
|
||||||
|
let [p0t64, p1t64, p2t64] = [p0t.cast::<f64>(), p1t.cast::<f64>(), p2t.cast::<f64>()];
|
||||||
|
|
||||||
|
e0 = (p2t64.y() * p1t64.x() - p2t64.x() * p1t64.y()) as Float;
|
||||||
|
e1 = (p0t64.y() * p2t64.x() - p0t64.x() * p2t64.y()) as Float;
|
||||||
|
e2 = (p1t64.y() * p0t64.x() - p1t64.x() * p0t64.y()) as Float;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e0 < 0. || e1 < 0. || e2 < 0.) && (e0 > 0. || e1 > 0. || e2 > 0.) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let det = e0 + e1 + e2;
|
||||||
|
if det == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute scaled hit distance to triangle and test against ray
|
||||||
|
p0t[2] *= sz;
|
||||||
|
p1t[2] *= sz;
|
||||||
|
p2t[2] *= sz;
|
||||||
|
|
||||||
|
let t_scaled = e0 * p0t.z() + e1 * p1t.z() + e2 * p2t.z();
|
||||||
|
if det < 0. && (t_scaled >= 0. || t_scaled < t_max * det)
|
||||||
|
|| (det > 0. && (t_scaled <= 0. || t_scaled > t_max * det))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute barycentric coordinates and value for triangle intersection
|
||||||
|
let inv_det = 1. / det;
|
||||||
|
let b0 = e0 * inv_det;
|
||||||
|
let b1 = e1 * inv_det;
|
||||||
|
let b2 = e2 * inv_det;
|
||||||
|
let t = t_scaled * inv_det;
|
||||||
|
|
||||||
|
// Ensure that computed triangle is conservatively greater than zero
|
||||||
|
let max_z_t = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
|
||||||
|
.abs()
|
||||||
|
.max_component_value();
|
||||||
|
let delta_z = gamma(3) * max_z_t;
|
||||||
|
|
||||||
|
let max_x_t = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
|
||||||
|
.abs()
|
||||||
|
.max_component_value();
|
||||||
|
let max_y_t = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
|
||||||
|
.abs()
|
||||||
|
.max_component_value();
|
||||||
|
let delta_x = gamma(5) * (max_x_t + max_z_t);
|
||||||
|
let delta_y = gamma(5) * (max_y_t + max_z_t);
|
||||||
|
|
||||||
|
let delta_e = 2. * (gamma(2) * max_x_t * max_y_t + delta_y * max_x_t + delta_x * max_y_t);
|
||||||
|
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
|
||||||
|
let delta_t =
|
||||||
|
3. * (gamma(3) * max_e * max_z_t + delta_e * max_z_t + delta_z * max_e) * inv_det.abs();
|
||||||
|
|
||||||
|
if t <= delta_t {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(TriangleIntersection::new(b0, b1, b2, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interaction_from_intersection(
|
||||||
|
&self,
|
||||||
|
ti: TriangleIntersection,
|
||||||
|
time: Float,
|
||||||
|
wo: Vector3f,
|
||||||
|
) -> SurfaceInteraction {
|
||||||
|
let data = self.get_data();
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
let [uv0, uv1, uv2] = data.uvs;
|
||||||
|
// Compute triangle partial derivatives
|
||||||
|
let (dpdu, dpdv, degenerate_uv, det) = self.compute_partials(data);
|
||||||
|
// Interpolate (u, v) parametric coordinates and hit point
|
||||||
|
let p_hit_vec =
|
||||||
|
ti.b0 * Vector3f::from(p0) + ti.b1 * Vector3f::from(p1) + ti.b2 * Vector3f::from(p2);
|
||||||
|
let p_hit = Point3f::from(p_hit_vec);
|
||||||
|
let uv_hit_vec =
|
||||||
|
ti.b0 * Vector2f::from(uv0) + ti.b1 * Vector2f::from(uv1) + ti.b2 * Vector2f::from(uv2);
|
||||||
|
let uv_hit = Point2f::from(uv_hit_vec);
|
||||||
|
|
||||||
|
// Return SurfaceInteraction for triangle hit>
|
||||||
|
let flip_normal = data.reverse_orientation ^ data.transform_swaps_handedness;
|
||||||
|
let p_abs_sum = (ti.b0 * Vector3f::from(p0)).abs()
|
||||||
|
+ (ti.b1 * Vector3f::from(p1)).abs()
|
||||||
|
+ (ti.b2 * Vector3f::from(p2)).abs();
|
||||||
|
let p_error = gamma(7) * p_abs_sum;
|
||||||
|
let mut isect = SurfaceInteraction::new(
|
||||||
|
Point3fi::new_with_error(p_hit, p_error),
|
||||||
|
uv_hit,
|
||||||
|
wo,
|
||||||
|
dpdu,
|
||||||
|
dpdv,
|
||||||
|
Normal3f::default(),
|
||||||
|
Normal3f::default(),
|
||||||
|
time,
|
||||||
|
flip_normal,
|
||||||
|
);
|
||||||
|
|
||||||
|
isect.face_index = self
|
||||||
|
.mesh()
|
||||||
|
.face_indices
|
||||||
|
.as_ref()
|
||||||
|
.map_or(0, |fi| fi[self.tri_index]);
|
||||||
|
isect.common.n = data.normal;
|
||||||
|
isect.shading.n = isect.n();
|
||||||
|
if flip_normal {
|
||||||
|
isect.common.n = -isect.n();
|
||||||
|
isect.shading.n = -isect.shading.n;
|
||||||
|
}
|
||||||
|
|
||||||
|
if data.normals.is_some() || self.mesh().s.is_some() {
|
||||||
|
self.apply_shading_normals(&mut isect, ti, data, degenerate_uv, det);
|
||||||
|
}
|
||||||
|
|
||||||
|
isect
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compute_partials(&self, data: TriangleData) -> (Vector3f, Vector3f, bool, Float) {
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
let [uv0, uv1, uv2] = data.uvs;
|
||||||
|
let duv02 = uv0 - uv2;
|
||||||
|
let duv12 = uv1 - uv2;
|
||||||
|
let dp02 = p0 - p2;
|
||||||
|
let dp12 = p1 - p2;
|
||||||
|
let det = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
|
||||||
|
let degenerate_uv = det.abs() < 1e-9;
|
||||||
|
let (dpdu, dpdv) = if !degenerate_uv {
|
||||||
|
let inv_det = 1. / det;
|
||||||
|
(
|
||||||
|
(dp02 * duv12[1] - dp12 * duv02[1]) * inv_det,
|
||||||
|
(dp12 * duv02[0] - dp02 * duv12[0]) * inv_det,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
let dp20 = p2 - p0;
|
||||||
|
let dp10 = p1 - p0;
|
||||||
|
let mut ng = dp20.cross(dp10);
|
||||||
|
if ng.norm_squared() == 0. {
|
||||||
|
ng = (dp20.cast::<f64>().cross(dp10.cast::<f64>())).cast();
|
||||||
|
}
|
||||||
|
let n = ng.normalize();
|
||||||
|
n.coordinate_system()
|
||||||
|
};
|
||||||
|
(dpdu, dpdv, degenerate_uv, det)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_shading_normals(
|
||||||
|
&self,
|
||||||
|
isect: &mut SurfaceInteraction,
|
||||||
|
ti: TriangleIntersection,
|
||||||
|
data: TriangleData,
|
||||||
|
degenerate_uv: bool,
|
||||||
|
det: Float,
|
||||||
|
) {
|
||||||
|
let Some([n0, n1, n2]) = data.normals else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let [uv0, uv1, uv2] = data.uvs;
|
||||||
|
let duv02 = uv0 - uv2;
|
||||||
|
let duv12 = uv1 - uv2;
|
||||||
|
|
||||||
|
let ns = ti.b0 * n0 + ti.b1 * n1 + ti.b2 * n2;
|
||||||
|
let ns = if ns.norm_squared() > 0. {
|
||||||
|
ns.normalize()
|
||||||
|
} else {
|
||||||
|
isect.n()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ss = self.mesh().s.as_ref().map_or(isect.dpdu, |s| {
|
||||||
|
let indices = &self.mesh().vertex_indices[3 * self.tri_index..3 * self.tri_index + 3];
|
||||||
|
let interp_s = ti.b0 * s[indices[0]] + ti.b1 * s[indices[1]] + ti.b2 * s[indices[2]];
|
||||||
|
|
||||||
|
if interp_s.norm_squared() > 0. {
|
||||||
|
interp_s
|
||||||
|
} else {
|
||||||
|
isect.dpdu
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut ts = Vector3f::from(ns).cross(ss);
|
||||||
|
if ts.norm_squared() > 0. {
|
||||||
|
ss = ts.cross(Vector3f::from(ns)).into();
|
||||||
|
} else {
|
||||||
|
(ss, ts) = Vector3f::from(ns).coordinate_system();
|
||||||
|
}
|
||||||
|
let (dndu, dndv) = if degenerate_uv {
|
||||||
|
let dn = (n2 - n0).cross(n1 - n0);
|
||||||
|
if dn.norm_squared() == 0. {
|
||||||
|
(Normal3f::zero(), Normal3f::zero())
|
||||||
|
} else {
|
||||||
|
dn.coordinate_system()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let inv_det = 1. / det;
|
||||||
|
let dn02 = n0 - n2;
|
||||||
|
let dn12 = n1 - n2;
|
||||||
|
(
|
||||||
|
(dn02 * duv12[1] - dn12 * duv02[1]) * inv_det,
|
||||||
|
(dn12 * duv02[0] - dn02 * duv12[0]) * inv_det,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
isect.shading.n = ns;
|
||||||
|
isect.shading.dpdu = ss;
|
||||||
|
isect.shading.dpdv = ts.into();
|
||||||
|
isect.dndu = dndu;
|
||||||
|
isect.dndv = dndv;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShapeTrait for TriangleShape {
|
||||||
|
fn bounds(&self) -> Bounds3f {
|
||||||
|
let [p0, p1, p2] = self.get_data().vertices;
|
||||||
|
Bounds3f::from_points(p0, p1).union_point(p2)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_bounds(&self) -> DirectionCone {
|
||||||
|
let data = self.get_data();
|
||||||
|
let mut n = data.normal;
|
||||||
|
if let Some([n0, n1, n2]) = data.normals {
|
||||||
|
n = n.face_forward((n0 + n1 + n2).into());
|
||||||
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
DirectionCone::new_from_vector(Vector3f::from(n))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn area(&self) -> Float {
|
||||||
|
self.get_data().area
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf(&self, _interaction: Arc<&dyn Interaction>) -> Float {
|
||||||
|
1. / self.area()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
||||||
|
let solid_angle = self.solid_angle(ctx.p());
|
||||||
|
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|
||||||
|
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
|
||||||
|
{
|
||||||
|
let ray = ctx.spawn_ray(wi);
|
||||||
|
return self.intersect(&ray, None).map_or(0., |isect| {
|
||||||
|
let absdot = Vector3f::from(isect.intr.n()).dot(-wi).abs();
|
||||||
|
let d2 = ctx.p().distance_squared(isect.intr.p());
|
||||||
|
let pdf = 1. / self.area() * (d2 / absdot);
|
||||||
|
if pdf.is_infinite() { 0. } else { pdf }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pdf = 1. / solid_angle;
|
||||||
|
if ctx.ns != Normal3f::zero() {
|
||||||
|
let [p0, p1, p2] = self.get_data().vertices;
|
||||||
|
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
|
||||||
|
.unwrap_or(Point2f::zero());
|
||||||
|
|
||||||
|
let rp = ctx.p();
|
||||||
|
let wi: [Vector3f; 3] = [
|
||||||
|
(p0 - rp).normalize(),
|
||||||
|
(p1 - rp).normalize(),
|
||||||
|
(p2 - rp).normalize(),
|
||||||
|
];
|
||||||
|
let w: [Float; 4] = [
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
|
||||||
|
];
|
||||||
|
pdf *= bilinear_pdf(u, &w);
|
||||||
|
}
|
||||||
|
pdf
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let data = self.get_data();
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
let solid_angle = self.solid_angle(ctx.p());
|
||||||
|
if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA
|
||||||
|
|| solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA
|
||||||
|
{
|
||||||
|
// Sample shape by area and compute incident direction wi
|
||||||
|
return self
|
||||||
|
.sample(u)
|
||||||
|
.map(|mut ss| {
|
||||||
|
let mut intr_clone = (*ss.intr).clone();
|
||||||
|
intr_clone.common.time = ctx.time;
|
||||||
|
ss.intr = Arc::new(intr_clone);
|
||||||
|
|
||||||
|
let wi = (ss.intr.p() - ctx.p()).normalize();
|
||||||
|
if wi.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let absdot = Vector3f::from(ss.intr.n()).abs_dot(-wi);
|
||||||
|
let d2 = ctx.p().distance_squared(ss.intr.p());
|
||||||
|
ss.pdf /= absdot / d2;
|
||||||
|
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
||||||
|
})
|
||||||
|
.flatten();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample spherical triangle from reference point
|
||||||
|
let mut pdf = 1.;
|
||||||
|
if ctx.ns != Normal3f::zero() {
|
||||||
|
let rp = ctx.p();
|
||||||
|
let wi: [Vector3f; 3] = [
|
||||||
|
(p0 - rp).normalize(),
|
||||||
|
(p1 - rp).normalize(),
|
||||||
|
(p2 - rp).normalize(),
|
||||||
|
];
|
||||||
|
let w: [Float; 4] = [
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
|
||||||
|
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
|
||||||
|
];
|
||||||
|
|
||||||
|
let u = sample_bilinear(u, &w);
|
||||||
|
pdf = bilinear_pdf(u, &w);
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((b, tri_pdf)) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u) else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
if tri_pdf == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
pdf *= tri_pdf;
|
||||||
|
let b2 = 1. - b[0] - b[1];
|
||||||
|
|
||||||
|
let p_abs_sum = b[0] * Vector3f::from(p0)
|
||||||
|
+ b[1] * Vector3f::from(p1)
|
||||||
|
+ (1. - b[0] - b[1]) * Vector3f::from(p2);
|
||||||
|
let p_error = gamma(6) * p_abs_sum;
|
||||||
|
// Return ShapeSample for solid angle sampled point on triangle
|
||||||
|
let p_vec =
|
||||||
|
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
|
||||||
|
let p = Point3f::from(p_vec);
|
||||||
|
let mut n = Normal3f::from((p1 - p0).cross(p2 - p0).normalize());
|
||||||
|
if let Some([n0, n1, n2]) = data.normals {
|
||||||
|
let ns = b[0] * n0 + b[1] * n1 + b2 * n2;
|
||||||
|
n = n.face_forward(ns.into());
|
||||||
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
|
||||||
|
let [uv0, uv1, uv2] = data.uvs;
|
||||||
|
let uv_sample_vec =
|
||||||
|
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
|
||||||
|
let uv_sample = Point2f::from(uv_sample_vec);
|
||||||
|
let pi = Point3fi::new_with_error(p, p_error);
|
||||||
|
let mut si = SurfaceInteraction::new_simple(pi, n, uv_sample);
|
||||||
|
si.common.time = ctx.time;
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(si),
|
||||||
|
pdf,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
||||||
|
let data = self.get_data();
|
||||||
|
let [p0, p1, p2] = data.vertices;
|
||||||
|
let [uv0, uv1, uv2] = data.uvs;
|
||||||
|
let b = sample_uniform_triangle(u);
|
||||||
|
let p_vec =
|
||||||
|
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
|
||||||
|
let b2 = 1. - b[0] - b[1];
|
||||||
|
let p = Point3f::from(p_vec);
|
||||||
|
let mut n = data.normal;
|
||||||
|
if let Some([n0, n1, n2]) = data.normals {
|
||||||
|
let interp_n = b[0] * n0 + b[1] * n1 + b2 * n2;
|
||||||
|
n = n.face_forward(interp_n.into());
|
||||||
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uv_sample_vec =
|
||||||
|
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
|
||||||
|
let uv_sample = Point2f::from(uv_sample_vec);
|
||||||
|
let p_abs_sum = (b[0] * Vector3f::from(p0)).abs()
|
||||||
|
+ (b[1] * Vector3f::from(p1)).abs()
|
||||||
|
+ ((1. - b[0] - b[1]) * Vector3f::from(p2)).abs();
|
||||||
|
let p_error = gamma(6) * p_abs_sum;
|
||||||
|
let pi = Point3fi::new_with_error(p, p_error);
|
||||||
|
Some(ShapeSample {
|
||||||
|
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv_sample)),
|
||||||
|
pdf: 1. / self.area(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
||||||
|
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
||||||
|
.map(|ti| {
|
||||||
|
let intr = self.interaction_from_intersection(ti, ray.time, -ray.d);
|
||||||
|
ShapeIntersection { intr, t_hit: ti.t }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
||||||
|
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
||||||
|
.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,39 @@
|
||||||
use std::ops::{Index, IndexMut, Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg};
|
use std::any::TypeId;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::ops::{
|
||||||
|
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::core::pbrt::{Float, lerp};
|
|
||||||
use super::geometry::Point2f;
|
|
||||||
use super::spectrum::Spectrum;
|
use super::spectrum::Spectrum;
|
||||||
|
use crate::core::pbrt::{Float, lerp};
|
||||||
|
use crate::geometry::Point2f;
|
||||||
|
use crate::utils::math::SquareMatrix;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
pub trait Triplet {
|
||||||
|
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct XYZ {
|
pub struct XYZ {
|
||||||
x: Float,
|
pub x: Float,
|
||||||
y: Float,
|
pub y: Float,
|
||||||
z: Float,
|
pub z: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Triplet for XYZ {
|
||||||
|
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
||||||
|
XYZ::new(c1, c2, c3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a XYZ {
|
||||||
|
type Item = &'a Float;
|
||||||
|
type IntoIter = std::array::IntoIter<&'a Float, 3>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
[&self.x, &self.y, &self.z].into_iter()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl XYZ {
|
impl XYZ {
|
||||||
|
|
@ -38,11 +62,25 @@ impl XYZ {
|
||||||
Point2f::new(self.x / sum, self.y / sum)
|
Point2f::new(self.x / sum, self.y / sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_xyy(xy: Point2f, y: Float) -> Self {
|
pub fn from_xyy(xy: Point2f, y: Option<Float>) -> Self {
|
||||||
if xy.y() == 0.0 {
|
let scale: Float;
|
||||||
return Self { x: 0.0, y: 0.0, z: 0.0}
|
if let Some(y_val) = y {
|
||||||
|
scale = y_val;
|
||||||
|
} else {
|
||||||
|
scale = 1.;
|
||||||
}
|
}
|
||||||
Self::new(xy.x() * y / xy.y(), y, (1.0 - xy.x() - xy.y()) * y / xy.y())
|
if xy.y() == 0.0 {
|
||||||
|
return Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Self::new(
|
||||||
|
xy.x() * scale / xy.y(),
|
||||||
|
scale,
|
||||||
|
(1.0 - xy.x() - xy.y()) * scale / xy.y(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -144,7 +182,6 @@ impl MulAssign for XYZ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scalar multiplication (xyz * float)
|
|
||||||
impl Mul<Float> for XYZ {
|
impl Mul<Float> for XYZ {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, rhs: Float) -> Self {
|
fn mul(self, rhs: Float) -> Self {
|
||||||
|
|
@ -213,7 +250,6 @@ impl fmt::Display for XYZ {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RGB {
|
pub struct RGB {
|
||||||
pub r: Float,
|
pub r: Float,
|
||||||
|
|
@ -221,6 +257,20 @@ pub struct RGB {
|
||||||
pub b: Float,
|
pub b: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Triplet for RGB {
|
||||||
|
fn from_triplet(c1: Float, c2: Float, c3: Float) -> Self {
|
||||||
|
RGB::new(c1, c2, c3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoIterator for &'a RGB {
|
||||||
|
type Item = &'a Float;
|
||||||
|
type IntoIter = std::array::IntoIter<&'a Float, 3>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
[&self.r, &self.g, &self.b].into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RGB {
|
impl RGB {
|
||||||
pub fn new(r: Float, g: Float, b: Float) -> Self {
|
pub fn new(r: Float, g: Float, b: Float) -> Self {
|
||||||
|
|
@ -399,10 +449,10 @@ impl fmt::Display for RGB {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub const RES: usize = 64;
|
pub const RES: usize = 64;
|
||||||
pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
|
pub type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct RGBToSpectrumTable {
|
pub struct RGBToSpectrumTable {
|
||||||
z_nodes: &'static [f32],
|
z_nodes: &'static [f32],
|
||||||
coeffs: &'static CoefficientArray,
|
coeffs: &'static CoefficientArray,
|
||||||
|
|
@ -421,9 +471,12 @@ impl RGBSigmoidPolynomial {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(&self, lambda: Float) -> Float {
|
pub fn evaluate(&self, lambda: Float) -> Float {
|
||||||
let eval = match crate::core::pbrt::evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
|
let eval =
|
||||||
|
match crate::core::pbrt::evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
|
||||||
Some(value) => value,
|
Some(value) => value,
|
||||||
None => { panic!("evaluate_polynomial returned None with non-empty coefficients, this should not happen.") },
|
None => {
|
||||||
|
panic!("evaluate_polynomial returned None with non-empty coefficients")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::s(eval)
|
Self::s(eval)
|
||||||
|
|
@ -433,18 +486,14 @@ impl RGBSigmoidPolynomial {
|
||||||
let lambda = -self.c1 / (2.0 * self.c0);
|
let lambda = -self.c1 / (2.0 * self.c0);
|
||||||
let result = self.evaluate(360.0).max(self.evaluate(830.0));
|
let result = self.evaluate(360.0).max(self.evaluate(830.0));
|
||||||
if lambda >= 360.0 && lambda <= 830.0 {
|
if lambda >= 360.0 && lambda <= 830.0 {
|
||||||
return result.max(self.evaluate(lambda))
|
return result.max(self.evaluate(lambda));
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn s(x: Float) -> Float {
|
fn s(x: Float) -> Float {
|
||||||
if x.is_infinite() {
|
if x.is_infinite() {
|
||||||
if x > 0.0 {
|
if x > 0.0 { return 1.0 } else { return 0.0 }
|
||||||
return 1.0
|
|
||||||
} else {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
|
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
|
||||||
}
|
}
|
||||||
|
|
@ -457,7 +506,11 @@ impl RGBToSpectrumTable {
|
||||||
|
|
||||||
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||||
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
|
if rgb[0] == rgb[1] && rgb[1] == rgb[2] {
|
||||||
return RGBSigmoidPolynomial::new(0.0, 0.0, (rgb[0] - 0.5)/(rgb[0] * (1.0 - rgb[0])).sqrt())
|
return RGBSigmoidPolynomial::new(
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
(rgb[0] - 0.5) / (rgb[0] * (1.0 - rgb[0])).sqrt(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let maxc;
|
let maxc;
|
||||||
if rgb[0] > rgb[1] {
|
if rgb[0] > rgb[1] {
|
||||||
|
|
@ -486,11 +539,387 @@ impl RGBToSpectrumTable {
|
||||||
let dz = (z - self.z_nodes[zi]) / (self.z_nodes[zi + 1] - self.z_nodes[zi]);
|
let dz = (z - self.z_nodes[zi]) / (self.z_nodes[zi + 1] - self.z_nodes[zi]);
|
||||||
let mut c = [0.0; 3];
|
let mut c = [0.0; 3];
|
||||||
for i in 0..3 {
|
for i in 0..3 {
|
||||||
let co = |dx: usize, dy: usize, dz: usize| self.coeffs[maxc][zi as usize + dz][yi as usize + dy][xi as usize + dx][i];
|
let co = |dx: usize, dy: usize, dz: usize| {
|
||||||
c[i] = lerp(dz,
|
self.coeffs[maxc][zi as usize + dz][yi as usize + dy][xi as usize + dx][i]
|
||||||
lerp(dy as Float, lerp(dx as Float, co(0, 0, 0) as Float, co(1, 0, 0)) as Float, lerp(dx as Float, co(0, 1, 0) as Float, co(1, 1, 0) as Float)),
|
};
|
||||||
lerp(dy as Float, lerp(dx as Float, co(0, 0, 1) as Float, co(1, 0, 1)) as Float, lerp(dx as Float, co(0, 1, 1) as Float, co(1, 1, 1) as Float)));
|
c[i] = lerp(
|
||||||
|
dz,
|
||||||
|
lerp(
|
||||||
|
dy as Float,
|
||||||
|
lerp(dx as Float, co(0, 0, 0) as Float, co(1, 0, 0)) as Float,
|
||||||
|
lerp(dx as Float, co(0, 1, 0) as Float, co(1, 1, 0) as Float),
|
||||||
|
),
|
||||||
|
lerp(
|
||||||
|
dy as Float,
|
||||||
|
lerp(dx as Float, co(0, 0, 1) as Float, co(1, 0, 1)) as Float,
|
||||||
|
lerp(dx as Float, co(0, 1, 1) as Float, co(1, 1, 1) as Float),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RGBSigmoidPolynomial {
|
||||||
|
c0: c[0],
|
||||||
|
c1: c[1],
|
||||||
|
c2: c[2],
|
||||||
}
|
}
|
||||||
RGBSigmoidPolynomial { c0: c[0], c1: c[1], c2: c[2] }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LMS_FROM_XYZ: SquareMatrix<Float, 3> = SquareMatrix::new([
|
||||||
|
[0.8951, 0.2664, -0.1614],
|
||||||
|
[-0.7502, 1.7135, 0.0367],
|
||||||
|
[0.0389, -0.0685, 1.0296],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const XYZ_FROM_LMS: SquareMatrix<Float, 3> = SquareMatrix::new([
|
||||||
|
[0.986993, -0.147054, 0.159963],
|
||||||
|
[0.432305, 0.51836, 0.0492912],
|
||||||
|
[-0.00852866, 0.0400428, 0.968487],
|
||||||
|
]);
|
||||||
|
|
||||||
|
pub fn white_balance(src_white: Point2f, target_white: Point2f) -> SquareMatrix<Float, 3> {
|
||||||
|
// Find LMS coefficients for source and target white
|
||||||
|
let src_xyz = XYZ::from_xyy(src_white, None);
|
||||||
|
let dst_xyz = XYZ::from_xyy(target_white, None);
|
||||||
|
let src_lms = LMS_FROM_XYZ * src_xyz;
|
||||||
|
let dst_lms = LMS_FROM_XYZ * dst_xyz;
|
||||||
|
|
||||||
|
// Return white balancing matrix for source and target white
|
||||||
|
let lms_correct = SquareMatrix::<Float, 3>::diag(&[
|
||||||
|
dst_lms[0] / src_lms[0],
|
||||||
|
dst_lms[1] / src_lms[1],
|
||||||
|
dst_lms[2] / src_lms[2],
|
||||||
|
]);
|
||||||
|
XYZ_FROM_LMS * lms_correct * LMS_FROM_XYZ
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ColorEncoding: 'static + Send + Sync + fmt::Debug + fmt::Display {
|
||||||
|
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]);
|
||||||
|
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]);
|
||||||
|
fn to_float_linear(&self, v: Float) -> Float;
|
||||||
|
fn type_id(&self) -> TypeId {
|
||||||
|
TypeId::of::<Self>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LinearEncoding;
|
||||||
|
impl ColorEncoding for LinearEncoding {
|
||||||
|
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) {
|
||||||
|
for (i, &v) in vin.iter().enumerate() {
|
||||||
|
vout[i] = (v.clamp(0.0, 1.0) * 255.0 + 0.5) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
|
||||||
|
for (i, &v) in vin.iter().enumerate() {
|
||||||
|
vout[i] = v as Float / 255.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_float_linear(&self, v: Float) -> Float {
|
||||||
|
v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for LinearEncoding {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "Linear")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SRGBEncoding;
|
||||||
|
impl ColorEncoding for SRGBEncoding {
|
||||||
|
fn from_linear_slice(&self, vin: &[Float], vout: &mut [u8]) {
|
||||||
|
for (i, &v_linear) in vin.iter().enumerate() {
|
||||||
|
let v = v_linear.clamp(0.0, 1.0);
|
||||||
|
let v_encoded = if v <= 0.0031308 {
|
||||||
|
v * 12.92
|
||||||
|
} else {
|
||||||
|
1.055 * v.powf(1.0 / 2.4) - 0.055
|
||||||
|
};
|
||||||
|
vout[i] = (v_encoded * 255.0 + 0.5) as u8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_linear_slice(&self, vin: &[u8], vout: &mut [Float]) {
|
||||||
|
for (i, &v) in vin.iter().enumerate() {
|
||||||
|
vout[i] = SRGB_TO_LINEAR_LUT[v as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_float_linear(&self, v: Float) -> Float {
|
||||||
|
let v = v.clamp(0.0, 1.0);
|
||||||
|
if v <= 0.04045 {
|
||||||
|
v / 12.92
|
||||||
|
} else {
|
||||||
|
((v + 0.055) / 1.055).powf(2.4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SRGBEncoding {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "sRGB")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static LINEAR: Lazy<&'static dyn ColorEncoding> = Lazy::new(|| &LinearEncoding);
|
||||||
|
pub static SRGB: Lazy<&'static dyn ColorEncoding> = Lazy::new(|| &SRGBEncoding);
|
||||||
|
|
||||||
|
const SRGB_TO_LINEAR_LUT: [Float; 256] = [
|
||||||
|
0.0000000000,
|
||||||
|
0.0003035270,
|
||||||
|
0.0006070540,
|
||||||
|
0.0009105810,
|
||||||
|
0.0012141080,
|
||||||
|
0.0015176350,
|
||||||
|
0.0018211619,
|
||||||
|
0.0021246888,
|
||||||
|
0.0024282159,
|
||||||
|
0.0027317430,
|
||||||
|
0.0030352699,
|
||||||
|
0.0033465356,
|
||||||
|
0.0036765069,
|
||||||
|
0.0040247170,
|
||||||
|
0.0043914421,
|
||||||
|
0.0047769533,
|
||||||
|
0.0051815170,
|
||||||
|
0.0056053917,
|
||||||
|
0.0060488326,
|
||||||
|
0.0065120910,
|
||||||
|
0.0069954102,
|
||||||
|
0.0074990317,
|
||||||
|
0.0080231922,
|
||||||
|
0.0085681248,
|
||||||
|
0.0091340570,
|
||||||
|
0.0097212177,
|
||||||
|
0.0103298230,
|
||||||
|
0.0109600937,
|
||||||
|
0.0116122449,
|
||||||
|
0.0122864870,
|
||||||
|
0.0129830306,
|
||||||
|
0.0137020806,
|
||||||
|
0.0144438436,
|
||||||
|
0.0152085144,
|
||||||
|
0.0159962922,
|
||||||
|
0.0168073755,
|
||||||
|
0.0176419523,
|
||||||
|
0.0185002182,
|
||||||
|
0.0193823613,
|
||||||
|
0.0202885624,
|
||||||
|
0.0212190095,
|
||||||
|
0.0221738834,
|
||||||
|
0.0231533647,
|
||||||
|
0.0241576303,
|
||||||
|
0.0251868572,
|
||||||
|
0.0262412224,
|
||||||
|
0.0273208916,
|
||||||
|
0.0284260381,
|
||||||
|
0.0295568332,
|
||||||
|
0.0307134409,
|
||||||
|
0.0318960287,
|
||||||
|
0.0331047624,
|
||||||
|
0.0343398079,
|
||||||
|
0.0356013142,
|
||||||
|
0.0368894450,
|
||||||
|
0.0382043645,
|
||||||
|
0.0395462364,
|
||||||
|
0.0409151986,
|
||||||
|
0.0423114114,
|
||||||
|
0.0437350273,
|
||||||
|
0.0451862030,
|
||||||
|
0.0466650836,
|
||||||
|
0.0481718220,
|
||||||
|
0.0497065634,
|
||||||
|
0.0512694679,
|
||||||
|
0.0528606549,
|
||||||
|
0.0544802807,
|
||||||
|
0.0561284944,
|
||||||
|
0.0578054339,
|
||||||
|
0.0595112406,
|
||||||
|
0.0612460710,
|
||||||
|
0.0630100295,
|
||||||
|
0.0648032799,
|
||||||
|
0.0666259527,
|
||||||
|
0.0684781820,
|
||||||
|
0.0703601092,
|
||||||
|
0.0722718611,
|
||||||
|
0.0742135793,
|
||||||
|
0.0761853904,
|
||||||
|
0.0781874284,
|
||||||
|
0.0802198276,
|
||||||
|
0.0822827145,
|
||||||
|
0.0843762159,
|
||||||
|
0.0865004659,
|
||||||
|
0.0886556059,
|
||||||
|
0.0908417329,
|
||||||
|
0.0930589810,
|
||||||
|
0.0953074843,
|
||||||
|
0.0975873619,
|
||||||
|
0.0998987406,
|
||||||
|
0.1022417471,
|
||||||
|
0.1046164930,
|
||||||
|
0.1070231125,
|
||||||
|
0.1094617173,
|
||||||
|
0.1119324341,
|
||||||
|
0.1144353822,
|
||||||
|
0.1169706732,
|
||||||
|
0.1195384338,
|
||||||
|
0.1221387982,
|
||||||
|
0.1247718409,
|
||||||
|
0.1274376959,
|
||||||
|
0.1301364899,
|
||||||
|
0.1328683347,
|
||||||
|
0.1356333494,
|
||||||
|
0.1384316236,
|
||||||
|
0.1412633061,
|
||||||
|
0.1441284865,
|
||||||
|
0.1470272839,
|
||||||
|
0.1499598026,
|
||||||
|
0.1529261619,
|
||||||
|
0.1559264660,
|
||||||
|
0.1589608639,
|
||||||
|
0.1620294005,
|
||||||
|
0.1651322246,
|
||||||
|
0.1682693958,
|
||||||
|
0.1714410931,
|
||||||
|
0.1746473908,
|
||||||
|
0.1778884083,
|
||||||
|
0.1811642349,
|
||||||
|
0.1844749898,
|
||||||
|
0.1878207624,
|
||||||
|
0.1912016720,
|
||||||
|
0.1946178079,
|
||||||
|
0.1980693042,
|
||||||
|
0.2015562356,
|
||||||
|
0.2050787061,
|
||||||
|
0.2086368501,
|
||||||
|
0.2122307271,
|
||||||
|
0.2158605307,
|
||||||
|
0.2195262313,
|
||||||
|
0.2232279778,
|
||||||
|
0.2269658893,
|
||||||
|
0.2307400703,
|
||||||
|
0.2345506549,
|
||||||
|
0.2383976579,
|
||||||
|
0.2422811985,
|
||||||
|
0.2462013960,
|
||||||
|
0.2501583695,
|
||||||
|
0.2541521788,
|
||||||
|
0.2581829131,
|
||||||
|
0.2622507215,
|
||||||
|
0.2663556635,
|
||||||
|
0.2704978585,
|
||||||
|
0.2746773660,
|
||||||
|
0.2788943350,
|
||||||
|
0.2831487954,
|
||||||
|
0.2874408960,
|
||||||
|
0.2917706966,
|
||||||
|
0.2961383164,
|
||||||
|
0.3005438447,
|
||||||
|
0.3049873710,
|
||||||
|
0.3094689548,
|
||||||
|
0.3139887452,
|
||||||
|
0.3185468316,
|
||||||
|
0.3231432438,
|
||||||
|
0.3277781308,
|
||||||
|
0.3324515820,
|
||||||
|
0.3371636569,
|
||||||
|
0.3419144452,
|
||||||
|
0.3467040956,
|
||||||
|
0.3515326977,
|
||||||
|
0.3564002514,
|
||||||
|
0.3613068759,
|
||||||
|
0.3662526906,
|
||||||
|
0.3712377846,
|
||||||
|
0.3762622178,
|
||||||
|
0.3813261092,
|
||||||
|
0.3864295185,
|
||||||
|
0.3915725648,
|
||||||
|
0.3967553079,
|
||||||
|
0.4019778669,
|
||||||
|
0.4072403014,
|
||||||
|
0.4125427008,
|
||||||
|
0.4178851545,
|
||||||
|
0.4232677519,
|
||||||
|
0.4286905527,
|
||||||
|
0.4341537058,
|
||||||
|
0.4396572411,
|
||||||
|
0.4452012479,
|
||||||
|
0.4507858455,
|
||||||
|
0.4564110637,
|
||||||
|
0.4620770514,
|
||||||
|
0.4677838385,
|
||||||
|
0.4735315442,
|
||||||
|
0.4793202281,
|
||||||
|
0.4851499796,
|
||||||
|
0.4910208881,
|
||||||
|
0.4969330430,
|
||||||
|
0.5028865933,
|
||||||
|
0.5088814497,
|
||||||
|
0.5149177909,
|
||||||
|
0.5209956765,
|
||||||
|
0.5271152258,
|
||||||
|
0.5332764983,
|
||||||
|
0.5394796133,
|
||||||
|
0.5457245708,
|
||||||
|
0.5520114899,
|
||||||
|
0.5583404899,
|
||||||
|
0.5647116303,
|
||||||
|
0.5711249113,
|
||||||
|
0.5775805116,
|
||||||
|
0.5840784907,
|
||||||
|
0.5906189084,
|
||||||
|
0.5972018838,
|
||||||
|
0.6038274169,
|
||||||
|
0.6104956269,
|
||||||
|
0.6172066331,
|
||||||
|
0.6239604354,
|
||||||
|
0.6307572126,
|
||||||
|
0.6375969648,
|
||||||
|
0.6444797516,
|
||||||
|
0.6514056921,
|
||||||
|
0.6583748460,
|
||||||
|
0.6653873324,
|
||||||
|
0.6724432111,
|
||||||
|
0.6795425415,
|
||||||
|
0.6866854429,
|
||||||
|
0.6938719153,
|
||||||
|
0.7011020184,
|
||||||
|
0.7083759308,
|
||||||
|
0.7156936526,
|
||||||
|
0.7230552435,
|
||||||
|
0.7304608822,
|
||||||
|
0.7379105687,
|
||||||
|
0.7454043627,
|
||||||
|
0.7529423237,
|
||||||
|
0.7605246305,
|
||||||
|
0.7681512833,
|
||||||
|
0.7758223414,
|
||||||
|
0.7835379243,
|
||||||
|
0.7912980318,
|
||||||
|
0.7991028428,
|
||||||
|
0.8069523573,
|
||||||
|
0.8148466945,
|
||||||
|
0.8227858543,
|
||||||
|
0.8307699561,
|
||||||
|
0.8387991190,
|
||||||
|
0.8468732834,
|
||||||
|
0.8549926877,
|
||||||
|
0.8631572723,
|
||||||
|
0.8713672161,
|
||||||
|
0.8796223402,
|
||||||
|
0.8879231811,
|
||||||
|
0.8962693810,
|
||||||
|
0.9046613574,
|
||||||
|
0.9130986929,
|
||||||
|
0.9215820432,
|
||||||
|
0.9301108718,
|
||||||
|
0.9386858940,
|
||||||
|
0.9473065734,
|
||||||
|
0.9559735060,
|
||||||
|
0.9646862745,
|
||||||
|
0.9734454751,
|
||||||
|
0.9822505713,
|
||||||
|
0.9911022186,
|
||||||
|
1.0000000000,
|
||||||
|
];
|
||||||
|
|
|
||||||
|
|
@ -1,47 +1,66 @@
|
||||||
|
use super::color::{RGB, RGBSigmoidPolynomial, RGBToSpectrumTable, XYZ};
|
||||||
|
use super::math::SquareMatrix;
|
||||||
|
use super::spectrum::{DenselySampledSpectrum, SampledSpectrum, Spectrum};
|
||||||
use crate::core::pbrt::Float;
|
use crate::core::pbrt::Float;
|
||||||
use super::geometry::Point2f;
|
use crate::geometry::Point2f;
|
||||||
use super::transform::SquareMatrix;
|
|
||||||
use super::color::{RGBSigmoidPolynomial, RGBToSpectrumTable, RGB, XYZ};
|
|
||||||
use super::spectrum::{DenselySampledSpectrum, Spectrum, SampledSpectrum};
|
|
||||||
|
|
||||||
use std::cmp::{Eq, PartialEq};
|
use std::cmp::{Eq, PartialEq};
|
||||||
|
use std::error::Error;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Clone)]
|
pub enum ColorEncoding {
|
||||||
pub struct RGBColorspace {
|
Linear,
|
||||||
r: Point2f,
|
SRGB,
|
||||||
g: Point2f,
|
}
|
||||||
b: Point2f,
|
|
||||||
w: Point2f,
|
|
||||||
illuminant: Spectrum,
|
|
||||||
rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
|
|
||||||
xyz_from_rgb: SquareMatrix<Float, 3>,
|
|
||||||
rgb_from_xyz: SquareMatrix<Float, 3>,
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RGBColorspace {
|
||||||
|
pub r: Point2f,
|
||||||
|
pub g: Point2f,
|
||||||
|
pub b: Point2f,
|
||||||
|
pub w: Point2f,
|
||||||
|
pub illuminant: Spectrum,
|
||||||
|
pub rgb_to_spectrum_table: Arc<RGBToSpectrumTable>,
|
||||||
|
pub xyz_from_rgb: SquareMatrix<Float, 3>,
|
||||||
|
pub rgb_from_xyz: SquareMatrix<Float, 3>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RGBColorspace {
|
impl RGBColorspace {
|
||||||
pub fn new(r: Point2f,
|
pub fn new(
|
||||||
|
r: Point2f,
|
||||||
g: Point2f,
|
g: Point2f,
|
||||||
b: Point2f,
|
b: Point2f,
|
||||||
illuminant: Spectrum,
|
illuminant: Spectrum,
|
||||||
rgb_to_spectrum_table: RGBToSpectrumTable) -> Self {
|
rgb_to_spectrum_table: RGBToSpectrumTable,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
let w_xyz = illuminant.to_xyz();
|
let w_xyz = illuminant.to_xyz();
|
||||||
let w = w_xyz.xy();
|
let w = w_xyz.xy();
|
||||||
let r_xyz = XYZ::from_xyy(r, 1.0);
|
let r_xyz = XYZ::from_xyy(r, Some(1.0));
|
||||||
let g_xyz = XYZ::from_xyy(g, 1.0);
|
let g_xyz = XYZ::from_xyy(g, Some(1.0));
|
||||||
let b_xyz = XYZ::from_xyy(b, 1.0);
|
let b_xyz = XYZ::from_xyy(b, Some(1.0));
|
||||||
let rgb_values = [[r_xyz.x(), g_xyz.x(), b_xyz.x()], [r_xyz.y(), g_xyz.y(), b_xyz.y()], [r_xyz.z(), g_xyz.z(), g_xyz.z()]];
|
let rgb_values = [
|
||||||
let rgb: SquareMatrix<Float, 3> = SquareMatrix { m: rgb_values };
|
[r_xyz.x(), g_xyz.x(), b_xyz.x()],
|
||||||
let c = match rgb.inverse() {
|
[r_xyz.y(), g_xyz.y(), b_xyz.y()],
|
||||||
Some(inv_matrix) => { inv_matrix * w_xyz },
|
[r_xyz.z(), g_xyz.z(), g_xyz.z()],
|
||||||
None => { panic!("Cannot create RGBColorspace: The RGB primaries form a singular matrix."); }
|
];
|
||||||
};
|
let rgb = SquareMatrix::new(rgb_values);
|
||||||
|
let c = rgb.inverse()? * w_xyz;
|
||||||
let xyz_from_rgb_m = [[c[0], 0.0, 0.0], [0.0, c[1], 0.0], [0.0, 0.0, c[2]]];
|
let xyz_from_rgb_m = [[c[0], 0.0, 0.0], [0.0, c[1], 0.0], [0.0, 0.0, c[2]]];
|
||||||
let xyz_from_rgb = rgb * SquareMatrix { m: xyz_from_rgb_m };
|
let xyz_from_rgb = rgb * SquareMatrix::new(xyz_from_rgb_m);
|
||||||
let rgb_from_xyz = xyz_from_rgb.inverse().expect("Failed to invert the XYZfromRGB matrix. Is it singular?");
|
let rgb_from_xyz = xyz_from_rgb
|
||||||
|
.inverse()
|
||||||
|
.expect("Failed to invert the XYZfromRGB matrix. Is it singular?");
|
||||||
|
|
||||||
Self { r, g, b, w, illuminant, rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table), xyz_from_rgb, rgb_from_xyz }
|
Ok(Self {
|
||||||
|
r,
|
||||||
|
g,
|
||||||
|
b,
|
||||||
|
w,
|
||||||
|
illuminant,
|
||||||
|
rgb_to_spectrum_table: Arc::new(rgb_to_spectrum_table),
|
||||||
|
xyz_from_rgb,
|
||||||
|
rgb_from_xyz,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
pub fn to_xyz(&self, rgb: RGB) -> XYZ {
|
||||||
|
|
@ -54,12 +73,11 @@ impl RGBColorspace {
|
||||||
|
|
||||||
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
|
||||||
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
self.rgb_to_spectrum_table.to_polynomial(rgb)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn convert_colorspace(&self, other: &RGBColorspace) -> SquareMatrix<Float, 3> {
|
pub fn convert_colorspace(&self, other: &RGBColorspace) -> SquareMatrix<Float, 3> {
|
||||||
if self == other {
|
if self == other {
|
||||||
return SquareMatrix::default()
|
return SquareMatrix::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.rgb_from_xyz * other.xyz_from_rgb
|
self.rgb_from_xyz * other.xyz_from_rgb
|
||||||
|
|
@ -68,8 +86,10 @@ impl RGBColorspace {
|
||||||
|
|
||||||
impl PartialEq for RGBColorspace {
|
impl PartialEq for RGBColorspace {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.r == other.r && self.g == other.g && self.b == other.b && self.w == other.w &&
|
self.r == other.r
|
||||||
Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table)
|
&& self.g == other.g
|
||||||
|
&& self.b == other.b
|
||||||
|
&& self.w == other.w
|
||||||
|
&& Arc::ptr_eq(&self.rgb_to_spectrum_table, &other.rgb_to_spectrum_table)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@ use std::hash::{BuildHasher, Hash, Hasher};
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use crate::utils::geometry::{Bounds2i, Bounds3i, Point2i, Point3i, Point3f, Vector3i, Vector3f};
|
use crate::geometry::{Bounds2i, Bounds3i, Point2i, Point3f, Point3i, Vector3f, Vector3i};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Array2D<T> {
|
pub struct Array2D<T> {
|
||||||
values: Vec<T>,
|
values: Vec<T>,
|
||||||
extent: Bounds2i,
|
extent: Bounds2i,
|
||||||
|
|
@ -21,7 +22,7 @@ impl<T> Array2D<T> {
|
||||||
Self { values, extent }
|
Self { values, extent }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_default(extent: Bounds2i, default_val: T) -> Self
|
pub fn new_from_bounds(extent: Bounds2i, default_val: T) -> Self
|
||||||
where
|
where
|
||||||
T: Clone,
|
T: Clone,
|
||||||
{
|
{
|
||||||
|
|
@ -30,12 +31,12 @@ impl<T> Array2D<T> {
|
||||||
Self { values, extent }
|
Self { values, extent }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn x_size(&self) -> i32 {
|
pub fn x_size(&self) -> usize {
|
||||||
self.extent.p_max.x() - self.extent.p_min.x()
|
(self.extent.p_max.x() - self.extent.p_min.x()) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn y_size(&self) -> i32 {
|
pub fn y_size(&self) -> usize {
|
||||||
self.extent.p_max.y() - self.extent.p_min.y()
|
(self.extent.p_max.y() - self.extent.p_min.y()) as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> usize {
|
pub fn size(&self) -> usize {
|
||||||
|
|
@ -55,7 +56,7 @@ impl<T> Array2D<T> {
|
||||||
debug_assert!(p.y() >= self.extent.p_min.y() && p.y() < self.extent.p_max.y());
|
debug_assert!(p.y() >= self.extent.p_min.y() && p.y() < self.extent.p_max.y());
|
||||||
let width = self.x_size();
|
let width = self.x_size();
|
||||||
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
|
let pp = Point2i::new(p.x() - self.extent.p_min.x(), p.y() - self.extent.p_min.y());
|
||||||
(pp.y() * width + pp.x()) as usize
|
(pp.y() * width as i32 + pp.x()) as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use thiserror::Error;
|
|
||||||
use image::error;
|
use image::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::utils::image::PixelFormat;
|
use crate::utils::image::PixelFormat;
|
||||||
|
|
||||||
|
|
@ -72,13 +72,10 @@ pub enum PixelFormatError {
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Invalid conversion from pixel format {from:?} to {to:?}.")]
|
#[error("Invalid conversion from pixel format {from:?} to {to:?}.")]
|
||||||
InvalidConversion {
|
InvalidConversion { from: PixelFormat, to: PixelFormat },
|
||||||
from: PixelFormat,
|
|
||||||
to: PixelFormat,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Internal invariant violated: image format is {expected:?} but pixel data is of a different type.")]
|
#[error(
|
||||||
InternalFormatMismatch {
|
"Internal invariant violated: image format is {expected:?} but pixel data is of a different type."
|
||||||
expected: PixelFormat,
|
)]
|
||||||
},
|
InternalFormatMismatch { expected: PixelFormat },
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,781 +0,0 @@
|
||||||
use num_traits::{Num, Bounded, Float as NumFloat, Zero, Signed};
|
|
||||||
use std::ops::{Sub, SubAssign, Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Index, IndexMut};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::f32::consts::PI;
|
|
||||||
|
|
||||||
use crate::core::medium::Medium;
|
|
||||||
use crate::core::pbrt::{Float, next_float_up, next_float_down, lerp, clamp_t};
|
|
||||||
use super::interval::Interval;
|
|
||||||
|
|
||||||
pub trait Tuple<T, const N: usize>:
|
|
||||||
Sized + Copy + Index<usize, Output = T> + IndexMut<usize>
|
|
||||||
{
|
|
||||||
fn data(&self) -> &[T; N];
|
|
||||||
fn data_mut(&mut self) -> &mut [T; N];
|
|
||||||
fn from_array(arr: [T; N]) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn min<T: PartialOrd>(a: T, b: T) -> T { if a < b { a } else { b }}
|
|
||||||
fn max<T: PartialOrd>(a: T, b: T) -> T { if a > b { a } else { b }}
|
|
||||||
|
|
||||||
// N-dimensional displacement
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Vector<T, const N: usize>(pub [T; N]);
|
|
||||||
// N-dimensional location
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Point<T, const N: usize>(pub [T; N]);
|
|
||||||
// N-dimensional surface normal
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub struct Normal<T, const N: usize>(pub [T; N]);
|
|
||||||
|
|
||||||
macro_rules! impl_tuple_core {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T: Copy, const N: usize> Tuple<T, N> for $Struct<T, N> {
|
|
||||||
#[inline] fn data(&self) -> &[T; N] { &self.0 }
|
|
||||||
#[inline] fn data_mut(&mut self) -> &mut [T; N] { &mut self.0 }
|
|
||||||
#[inline] fn from_array(arr: [T; N]) -> Self { Self(arr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Default + Copy, const N: usize> Default for $Struct<T, N> {
|
|
||||||
fn default() -> Self { Self([T::default(); N]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> $Struct<T, N> where T: Zero + Copy {
|
|
||||||
#[inline] pub fn zero() -> Self { Self([T::zero(); N]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> $Struct<T, N> where T: Copy {
|
|
||||||
#[inline]
|
|
||||||
pub fn fill(value: T) -> Self {
|
|
||||||
Self([value; N])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Index<usize> for $Struct<T, N> {
|
|
||||||
type Output = T;
|
|
||||||
#[inline] fn index(&self, index: usize) -> &Self::Output { &self.0[index] }
|
|
||||||
}
|
|
||||||
impl<T, const N: usize> IndexMut<usize> for $Struct<T, N> {
|
|
||||||
#[inline] fn index_mut(&mut self, index: usize) -> &mut Self::Output { &mut self.0[index] }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Neg for $Struct<T, N> where T: Neg<Output = T> + Copy {
|
|
||||||
type Output = Self;
|
|
||||||
fn neg(self) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N { result[i] = -result[i]; }
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_scalar_ops {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T, const N: usize> Mul<T> for $Struct<T, N> where T: Mul<Output = T> + Copy {
|
|
||||||
type Output = Self;
|
|
||||||
fn mul(self, rhs: T) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N { result[i] = result[i] * rhs; }
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Mul<$Struct<Float, N>> for Float {
|
|
||||||
type Output = $Struct<Float, N>;
|
|
||||||
fn mul(self, rhs: $Struct<Float, N>) -> Self::Output {
|
|
||||||
rhs * self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> MulAssign<T> for $Struct<T, N> where T: MulAssign + Copy {
|
|
||||||
fn mul_assign(&mut self, rhs: T) {
|
|
||||||
for i in 0..N { self.0[i] *= rhs; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Div<T> for $Struct<T, N> where T: Div<Output = T> + Copy {
|
|
||||||
type Output = Self;
|
|
||||||
fn div(self, rhs: T) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N { result[i] = result[i] / rhs; }
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<T, const N: usize> DivAssign<T> for $Struct<T, N> where T: DivAssign + Copy {
|
|
||||||
fn div_assign(&mut self, rhs: T) {
|
|
||||||
for i in 0..N { self.0[i] /= rhs; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_tuple_core!(Vector);
|
|
||||||
impl_tuple_core!(Point);
|
|
||||||
impl_tuple_core!(Normal);
|
|
||||||
|
|
||||||
impl_scalar_ops!(Vector);
|
|
||||||
impl_scalar_ops!(Normal);
|
|
||||||
|
|
||||||
|
|
||||||
macro_rules! impl_op {
|
|
||||||
($Op:ident, $op:ident, $Lhs:ident, $Rhs:ident, $Output:ident) => {
|
|
||||||
impl<T, const N: usize> $Op<$Rhs<T, N>> for $Lhs<T, N>
|
|
||||||
where
|
|
||||||
T: $Op<Output = T> + Copy,
|
|
||||||
{
|
|
||||||
type Output = $Output<T, N>;
|
|
||||||
fn $op(self, rhs: $Rhs<T, N>) -> Self::Output {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = self.0[i].$op(rhs.0[i]);
|
|
||||||
}
|
|
||||||
$Output(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_op_assign {
|
|
||||||
($OpAssign:ident, $op_assign:ident, $Lhs:ident, $Rhs:ident) => {
|
|
||||||
impl<T, const N: usize> $OpAssign<$Rhs<T, N>> for $Lhs<T, N>
|
|
||||||
where
|
|
||||||
T: $OpAssign + Copy,
|
|
||||||
{
|
|
||||||
fn $op_assign(&mut self, rhs: $Rhs<T, N>) {
|
|
||||||
for i in 0..N {
|
|
||||||
self.0[i].$op_assign(rhs.0[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addition
|
|
||||||
impl_op!(Add, add, Vector, Vector, Vector);
|
|
||||||
impl_op!(Add, add, Point, Vector, Point);
|
|
||||||
impl_op!(Add, add, Vector, Point, Point);
|
|
||||||
impl_op!(Add, add, Normal, Normal, Normal);
|
|
||||||
|
|
||||||
// Subtraction
|
|
||||||
impl_op!(Sub, sub, Vector, Vector, Vector);
|
|
||||||
impl_op!(Sub, sub, Point, Vector, Point);
|
|
||||||
impl_op!(Sub, sub, Point, Point, Vector);
|
|
||||||
impl_op!(Sub, sub, Normal, Normal, Normal);
|
|
||||||
|
|
||||||
// AddAssign
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Vector, Vector);
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Point, Vector);
|
|
||||||
impl_op_assign!(AddAssign, add_assign, Normal, Normal);
|
|
||||||
|
|
||||||
// SubAssign
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Vector, Vector);
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Point, Vector);
|
|
||||||
impl_op_assign!(SubAssign, sub_assign, Normal, Normal);
|
|
||||||
|
|
||||||
pub trait Dot<Rhs = Self> {
|
|
||||||
type Output;
|
|
||||||
fn dot(self, rhs: Rhs) -> Self::Output;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_dot_for {
|
|
||||||
($Lhs:ident, $Rhs:ident) => {
|
|
||||||
impl<T: Num + Copy, const N: usize> Dot<$Rhs<T, N>> for $Lhs<T, N> {
|
|
||||||
type Output = T;
|
|
||||||
fn dot(self, rhs: $Rhs<T, N>) -> T {
|
|
||||||
let mut sum = T::zero();
|
|
||||||
for i in 0..N {
|
|
||||||
sum = sum + self[i] * rhs[i];
|
|
||||||
}
|
|
||||||
sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_dot_for!(Normal, Vector);
|
|
||||||
impl_dot_for!(Vector, Normal);
|
|
||||||
impl_dot_for!(Vector, Vector);
|
|
||||||
impl_dot_for!(Normal, Normal);
|
|
||||||
impl_dot_for!(Normal, Point);
|
|
||||||
impl_dot_for!(Vector, Point);
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
|
|
||||||
fn from(v: Vector<T, N>) -> Self { Self(v.0) }
|
|
||||||
}
|
|
||||||
impl<T: Copy, const N: usize> From<Normal<T, N>> for Vector<T, N> {
|
|
||||||
fn from(n: Normal<T, N>) -> Self { Self(n.0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Vector<T, N>> for Point<T, N> {
|
|
||||||
fn from(v: Vector<T, N>) -> Self { Self(v.0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Copy, const N: usize> From<Point<T, N>> for Vector<T, N> {
|
|
||||||
fn from(n: Point<T, N>) -> Self { Self(n.0) }
|
|
||||||
}
|
|
||||||
macro_rules! impl_accessors {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T: Copy> $Struct<T, 2> {
|
|
||||||
pub fn x(&self) -> T { self.0[0] }
|
|
||||||
pub fn y(&self) -> T { self.0[1] }
|
|
||||||
}
|
|
||||||
impl<T: Copy> $Struct<T, 3> {
|
|
||||||
pub fn x(&self) -> T { self.0[0] }
|
|
||||||
pub fn y(&self) -> T { self.0[1] }
|
|
||||||
pub fn z(&self) -> T { self.0[2] }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_accessors!(Vector);
|
|
||||||
impl_accessors!(Point);
|
|
||||||
impl_accessors!(Normal);
|
|
||||||
|
|
||||||
|
|
||||||
// Vector stuff
|
|
||||||
pub trait Normed {
|
|
||||||
type Scalar;
|
|
||||||
fn norm_squared(&self) -> Self::Scalar;
|
|
||||||
fn norm(&self) -> Self::Scalar where Self::Scalar: NumFloat { self.norm_squared().sqrt() }
|
|
||||||
fn normalize(self) -> Self where Self: Sized, Self: Div<Self::Scalar, Output=Self>, Self::Scalar: NumFloat;
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_normed_for {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T: Num + Copy, const N: usize> Normed for $Struct<T, N> {
|
|
||||||
type Scalar = T;
|
|
||||||
fn norm_squared(&self) -> T { self.dot(*self) }
|
|
||||||
fn normalize(self) -> Self where Self: Div<T, Output=Self>, T: NumFloat { self / self.norm() }
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_normed_for!(Vector);
|
|
||||||
impl_normed_for!(Normal);
|
|
||||||
|
|
||||||
macro_rules! impl_abs {
|
|
||||||
($Struct:ident) => {
|
|
||||||
impl<T, const N: usize> $Struct<T, N>
|
|
||||||
where
|
|
||||||
T: Signed + Copy,
|
|
||||||
{
|
|
||||||
pub fn abs(self) -> Self {
|
|
||||||
let mut result = self.0;
|
|
||||||
for i in 0..N {
|
|
||||||
result[i] = result[i].abs();
|
|
||||||
}
|
|
||||||
Self(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_abs!(Vector);
|
|
||||||
impl_abs!(Normal);
|
|
||||||
|
|
||||||
impl<T, const N: usize> Point<T, N>
|
|
||||||
where
|
|
||||||
T: NumFloat + Copy,
|
|
||||||
Point<T, N>: Sub<Output = Vector<T, N>>,
|
|
||||||
Vector<T, N>: Normed<Scalar = T>,
|
|
||||||
{
|
|
||||||
pub fn distance(self, other: Self) -> T {
|
|
||||||
(self - other).norm()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn distance_squared(self, other: Self) -> T {
|
|
||||||
(self - other).norm_squared()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility aliases and functions
|
|
||||||
pub type Point2<T> = Point<T, 2>;
|
|
||||||
pub type Point2f = Point2<Float>;
|
|
||||||
pub type Point2i = Point2<i32>;
|
|
||||||
pub type Point3<T> = Point<T, 3>;
|
|
||||||
pub type Point3f = Point3<Float>;
|
|
||||||
pub type Point3i = Point3<i32>;
|
|
||||||
pub type Vector2<T> = Vector<T, 2>;
|
|
||||||
pub type Vector2f = Vector2<Float>;
|
|
||||||
pub type Vector2i = Vector2<i32>;
|
|
||||||
pub type Vector3<T> = Vector<T, 3>;
|
|
||||||
pub type Vector3f = Vector3<Float>;
|
|
||||||
pub type Vector3i = Vector3<i32>;
|
|
||||||
pub type Normal3<T> = Normal<T, 3>;
|
|
||||||
pub type Normal3f = Normal3<Float>;
|
|
||||||
pub type Normal3i = Normal3<i32>;
|
|
||||||
pub type Vector3fi = Vector<Interval, 3>;
|
|
||||||
pub type Point3fi = Point<Interval, 3>;
|
|
||||||
|
|
||||||
|
|
||||||
impl<T: Copy> Vector2<T> { pub fn new(x: T, y: T) -> Self { Self([x, y]) } }
|
|
||||||
impl<T: Copy> Point2<T> { pub fn new(x: T, y: T) -> Self { Self([x, y]) } }
|
|
||||||
impl<T: Copy> Vector3<T> { pub fn new(x: T, y: T, z: T) -> Self { Self([x, y, z]) } }
|
|
||||||
impl<T: Copy> Point3<T> { pub fn new(x: T, y: T, z: T) -> Self { Self([x, y, z]) } }
|
|
||||||
impl<T: Copy> Normal3<T> { pub fn new(x: T, y: T, z: T) -> Self { Self([x, y, z]) } }
|
|
||||||
|
|
||||||
|
|
||||||
// Vector operations
|
|
||||||
impl<T> Vector3<T>
|
|
||||||
where T: Num + Copy + Neg<Output = T>
|
|
||||||
{
|
|
||||||
pub fn cross(self, rhs: Self) -> Self {
|
|
||||||
Self([
|
|
||||||
self[1] * rhs[2] - self[2] * rhs[1],
|
|
||||||
self[2] * rhs[0] - self[0] * rhs[2],
|
|
||||||
self[0] * rhs[1] - self[1] * rhs[0],
|
|
||||||
])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Vector3<T>
|
|
||||||
where T: Num + NumFloat + Copy + Neg<Output = T>
|
|
||||||
{
|
|
||||||
pub fn coordinate_system(&self) -> (Self, Self) where T: NumFloat {
|
|
||||||
let v2 = if self[0].abs() > self[1].abs() {
|
|
||||||
Self::new(-self[2], T::zero(), self[0]) / (self[0] * self[0] + self[2] * self[2]).sqrt()
|
|
||||||
} else {
|
|
||||||
Self::new(T::zero(), self[2], -self[1]) / (self[1] * self[1] + self[2] * self[2]).sqrt()
|
|
||||||
};
|
|
||||||
(v2, self.cross(v2))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Point<Interval, N> {
|
|
||||||
pub fn new_from_point(p: Point<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new(p[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_error(p: Point<Float, N>, e: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new_from_value_and_error(p[i], e[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].width() / 2.0;
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn midpoint(&self) -> Point<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].midpoint();
|
|
||||||
}
|
|
||||||
Point(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exact(&self) -> bool {
|
|
||||||
self.0.iter().all(|interval| interval.width() == 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> Vector<Interval, N> {
|
|
||||||
pub fn new_from_vector(v: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new(v[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_with_error(v: Vector<Float, N>, e: Vector<Float, N>) -> Self {
|
|
||||||
let mut arr = [Interval::default(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = Interval::new_from_value_and_error(v[i], e[i]);
|
|
||||||
}
|
|
||||||
Self(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn error(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].width() / 2.0;
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn midpoint(&self) -> Vector<Float, N> {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = self[i].midpoint();
|
|
||||||
}
|
|
||||||
Vector(arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_exact(&self) -> bool {
|
|
||||||
self.0.iter().all(|interval| interval.width() == 0.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const N: usize> From<Point<Interval, N>> for Point<Float, N> {
|
|
||||||
fn from(pi: Point<Interval, N>) -> Self {
|
|
||||||
let mut arr = [0.0; N];
|
|
||||||
for i in 0..N {
|
|
||||||
arr[i] = pi[i].midpoint();
|
|
||||||
}
|
|
||||||
Point(arr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Normal3<T>
|
|
||||||
where T: Num + PartialOrd + Copy + Neg<Output = T>
|
|
||||||
{
|
|
||||||
pub fn face_forward(self, v: Vector3<T>) -> Self {
|
|
||||||
if self.dot(v) < T::zero() { -self } else { self }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn abs_cos_theta(w: Vector3f) -> Float {
|
|
||||||
w.z().abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SPHERICAL GEOMETRY
|
|
||||||
pub fn same_hemisphere(w: Vector3f, wp: Vector3f) -> bool {
|
|
||||||
w.z() * wp.z() > 0.
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_direction(sin_theta: Float, cos_theta: Float, phi: Float) -> Vector3f {
|
|
||||||
Vector3f::new(sin_theta * phi.cos(), sin_theta * phi.sin(), cos_theta)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_triangle_area<T: NumFloat>(a: Vector3f, b: Vector3f, c: Vector3f) -> Float {
|
|
||||||
(2.0 * (a.dot(b.cross(c))).atan2(1.0 + a.dot(b) + a.dot(c) + b.dot(c))).abs()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_theta<T: NumFloat>(v: Vector3f) -> Float {
|
|
||||||
clamp_t(v.z(), -1.0, 1.0).acos()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spherical_phi<T: NumFloat>(v: Vector3f) -> Float {
|
|
||||||
let p = v.y().atan2(v.x());
|
|
||||||
if p < 0.0 {
|
|
||||||
p + 2.0 * PI
|
|
||||||
} else {
|
|
||||||
p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AABB BOUNDING BOXES
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct Bounds<T, const N: usize> {
|
|
||||||
pub p_min: Point<T, N>,
|
|
||||||
pub p_max: Point<T, N>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Bounds<T, N>
|
|
||||||
where
|
|
||||||
T: Num + PartialOrd + Copy
|
|
||||||
{
|
|
||||||
pub fn from_point(p: Point<T, N>) -> Self {
|
|
||||||
Self { p_min: p, p_max: p }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_points(p1: Point<T, N>, p2: Point<T, N>) -> Self {
|
|
||||||
let mut p_min_arr = [T::zero(); N];
|
|
||||||
let mut p_max_arr = [T::zero(); N];
|
|
||||||
|
|
||||||
for i in 0..N {
|
|
||||||
if p1[i] < p2[i] {
|
|
||||||
p_min_arr[i] = p1[i];
|
|
||||||
p_max_arr[i] = p2[i];
|
|
||||||
} else {
|
|
||||||
p_min_arr[i] = p2[i];
|
|
||||||
p_max_arr[i] = p1[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Self { p_min: Point(p_min_arr), p_max: Point(p_max_arr) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union_point(self, p: Point<T, N>) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
for i in 0..N {
|
|
||||||
p_min[i] = min(p_min[i], p[i]);
|
|
||||||
p_max[i] = max(p_max[i], p[i]);
|
|
||||||
}
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn union(self, b2: Self) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
for i in 0..N {
|
|
||||||
p_min[i] = min(p_min[i], b2.p_min[i]);
|
|
||||||
p_max[i] = max(p_max[i], b2.p_max[i]);
|
|
||||||
}
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn diagonal(&self) -> Vector<T, N> {
|
|
||||||
self.p_max - self.p_min
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn volume(&self) -> T {
|
|
||||||
let d = self.diagonal();
|
|
||||||
d.0.iter().fold(T::one(), |acc, &val| acc * val)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn expand(&self, delta: T) -> Self {
|
|
||||||
let mut p_min = self.p_min;
|
|
||||||
let mut p_max = self.p_max;
|
|
||||||
p_min = p_min - Vector::fill(delta);
|
|
||||||
p_max = p_max + Vector::fill(delta);
|
|
||||||
Self { p_min, p_max }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp(&self, t: Point<T, N>) -> Point<T, N>
|
|
||||||
{
|
|
||||||
let mut results_arr = [T::zero(); N];
|
|
||||||
for i in 0..N {
|
|
||||||
results_arr[i] = lerp(t[i], self.p_min[i], self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
Point(results_arr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_dimension(&self) -> usize
|
|
||||||
where
|
|
||||||
Point<T, N>: Sub<Output = Vector<T, N>>,
|
|
||||||
{
|
|
||||||
let d = self.diagonal();
|
|
||||||
let mut max_dim = 0;
|
|
||||||
let mut max_span = d[0];
|
|
||||||
|
|
||||||
for i in 1..N {
|
|
||||||
if d[i] > max_span {
|
|
||||||
max_span = d[i];
|
|
||||||
max_dim = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
max_dim
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset(&self, p: &Point<T, N>) -> Vector<T, N>
|
|
||||||
where
|
|
||||||
Point<T, N>: Sub<Output = Vector<T, N>>,
|
|
||||||
Vector<T, N>: DivAssign<T>
|
|
||||||
{
|
|
||||||
let mut o = *p - self.p_min;
|
|
||||||
let d = self.diagonal();
|
|
||||||
for i in 0..N {
|
|
||||||
if d[i] > T::zero() {
|
|
||||||
o[i] = o[i] / d[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
o
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, p: Point<T, N>) -> bool {
|
|
||||||
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] <= self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_exclusive(&self, p: Point<T, N>) -> bool {
|
|
||||||
(0..N).all(|i| p[i] >= self.p_min[i] && p[i] < self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
(0..N).any(|i| self.p_min[i] >= self.p_max[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_degenerate(&self) -> bool {
|
|
||||||
(0..N).any(|i| self.p_min[i] > self.p_max[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, const N: usize> Default for Bounds<T, N>
|
|
||||||
where
|
|
||||||
T: Bounded + Copy,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
p_min: Point([T::max_value(); N]),
|
|
||||||
p_max: Point([T::min_value(); N]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Bounds2<T> = Bounds<T, 2>;
|
|
||||||
pub type Bounds2f = Bounds2<Float>;
|
|
||||||
pub type Bounds2i = Bounds2<i32>;
|
|
||||||
pub type Bounds2fi = Bounds2<Interval>;
|
|
||||||
pub type Bounds3<T> = Bounds<T, 3>;
|
|
||||||
pub type Bounds3i = Bounds3<i32>;
|
|
||||||
pub type Bounds3f = Bounds3<Float>;
|
|
||||||
pub type Bounds3fi = Bounds3<Interval>;
|
|
||||||
|
|
||||||
impl<T> Bounds3<T>
|
|
||||||
where
|
|
||||||
T: Num + PartialOrd + Copy + Default
|
|
||||||
{
|
|
||||||
pub fn surface_area(&self) -> T {
|
|
||||||
let d = self.diagonal();
|
|
||||||
let two = T::one() + T::one();
|
|
||||||
two * (d.x() * d.y() + d.x() * d.z() + d.y() * d.z())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bounding_sphere(&self) -> (Point3<T>, T)
|
|
||||||
where
|
|
||||||
<<Point3<T> as Sub>::Output as Normed>::Scalar: NumFloat,
|
|
||||||
{
|
|
||||||
let two = T::one() + T::one();
|
|
||||||
let center = self.p_min + self.diagonal() / two;
|
|
||||||
let radius = if self.contains(center) { center.distance(self.p_max) } else { T::zero() };
|
|
||||||
(center, radius)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Bounds2<T>
|
|
||||||
where
|
|
||||||
T: Num + Copy + Default
|
|
||||||
{
|
|
||||||
pub fn area(&self) -> T {
|
|
||||||
let d: Vector2<T> = self.p_max - self.p_min;
|
|
||||||
d.x() * d.y()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, PartialEq)]
|
|
||||||
pub struct Frame {
|
|
||||||
pub x: Vector3f,
|
|
||||||
pub y: Vector3f,
|
|
||||||
pub z: Vector3f,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Frame {
|
|
||||||
pub fn from_x(x: Vector3f) -> Self {
|
|
||||||
let (y, z) = x.normalize().coordinate_system();
|
|
||||||
Self { x: x.normalize(), y, z }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_y(y: Vector3f) -> Self {
|
|
||||||
let (z, x) = y.normalize().coordinate_system();
|
|
||||||
Self { x, y: y.normalize(), z }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_z(z: Vector3f) -> Self {
|
|
||||||
let (x, y) = z.normalize().coordinate_system();
|
|
||||||
Self { x, y, z: z.normalize() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_local(&self, v: Vector3f) -> Vector3f {
|
|
||||||
Vector3f::new(v.dot(self.x), v.dot(self.y), v.dot(self.z))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_local(&self, v: Vector3f) -> Vector3f {
|
|
||||||
self.x * v.x() + self.y * v.y() + self.z * v.z()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Ray {
|
|
||||||
pub o: Point3f,
|
|
||||||
pub d: Vector3f,
|
|
||||||
pub medium: Option<Arc<Medium>>,
|
|
||||||
pub time: Float,
|
|
||||||
// We do this instead of creating a trait for Rayable or some gnarly thing like that
|
|
||||||
pub differential: Option<RayDifferential>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Ray {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
o: Point3f::new(0.0, 0.0, 0.0),
|
|
||||||
d: Vector3f::new(0.0, 0.0, 0.0),
|
|
||||||
medium: None,
|
|
||||||
time: 0.0,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ray {
|
|
||||||
pub fn new(o: Point3f, d: Vector3f, time: Option<Float>, medium: Option<Arc<Medium>>) -> Self {
|
|
||||||
Self { o, d, time: time.unwrap_or_else(|| Self::default().time), medium, ..Self::default() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn evaluate(&self, t: Float) -> Point3f {
|
|
||||||
self.o + self.d * t
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn offset_origin(p: &Point3fi, n: &Normal3f, w: &Vector3f) -> Point3f {
|
|
||||||
let d: Float = n.abs().dot(p.error());
|
|
||||||
let normal: Vector3f = Vector3f::from(*n);
|
|
||||||
|
|
||||||
let mut offset = p.midpoint();
|
|
||||||
if w.dot(normal) < 0.0 {
|
|
||||||
offset -= normal * d;
|
|
||||||
} else {
|
|
||||||
offset += normal * d;
|
|
||||||
}
|
|
||||||
|
|
||||||
for i in 0..3 {
|
|
||||||
if n[i] > 0.0 {
|
|
||||||
offset[i] = next_float_up(offset[i]);
|
|
||||||
} else if n[i] < 0.0 {
|
|
||||||
offset[i] = next_float_down(offset[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
offset
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn(pi: &Point3fi, n: &Normal3f, time: Float, d: Vector3f) -> Ray {
|
|
||||||
let origin = Self::offset_origin(pi, n, &d);
|
|
||||||
Ray {
|
|
||||||
o: origin,
|
|
||||||
d,
|
|
||||||
time,
|
|
||||||
medium: None,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_to_point(p_from: &Point3fi, n: &Normal3f, time: Float, p_to: Point3f) -> Ray {
|
|
||||||
let d = p_to - p_from.midpoint();
|
|
||||||
Self::spawn(p_from, n, time, d)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn spawn_to_interaction(p_from: &Point3fi, n_from: &Normal3f, time: Float, p_to: &Point3fi, n_to: &Normal3f) -> Ray {
|
|
||||||
let dir_for_offset = p_to.midpoint() - p_from.midpoint();
|
|
||||||
let pf = Self::offset_origin(p_from, n_from, &dir_for_offset);
|
|
||||||
let pt = Self::offset_origin(p_to, n_to, &(pf - p_to.midpoint()));
|
|
||||||
|
|
||||||
let d = pt - pf;
|
|
||||||
|
|
||||||
Ray {
|
|
||||||
o: pf,
|
|
||||||
d,
|
|
||||||
time,
|
|
||||||
medium: None,
|
|
||||||
differential: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale_differentials(&mut self, s: Float) {
|
|
||||||
if let Some(differential) = &mut self.differential {
|
|
||||||
differential.rx_origin = self.o + (differential.rx_origin - self.o) * s;
|
|
||||||
differential.ry_origin = self.o + (differential.ry_origin - self.o) * s;
|
|
||||||
differential.rx_direction = self.d + (differential.rx_direction - self.d) * s;
|
|
||||||
differential.ry_direction = self.d + (differential.ry_direction - self.d) * s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Copy, Clone)]
|
|
||||||
pub struct RayDifferential {
|
|
||||||
pub rx_origin: Point3f,
|
|
||||||
pub ry_origin: Point3f,
|
|
||||||
pub rx_direction: Vector3f,
|
|
||||||
pub ry_direction: Vector3f,
|
|
||||||
}
|
|
||||||
14
src/utils/hash.rs
Normal file
14
src/utils/hash.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use std::collections::hash_map::DefaultHasher;
|
||||||
|
|
||||||
|
const U32_TO_F32_SCALE: f32 = 1.0 / 4294967296.0;
|
||||||
|
|
||||||
|
pub fn hash_float<T: Hash>(args: T) -> Float {
|
||||||
|
let mut hasher = DefaultHasher::new();
|
||||||
|
args.hash(&mut hasher);
|
||||||
|
let hash_u64 = hasher.finish();
|
||||||
|
let hash_u32 = hash_u64 as u32;
|
||||||
|
(hash_u32 as f32) * U32_TO_F32_SCALE
|
||||||
|
}
|
||||||
|
|
||||||
1930
src/utils/image.rs
1930
src/utils/image.rs
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::core::pbrt::{Float, next_float_up, next_float_down};
|
use crate::core::pbrt::Float;
|
||||||
use std::ops::{Add, Sub, Mul, Div};
|
use crate::utils::math::{next_float_down, next_float_up};
|
||||||
|
use num_traits::Zero;
|
||||||
|
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct Interval {
|
pub struct Interval {
|
||||||
|
|
@ -90,13 +92,82 @@ impl Mul for Interval {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Mul<Float> for Interval {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Float) -> Self::Output {
|
||||||
|
if rhs > 0.0 {
|
||||||
|
Self {
|
||||||
|
low: next_float_down(self.low * rhs),
|
||||||
|
high: next_float_up(self.high * rhs),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
low: next_float_down(self.high * rhs),
|
||||||
|
high: next_float_up(self.low * rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Interval> for Float {
|
||||||
|
type Output = Interval;
|
||||||
|
fn mul(self, rhs: Interval) -> Self::Output {
|
||||||
|
rhs * self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Div for Interval {
|
impl Div for Interval {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn div(self, rhs: Self) -> Self::Output {
|
fn div(self, rhs: Self) -> Self::Output {
|
||||||
if rhs.contains(0.0) {
|
if rhs.contains(0.0) {
|
||||||
return Interval { low: Float::NEG_INFINITY, high: Float::INFINITY };
|
return Interval {
|
||||||
|
low: Float::NEG_INFINITY,
|
||||||
|
high: Float::INFINITY,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
self * Interval::new_from_endpoints(1.0 / rhs.low, 1.0 / rhs.high)
|
self * Interval::new_from_endpoints(1.0 / rhs.low, 1.0 / rhs.high)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Div<Float> for Interval {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: Float) -> Self::Output {
|
||||||
|
if rhs > 0.0 {
|
||||||
|
Self {
|
||||||
|
low: next_float_down(self.low / rhs),
|
||||||
|
high: next_float_up(self.high / rhs),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Self {
|
||||||
|
low: next_float_down(self.high / rhs),
|
||||||
|
high: next_float_up(self.low / rhs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Neg for Interval {
|
||||||
|
type Output = Self;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Self {
|
||||||
|
low: next_float_down(-self.high),
|
||||||
|
high: next_float_up(-self.low),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Zero for Interval {
|
||||||
|
fn zero() -> Self {
|
||||||
|
Self::new(0.0)
|
||||||
|
}
|
||||||
|
fn is_zero(&self) -> bool {
|
||||||
|
self.low == 0.0 && self.high == 0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Interval> for Float {
|
||||||
|
fn from(interval: Interval) -> Self {
|
||||||
|
interval.midpoint()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,44 @@
|
||||||
use super::geometry::{Point2f, Vector3f};
|
use super::color::{RGB, XYZ};
|
||||||
use crate::core::pbrt::{PI, PI_OVER_4, square, lerp, Float, ONE_MINUS_EPSILON, clamp_t};
|
use super::error::{InversionError, LlsError};
|
||||||
|
use crate::core::pbrt::{Float, ONE_MINUS_EPSILON, PI, PI_OVER_4, clamp_t, lerp};
|
||||||
|
use crate::geometry::{Point, Point2f, Vector, Vector3f};
|
||||||
|
|
||||||
|
use num_traits::{Float as NumFloat, Num, One, Signed, Zero};
|
||||||
|
use std::error::Error;
|
||||||
|
use std::fmt::{self, Display};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Mul, Neg, Add};
|
use std::ops::{Add, Div, Index, IndexMut, Mul, Neg};
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn degrees(a: Float) -> Float {
|
||||||
|
a * 180.0 / PI
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn radians(a: Float) -> Float {
|
||||||
|
a * PI / 180.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn square<T>(n: T) -> T
|
||||||
|
where
|
||||||
|
T: Mul<Output = T> + Copy,
|
||||||
|
{
|
||||||
|
n * n
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn fma<T>(a: T, b: T, c: T) -> T
|
fn fma<T>(a: T, b: T, c: T) -> T
|
||||||
where
|
where
|
||||||
T: Mul<Output = T> + Add<Output = T> + Copy
|
T: Mul<Output = T> + Add<Output = T> + Copy,
|
||||||
{
|
{
|
||||||
a * b + c
|
a * b + c
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
|
pub fn difference_of_products<T>(a: T, b: T, c: T, d: T) -> T
|
||||||
where
|
where
|
||||||
T: Mul<Output = T> + Add<Output = T> + Neg<Output = T> + Copy
|
T: Mul<Output = T> + Add<Output = T> + Neg<Output = T> + Copy,
|
||||||
{
|
{
|
||||||
let cd = c * d;
|
let cd = c * d;
|
||||||
let difference_of_products = fma(a, b, -cd);
|
let difference_of_products = fma(a, b, -cd);
|
||||||
|
|
@ -23,9 +47,27 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn safe_asin(x: Float) -> Float {
|
pub fn sum_of_products<T>(a: T, b: T, c: T, d: T) -> T
|
||||||
if x >= -1.0001 && x <= 1.0001 {
|
where
|
||||||
clamp_t(x, -1., 1.).asin()
|
T: Mul<Output = T> + Add<Output = T> + Neg<Output = T> + Copy,
|
||||||
|
{
|
||||||
|
let cd = c * d;
|
||||||
|
let sum_of_products = fma(a, b, cd);
|
||||||
|
let error = fma(c, d, -cd);
|
||||||
|
return sum_of_products + error;
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
pub fn safe_sqrt(x: Float) -> Float {
|
||||||
|
assert!(x > -1e-3);
|
||||||
|
0.0_f32.max(x).sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn safe_asin<T: NumFloat>(x: T) -> T {
|
||||||
|
let epsilon = T::from(0.0001).unwrap();
|
||||||
|
let one = T::one();
|
||||||
|
if x >= -(one + epsilon) && x <= one + epsilon {
|
||||||
|
clamp_t(x, -one, one).asin()
|
||||||
} else {
|
} else {
|
||||||
panic!("Not valid value for asin")
|
panic!("Not valid value for asin")
|
||||||
}
|
}
|
||||||
|
|
@ -36,7 +78,7 @@ pub fn safe_acos(x: Float) -> Float {
|
||||||
if x >= -1.0001 && x <= 1.0001 {
|
if x >= -1.0001 && x <= 1.0001 {
|
||||||
clamp_t(x, -1., 1.).asin()
|
clamp_t(x, -1., 1.).asin()
|
||||||
} else {
|
} else {
|
||||||
panic!("Not valid value for asin")
|
panic!("Not valid value for acos")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,6 +90,12 @@ pub fn sinx_over_x(x: Float) -> Float {
|
||||||
return x.sin() / x;
|
return x.sin() / x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn sinc(x: Float) -> Float {
|
||||||
|
return sinx_over_x(PI * x);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
|
pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
|
||||||
if x.abs() > radius {
|
if x.abs() > radius {
|
||||||
return 0.;
|
return 0.;
|
||||||
|
|
@ -55,17 +103,13 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
|
||||||
return sinc(x) * sinc(x / tau);
|
return sinc(x) * sinc(x / tau);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sinc(x: Float) -> Float {
|
|
||||||
return sinx_over_x(PI * x);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> {
|
pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> {
|
||||||
if a == 0. {
|
if a == 0. {
|
||||||
if b == 0. {
|
if b == 0. {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let t0 = -c / b;
|
let t0 = -c / b;
|
||||||
let t1 = - c / b;
|
let t1 = -c / b;
|
||||||
return Some((t0, t1));
|
return Some((t0, t1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,22 +129,32 @@ pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> {
|
||||||
Some((t0, t1))
|
Some((t0, t1))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn linear_least_squares<const R: usize, const N: usize>(
|
||||||
|
a: [[Float; R]; N],
|
||||||
|
b: [[Float; R]; N],
|
||||||
|
) -> Result<SquareMatrix<Float, R>, Box<dyn Error>> {
|
||||||
|
let am = Matrix::from(a);
|
||||||
|
let bm = Matrix::from(b);
|
||||||
|
let ata = am.transpose() * am;
|
||||||
|
let atb = am.transpose() * bm;
|
||||||
|
let at_ai = ata.inverse()?;
|
||||||
|
Ok((at_ai * atb).transpose())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
|
pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
|
||||||
if uv[0] < 0. {
|
if uv[0] < 0. {
|
||||||
uv[0] = -uv[0]; // mirror across u = 0
|
uv[0] = -uv[0];
|
||||||
uv[1] = 1. - uv[1]; // mirror across v = 0.5
|
uv[1] = 1. - uv[1];
|
||||||
} else if uv[0] > 1.
|
} else if uv[0] > 1. {
|
||||||
{
|
uv[0] = 2. - uv[0];
|
||||||
uv[0] = 2. - uv[0]; // mirror across u = 1
|
uv[1] = 1. - uv[1];
|
||||||
uv[1] = 1. - uv[1]; // mirror across v = 0.5
|
|
||||||
}
|
}
|
||||||
if uv[1] < 0. {
|
if uv[1] < 0. {
|
||||||
uv[0] = 1. - uv[0]; // mirror across u = 0.5
|
uv[0] = 1. - uv[0];
|
||||||
uv[1] = -uv[1]; // mirror across v = 0;
|
uv[1] = -uv[1];
|
||||||
} else if uv[1] > 1.
|
} else if uv[1] > 1. {
|
||||||
{
|
uv[0] = 1. - uv[0];
|
||||||
uv[0] = 1. - uv[0]; // mirror across u = 0.5
|
uv[1] = 2. - uv[1];
|
||||||
uv[1] = 2. - uv[1]; // mirror across v = 1
|
|
||||||
}
|
}
|
||||||
*uv
|
*uv
|
||||||
}
|
}
|
||||||
|
|
@ -108,33 +162,35 @@ pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
|
||||||
pub fn equal_area_square_to_sphere(p: Point2f) -> Vector3f {
|
pub fn equal_area_square_to_sphere(p: Point2f) -> Vector3f {
|
||||||
assert!(p.x() >= 0. && p.x() <= 1. && p.y() >= 0. && p.y() <= 1.);
|
assert!(p.x() >= 0. && p.x() <= 1. && p.y() >= 0. && p.y() <= 1.);
|
||||||
|
|
||||||
// Transform _p_ to $[-1,1]^2$ and compute absolute values
|
// Transform p to [-1,1]^2 and compute absolute values
|
||||||
let u = 2. * p.x() - 1.;
|
let u = 2. * p.x() - 1.;
|
||||||
let v = 2. * p.y() - 1.;
|
let v = 2. * p.y() - 1.;
|
||||||
let up = u.abs();
|
let up = u.abs();
|
||||||
let vp = v.abs();
|
let vp = v.abs();
|
||||||
|
|
||||||
// Compute radius _r_ as signed distance from diagonal
|
// Compute radius as signed distance from diagonal
|
||||||
let signed_distance = 1. - (up + vp);
|
let signed_distance = 1. - (up + vp);
|
||||||
let d = signed_distance.abs();
|
let d = signed_distance.abs();
|
||||||
let r = 1. - d;
|
let r = 1. - d;
|
||||||
|
|
||||||
// Compute angle $\phi$ for square to sphere mapping
|
// Compute angle \phi for square to sphere mapping
|
||||||
let mut phi = if r == 0. { 1. } else {(vp - up) / r + 1.};
|
let mut phi = if r == 0. { 1. } else { (vp - up) / r + 1. };
|
||||||
phi /= PI_OVER_4;
|
phi /= PI_OVER_4;
|
||||||
|
|
||||||
// Find $z$ coordinate for spherical direction
|
// Find z for spherical direction
|
||||||
let z = (1. - square(r)).copysign(signed_distance);
|
let z = (1. - square(r)).copysign(signed_distance);
|
||||||
|
|
||||||
// Compute $\cos\phi$ and $\sin\phi$ for original quadrant and return vector
|
// Compute $\cos\phi$ and $\sin\phi$ for original quadrant and return vector
|
||||||
let cos_phi = phi.cos().copysign(u);
|
let cos_phi = phi.cos().copysign(u);
|
||||||
let sin_phi = phi.sin().copysign(v);
|
let sin_phi = phi.sin().copysign(v);
|
||||||
return Vector3f::new(cos_phi * r * (2. - square(r)).sqrt(),
|
return Vector3f::new(
|
||||||
sin_phi * r * (2. - square(r)).sqrt(), z);
|
cos_phi * r * (2. - square(r)).sqrt(),
|
||||||
|
sin_phi * r * (2. - square(r)).sqrt(),
|
||||||
|
z,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn gaussian(x: Float, y: Float, sigma: Float) -> Float
|
pub fn gaussian(x: Float, y: Float, sigma: Float) -> Float {
|
||||||
{
|
|
||||||
(-(x * x + y * y) / (2. * sigma * sigma)).exp()
|
(-(x * x + y * y) / (2. * sigma * sigma)).exp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,28 +203,64 @@ pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float
|
||||||
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
|
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
|
||||||
assert!(a >= 0. && b >= 0.);
|
assert!(a >= 0. && b >= 0.);
|
||||||
if u == 0. && a == 0. {
|
if u == 0. && a == 0. {
|
||||||
return 0.
|
return 0.;
|
||||||
}
|
}
|
||||||
let x = u * (a + b) / (a + (lerp(u, square(a), square(b))));
|
let x = u * (a + b) / (a + (lerp(u, square(a), square(b))));
|
||||||
x.min(ONE_MINUS_EPSILON)
|
x.min(ONE_MINUS_EPSILON)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_float_down(a: f32) -> f32 {
|
#[inline]
|
||||||
if a.is_infinite() && a < 0.0 {
|
pub fn float_to_bits(f: Float) -> u32 {
|
||||||
return a;
|
f.to_bits()
|
||||||
}
|
|
||||||
if a == 0.0 {
|
|
||||||
return -0.0;
|
|
||||||
}
|
|
||||||
let bits = a.to_bits();
|
|
||||||
if a > 0.0 {
|
|
||||||
f32::from_bits(bits - 1)
|
|
||||||
} else {
|
|
||||||
f32::from_bits(bits + 1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remapped: Option<&mut f32>,
|
#[inline]
|
||||||
|
pub fn bits_to_float(ui: u32) -> Float {
|
||||||
|
f32::from_bits(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn f64_to_bits(f: f64) -> u64 {
|
||||||
|
f.to_bits()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn next_float_up(v: Float) -> Float {
|
||||||
|
if v.is_infinite() && v > 0.0 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
let v = if v == -0.0 { 0.0 } else { v };
|
||||||
|
|
||||||
|
let mut ui = float_to_bits(v);
|
||||||
|
if v >= 0.0 {
|
||||||
|
ui += 1;
|
||||||
|
} else {
|
||||||
|
ui -= 1;
|
||||||
|
}
|
||||||
|
bits_to_float(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn next_float_down(v: Float) -> Float {
|
||||||
|
if v.is_infinite() && v < 0.0 {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
let v = if v == 0.0 { -0.0 } else { v };
|
||||||
|
|
||||||
|
let mut ui = float_to_bits(v);
|
||||||
|
if v > 0.0 {
|
||||||
|
ui -= 1;
|
||||||
|
} else {
|
||||||
|
ui += 1;
|
||||||
|
}
|
||||||
|
bits_to_float(ui)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_discrete(
|
||||||
|
weights: &[f32],
|
||||||
|
u: f32,
|
||||||
|
pmf: Option<&mut f32>,
|
||||||
|
u_remapped: Option<&mut f32>,
|
||||||
) -> Option<usize> {
|
) -> Option<usize> {
|
||||||
// Handle empty weights for discrete sampling.
|
// Handle empty weights for discrete sampling.
|
||||||
if weights.is_empty() {
|
if weights.is_empty() {
|
||||||
|
|
@ -178,10 +270,8 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the sum of all weights.
|
|
||||||
let sum_weights: f32 = weights.iter().sum();
|
|
||||||
|
|
||||||
// If the total weight is zero, sampling is not possible.
|
// If the total weight is zero, sampling is not possible.
|
||||||
|
let sum_weights: f32 = weights.iter().sum();
|
||||||
if sum_weights == 0.0 {
|
if sum_weights == 0.0 {
|
||||||
if let Some(p) = pmf {
|
if let Some(p) = pmf {
|
||||||
*p = 0.0;
|
*p = 0.0;
|
||||||
|
|
@ -189,7 +279,6 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute rescaled u' sample, ensuring it's strictly less than sum_weights.
|
|
||||||
let mut up = u * sum_weights;
|
let mut up = u * sum_weights;
|
||||||
if up >= sum_weights {
|
if up >= sum_weights {
|
||||||
up = next_float_down(up);
|
up = next_float_down(up);
|
||||||
|
|
@ -201,17 +290,14 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
|
||||||
while sum + weights[offset] <= up {
|
while sum + weights[offset] <= up {
|
||||||
sum += weights[offset];
|
sum += weights[offset];
|
||||||
offset += 1;
|
offset += 1;
|
||||||
// This assertion should hold true due to the guard `up = next_float_down(up)`.
|
|
||||||
debug_assert!(offset < weights.len());
|
debug_assert!(offset < weights.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute PMF and remapped u value, if requested.
|
|
||||||
if let Some(p) = pmf {
|
if let Some(p) = pmf {
|
||||||
*p = weights[offset] / sum_weights;
|
*p = weights[offset] / sum_weights;
|
||||||
}
|
}
|
||||||
if let Some(ur) = u_remapped {
|
if let Some(ur) = u_remapped {
|
||||||
let weight = weights[offset];
|
let weight = weights[offset];
|
||||||
// The logic guarantees weight > 0 here if sum_weights > 0.
|
|
||||||
*ur = ((up - sum) / weight).min(ONE_MINUS_EPSILON);
|
*ur = ((up - sum) / weight).min(ONE_MINUS_EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,10 +306,550 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
|
||||||
|
|
||||||
pub fn sample_tent(u: Float, r: Float) -> Float {
|
pub fn sample_tent(u: Float, r: Float) -> Float {
|
||||||
let mut u_remapped = 0.0;
|
let mut u_remapped = 0.0;
|
||||||
let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped)).expect("Discrete sampling shouldn't fail");
|
let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped))
|
||||||
|
.expect("Discrete sampling shouldn't fail");
|
||||||
if offset == 0 {
|
if offset == 0 {
|
||||||
return -r + r * sample_linear(u, 0., 1.);
|
return -r + r * sample_linear(u, 0., 1.);
|
||||||
} else {
|
} else {
|
||||||
return r * sample_linear(u, 1., 0.);
|
return r * sample_linear(u, 1., 0.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn round_up_pow2(mut n: i32) -> i32 {
|
||||||
|
if n <= 0 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
n -= 1;
|
||||||
|
n |= n >> 1;
|
||||||
|
n |= n >> 2;
|
||||||
|
n |= n >> 4;
|
||||||
|
n |= n >> 8;
|
||||||
|
n |= n >> 16;
|
||||||
|
n + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// MATRIX STUFF (TEST THOROUGHLY)
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct Matrix<T, const R: usize, const C: usize> {
|
||||||
|
m: [[T; C]; R],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> Matrix<T, R, C> {
|
||||||
|
pub const fn new(data: [[T; C]; R]) -> Self {
|
||||||
|
Self { m: data }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zero() -> Self
|
||||||
|
where
|
||||||
|
T: Clone + Zero,
|
||||||
|
{
|
||||||
|
let m: [[T; C]; R] = std::array::from_fn(|_| std::array::from_fn(|_| T::zero()));
|
||||||
|
Self { m }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transpose(&self) -> Matrix<T, C, R>
|
||||||
|
where
|
||||||
|
T: Clone + Zero,
|
||||||
|
{
|
||||||
|
let mut result = Matrix::<T, C, R>::zero();
|
||||||
|
for i in 0..R {
|
||||||
|
for j in 0..C {
|
||||||
|
result.m[j][i] = self.m[i][j].clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Zero + Clone, const R: usize, const C: usize> Default for Matrix<T, R, C> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::zero()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> From<[[T; C]; R]> for Matrix<T, R, C> {
|
||||||
|
fn from(m: [[T; C]; R]) -> Self {
|
||||||
|
Self::new(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> Index<(usize, usize)> for Matrix<T, R, C> {
|
||||||
|
type Output = T;
|
||||||
|
fn index(&self, index: (usize, usize)) -> &Self::Output {
|
||||||
|
&self.m[index.0][index.1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> IndexMut<(usize, usize)> for Matrix<T, R, C> {
|
||||||
|
fn index_mut(&mut self, index: (usize, usize)) -> &mut Self::Output {
|
||||||
|
&mut self.m[index.0][index.1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: PartialEq, const R: usize, const C: usize> PartialEq for Matrix<T, R, C> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.m == other.m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Eq, const R: usize, const C: usize> Eq for Matrix<T, R, C> {}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize, const N: usize> Mul<Matrix<T, N, C>> for Matrix<T, R, N>
|
||||||
|
where
|
||||||
|
T: Mul<Output = T> + Add<Output = T> + Clone + Zero,
|
||||||
|
{
|
||||||
|
type Output = Matrix<T, R, C>;
|
||||||
|
fn mul(self, rhs: Matrix<T, N, C>) -> Self::Output {
|
||||||
|
let mut result = Matrix::<T, R, C>::zero();
|
||||||
|
for i in 0..R {
|
||||||
|
for j in 0..C {
|
||||||
|
let mut sum = T::zero();
|
||||||
|
for k in 0..N {
|
||||||
|
sum = sum + self.m[i][k].clone() * rhs.m[k][j].clone();
|
||||||
|
}
|
||||||
|
result.m[i][j] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> Mul<T> for Matrix<T, R, C>
|
||||||
|
where
|
||||||
|
T: Mul<Output = T> + Clone + Zero,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
let mut result = Self::zero();
|
||||||
|
for i in 0..R {
|
||||||
|
for j in 0..C {
|
||||||
|
result.m[i][j] = self.m[i][j].clone() * rhs.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const R: usize, const C: usize> Mul<Matrix<Float, R, C>> for Float {
|
||||||
|
type Output = Matrix<Float, R, C>;
|
||||||
|
fn mul(self, rhs: Matrix<Float, R, C>) -> Self::Output {
|
||||||
|
rhs * self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> Div<T> for Matrix<T, R, C>
|
||||||
|
where
|
||||||
|
T: Div<Output = T> + Clone + Zero,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
let mut result = Self::zero();
|
||||||
|
for i in 0..R {
|
||||||
|
for j in 0..C {
|
||||||
|
result.m[i][j] = self.m[i][j].clone() / rhs.clone();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display, const R: usize, const C: usize> Display for Matrix<T, R, C> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut col_widths = [0; C];
|
||||||
|
for row in self.m.iter() {
|
||||||
|
for (j, element) in row.iter().enumerate() {
|
||||||
|
let width = format!("{}", element).len();
|
||||||
|
if width > col_widths[j] {
|
||||||
|
col_widths[j] = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..R {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for j in 0..C {
|
||||||
|
write!(f, "{: >width$} ", self.m[i][j], width = col_widths[j])?;
|
||||||
|
}
|
||||||
|
writeln!(f, "]")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const R: usize, const C: usize> Add for Matrix<T, R, C>
|
||||||
|
where
|
||||||
|
T: Copy + Add<Output = T> + Zero,
|
||||||
|
{
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
let mut result = Matrix::<T, R, C>::zero();
|
||||||
|
for i in 0..R {
|
||||||
|
for j in 0..C {
|
||||||
|
result.m[i][j] = self.m[i][j] + rhs.m[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SquareMatrix<T, const N: usize> = Matrix<T, N, N>;
|
||||||
|
|
||||||
|
impl<T, const N: usize> SquareMatrix<T, N> {
|
||||||
|
pub fn identity() -> Self
|
||||||
|
where
|
||||||
|
T: Copy + Zero + One,
|
||||||
|
{
|
||||||
|
let mut m = [[T::zero(); N]; N];
|
||||||
|
for i in 0..N {
|
||||||
|
m[i][i] = T::one();
|
||||||
|
}
|
||||||
|
Self { m }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diag(v: &[T]) -> Self
|
||||||
|
where
|
||||||
|
T: Zero + Copy,
|
||||||
|
{
|
||||||
|
let mut m = [[T::zero(); N]; N];
|
||||||
|
for i in 0..N {
|
||||||
|
m[i][i] = v[i];
|
||||||
|
}
|
||||||
|
Self { m }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_identity(&self) -> bool
|
||||||
|
where
|
||||||
|
T: Zero + One + PartialEq,
|
||||||
|
{
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
if i == j {
|
||||||
|
if self.m[i][j] != T::one() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if self.m[i][j] != T::zero() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn trace(&self) -> T
|
||||||
|
where
|
||||||
|
T: Zero + Copy,
|
||||||
|
{
|
||||||
|
let mut sum = T::zero();
|
||||||
|
for i in 0..N {
|
||||||
|
sum = sum + self.m[i][i];
|
||||||
|
}
|
||||||
|
sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: NumFloat + Copy + PartialEq, const N: usize> SquareMatrix<T, N> {
|
||||||
|
pub fn inverse(&self) -> Result<Self, InversionError> {
|
||||||
|
if N == 0 {
|
||||||
|
return Err(InversionError::EmptyMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mat = self.m;
|
||||||
|
let mut inv = Self::identity();
|
||||||
|
|
||||||
|
for i in 0..N {
|
||||||
|
let mut pivot_row = i;
|
||||||
|
for j in (i + 1)..N {
|
||||||
|
if mat[j][i].abs() > mat[pivot_row][i].abs() {
|
||||||
|
pivot_row = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if pivot_row != i {
|
||||||
|
mat.swap(i, pivot_row);
|
||||||
|
inv.m.swap(i, pivot_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
let pivot = mat[i][i];
|
||||||
|
if pivot.is_zero() {
|
||||||
|
return Err(InversionError::SingularMatrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
for j in i..N {
|
||||||
|
mat[i][j] = mat[i][j] / pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
for j in 0..N {
|
||||||
|
inv.m[i][j] = inv.m[i][j] / pivot;
|
||||||
|
}
|
||||||
|
|
||||||
|
for j in 0..N {
|
||||||
|
if i != j {
|
||||||
|
let factor = mat[j][i];
|
||||||
|
for k in i..N {
|
||||||
|
mat[j][k] = mat[j][k] - factor * mat[i][k];
|
||||||
|
}
|
||||||
|
for k in 0..N {
|
||||||
|
inv.m[j][k] = inv.m[j][k] - factor * inv.m[i][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(inv)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn determinant(&self) -> T {
|
||||||
|
let m = &self.m;
|
||||||
|
|
||||||
|
match N {
|
||||||
|
0 => T::one(),
|
||||||
|
1 => m[0][0],
|
||||||
|
2 => m[0][0] * m[1][1] - m[0][1] * m[1][0],
|
||||||
|
3 => {
|
||||||
|
let a = m[0][0];
|
||||||
|
let b = m[0][1];
|
||||||
|
let c = m[0][2];
|
||||||
|
let d = m[1][0];
|
||||||
|
let e = m[1][1];
|
||||||
|
let f = m[1][2];
|
||||||
|
let g = m[2][0];
|
||||||
|
let h = m[2][1];
|
||||||
|
let i = m[2][2];
|
||||||
|
|
||||||
|
a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g)
|
||||||
|
}
|
||||||
|
4 => {
|
||||||
|
let det3 =
|
||||||
|
|m11: T, m12: T, m13: T, m21: T, m22: T, m23: T, m31: T, m32: T, m33: T| -> T {
|
||||||
|
m11 * (m22 * m33 - m23 * m32) - m12 * (m21 * m33 - m23 * m31)
|
||||||
|
+ m13 * (m21 * m32 - m22 * m31)
|
||||||
|
};
|
||||||
|
|
||||||
|
let c0 = det3(
|
||||||
|
m[1][1], m[1][2], m[1][3], m[2][1], m[2][2], m[2][3], m[3][1], m[3][2], m[3][3],
|
||||||
|
);
|
||||||
|
let c1 = det3(
|
||||||
|
m[1][0], m[1][2], m[1][3], m[2][0], m[2][2], m[2][3], m[3][0], m[3][2], m[3][3],
|
||||||
|
);
|
||||||
|
let c2 = det3(
|
||||||
|
m[1][0], m[1][1], m[1][3], m[2][0], m[2][1], m[2][3], m[3][0], m[3][1], m[3][3],
|
||||||
|
);
|
||||||
|
let c3 = det3(
|
||||||
|
m[1][0], m[1][1], m[1][2], m[2][0], m[2][1], m[2][2], m[3][0], m[3][1], m[3][2],
|
||||||
|
);
|
||||||
|
|
||||||
|
m[0][0] * c0 - m[0][1] * c1 + m[0][2] * c2 - m[0][3] * c3
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Fallback to LU decomposition for N > 4
|
||||||
|
let mut lum = self.m;
|
||||||
|
let mut parity = 0;
|
||||||
|
|
||||||
|
for i in 0..N {
|
||||||
|
let mut max_row = i;
|
||||||
|
for k in (i + 1)..N {
|
||||||
|
if lum[k][i].abs() > lum[max_row][i].abs() {
|
||||||
|
max_row = k;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if max_row != i {
|
||||||
|
lum.swap(i, max_row);
|
||||||
|
parity += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singular matrix
|
||||||
|
if lum[i][i] == T::zero() {
|
||||||
|
return T::zero();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Gaussian elimination
|
||||||
|
for j in (i + 1)..N {
|
||||||
|
let factor = lum[j][i] / lum[i][i];
|
||||||
|
for k in i..N {
|
||||||
|
lum[j][k] = lum[j][k] - factor * lum[i][k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut det = T::one();
|
||||||
|
for i in 0..N {
|
||||||
|
det = det * lum[i][i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if parity % 2 != 0 {
|
||||||
|
det = -det;
|
||||||
|
}
|
||||||
|
|
||||||
|
det
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> Index<usize> for SquareMatrix<T, N> {
|
||||||
|
type Output = [T; N];
|
||||||
|
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.m[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, const N: usize> IndexMut<usize> for SquareMatrix<T, N> {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
&mut self.m[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Num + Copy, const N: usize> Mul<Vector<T, N>> for SquareMatrix<T, N> {
|
||||||
|
type Output = Vector<T, N>;
|
||||||
|
fn mul(self, rhs: Vector<T, N>) -> Self::Output {
|
||||||
|
let mut result_arr = [T::zero(); N];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result_arr[i] = result_arr[i] + self.m[i][j] * rhs.0[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector(result_arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Num + Copy, const N: usize> Mul<Point<T, N>> for SquareMatrix<T, N> {
|
||||||
|
type Output = Point<T, N>;
|
||||||
|
fn mul(self, rhs: Point<T, N>) -> Self::Output {
|
||||||
|
let mut result_arr = [T::zero(); N];
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
result_arr[i] = result_arr[i] + self.m[i][j] * rhs.0[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Point(result_arr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SquareMatrix<Float, 3> {
|
||||||
|
pub fn transform_to_xyz(&self, rgb: RGB) -> XYZ {
|
||||||
|
let r = rgb[0];
|
||||||
|
let g = rgb[1];
|
||||||
|
let b = rgb[2];
|
||||||
|
|
||||||
|
let x = self.m[0][0] * r + self.m[0][1] * g + self.m[0][2] * b;
|
||||||
|
let y = self.m[1][0] * r + self.m[1][1] * g + self.m[1][2] * b;
|
||||||
|
let z = self.m[2][0] * r + self.m[2][1] * g + self.m[2][2] * b;
|
||||||
|
|
||||||
|
XYZ::new(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_to_rgb(&self, xyz: XYZ) -> RGB {
|
||||||
|
let x = xyz[0];
|
||||||
|
let y = xyz[1];
|
||||||
|
let z = xyz[2];
|
||||||
|
|
||||||
|
let r = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z;
|
||||||
|
let g = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z;
|
||||||
|
let b = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z;
|
||||||
|
|
||||||
|
RGB::new(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<XYZ> for SquareMatrix<Float, 3> {
|
||||||
|
type Output = XYZ;
|
||||||
|
fn mul(self, rhs: XYZ) -> Self::Output {
|
||||||
|
let mut result_arr = [0.0; 3];
|
||||||
|
for i in 0..3 {
|
||||||
|
for j in 0..3 {
|
||||||
|
result_arr[i] = result_arr[i] + self.m[i][j] * rhs[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XYZ::new(result_arr[0], result_arr[1], result_arr[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<RGB> for SquareMatrix<Float, 3> {
|
||||||
|
type Output = RGB;
|
||||||
|
fn mul(self, rhs: RGB) -> Self::Output {
|
||||||
|
let mut result_arr = [0.0; 3];
|
||||||
|
for i in 0..3 {
|
||||||
|
for j in 0..3 {
|
||||||
|
result_arr[i] = result_arr[i] + self.m[i][j] * rhs[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RGB::new(result_arr[0], result_arr[1], result_arr[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn assert_matrix_approx_eq<const N: usize>(a: &SquareMatrix<f64, N>, b: &SquareMatrix<f64, N>) {
|
||||||
|
const EPSILON: f64 = 1e-9;
|
||||||
|
for i in 0..N {
|
||||||
|
for j in 0..N {
|
||||||
|
assert!(
|
||||||
|
(a[i][j] - b[i][j]).abs() < EPSILON,
|
||||||
|
"Matrices differ at ({},{}): {} vs {}\nLeft:\n{}\nRight:\n{}",
|
||||||
|
i,
|
||||||
|
j,
|
||||||
|
a[i][j],
|
||||||
|
b[i][j],
|
||||||
|
a,
|
||||||
|
b
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inverse_2x2() {
|
||||||
|
let m = SquareMatrix {
|
||||||
|
m: [[4.0, 7.0], [2.0, 6.0]],
|
||||||
|
};
|
||||||
|
let identity = SquareMatrix::identity();
|
||||||
|
let inv = m.inverse().expect("Matrix should be invertible");
|
||||||
|
let product = m * inv;
|
||||||
|
assert_matrix_approx_eq(&product, &identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_inverse_3x3() {
|
||||||
|
let m = SquareMatrix::new([[1.0, 2.0, 3.0], [0.0, 1.0, 4.0], [5.0, 6.0, 0.0]]);
|
||||||
|
let identity = SquareMatrix::identity();
|
||||||
|
let inv = m.inverse().expect("Matrix should be invertible");
|
||||||
|
let product = m.clone() * inv.clone();
|
||||||
|
let product_inv = inv.clone() * m.clone();
|
||||||
|
assert_matrix_approx_eq(&product, &identity);
|
||||||
|
assert_matrix_approx_eq(&product_inv, &identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_singular_inverse() {
|
||||||
|
let m = SquareMatrix {
|
||||||
|
m: [[1.0, 2.0], [2.0, 4.0]],
|
||||||
|
}; // Determinant is 0
|
||||||
|
assert!(m.inverse().is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_multiplication_2x2() {
|
||||||
|
let a = SquareMatrix {
|
||||||
|
m: [[1.0, 2.0], [3.0, 4.0]],
|
||||||
|
};
|
||||||
|
let b = SquareMatrix {
|
||||||
|
m: [[2.0, 0.0], [1.0, 2.0]],
|
||||||
|
};
|
||||||
|
let expected = SquareMatrix {
|
||||||
|
m: [[4.0, 4.0], [10.0, 8.0]],
|
||||||
|
};
|
||||||
|
let result = a * b;
|
||||||
|
assert_matrix_approx_eq(&result, &expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_determinant_3x3() {
|
||||||
|
let m = SquareMatrix {
|
||||||
|
m: [[1.0, 2.0, 3.0], [0.0, 1.0, 4.0], [5.0, 6.0, 0.0]],
|
||||||
|
};
|
||||||
|
assert_eq!(m.determinant(), 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
161
src/utils/mesh.rs
Normal file
161
src/utils/mesh.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
use crate::core::sampler::PiecewiseConstant2D;
|
||||||
|
use crate::geometry::{Normal3f, Point2f, Point3f, Vector3f};
|
||||||
|
use crate::utils::transform::Transform;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TriangleMesh {
|
||||||
|
pub n_triangles: usize,
|
||||||
|
pub n_vertices: usize,
|
||||||
|
pub vertex_indices: Arc<Vec<usize>>,
|
||||||
|
pub p: Arc<Vec<Point3f>>,
|
||||||
|
pub n: Option<Arc<Vec<Normal3f>>>,
|
||||||
|
pub s: Option<Arc<Vec<Vector3f>>>,
|
||||||
|
pub uv: Option<Arc<Vec<Point2f>>>,
|
||||||
|
pub face_indices: Option<Arc<Vec<usize>>>,
|
||||||
|
pub reverse_orientation: bool,
|
||||||
|
pub transform_swaps_handedness: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriangleMesh {
|
||||||
|
pub fn new(
|
||||||
|
render_from_object: &Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
indices: Vec<usize>,
|
||||||
|
mut p: Vec<Point3f>,
|
||||||
|
mut s: Vec<Vector3f>,
|
||||||
|
mut n: Vec<Normal3f>,
|
||||||
|
uv: Vec<Point2f>,
|
||||||
|
face_indices: Vec<usize>,
|
||||||
|
) -> Self {
|
||||||
|
let n_triangles = indices.len() / 3;
|
||||||
|
let n_vertices = p.len();
|
||||||
|
for pt in p.iter_mut() {
|
||||||
|
*pt = render_from_object.apply_to_point(*pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform_swaps_handedness = render_from_object.swaps_handedness();
|
||||||
|
|
||||||
|
let uv = if !uv.is_empty() {
|
||||||
|
assert_eq!(n_vertices, uv.len());
|
||||||
|
Some(uv)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = if !n.is_empty() {
|
||||||
|
assert_eq!(n_vertices, n.len());
|
||||||
|
for nn in n.iter_mut() {
|
||||||
|
*nn = render_from_object.apply_to_normal(*nn);
|
||||||
|
if reverse_orientation {
|
||||||
|
*nn = -*nn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(n)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let s = if !s.is_empty() {
|
||||||
|
assert_eq!(n_vertices, s.len());
|
||||||
|
for ss in s.iter_mut() {
|
||||||
|
*ss = render_from_object.apply_to_vector(*ss);
|
||||||
|
}
|
||||||
|
Some(s)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let face_indices = if !face_indices.is_empty() {
|
||||||
|
assert_eq!(n_triangles, face_indices.len());
|
||||||
|
Some(face_indices)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(p.len() <= i32::MAX as usize);
|
||||||
|
assert!(indices.len() <= i32::MAX as usize);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
n_triangles,
|
||||||
|
n_vertices,
|
||||||
|
vertex_indices: Arc::new(indices),
|
||||||
|
p: Arc::new(p),
|
||||||
|
n: n.map(Arc::new),
|
||||||
|
s: s.map(Arc::new),
|
||||||
|
uv: uv.map(Arc::new),
|
||||||
|
face_indices: face_indices.map(Arc::new),
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swaps_handedness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct BilinearPatchMesh {
|
||||||
|
pub reverse_orientation: bool,
|
||||||
|
pub transform_swaps_handedness: bool,
|
||||||
|
pub n_patches: usize,
|
||||||
|
pub n_vertices: usize,
|
||||||
|
pub vertex_indices: Arc<Vec<usize>>,
|
||||||
|
pub p: Arc<Vec<Point3f>>,
|
||||||
|
pub n: Option<Arc<Vec<Normal3f>>>,
|
||||||
|
pub uv: Option<Arc<Vec<Point2f>>>,
|
||||||
|
pub image_distribution: Option<PiecewiseConstant2D>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BilinearPatchMesh {
|
||||||
|
pub fn new(
|
||||||
|
render_from_object: &Transform<Float>,
|
||||||
|
reverse_orientation: bool,
|
||||||
|
indices: Vec<usize>,
|
||||||
|
mut p: Vec<Point3f>,
|
||||||
|
mut n: Vec<Normal3f>,
|
||||||
|
uv: Vec<Point2f>,
|
||||||
|
image_distribution: PiecewiseConstant2D,
|
||||||
|
) -> Self {
|
||||||
|
let n_patches = indices.len() / 3;
|
||||||
|
let n_vertices = p.len();
|
||||||
|
for pt in p.iter_mut() {
|
||||||
|
*pt = render_from_object.apply_to_point(*pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
let transform_swaps_handedness = render_from_object.swaps_handedness();
|
||||||
|
|
||||||
|
let uv = if !uv.is_empty() {
|
||||||
|
assert_eq!(n_vertices, uv.len());
|
||||||
|
Some(uv)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let n = if !n.is_empty() {
|
||||||
|
assert_eq!(n_vertices, n.len());
|
||||||
|
for nn in n.iter_mut() {
|
||||||
|
*nn = render_from_object.apply_to_normal(*nn);
|
||||||
|
if reverse_orientation {
|
||||||
|
*nn = -*nn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(n)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(p.len() <= i32::MAX as usize);
|
||||||
|
assert!(indices.len() <= i32::MAX as usize);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
n_patches,
|
||||||
|
n_vertices,
|
||||||
|
vertex_indices: Arc::new(indices),
|
||||||
|
p: Arc::new(p),
|
||||||
|
n: n.map(Arc::new),
|
||||||
|
uv: uv.map(Arc::new),
|
||||||
|
reverse_orientation,
|
||||||
|
transform_swaps_handedness,
|
||||||
|
image_distribution: Some(image_distribution),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod colorspace;
|
pub mod colorspace;
|
||||||
pub mod containers;
|
pub mod containers;
|
||||||
pub mod geometry;
|
pub mod error;
|
||||||
|
pub mod hash;
|
||||||
|
pub mod image;
|
||||||
pub mod interval;
|
pub mod interval;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
|
pub mod mesh;
|
||||||
pub mod quaternion;
|
pub mod quaternion;
|
||||||
pub mod spectrum;
|
|
||||||
pub mod transform;
|
|
||||||
pub mod scattering;
|
|
||||||
pub mod sampling;
|
pub mod sampling;
|
||||||
|
pub mod scattering;
|
||||||
|
pub mod spectrum;
|
||||||
|
pub mod splines;
|
||||||
|
pub mod transform;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use std::ops::{Index, IndexMut};
|
|
||||||
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg};
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
use crate::core::pbrt::{safe_asin, sinx_over_x, Float};
|
use crate::core::pbrt::Float;
|
||||||
use super::geometry::{Vector3f, Dot, Normed};
|
use crate::geometry::{Vector3f, VectorLike};
|
||||||
|
use crate::utils::math::{safe_asin, sinx_over_x};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
pub struct Quaternion {
|
pub struct Quaternion {
|
||||||
|
|
@ -13,51 +14,89 @@ pub struct Quaternion {
|
||||||
|
|
||||||
impl Default for Quaternion {
|
impl Default for Quaternion {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { v: Vector3f::default(), w: 1.0 }
|
Self {
|
||||||
|
v: Vector3f::default(),
|
||||||
|
w: 1.0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for Quaternion {
|
impl Add for Quaternion {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn add(self, rhs: Quaternion) -> Self {
|
fn add(self, rhs: Quaternion) -> Self {
|
||||||
Self { v: self.v + rhs.v, w: self.w + rhs.w }
|
Self {
|
||||||
|
v: self.v + rhs.v,
|
||||||
|
w: self.w + rhs.w,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AddAssign for Quaternion {
|
impl AddAssign for Quaternion {
|
||||||
fn add_assign(&mut self, rhs: Self) { self.v += rhs.v; self.w += rhs.w; }
|
fn add_assign(&mut self, rhs: Self) {
|
||||||
|
self.v += rhs.v;
|
||||||
|
self.w += rhs.w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Sub for Quaternion {
|
impl Sub for Quaternion {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn sub(self, rhs: Self) -> Self { Self { v: self.v - rhs.v, w: self.w - rhs.w }}
|
fn sub(self, rhs: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v - rhs.v,
|
||||||
|
w: self.w - rhs.w,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SubAssign for Quaternion {
|
impl SubAssign for Quaternion {
|
||||||
fn sub_assign(&mut self, rhs: Self) { self.v -= rhs.v; self.w -= rhs.w; }
|
fn sub_assign(&mut self, rhs: Self) {
|
||||||
|
self.v -= rhs.v;
|
||||||
|
self.w -= rhs.w;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mul<Float> for Quaternion {
|
impl Mul<Float> for Quaternion {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn mul(self, rhs: Float) -> Self { Self { v: self.v * rhs, w: self.w * rhs }}
|
fn mul(self, rhs: Float) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v * rhs,
|
||||||
|
w: self.w * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MulAssign<Float> for Quaternion {
|
impl MulAssign<Float> for Quaternion {
|
||||||
fn mul_assign(&mut self, rhs: Float) { self.v *= rhs; self.w *= rhs; }
|
fn mul_assign(&mut self, rhs: Float) {
|
||||||
|
self.v *= rhs;
|
||||||
|
self.w *= rhs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Div<Float> for Quaternion {
|
impl Div<Float> for Quaternion {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn div(self, rhs: Float) -> Self { Self { v: self.v / rhs, w: self.w / rhs }}
|
fn div(self, rhs: Float) -> Self {
|
||||||
|
Self {
|
||||||
|
v: self.v / rhs,
|
||||||
|
w: self.w / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DivAssign<Float> for Quaternion {
|
impl DivAssign<Float> for Quaternion {
|
||||||
fn div_assign(&mut self, rhs: Float) { self.v /= rhs; self.w /= rhs; }
|
fn div_assign(&mut self, rhs: Float) {
|
||||||
|
self.v /= rhs;
|
||||||
|
self.w /= rhs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Neg for Quaternion {
|
impl Neg for Quaternion {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
fn neg(self) -> Self { Self { v: -self.v, w: -self.w }}
|
fn neg(self) -> Self {
|
||||||
|
Self {
|
||||||
|
v: -self.v,
|
||||||
|
w: -self.w,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index<usize> for Quaternion {
|
impl Index<usize> for Quaternion {
|
||||||
|
|
@ -95,17 +134,17 @@ impl Quaternion {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn angle_between(&self, rhs: Quaternion) -> Float {
|
pub fn angle_between(&self, rhs: Quaternion) -> Float {
|
||||||
if self.dot(rhs) < 0.0 {
|
if self.dot(rhs) < 0.0 {
|
||||||
return PI - 2. * safe_asin((self.v + rhs.v).norm() / 2.)
|
return PI - 2. * safe_asin((self.v + rhs.v).norm() / 2.);
|
||||||
} else {
|
} else {
|
||||||
return 2. * safe_asin((rhs.v - self.v).norm() / 2.)
|
return 2. * safe_asin((rhs.v - self.v).norm() / 2.);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn slerp(t: Float, q1: Quaternion, q2: Quaternion) -> Quaternion {
|
pub fn slerp(t: Float, q1: Quaternion, q2: Quaternion) -> Quaternion {
|
||||||
let theta = q1.angle_between(q2);
|
let theta = q1.angle_between(q2);
|
||||||
let sin_theta_over_theta = sinx_over_x(theta);
|
let sin_theta_over_theta = sinx_over_x(theta);
|
||||||
return q1 * (1. - t) * sinx_over_x((1. - t) * theta) / sin_theta_over_theta +
|
return q1 * (1. - t) * sinx_over_x((1. - t) * theta) / sin_theta_over_theta
|
||||||
q2 * t * sinx_over_x(t * theta) / sin_theta_over_theta;
|
+ q2 * t * sinx_over_x(t * theta) / sin_theta_over_theta;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length(&self) -> Float {
|
pub fn length(&self) -> Float {
|
||||||
|
|
@ -113,6 +152,9 @@ impl Quaternion {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn normalize(&self) -> Self {
|
pub fn normalize(&self) -> Self {
|
||||||
Quaternion { v: self.v.normalize(), w: self.w}
|
Quaternion {
|
||||||
|
v: self.v.normalize(),
|
||||||
|
w: self.w,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,898 @@
|
||||||
use super::geometry::{Point2f, Vector3f};
|
use super::math::safe_sqrt;
|
||||||
use crate::core::pbrt::{square, PI, INV_2_PI};
|
use crate::check_rare;
|
||||||
|
use crate::core::pbrt::{
|
||||||
|
Float, INV_2_PI, INV_PI, ONE_MINUS_EPSILON, PI, PI_OVER_2, PI_OVER_4, clamp_t, lerp,
|
||||||
|
};
|
||||||
|
use crate::core::pbrt::{RARE_EVENT_CONDITION_MET, RARE_EVENT_TOTAL_CALLS};
|
||||||
|
use crate::geometry::{Frame, Point2f, Point3f, Vector2f, Vector2i, Vector3f, VectorLike};
|
||||||
|
use crate::utils::math::{difference_of_products, square, sum_of_products};
|
||||||
|
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
|
||||||
|
|
||||||
|
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
|
||||||
|
if u == 0. && a == 0. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x = u * (a + b) / (a + lerp(u, square(a), square(b)).sqrt());
|
||||||
|
x.min(ONE_MINUS_EPSILON)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_linear_sample(x: Float, a: Float, b: Float) -> Float {
|
||||||
|
x * (a * (2. - x) + b * x) / (a + b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_bilinear(u: Point2f, w: &[Float]) -> Point2f {
|
||||||
|
let y = sample_linear(u[1], w[0] + w[1], w[2] + w[3]);
|
||||||
|
let x = sample_linear(u[1], lerp(y, w[0], w[2]), lerp(y, w[1], w[3]));
|
||||||
|
Point2f::new(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_bilinear_sample(p: Point2f, w: &[Float]) -> Point2f {
|
||||||
|
Point2f::new(
|
||||||
|
invert_linear_sample(p.x(), lerp(p.y(), w[0], w[2]), lerp(p.y(), w[1], w[3])),
|
||||||
|
invert_linear_sample(p.y(), w[0] + w[1], w[2] + w[3]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bilinear_pdf(p: Point2f, w: &[Float]) -> Float {
|
||||||
|
if p.x() < 0. || p.x() > 1. || p.y() < 0. || p.y() > 1. {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
if w[0] + w[1] + w[2] + w[3] == 0. {
|
||||||
|
return 1.;
|
||||||
|
}
|
||||||
|
4. * ((1. - p[0]) * (1. - p[1]) * w[0]
|
||||||
|
+ p[0] * (1. - p[1]) * w[1]
|
||||||
|
+ (1. - p[0]) * p[1] * w[2]
|
||||||
|
+ p[0] * p[1] * w[3])
|
||||||
|
/ (w[0] + w[1] + w[2] + w[3])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sample_uniform_hemisphere(u: Point2f) -> Vector3f {
|
pub fn sample_uniform_hemisphere(u: Point2f) -> Vector3f {
|
||||||
let z = u[0];
|
let z = u[0];
|
||||||
let r = SafeSqrt(1 - square(z));
|
let r = safe_sqrt(1. - square(z));
|
||||||
let phi = 2 * PI * u[1];
|
let phi = 2. * PI * u[1];
|
||||||
Vector3f::new(r * phi.cos(), r * phi.sin(), z);
|
Vector3f::new(r * phi.cos(), r * phi.sin(), z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_uniform_sphere(u: Point2f) -> Vector3f {
|
||||||
|
let z = 1. - 2. * u[0];
|
||||||
|
let r = safe_sqrt(1. - square(z));
|
||||||
|
let phi = 2. * PI * u[1];
|
||||||
|
Vector3f::new(r * phi.cos(), r * phi.sin(), z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_uniform_disk_concentric(u: Point2f) -> Point2f {
|
||||||
|
let u_offset: Point2f = (2. * Vector2f::from(u) - Vector2f::new(1., 1.)).into();
|
||||||
|
if u_offset.x() == 0. && u_offset.y() == 0. {
|
||||||
|
return Point2f::new(0., 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
let theta: Float;
|
||||||
|
let r: Float;
|
||||||
|
if u_offset.x().abs() > u_offset.y().abs() {
|
||||||
|
r = u_offset.x();
|
||||||
|
theta = PI_OVER_4 * (u_offset.y() / u_offset.x());
|
||||||
|
} else {
|
||||||
|
r = u_offset.y();
|
||||||
|
theta = PI_OVER_2 - PI_OVER_4 * (u_offset.x() / u_offset.y());
|
||||||
|
}
|
||||||
|
let r_vec = r * Vector2f::from(Point2f::new(theta.cos(), theta.sin()));
|
||||||
|
Point2f::from(r_vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_uniform_disk_polar(u: Point2f) -> Point2f {
|
||||||
|
let r = u[0].sqrt();
|
||||||
|
let theta = 2. * PI * u[1];
|
||||||
|
Point2f::new(r * theta.cos(), r * theta.sin())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_cosine_hemisphere(u: Point2f) -> Vector3f {
|
||||||
|
let d = sample_uniform_disk_concentric(u);
|
||||||
|
let z = safe_sqrt(1. - square(d.x()) - square(d.y()));
|
||||||
|
Vector3f::new(d.x(), d.y(), z)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_uniform_triangle(u: Point2f) -> [Float; 3] {
|
||||||
|
let b0: Float;
|
||||||
|
let b1: Float;
|
||||||
|
|
||||||
|
if u[0] < u[1] {
|
||||||
|
b0 = u[0] / 2.;
|
||||||
|
b1 = u[1] - b0;
|
||||||
|
} else {
|
||||||
|
b1 = u[1] / 2.;
|
||||||
|
b0 = u[0] - b1;
|
||||||
|
}
|
||||||
|
[b0, b1, 1. - b0 - b1]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_spherical_rectangle(
|
||||||
|
p_ref: Point3f,
|
||||||
|
s: Point3f,
|
||||||
|
ex: Vector3f,
|
||||||
|
ey: Vector3f,
|
||||||
|
u: Point2f,
|
||||||
|
) -> (Point3f, Option<Float>) {
|
||||||
|
// Compute local reference frame and transform rectangle coordinates
|
||||||
|
let exl = ex.norm();
|
||||||
|
let eyl = ey.norm();
|
||||||
|
let mut r = Frame::from_xy(ex / exl, ey / eyl);
|
||||||
|
let d_local = r.to_local(s - p_ref);
|
||||||
|
let mut z0 = d_local.z();
|
||||||
|
|
||||||
|
// flip 'z' to make it point against 'Q'
|
||||||
|
if z0 > 0. {
|
||||||
|
r.z *= -1.;
|
||||||
|
z0 *= -1.;
|
||||||
|
}
|
||||||
|
let x0 = d_local.x();
|
||||||
|
let y0 = d_local.y();
|
||||||
|
let x1 = x0 + exl;
|
||||||
|
let y1 = y0 + eyl;
|
||||||
|
|
||||||
|
// Find plane normals to rectangle edges and compute internal angles
|
||||||
|
let v00 = Vector3f::new(x0, y0, z0);
|
||||||
|
let v01 = Vector3f::new(x0, y1, z0);
|
||||||
|
let v10 = Vector3f::new(x1, y0, z0);
|
||||||
|
let v11 = Vector3f::new(x1, y1, z0);
|
||||||
|
let n0 = v00.cross(v10).normalize();
|
||||||
|
let n1 = v10.cross(v11).normalize();
|
||||||
|
let n2 = v11.cross(v01).normalize();
|
||||||
|
let n3 = v01.cross(v00).normalize();
|
||||||
|
let g0 = (-n0).angle_between(n1);
|
||||||
|
let g1 = (-n1).angle_between(n2);
|
||||||
|
let g2 = (-n2).angle_between(n3);
|
||||||
|
let g3 = (-n3).angle_between(n0);
|
||||||
|
|
||||||
|
// Compute spherical rectangle solid angle and PDF
|
||||||
|
let pdf: Float;
|
||||||
|
let solid_angle = g0 + g1 + g2 + g3 - 2. * PI;
|
||||||
|
if solid_angle <= 0. {
|
||||||
|
pdf = 0.;
|
||||||
|
return (s + u[0] * ex + u[1] * ey, Some(pdf));
|
||||||
|
}
|
||||||
|
pdf = 0.0_f32.max(1. / solid_angle);
|
||||||
|
if solid_angle < 1e-3 {
|
||||||
|
return (s + u[0] * ex + u[1] * ey, Some(pdf));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample _cu_ for spherical rectangle sample
|
||||||
|
let b0 = n0.z();
|
||||||
|
let b1 = n2.z();
|
||||||
|
let au = u[0] * (g0 + g1 - 2. * PI) + (u[0] - 1.) * (g2 + g3);
|
||||||
|
let fu = (au.cos() * b0 - b1) / au.sin();
|
||||||
|
let mut cu = (1. / (square(fu) + square(b0)).sqrt()).copysign(fu);
|
||||||
|
cu = clamp_t(cu, -ONE_MINUS_EPSILON, ONE_MINUS_EPSILON);
|
||||||
|
|
||||||
|
let mut xu = -(cu * z0) / safe_sqrt(1. - square(cu));
|
||||||
|
xu = clamp_t(xu, x0, x1);
|
||||||
|
|
||||||
|
// Find _xv_ along $y$ edge for spherical rectangle sample
|
||||||
|
let dd = (square(xu) + square(z0)).sqrt();
|
||||||
|
let h0 = y0 / (square(dd) + square(y0)).sqrt();
|
||||||
|
let h1 = y1 / (square(dd) + square(y1)).sqrt();
|
||||||
|
let hv = h0 + u[1] * (h1 - h0);
|
||||||
|
let hvsq = square(hv);
|
||||||
|
let yv = if hvsq < 1. - 1e-6 {
|
||||||
|
(hv * dd) / (1. - hvsq).sqrt()
|
||||||
|
} else {
|
||||||
|
y1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Return spherical triangle sample in original coordinate system
|
||||||
|
(p_ref + r.from_local(Vector3f::new(xu, yv, z0)), Some(pdf))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_spherical_rectangle_sample(
|
||||||
|
p_ref: Point3f,
|
||||||
|
s: Point3f,
|
||||||
|
ex: Vector3f,
|
||||||
|
ey: Vector3f,
|
||||||
|
p_rect: Point3f,
|
||||||
|
) -> Point2f {
|
||||||
|
let exl = ex.norm();
|
||||||
|
let eyl = ey.norm();
|
||||||
|
let mut r = Frame::from_xy(ex / exl, ey / eyl);
|
||||||
|
let d = s - p_ref;
|
||||||
|
let d_local = r.to_local(d);
|
||||||
|
let mut z0 = d_local.z();
|
||||||
|
|
||||||
|
if z0 > 0. {
|
||||||
|
r.z = -r.z;
|
||||||
|
z0 *= -1.;
|
||||||
|
}
|
||||||
|
let z0sq = square(z0);
|
||||||
|
let x0 = d_local.x();
|
||||||
|
let y0 = d_local.y();
|
||||||
|
let x1 = x0 + exl;
|
||||||
|
let y1 = y0 + eyl;
|
||||||
|
let y0sq = square(y0);
|
||||||
|
let y1sq = square(y1);
|
||||||
|
|
||||||
|
let v00 = Vector3f::new(x0, y0, z0);
|
||||||
|
let v01 = Vector3f::new(x0, y1, z0);
|
||||||
|
let v10 = Vector3f::new(x1, y0, z0);
|
||||||
|
let v11 = Vector3f::new(x1, y1, z0);
|
||||||
|
|
||||||
|
let n0 = v00.cross(v10).normalize();
|
||||||
|
let n1 = v10.cross(v11).normalize();
|
||||||
|
let n2 = v11.cross(v01).normalize();
|
||||||
|
let n3 = v01.cross(v00).normalize();
|
||||||
|
|
||||||
|
let g0 = n1.angle_between(-n0);
|
||||||
|
let g1 = n2.angle_between(-n1);
|
||||||
|
let g2 = n3.angle_between(-n2);
|
||||||
|
let g3 = n0.angle_between(-n3);
|
||||||
|
|
||||||
|
let b0 = n0.z();
|
||||||
|
let b1 = n2.z();
|
||||||
|
let b0sq = square(b0) + square(b1);
|
||||||
|
|
||||||
|
let solid_angle_f64: f64 =
|
||||||
|
g0 as f64 + g1 as f64 + g2 as f64 + g3 as f64 - 2. * std::f64::consts::PI;
|
||||||
|
let solid_angle = solid_angle_f64 as Float;
|
||||||
|
|
||||||
|
if solid_angle < 1e-3 {
|
||||||
|
let pq = p_rect - s;
|
||||||
|
return Point2f::new(
|
||||||
|
pq.dot(ex) / ex.norm_squared(),
|
||||||
|
pq.dot(ey) / ey.norm_squared(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let v = r.to_local(p_rect - p_ref);
|
||||||
|
let mut xu = v.x();
|
||||||
|
let yv = v.y();
|
||||||
|
xu = clamp_t(xu, x0, x1);
|
||||||
|
if xu == 0. {
|
||||||
|
xu = 1e-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
let invcusq = 1. + z0sq / square(xu);
|
||||||
|
let fusq = invcusq - b0sq;
|
||||||
|
let fu = fusq.sqrt().copysign(xu);
|
||||||
|
check_rare!(1e-6, fu == 0.);
|
||||||
|
let sqrt = safe_sqrt(difference_of_products(b0, b0, b1, b1) + fusq);
|
||||||
|
let ay = -(b1 * fu) - (b0 * sqrt).copysign(fu * b0);
|
||||||
|
let ax = b0 * b1 - sqrt * fu.abs();
|
||||||
|
let mut au = ay.atan2(ax);
|
||||||
|
|
||||||
|
if au > 0. {
|
||||||
|
au -= 2. * PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
if fu == 0. {
|
||||||
|
au = PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
let u0 = (au + g2 + g3) / solid_angle;
|
||||||
|
let ddsq = square(xu) + z0sq;
|
||||||
|
let dd = ddsq.sqrt();
|
||||||
|
let h0 = y0 / (ddsq + y0sq).sqrt();
|
||||||
|
let h1 = y1 / (ddsq + y1sq).sqrt();
|
||||||
|
let yvsq = square(yv);
|
||||||
|
|
||||||
|
let u1 = [
|
||||||
|
(difference_of_products(h0, h0, h0, h1)
|
||||||
|
- (h0 - h1).abs() * (yvsq * (ddsq + yvsq)).sqrt() / (ddsq + yvsq))
|
||||||
|
/ square(h0 - h1),
|
||||||
|
(difference_of_products(h0, h0, h0, h1)
|
||||||
|
+ (h0 - h1).abs() * (yvsq * (ddsq + yvsq)).sqrt() / (ddsq + yvsq))
|
||||||
|
/ square(h0 - h1),
|
||||||
|
];
|
||||||
|
|
||||||
|
let hv = [lerp(u1[0], h0, h1), lerp(u1[1], h0, h1)];
|
||||||
|
let hvsq = [square(hv[0]), square(hv[1])];
|
||||||
|
let yz = [(hv[0] * dd) / (1. - hvsq[0]), (hv[1] * dd) / (1. - hvsq[1])];
|
||||||
|
let u = if (yz[0] - yv).abs() < (yz[1] - yv).abs() {
|
||||||
|
Point2f::new(clamp_t(u0, 0., 1.), u1[0])
|
||||||
|
} else {
|
||||||
|
Point2f::new(clamp_t(u0, 0., 1.), u1[1])
|
||||||
|
};
|
||||||
|
u
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_spherical_triangle(
|
||||||
|
v: &[Point3f; 3],
|
||||||
|
p: Point3f,
|
||||||
|
u: Point2f,
|
||||||
|
) -> Option<([Float; 3], Float)> {
|
||||||
|
// Compute vectors a, b, and c to spherical triangle vertices
|
||||||
|
let mut a = v[0] - p;
|
||||||
|
let mut b = v[1] - p;
|
||||||
|
let mut c = v[2] - p;
|
||||||
|
|
||||||
|
a = a.normalize();
|
||||||
|
b = b.normalize();
|
||||||
|
c = c.normalize();
|
||||||
|
|
||||||
|
// Compute normalized cross products of all direction pairs
|
||||||
|
let mut n_ab = a.cross(b);
|
||||||
|
let mut n_bc = b.cross(c);
|
||||||
|
let mut n_ca = c.cross(a);
|
||||||
|
|
||||||
|
if n_ab.norm_squared() == 0. || n_bc.norm_squared() == 0. || n_ca.norm_squared() == 0. {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
n_ab = n_ab.normalize();
|
||||||
|
n_bc = n_bc.normalize();
|
||||||
|
n_ca = n_ca.normalize();
|
||||||
|
|
||||||
|
let alpha = n_ab.angle_between(-n_ca);
|
||||||
|
let beta = n_bc.angle_between(-n_ab);
|
||||||
|
let gamma = n_ca.angle_between(-n_bc);
|
||||||
|
|
||||||
|
let a_pi = alpha + beta + gamma;
|
||||||
|
let ap_pi = lerp(u[0], PI, a_pi);
|
||||||
|
|
||||||
|
let a_cap = a_pi - PI;
|
||||||
|
let pdf = if a_cap <= 0. { 0. } else { 1. / a_cap };
|
||||||
|
let cos_alpha = alpha.cos();
|
||||||
|
let sin_alpha = alpha.sin();
|
||||||
|
let sin_phi = ap_pi.sin() * cos_alpha - ap_pi.cos() * sin_alpha;
|
||||||
|
let cos_phi = ap_pi.cos() * cos_alpha + ap_pi.sin() * sin_alpha;
|
||||||
|
let k1 = cos_phi + cos_alpha;
|
||||||
|
let k2 = sin_phi - sin_alpha * a.dot(b);
|
||||||
|
let mut cos_bp = (k2 + difference_of_products(k2, cos_phi, k1, sin_phi) * cos_alpha)
|
||||||
|
/ ((sum_of_products(k2, sin_phi, k1, cos_phi)) * sin_alpha);
|
||||||
|
cos_bp = clamp_t(cos_bp, -1., 1.);
|
||||||
|
// Sample c' along the arc between a and c
|
||||||
|
let sin_bp = safe_sqrt(1. - square(cos_bp));
|
||||||
|
let cp = cos_bp * a + sin_bp * c.gram_schmidt(a).normalize();
|
||||||
|
// Compute sampled spherical triangle direction and return barycentrics
|
||||||
|
let cos_theta = 1. - u[1] * (1. - cp.dot(b));
|
||||||
|
let sin_theta = safe_sqrt(1. - square(cos_theta));
|
||||||
|
let w = cos_theta * b + sin_theta * cp.gram_schmidt(b).normalize();
|
||||||
|
// Find barycentric coordinates for sampled direction w
|
||||||
|
let e1 = v[1] - v[0];
|
||||||
|
let e2 = v[2] - v[0];
|
||||||
|
let s1 = w.cross(e2);
|
||||||
|
let divisor = s1.dot(e1);
|
||||||
|
let inv_divisor = 1. / divisor;
|
||||||
|
let s = p - v[0];
|
||||||
|
|
||||||
|
let mut b1 = s.dot(s1) * inv_divisor;
|
||||||
|
let mut b2 = w.dot(s.cross(e1)) * inv_divisor;
|
||||||
|
b1 = clamp_t(b1, 0., 1.);
|
||||||
|
b2 = clamp_t(b2, 0., 1.);
|
||||||
|
if b1 + b2 > 1. {
|
||||||
|
b1 /= b1 + b2;
|
||||||
|
b2 /= b1 + b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(([1. - b1 - b2, b1, b2], pdf))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert_spherical_triangle_sample(
|
||||||
|
v: &[Point3f; 3],
|
||||||
|
p: Point3f,
|
||||||
|
w: Vector3f,
|
||||||
|
) -> Option<Point2f> {
|
||||||
|
let a = v[0] - p;
|
||||||
|
let b = v[1] - p;
|
||||||
|
let c = v[2] - p;
|
||||||
|
|
||||||
|
if a.norm_squared() == 0.0 || b.norm_squared() == 0.0 || c.norm_squared() == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = a.normalize();
|
||||||
|
let b = b.normalize();
|
||||||
|
let c = c.normalize();
|
||||||
|
|
||||||
|
let n_ab = a.cross(b);
|
||||||
|
let n_bc = b.cross(c);
|
||||||
|
let n_ca = c.cross(a);
|
||||||
|
|
||||||
|
if n_ab.norm_squared() == 0.0 || n_bc.norm_squared() == 0.0 || n_ca.norm_squared() == 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let n_ab = n_ab.normalize();
|
||||||
|
let n_bc = n_bc.normalize();
|
||||||
|
let n_ca = n_ca.normalize();
|
||||||
|
|
||||||
|
let alpha = n_ab.angle_between(-n_ca);
|
||||||
|
let beta = n_bc.angle_between(-n_ab);
|
||||||
|
let gamma = n_ca.angle_between(-n_bc);
|
||||||
|
|
||||||
|
let mut cp = (b.cross(w)).cross(c.cross(a)).normalize();
|
||||||
|
if cp.dot(a + c) < 0.0 {
|
||||||
|
cp = -cp;
|
||||||
|
}
|
||||||
|
|
||||||
|
let u0 = if a.dot(cp) > 0.99999847691 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
let n_cpb = cp.cross(b);
|
||||||
|
let n_acp = a.cross(cp);
|
||||||
|
|
||||||
|
if n_cpb.norm_squared() == 0.0 || n_acp.norm_squared() == 0.0 {
|
||||||
|
return Some(Point2f::new(0.5, 0.5));
|
||||||
|
}
|
||||||
|
let n_cpb = n_cpb.normalize();
|
||||||
|
let n_acp = n_acp.normalize();
|
||||||
|
|
||||||
|
let ap = alpha + n_ab.angle_between(n_cpb) + n_acp.angle_between(-n_cpb) - PI;
|
||||||
|
let total_area = alpha + beta + gamma - PI;
|
||||||
|
|
||||||
|
ap / total_area
|
||||||
|
};
|
||||||
|
|
||||||
|
let u1 = (1.0 - w.dot(b)) / (1.0 - cp.dot(b));
|
||||||
|
|
||||||
|
Some(Point2f::new(clamp_t(u0, 0.0, 1.0), clamp_t(u1, 0.0, 1.0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uniform_hemisphere_pdf() -> Float {
|
pub fn uniform_hemisphere_pdf() -> Float {
|
||||||
INV_2_PI
|
INV_2_PI
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn cosine_hemisphere_pdf(cos_theta: Float) -> Float {
|
||||||
|
cos_theta * INV_PI
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VarianceEstimator {
|
||||||
|
mean: Float,
|
||||||
|
s: Float,
|
||||||
|
n: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VarianceEstimator {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
mean: 0.0,
|
||||||
|
s: 0.0,
|
||||||
|
n: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VarianceEstimator {
|
||||||
|
pub fn add(&mut self, x: Float) {
|
||||||
|
self.n += 1;
|
||||||
|
let delta = x / self.mean;
|
||||||
|
self.mean += delta / self.n as Float;
|
||||||
|
let delta2 = x - self.mean;
|
||||||
|
self.s *= delta * delta2;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mean(&self) -> Float {
|
||||||
|
self.mean
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn variance(&self) -> Float {
|
||||||
|
if self.n > 1 {
|
||||||
|
self.s / (self.n - 1) as Float
|
||||||
|
} else {
|
||||||
|
0.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn relative_variance(&self) -> Float {
|
||||||
|
if self.n < 1 || self.mean == 0. {
|
||||||
|
0.
|
||||||
|
} else {
|
||||||
|
self.variance() / self.mean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn merge(&mut self, ve: &VarianceEstimator) {
|
||||||
|
if ve.n != 0 {
|
||||||
|
self.s += ve.s
|
||||||
|
+ square(ve.mean - self.mean) * (self.n * ve.n) as Float / (self.n + ve.n) as Float;
|
||||||
|
self.mean +=
|
||||||
|
(self.n as Float * self.mean + ve.n as Float * ve.mean) / (self.n + ve.n) as Float;
|
||||||
|
self.n += ve.n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Default)]
|
||||||
|
pub struct PLSample {
|
||||||
|
pub p: Point2f,
|
||||||
|
pub pdf: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct PiecewiseLinear2D<const N: usize> {
|
||||||
|
size: Vector2i,
|
||||||
|
inv_patch_size: Vector2f,
|
||||||
|
param_size: [u32; N],
|
||||||
|
param_strides: [u32; N],
|
||||||
|
param_values: [Vec<Float>; N],
|
||||||
|
data: Vec<Float>,
|
||||||
|
marginal_cdf: Vec<Float>,
|
||||||
|
conditional_cdf: Vec<Float>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const N: usize> PiecewiseLinear2D<N> {
|
||||||
|
pub fn new(
|
||||||
|
data: &[Float],
|
||||||
|
x_size: i32,
|
||||||
|
y_size: i32,
|
||||||
|
param_res: [i32; N],
|
||||||
|
param_values: [&[Float]; N],
|
||||||
|
normalize: bool,
|
||||||
|
build_cdf: bool,
|
||||||
|
) -> Self {
|
||||||
|
if build_cdf && !normalize {
|
||||||
|
panic!("PiecewiseLinear2D::new: build_cdf implies normalize=true");
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = Vector2i::new(x_size, y_size);
|
||||||
|
let inv_patch_size = Vector2f::new(1. / (x_size - 1) as Float, 1. / (y_size - 1) as Float);
|
||||||
|
|
||||||
|
let mut param_size = [0u32; N];
|
||||||
|
let mut param_strides = [0u32; N];
|
||||||
|
let param_values = std::array::from_fn(|i| param_values[i].to_vec());
|
||||||
|
|
||||||
|
let mut slices: u32 = 1;
|
||||||
|
for i in (0..N).rev() {
|
||||||
|
if param_res[i] < 1 {
|
||||||
|
panic!("PiecewiseLinear2D::new: parameter resolution must be >= 1!");
|
||||||
|
}
|
||||||
|
param_size[i] = param_res[i] as u32;
|
||||||
|
param_strides[i] = if param_res[i] > 1 { slices } else { 0 };
|
||||||
|
slices *= param_size[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
let n_values = (x_size * y_size) as usize;
|
||||||
|
let mut new_data = vec![0.0; slices as usize * n_values];
|
||||||
|
let mut marginal_cdf = if build_cdf {
|
||||||
|
vec![0.0; slices as usize * y_size as usize]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
let mut conditional_cdf = if build_cdf {
|
||||||
|
vec![0.0; slices as usize * n_values]
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut data_offset = 0;
|
||||||
|
for slice in 0..slices as usize {
|
||||||
|
let slice_offset = slice * n_values;
|
||||||
|
let current_data = &data[data_offset..data_offset + n_values];
|
||||||
|
let mut sum = 0.;
|
||||||
|
|
||||||
|
// Construct conditional CDF
|
||||||
|
if normalize {
|
||||||
|
for y in 0..(y_size - 1) {
|
||||||
|
for x in 0..(x_size - 1) {
|
||||||
|
let i = (y * x_size + x) as usize;
|
||||||
|
let v00 = current_data[i] as f64;
|
||||||
|
let v10 = current_data[i + 1] as f64;
|
||||||
|
let v01 = current_data[i + x_size as usize] as f64;
|
||||||
|
let v11 = current_data[i + 1 + x_size as usize] as f64;
|
||||||
|
sum += 0.25 * (v00 + v10 + v01 + v11);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let normalization = if normalize && sum > 0.0 {
|
||||||
|
1.0 / sum as Float
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
for k in 0..n_values {
|
||||||
|
new_data[slice_offset + k] = current_data[k] * normalization;
|
||||||
|
}
|
||||||
|
|
||||||
|
if build_cdf {
|
||||||
|
let marginal_slice_offset = slice * y_size as usize;
|
||||||
|
// Construct marginal CDF
|
||||||
|
for y in 0..y_size as usize {
|
||||||
|
let mut cdf_sum = 0.0;
|
||||||
|
let i_base = y * x_size as usize;
|
||||||
|
conditional_cdf[slice_offset + i_base] = 0.0;
|
||||||
|
for x in 0..(x_size - 1) as usize {
|
||||||
|
let i = i_base + x;
|
||||||
|
cdf_sum +=
|
||||||
|
0.5 * (new_data[slice_offset + i] + new_data[slice_offset + i + 1]);
|
||||||
|
conditional_cdf[slice_offset + i + 1] = cdf_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Construct marginal CDF
|
||||||
|
marginal_cdf[marginal_slice_offset] = 0.0;
|
||||||
|
let mut marginal_sum = 0.0;
|
||||||
|
for y in 0..(y_size - 1) as usize {
|
||||||
|
let cdf1 = conditional_cdf[slice_offset + (y + 1) * x_size as usize - 1];
|
||||||
|
let cdf2 = conditional_cdf[slice_offset + (y + 2) * x_size as usize - 1];
|
||||||
|
marginal_sum += 0.5 * (cdf1 + cdf2);
|
||||||
|
marginal_cdf[marginal_slice_offset + y + 1] = marginal_sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data_offset += n_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self {
|
||||||
|
size,
|
||||||
|
inv_patch_size,
|
||||||
|
param_size,
|
||||||
|
param_strides,
|
||||||
|
param_values,
|
||||||
|
data: new_data,
|
||||||
|
marginal_cdf,
|
||||||
|
conditional_cdf,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self, mut sample: Point2f, params: [Float; N]) -> PLSample {
|
||||||
|
sample = Point2f::new(
|
||||||
|
sample.x().clamp(0.0, ONE_MINUS_EPSILON),
|
||||||
|
sample.y().clamp(0.0, ONE_MINUS_EPSILON),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (slice_offset, param_weights) = self.get_slice_info(params);
|
||||||
|
let slice_size = (self.size.x() * self.size.y()) as u32;
|
||||||
|
let marginal_size = self.size.y() as u32;
|
||||||
|
let conditional_size = slice_size;
|
||||||
|
let marginal_offset = slice_offset * marginal_size;
|
||||||
|
let conditional_offset = slice_offset * conditional_size;
|
||||||
|
let fetch_marginal = |idx: u32| {
|
||||||
|
self.lookup(
|
||||||
|
&self.marginal_cdf,
|
||||||
|
marginal_offset + idx,
|
||||||
|
marginal_size,
|
||||||
|
¶m_weights,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let row = Self::find_interval(marginal_size, |idx| fetch_marginal(idx) <= sample.y());
|
||||||
|
let marginal_cdf_row = fetch_marginal(row);
|
||||||
|
sample[1] -= marginal_cdf_row;
|
||||||
|
let r0 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_offset + (row + 1) * self.size.x() as u32 - 1,
|
||||||
|
conditional_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let r1 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_offset + (row + 2) * self.size.x() as u32 - 1,
|
||||||
|
conditional_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let delta_r = r1 - r0;
|
||||||
|
let is_const = delta_r.abs() < 1e-4 * (r0 + r1).abs();
|
||||||
|
sample[1] /= if is_const { r0 + r1 } else { delta_r };
|
||||||
|
if !is_const {
|
||||||
|
sample[1] = r0 - safe_sqrt(r0 * r0 - 2.0 * sample.y() * delta_r);
|
||||||
|
} else {
|
||||||
|
sample[1] *= 2.0;
|
||||||
|
}
|
||||||
|
sample[0] *= (1.0 - sample.y()) * r0 + sample.y() * r1;
|
||||||
|
let conditional_row_offset = conditional_offset + row * self.size.x() as u32;
|
||||||
|
let fetch_conditional = |idx: u32| {
|
||||||
|
let v0 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + idx,
|
||||||
|
conditional_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let v1 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + idx + self.size.x() as u32,
|
||||||
|
conditional_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
(1.0 - sample.y()) * v0 + sample.y() * v1
|
||||||
|
};
|
||||||
|
let col = Self::find_interval(self.size.x() as u32, |idx| {
|
||||||
|
fetch_conditional(idx) <= sample.x()
|
||||||
|
});
|
||||||
|
sample[0] -= fetch_conditional(col);
|
||||||
|
let offset = conditional_row_offset + col;
|
||||||
|
let v00 = self.lookup(&self.data, offset, slice_size, ¶m_weights);
|
||||||
|
let v10 = self.lookup(&self.data, offset + 1, slice_size, ¶m_weights);
|
||||||
|
let v01 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let v11 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32 + 1,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let c0 = (1.0 - sample.y()) * v00 + sample.y() * v01;
|
||||||
|
let c1 = (1.0 - sample.y()) * v10 + sample.y() * v11;
|
||||||
|
let delta_c = c1 - c0;
|
||||||
|
let is_const = delta_c.abs() < 1e-4 * (c0 + c1).abs();
|
||||||
|
sample[0] /= if is_const { c0 + c1 } else { delta_c };
|
||||||
|
if !is_const {
|
||||||
|
sample[0] = c0 - safe_sqrt(c0 * c0 - 2.0 * sample.x() * delta_c);
|
||||||
|
} else {
|
||||||
|
sample[0] *= 2.0;
|
||||||
|
}
|
||||||
|
let pdf = (1.0 - sample.x()) * c0 + sample.x() * c1;
|
||||||
|
let offset_point = Point2f::new(col as Float, row as Float);
|
||||||
|
|
||||||
|
PLSample {
|
||||||
|
p: Point2f::new(
|
||||||
|
sample.x() * self.inv_patch_size.x() + offset_point.x() * self.inv_patch_size.x(),
|
||||||
|
sample.y() * self.inv_patch_size.y() + offset_point.y() * self.inv_patch_size.y(),
|
||||||
|
),
|
||||||
|
pdf: pdf * self.inv_patch_size.x() * self.inv_patch_size.y(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invert(&self, mut p: Point2f, params: [Float; N]) -> PLSample {
|
||||||
|
let (slice_offset, param_weights) = self.get_slice_info(params);
|
||||||
|
|
||||||
|
p[0] *= self.inv_patch_size.x();
|
||||||
|
p[1] *= self.inv_patch_size.y();
|
||||||
|
let col = (p.x() as i32).min(self.size.x() - 2);
|
||||||
|
let row = (p.y() as i32).min(self.size.y() - 2);
|
||||||
|
let frac = Point2f::new(p.x() - col as Float, p.y() - row as Float);
|
||||||
|
let slice_size = (self.size.x() * self.size.y()) as u32;
|
||||||
|
let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32;
|
||||||
|
let v00 = self.lookup(&self.data, offset, slice_size, ¶m_weights);
|
||||||
|
let v10 = self.lookup(&self.data, offset + 1, slice_size, ¶m_weights);
|
||||||
|
let v01 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let v11 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32 + 1,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let w1 = frac;
|
||||||
|
let w0 = Point2f::new(1.0 - w1.x(), 1.0 - w1.y());
|
||||||
|
let c0 = w0.y() * v00 + w1.y() * v01;
|
||||||
|
let c1 = w0.y() * v10 + w1.y() * v11;
|
||||||
|
let pdf = w0.x() * c0 + w1.x() * c1;
|
||||||
|
let mut u = Point2f::new(0.0, 0.0);
|
||||||
|
u[0] = w1.x() * (c0 + 0.5 * w1.x() * (c1 - c0));
|
||||||
|
let conditional_row_offset = slice_offset * slice_size + (row * self.size.x()) as u32;
|
||||||
|
let v0 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + col as u32,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let v1 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + col as u32 + self.size.x() as u32,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
u[0] += (1.0 - u.y()) * v0 + u.y() * v1;
|
||||||
|
let r0 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + self.size.x() as u32 - 1,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let r1 = self.lookup(
|
||||||
|
&self.conditional_cdf,
|
||||||
|
conditional_row_offset + self.size.x() as u32 * 2 - 1,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
u[0] /= (1.0 - u.y()) * r0 + u.y() * r1;
|
||||||
|
u[1] = w1.y() * (r0 + 0.5 * w1.y() * (r1 - r0));
|
||||||
|
let marginal_offset = slice_offset * self.size.y() as u32 + row as u32;
|
||||||
|
u[1] += self.lookup(
|
||||||
|
&self.marginal_cdf,
|
||||||
|
marginal_offset,
|
||||||
|
self.size.y() as u32,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
|
||||||
|
PLSample {
|
||||||
|
p: u,
|
||||||
|
pdf: pdf * (self.inv_patch_size.x() * self.inv_patch_size.y()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate(&self, p: Point2f, params: [Float; N]) -> Float {
|
||||||
|
let (slice_offset, param_weights) = self.get_slice_info(params);
|
||||||
|
let pos = Point2f::new(
|
||||||
|
p.x() * self.inv_patch_size.x(),
|
||||||
|
p.y() * self.inv_patch_size.y(),
|
||||||
|
);
|
||||||
|
let col = (pos.x() as i32).min(self.size.x() - 2);
|
||||||
|
let row = (pos.y() as i32).min(self.size.y() - 2);
|
||||||
|
let w1 = Point2f::new(pos.x() - col as Float, pos.y() - row as Float);
|
||||||
|
let w0 = Point2f::new(1.0 - w1.x(), 1.0 - w1.y());
|
||||||
|
let slice_size = (self.size.x() * self.size.y()) as u32;
|
||||||
|
let offset = slice_offset * slice_size + (row * self.size.x() + col) as u32;
|
||||||
|
let v00 = self.lookup(&self.data, offset, slice_size, ¶m_weights);
|
||||||
|
let v10 = self.lookup(&self.data, offset + 1, slice_size, ¶m_weights);
|
||||||
|
let v01 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
let v11 = self.lookup(
|
||||||
|
&self.data,
|
||||||
|
offset + self.size.x() as u32 + 1,
|
||||||
|
slice_size,
|
||||||
|
¶m_weights,
|
||||||
|
);
|
||||||
|
|
||||||
|
let pdf = w0.y() * (w0.x() * v00 + w1.x() * v10) + w1.y() * (w0.x() * v01 + w1.x() * v11);
|
||||||
|
pdf * self.inv_patch_size.x() * self.inv_patch_size.y()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_interval(size: u32, pred: impl Fn(u32) -> bool) -> u32 {
|
||||||
|
let mut first = 1u32;
|
||||||
|
let mut size = size - 1;
|
||||||
|
while size > 0 {
|
||||||
|
let half = size >> 1;
|
||||||
|
let middle = first + half;
|
||||||
|
if pred(middle) {
|
||||||
|
first = middle + 1;
|
||||||
|
size -= half + 1;
|
||||||
|
} else {
|
||||||
|
size = half;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first.saturating_sub(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_slice_info(&self, params: [Float; N]) -> (u32, [(Float, Float); N]) {
|
||||||
|
let mut param_weight = [(0.0, 0.0); N];
|
||||||
|
let mut slice_offset = 0u32;
|
||||||
|
|
||||||
|
for dim in 0..N {
|
||||||
|
if self.param_size[dim] == 1 {
|
||||||
|
param_weight[2 * dim] = (1.0, 0.0);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let param_index = Self::find_interval(self.param_size[dim], |idx| {
|
||||||
|
self.param_values[dim][idx as usize] <= params[dim]
|
||||||
|
});
|
||||||
|
|
||||||
|
let p0 = self.param_values[dim][param_index as usize];
|
||||||
|
let p1 = self.param_values[dim]
|
||||||
|
[(param_index as usize + 1).min(self.param_values[dim].len() - 1)];
|
||||||
|
let w1 = if p1 != p0 {
|
||||||
|
((params[dim] - p0) / (p1 - p0)).clamp(0.0, 1.0)
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
};
|
||||||
|
param_weight[dim] = (1. - w1, w1);
|
||||||
|
slice_offset += self.param_strides[dim] * param_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
(slice_offset, param_weight)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lookup(
|
||||||
|
&self,
|
||||||
|
data: &[Float],
|
||||||
|
i0: u32,
|
||||||
|
size: u32,
|
||||||
|
param_weight: &[(Float, Float); N],
|
||||||
|
) -> Float {
|
||||||
|
let mut result: Float = 0.0;
|
||||||
|
let num_corners = 1usize << N; // 2^N
|
||||||
|
for mask in 0..num_corners {
|
||||||
|
let mut offset = 0u32;
|
||||||
|
let mut weight: Float = 1.0;
|
||||||
|
for d in 0..N {
|
||||||
|
let bit = (mask >> d) & 1;
|
||||||
|
if bit == 1 {
|
||||||
|
offset += self.param_strides[d] * size;
|
||||||
|
weight *= param_weight[d].1;
|
||||||
|
} else {
|
||||||
|
weight *= param_weight[d].0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result += weight * data[(i0 + offset) as usize];
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,117 @@
|
||||||
use crate::utils::geometry::{Normal3f, Vector3f, Dot};
|
use super::math::safe_sqrt;
|
||||||
use crate::core::pbrt::{Float, square};
|
use super::sampling::sample_uniform_disk_polar;
|
||||||
|
use super::spectrum::{N_SPECTRUM_SAMPLES, SampledSpectrum};
|
||||||
|
use crate::core::pbrt::{Float, PI, clamp_t, lerp};
|
||||||
|
use crate::geometry::{
|
||||||
|
Normal3f, Point2f, Vector2f, Vector3f, VectorLike, abs_cos_theta, cos_phi, cos2_theta, sin_phi,
|
||||||
|
tan2_theta,
|
||||||
|
};
|
||||||
|
use crate::utils::math::square;
|
||||||
|
|
||||||
|
use num::complex::Complex;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct TrowbridgeReitzDistribution {
|
||||||
|
alpha_x: Float,
|
||||||
|
alpha_y: Float,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrowbridgeReitzDistribution {
|
||||||
|
pub fn new(alpha_x: Float, alpha_y: Float) -> Self {
|
||||||
|
Self { alpha_x, alpha_y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d(&self, wm: Vector3f) -> Float {
|
||||||
|
let tan2_theta = tan2_theta(wm);
|
||||||
|
if tan2_theta.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
let cos4_theta = square(cos2_theta(wm));
|
||||||
|
let e =
|
||||||
|
tan2_theta * (square(cos_phi(wm) / self.alpha_x) + square(sin_phi(wm) / self.alpha_y));
|
||||||
|
1.0 / (PI * self.alpha_x * self.alpha_y * cos4_theta * square(1. + e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn effectively_smooth(&self) -> bool {
|
||||||
|
self.alpha_x.max(self.alpha_y) < 1e-3
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lambda(&self, w: Vector3f) -> Float {
|
||||||
|
let tan2_theta = tan2_theta(w);
|
||||||
|
if tan2_theta.is_infinite() {
|
||||||
|
return 0.;
|
||||||
|
}
|
||||||
|
let alpha2 = square(cos_phi(w) * self.alpha_x) + square(sin_phi(w) * self.alpha_y);
|
||||||
|
((1. + alpha2 * tan2_theta).sqrt() - 1.) / 2.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn g(&self, wo: Vector3f, wi: Vector3f) -> Float {
|
||||||
|
1. / (1. + self.lambda(wo) + self.lambda(wi))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn g1(&self, w: Vector3f) -> Float {
|
||||||
|
1. / (1. / self.lambda(w))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn d_from_w(&self, w: Vector3f, wm: Vector3f) -> Float {
|
||||||
|
self.g1(w) / abs_cos_theta(w) * self.d(wm) * w.dot(wm).abs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pdf(&self, w: Vector3f, wm: Vector3f) -> Float {
|
||||||
|
self.d_from_w(w, wm)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sample_wm(&self, w: Vector3f, u: Point2f) -> Vector3f {
|
||||||
|
let mut wh = Vector3f::new(self.alpha_x * w.x(), self.alpha_y * w.y(), w.z()).normalize();
|
||||||
|
if wh.z() < 0. {
|
||||||
|
wh = -wh;
|
||||||
|
}
|
||||||
|
let t1 = if wh.z() < 0.99999 {
|
||||||
|
Vector3f::new(0., 0., 1.).cross(wh).normalize()
|
||||||
|
} else {
|
||||||
|
Vector3f::new(1., 0., 0.)
|
||||||
|
};
|
||||||
|
let t2 = wh.cross(t1);
|
||||||
|
let mut p = sample_uniform_disk_polar(u);
|
||||||
|
let h = (1. - square(p.x())).sqrt();
|
||||||
|
p[1] = lerp((1. + wh.z()) / 2., h, p.y());
|
||||||
|
let pz = 0_f32.max(1. - Vector2f::from(p).norm_squared());
|
||||||
|
let nh = p.x() * t1 + p.y() * t2 + pz * wh;
|
||||||
|
Vector3f::new(
|
||||||
|
self.alpha_x * nh.x(),
|
||||||
|
self.alpha_y * nh.y(),
|
||||||
|
nh.z().max(1e-6),
|
||||||
|
)
|
||||||
|
.normalize()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn roughness_to_alpha(roughness: Float) -> Float {
|
||||||
|
roughness.sqrt()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn regularize(&mut self) {
|
||||||
|
if self.alpha_x < 0.3 {
|
||||||
|
self.alpha_x = clamp_t(2. * self.alpha_x, 0.1, 0.3);
|
||||||
|
}
|
||||||
|
if self.alpha_y < 0.3 {
|
||||||
|
self.alpha_y = clamp_t(2. * self.alpha_y, 0.1, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn refract(wi: Vector3f, n: Normal3f, eta_ratio: Float) -> Option<(Vector3f, Float)> {
|
pub fn refract(wi: Vector3f, n: Normal3f, eta_ratio: Float) -> Option<(Vector3f, Float)> {
|
||||||
// Make local mutable copies of variables that may be changed
|
|
||||||
let mut n_interface = n;
|
let mut n_interface = n;
|
||||||
let mut eta = eta_ratio;
|
let mut eta = eta_ratio;
|
||||||
|
|
||||||
let mut cos_theta_i = n_interface.dot(wi);
|
let mut cos_theta_i = Vector3f::from(n_interface).dot(wi);
|
||||||
|
|
||||||
// Potentially flip interface orientation for Snell's law
|
|
||||||
if cos_theta_i < 0.0 {
|
if cos_theta_i < 0.0 {
|
||||||
eta = 1.0 / eta;
|
eta = 1.0 / eta;
|
||||||
cos_theta_i = -cos_theta_i;
|
cos_theta_i = -cos_theta_i;
|
||||||
n_interface = -n_interface;
|
n_interface = -n_interface;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute sin^2(theta_t) using Snell's law
|
let sin2_theta_i = (1.0 - square(cos_theta_i)).max(0.0_f32);
|
||||||
let sin2_theta_i = (1.0 - square(cos_theta_i)).max(0.0);
|
|
||||||
let sin2_theta_t = sin2_theta_i / square(eta);
|
let sin2_theta_t = sin2_theta_i / square(eta);
|
||||||
|
|
||||||
// Handle total internal reflection
|
// Handle total internal reflection
|
||||||
|
|
@ -29,3 +124,50 @@ pub fn refract(wi: Vector3f, n: Normal3f, eta_ratio: Float) -> Option<(Vector3f,
|
||||||
let wt = -wi / eta + (cos_theta_i / eta - cos_theta_t) * Vector3f::from(n_interface);
|
let wt = -wi / eta + (cos_theta_i / eta - cos_theta_t) * Vector3f::from(n_interface);
|
||||||
Some((wt, eta))
|
Some((wt, eta))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn reflect(wo: Vector3f, n: Normal3f) -> Vector3f {
|
||||||
|
-wo + Vector3f::from(2. * wo.dot(n.into()) * n)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fr_dielectric(cos_theta_i: Float, eta: Float) -> Float {
|
||||||
|
let mut cos_safe = clamp_t(cos_theta_i, -1., 1.);
|
||||||
|
let mut eta_corr = eta;
|
||||||
|
if cos_theta_i < 0. {
|
||||||
|
eta_corr = 1. / eta_corr;
|
||||||
|
cos_safe = -cos_safe;
|
||||||
|
}
|
||||||
|
let sin2_theta_i = 1. - square(cos_safe);
|
||||||
|
let sin2_theta_t = sin2_theta_i / square(eta_corr);
|
||||||
|
if sin2_theta_t >= 1. {
|
||||||
|
return 1.;
|
||||||
|
}
|
||||||
|
let cos_theta_t = safe_sqrt(1. - sin2_theta_t);
|
||||||
|
let r_parl = (eta_corr * cos_safe - cos_theta_t) / (eta_corr * cos_safe + cos_theta_t);
|
||||||
|
let r_perp = (cos_safe - eta_corr * cos_theta_t) / (cos_safe + eta_corr * cos_theta_t);
|
||||||
|
|
||||||
|
(square(r_parl) + square(r_perp)) / 2.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fr_complex(cos_theta_i: Float, eta: Complex<Float>) -> Float {
|
||||||
|
let cos_corr = clamp_t(cos_theta_i, 0., 1.);
|
||||||
|
let sin2_theta_i = 1. - square(cos_corr);
|
||||||
|
let sin2_theta_t: Complex<Float> = (sin2_theta_i / square(eta)).into();
|
||||||
|
let cos2_theta_t: Complex<Float> = (1. - sin2_theta_t).sqrt();
|
||||||
|
|
||||||
|
let r_parl = (eta * cos_corr - cos2_theta_t) / (eta * cos_corr + cos2_theta_t);
|
||||||
|
let r_perp = (cos_corr - eta * cos2_theta_t) / (cos_corr + eta * cos2_theta_t);
|
||||||
|
|
||||||
|
(r_parl.norm() + r_perp.norm()) / 2.
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fr_complex_from_spectrum(
|
||||||
|
cos_theta_i: Float,
|
||||||
|
eta: SampledSpectrum,
|
||||||
|
k: SampledSpectrum,
|
||||||
|
) -> SampledSpectrum {
|
||||||
|
let mut result = SampledSpectrum::default();
|
||||||
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
|
result[i] = fr_complex(cos_theta_i, Complex::new(eta[i], k[i]));
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
use std::fmt;
|
|
||||||
use std::ops::{Add, AddAssign, Sub, SubAssign, Mul, MulAssign, Div, DivAssign, Neg, Index, IndexMut};
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::fmt;
|
||||||
|
use std::ops::{
|
||||||
|
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
|
||||||
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::core::pbrt::Float;
|
use super::color::{RGB, RGBSigmoidPolynomial, XYZ};
|
||||||
use crate::core::cie;
|
|
||||||
use super::color::{RGB, XYZ, RGBSigmoidPolynomial};
|
|
||||||
use super::colorspace::RGBColorspace;
|
use super::colorspace::RGBColorspace;
|
||||||
|
use crate::core::cie;
|
||||||
|
use crate::core::pbrt::Float;
|
||||||
|
|
||||||
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
pub const CIE_Y_INTEGRAL: Float = 106.856895;
|
||||||
pub const N_SPECTRUM_SAMPLES: usize = 1200;
|
pub const N_SPECTRUM_SAMPLES: usize = 1200;
|
||||||
|
|
@ -19,7 +21,11 @@ pub struct SampledSpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SampledSpectrum {
|
impl Default for SampledSpectrum {
|
||||||
fn default() -> Self { Self { values: [0.0; N_SPECTRUM_SAMPLES] } }
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
values: [0.0; N_SPECTRUM_SAMPLES],
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SampledSpectrum {
|
impl SampledSpectrum {
|
||||||
|
|
@ -37,6 +43,10 @@ impl SampledSpectrum {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_black(&self) -> bool {
|
||||||
|
self.values.iter().all(|&sample| sample == 0.0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn has_nans(&self) -> bool {
|
pub fn has_nans(&self) -> bool {
|
||||||
self.values.iter().any(|&v| v.is_nan())
|
self.values.iter().any(|&v| v.is_nan())
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +56,9 @@ impl SampledSpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_component_value(&self) -> Float {
|
pub fn max_component_value(&self) -> Float {
|
||||||
self.values.iter().fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
self.values
|
||||||
|
.iter()
|
||||||
|
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn average(&self) -> Float {
|
pub fn average(&self) -> Float {
|
||||||
|
|
@ -56,7 +68,11 @@ impl SampledSpectrum {
|
||||||
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
||||||
let mut r = SampledSpectrum::new(0.0);
|
let mut r = SampledSpectrum::new(0.0);
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
r.values[i] = if rhs[i] != 0.0 {self.values[i] / rhs.values[i]} else { 0.0 }
|
r.values[i] = if rhs[i] != 0.0 {
|
||||||
|
self.values[i] / rhs.values[i]
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
@ -79,14 +95,17 @@ impl SampledSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Index<usize> for SampledSpectrum {
|
impl Index<usize> for SampledSpectrum {
|
||||||
type Output = Float;
|
type Output = Float;
|
||||||
fn index(&self, i: usize) -> &Self::Output { &self.values[i] }
|
fn index(&self, i: usize) -> &Self::Output {
|
||||||
|
&self.values[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexMut<usize> for SampledSpectrum {
|
impl IndexMut<usize> for SampledSpectrum {
|
||||||
fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.values[i] }
|
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
|
||||||
|
&mut self.values[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Add for SampledSpectrum {
|
impl Add for SampledSpectrum {
|
||||||
|
|
@ -248,7 +267,7 @@ impl SampledWavelengths {
|
||||||
pub fn secondary_terminated(&self) -> bool {
|
pub fn secondary_terminated(&self) -> bool {
|
||||||
for i in 1..N_SPECTRUM_SAMPLES {
|
for i in 1..N_SPECTRUM_SAMPLES {
|
||||||
if self.pdf[i] != 0.0 {
|
if self.pdf[i] != 0.0 {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
|
@ -287,7 +306,9 @@ impl SampledWavelengths {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_wavelengths_pdf(lambda: Float) -> Float {
|
pub fn visible_wavelengths_pdf(lambda: Float) -> Float {
|
||||||
if lambda < 360.0 || lambda > 830.0 { return 0.0; }
|
if lambda < 360.0 || lambda > 830.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
0.0039398042 / (Float::cosh(0.0072 * (lambda - 538.0))).sqrt()
|
0.0039398042 / (Float::cosh(0.0072 * (lambda - 538.0))).sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -297,7 +318,9 @@ impl SampledWavelengths {
|
||||||
|
|
||||||
for i in 0..N_SPECTRUM_SAMPLES {
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
let mut up = u + i as Float / N_SPECTRUM_SAMPLES as Float;
|
let mut up = u + i as Float / N_SPECTRUM_SAMPLES as Float;
|
||||||
if up > 1.0 { up -= 1.0; }
|
if up > 1.0 {
|
||||||
|
up -= 1.0;
|
||||||
|
}
|
||||||
lambda[i] = Self::sample_visible_wavelengths(up);
|
lambda[i] = Self::sample_visible_wavelengths(up);
|
||||||
pdf[i] = Self::visible_wavelengths_pdf(lambda[i]);
|
pdf[i] = Self::visible_wavelengths_pdf(lambda[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -307,11 +330,15 @@ impl SampledWavelengths {
|
||||||
|
|
||||||
impl Index<usize> for SampledWavelengths {
|
impl Index<usize> for SampledWavelengths {
|
||||||
type Output = Float;
|
type Output = Float;
|
||||||
fn index(&self, i: usize) -> &Self::Output { &self.lambda[i] }
|
fn index(&self, i: usize) -> &Self::Output {
|
||||||
|
&self.lambda[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexMut<usize> for SampledWavelengths {
|
impl IndexMut<usize> for SampledWavelengths {
|
||||||
fn index_mut(&mut self, i: usize) -> &mut Self::Output { &mut self.lambda[i] }
|
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
|
||||||
|
&mut self.lambda[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -353,9 +380,11 @@ impl Spectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_xyz(&self) -> XYZ {
|
pub fn to_xyz(&self) -> XYZ {
|
||||||
XYZ::new(inner_product(&spectra::X, self),
|
XYZ::new(
|
||||||
|
inner_product(&spectra::X, self),
|
||||||
inner_product(&spectra::Y, self),
|
inner_product(&spectra::Y, self),
|
||||||
inner_product(&spectra::Z, self))/CIE_Y_INTEGRAL
|
inner_product(&spectra::Z, self),
|
||||||
|
) / CIE_Y_INTEGRAL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,20 +396,23 @@ pub fn inner_product(f: &Spectrum, g: &Spectrum) -> Float {
|
||||||
integral
|
integral
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct ConstantSpectrum {
|
pub struct ConstantSpectrum {
|
||||||
c: Float,
|
c: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstantSpectrum {
|
impl ConstantSpectrum {
|
||||||
pub fn new(c: Float) -> Self { Self { c } }
|
pub fn new(c: Float) -> Self {
|
||||||
|
Self { c }
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sample_at(&self, _lambda: Float) -> Float {
|
pub fn sample_at(&self, _lambda: Float) -> Float {
|
||||||
self.c
|
self.c
|
||||||
}
|
}
|
||||||
|
|
||||||
fn max_value(&self) -> Float { self.c }
|
fn max_value(&self) -> Float {
|
||||||
|
self.c
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
@ -390,7 +422,9 @@ pub struct RGBAlbedoSpectrum {
|
||||||
|
|
||||||
impl RGBAlbedoSpectrum {
|
impl RGBAlbedoSpectrum {
|
||||||
pub fn new(cs: &RGBColorspace, rgb: RGB) -> Self {
|
pub fn new(cs: &RGBColorspace, rgb: RGB) -> Self {
|
||||||
Self { rsp: cs.to_rgb_coeffs(rgb) }
|
Self {
|
||||||
|
rsp: cs.to_rgb_coeffs(rgb),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample_at(&self, lambda: Float) -> Float {
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
||||||
|
|
@ -420,8 +454,15 @@ impl UnboundedRGBSpectrum {
|
||||||
pub fn new(cs: RGBColorspace, rgb: RGB) -> Self {
|
pub fn new(cs: RGBColorspace, rgb: RGB) -> Self {
|
||||||
let m = rgb.r.max(rgb.g).max(rgb.b);
|
let m = rgb.r.max(rgb.g).max(rgb.b);
|
||||||
let scale = 2.0 * m;
|
let scale = 2.0 * m;
|
||||||
let scaled_rgb = if scale != 0.0 { rgb / scale } else { RGB::new(0.0, 0.0, 0.0) };
|
let scaled_rgb = if scale != 0.0 {
|
||||||
Self { scale, rsp: cs.to_rgb_coeffs(scaled_rgb) }
|
rgb / scale
|
||||||
|
} else {
|
||||||
|
RGB::new(0.0, 0.0, 0.0)
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
scale,
|
||||||
|
rsp: cs.to_rgb_coeffs(scaled_rgb),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn sample_at(&self, lambda: Float) -> Float {
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
||||||
self.scale * self.rsp.evaluate(lambda)
|
self.scale * self.rsp.evaluate(lambda)
|
||||||
|
|
@ -442,7 +483,9 @@ pub struct RGBIlluminantSpectrum {
|
||||||
impl RGBIlluminantSpectrum {
|
impl RGBIlluminantSpectrum {
|
||||||
pub fn sample_at(&self, lambda: Float) -> Float {
|
pub fn sample_at(&self, lambda: Float) -> Float {
|
||||||
match &self.illuminant {
|
match &self.illuminant {
|
||||||
Some(illuminant) => self.scale * self.rsp.evaluate(lambda) * illuminant.sample_at(lambda),
|
Some(illuminant) => {
|
||||||
|
self.scale * self.rsp.evaluate(lambda) * illuminant.sample_at(lambda)
|
||||||
|
}
|
||||||
None => 0.0,
|
None => 0.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -455,7 +498,6 @@ impl RGBIlluminantSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct DenselySampledSpectrum {
|
pub struct DenselySampledSpectrum {
|
||||||
lambda_min: i32,
|
lambda_min: i32,
|
||||||
|
|
@ -466,12 +508,18 @@ pub struct DenselySampledSpectrum {
|
||||||
impl DenselySampledSpectrum {
|
impl DenselySampledSpectrum {
|
||||||
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
|
||||||
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
let n_values = (lambda_max - lambda_min + 1).max(0) as usize;
|
||||||
Self { lambda_min, lambda_max, values: vec![0.0; n_values] }
|
Self {
|
||||||
|
lambda_min,
|
||||||
|
lambda_max,
|
||||||
|
values: vec![0.0; n_values],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_spectrum(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
|
pub fn from_spectrum(spec: &Spectrum, lambda_min: i32, lambda_max: i32) -> Self {
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
let mut s = Self::new(lambda_min, lambda_max);
|
||||||
if s.values.is_empty() { return s; }
|
if s.values.is_empty() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
for lambda in lambda_min..=lambda_max {
|
for lambda in lambda_min..=lambda_max {
|
||||||
let index = (lambda - lambda_min) as usize;
|
let index = (lambda - lambda_min) as usize;
|
||||||
s.values[index] = spec.sample_at(lambda as Float);
|
s.values[index] = spec.sample_at(lambda as Float);
|
||||||
|
|
@ -479,9 +527,14 @@ impl DenselySampledSpectrum {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self where F: Fn(Float) -> Float {
|
pub fn from_function<F>(f: F, lambda_min: i32, lambda_max: i32) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(Float) -> Float,
|
||||||
|
{
|
||||||
let mut s = Self::new(lambda_min, lambda_max);
|
let mut s = Self::new(lambda_min, lambda_max);
|
||||||
if s.values.is_empty() { return s; }
|
if s.values.is_empty() {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
for lambda in lambda_min..=lambda_max {
|
for lambda in lambda_min..=lambda_max {
|
||||||
let index = (lambda - lambda_min) as usize;
|
let index = (lambda - lambda_min) as usize;
|
||||||
s.values[index] = f(lambda as Float);
|
s.values[index] = f(lambda as Float);
|
||||||
|
|
@ -498,8 +551,48 @@ impl DenselySampledSpectrum {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sample(&self, lambda: &SampledWavelengths) -> SampledSpectrum {
|
||||||
|
let mut s = SampledSpectrum::default();
|
||||||
|
|
||||||
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
|
let offset = lambda[i].round() as usize - LAMBDA_MIN as usize;
|
||||||
|
if offset >= self.values.len() {
|
||||||
|
s[i] = 0.;
|
||||||
|
} else {
|
||||||
|
s[i] = self.values[offset];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
pub fn max_value(&self) -> Float {
|
pub fn max_value(&self) -> Float {
|
||||||
self.values.iter().fold(Float::MIN, |a,b| a.max(*b))
|
self.values.iter().fold(Float::MIN, |a, b| a.max(*b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min_component_value(&self) -> Float {
|
||||||
|
self.values.iter().fold(Float::INFINITY, |a, &b| a.min(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max_component_value(&self) -> Float {
|
||||||
|
self.values
|
||||||
|
.iter()
|
||||||
|
.fold(Float::NEG_INFINITY, |a, &b| a.max(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn average(&self) -> Float {
|
||||||
|
self.values.iter().sum::<Float>() / (N_SPECTRUM_SAMPLES as Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
|
||||||
|
let mut r = Self::new(1, 1);
|
||||||
|
for i in 0..N_SPECTRUM_SAMPLES {
|
||||||
|
r.values[i] = if rhs[i] != 0.0 {
|
||||||
|
self.values[i] / rhs.values[i]
|
||||||
|
} else {
|
||||||
|
0.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,7 +603,10 @@ pub struct PiecewiseLinearSpectrum {
|
||||||
|
|
||||||
impl PiecewiseLinearSpectrum {
|
impl PiecewiseLinearSpectrum {
|
||||||
pub fn from_interleaved(data: &[Float]) -> Self {
|
pub fn from_interleaved(data: &[Float]) -> Self {
|
||||||
assert!(data.len() % 2 == 0, "Interleaved data must have an even number of elements");
|
assert!(
|
||||||
|
data.len() % 2 == 0,
|
||||||
|
"Interleaved data must have an even number of elements"
|
||||||
|
);
|
||||||
let mut samples = Vec::new();
|
let mut samples = Vec::new();
|
||||||
for pair in data.chunks(2) {
|
for pair in data.chunks(2) {
|
||||||
samples.push((pair[0], pair[1]));
|
samples.push((pair[0], pair[1]));
|
||||||
|
|
@ -563,7 +659,9 @@ impl BlackbodySpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
|
fn planck_law(lambda_nm: Float, temp: Float) -> Float {
|
||||||
if temp <= 0.0 { return 0.0; }
|
if temp <= 0.0 {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
let lambda_m = lambda_nm * 1e-9; // Convert nm to meters
|
let lambda_m = lambda_nm * 1e-9; // Convert nm to meters
|
||||||
let c1 = 2.0 * Self::H * Self::C * Self::C;
|
let c1 = 2.0 * Self::H * Self::C * Self::C;
|
||||||
let c2 = (Self::H * Self::C) / Self::KB;
|
let c2 = (Self::H * Self::C) / Self::KB;
|
||||||
|
|
@ -571,7 +669,11 @@ impl BlackbodySpectrum {
|
||||||
let numerator = c1 / lambda_m.powi(5);
|
let numerator = c1 / lambda_m.powi(5);
|
||||||
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
|
let denominator = (c2 / (lambda_m * temp)).exp() - 1.0;
|
||||||
|
|
||||||
if denominator.is_infinite() { 0.0 } else { numerator / denominator }
|
if denominator.is_infinite() {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
numerator / denominator
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_at(&self, lambda: Float) -> Float {
|
fn sample_at(&self, lambda: Float) -> Float {
|
||||||
|
|
@ -580,7 +682,9 @@ impl BlackbodySpectrum {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBSpectrum { pub c: [Float; 3] }
|
pub struct RGBSpectrum {
|
||||||
|
pub c: [Float; 3],
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct RGBUnboundedSpectrum(pub RGBSpectrum);
|
pub struct RGBUnboundedSpectrum(pub RGBSpectrum);
|
||||||
|
|
@ -614,5 +718,4 @@ pub mod spectra {
|
||||||
);
|
);
|
||||||
Spectrum::DenselySampled(dss)
|
Spectrum::DenselySampled(dss)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
76
src/utils/splines.rs
Normal file
76
src/utils/splines.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use crate::core::pbrt::{Float, lerp};
|
||||||
|
use crate::geometry::{Bounds3f, Lerp, Point3f, Vector3f, VectorLike};
|
||||||
|
use num_traits::Num;
|
||||||
|
|
||||||
|
fn bounds_cubic_bezier(cp: &[Point3f]) -> Bounds3f {
|
||||||
|
Bounds3f::from_points(cp[0], cp[1]).union(Bounds3f::from_points(cp[2], cp[3]))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bound_cubic_bezier(cp: &[Point3f], u_min: Float, u_max: Float) -> Bounds3f {
|
||||||
|
if u_min == 0. && u_max == 1. {
|
||||||
|
return bounds_cubic_bezier(cp);
|
||||||
|
}
|
||||||
|
let cp_seg = cubic_bezier_control_points(cp, u_min, u_max);
|
||||||
|
bounds_cubic_bezier(&cp_seg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cubic_bezier_control_points(cp: &[Point3f], u_min: Float, u_max: Float) -> [Point3f; 4] {
|
||||||
|
[
|
||||||
|
blossom_cubic_bezier(cp, u_min, u_max, u_min),
|
||||||
|
blossom_cubic_bezier(cp, u_min, u_min, u_max),
|
||||||
|
blossom_cubic_bezier(cp, u_min, u_max, u_max),
|
||||||
|
blossom_cubic_bezier(cp, u_max, u_max, u_max),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn blossom_cubic_bezier<P, F>(p: &[P], u0: F, u1: F, u2: F) -> P
|
||||||
|
where
|
||||||
|
F: Copy + Num,
|
||||||
|
P: Lerp<F>,
|
||||||
|
{
|
||||||
|
let a: [P; 3] = [
|
||||||
|
lerp(u0, p[0], p[1]),
|
||||||
|
lerp(u0, p[1], p[2]),
|
||||||
|
lerp(u0, p[2], p[3]),
|
||||||
|
];
|
||||||
|
let b: [P; 2] = [lerp(u1, a[0], a[1]), lerp(u1, a[1], a[2])];
|
||||||
|
lerp(u2, b[0], b[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn subdivide_cubic_bezier(cp: &[Point3f]) -> [Point3f; 7] {
|
||||||
|
let v: Vec<Vector3f> = cp.iter().map(|&p| p.into()).collect();
|
||||||
|
let v01 = (v[0] + v[1]) / 2.0;
|
||||||
|
let v12 = (v[1] + v[2]) / 2.0;
|
||||||
|
let v23 = (v[2] + v[3]) / 2.0;
|
||||||
|
|
||||||
|
let v012 = (v01 + v12) / 2.0;
|
||||||
|
let v123 = (v12 + v23) / 2.0;
|
||||||
|
|
||||||
|
let v0123 = (v012 + v123) / 2.0;
|
||||||
|
[
|
||||||
|
cp[0],
|
||||||
|
v01.into(),
|
||||||
|
v012.into(),
|
||||||
|
v0123.into(),
|
||||||
|
v123.into(),
|
||||||
|
v23.into(),
|
||||||
|
cp[3],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn evaluate_cubic_bezier(cp: &[Point3f], u: Float) -> (Point3f, Vector3f) {
|
||||||
|
let cp1 = [
|
||||||
|
lerp(u, cp[0], cp[1]),
|
||||||
|
lerp(u, cp[1], cp[2]),
|
||||||
|
lerp(u, cp[2], cp[3]),
|
||||||
|
];
|
||||||
|
let cp2 = [lerp(u, cp1[0], cp1[1]), lerp(u, cp1[1], cp1[2])];
|
||||||
|
let deriv: Vector3f;
|
||||||
|
if (cp2[1] - cp2[0]).norm_squared() > 0. {
|
||||||
|
deriv = (cp2[1] - cp2[0]) * 3.;
|
||||||
|
} else {
|
||||||
|
deriv = cp[3] - cp[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
(lerp(u, cp2[0], cp2[1]), deriv)
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue