use crate::core::geometry::{ Bounds3f, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector3f, VectorLike, }; use crate::core::interaction::{Interaction, InteractionBase, InteractionTrait, SimpleInteraction}; use crate::core::light::{LightBase, LightBounds, LightLiSample, LightSampleContext, LightTrait}; use crate::core::spectrum::SpectrumTrait; use crate::spectra::{DenselySampledSpectrum, SampledSpectrum, SampledWavelengths}; use crate::{Float, PI, Ptr, Transform}; use num_traits::Float as NumFloat; #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct SpotLight { pub base: LightBase, pub iemit: Ptr, pub scale: Float, pub cos_falloff_start: Float, pub cos_falloff_end: Float, } impl SpotLight { pub fn i(&self, w: Vector3f, lambda: &SampledWavelengths) -> SampledSpectrum { let cos_theta = w.z(); // assuming normalized in light space let falloff = crate::utils::math::smooth_step( cos_theta, self.cos_falloff_end, self.cos_falloff_start, ); falloff * self.scale * self.iemit.sample(lambda) } } impl LightTrait for SpotLight { fn base(&self) -> &LightBase { &self.base } fn sample_li( &self, ctx: &LightSampleContext, _u: Point2f, lambda: &SampledWavelengths, _allow_incomplete_pdf: bool, ) -> Option { let pi = self .base .render_from_light .apply_to_interval(&Point3fi::default()); let p: Point3f = pi.into(); let wi = (p - ctx.p()).normalize(); let w_light = self.base.render_from_light.apply_inverse_vector(-wi); let li = self.i(w_light, lambda) / p.distance_squared(ctx.p()); 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. } #[cfg(not(gpu))] fn phi(&self, lambda: SampledWavelengths) -> SampledSpectrum { self.scale * self.iemit.sample(&lambda) * 2. * PI * ((1. - self.cos_falloff_start) + (self.cos_falloff_start - self.cos_falloff_end) / 2.) } fn preprocess(&mut self, _scene_bounds: &Bounds3f) {} fn bounds(&self) -> Option { let p = self .base .render_from_light .apply_to_point(Point3f::default()); let w = self .base .render_from_light .apply_to_vector(Vector3f::new(0., 0., 1.)) .normalize(); let phi = self.scale * self.iemit.max_value() * 4. * PI; let cos_theta_e = (self.cos_falloff_end.acos() - self.cos_falloff_start.acos()).cos(); Some(LightBounds::new( &Bounds3f::from_points(p, p), w, phi, self.cos_falloff_start, cos_theta_e, false, )) } }