Skip to main content

fyrox_impl/scene/light/
spot.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Spot light is can be imagined as flash light - it has direction and cone
22//! shape of light volume. It defined by two angles:
23//! 1) Hot spot inner angle - this is zone where intensity of light is max.
24//! 2) Falloff outer angle delta - small angle that adds to hotspot angle and
25//! at this final angle light will have zero intensity. Intensity between those
26//! two angles will have smooth transition.
27//!
28//! Same as point lights, spot lights have distance attenuation which defines
29//! how intensity of light changes over distance to point in world. Currently
30//! engine uses inverse square root law of distance attenuation.
31//!
32//! # Light scattering
33//!
34//! Spot lights support light scattering feature - it means that you will see
35//! light volume itself, not just lighted surfaces. Example from real life: flash
36//! light in the fog. This effect significantly improves perception of light, but
37//! should be used carefully with sane values of light scattering, otherwise you'll
38//! get bright glowing cone instead of slightly visible light volume.
39//!
40//! # Performance notes
41//!
42//! Light scattering feature may significantly impact performance on low-end
43//! hardware!
44
45use crate::scene::base::BaseBuilder;
46use crate::scene::node::constructor::NodeConstructor;
47use crate::{
48    core::{
49        algebra::{Matrix4, UnitQuaternion, Vector3},
50        color::Color,
51        math::{aabb::AxisAlignedBoundingBox, Matrix4Ext},
52        pool::Handle,
53        reflect::prelude::*,
54        type_traits::prelude::*,
55        uuid::{uuid, Uuid},
56        variable::InheritableVariable,
57        visitor::{Visit, VisitResult, Visitor},
58    },
59    resource::texture::TextureResource,
60    scene::{
61        base::Base,
62        debug::SceneDrawingContext,
63        graph::Graph,
64        light::{BaseLight, BaseLightBuilder},
65        node::{Node, NodeTrait},
66    },
67};
68use fyrox_graph::constructor::ConstructorProvider;
69use fyrox_graph::SceneGraph;
70use std::ops::{Deref, DerefMut};
71
72/// See module docs.
73#[derive(Debug, Reflect, Clone, Visit, ComponentProvider)]
74#[reflect(derived_type = "Node")]
75pub struct SpotLight {
76    #[component(include)]
77    base_light: BaseLight,
78
79    #[reflect(min_value = 0.0, max_value = 3.14159, step = 0.1)]
80    #[reflect(setter = "set_hotspot_cone_angle")]
81    hotspot_cone_angle: InheritableVariable<f32>,
82
83    #[reflect(min_value = 0.0, step = 0.1)]
84    #[reflect(setter = "set_falloff_angle_delta")]
85    falloff_angle_delta: InheritableVariable<f32>,
86
87    #[reflect(min_value = 0.0, step = 0.001)]
88    #[reflect(setter = "set_shadow_bias")]
89    shadow_bias: InheritableVariable<f32>,
90
91    #[reflect(min_value = 0.0, step = 0.1)]
92    #[reflect(setter = "set_distance")]
93    distance: InheritableVariable<f32>,
94
95    #[reflect(setter = "set_cookie_texture")]
96    cookie_texture: InheritableVariable<Option<TextureResource>>,
97}
98
99impl Deref for SpotLight {
100    type Target = Base;
101
102    fn deref(&self) -> &Self::Target {
103        &self.base_light.base
104    }
105}
106
107impl DerefMut for SpotLight {
108    fn deref_mut(&mut self) -> &mut Self::Target {
109        &mut self.base_light.base
110    }
111}
112
113impl Default for SpotLight {
114    fn default() -> Self {
115        Self {
116            base_light: Default::default(),
117            hotspot_cone_angle: InheritableVariable::new_modified(90.0f32.to_radians()),
118            falloff_angle_delta: InheritableVariable::new_modified(5.0f32.to_radians()),
119            shadow_bias: InheritableVariable::new_modified(0.00005),
120            distance: InheritableVariable::new_modified(10.0),
121            cookie_texture: InheritableVariable::new_modified(None),
122        }
123    }
124}
125
126impl TypeUuidProvider for SpotLight {
127    fn type_uuid() -> Uuid {
128        uuid!("9856a3c1-ced7-47ec-b682-4dc4dea89d8f")
129    }
130}
131
132impl SpotLight {
133    /// Returns a reference to base light.
134    pub fn base_light_ref(&self) -> &BaseLight {
135        &self.base_light
136    }
137
138    /// Returns a reference to base light.
139    pub fn base_light_mut(&mut self) -> &mut BaseLight {
140        &mut self.base_light
141    }
142
143    /// Returns hotspot angle of light.
144    #[inline]
145    pub fn hotspot_cone_angle(&self) -> f32 {
146        *self.hotspot_cone_angle
147    }
148
149    /// Sets new value of hotspot angle of light.
150    #[inline]
151    pub fn set_hotspot_cone_angle(&mut self, cone_angle: f32) -> f32 {
152        self.hotspot_cone_angle
153            .set_value_and_mark_modified(cone_angle.abs())
154    }
155
156    /// Sets new falloff angle range for spot light.
157    #[inline]
158    pub fn set_falloff_angle_delta(&mut self, delta: f32) -> f32 {
159        self.falloff_angle_delta.set_value_and_mark_modified(delta)
160    }
161
162    /// Returns falloff angle range of light.
163    #[inline]
164    pub fn falloff_angle_delta(&self) -> f32 {
165        *self.falloff_angle_delta
166    }
167
168    /// Returns full angle at top of light cone.
169    #[inline]
170    pub fn full_cone_angle(&self) -> f32 {
171        *self.hotspot_cone_angle + *self.falloff_angle_delta
172    }
173
174    /// Sets new shadow bias value. Bias will be used to offset fragment's depth before
175    /// compare it with shadow map value, it is used to remove "shadow acne".
176    pub fn set_shadow_bias(&mut self, bias: f32) -> f32 {
177        self.shadow_bias.set_value_and_mark_modified(bias)
178    }
179
180    /// Returns current value of shadow bias.
181    pub fn shadow_bias(&self) -> f32 {
182        *self.shadow_bias
183    }
184
185    /// Sets maximum distance at which light intensity will be zero. Intensity
186    /// of light will be calculated using inverse square root law.
187    #[inline]
188    pub fn set_distance(&mut self, distance: f32) -> f32 {
189        self.distance.set_value_and_mark_modified(distance.abs())
190    }
191
192    /// Returns maximum distance of light.
193    #[inline]
194    pub fn distance(&self) -> f32 {
195        *self.distance
196    }
197
198    /// Set cookie texture. Also called gobo this texture gets projected
199    /// by the spot light.
200    #[inline]
201    pub fn set_cookie_texture(
202        &mut self,
203        texture: Option<TextureResource>,
204    ) -> Option<TextureResource> {
205        self.cookie_texture.set_value_and_mark_modified(texture)
206    }
207
208    /// Get cookie texture. Also called gobo this texture gets projected
209    /// by the spot light.
210    #[inline]
211    pub fn cookie_texture(&self) -> Option<TextureResource> {
212        (*self.cookie_texture).clone()
213    }
214
215    /// Get cookie texture by ref. Also called gobo this texture gets projected
216    /// by the spot light.
217    #[inline]
218    pub fn cookie_texture_ref(&self) -> Option<&TextureResource> {
219        self.cookie_texture.as_ref()
220    }
221}
222
223impl ConstructorProvider<Node, Graph> for SpotLight {
224    fn constructor() -> NodeConstructor {
225        NodeConstructor::new::<Self>()
226            .with_variant("Spot Light", |_| {
227                SpotLightBuilder::new(BaseLightBuilder::new(
228                    BaseBuilder::new().with_name("SpotLight"),
229                ))
230                .with_distance(10.0)
231                .with_hotspot_cone_angle(45.0f32.to_radians())
232                .with_falloff_angle_delta(2.0f32.to_radians())
233                .build_node()
234                .into()
235            })
236            .with_group("Light")
237    }
238}
239
240impl NodeTrait for SpotLight {
241    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
242        AxisAlignedBoundingBox::from_radius(self.distance())
243    }
244
245    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
246        // Discard scaling part, light emission distance does not affected by scaling.
247        self.local_bounding_box()
248            .transform(&self.global_transform_without_scaling())
249    }
250
251    fn id(&self) -> Uuid {
252        Self::type_uuid()
253    }
254
255    fn debug_draw(&self, ctx: &mut SceneDrawingContext) {
256        ctx.draw_cone(
257            16,
258            (self.full_cone_angle() * 0.5).tan() * self.distance(),
259            self.distance(),
260            Matrix4::new_translation(&self.global_position())
261                * UnitQuaternion::from_matrix_eps(
262                    &self.global_transform().basis(),
263                    f32::EPSILON,
264                    16,
265                    UnitQuaternion::identity(),
266                )
267                .to_homogeneous()
268                * Matrix4::new_translation(&Vector3::new(0.0, -self.distance() * 0.5, 0.0)),
269            Color::GREEN,
270            false,
271        );
272    }
273}
274
275/// Allows you to build spot light in declarative manner.
276pub struct SpotLightBuilder {
277    base_light_builder: BaseLightBuilder,
278    hotspot_cone_angle: f32,
279    falloff_angle_delta: f32,
280    shadow_bias: f32,
281    distance: f32,
282    cookie_texture: Option<TextureResource>,
283}
284
285impl SpotLightBuilder {
286    /// Creates new builder instance.
287    pub fn new(base_light_builder: BaseLightBuilder) -> Self {
288        Self {
289            base_light_builder,
290            hotspot_cone_angle: 90.0f32.to_radians(),
291            falloff_angle_delta: 5.0f32.to_radians(),
292            shadow_bias: 0.00005,
293            distance: 10.0,
294            cookie_texture: None,
295        }
296    }
297
298    /// Sets desired hot spot cone angle.
299    pub fn with_hotspot_cone_angle(mut self, hotspot_cone_angle: f32) -> Self {
300        self.hotspot_cone_angle = hotspot_cone_angle;
301        self
302    }
303
304    /// Sets desired falloff angle delta.
305    pub fn with_falloff_angle_delta(mut self, falloff_angle_delta: f32) -> Self {
306        self.falloff_angle_delta = falloff_angle_delta;
307        self
308    }
309
310    /// Sets desired light distance.
311    pub fn with_distance(mut self, distance: f32) -> Self {
312        self.distance = distance;
313        self
314    }
315
316    /// Sets desired shadow bias.
317    pub fn with_shadow_bias(mut self, bias: f32) -> Self {
318        self.shadow_bias = bias;
319        self
320    }
321
322    /// Sets the desired cookie/gobo texture.
323    pub fn with_cookie_texture(mut self, texture: TextureResource) -> Self {
324        self.cookie_texture = Some(texture);
325        self
326    }
327
328    /// Creates new spot light.
329    pub fn build_spot_light(self) -> SpotLight {
330        SpotLight {
331            base_light: self.base_light_builder.build(),
332            hotspot_cone_angle: self.hotspot_cone_angle.into(),
333            falloff_angle_delta: self.falloff_angle_delta.into(),
334            shadow_bias: self.shadow_bias.into(),
335            distance: self.distance.into(),
336            cookie_texture: self.cookie_texture.into(),
337        }
338    }
339
340    /// Creates new spot light node.
341    pub fn build_node(self) -> Node {
342        Node::new(self.build_spot_light())
343    }
344
345    /// Creates new spot light instance and adds it to the graph.
346    pub fn build(self, graph: &mut Graph) -> Handle<SpotLight> {
347        graph.add_node(self.build_node()).to_variant()
348    }
349}