Added shapes, starting work on interactions and materials

This commit is contained in:
pingupingou 2025-11-13 23:02:54 +00:00
parent 21a2c0c674
commit d58203e97a
53 changed files with 17210 additions and 5004 deletions

View file

@ -1,18 +1,18 @@
mod perspective;
mod orthographic;
mod spherical;
mod perspective;
mod realistic;
mod spherical;
pub use perspective::PerspectiveCamera;
pub use orthographic::OrthographicCamera;
pub use spherical::SphericalCamera;
pub use perspective::PerspectiveCamera;
pub use realistic::RealisticCamera;
pub use spherical::SphericalCamera;
use crate::core::film::Film;
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::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::transform::{AnimatedTransform, Transform};
@ -30,7 +30,10 @@ pub struct 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 {
RenderingCoordinateSystem::Camera => {
let t_mid = (world_from_camera.start_time + world_from_camera.end_time) / 2.;
@ -44,10 +47,20 @@ impl CameraTransform {
RenderingCoordinateSystem::World => Transform::identity(),
};
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 render_from_camera = AnimatedTransform::new(&rfc[0], world_from_camera.start_time, &rfc[1], world_from_camera.end_time).expect("Render wrong");
Self { render_from_camera, world_from_render }
let rfc = [
render_from_world * world_from_camera.start_transform,
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 {
@ -90,7 +103,11 @@ pub struct CameraBase {
pub trait CameraTrait {
fn base(&self) -> &CameraBase;
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) {
Some(cr) => cr,
None => return None,
@ -105,8 +122,10 @@ pub trait CameraTrait {
s_shift.p_film[0] += eps;
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_direction = central_cam_ray.ray.d + (rx_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
rd.rx_origin =
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;
break;
}
@ -117,10 +136,12 @@ pub trait CameraTrait {
s_shift.p_film[1] += eps;
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_direction = central_cam_ray.ray.d + (ry_cam_ray.ray.d - central_cam_ray.ray.d) / eps;
rd.ry_origin =
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;
break
break;
}
}
@ -135,32 +156,61 @@ pub trait CameraTrait {
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_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 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 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,
None, None);
let y_ray = Ray::new(Point3f::new(0., 0., 0.) + self.base().min_pos_differential_y,
None,
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,
None, None);
let tx = -(n_down_z.dot(y_ray.o)) / n_down_z.dot(x_ray.d);
let ty = -(n_down_z.dot(x_ray.o) - d) / n_down_z.dot(y_ray.d);
None,
None,
);
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 py = y_ray.evaluate(ty);
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);
*dpdy = spp_scale * self.base().camera_transform.render_from_camera_vector(down_z_from_camera.apply_inverse_vector(py - p_down_z), time);
*dpdx = spp_scale
* 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 {
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 {
@ -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 {
Camera::Perspective(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,
}
struct ExitPupilSample {
pub struct ExitPupilSample {
pub p_pupil: Point3f,
pub pdf: Float,
}

View file

@ -1,10 +1,13 @@
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::utils::transform::Transform;
use crate::utils::geometry::{Bounds2f, Point3f, Vector3f, Ray, RayDifferential, Normed};
use crate::core::pbrt::Float;
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::transform::Transform;
pub struct OrthographicCamera {
pub base: CameraBase,
@ -19,12 +22,26 @@ pub struct OrthographicCamera {
}
impl OrthographicCamera {
pub fn new(base: CameraBase, screen_window: Bounds2f, lens_radius: Float, focal_distance: Float) -> Self {
let ndc_from_screen: Transform<Float> = Transform::scale(
1./(screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()), 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.);
pub fn new(
base: CameraBase,
screen_window: Bounds2f,
lens_radius: Float,
focal_distance: Float,
) -> Self {
let ndc_from_screen: Transform<Float> = Transform::scale(
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
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 screen_from_raster = raster_from_screen.inverse();
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_pos_differential_x = dx_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 {
fn base(&self) -> &CameraBase {
&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
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 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
if self.lens_radius > 0. {
// 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
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);
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) {
Some(cr) => cr,
None => return None,
@ -81,8 +125,14 @@ impl CameraTrait for OrthographicCamera {
return self.generate_ray_differential(sample, lambda);
} else {
let time = self.sample_time(sample.time);
let world_dx = self.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);
let world_dx = self
.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.ry_origin = central_cam_ray.ray.o + world_dy;

View file

@ -1,10 +1,13 @@
use super::{CameraRay, CameraBase, CameraTrait};
use super::{CameraBase, CameraRay, CameraTrait};
use crate::camera;
use crate::core::film::FilmTrait;
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::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::transform::Transform;
@ -22,23 +25,42 @@ pub struct PerspectiveCamera {
}
impl PerspectiveCamera {
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(
1./(screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()), 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.);
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(
1. / (screen_window.p_max.x() - screen_window.p_min.x()),
1. / (screen_window.p_max.y() - screen_window.p_min.y()),
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 screen_from_raster = raster_from_screen.inverse();
let camera_from_raster = screen_from_camera.inverse() * screen_from_raster;
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 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 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 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 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();
Self { base,
screen_from_camera: *screen_from_camera,
Self {
base,
screen_from_camera: screen_from_camera.clone(),
camera_from_raster,
raster_from_screen,
screen_from_raster,
@ -55,17 +77,27 @@ impl CameraTrait for PerspectiveCamera {
fn base(&self) -> &CameraBase {
&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
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_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
if self.lens_radius > 0. {
// 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
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);
Some(CameraRay{ray, weight: SampledSpectrum::default()})
Some(CameraRay {
ray,
weight: SampledSpectrum::default(),
})
}
}

View file

@ -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::pbrt::{Float, square, lerp};
use crate::core::pbrt::{Float, lerp};
use crate::core::sampler::CameraSample;
use crate::utils::math::quadratic;
use crate::utils::geometry::{Bounds2f, Point2f, Point3f, Point2i, Normal3f, Vector2i, Vector3f, Ray, Normed, Dot};
use crate::geometry::{
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::spectrum::{SampledWavelengths, SampledSpectrum};
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
pub struct RealisticCamera {
base: CameraBase,
focus_distance: Float,
set_aperture_diameter: Float,
// aperture_image: Image,
aperture_image: Image,
element_interface: Vec<LensElementInterface>,
physical_extent: Bounds2f,
exit_pupil_bounds: Vec<Bounds2f>,
}
impl RealisticCamera {
pub fn new(&self,
pub fn new(
&self,
base: CameraBase,
lens_params: Vec<Float>,
focus_distance: Float,
set_aperture_diameter: Float,
) -> Self {
let aspect = base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
aperture_image: Image,
) -> Self {
let aspect =
base.film.full_resolution().x() as Float / base.film.full_resolution().y() as Float;
let diagonal = base.film.diagonal();
let x = (square(diagonal) / (1.0 + square(diagonal))).sqrt();
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();
for i in (0..lens_params.len()).step_by(4) {
@ -46,7 +52,12 @@ impl RealisticCamera {
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);
}
@ -54,15 +65,25 @@ impl RealisticCamera {
let n_samples = 64;
for i in 0..64 {
let r0 = i as Float / 64. * base.film.diagonal() / 2.;
let r0 = i as Float / 64. * base.film.diagonal() / 2.;
let r1 = (i + 1) as Float / n_samples as Float * base.film.diagonal() / 2.;
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 {
let mut z_sum = 0.;
@ -72,48 +93,66 @@ impl RealisticCamera {
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 {
let mut pupil_bounds = Bounds2f::default();
let n_samples = 1024 * 1024;
let rear_radius = self.rear_element_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));
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),
);
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;
for i in 0..n_samples {
// 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 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()),
lens_rear_z());
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()),
lens_rear_z(),
);
// Expand pupil bounds if ray makes it through the lens system
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) {
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)
{
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
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;
}
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 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 (t0, t1) = quadratic(a, b, c)?;
@ -128,7 +167,9 @@ impl RealisticCamera {
let p_hit_relative = o + ray.d * t;
// 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))
}
@ -137,8 +178,12 @@ impl RealisticCamera {
let mut element_z = 0.;
let weight = 1.;
// 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()),
Vector3f::new(r_camera.d.x(), r_camera.d.y(), -r_camera.d.z()), Some(r_camera.time), None);
let mut r_lens = Ray::new(
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() {
let element: &LensElementInterface = &self.element_interface[i];
@ -155,7 +200,9 @@ impl RealisticCamera {
// Intersect ray with element to compute _t_ and _n_
let radius = 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;
n = intersect_n;
} else {
@ -178,7 +225,11 @@ impl RealisticCamera {
// Update ray path for element interface interaction
if !is_stop {
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 eta_ratio = eta_t / eta_i;
@ -193,8 +244,12 @@ impl RealisticCamera {
}
// 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()),
Vector3f::new(r_lens.d.x(), r_lens.d.y(), -r_lens.d.z()), Some(r_lens.time), None);
let r_out = Ray::new(
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))
}
@ -202,7 +257,8 @@ impl RealisticCamera {
pub fn sample_exit_pupil(&self, p_film: Point2f, u_lens: Point2f) -> Option<ExitPupilSample> {
// Find exit pupil bound for sample distance from film center
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);
let pupil_bounds = self.exit_pupil_bounds[r_index];
@ -215,12 +271,23 @@ impl RealisticCamera {
let pdf = 1. / pupil_bounds.area();
// 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 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());
let sin_theta = if r_film != 0. {
p_film.y() / r_film
} else {
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
}
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_
let s = Point2f::new(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 s = Point2f::new(
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_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 (weight, mut ray) = self.trace_lenses_from_film(&r_film)?;
if weight == 0. {
return None
return None;
}
// Finish initialization of _RealisticCamera_ ray
@ -254,8 +327,12 @@ impl CameraTrait for RealisticCamera {
// Compute weighting for _RealisticCamera_ ray
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),
})
}
}

View file

@ -1,10 +1,10 @@
use super::{CameraRay, CameraTrait, CameraBase};
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 super::{CameraBase, CameraRay, CameraTrait};
use crate::core::film::FilmTrait;
use crate::core::pbrt::{Float, PI};
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)]
pub struct EquiRectangularMapping;
@ -27,17 +27,22 @@ impl CameraTrait for SphericalCamera {
&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
let mut uv = Point2f::new(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 mut uv = Point2f::new(
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;
if self.mapping == Mapping::EquiRectangular(EquiRectangularMapping) {
// Compute ray direction using equirectangular mapping
let theta = PI * uv[1];
let phi = 2. * PI * uv[0];
dir = spherical_direction(theta.sin(), theta.cos(), phi);
} else {
// Compute ray direction using equal area mapping
uv = wrap_equal_area_square(&mut uv);
@ -45,7 +50,15 @@ impl CameraTrait for SphericalCamera {
}
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());
Some(CameraRay{ray: self.render_from_camera(&ray, &mut None), weight: SampledSpectrum::default() })
let ray = Ray::new(
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(),
})
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,58 +1,464 @@
use crate::core::filter::{Filter, FilterTrait};
use crate::core::interaction::SurfaceInteraction;
use crate::core::pbrt::Float;
use crate::utils::color::RGB;
use crate::core::filter::Filter;
use crate::utils::geometry::{Point2i, Point2f, Bounds2f, Bounds2fi};
use crate::utils::spectrum::{SampledSpectrum, SampledWavelengths};
use crate::geometry::{
Bounds2f, Bounds2fi, Bounds2i, Normal3f, Point2f, Point2i, Point3f, Vector2f, Vector2fi,
Vector2i, Vector3f,
};
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 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 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 base: FilmBase,
output_from_render: AnimatedTransform,
pixels: Array2D<SpectralPixel>,
}
pub struct VisibleSurface;
pub struct PixelSensor;
pub struct ImageMetadata;
struct SpectralPixel;
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 full_resolution: Point2i,
pub pixel_bounds: Bounds2fi,
pub pixel_bounds: Bounds2i,
pub filter: Filter,
pub diagonal: Float,
pub sensor: Option<PixelSensor>,
pub filename: String,
}
pub trait FilmTrait {
// Must be implemented
impl FilmBase {
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_mut(&mut self) -> &mut FilmBase;
fn add_sample(&mut self,
fn add_sample(
&mut self,
_p_filme: Point2i,
_l: SampledSpectrum,
_lambda: &SampledWavelengths,
_visible_surface: Option<&VisibleSurface>,
_weight: Float) {todo!()}
fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) { todo!() }
fn write_image(&self, _splat_scale: Float) { todo!() }
fn get_image(&self, _metadata: &ImageMetadata, _splat_scale: Float) { todo!() }
fn get_pixel_rgb(&self, _p: Point2i) -> 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) -> &SampledWavelengths { todo!() }
fn uses_visible_surface(&self) -> bool { todo!() }
_weight: Float,
) {
todo!()
}
fn add_splat(&mut self, _p: Point2f, _v: SampledSpectrum, _lambda: &SampledWavelengths) {
todo!()
}
// Sensible defaults
fn full_resolution(&self) -> Point2i { self.base().full_resolution }
fn sample_bounds(&self) -> Bounds2f { Bounds2f::default() }
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 write_image(&self, metadata: &ImageMetadata, splat_scale: Float) {
let image = self.get_image(metadata, splat_scale);
image
.write(self.get_filename(), metadata)
.expect("Something")
}
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 {
@ -69,6 +475,108 @@ impl FilmTrait for RGBFilm {
fn base_mut(&mut self) -> &mut FilmBase {
&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 {
@ -79,6 +587,77 @@ impl FilmTrait for GBufferBFilm {
fn base_mut(&mut self) -> &mut FilmBase {
&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 {
@ -89,6 +668,10 @@ impl FilmTrait for SpectralFilm {
fn base_mut(&mut self) -> &mut FilmBase {
&mut self.base
}
fn uses_visible_surface(&self) -> bool {
true
}
}
impl FilmTrait for Film {
@ -107,4 +690,12 @@ impl FilmTrait for Film {
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(),
}
}
}

View file

@ -1,16 +1,17 @@
use crate::utils::geometry::{Vector2f, Point2f, Point2i, Bounds2i, Bounds2f};
use crate::core::pbrt::{Float, lerp};
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
use crate::core::sampler::PiecewiseConstant2D;
use crate::geometry::{Bounds2f, Bounds2i, Point2f, Point2i, Vector2f};
use crate::utils::containers::Array2D;
use crate::utils::math::{gaussian, gaussian_integral, sample_tent, windowed_sinc};
use std::hash::Hash;
use rand::Rng;
use std::hash::Hash;
pub struct FilterSample {
p: Point2f,
weight: Float,
}
#[derive(Debug)]
pub struct FilterSampler {
domain: Bounds2f,
distrib: PiecewiseConstant2D,
@ -18,12 +19,14 @@ pub struct 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
where
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 mut f = Array2D::new(array_bounds);
for j in 0..resolution.y() {
@ -35,7 +38,7 @@ impl FilterSampler {
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 }
}
@ -57,6 +60,7 @@ pub trait FilterTrait {
fn sample(&self, u: Point2f) -> FilterSample;
}
#[derive(Debug)]
pub enum Filter {
Box(BoxFilter),
Gaussian(GaussianFilter),
@ -107,6 +111,7 @@ impl FilterTrait for Filter {
}
}
#[derive(Debug)]
pub struct BoxFilter {
pub radius: Vector2f,
}
@ -135,11 +140,15 @@ impl FilterTrait for BoxFilter {
}
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()));
FilterSample { p, weight: 1.0 }
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 }
}
}
#[derive(Debug)]
pub struct GaussianFilter {
pub radius: Vector2f,
pub sigma: Float,
@ -157,16 +166,16 @@ impl GaussianFilter {
radius,
Point2i::new((32.0 * radius.x()) as i32, (32.0 * radius.y()) as i32),
|p: Point2f| {
(gaussian(p.x(), 0., sigma) - exp_x).max(0.) *
(gaussian(p.y(), 0., sigma) - exp_y).max(0.)
}
(gaussian(p.x(), 0., sigma) - exp_x).max(0.)
* (gaussian(p.y(), 0., sigma) - exp_y).max(0.)
},
);
Self {
radius,
sigma,
exp_x: gaussian(radius.x(), 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 {
(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.x(), 0.0, self.sigma) - self.exp_x).max(0.0)
* (gaussian(p.y(), 0.0, self.sigma) - self.exp_y).max(0.0)
}
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.y(), self.radius.y(), 0.0, self.sigma) - 2.0 * self.radius.y() * self.exp_y)
(gaussian_integral(-self.radius.x(), self.radius.x(), 0.0, self.sigma)
- 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 {
@ -191,6 +202,7 @@ impl FilterTrait for GaussianFilter {
}
}
#[derive(Debug)]
pub struct MitchellFilter {
pub radius: Vector2f,
pub b: Float,
@ -207,14 +219,16 @@ impl MitchellFilter {
let mitchell_1d = |x: Float| {
let x = x.abs();
if x <= 1.0 {
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3) +
(-18.0 + 12.0 * b + 6.0 * c) * x.powi(2) +
(6.0 - 2.0 * b)) * (1.0 / 6.0)
((12.0 - 9.0 * b - 6.0 * c) * x.powi(3)
+ (-18.0 + 12.0 * b + 6.0 * c) * x.powi(2)
+ (6.0 - 2.0 * b))
* (1.0 / 6.0)
} else if x <= 2.0 {
((-b - 6.0 * c) * x.powi(3) +
(6.0 * b + 30.0 * c) * x.powi(2) +
(-12.0 * b - 48.0 * c) * x +
(8.0 * b + 24.0 * c)) * (1.0 / 6.0)
((-b - 6.0 * c) * x.powi(3)
+ (6.0 * b + 30.0 * c) * x.powi(2)
+ (-12.0 * b - 48.0 * c) * x
+ (8.0 * b + 24.0 * c))
* (1.0 / 6.0)
} else {
0.0
}
@ -222,20 +236,27 @@ impl MitchellFilter {
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 {
let x = x.abs();
if x <= 1.0 {
((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) +
(6.0 - 2.0 * self.b)) * (1.0 / 6.0)
((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)
+ (6.0 - 2.0 * self.b))
* (1.0 / 6.0)
} else if x <= 2.0 {
((-self.b - 6.0 * self.c) * x.powi(3) +
(6.0 * self.b + 30.0 * self.c) * x.powi(2) +
(-12.0 * self.b - 48.0 * self.c) * x +
(8.0 * self.b + 24.0 * self.c)) * (1.0 / 6.0)
((-self.b - 6.0 * self.c) * x.powi(3)
+ (6.0 * self.b + 30.0 * self.c) * x.powi(2)
+ (-12.0 * self.b - 48.0 * self.c) * x
+ (8.0 * self.b + 24.0 * self.c))
* (1.0 / 6.0)
} else {
0.0
}
@ -243,10 +264,13 @@ impl MitchellFilter {
}
impl FilterTrait for MitchellFilter {
fn radius(&self) -> Vector2f { self.radius }
fn radius(&self) -> Vector2f {
self.radius
}
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 {
@ -258,6 +282,7 @@ impl FilterTrait for MitchellFilter {
}
}
#[derive(Debug)]
pub struct LanczosSincFilter {
pub radius: Vector2f,
pub tau: Float,
@ -273,15 +298,22 @@ impl LanczosSincFilter {
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 {
fn radius(&self) -> Vector2f { self.radius }
fn radius(&self) -> Vector2f {
self.radius
}
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 {
@ -312,6 +344,7 @@ impl FilterTrait for LanczosSincFilter {
}
}
#[derive(Debug)]
pub struct TriangleFilter {
pub radius: Vector2f,
}
@ -323,11 +356,12 @@ impl TriangleFilter {
}
impl FilterTrait for TriangleFilter {
fn radius(&self) -> Vector2f { self.radius }
fn radius(&self) -> Vector2f {
self.radius
}
fn evaluate(&self, p: Point2f) -> Float {
(self.radius.x() - p.x().abs()).max(0.0) *
(self.radius.y() - p.y().abs()).max(0.0)
(self.radius.x() - p.x().abs()).max(0.0) * (self.radius.y() - p.y().abs()).max(0.0)
}
fn integral(&self) -> Float {

View file

@ -10,5 +10,4 @@ pub enum Integrator {
Sampler(SamplerIntegrator),
}
impl Integrator {
}
impl Integrator {}

View file

@ -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::light::Light;
use crate::utils::geometry::{Vector3f, Normal3f, Point2f, Point3f, Point3fi, Normed, Ray, Dot};
use crate::core::pbrt::Float;
use crate::geometry::{Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike};
use crate::lights::Light;
use crate::shapes::ShapeTrait;
use std::any::Any;
use std::sync::Arc;
#[derive(Default, Clone, Debug)]
pub struct InteractionData {
pub pi: Point3fi,
pub n: Normal3f,
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 {
fn get_common(&self) -> &InteractionData;
fn get_common_mut(&mut self) -> &mut InteractionData;
fn as_any(&self) -> &dyn Any;
fn p(&self) -> Point3f { self.get_common().pi.into() }
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 p(&self) -> Point3f {
self.get_common().pi.into()
}
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 {
self.as_any().is::<SurfaceInteraction>()
@ -29,13 +49,10 @@ pub trait Interaction: Send + Sync {
self.as_any().is::<MediumInteraction>()
}
/// Determines the medium for a ray starting at this interaction.
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;
/// Spawns a ray from this interaction to a specified point.
fn spawn_ray_to_point(&self, p2: Point3f) -> Ray {
let origin = self.p();
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 {
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 n: Normal3f,
pub dpdu: Vector3f,
@ -63,9 +87,9 @@ pub struct ShadingGeometry {
pub dndv: Normal3f,
}
#[derive(Default, Debug, Clone)]
pub struct SurfaceInteraction {
pub common: InteractionData,
pub n: Normal3f,
pub uv: Point2f,
pub dpdu: Vector3f,
pub dpdv: Vector3f,
@ -73,16 +97,35 @@ pub struct SurfaceInteraction {
pub dndv: Normal3f,
pub shading: ShadingGeometry,
pub medium_interface: Option<Arc<MediumInterface>>,
pub face_index: bool,
pub face_index: usize,
pub area_light: Option<Arc<Light>>,
pub material: Option<Arc<dyn MaterialTrait>>,
pub dpdx: Vector3f,
pub dpdy: Vector3f,
pub dudx: Float,
pub dvdx: Float,
pub dudy: Float,
pub dvdy: Float,
// pub shape: Option<Arc<dyn Shape>>,
// pub bsdf: Option<BSDF>,
pub shape: Option<Arc<dyn ShapeTrait>>,
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;
@ -94,12 +137,21 @@ pub struct MediumInteraction {
}
impl Interaction for SurfaceInteraction {
fn get_common(&self) -> &InteractionData { &self.common }
fn as_any(&self) -> &dyn Any { self }
fn get_common(&self) -> &InteractionData {
&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>> {
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()
} else {
interface.inside.clone()
@ -108,13 +160,13 @@ impl Interaction for SurfaceInteraction {
}
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
}
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
}
@ -122,7 +174,13 @@ impl Interaction for SurfaceInteraction {
fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray {
// 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>() {
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
} else {
@ -133,7 +191,17 @@ impl Interaction for 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 shading_n = n;
@ -143,16 +211,22 @@ impl SurfaceInteraction {
}
Self {
common: InteractionData { pi, time, wo },
n,
common: InteractionData { pi, n, time, wo, medium_interface: None, medium: None },
uv,
dpdu,
dpdv,
dndu,
dndv,
shading: ShadingGeometry { n: shading_n, dpdu, dpdv, dndu, dndv },
shading: ShadingGeometry {
n: shading_n,
dpdu,
dpdv,
dndu,
dndv,
},
medium_interface: None,
face_index: false,
material: None,
face_index: 0,
area_light: None,
dpdx: Vector3f::zero(),
dpdy: Vector3f::zero(),
@ -160,24 +234,74 @@ impl SurfaceInteraction {
dudy: 0.0,
dvdx: 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;
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.dpdv = dpdvs;
self.shading.dndu = dndus.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 {
fn get_common(&self) -> &InteractionData { &self.common }
fn as_any(&self) -> &dyn Any { self }
fn get_common(&self) -> &InteractionData {
&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>> {
Some(self.medium.clone())

View file

@ -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>),
}

View file

@ -1,15 +1,27 @@
#[derive(Clone, Debug)]
pub struct CoatedDiffuseMaterial;
#[derive(Clone, Debug)]
pub struct CoatedConductorMaterial;
#[derive(Clone, Debug)]
pub struct ConductorMaterial;
#[derive(Clone, Debug)]
pub struct DielectricMaterial;
#[derive(Clone, Debug)]
pub struct DiffuseMaterial;
#[derive(Clone, Debug)]
pub struct DiffuseTransmissionMaterial;
#[derive(Clone, Debug)]
pub struct HairMaterial;
#[derive(Clone, Debug)]
pub struct MeasuredMaterial;
#[derive(Clone, Debug)]
pub struct SubsurfaceMaterial;
#[derive(Clone, Debug)]
pub struct ThinDielectricMaterial;
#[derive(Clone, Debug)]
pub struct MixMaterial;
#[derive(Clone, Debug)]
pub enum Material {
CoatedDiffuse(CoatedDiffuseMaterial),
CoatedConductor(CoatedConductorMaterial),
@ -24,5 +36,6 @@ pub enum Material {
Mix(MixMaterial),
}
impl Material {
}
impl Material {}
pub trait MaterialTrait: Send + Sync + std::fmt::Debug {}

View file

@ -20,8 +20,7 @@ pub enum Medium {
NanoVDB(NanoVDBMedium),
}
#[derive(Default, Clone)]
#[derive(Debug, Default, Clone)]
pub struct MediumInterface {
pub inside: Option<Arc<Medium>>,
pub outside: Option<Arc<Medium>>,
@ -33,26 +32,26 @@ impl MediumInterface {
}
pub fn is_medium_transition(&self) -> bool {
if let Some(ref inside) = self.inside {
// self.inside == Some
if let Some(ref outside) = self.outside {
// self.outside == Some
let pi = inside as *const _ as *const usize;
let po = outside as *const _ as *const usize;
pi != po
} else {
// self.outside == None
true
}
} else {
// self.inside == None
if let Some(ref _outside) = self.outside {
// self.outside == Some
true
} else {
// self.outside == None
false
}
}
if let Some(ref inside) = self.inside {
// self.inside == Some
if let Some(ref outside) = self.outside {
// self.outside == Some
let pi = inside as *const _ as *const usize;
let po = outside as *const _ as *const usize;
pi != po
} else {
// self.outside == None
true
}
} else {
// self.inside == None
if let Some(ref _outside) = self.outside {
// self.outside == Some
true
} else {
// self.outside == None
false
}
}
}
}

View file

@ -3,12 +3,10 @@ pub mod cie;
pub mod film;
pub mod filter;
pub mod integrator;
pub mod light;
pub mod interaction;
pub mod material;
pub mod medium;
pub mod pbrt;
pub mod primitive;
pub mod sampler;
pub mod shape;
pub mod texture;

View file

@ -1,7 +1,7 @@
use crate::geometry::{Lerp, Point2f, Vector2f, Vector3f};
use num_traits::Num;
use std::ops::{Add, Mul};
use crate::utils::geometry::{Point2f, Vector2f, Vector3f};
use std::sync::atomic::{AtomicU64, Ordering as SyncOrdering};
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;
#[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
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
@ -45,34 +46,6 @@ where
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
where
T: PartialOrd,
@ -88,54 +61,6 @@ where
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]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Option<Float> {
if coeffs.is_empty() {
@ -174,22 +99,52 @@ where
clamp_t(result, 0, sz - 2)
}
#[inline]
pub fn gamma(n: i32) -> Float {
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)]
pub enum RenderingCoordinateSystem {
Camera,
CameraWorld,
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
);
}
}
}
};
}

View file

@ -1,9 +1,69 @@
// use crate::core::medium::MediumInterface;
// use crate::core::light::Light;
// use crate::core::geometry::Bounds3f;
//
//
pub struct GeometricPrimitive;
use crate::core::interaction::Interaction;
use crate::core::pbrt::Float;
use crate::geometry::{Bounds3f, Ray};
use crate::shapes::{ShapeIntersection, ShapeTrait};
use crate::core::material::MaterialTrait;
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 AnimatedPrimitive;
pub struct BVHAggregatePrimitive;
@ -14,7 +74,7 @@ pub enum Primitive {
Transformed(TransformedPrimitive),
Animated(AnimatedPrimitive),
BVH(BVHAggregatePrimitive),
KdTree(KdTreeAggregate)
KdTree(KdTreeAggregate),
}
impl Primitive {

View file

@ -1,5 +1,5 @@
use crate::utils::geometry::{Point2f, Point2i, Vector2f, Bounds2f};
use crate::core::pbrt::{find_interval, Float, PI, PI_OVER_2, PI_OVER_4, lerp};
use crate::core::pbrt::{Float, PI, PI_OVER_2, PI_OVER_4, find_interval, lerp};
use crate::geometry::{Bounds2f, Point2f, Point2i, Vector2f};
use crate::utils::containers::Array2D;
#[derive(Debug, Clone, Copy)]
@ -12,10 +12,16 @@ pub struct CameraSample {
impl Default for CameraSample {
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 func: Vec<Float>,
pub cdf: Vec<Float>,
@ -27,6 +33,7 @@ impl PiecewiseConstant1D {
pub fn new(f: &[Float]) -> Self {
Self::new_with_bounds(f, 0., 1.)
}
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
assert!(max > min);
let n = f.len();
@ -34,11 +41,13 @@ impl PiecewiseConstant1D {
for &val in f {
func.push(val.abs());
}
let mut cdf = vec![0.; n + 1];
for i in 1..=n {
debug_assert!(func[i - 1] >= 0.);
cdf[i] = cdf[i - 1] + func[i - 1] * (max - min) / n as Float;
}
let func_integral = cdf[n];
if func_integral == 0. {
for i in 1..=n {
@ -49,14 +58,24 @@ impl PiecewiseConstant1D {
cdf[i] /= func_integral;
}
}
Self { func, cdf, func_integral, min, max }
Self {
func,
cdf,
func_integral,
min,
max,
}
}
pub fn integral(&self) -> Float {
self.func_integral
}
pub fn len(&self) -> usize {
pub fn size(&self) -> usize {
self.func.len()
}
pub fn sample(&self, u: Float) -> (Float, Float, usize) {
let o = find_interval(self.cdf.len(), |idx| self.cdf[idx] <= u);
let mut du = u - self.cdf[o];
@ -64,11 +83,16 @@ impl PiecewiseConstant1D {
du /= self.cdf[o + 1] - self.cdf[o];
}
debug_assert!(!du.is_nan());
let value = lerp((o as Float + du) / self.len() as Float, self.min, self.max);
let pdf_val = if self.func_integral > 0. { self.func[o] / self.func_integral } else { 0. };
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.
};
(value, pdf_val, o)
}
}
#[derive(Debug, Clone)]
pub struct PiecewiseConstant2D {
pub p_conditional_v: Vec<PiecewiseConstant1D>,
pub p_marginal: PiecewiseConstant1D,
@ -76,10 +100,7 @@ pub struct PiecewiseConstant2D {
}
impl PiecewiseConstant2D {
pub fn new(data: &Array2D<f32>, domain: Bounds2f) -> Self {
let resolution = data.size();
let nu = resolution.x as usize;
let nv = resolution.y as usize;
pub fn new(data: &Array2D<Float>, nu: usize, nv: usize, domain: Bounds2f) -> Self {
let mut p_conditional_v = Vec::with_capacity(nv);
for v in 0..nv {
let start = v * nu;
@ -90,19 +111,45 @@ impl PiecewiseConstant2D {
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(
&marginal_func,
domain.p_min.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 {
self.p_marginal.integral()
}
pub fn sample(&self, u: Point2f) -> (Point2f, f32, Point2i) {
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());
@ -110,14 +157,15 @@ impl PiecewiseConstant2D {
let offset = Point2i::new(off_x as i32, off_y as i32);
(Point2f::new(d0, d1), pdf, offset)
}
/// Calculates the PDF at a given point.
pub fn pdf(&self, p: Point2f) -> f32 {
let p_offset = self.domain.offset(&p);
let nu = self.p_conditional_v[0].len();
let nv = self.p_marginal.len();
let nu = self.p_conditional_v[0].size();
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 iv = (p_offset.y() * nv as f32).clamp(0.0, nv as f32 - 1.0) as usize;
let integral = self.p_marginal.integral();
if integral == 0.0 {
0.0

View file

View file

@ -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 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 {
FloatImage(FloatImageTexture),
GPUFloatImage(GPUFloatImageTexture),
@ -32,25 +134,25 @@ pub enum FloatTexture {
Wrinkled(WrinkledTexture),
}
impl FloatTexture {
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
// match self {
// FloatTexture::FloatImage(texture) => texture.evaluate(ctx),
// FloatTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
// FloatTexture::FloatMix(texture) => texture.evaluate(ctx),
// FloatTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
// FloatTexture::FloatScaled(texture) => texture.evaluate(ctx),
// FloatTexture::FloatConstant(texture) => texture.evaluate(ctx),
// FloatTexture::FloatBilerp(texture) => texture.evaluate(ctx),
// FloatTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
// FloatTexture::FloatDots(texture) => texture.evaluate(ctx),
// FloatTexture::FBm(texture) => texture.evaluate(ctx),
// FloatTexture::FloatPtex(texture) => texture.evaluate(ctx),
// FloatTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
// FloatTexture::Windy(texture) => texture.evaluate(ctx),
// FloatTexture::Wrinkled(texture) => texture.evaluate(ctx),
// }
// }
impl FloatTextureTrait for FloatTexture {
fn evaluate(&self, ctx: &TextureEvalContext) -> Float {
match self {
FloatTexture::FloatImage(texture) => texture.evaluate(ctx),
FloatTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
FloatTexture::FloatMix(texture) => texture.evaluate(ctx),
FloatTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
FloatTexture::FloatScaled(texture) => texture.evaluate(ctx),
FloatTexture::FloatConstant(texture) => texture.evaluate(ctx),
FloatTexture::FloatBilerp(texture) => texture.evaluate(ctx),
FloatTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
FloatTexture::FloatDots(texture) => texture.evaluate(ctx),
FloatTexture::FBm(texture) => texture.evaluate(ctx),
FloatTexture::FloatPtex(texture) => texture.evaluate(ctx),
FloatTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
FloatTexture::Windy(texture) => texture.evaluate(ctx),
FloatTexture::Wrinkled(texture) => texture.evaluate(ctx),
}
}
}
pub struct RGBConstantTexture;
@ -86,22 +188,22 @@ pub enum SpectrumTexture {
}
impl SpectrumTexture {
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
// match self {
// SpectrumTexture::FloatImage(texture) => texture.evaluate(ctx),
// SpectrumTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatMix(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatScaled(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatConstant(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatBilerp(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatDots(texture) => texture.evaluate(ctx),
// SpectrumTexture::FBm(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatPtex(texture) => texture.evaluate(ctx),
// SpectrumTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
// SpectrumTexture::Windy(texture) => texture.evaluate(ctx),
// SpectrumTexture::Wrinkled(texture) => texture.evaluate(ctx),
// }
// }
// pub fn evaluate(&self, ctx: TextureEvalContext) -> f32 {
// match self {
// SpectrumTexture::FloatImage(texture) => texture.evaluate(ctx),
// SpectrumTexture::GPUFloatImage(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatMix(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatDirectionMix(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatScaled(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatConstant(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatBilerp(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatCheckerboard(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatDots(texture) => texture.evaluate(ctx),
// SpectrumTexture::FBm(texture) => texture.evaluate(ctx),
// SpectrumTexture::FloatPtex(texture) => texture.evaluate(ctx),
// SpectrumTexture::GPUFloatPtex(texture) => texture.evaluate(ctx),
// SpectrumTexture::Windy(texture) => texture.evaluate(ctx),
// SpectrumTexture::Wrinkled(texture) => texture.evaluate(ctx),
// }
// }
}

View file

@ -1,10 +1,10 @@
use super::{Float, NumFloat};
use super::{Point, Point3, Point2f, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::geometry::traits::VectorLike;
use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f};
use crate::core::pbrt::lerp;
use crate::geometry::traits::{Sqrt, VectorLike};
use crate::geometry::{max, min};
use crate::utils::interval::Interval;
use num_traits::{Num, Bounded};
use num_traits::{Bounded, Num};
use std::mem;
use std::ops::{Add, Div, DivAssign, Mul, Sub};
@ -131,6 +131,27 @@ where
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 {
(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>
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 center = self.p_min + self.diagonal() / two;
let radius = if self.contains(center) {

View file

@ -1,6 +1,5 @@
use super::{Bounds3f, Float, PI, Point3f, Vector3f, VectorLike};
use crate::core::pbrt::square;
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt};
use crate::utils::math::{degrees, safe_acos, safe_asin, safe_sqrt, square};
use crate::utils::transform::Transform;
#[derive(Debug, Clone)]

View file

@ -5,15 +5,16 @@ pub mod ray;
pub mod traits;
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::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::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;
#[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()
}
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 {
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());
if p < 0.0 { p + 2.0 * PI } else { p }
}

View file

@ -1,21 +1,22 @@
use super::traits::Tuple;
use super::traits::{Lerp, Sqrt, Tuple, VectorLike};
use super::{Float, NumFloat, PI};
use crate::utils::interval::Interval;
use crate::utils::math::safe_asin;
use num_traits::{Num, Signed, Zero, FloatConst};
use crate::utils::math::{difference_of_products, quadratic, safe_asin};
use num_traits::{AsPrimitive, FloatConst, Num, Signed, Zero};
use std::hash::{Hash, Hasher};
use std::iter::Sum;
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
// 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]);
// 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]);
// 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]);
#[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> {
fn default() -> Self {
Self([T::default(); N])
@ -54,6 +67,7 @@ macro_rules! impl_tuple_core {
}
}
impl<const N: usize> $Struct<f32, N> {
#[inline]
pub fn floor(&self) -> $Struct<i32, N> {
@ -69,6 +83,15 @@ macro_rules! impl_tuple_core {
pub fn fill(value: T) -> Self {
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> {
@ -196,61 +219,29 @@ macro_rules! impl_op_assign {
#[macro_export]
macro_rules! impl_float_vector_ops {
($Struct:ident) => {
// This impl block is constrained to only apply when the scalar type `T` is a float.
impl<T, const N: usize> $Struct<T, N>
impl<T, const N: usize> VectorLike for $Struct<T, N>
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();
for i in 0..N {
sum = sum + self[i] * rhs[i];
}
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 {
($Struct:ident) => {
impl<T, const N: usize> $Struct<T, N>
@ -325,12 +316,11 @@ impl_float_vector_ops!(Vector);
impl_float_vector_ops!(Normal);
impl_abs!(Vector);
impl_abs!(Normal);
impl_abs!(Point);
impl_accessors!(Vector);
impl_accessors!(Point);
impl_accessors!(Normal);
impl<T: Copy, const N: usize> From<Vector<T, N>> for Normal<T, N> {
fn from(v: Vector<T, N>) -> Self {
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>
where
T: NumFloat,
T: NumFloat + Sqrt,
{
pub fn distance(self, other: Self) -> T {
(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
pub type Point2<T> = Point<T, 2>;
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>
where
T: Num + NumFloat + Copy + Neg<Output = T>,
@ -443,8 +490,76 @@ where
};
(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> {
pub fn new_from_point(p: Point<Float, N>) -> Self {
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 {
type Output = Vector<Interval, N>;
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>
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 {
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 {
let (z, x) = y.normalize().coordinate_system();
Self {

View file

@ -1,6 +1,7 @@
use super::{Normal3f, Point3f, Point3fi, Vector3f, VectorLike};
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;
#[derive(Clone, Debug)]

View file

@ -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 num_traits::{Float as NumFloat, Num, Zero, One, FloatConst};
pub trait Tuple<T, const N: 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 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:
@ -18,41 +76,106 @@ pub trait VectorLike:
+ Add<Output = Self>
+ Sub<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 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 {
self.norm_squared().sqrt()
}
fn normalize(self) -> Self {
fn normalize(self) -> Self
where
Self::Scalar: NumFloat,
{
let n = self.norm();
if n.is_zero() {
self
} else {
self / n
}
if n.is_zero() { 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 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()
}
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
}
}

View file

@ -1,7 +1,11 @@
#![allow(unused_imports, dead_code)]
#![feature(float_erf)]
#![feature(f16)]
#![feature(generic_const_exprs)]
mod camera;
mod core;
mod geometry;
mod lights;
mod shapes;
mod utils;

View file

@ -1,16 +1,19 @@
use crate::core::medium::MediumInterface;
use crate::core::pbrt::Float;
use crate::shapes::ShapeTrait;
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 shape: Arc<Shape>,
pub shape: Arc<&'a dyn ShapeTrait>,
pub two_sided: bool,
pub area: Float,
// inherited from class Light (see light.h)
pub flags: u8,
pub n_samples: i32,
pub medium_interface: MediumInterface,
// light_to_world: Transform,
// world_to_light: Transform,
light_to_world: Transform<Float>,
world_to_light: Transform<Float>,
}

View file

@ -1 +1,27 @@
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
View 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
View 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
View 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
View 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 disks 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
View 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
View 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
View 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()
}
}

View file

@ -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::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 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)]
pub struct XYZ {
x: Float,
y: Float,
z: Float,
pub x: Float,
pub y: 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 {
@ -38,11 +62,25 @@ impl XYZ {
Point2f::new(self.x / sum, self.y / sum)
}
pub fn from_xyy(xy: Point2f, y: Float) -> Self {
if xy.y() == 0.0 {
return Self { x: 0.0, y: 0.0, z: 0.0}
pub fn from_xyy(xy: Point2f, y: Option<Float>) -> Self {
let scale: Float;
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 {
type Output = Self;
fn mul(self, rhs: Float) -> Self {
@ -213,7 +250,6 @@ impl fmt::Display for XYZ {
}
}
#[derive(Debug, Clone)]
pub struct RGB {
pub r: Float,
@ -221,6 +257,20 @@ pub struct RGB {
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 {
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 type CoefficientArray = [[[[[Float; 3]; RES]; RES]; RES]; 3];
#[derive(Debug)]
pub struct RGBToSpectrumTable {
z_nodes: &'static [f32],
coeffs: &'static CoefficientArray,
@ -421,10 +471,13 @@ impl RGBSigmoidPolynomial {
}
pub fn evaluate(&self, lambda: Float) -> Float {
let eval = match crate::core::pbrt::evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
Some(value) => value,
None => { panic!("evaluate_polynomial returned None with non-empty coefficients, this should not happen.") },
};
let eval =
match crate::core::pbrt::evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]) {
Some(value) => value,
None => {
panic!("evaluate_polynomial returned None with non-empty coefficients")
}
};
Self::s(eval)
}
@ -433,18 +486,14 @@ impl RGBSigmoidPolynomial {
let lambda = -self.c1 / (2.0 * self.c0);
let result = self.evaluate(360.0).max(self.evaluate(830.0));
if lambda >= 360.0 && lambda <= 830.0 {
return result.max(self.evaluate(lambda))
return result.max(self.evaluate(lambda));
}
result
}
fn s(x: Float) -> Float {
if x.is_infinite() {
if x > 0.0 {
return 1.0
} else {
return 0.0
}
if x > 0.0 { return 1.0 } else { return 0.0 }
}
0.5 + x / (2.0 * (1.0 + (x * x)).sqrt())
}
@ -457,7 +506,11 @@ impl RGBToSpectrumTable {
pub fn to_polynomial(&self, rgb: RGB) -> RGBSigmoidPolynomial {
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;
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 mut c = [0.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];
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)));
let co = |dx: usize, dy: usize, dz: usize| {
self.coeffs[maxc][zi as usize + dz][yi as usize + dy][xi as usize + dx][i]
};
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,
];

View file

@ -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 super::geometry::Point2f;
use super::transform::SquareMatrix;
use super::color::{RGBSigmoidPolynomial, RGBToSpectrumTable, RGB, XYZ};
use super::spectrum::{DenselySampledSpectrum, Spectrum, SampledSpectrum};
use crate::geometry::Point2f;
use std::cmp::{Eq, PartialEq};
use std::error::Error;
use std::sync::Arc;
#[derive(Clone)]
pub struct RGBColorspace {
r: Point2f,
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>,
pub enum ColorEncoding {
Linear,
SRGB,
}
#[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 {
pub fn new(r: Point2f,
pub fn new(
r: Point2f,
g: Point2f,
b: Point2f,
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 = w_xyz.xy();
let r_xyz = XYZ::from_xyy(r, 1.0);
let g_xyz = XYZ::from_xyy(g, 1.0);
let b_xyz = XYZ::from_xyy(b, 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: SquareMatrix<Float, 3> = SquareMatrix { m: rgb_values };
let c = match rgb.inverse() {
Some(inv_matrix) => { inv_matrix * w_xyz },
None => { panic!("Cannot create RGBColorspace: The RGB primaries form a singular matrix."); }
};
let r_xyz = XYZ::from_xyy(r, Some(1.0));
let g_xyz = XYZ::from_xyy(g, Some(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 = 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 = rgb * SquareMatrix { m: xyz_from_rgb_m };
let rgb_from_xyz = xyz_from_rgb.inverse().expect("Failed to invert the XYZfromRGB matrix. Is it singular?");
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?");
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 {
@ -54,12 +73,11 @@ impl RGBColorspace {
pub fn to_rgb_coeffs(&self, rgb: RGB) -> RGBSigmoidPolynomial {
self.rgb_to_spectrum_table.to_polynomial(rgb)
}
pub fn convert_colorspace(&self, other: &RGBColorspace) -> SquareMatrix<Float, 3> {
if self == other {
return SquareMatrix::default()
return SquareMatrix::default();
}
self.rgb_from_xyz * other.xyz_from_rgb
@ -68,8 +86,10 @@ impl RGBColorspace {
impl PartialEq for RGBColorspace {
fn eq(&self, other: &Self) -> bool {
self.r == other.r && 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)
self.r == other.r
&& 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)
}
}

View file

@ -3,8 +3,9 @@ use std::hash::{BuildHasher, Hash, Hasher};
use std::ops::{Index, IndexMut};
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> {
values: Vec<T>,
extent: Bounds2i,
@ -21,7 +22,7 @@ impl<T> Array2D<T> {
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
T: Clone,
{
@ -30,12 +31,12 @@ impl<T> Array2D<T> {
Self { values, extent }
}
pub fn x_size(&self) -> i32 {
self.extent.p_max.x() - self.extent.p_min.x()
pub fn x_size(&self) -> usize {
(self.extent.p_max.x() - self.extent.p_min.x()) as usize
}
pub fn y_size(&self) -> i32 {
self.extent.p_max.y() - self.extent.p_min.y()
pub fn y_size(&self) -> usize {
(self.extent.p_max.y() - self.extent.p_min.y()) as 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());
let width = self.x_size();
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
}
}

View file

@ -1,6 +1,6 @@
use thiserror::Error;
use image::error;
use std::fmt;
use thiserror::Error;
use crate::utils::image::PixelFormat;
@ -72,13 +72,10 @@ pub enum PixelFormatError {
},
#[error("Invalid conversion from pixel format {from:?} to {to:?}.")]
InvalidConversion {
from: PixelFormat,
to: PixelFormat,
},
InvalidConversion { from: PixelFormat, to: PixelFormat },
#[error("Internal invariant violated: image format is {expected:?} but pixel data is of a different type.")]
InternalFormatMismatch {
expected: PixelFormat,
},
#[error(
"Internal invariant violated: image format is {expected:?} but pixel data is of a different type."
)]
InternalFormatMismatch { expected: PixelFormat },
}

View file

@ -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
View 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
}

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
use crate::core::pbrt::{Float, next_float_up, next_float_down};
use std::ops::{Add, Sub, Mul, Div};
use crate::core::pbrt::Float;
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)]
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 {
type Output = Self;
fn div(self, rhs: Self) -> Self::Output {
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)
}
}
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()
}
}

View file

@ -1,20 +1,44 @@
use super::geometry::{Point2f, Vector3f};
use crate::core::pbrt::{PI, PI_OVER_4, square, lerp, Float, ONE_MINUS_EPSILON, clamp_t};
use super::color::{RGB, XYZ};
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::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]
fn fma<T>(a: T, b: T, c: T) -> T
where
T: Mul<Output = T> + Add<Output = T> + Copy
T: Mul<Output = T> + Add<Output = T> + Copy,
{
a * b + c
}
#[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
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 difference_of_products = fma(a, b, -cd);
@ -23,9 +47,27 @@ where
}
#[inline]
pub fn safe_asin(x: Float) -> Float {
if x >= -1.0001 && x <= 1.0001 {
clamp_t(x, -1., 1.).asin()
pub fn sum_of_products<T>(a: T, b: T, c: T, d: T) -> T
where
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 {
panic!("Not valid value for asin")
}
@ -36,7 +78,7 @@ pub fn safe_acos(x: Float) -> Float {
if x >= -1.0001 && x <= 1.0001 {
clamp_t(x, -1., 1.).asin()
} 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;
}
#[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 {
if x.abs() > radius {
return 0.;
@ -55,17 +103,13 @@ pub fn windowed_sinc(x: Float, radius: Float, tau: Float) -> Float {
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)> {
if a == 0. {
if b == 0. {
return None;
}
let t0 = -c / b;
let t1 = - c / b;
let t1 = -c / b;
return Some((t0, t1));
}
@ -85,22 +129,32 @@ pub fn quadratic(a: Float, b: Float, c: Float) -> Option<(Float, Float)> {
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 {
if uv[0] < 0. {
uv[0] = -uv[0]; // mirror across u = 0
uv[1] = 1. - uv[1]; // mirror across v = 0.5
} else if uv[0] > 1.
{
uv[0] = 2. - uv[0]; // mirror across u = 1
uv[1] = 1. - uv[1]; // mirror across v = 0.5
uv[0] = -uv[0];
uv[1] = 1. - uv[1];
} else if uv[0] > 1. {
uv[0] = 2. - uv[0];
uv[1] = 1. - uv[1];
}
if uv[1] < 0. {
uv[0] = 1. - uv[0]; // mirror across u = 0.5
uv[1] = -uv[1]; // mirror across v = 0;
} else if uv[1] > 1.
{
uv[0] = 1. - uv[0]; // mirror across u = 0.5
uv[1] = 2. - uv[1]; // mirror across v = 1
uv[0] = 1. - uv[0];
uv[1] = -uv[1];
} else if uv[1] > 1. {
uv[0] = 1. - uv[0];
uv[1] = 2. - uv[1];
}
*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 {
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 v = 2. * p.y() - 1.;
let up = u.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 d = signed_distance.abs();
let r = 1. - d;
// Compute angle $\phi$ for square to sphere mapping
let mut phi = if r == 0. { 1. } else {(vp - up) / r + 1.};
// Compute angle \phi for square to sphere mapping
let mut phi = if r == 0. { 1. } else { (vp - up) / r + 1. };
phi /= PI_OVER_4;
// Find $z$ coordinate for spherical direction
// Find z for spherical direction
let z = (1. - square(r)).copysign(signed_distance);
// Compute $\cos\phi$ and $\sin\phi$ for original quadrant and return vector
let cos_phi = phi.cos().copysign(u);
let sin_phi = phi.sin().copysign(v);
return Vector3f::new(cos_phi * r * (2. - square(r)).sqrt(),
sin_phi * r * (2. - square(r)).sqrt(), z);
return Vector3f::new(
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()
}
@ -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 {
assert!(a >= 0. && b >= 0.);
if u == 0. && a == 0. {
return 0.
return 0.;
}
let x = u * (a + b) / (a + (lerp(u, square(a), square(b))));
x.min(ONE_MINUS_EPSILON)
}
fn next_float_down(a: f32) -> f32 {
if a.is_infinite() && a < 0.0 {
return a;
}
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)
}
#[inline]
pub fn float_to_bits(f: Float) -> u32 {
f.to_bits()
}
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> {
// Handle empty weights for discrete sampling.
if weights.is_empty() {
@ -178,10 +270,8 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
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.
let sum_weights: f32 = weights.iter().sum();
if sum_weights == 0.0 {
if let Some(p) = pmf {
*p = 0.0;
@ -189,7 +279,6 @@ pub fn sample_discrete(weights: &[f32], u: f32, pmf: Option<&mut f32>, u_remappe
return None;
}
// Compute rescaled u' sample, ensuring it's strictly less than sum_weights.
let mut up = u * sum_weights;
if up >= sum_weights {
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 {
sum += weights[offset];
offset += 1;
// This assertion should hold true due to the guard `up = next_float_down(up)`.
debug_assert!(offset < weights.len());
}
// Compute PMF and remapped u value, if requested.
if let Some(p) = pmf {
*p = weights[offset] / sum_weights;
}
if let Some(ur) = u_remapped {
let weight = weights[offset];
// The logic guarantees weight > 0 here if sum_weights > 0.
*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 {
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 {
return -r + r * sample_linear(u, 0., 1.);
} else {
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
View 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),
}
}
}

View file

@ -1,11 +1,15 @@
pub mod color;
pub mod colorspace;
pub mod containers;
pub mod geometry;
pub mod error;
pub mod hash;
pub mod image;
pub mod interval;
pub mod math;
pub mod mesh;
pub mod quaternion;
pub mod spectrum;
pub mod transform;
pub mod scattering;
pub mod sampling;
pub mod scattering;
pub mod spectrum;
pub mod splines;
pub mod transform;

View file

@ -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::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 super::geometry::{Vector3f, Dot, Normed};
use crate::core::pbrt::Float;
use crate::geometry::{Vector3f, VectorLike};
use crate::utils::math::{safe_asin, sinx_over_x};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Quaternion {
@ -13,51 +14,89 @@ pub struct Quaternion {
impl Default for Quaternion {
fn default() -> Self {
Self { v: Vector3f::default(), w: 1.0 }
Self {
v: Vector3f::default(),
w: 1.0,
}
}
}
impl Add for Quaternion {
type Output = 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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
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 {
@ -95,17 +134,17 @@ impl Quaternion {
#[inline]
pub fn angle_between(&self, rhs: Quaternion) -> Float {
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 {
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 {
let theta = q1.angle_between(q2);
let sin_theta_over_theta = sinx_over_x(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;
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;
}
pub fn length(&self) -> Float {
@ -113,6 +152,9 @@ impl Quaternion {
}
pub fn normalize(&self) -> Self {
Quaternion { v: self.v.normalize(), w: self.w}
Quaternion {
v: self.v.normalize(),
w: self.w,
}
}
}

View file

@ -1,13 +1,898 @@
use super::geometry::{Point2f, Vector3f};
use crate::core::pbrt::{square, PI, INV_2_PI};
use super::math::safe_sqrt;
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 {
let z = u[0];
let r = SafeSqrt(1 - square(z));
let phi = 2 * PI * u[1];
Vector3f::new(r * phi.cos(), r * phi.sin(), z);
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_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 {
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,
&param_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,
&param_weights,
);
let r1 = self.lookup(
&self.conditional_cdf,
conditional_offset + (row + 2) * self.size.x() as u32 - 1,
conditional_size,
&param_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,
&param_weights,
);
let v1 = self.lookup(
&self.conditional_cdf,
conditional_row_offset + idx + self.size.x() as u32,
conditional_size,
&param_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, &param_weights);
let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup(
&self.data,
offset + self.size.x() as u32,
slice_size,
&param_weights,
);
let v11 = self.lookup(
&self.data,
offset + self.size.x() as u32 + 1,
slice_size,
&param_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, &param_weights);
let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup(
&self.data,
offset + self.size.x() as u32,
slice_size,
&param_weights,
);
let v11 = self.lookup(
&self.data,
offset + self.size.x() as u32 + 1,
slice_size,
&param_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,
&param_weights,
);
let v1 = self.lookup(
&self.conditional_cdf,
conditional_row_offset + col as u32 + self.size.x() as u32,
slice_size,
&param_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,
&param_weights,
);
let r1 = self.lookup(
&self.conditional_cdf,
conditional_row_offset + self.size.x() as u32 * 2 - 1,
slice_size,
&param_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,
&param_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, &param_weights);
let v10 = self.lookup(&self.data, offset + 1, slice_size, &param_weights);
let v01 = self.lookup(
&self.data,
offset + self.size.x() as u32,
slice_size,
&param_weights,
);
let v11 = self.lookup(
&self.data,
offset + self.size.x() as u32 + 1,
slice_size,
&param_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
}
}

View file

@ -1,22 +1,117 @@
use crate::utils::geometry::{Normal3f, Vector3f, Dot};
use crate::core::pbrt::{Float, square};
use super::math::safe_sqrt;
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)> {
// Make local mutable copies of variables that may be changed
let mut n_interface = n;
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 {
eta = 1.0 / eta;
cos_theta_i = -cos_theta_i;
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);
let sin2_theta_i = (1.0 - square(cos_theta_i)).max(0.0_f32);
let sin2_theta_t = sin2_theta_i / square(eta);
// 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);
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
}

View file

@ -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 std::fmt;
use std::ops::{
Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
};
use std::sync::Arc;
use crate::core::pbrt::Float;
use crate::core::cie;
use super::color::{RGB, XYZ, RGBSigmoidPolynomial};
use super::color::{RGB, RGBSigmoidPolynomial, XYZ};
use super::colorspace::RGBColorspace;
use crate::core::cie;
use crate::core::pbrt::Float;
pub const CIE_Y_INTEGRAL: Float = 106.856895;
pub const N_SPECTRUM_SAMPLES: usize = 1200;
@ -19,7 +21,11 @@ pub struct 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 {
@ -37,6 +43,10 @@ impl SampledSpectrum {
s
}
pub fn is_black(&self) -> bool {
self.values.iter().all(|&sample| sample == 0.0)
}
pub fn has_nans(&self) -> bool {
self.values.iter().any(|&v| v.is_nan())
}
@ -46,7 +56,9 @@ impl SampledSpectrum {
}
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 {
@ -56,7 +68,11 @@ impl SampledSpectrum {
pub fn safe_div(&self, rhs: SampledSpectrum) -> Self {
let mut r = SampledSpectrum::new(0.0);
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
}
@ -70,7 +86,7 @@ impl SampledSpectrum {
(*self * x).safe_div(pdf).average(),
(*self * y).safe_div(pdf).average(),
(*self * z).safe_div(pdf).average(),
) / CIE_Y_INTEGRAL
) / CIE_Y_INTEGRAL
}
pub fn to_rgb(&self, lambda: &SampledWavelengths, c: &RGBColorspace) -> RGB {
@ -79,14 +95,17 @@ impl SampledSpectrum {
}
}
impl Index<usize> for SampledSpectrum {
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 {
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 {
@ -248,7 +267,7 @@ impl SampledWavelengths {
pub fn secondary_terminated(&self) -> bool {
for i in 1..N_SPECTRUM_SAMPLES {
if self.pdf[i] != 0.0 {
return false
return false;
}
}
true
@ -287,7 +306,9 @@ impl SampledWavelengths {
}
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()
}
@ -297,7 +318,9 @@ impl SampledWavelengths {
for i in 0..N_SPECTRUM_SAMPLES {
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);
pdf[i] = Self::visible_wavelengths_pdf(lambda[i]);
}
@ -307,11 +330,15 @@ impl SampledWavelengths {
impl Index<usize> for SampledWavelengths {
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 {
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)]
@ -353,9 +380,11 @@ impl Spectrum {
}
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::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
}
#[derive(Debug, Clone, Copy)]
pub struct ConstantSpectrum {
c: Float,
}
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 {
self.c
}
fn max_value(&self) -> Float { self.c }
fn max_value(&self) -> Float {
self.c
}
}
#[derive(Debug, Clone, Copy)]
@ -390,7 +422,9 @@ pub struct RGBAlbedoSpectrum {
impl RGBAlbedoSpectrum {
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 {
@ -420,8 +454,15 @@ impl UnboundedRGBSpectrum {
pub fn new(cs: RGBColorspace, rgb: RGB) -> Self {
let m = rgb.r.max(rgb.g).max(rgb.b);
let scale = 2.0 * m;
let scaled_rgb = if scale != 0.0 { rgb / scale } else { RGB::new(0.0, 0.0, 0.0) };
Self { scale, rsp: cs.to_rgb_coeffs(scaled_rgb) }
let scaled_rgb = if scale != 0.0 {
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 {
self.scale * self.rsp.evaluate(lambda)
@ -442,7 +483,9 @@ pub struct RGBIlluminantSpectrum {
impl RGBIlluminantSpectrum {
pub fn sample_at(&self, lambda: Float) -> Float {
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,
}
}
@ -455,7 +498,6 @@ impl RGBIlluminantSpectrum {
}
}
#[derive(Debug, Clone)]
pub struct DenselySampledSpectrum {
lambda_min: i32,
@ -466,12 +508,18 @@ pub struct DenselySampledSpectrum {
impl DenselySampledSpectrum {
pub fn new(lambda_min: i32, lambda_max: i32) -> Self {
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 {
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 {
let index = (lambda - lambda_min) as usize;
s.values[index] = spec.sample_at(lambda as Float);
@ -479,9 +527,14 @@ impl DenselySampledSpectrum {
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);
if s.values.is_empty() { return s; }
if s.values.is_empty() {
return s;
}
for lambda in lambda_min..=lambda_max {
let index = (lambda - lambda_min) as usize;
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 {
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 {
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();
for pair in data.chunks(2) {
samples.push((pair[0], pair[1]));
@ -563,7 +659,9 @@ impl BlackbodySpectrum {
}
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 c1 = 2.0 * Self::H * Self::C * Self::C;
let c2 = (Self::H * Self::C) / Self::KB;
@ -571,7 +669,11 @@ impl BlackbodySpectrum {
let numerator = c1 / lambda_m.powi(5);
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 {
@ -580,7 +682,9 @@ impl BlackbodySpectrum {
}
#[derive(Debug, Clone, Copy)]
pub struct RGBSpectrum { pub c: [Float; 3] }
pub struct RGBSpectrum {
pub c: [Float; 3],
}
#[derive(Debug, Clone, Copy)]
pub struct RGBUnboundedSpectrum(pub RGBSpectrum);
@ -614,5 +718,4 @@ pub mod spectra {
);
Spectrum::DenselySampled(dss)
});
}
}

76
src/utils/splines.rs Normal file
View 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