Skip to main content

fyrox_impl/scene/light/
directional.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//! Directional light is a light source with parallel rays, it has excellent example in real life - Sun.
22//! It does not have position, only direction which defined by parent light scene node.
23//!
24//! ## Shadows
25//!
26//! Directional light sources uses cascaded shadow maps for shadows. Each cascade has different position
27//! on the viewing frustum and overall split options can be changed by using [`FrustumSplitOptions`].
28
29use crate::{
30    core::{
31        algebra::{UnitQuaternion, Vector3},
32        color::Color,
33        math::aabb::AxisAlignedBoundingBox,
34        pool::Handle,
35        reflect::prelude::*,
36        type_traits::prelude::*,
37        uuid::{uuid, Uuid},
38        uuid_provider,
39        variable::InheritableVariable,
40        visitor::{Visit, VisitResult, Visitor},
41    },
42    scene::{
43        base::{Base, BaseBuilder},
44        debug::SceneDrawingContext,
45        graph::Graph,
46        light::{BaseLight, BaseLightBuilder},
47        node::constructor::NodeConstructor,
48        node::{Node, NodeTrait},
49    },
50};
51use fyrox_graph::constructor::ConstructorProvider;
52use fyrox_graph::SceneGraph;
53use std::ops::{Deref, DerefMut};
54use strum_macros::{AsRefStr, EnumString, VariantNames};
55
56/// Maximum amount of cascades.
57pub const CSM_NUM_CASCADES: usize = 3;
58
59/// Frustum split options defines how to split camera's frustum to generate cascades.
60#[derive(Reflect, Clone, Visit, Debug, PartialEq, AsRefStr, EnumString, VariantNames)]
61pub enum FrustumSplitOptions {
62    /// Camera frustum will be split into a [`CSM_NUM_CASCADES`] splits where each sub-frustum
63    /// will have fixed far plane location.
64    ///
65    /// This option allows you to set far planes very precisely, thus allowing you to set desired
66    /// quality of each cascade.
67    ///
68    /// This is default option.
69    Absolute {
70        /// A fixed set of distances, where each distance sets the location of far plane of
71        /// of sub-frustum. If far plane exceeds far plane of current camera, then cascade will
72        /// be discarded and won't be used for rendering.
73        far_planes: [f32; CSM_NUM_CASCADES],
74    },
75    /// Camera frustum will be split into a [`CSM_NUM_CASCADES`] splits using provided fractions.
76    ///
77    /// This option might give lesser quality results with camera that have large far plane, however
78    /// it does not require any precise tweaking.
79    Relative {
80        /// A fixed set of fractions in `[0; 1]` range which defines how far the far plane of
81        /// sub-frustum will be relative to camera's frustum.
82        fractions: [f32; CSM_NUM_CASCADES],
83    },
84}
85
86uuid_provider!(FrustumSplitOptions = "b2ed128a-b7da-4d34-b027-a0af19c2f563");
87
88impl Default for FrustumSplitOptions {
89    fn default() -> Self {
90        Self::Absolute {
91            far_planes: [5.0, 25.0, 64.0],
92        }
93    }
94}
95
96/// Cascade Shadow Mapping (CSM) options.
97#[derive(Reflect, Clone, Visit, PartialEq, Debug)]
98pub struct CsmOptions {
99    /// See [`FrustumSplitOptions`].
100    pub split_options: FrustumSplitOptions,
101
102    #[reflect(min_value = 0.0, step = 0.000025)]
103    shadow_bias: f32,
104}
105
106impl Default for CsmOptions {
107    fn default() -> Self {
108        Self {
109            split_options: Default::default(),
110            shadow_bias: 0.00025,
111        }
112    }
113}
114
115impl CsmOptions {
116    /// Sets new shadow bias value. Shadow bias allows you to prevent "shadow-acne" effect by
117    /// shifting values fetched from shadow map by a certain value. "Shadow acne" occur due to
118    /// insufficient precision.
119    pub fn set_shadow_bias(&mut self, bias: f32) {
120        self.shadow_bias = bias.max(0.0);
121    }
122
123    /// Returns current shadow bias value.
124    pub fn shadow_bias(&self) -> f32 {
125        self.shadow_bias
126    }
127}
128
129/// See module docs.
130#[derive(Default, Debug, Visit, Reflect, Clone, ComponentProvider)]
131#[reflect(derived_type = "Node")]
132pub struct DirectionalLight {
133    #[component(include)]
134    base_light: BaseLight,
135    /// See [`CsmOptions`].
136    pub csm_options: InheritableVariable<CsmOptions>,
137}
138
139impl From<BaseLight> for DirectionalLight {
140    fn from(base_light: BaseLight) -> Self {
141        Self {
142            base_light,
143            csm_options: Default::default(),
144        }
145    }
146}
147
148impl Deref for DirectionalLight {
149    type Target = Base;
150
151    fn deref(&self) -> &Self::Target {
152        &self.base_light.base
153    }
154}
155
156impl DerefMut for DirectionalLight {
157    fn deref_mut(&mut self) -> &mut Self::Target {
158        &mut self.base_light.base
159    }
160}
161
162impl TypeUuidProvider for DirectionalLight {
163    fn type_uuid() -> Uuid {
164        uuid!("8b8248e1-1cdf-42a3-9abe-0691de82c519")
165    }
166}
167
168impl DirectionalLight {
169    /// Returns a reference to base light.
170    pub fn base_light_ref(&self) -> &BaseLight {
171        &self.base_light
172    }
173
174    /// Returns a reference to base light.
175    pub fn base_light_mut(&mut self) -> &mut BaseLight {
176        &mut self.base_light
177    }
178}
179
180impl ConstructorProvider<Node, Graph> for DirectionalLight {
181    fn constructor() -> NodeConstructor {
182        NodeConstructor::new::<Self>()
183            .with_variant("Directional Light", |_| {
184                DirectionalLightBuilder::new(BaseLightBuilder::new(
185                    BaseBuilder::new().with_name("DirectionalLight"),
186                ))
187                .build_node()
188                .into()
189            })
190            .with_group("Light")
191    }
192}
193
194impl NodeTrait for DirectionalLight {
195    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
196        AxisAlignedBoundingBox::default()
197    }
198
199    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
200        self.local_bounding_box()
201            .transform(&self.global_transform())
202    }
203
204    fn id(&self) -> Uuid {
205        Self::type_uuid()
206    }
207
208    fn debug_draw(&self, ctx: &mut SceneDrawingContext) {
209        ctx.draw_arrow(
210            16,
211            Color::GREEN,
212            1.0,
213            0.2,
214            self.global_transform()
215                * UnitQuaternion::from_axis_angle(&Vector3::x_axis(), 180.0f32.to_radians())
216                    .to_homogeneous(),
217        );
218    }
219}
220
221/// Allows you to build directional light in declarative manner.
222pub struct DirectionalLightBuilder {
223    base_light_builder: BaseLightBuilder,
224    csm_options: CsmOptions,
225}
226
227impl DirectionalLightBuilder {
228    /// Creates new builder instance.
229    pub fn new(base_light_builder: BaseLightBuilder) -> Self {
230        Self {
231            base_light_builder,
232            csm_options: Default::default(),
233        }
234    }
235
236    /// Creates new instance of directional light.
237    pub fn build_directional_light(self) -> DirectionalLight {
238        DirectionalLight {
239            base_light: self.base_light_builder.build(),
240            csm_options: self.csm_options.into(),
241        }
242    }
243
244    /// Sets desired options for cascaded shadow maps.
245    pub fn with_csm_options(mut self, csm_options: CsmOptions) -> Self {
246        self.csm_options = csm_options;
247        self
248    }
249
250    /// Creates new instance of directional light node.
251    pub fn build_node(self) -> Node {
252        Node::new(self.build_directional_light())
253    }
254
255    /// Creates new instance of directional light and adds it to the graph.
256    pub fn build(self, graph: &mut Graph) -> Handle<DirectionalLight> {
257        graph.add_node(self.build_node()).to_variant()
258    }
259}