use crate::camera::{Camera, CameraTrait}; use crate::core::bssrdf::BSSRDF; use crate::core::bxdf::{BSDF, BxDFFlags, DiffuseBxDF}; use crate::core::material::{ Material, MaterialEvalContext, MaterialTrait, NormalBumpEvalContext, bump_map, normal_map, }; use crate::core::medium::{Medium, MediumInterface, PhaseFunction}; use crate::core::options::get_options; use crate::core::pbrt::{Float, clamp_t}; use crate::core::sampler::{Sampler, SamplerTrait}; use crate::core::texture::{FloatTexture, UniversalTextureEvaluator}; use crate::geometry::{ Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, }; use crate::image::Image; use crate::lights::{Light, LightTrait}; use crate::shapes::Shape; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{difference_of_products, square}; use bumpalo::Bump; use enum_dispatch::enum_dispatch; 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, pub medium_interface: Option, pub medium: Option>, } #[enum_dispatch] pub trait InteractionTrait: Send + Sync + std::fmt::Debug { fn get_common(&self) -> &InteractionData; fn get_common_mut(&mut self) -> &mut InteractionData; 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 { false } fn is_medium_interaction(&self) -> bool { false } fn get_medium(&self, w: Vector3f) -> Option> { let data = self.get_common(); if let Some(mi) = &data.medium_interface { if w.dot(data.n.into()) > 0.0 { mi.outside.clone() } else { mi.inside.clone() } } else { data.medium.clone() } } fn spawn_ray(&self, d: Vector3f) -> Ray { let data = self.get_common(); let mut ray = Ray::spawn(&data.pi, &data.n, data.time, d); ray.medium = self.get_medium(d); ray } fn spawn_ray_to_point(&self, p2: Point3f) -> Ray { let data = self.get_common(); let mut ray = Ray::spawn_to_point(&data.pi, &data.n, data.time, p2); ray.medium = self.get_medium(ray.d); ray } fn spawn_ray_to_interaction(&self, other: &dyn InteractionTrait) -> Ray { let data = self.get_common(); let other_data = other.get_common(); let mut ray = Ray::spawn_to_interaction(&data.pi, &data.n, data.time, &other_data.pi, &other_data.n); ray.medium = self.get_medium(ray.d); ray } 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()) } } #[enum_dispatch(InteractionTrait)] #[derive(Debug, Clone)] pub enum Interaction { Surface(SurfaceInteraction), Medium(MediumInteraction), Simple(SimpleInteraction), } impl Interaction { pub fn set_medium_interface(&mut self, mi: Option) { match self { Interaction::Surface(si) => si.common.medium_interface = mi, Interaction::Simple(si) => si.common.medium_interface = mi, Interaction::Medium(_) => {} // Medium interactions don't usually sit on boundaries } } } #[derive(Debug, Clone)] pub struct SimpleInteraction { pub common: InteractionData, } impl SimpleInteraction { pub fn new(pi: Point3fi, time: Float, medium_interface: Option) -> Self { Self { common: InteractionData { pi, time, medium_interface, n: Normal3f::default(), wo: Vector3f::default(), medium: None, }, } } pub fn new_interface(p: Point3f, medium_interface: Option) -> Self { Self { common: InteractionData { pi: Point3fi::new_from_point(p), n: Normal3f::zero(), wo: Vector3f::zero(), time: 0.0, medium: None, medium_interface, }, } } } impl InteractionTrait for SimpleInteraction { fn get_common(&self) -> &InteractionData { &self.common } fn get_common_mut(&mut self) -> &mut InteractionData { &mut self.common } } #[derive(Default, Clone, Debug)] pub struct ShadingGeometry { pub n: Normal3f, pub dpdu: Vector3f, pub dpdv: Vector3f, pub dndu: Normal3f, pub dndv: Normal3f, } #[derive(Debug, Default, Clone)] pub struct SurfaceInteraction { pub common: InteractionData, pub uv: Point2f, pub dpdu: Vector3f, pub dpdv: Vector3f, pub dndu: Normal3f, pub dndv: Normal3f, pub shading: ShadingGeometry, pub face_index: usize, pub area_light: Option>, pub material: Option>, pub dpdx: Vector3f, pub dpdy: Vector3f, pub dudx: Float, pub dvdx: Float, pub dudy: Float, pub dvdy: Float, pub shape: Arc, } impl SurfaceInteraction { pub fn le(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { if let Some(area_light) = &self.area_light { area_light.l(self.p(), self.n(), self.uv, w, lambda) } else { SampledSpectrum::new(0.) } } pub fn compute_differentials(&mut self, r: &Ray, camera: &Camera, samples_per_pixel: i32) { let computed = if let Some(diff) = &r.differential { let dot_rx = self.common.n.dot(diff.rx_direction.into()); let dot_ry = self.common.n.dot(diff.ry_direction.into()); if dot_rx != 0.0 && dot_ry != 0.0 { // Estimate screen-space change in p using ray differentials> let p_as_vec = Normal3f::new(self.p().x(), self.p().y(), self.p().z()); let d = -self.common.n.dot(p_as_vec); // Compute t for x-auxiliary ray let rx_origin_vec = Normal3f::new(diff.rx_origin.x(), diff.rx_origin.y(), diff.rx_origin.z()); let tx = (-self.common.n.dot(rx_origin_vec) - d) / dot_rx; // Compute intersection point px let px = diff.rx_origin + diff.rx_direction * tx; // Compute t for y-auxiliary ray let ry_origin_vec = Normal3f::new(diff.ry_origin.x(), diff.ry_origin.y(), diff.ry_origin.z()); let ty = (-self.common.n.dot(ry_origin_vec) - d) / dot_ry; let py = diff.ry_origin + diff.ry_direction * ty; self.dpdx = px - self.p(); self.dpdy = py - self.p(); true } else { false } } else { false }; if !computed { camera.approximate_dp_dxy( self.p(), self.n(), self.time(), samples_per_pixel, &mut self.dpdx, &mut self.dpdy, ); } let ata00 = self.dpdu.dot(self.dpdu); let ata01 = self.dpdu.dot(self.dpdv); let ata11 = self.dpdv.dot(self.dpdv); let mut inv_det = 1. / difference_of_products(ata00, ata11, ata01, ata01); inv_det = if inv_det.is_finite() { inv_det } else { 0. }; let atb0x = self.dpdu.dot(self.dpdx); let atb1x = self.dpdv.dot(self.dpdx); let atb0y = self.dpdu.dot(self.dpdy); let atb1y = self.dpdv.dot(self.dpdy); // Compute u and v derivatives in x and y self.dudx = difference_of_products(ata11, atb0x, ata01, atb1x) * inv_det; self.dvdx = difference_of_products(ata00, atb1x, ata01, atb0x) * inv_det; self.dudy = difference_of_products(ata11, atb0y, ata01, atb1y) * inv_det; self.dvdy = difference_of_products(ata00, atb1y, ata01, atb0y) * inv_det; // Clamp derivatives self.dudx = if self.dudx.is_finite() { clamp_t(self.dudx, -1e8, 1e8) } else { 0. }; self.dvdx = if self.dvdx.is_finite() { clamp_t(self.dvdx, -1e8, 1e8) } else { 0. }; self.dudy = if self.dudy.is_finite() { clamp_t(self.dudy, -1e8, 1e8) } else { 0. }; self.dvdy = if self.dvdy.is_finite() { clamp_t(self.dvdy, -1e8, 1e8) } else { 0. }; } pub fn skip_intersection(&self, ray: &mut Ray, t: Float) { let new_ray = Ray::spawn(&self.pi(), &self.n(), ray.time, ray.d); ray.o = new_ray.o; // Skipping other variables, since they should not change when passing through surface if let Some(diff) = &mut ray.differential { diff.rx_origin += diff.rx_direction * t; diff.ry_origin += diff.ry_direction * t; } } pub fn get_bsdf<'a>( &mut self, r: &Ray, lambda: &SampledWavelengths, camera: &Camera, scratch: &'a Bump, sampler: &mut Sampler, ) -> Option> { self.compute_differentials(r, camera, sampler.samples_per_pixel() as i32); let material = { let root_mat = self.material.as_deref()?; let mut active_mat: &Material = root_mat; let tex_eval = UniversalTextureEvaluator; while let Material::Mix(mix) = active_mat { // We need a context to evaluate the 'amount' texture let ctx = MaterialEvalContext::from(&*self); active_mat = mix.choose_material(&tex_eval, &ctx); } active_mat.clone() }; let ctx = MaterialEvalContext::from(&*self); let tex_eval = UniversalTextureEvaluator; let displacement = material.get_displacement(); let normal_map = material.get_normal_map(); if displacement.is_some() || normal_map.is_some() { // This calls the function defined above self.compute_bump_geometry(&tex_eval, displacement, normal_map); } let mut bsdf = material.get_bxdf(&tex_eval, &ctx, lambda, scratch); if get_options().force_diffuse { let r = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]); let diff_bxdf = scratch.alloc(DiffuseBxDF::new(r)); bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Some(diff_bxdf)); } Some(bsdf) } pub fn get_bssrdf( &self, _ray: &Ray, lambda: &SampledWavelengths, _camera: &Camera, _scratch: &Bump, ) -> Option> { let material = { let root_mat = self.material.as_deref()?; let mut active_mat: &Material = root_mat; let tex_eval = UniversalTextureEvaluator; while let Material::Mix(mix) = active_mat { // We need a context to evaluate the 'amount' texture let ctx = MaterialEvalContext::from(self); active_mat = mix.choose_material(&tex_eval, &ctx); } active_mat.clone() }; let ctx = MaterialEvalContext::from(self); let tex_eval = UniversalTextureEvaluator; material.get_bssrdf(&tex_eval, &ctx, lambda) } fn compute_bump_geometry( &mut self, tex_eval: &UniversalTextureEvaluator, displacement: Option, normal_image: Option<&Image>, ) { let ctx = NormalBumpEvalContext::from(&*self); let (dpdu, dpdv) = if let Some(disp) = displacement { bump_map(tex_eval, &disp, &ctx) } else if let Some(map) = normal_image { normal_map(map, &ctx) } else { (self.shading.dpdu, self.shading.dpdv) }; let mut ns = Normal3f::from(dpdu.cross(dpdv).normalize()); if ns.dot(self.n()) < 0.0 { ns = -ns; } self.set_shading_geometry(ns, dpdu, dpdv, self.shading.dndu, self.shading.dndv, false); } pub fn spawn_ray_with_differentials( &self, ray_i: &Ray, wi: Vector3f, flags: BxDFFlags, eta: Float, ) -> Ray { let mut rd = self.spawn_ray(wi); if let Some(diff_i) = &ray_i.differential { let mut n = self.shading.n; let mut dndx = self.shading.dndu * self.dudx + self.shading.dndv * self.dvdx; let mut dndy = self.shading.dndu * self.dudy + self.shading.dndv * self.dvdy; let dwodx = -diff_i.rx_direction - self.wo(); let dwody = -diff_i.ry_direction - self.wo(); let new_diff_rx_origin = self.p() + self.dpdx; let new_diff_ry_origin = self.p() + self.dpdy; let mut new_diff_rx_dir = Vector3f::default(); let mut new_diff_ry_dir = Vector3f::default(); let mut valid_differentials = false; if flags.contains(BxDFFlags::SPECULAR_REFLECTION) { valid_differentials = true; let d_wo_dot_n_dx = dwodx.dot(n.into()) + self.wo().dot(dndx.into()); let d_wo_dot_n_dy = dwody.dot(n.into()) + self.wo().dot(dndy.into()); let wo_dot_n = self.wo().dot(n.into()); new_diff_rx_dir = wi - dwodx + (Vector3f::from(dndx) * wo_dot_n + Vector3f::from(n) * d_wo_dot_n_dx) * 2.0; new_diff_ry_dir = wi - dwody + (Vector3f::from(dndy) * wo_dot_n + Vector3f::from(n) * d_wo_dot_n_dy) * 2.0; } else if flags.contains(BxDFFlags::SPECULAR_TRANSMISSION) { valid_differentials = true; if self.wo().dot(n.into()) < 0.0 { n = -n; dndx = -dndx; dndy = -dndy; } // Compute partial derivatives let d_wo_dot_n_dx = dwodx.dot(n.into()) + self.wo().dot(dndx.into()); let d_wo_dot_n_dy = dwody.dot(n.into()) + self.wo().dot(dndy.into()); let wo_dot_n = self.wo().dot(n.into()); let wi_dot_n = wi.dot(Vector3f::from(n)); let abs_wi_dot_n = wi.abs_dot(n.into()); let mu = wo_dot_n / eta - abs_wi_dot_n; let f_eta = 1.0 / eta; let f_eta2 = 1.0 / square(eta); let term = f_eta + (f_eta2 * wo_dot_n / wi_dot_n); let dmudx = d_wo_dot_n_dx * term; let dmudy = d_wo_dot_n_dy * term; new_diff_rx_dir = wi - dwodx * eta + (Vector3f::from(dndx) * mu + Vector3f::from(n) * dmudx); new_diff_ry_dir = wi - dwody * eta + (Vector3f::from(dndy) * mu + Vector3f::from(n) * dmudy); } if valid_differentials { let threshold = 1e16; if new_diff_rx_dir.norm_squared() > threshold || new_diff_ry_dir.norm_squared() > threshold || Vector3f::from(new_diff_rx_origin).norm_squared() > threshold || Vector3f::from(new_diff_ry_origin).norm_squared() > threshold { rd.differential = None; } else { rd.differential = Some(RayDifferential { rx_origin: new_diff_rx_origin, ry_origin: new_diff_ry_origin, rx_direction: new_diff_rx_dir, ry_direction: new_diff_ry_dir, }); } } } rd } } impl InteractionTrait for SurfaceInteraction { fn get_common(&self) -> &InteractionData { &self.common } fn get_common_mut(&mut self) -> &mut InteractionData { &mut self.common } fn get_medium(&self, w: Vector3f) -> Option> { self.common.medium_interface.as_ref().and_then(|interface| { if self.n().dot(w.into()) > 0.0 { interface.outside.clone() } else { interface.inside.clone() } }) } fn is_surface_interaction(&self) -> bool { true } } impl SurfaceInteraction { 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; if flip { n *= -1.0; shading_n *= -1.0; } Self { 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, }, material: None, face_index: 0, area_light: None, dpdx: Vector3f::zero(), dpdy: Vector3f::zero(), dudx: 0.0, dudy: 0.0, dvdx: 0.0, dvdy: 0.0, shape: Arc::new(Shape::default()), } } 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.common.n = self.n().face_forward(self.shading.n.into()); } self.shading.dpdu = dpdus; self.shading.dpdv = dpdvs; self.shading.dndu = dndus; self.shading.dndv = dndvs; } pub fn new_simple(pi: Point3fi, n: Normal3f, uv: Point2f) -> Self { Self { common: InteractionData { pi, n, time: 0., wo: Vector3f::zero(), medium_interface: None, medium: None, }, uv, ..Default::default() } } pub fn new_minimal(pi: Point3fi, uv: Point2f) -> Self { Self { common: InteractionData { pi, ..Default::default() }, uv, ..Default::default() } } pub fn set_intersection_properties( &mut self, mtl: Arc, area: Arc, prim_medium_interface: Option, ray_medium: Arc, ) { self.material = Some(mtl); self.area_light = Some(area); if prim_medium_interface .as_ref() .is_some_and(|mi| mi.is_medium_transition()) { self.common.medium_interface = prim_medium_interface; } else { self.common.medium = Some(ray_medium); } } } #[derive(Clone, Debug)] pub struct MediumInteraction { pub common: InteractionData, pub medium: Arc, pub phase: PhaseFunction, } impl MediumInteraction { pub fn new( p: Point3f, wo: Vector3f, time: Float, medium: Arc, phase: PhaseFunction, ) -> Self { Self { common: InteractionData { pi: Point3fi::new_from_point(p), n: Normal3f::default(), time, wo: wo.normalize(), medium_interface: None, medium: Some(medium.clone()), }, medium, phase, } } } impl InteractionTrait for MediumInteraction { fn is_medium_interaction(&self) -> bool { true } fn get_common(&self) -> &InteractionData { &self.common } fn get_common_mut(&mut self) -> &mut InteractionData { &mut self.common } }