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::BaseSceneGraph;
70use std::ops::{Deref, DerefMut};
71
72/// See module docs.
73#[derive(Debug, Reflect, Clone, Visit, ComponentProvider)]
74pub struct SpotLight {
75    #[component(include)]
76    base_light: BaseLight,
77
78    #[reflect(min_value = 0.0, max_value = 3.14159, step = 0.1)]
79    #[reflect(setter = "set_hotspot_cone_angle")]
80    hotspot_cone_angle: InheritableVariable<f32>,
81
82    #[reflect(min_value = 0.0, step = 0.1)]
83    #[reflect(setter = "set_falloff_angle_delta")]
84    falloff_angle_delta: InheritableVariable<f32>,
85
86    #[reflect(min_value = 0.0, step = 0.001)]
87    #[reflect(setter = "set_shadow_bias")]
88    shadow_bias: InheritableVariable<f32>,
89
90    #[reflect(min_value = 0.0, step = 0.1)]
91    #[reflect(setter = "set_distance")]
92    distance: InheritableVariable<f32>,
93
94    #[reflect(setter = "set_cookie_texture")]
95    cookie_texture: InheritableVariable<Option<TextureResource>>,
96}
97
98impl Deref for SpotLight {
99    type Target = Base;
100
101    fn deref(&self) -> &Self::Target {
102        &self.base_light.base
103    }
104}
105
106impl DerefMut for SpotLight {
107    fn deref_mut(&mut self) -> &mut Self::Target {
108        &mut self.base_light.base
109    }
110}
111
112impl Default for SpotLight {
113    fn default() -> Self {
114        Self {
115            base_light: Default::default(),
116            hotspot_cone_angle: InheritableVariable::new_modified(90.0f32.to_radians()),
117            falloff_angle_delta: InheritableVariable::new_modified(5.0f32.to_radians()),
118            shadow_bias: InheritableVariable::new_modified(0.00005),
119            distance: InheritableVariable::new_modified(10.0),
120            cookie_texture: InheritableVariable::new_modified(None),
121        }
122    }
123}
124
125impl TypeUuidProvider for SpotLight {
126    fn type_uuid() -> Uuid {
127        uuid!("9856a3c1-ced7-47ec-b682-4dc4dea89d8f")
128    }
129}
130
131impl SpotLight {
132    /// Returns a reference to base light.
133    pub fn base_light_ref(&self) -> &BaseLight {
134        &self.base_light
135    }
136
137    /// Returns a reference to base light.
138    pub fn base_light_mut(&mut self) -> &mut BaseLight {
139        &mut self.base_light
140    }
141
142    /// Returns hotspot angle of light.
143    #[inline]
144    pub fn hotspot_cone_angle(&self) -> f32 {
145        *self.hotspot_cone_angle
146    }
147
148    /// Sets new value of hotspot angle of light.
149    #[inline]
150    pub fn set_hotspot_cone_angle(&mut self, cone_angle: f32) -> f32 {
151        self.hotspot_cone_angle
152            .set_value_and_mark_modified(cone_angle.abs())
153    }
154
155    /// Sets new falloff angle range for spot light.
156    #[inline]
157    pub fn set_falloff_angle_delta(&mut self, delta: f32) -> f32 {
158        self.falloff_angle_delta.set_value_and_mark_modified(delta)
159    }
160
161    /// Returns falloff angle range of light.
162    #[inline]
163    pub fn falloff_angle_delta(&self) -> f32 {
164        *self.falloff_angle_delta
165    }
166
167    /// Returns full angle at top of light cone.
168    #[inline]
169    pub fn full_cone_angle(&self) -> f32 {
170        *self.hotspot_cone_angle + *self.falloff_angle_delta
171    }
172
173    /// Sets new shadow bias value. Bias will be used to offset fragment's depth before
174    /// compare it with shadow map value, it is used to remove "shadow acne".
175    pub fn set_shadow_bias(&mut self, bias: f32) -> f32 {
176        self.shadow_bias.set_value_and_mark_modified(bias)
177    }
178
179    /// Returns current value of shadow bias.
180    pub fn shadow_bias(&self) -> f32 {
181        *self.shadow_bias
182    }
183
184    /// Sets maximum distance at which light intensity will be zero. Intensity
185    /// of light will be calculated using inverse square root law.
186    #[inline]
187    pub fn set_distance(&mut self, distance: f32) -> f32 {
188        self.distance.set_value_and_mark_modified(distance.abs())
189    }
190
191    /// Returns maximum distance of light.
192    #[inline]
193    pub fn distance(&self) -> f32 {
194        *self.distance
195    }
196
197    /// Set cookie texture. Also called gobo this texture gets projected
198    /// by the spot light.
199    #[inline]
200    pub fn set_cookie_texture(
201        &mut self,
202        texture: Option<TextureResource>,
203    ) -> Option<TextureResource> {
204        self.cookie_texture.set_value_and_mark_modified(texture)
205    }
206
207    /// Get cookie texture. Also called gobo this texture gets projected
208    /// by the spot light.
209    #[inline]
210    pub fn cookie_texture(&self) -> Option<TextureResource> {
211        (*self.cookie_texture).clone()
212    }
213
214    /// Get cookie texture by ref. Also called gobo this texture gets projected
215    /// by the spot light.
216    #[inline]
217    pub fn cookie_texture_ref(&self) -> Option<&TextureResource> {
218        self.cookie_texture.as_ref()
219    }
220}
221
222impl ConstructorProvider<Node, Graph> for SpotLight {
223    fn constructor() -> NodeConstructor {
224        NodeConstructor::new::<Self>()
225            .with_variant("Spot Light", |_| {
226                SpotLightBuilder::new(BaseLightBuilder::new(
227                    BaseBuilder::new().with_name("SpotLight"),
228                ))
229                .with_distance(10.0)
230                .with_hotspot_cone_angle(45.0f32.to_radians())
231                .with_falloff_angle_delta(2.0f32.to_radians())
232                .build_node()
233                .into()
234            })
235            .with_group("Light")
236    }
237}
238
239impl NodeTrait for SpotLight {
240    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
241        AxisAlignedBoundingBox::from_radius(self.distance())
242    }
243
244    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
245        // Discard scaling part, light emission distance does not affected by scaling.
246        self.local_bounding_box()
247            .transform(&self.global_transform_without_scaling())
248    }
249
250    fn id(&self) -> Uuid {
251        Self::type_uuid()
252    }
253
254    fn debug_draw(&self, ctx: &mut SceneDrawingContext) {
255        ctx.draw_cone(
256            16,
257            (self.full_cone_angle() * 0.5).tan() * self.distance(),
258            self.distance(),
259            Matrix4::new_translation(&self.global_position())
260                * UnitQuaternion::from_matrix_eps(
261                    &self.global_transform().basis(),
262                    f32::EPSILON,
263                    16,
264                    UnitQuaternion::identity(),
265                )
266                .to_homogeneous()
267                * Matrix4::new_translation(&Vector3::new(0.0, -self.distance() * 0.5, 0.0)),
268            Color::GREEN,
269            false,
270        );
271    }
272}
273
274/// Allows you to build spot light in declarative manner.
275pub struct SpotLightBuilder {
276    base_light_builder: BaseLightBuilder,
277    hotspot_cone_angle: f32,
278    falloff_angle_delta: f32,
279    shadow_bias: f32,
280    distance: f32,
281    cookie_texture: Option<TextureResource>,
282}
283
284impl SpotLightBuilder {
285    /// Creates new builder instance.
286    pub fn new(base_light_builder: BaseLightBuilder) -> Self {
287        Self {
288            base_light_builder,
289            hotspot_cone_angle: 90.0f32.to_radians(),
290            falloff_angle_delta: 5.0f32.to_radians(),
291            shadow_bias: 0.00005,
292            distance: 10.0,
293            cookie_texture: None,
294        }
295    }
296
297    /// Sets desired hot spot cone angle.
298    pub fn with_hotspot_cone_angle(mut self, hotspot_cone_angle: f32) -> Self {
299        self.hotspot_cone_angle = hotspot_cone_angle;
300        self
301    }
302
303    /// Sets desired falloff angle delta.
304    pub fn with_falloff_angle_delta(mut self, falloff_angle_delta: f32) -> Self {
305        self.falloff_angle_delta = falloff_angle_delta;
306        self
307    }
308
309    /// Sets desired light distance.
310    pub fn with_distance(mut self, distance: f32) -> Self {
311        self.distance = distance;
312        self
313    }
314
315    /// Sets desired shadow bias.
316    pub fn with_shadow_bias(mut self, bias: f32) -> Self {
317        self.shadow_bias = bias;
318        self
319    }
320
321    /// Sets the desired cookie/gobo texture.
322    pub fn with_cookie_texture(mut self, texture: TextureResource) -> Self {
323        self.cookie_texture = Some(texture);
324        self
325    }
326
327    /// Creates new spot light.
328    pub fn build_spot_light(self) -> SpotLight {
329        SpotLight {
330            base_light: self.base_light_builder.build(),
331            hotspot_cone_angle: self.hotspot_cone_angle.into(),
332            falloff_angle_delta: self.falloff_angle_delta.into(),
333            shadow_bias: self.shadow_bias.into(),
334            distance: self.distance.into(),
335            cookie_texture: self.cookie_texture.into(),
336        }
337    }
338
339    /// Creates new spot light node.
340    pub fn build_node(self) -> Node {
341        Node::new(self.build_spot_light())
342    }
343
344    /// Creates new spot light instance and adds it to the graph.
345    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
346        graph.add_node(self.build_node())
347    }
348}