use crate::camera::{Camera, CameraTrait}; use crate::core::bxdf::{BSDF, BxDFFlags}; use crate::core::material::MaterialTrait; use crate::core::medium::{Medium, MediumInterface}; use crate::core::pbrt::{Float, clamp_t}; use crate::geometry::{ Normal3f, Point2f, Point3f, Point3fi, Ray, RayDifferential, Vector3f, VectorLike, }; use crate::lights::Light; use crate::shapes::Shape; use crate::utils::math::{difference_of_products, square}; 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>, } 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 as_any_mut(&mut self) -> &mut dyn Any; fn into_any(self: Box) -> Box; 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::() } fn is_medium_interaction(&self) -> bool { self.as_any().is::() } fn get_medium(&self, w: Vector3f) -> Option>; fn spawn_ray(&self, d: Vector3f) -> Ray; fn spawn_ray_to_point(&self, p2: Point3f) -> Ray { let origin = self.p(); let direction = p2 - origin; Ray { o: origin, d: direction, time: self.time(), medium: self.get_medium(direction), differential: None, } } 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, pub dpdv: Vector3f, pub dndu: Normal3f, pub dndv: Normal3f, } #[derive(Default, Debug, 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 medium_interface: Option>, 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, pub bsdf: Option, } impl SurfaceInteraction { 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 spawn_ray(&self, d: Vector3f) -> Ray { let mut ray = Ray::spawn(&self.pi(), &self.n(), self.time(), d); ray.medium = self.get_medium(d); ray } pub fn spawn_ray_to(&self, p2: Point3f) -> Ray { let mut ray = Ray::spawn_to_point(&self.pi(), &self.n(), self.time(), p2); ray.medium = self.get_medium(ray.d); ray } // fn get_medium(&self, w: &Vector3f) -> Option> { // if w.dot(self.n().into()) > 0.0 { // self.medium_interface.outside.clone() // } else { // self.medium_interface.inside.clone() // } // } 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 } } #[derive(Default, Debug, Clone)] pub struct PhaseFunction; pub struct MediumInteraction { pub common: InteractionData, pub medium: Arc, pub phase: PhaseFunction, } impl Interaction for SurfaceInteraction { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn into_any(self: Box) -> Box { self } 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.medium_interface.as_ref().and_then(|interface| { if self.n().dot(w.into()) > 0.0 { interface.outside.clone() } else { interface.inside.clone() } }) } fn spawn_ray(&self, d: Vector3f) -> Ray { 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); ray.medium = self.get_medium(ray.d); ray } fn spawn_ray_to_interaction(&self, other: &dyn Interaction) -> Ray { // Check if the other interaction is a surface to use the robust spawn method if let Some(si_to) = other.as_any().downcast_ref::() { 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 { // Fallback for non-surface interactions self.spawn_ray_to_point(other.p()) } } } 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, }, medium_interface: None, 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()), bsdf: None, } } 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 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); } } } impl Interaction for MediumInteraction { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn into_any(self: Box) -> Box { self } 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> { Some(self.medium.clone()) } fn spawn_ray(&self, d: Vector3f) -> Ray { Ray { o: self.p(), d, time: self.time(), medium: Some(self.medium.clone()), differential: None, } } }