use crate::Float; use crate::core::color::RGB; use crate::core::geometry::{ Bounds2f, Bounds3f, Normal3f, Point2f, Point2i, Point3f, Ray, Vector3f, VectorLike, cos_theta, }; use crate::core::image::Image; use crate::core::interaction::{Interaction, InteractionBase, SimpleInteraction}; use crate::core::light::{ LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait, LightType, }; use crate::core::medium::MediumInterface; use crate::core::spectrum::SpectrumTrait; use crate::spectra::{SampledSpectrum, SampledWavelengths}; use crate::utils::math::{radians, square}; use crate::{ spectra::{RGBColorSpace, RGBIlluminantSpectrum}, utils::{Ptr, Transform, sampling::PiecewiseConstant2D}, }; use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct ProjectionLight { pub base: LightBase, pub scale: Float, pub hither: Float, pub screen_bounds: Bounds2f, pub screen_from_light: Transform, pub light_from_screen: Transform, pub a: Float, pub image: Ptr, pub distrib: Ptr, pub image_color_space: Ptr, } impl ProjectionLight { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { if w.z() < self.hither { return SampledSpectrum::new(0.); } let ps = self.screen_from_light.apply_to_point(w.into()); if !self.screen_bounds.contains(Point2f::new(ps.x(), ps.y())) { return SampledSpectrum::new(0.); } let uv = Point2f::from(self.screen_bounds.offset(&Point2f::new(ps.x(), ps.y()))); let mut rgb = RGB::default(); for c in 0..3 { rgb[c] = self.image.lookup_nearest_channel(uv, c); } let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); self.scale * s.sample(lambda) } } impl LightTrait for ProjectionLight { fn base(&self) -> &LightBase { &self.base } fn sample_li( &self, ctx: &LightSampleContext, _u: Point2f, lambda: &SampledWavelengths, _allow_incomplete_pdf: bool, ) -> Option { let render_from_light = self.base().render_from_light; let p = render_from_light.apply_to_point(Point3f::new(0., 0., 0.)); let wi = (p - ctx.p()).normalize(); let wl = render_from_light.apply_inverse_vector(-wi); let li = self.i(wl, lambda) / p.distance_squared(ctx.p()); if li.is_black() { return None; } let base = InteractionBase::new_boundary(p, 0., self.base.medium_interface); let intr = SimpleInteraction::new(base); Some(LightLiSample::new(li, wi, 1., Interaction::Simple(intr))) } fn pdf_li( &self, _ctx: &LightSampleContext, _wi: Vector3f, _allow_incomplete_pdf: bool, ) -> Float { 0. } fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut sum = SampledSpectrum::new(0.); let res = self.image.resolution(); for y in 0..res.y() { for x in 0..res.x() { let ps = self.screen_bounds.lerp(Point2f::new( (x as Float + 0.5) / res.x() as Float, (y as Float + 0.5) / res.y() as Float, )); let w_raw = Vector3f::from(self.light_from_screen.apply_to_point(Point3f::new( ps.x(), ps.y(), 0., ))); let w = w_raw.normalize(); let dwda = cos_theta(w).powi(3); let mut rgb = RGB::default(); for c in 0..3 { rgb[c] = self.image.get_channel(Point2i::new(x, y), c); } let s = RGBIlluminantSpectrum::new(&self.image_color_space, rgb.clamp_zero()); sum += s.sample(&lambda) * dwda; } } self.scale * self.a * sum / (res.x() * res.y()) as Float } fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn bounds(&self) -> Option { let mut sum = 0.; for v in 0..self.image.resolution().y() { for u in 0..self.image.resolution().x() { let uv = Point2i::new(u, v); sum += self.image.get_channel(uv, 0).max( self.image .get_channel(uv, 1) .max(self.image.get_channel(uv, 2)), ); } } let phi = self.scale * sum / (self.image.resolution().x() * self.image.resolution().y()) as f32; let p_corner = Point3f::new( self.screen_bounds.p_max.x(), self.screen_bounds.p_max.y(), 0., ); let w_corner = Vector3f::from(self.light_from_screen.apply_to_point(p_corner)).normalize(); let cos_total_width = cos_theta(w_corner); let p = self .base .render_from_light .apply_to_point(Point3f::new(0., 0., 0.)); let w = self .base .render_from_light .apply_to_vector(Vector3f::new(0., 0., 1.)); Some(LightBounds::new( &Bounds3f::from_points(p, p), w, phi, 1., cos_total_width, false, )) } }