use crate::{ spectra::{RGB, RGBColorSpace}, utils::{Transform, sampling::PiecewiseConstant2D}, }; #[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 image_id: u32, pub a: Float, pub distrib: PiecewiseConstant2D, pub image_color_space: *const RGBColorSpace, } impl ProjectionLight { pub fn new( render_from_light: Transform, medium_interface: MediumInterface, image_id: u32, image_color_space: RGBColorSpace, scale: Float, fov: Float, ) -> Self { let base = LightBase::new( LightType::DeltaPosition, &render_from_light, &medium_interface, ); let image = Image::new(); let aspect = image.resolution().x() as Float / image.resolution().y() as Float; let screen_bounds = if aspect > 1. { Bounds2f::from_points(Point2f::new(-aspect, -1.), Point2f::new(aspect, 1.)) } else { Bounds2f::from_points( Point2f::new(-1., 1. / aspect), Point2f::new(1., 1. / aspect), ) }; let hither = 1e-3; let screen_from_light = TransformGeneric::perspective(fov, hither, 1e30).unwrap(); let light_from_screen = screen_from_light.inverse(); let opposite = (radians(fov) / 2.).tan(); let aspect_ratio = if aspect > 1. { aspect } else { 1. / aspect }; let a = 4. * square(opposite) * aspect_ratio; let dwda = |p: Point2f| { let w = Vector3f::from(light_from_screen.apply_to_point(Point3f::new(p.x(), p.y(), 0.))); cos_theta(w.normalize()).powi(3) }; let d = image.get_sampling_distribution(dwda, screen_bounds); let distrib = PiecewiseConstant2D::new_with_bounds(&d, screen_bounds); Self { base, image_id, image_color_space, screen_bounds, screen_from_light, light_from_screen, scale, hither, a, distrib, } } 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.as_ref(), RGB::clamp_zero(rgb)); 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 { todo!() } fn pdf_li( &self, _ctx: &LightSampleContext, _wi: Vector3f, _allow_incomplete_pdf: bool, ) -> Float { todo!() } fn l( &self, _p: Point3f, _n: Normal3f, _uv: Point2f, _w: Vector3f, _lambda: &SampledWavelengths, ) -> SampledSpectrum { todo!() } fn le(&self, _ray: &Ray, _lambda: &SampledWavelengths) -> SampledSpectrum { todo!() } fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { let mut sum = SampledSpectrum::new(0.); for y in 0..self.image.resolution.y() { for x in 0..self.image.resolution.x() { let ps = self.screen_bounds.lerp(Point2f::new( (x as Float + 0.5) / self.image.resolution.x() as Float, (y as Float + 0.5) / self.image.resolution.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.as_ref(), RGB::clamp_zero(rgb), ); sum += s.sample(&lambda) * dwda; } } self.scale * self.a * sum / (self.image.resolution.x() * self.image.resolution.y()) as Float } fn preprocess(&mut self, _scene_bounds: &Bounds3f) { todo!() } fn bounds(&self) -> Option { todo!() } }