fyrox_impl/scene/
camera.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//! Contains all methods and structures to create and manage cameras. See [`Camera`] docs for more info.
22
23use crate::scene::node::constructor::NodeConstructor;
24use crate::{
25    asset::{
26        embedded_data_source, manager::BuiltInResource, state::LoadError, untyped::ResourceKind,
27    },
28    core::{
29        algebra::{Matrix4, Point3, Vector2, Vector3, Vector4},
30        color::Color,
31        log::Log,
32        math::{aabb::AxisAlignedBoundingBox, frustum::Frustum, ray::Ray, Rect},
33        pool::Handle,
34        reflect::prelude::*,
35        type_traits::prelude::*,
36        uuid::{uuid, Uuid},
37        uuid_provider,
38        variable::InheritableVariable,
39        visitor::{Visit, VisitResult, Visitor},
40    },
41    graph::BaseSceneGraph,
42    resource::texture::{
43        CompressionOptions, Texture, TextureImportOptions, TextureKind, TextureMinificationFilter,
44        TexturePixelKind, TextureResource, TextureResourceExtension, TextureWrapMode,
45    },
46    scene::{
47        base::{Base, BaseBuilder},
48        debug::SceneDrawingContext,
49        graph::Graph,
50        node::{Node, NodeTrait, UpdateContext},
51    },
52};
53use fyrox_graph::constructor::ConstructorProvider;
54use lazy_static::lazy_static;
55use serde::{Deserialize, Serialize};
56use std::{
57    fmt::{Display, Formatter},
58    ops::{Deref, DerefMut},
59};
60use strum_macros::{AsRefStr, EnumString, VariantNames};
61
62/// Perspective projection make parallel lines to converge at some point. Objects will be smaller
63/// with increasing distance. This the projection type "used" by human eyes, photographic lens and
64/// it looks most realistic.
65#[derive(Reflect, Clone, Debug, PartialEq, Visit, Serialize, Deserialize)]
66pub struct PerspectiveProjection {
67    /// Vertical angle at the top of viewing frustum, in radians. Larger values will increase field
68    /// of view and create fish-eye effect, smaller values could be used to create "binocular" effect
69    /// or scope effect.
70    #[reflect(min_value = 0.0, max_value = 6.28, step = 0.1)]
71    pub fov: f32,
72    /// Location of the near clipping plane. If it is larger than [`Self::z_far`] then it will be
73    /// treated like far clipping plane.
74    #[reflect(min_value = 0.0, step = 0.1)]
75    pub z_near: f32,
76    /// Location of the far clipping plane. If it is less than [`Self::z_near`] then it will be
77    /// treated like near clipping plane.
78    #[reflect(min_value = 0.0, step = 0.1)]
79    pub z_far: f32,
80}
81
82impl Default for PerspectiveProjection {
83    fn default() -> Self {
84        Self {
85            fov: 75.0f32.to_radians(),
86            z_near: 0.025,
87            z_far: 2048.0,
88        }
89    }
90}
91
92impl PerspectiveProjection {
93    /// Returns perspective projection matrix.
94    #[inline]
95    pub fn matrix(&self, frame_size: Vector2<f32>) -> Matrix4<f32> {
96        let limit = 10.0 * f32::EPSILON;
97
98        let z_near = self.z_far.min(self.z_near);
99        let mut z_far = self.z_far.max(self.z_near);
100
101        // Prevent planes from superimposing which could cause panic.
102        if z_far - z_near < limit {
103            z_far += limit;
104        }
105
106        Matrix4::new_perspective(
107            (frame_size.x / frame_size.y).max(limit),
108            self.fov,
109            z_near,
110            z_far,
111        )
112    }
113}
114
115/// Parallel projection. Object's size won't be affected by distance from the viewer, it can be
116/// used for 2D games.
117#[derive(Reflect, Clone, Debug, PartialEq, Visit, Serialize, Deserialize)]
118pub struct OrthographicProjection {
119    /// Location of the near clipping plane. If it is larger than [`Self::z_far`] then it will be
120    /// treated like far clipping plane.
121    #[reflect(min_value = 0.0, step = 0.1)]
122    pub z_near: f32,
123    /// Location of the far clipping plane. If it is less than [`Self::z_near`] then it will be
124    /// treated like near clipping plane.
125    #[reflect(min_value = 0.0, step = 0.1)]
126    pub z_far: f32,
127    /// Vertical size of the "view box". Horizontal size is derived value and depends on the aspect
128    /// ratio of the viewport. Any values very close to zero (from both sides) will be clamped to
129    /// some minimal value to prevent singularities from occuring.
130    #[reflect(step = 0.1)]
131    pub vertical_size: f32,
132}
133
134impl Default for OrthographicProjection {
135    fn default() -> Self {
136        Self {
137            z_near: 0.0,
138            z_far: 2048.0,
139            vertical_size: 5.0,
140        }
141    }
142}
143
144impl OrthographicProjection {
145    /// Returns orthographic projection matrix.
146    #[inline]
147    pub fn matrix(&self, frame_size: Vector2<f32>) -> Matrix4<f32> {
148        fn clamp_to_limit_signed(value: f32, limit: f32) -> f32 {
149            if value < 0.0 && -value < limit {
150                -limit
151            } else if value >= 0.0 && value < limit {
152                limit
153            } else {
154                value
155            }
156        }
157
158        let limit = 10.0 * f32::EPSILON;
159
160        let aspect = (frame_size.x / frame_size.y).max(limit);
161
162        // Prevent collapsing projection "box" into a point, which could cause panic.
163        let vertical_size = clamp_to_limit_signed(self.vertical_size, limit);
164        let horizontal_size = clamp_to_limit_signed(aspect * vertical_size, limit);
165
166        let z_near = self.z_far.min(self.z_near);
167        let mut z_far = self.z_far.max(self.z_near);
168
169        // Prevent planes from superimposing which could cause panic.
170        if z_far - z_near < limit {
171            z_far += limit;
172        }
173
174        let left = -horizontal_size;
175        let top = vertical_size;
176        let right = horizontal_size;
177        let bottom = -vertical_size;
178        Matrix4::new_orthographic(left, right, bottom, top, z_near, z_far)
179    }
180}
181
182/// A method of projection. Different projection types suitable for different purposes:
183///
184/// 1) Perspective projection most useful for 3D games, it makes a scene to look most natural,
185/// objects will look smaller with increasing distance.
186/// 2) Orthographic projection most useful for 2D games, objects won't look smaller with increasing
187/// distance.
188#[derive(
189    Reflect,
190    Clone,
191    Debug,
192    PartialEq,
193    Visit,
194    AsRefStr,
195    EnumString,
196    VariantNames,
197    Serialize,
198    Deserialize,
199)]
200pub enum Projection {
201    /// See [`PerspectiveProjection`] docs.
202    Perspective(PerspectiveProjection),
203    /// See [`OrthographicProjection`] docs.
204    Orthographic(OrthographicProjection),
205}
206
207uuid_provider!(Projection = "0eb5bec0-fc4e-4945-99b6-e6c5392ad971");
208
209impl Projection {
210    /// Sets the new value for the near clipping plane.
211    #[inline]
212    #[must_use]
213    pub fn with_z_near(mut self, z_near: f32) -> Self {
214        match self {
215            Projection::Perspective(ref mut v) => v.z_near = z_near,
216            Projection::Orthographic(ref mut v) => v.z_near = z_near,
217        }
218        self
219    }
220
221    /// Sets the new value for the far clipping plane.
222    #[inline]
223    #[must_use]
224    pub fn with_z_far(mut self, z_far: f32) -> Self {
225        match self {
226            Projection::Perspective(ref mut v) => v.z_far = z_far,
227            Projection::Orthographic(ref mut v) => v.z_far = z_far,
228        }
229        self
230    }
231
232    /// Sets the new value for the near clipping plane.
233    #[inline]
234    pub fn set_z_near(&mut self, z_near: f32) {
235        match self {
236            Projection::Perspective(v) => v.z_near = z_near,
237            Projection::Orthographic(v) => v.z_near = z_near,
238        }
239    }
240
241    /// Sets the new value for the far clipping plane.
242    #[inline]
243    pub fn set_z_far(&mut self, z_far: f32) {
244        match self {
245            Projection::Perspective(v) => v.z_far = z_far,
246            Projection::Orthographic(v) => v.z_far = z_far,
247        }
248    }
249
250    /// Returns near clipping plane distance.
251    #[inline]
252    pub fn z_near(&self) -> f32 {
253        match self {
254            Projection::Perspective(v) => v.z_near,
255            Projection::Orthographic(v) => v.z_near,
256        }
257    }
258
259    /// Returns far clipping plane distance.
260    #[inline]
261    pub fn z_far(&self) -> f32 {
262        match self {
263            Projection::Perspective(v) => v.z_far,
264            Projection::Orthographic(v) => v.z_far,
265        }
266    }
267
268    /// Returns projection matrix.
269    #[inline]
270    pub fn matrix(&self, frame_size: Vector2<f32>) -> Matrix4<f32> {
271        match self {
272            Projection::Perspective(v) => v.matrix(frame_size),
273            Projection::Orthographic(v) => v.matrix(frame_size),
274        }
275    }
276
277    /// Returns `true` if the current projection is perspective.
278    #[inline]
279    pub fn is_perspective(&self) -> bool {
280        matches!(self, Projection::Perspective(_))
281    }
282
283    /// Returns `true` if the current projection is orthographic.
284    #[inline]
285    pub fn is_orthographic(&self) -> bool {
286        matches!(self, Projection::Orthographic(_))
287    }
288}
289
290impl Default for Projection {
291    fn default() -> Self {
292        Self::Perspective(PerspectiveProjection::default())
293    }
294}
295
296/// Exposure is a parameter that describes how many light should be collected for one
297/// frame. The higher the value, the more brighter the final frame will be and vice versa.
298#[derive(Visit, Copy, Clone, PartialEq, Debug, Reflect, AsRefStr, EnumString, VariantNames)]
299pub enum Exposure {
300    /// Automatic exposure based on the frame luminance. High luminance values will result
301    /// in lower exposure levels and vice versa. This is default option.
302    ///
303    /// # Equation
304    ///
305    /// `exposure = key_value / clamp(avg_luminance, min_luminance, max_luminance)`
306    Auto {
307        /// A key value in the formula above. Default is 0.01556.
308        #[reflect(min_value = 0.0, step = 0.1)]
309        key_value: f32,
310        /// A min luminance value in the formula above. Default is 0.00778.
311        #[reflect(min_value = 0.0, step = 0.1)]
312        min_luminance: f32,
313        /// A max luminance value in the formula above. Default is 64.0.
314        #[reflect(min_value = 0.0, step = 0.1)]
315        max_luminance: f32,
316    },
317
318    /// Specific exposure level. To "disable" any HDR effects use [`std::f32::consts::E`] as a value.
319    Manual(f32),
320}
321
322uuid_provider!(Exposure = "0e35ee3d-8baa-4b0c-b3dd-6c31a08c121e");
323
324impl Default for Exposure {
325    fn default() -> Self {
326        Self::Auto {
327            key_value: 0.01556,
328            min_luminance: 0.00778,
329            max_luminance: 64.0,
330        }
331    }
332}
333
334/// Camera allows you to see world from specific point in world. You must have at least one camera in
335/// your scene to see anything.
336///
337/// ## Projection
338///
339/// There are two main projection modes supported by Camera node: perspective and orthogonal projections.
340/// Perspective projection is used primarily to display 3D scenes, while orthogonal projection could be
341/// used for both 3D and 2D. Orthogonal projection could also be used in CAD software.
342///
343/// ## Skybox
344///
345/// Skybox is a cube around the camera with six textures forming seamless "sky". It could be anything,
346/// starting from simple blue sky and ending with outer space.
347///
348/// ## Multiple cameras
349///
350/// Fyrox supports multiple cameras per scene, it means that you can create split screen games, make
351/// picture-in-picture insertions in your main camera view and any other combinations you need.
352///
353/// ## Performance
354///
355/// Each camera forces engine to re-render same scene one more time, which may cause almost double load
356/// of your GPU.
357#[derive(Debug, Visit, Reflect, Clone, ComponentProvider)]
358pub struct Camera {
359    base: Base,
360
361    #[reflect(setter = "set_projection")]
362    projection: InheritableVariable<Projection>,
363
364    #[reflect(setter = "set_viewport")]
365    viewport: InheritableVariable<Rect<f32>>,
366
367    #[reflect(setter = "set_enabled")]
368    enabled: InheritableVariable<bool>,
369
370    #[reflect(setter = "set_skybox")]
371    sky_box: InheritableVariable<Option<SkyBox>>,
372
373    #[reflect(setter = "set_environment")]
374    environment: InheritableVariable<Option<TextureResource>>,
375
376    #[reflect(setter = "set_exposure")]
377    exposure: InheritableVariable<Exposure>,
378
379    #[reflect(setter = "set_color_grading_lut")]
380    color_grading_lut: InheritableVariable<Option<ColorGradingLut>>,
381
382    #[reflect(setter = "set_color_grading_enabled")]
383    color_grading_enabled: InheritableVariable<bool>,
384
385    #[visit(skip)]
386    #[reflect(hidden)]
387    view_matrix: Matrix4<f32>,
388
389    #[visit(skip)]
390    #[reflect(hidden)]
391    projection_matrix: Matrix4<f32>,
392}
393
394impl Deref for Camera {
395    type Target = Base;
396
397    fn deref(&self) -> &Self::Target {
398        &self.base
399    }
400}
401
402impl DerefMut for Camera {
403    fn deref_mut(&mut self) -> &mut Self::Target {
404        &mut self.base
405    }
406}
407
408impl Default for Camera {
409    fn default() -> Self {
410        CameraBuilder::new(BaseBuilder::new()).build_camera()
411    }
412}
413
414impl TypeUuidProvider for Camera {
415    fn type_uuid() -> Uuid {
416        uuid!("198d3aca-433c-4ce1-bb25-3190699b757f")
417    }
418}
419
420/// A set of camera fitting parameters for different projection modes. You should take these parameters
421/// and modify camera position and projection accordingly. In case of perspective projection all you need
422/// to do is to set new world-space position of the camera. In cae of orthographic projection, do previous
423/// step and also modify vertical size of orthographic projection (see [`OrthographicProjection`] for more
424/// info).
425pub enum FitParameters {
426    /// Fitting parameters for perspective projection.
427    Perspective {
428        /// New world-space position of the camera.
429        position: Vector3<f32>,
430        /// Distance from the center of an AABB of the object to the `position`.
431        distance: f32,
432    },
433    /// Fitting parameters for orthographic projection.
434    Orthographic {
435        /// New world-space position of the camera.
436        position: Vector3<f32>,
437        /// New vertical size for orthographic projection.
438        vertical_size: f32,
439    },
440}
441
442impl Camera {
443    /// Explicitly calculates view and projection matrices. Normally, you should not call
444    /// this method, it will be called automatically when new frame starts.
445    #[inline]
446    pub fn calculate_matrices(&mut self, frame_size: Vector2<f32>) {
447        let pos = self.base.global_position();
448        let look = self.base.look_vector();
449        let up = self.base.up_vector();
450
451        self.view_matrix = Matrix4::look_at_rh(&Point3::from(pos), &Point3::from(pos + look), &up);
452        self.projection_matrix = self.projection.matrix(frame_size);
453    }
454
455    /// Sets new viewport in resolution-independent format. In other words
456    /// each parameter of viewport defines portion of your current resolution
457    /// in percents. In example viewport (0.0, 0.0, 0.5, 1.0) will force camera
458    /// to use left half of your screen and (0.5, 0.0, 0.5, 1.0) - right half.
459    /// Why not just use pixels directly? Because you can change resolution while
460    /// your application is running and you'd be force to manually recalculate
461    /// pixel values everytime when resolution changes.
462    pub fn set_viewport(&mut self, mut viewport: Rect<f32>) -> Rect<f32> {
463        viewport.position.x = viewport.position.x.clamp(0.0, 1.0);
464        viewport.position.y = viewport.position.y.clamp(0.0, 1.0);
465        viewport.size.x = viewport.size.x.clamp(0.0, 1.0);
466        viewport.size.y = viewport.size.y.clamp(0.0, 1.0);
467        self.viewport.set_value_and_mark_modified(viewport)
468    }
469
470    /// Returns current viewport.
471    pub fn viewport(&self) -> Rect<f32> {
472        *self.viewport
473    }
474
475    /// Calculates viewport rectangle in pixels based on internal resolution-independent
476    /// viewport. It is useful when you need to get real viewport rectangle in pixels.
477    ///
478    /// # Notes
479    ///
480    /// Viewport cannot be less than 1x1 pixel in size, so the method clamps values to
481    /// range `[1; infinity]`. This is strictly needed because having viewport of 0 in size
482    /// will cause panics in various places. It happens because viewport size is used as
483    /// divisor in math formulas, but you cannot divide by zero.
484    #[inline]
485    pub fn viewport_pixels(&self, frame_size: Vector2<f32>) -> Rect<i32> {
486        Rect::new(
487            (self.viewport.x() * frame_size.x) as i32,
488            (self.viewport.y() * frame_size.y) as i32,
489            ((self.viewport.w() * frame_size.x) as i32).max(1),
490            ((self.viewport.h() * frame_size.y) as i32).max(1),
491        )
492    }
493
494    /// Returns current view-projection matrix.
495    #[inline]
496    pub fn view_projection_matrix(&self) -> Matrix4<f32> {
497        self.projection_matrix * self.view_matrix
498    }
499
500    /// Returns current projection matrix.
501    #[inline]
502    pub fn projection_matrix(&self) -> Matrix4<f32> {
503        self.projection_matrix
504    }
505
506    /// Returns current view matrix.
507    #[inline]
508    pub fn view_matrix(&self) -> Matrix4<f32> {
509        self.view_matrix
510    }
511
512    /// Returns inverse view matrix.
513    #[inline]
514    pub fn inv_view_matrix(&self) -> Option<Matrix4<f32>> {
515        self.view_matrix.try_inverse()
516    }
517
518    /// Returns current projection mode.
519    #[inline]
520    pub fn projection(&self) -> &Projection {
521        &self.projection
522    }
523
524    /// Returns current projection mode.
525    #[inline]
526    pub fn projection_value(&self) -> Projection {
527        (*self.projection).clone()
528    }
529
530    /// Returns current projection mode as mutable reference.
531    #[inline]
532    pub fn projection_mut(&mut self) -> &mut Projection {
533        self.projection.get_value_mut_and_mark_modified()
534    }
535
536    /// Sets current projection mode.
537    #[inline]
538    pub fn set_projection(&mut self, projection: Projection) -> Projection {
539        self.projection.set_value_and_mark_modified(projection)
540    }
541
542    /// Returns state of camera: enabled or not.
543    #[inline]
544    pub fn is_enabled(&self) -> bool {
545        *self.enabled
546    }
547
548    /// Enables or disables camera. Disabled cameras will be ignored during
549    /// rendering. This allows you to exclude views from specific cameras from
550    /// final picture.
551    #[inline]
552    pub fn set_enabled(&mut self, enabled: bool) -> bool {
553        self.enabled.set_value_and_mark_modified(enabled)
554    }
555
556    /// Sets new skybox. Could be None if no skybox needed.
557    pub fn set_skybox(&mut self, skybox: Option<SkyBox>) -> Option<SkyBox> {
558        self.sky_box.set_value_and_mark_modified(skybox)
559    }
560
561    /// Return optional mutable reference to current skybox.
562    pub fn skybox_mut(&mut self) -> Option<&mut SkyBox> {
563        self.sky_box.get_value_mut_and_mark_modified().as_mut()
564    }
565
566    /// Return optional shared reference to current skybox.
567    pub fn skybox_ref(&self) -> Option<&SkyBox> {
568        self.sky_box.as_ref()
569    }
570
571    /// Replaces the skybox.
572    pub fn replace_skybox(&mut self, new: Option<SkyBox>) -> Option<SkyBox> {
573        std::mem::replace(self.sky_box.get_value_mut_and_mark_modified(), new)
574    }
575
576    /// Sets new environment.
577    pub fn set_environment(
578        &mut self,
579        environment: Option<TextureResource>,
580    ) -> Option<TextureResource> {
581        self.environment.set_value_and_mark_modified(environment)
582    }
583
584    /// Return optional mutable reference to current environment.
585    pub fn environment_mut(&mut self) -> Option<&mut TextureResource> {
586        self.environment.get_value_mut_and_mark_modified().as_mut()
587    }
588
589    /// Return optional shared reference to current environment.
590    pub fn environment_ref(&self) -> Option<&TextureResource> {
591        self.environment.as_ref()
592    }
593
594    /// Return current environment map.
595    pub fn environment_map(&self) -> Option<TextureResource> {
596        (*self.environment).clone()
597    }
598
599    /// Creates picking ray from given screen coordinates.
600    pub fn make_ray(&self, screen_coord: Vector2<f32>, screen_size: Vector2<f32>) -> Ray {
601        let viewport = self.viewport_pixels(screen_size);
602        let nx = screen_coord.x / (viewport.w() as f32) * 2.0 - 1.0;
603        // Invert y here because OpenGL has origin at left bottom corner,
604        // but window coordinates starts from left *upper* corner.
605        let ny = (viewport.h() as f32 - screen_coord.y) / (viewport.h() as f32) * 2.0 - 1.0;
606        let inv_view_proj = self
607            .view_projection_matrix()
608            .try_inverse()
609            .unwrap_or_default();
610        let near = inv_view_proj * Vector4::new(nx, ny, -1.0, 1.0);
611        let far = inv_view_proj * Vector4::new(nx, ny, 1.0, 1.0);
612        let begin = near.xyz().scale(1.0 / near.w);
613        let end = far.xyz().scale(1.0 / far.w);
614        Ray::from_two_points(begin, end)
615    }
616
617    /// Calculates new fitting parameters for the given axis-aligned bounding box using current camera's
618    /// global transform and provided aspect ratio. See [`FitParameters`] docs for more info.
619    ///
620    /// This method returns fitting parameters and **do not** modify camera's state. It is needed, because in
621    /// some cases your camera could be attached to some sort of a hinge node and setting its local position
622    /// in order to fit it to the given AABB would break the preset spatial relations between nodes. Instead,
623    /// the method returns a set of parameters that can be used as you want.
624    #[inline]
625    #[must_use]
626    pub fn fit(&self, aabb: &AxisAlignedBoundingBox, aspect_ratio: f32) -> FitParameters {
627        let look_vector = self
628            .look_vector()
629            .try_normalize(f32::EPSILON)
630            .unwrap_or_default();
631
632        match self.projection.deref() {
633            Projection::Perspective(perspective) => {
634                let radius = aabb.half_extents().max();
635                let distance = radius / (perspective.fov * 0.5).sin();
636
637                FitParameters::Perspective {
638                    position: aabb.center() - look_vector.scale(distance),
639                    distance,
640                }
641            }
642            Projection::Orthographic(_) => {
643                let mut min_x = f32::MAX;
644                let mut min_y = f32::MAX;
645                let mut max_x = -f32::MAX;
646                let mut max_y = -f32::MAX;
647                let inv = self.global_transform().try_inverse().unwrap_or_default();
648                for point in aabb.corners() {
649                    let local = inv.transform_point(&Point3::from(point));
650                    if local.x < min_x {
651                        min_x = local.x;
652                    }
653                    if local.y < min_y {
654                        min_y = local.y;
655                    }
656                    if local.x > max_x {
657                        max_x = local.x;
658                    }
659                    if local.y > max_y {
660                        max_y = local.y;
661                    }
662                }
663
664                FitParameters::Orthographic {
665                    position: aabb.center() - look_vector.scale((aabb.max - aabb.min).norm()),
666                    vertical_size: (max_y - min_y).max((max_x - min_x) * aspect_ratio),
667                }
668            }
669        }
670    }
671
672    /// Returns current frustum of the camera.
673    #[inline]
674    pub fn frustum(&self) -> Frustum {
675        Frustum::from_view_projection_matrix(self.view_projection_matrix()).unwrap_or_default()
676    }
677
678    /// Projects given world space point on screen plane.
679    pub fn project(
680        &self,
681        world_pos: Vector3<f32>,
682        screen_size: Vector2<f32>,
683    ) -> Option<Vector2<f32>> {
684        let viewport = self.viewport_pixels(screen_size);
685        let proj = self.view_projection_matrix()
686            * Vector4::new(world_pos.x, world_pos.y, world_pos.z, 1.0);
687        if proj.w != 0.0 && proj.z >= 0.0 {
688            let k = (1.0 / proj.w) * 0.5;
689            Some(Vector2::new(
690                viewport.x() as f32 + viewport.w() as f32 * (proj.x * k + 0.5),
691                viewport.h() as f32
692                    - (viewport.y() as f32 + viewport.h() as f32 * (proj.y * k + 0.5)),
693            ))
694        } else {
695            None
696        }
697    }
698
699    /// Sets new color grading LUT.
700    pub fn set_color_grading_lut(
701        &mut self,
702        lut: Option<ColorGradingLut>,
703    ) -> Option<ColorGradingLut> {
704        self.color_grading_lut.set_value_and_mark_modified(lut)
705    }
706
707    /// Returns current color grading map.
708    pub fn color_grading_lut(&self) -> Option<ColorGradingLut> {
709        (*self.color_grading_lut).clone()
710    }
711
712    /// Returns current color grading map by ref.
713    pub fn color_grading_lut_ref(&self) -> Option<&ColorGradingLut> {
714        self.color_grading_lut.as_ref()
715    }
716
717    /// Enables or disables color grading.
718    pub fn set_color_grading_enabled(&mut self, enable: bool) -> bool {
719        self.color_grading_enabled
720            .set_value_and_mark_modified(enable)
721    }
722
723    /// Whether color grading enabled or not.
724    pub fn color_grading_enabled(&self) -> bool {
725        *self.color_grading_enabled
726    }
727
728    /// Sets new exposure. See `Exposure` struct docs for more info.
729    pub fn set_exposure(&mut self, exposure: Exposure) -> Exposure {
730        self.exposure.set_value_and_mark_modified(exposure)
731    }
732
733    /// Returns current exposure value.
734    pub fn exposure(&self) -> Exposure {
735        *self.exposure
736    }
737}
738
739impl ConstructorProvider<Node, Graph> for Camera {
740    fn constructor() -> NodeConstructor {
741        NodeConstructor::new::<Self>().with_variant("Camera", |_| {
742            CameraBuilder::new(BaseBuilder::new().with_name("Camera"))
743                .build_node()
744                .into()
745        })
746    }
747}
748
749impl NodeTrait for Camera {
750    /// Returns current **local-space** bounding box.
751    #[inline]
752    fn local_bounding_box(&self) -> AxisAlignedBoundingBox {
753        // TODO: Maybe calculate AABB using frustum corners?
754        self.base.local_bounding_box()
755    }
756
757    /// Returns current **world-space** bounding box.
758    fn world_bounding_box(&self) -> AxisAlignedBoundingBox {
759        self.base.world_bounding_box()
760    }
761
762    fn id(&self) -> Uuid {
763        Self::type_uuid()
764    }
765
766    fn update(&mut self, context: &mut UpdateContext) {
767        self.calculate_matrices(context.frame_size);
768    }
769
770    fn debug_draw(&self, ctx: &mut SceneDrawingContext) {
771        let transform = self.global_transform.get();
772        ctx.draw_pyramid(
773            self.frustum().center(),
774            self.frustum().right_top_front_corner(),
775            self.frustum().left_top_front_corner(),
776            self.frustum().left_bottom_front_corner(),
777            self.frustum().right_bottom_front_corner(),
778            Color::GREEN,
779            transform,
780        );
781    }
782}
783
784/// All possible error that may occur during color grading look-up table creation.
785#[derive(Debug)]
786pub enum ColorGradingLutCreationError {
787    /// There is not enough data in provided texture to build LUT.
788    NotEnoughData {
789        /// Required amount of bytes.
790        required: usize,
791        /// Actual data size.
792        current: usize,
793    },
794
795    /// Pixel format is not supported. It must be either RGB8 or RGBA8.
796    InvalidPixelFormat(TexturePixelKind),
797
798    /// Texture error.
799    Texture(LoadError),
800}
801
802impl Display for ColorGradingLutCreationError {
803    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
804        match self {
805            ColorGradingLutCreationError::NotEnoughData { required, current } => {
806                write!(
807                    f,
808                    "There is not enough data in provided \
809                texture to build LUT. Required: {required}, current: {current}.",
810                )
811            }
812            ColorGradingLutCreationError::InvalidPixelFormat(v) => {
813                write!(
814                    f,
815                    "Pixel format is not supported. It must be either RGB8 \
816                or RGBA8, but texture has {v:?} pixel format"
817                )
818            }
819            ColorGradingLutCreationError::Texture(v) => {
820                write!(f, "Texture load error: {v:?}")
821            }
822        }
823    }
824}
825
826/// Color grading look up table (LUT). Color grading is used to modify color space of the
827/// rendered frame; it maps one color space to another. It is widely used effect in games,
828/// you've probably noticed either "warmness" or "coldness" in colors in various scenes in
829/// games - this is achieved by color grading.
830///
831/// See [more info in Unreal engine docs](https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/PostProcessEffects/UsingLUTs/)
832#[derive(Visit, Clone, Default, PartialEq, Debug, Reflect, Eq)]
833pub struct ColorGradingLut {
834    unwrapped_lut: Option<TextureResource>,
835
836    #[visit(skip)]
837    #[reflect(hidden)]
838    lut: Option<TextureResource>,
839}
840
841uuid_provider!(ColorGradingLut = "bca9c90a-7cde-4960-8814-c132edfc9614");
842
843impl ColorGradingLut {
844    /// Creates 3D look-up texture from 2D strip.
845    ///
846    /// # Input Texture Requirements
847    ///
848    /// Width: 1024px
849    /// Height: 16px
850    /// Pixel Format: RGB8/RGBA8
851    ///
852    /// # Usage
853    ///
854    /// Typical usage would be:
855    ///
856    /// ```no_run
857    /// # use fyrox_impl::scene::camera::ColorGradingLut;
858    /// # use fyrox_impl::asset::manager::{ResourceManager};
859    /// # use fyrox_impl::resource::texture::Texture;
860    ///
861    /// async fn create_lut(resource_manager: ResourceManager) -> ColorGradingLut {
862    ///     ColorGradingLut::new(resource_manager.request::<Texture>(
863    ///         "your_lut.jpg",
864    ///     ))
865    ///     .await
866    ///     .unwrap()
867    /// }
868    /// ```
869    ///
870    /// Then pass LUT to either CameraBuilder or to camera instance, and don't forget to enable
871    /// color grading.
872    pub async fn new(unwrapped_lut: TextureResource) -> Result<Self, ColorGradingLutCreationError> {
873        match unwrapped_lut.await {
874            Ok(unwrapped_lut) => {
875                let data = unwrapped_lut.data_ref();
876
877                if data.pixel_kind() != TexturePixelKind::RGBA8
878                    && data.pixel_kind() != TexturePixelKind::RGB8
879                {
880                    return Err(ColorGradingLutCreationError::InvalidPixelFormat(
881                        data.pixel_kind(),
882                    ));
883                }
884
885                let bytes = data.data();
886
887                const RGBA8_SIZE: usize = 16 * 16 * 16 * 4;
888                const RGB8_SIZE: usize = 16 * 16 * 16 * 3;
889
890                if data.pixel_kind() == TexturePixelKind::RGBA8 {
891                    if bytes.len() != RGBA8_SIZE {
892                        return Err(ColorGradingLutCreationError::NotEnoughData {
893                            required: RGBA8_SIZE,
894                            current: bytes.len(),
895                        });
896                    }
897                } else if bytes.len() != RGB8_SIZE {
898                    return Err(ColorGradingLutCreationError::NotEnoughData {
899                        required: RGB8_SIZE,
900                        current: bytes.len(),
901                    });
902                }
903
904                let pixel_size = if data.pixel_kind() == TexturePixelKind::RGBA8 {
905                    4
906                } else {
907                    3
908                };
909
910                let mut lut_bytes = Vec::with_capacity(16 * 16 * 16 * 3);
911
912                for z in 0..16 {
913                    for y in 0..16 {
914                        for x in 0..16 {
915                            let pixel_index = z * 16 + y * 16 * 16 + x;
916                            let pixel_byte_pos = pixel_index * pixel_size;
917
918                            lut_bytes.push(bytes[pixel_byte_pos]); // R
919                            lut_bytes.push(bytes[pixel_byte_pos + 1]); // G
920                            lut_bytes.push(bytes[pixel_byte_pos + 2]); // B
921                        }
922                    }
923                }
924
925                let lut = TextureResource::from_bytes(
926                    TextureKind::Volume {
927                        width: 16,
928                        height: 16,
929                        depth: 16,
930                    },
931                    TexturePixelKind::RGB8,
932                    lut_bytes,
933                    ResourceKind::Embedded,
934                )
935                .unwrap();
936
937                let mut lut_ref = lut.data_ref();
938
939                lut_ref.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
940                lut_ref.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
941
942                drop(lut_ref);
943                drop(data);
944
945                Ok(Self {
946                    lut: Some(lut),
947                    unwrapped_lut: Some(unwrapped_lut),
948                })
949            }
950            Err(e) => Err(ColorGradingLutCreationError::Texture(e)),
951        }
952    }
953
954    /// Returns color grading unwrapped look-up table. This is initial texture that was
955    /// used to create the look-up table.
956    pub fn unwrapped_lut(&self) -> TextureResource {
957        self.unwrapped_lut.clone().unwrap()
958    }
959
960    /// Returns 3D color grading look-up table ready for use on GPU.
961    pub fn lut(&self) -> TextureResource {
962        self.lut.clone().unwrap()
963    }
964
965    /// Returns 3D color grading look-up table by ref ready for use on GPU.
966    pub fn lut_ref(&self) -> &TextureResource {
967        self.lut.as_ref().unwrap()
968    }
969}
970
971/// A fixed set of possible sky boxes, that can be selected when building [`Camera`] scene node.
972#[derive(Default)]
973pub enum SkyBoxKind {
974    /// Uses built-in sky box. This is default sky box.
975    #[default]
976    Builtin,
977    /// No sky box. Surroundings will be filled with back buffer clear color.
978    None,
979    /// Specific skybox. One can be built using [`SkyBoxBuilder`].
980    Specific(SkyBox),
981}
982
983fn load_texture(data: &[u8], id: &str) -> TextureResource {
984    TextureResource::load_from_memory(
985        ResourceKind::External(id.into()),
986        data,
987        TextureImportOptions::default()
988            .with_compression(CompressionOptions::NoCompression)
989            .with_minification_filter(TextureMinificationFilter::Linear),
990    )
991    .ok()
992    .unwrap()
993}
994
995lazy_static! {
996    static ref BUILT_IN_SKYBOX_FRONT: BuiltInResource<Texture> =
997        BuiltInResource::new(embedded_data_source!("skybox/front.png"), |data| {
998            load_texture(data, "__BUILT_IN_SKYBOX_FRONT")
999        });
1000    static ref BUILT_IN_SKYBOX_BACK: BuiltInResource<Texture> =
1001        BuiltInResource::new(embedded_data_source!("skybox/back.png"), |data| {
1002            load_texture(data, "__BUILT_IN_SKYBOX_BACK")
1003        });
1004    static ref BUILT_IN_SKYBOX_TOP: BuiltInResource<Texture> =
1005        BuiltInResource::new(embedded_data_source!("skybox/top.png"), |data| {
1006            load_texture(data, "__BUILT_IN_SKYBOX_TOP")
1007        });
1008    static ref BUILT_IN_SKYBOX_BOTTOM: BuiltInResource<Texture> =
1009        BuiltInResource::new(embedded_data_source!("skybox/bottom.png"), |data| {
1010            load_texture(data, "__BUILT_IN_SKYBOX_BOTTOM")
1011        });
1012    static ref BUILT_IN_SKYBOX_LEFT: BuiltInResource<Texture> =
1013        BuiltInResource::new(embedded_data_source!("skybox/left.png"), |data| {
1014            load_texture(data, "__BUILT_IN_SKYBOX_LEFT")
1015        });
1016    static ref BUILT_IN_SKYBOX_RIGHT: BuiltInResource<Texture> =
1017        BuiltInResource::new(embedded_data_source!("skybox/right.png"), |data| {
1018            load_texture(data, "__BUILT_IN_SKYBOX_RIGHT")
1019        });
1020    static ref BUILT_IN_SKYBOX: SkyBox = SkyBoxKind::make_built_in_skybox();
1021}
1022
1023impl SkyBoxKind {
1024    fn make_built_in_skybox() -> SkyBox {
1025        let front = BUILT_IN_SKYBOX_FRONT.resource();
1026        let back = BUILT_IN_SKYBOX_BACK.resource();
1027        let top = BUILT_IN_SKYBOX_TOP.resource();
1028        let bottom = BUILT_IN_SKYBOX_BOTTOM.resource();
1029        let left = BUILT_IN_SKYBOX_LEFT.resource();
1030        let right = BUILT_IN_SKYBOX_RIGHT.resource();
1031
1032        SkyBoxBuilder {
1033            front: Some(front),
1034            back: Some(back),
1035            left: Some(left),
1036            right: Some(right),
1037            top: Some(top),
1038            bottom: Some(bottom),
1039        }
1040        .build()
1041        .unwrap()
1042    }
1043
1044    /// Returns a references to built-in sky box.
1045    pub fn built_in_skybox() -> &'static SkyBox {
1046        &BUILT_IN_SKYBOX
1047    }
1048
1049    /// Returns an array with references to the textures being used in built-in sky box. The order is:
1050    /// front, back, top, bottom, left, right.
1051    pub fn built_in_skybox_textures() -> [&'static BuiltInResource<Texture>; 6] {
1052        [
1053            &BUILT_IN_SKYBOX_FRONT,
1054            &BUILT_IN_SKYBOX_BACK,
1055            &BUILT_IN_SKYBOX_TOP,
1056            &BUILT_IN_SKYBOX_BOTTOM,
1057            &BUILT_IN_SKYBOX_LEFT,
1058            &BUILT_IN_SKYBOX_RIGHT,
1059        ]
1060    }
1061}
1062
1063/// Camera builder is used to create new camera in declarative manner.
1064/// This is typical implementation of Builder pattern.
1065pub struct CameraBuilder {
1066    base_builder: BaseBuilder,
1067    fov: f32,
1068    z_near: f32,
1069    z_far: f32,
1070    viewport: Rect<f32>,
1071    enabled: bool,
1072    skybox: SkyBoxKind,
1073    environment: Option<TextureResource>,
1074    exposure: Exposure,
1075    color_grading_lut: Option<ColorGradingLut>,
1076    color_grading_enabled: bool,
1077    projection: Projection,
1078}
1079
1080impl CameraBuilder {
1081    /// Creates new camera builder using given base node builder.
1082    pub fn new(base_builder: BaseBuilder) -> Self {
1083        Self {
1084            enabled: true,
1085            base_builder,
1086            fov: 75.0f32.to_radians(),
1087            z_near: 0.025,
1088            z_far: 2048.0,
1089            viewport: Rect::new(0.0, 0.0, 1.0, 1.0),
1090            skybox: SkyBoxKind::Builtin,
1091            environment: None,
1092            exposure: Exposure::Manual(std::f32::consts::E),
1093            color_grading_lut: None,
1094            color_grading_enabled: false,
1095            projection: Projection::default(),
1096        }
1097    }
1098
1099    /// Sets desired field of view in radians.
1100    pub fn with_fov(mut self, fov: f32) -> Self {
1101        self.fov = fov;
1102        self
1103    }
1104
1105    /// Sets desired near projection plane.
1106    pub fn with_z_near(mut self, z_near: f32) -> Self {
1107        self.z_near = z_near;
1108        self
1109    }
1110
1111    /// Sets desired far projection plane.
1112    pub fn with_z_far(mut self, z_far: f32) -> Self {
1113        self.z_far = z_far;
1114        self
1115    }
1116
1117    /// Sets desired viewport.
1118    pub fn with_viewport(mut self, viewport: Rect<f32>) -> Self {
1119        self.viewport = viewport;
1120        self
1121    }
1122
1123    /// Sets desired initial state of camera: enabled or disabled.
1124    pub fn enabled(mut self, enabled: bool) -> Self {
1125        self.enabled = enabled;
1126        self
1127    }
1128
1129    /// Sets desired skybox.
1130    pub fn with_skybox(mut self, skybox: SkyBox) -> Self {
1131        self.skybox = SkyBoxKind::Specific(skybox);
1132        self
1133    }
1134
1135    /// Sets desired skybox.
1136    pub fn with_specific_skybox(mut self, skybox_kind: SkyBoxKind) -> Self {
1137        self.skybox = skybox_kind;
1138        self
1139    }
1140
1141    /// Sets desired environment map.
1142    pub fn with_environment(mut self, environment: TextureResource) -> Self {
1143        self.environment = Some(environment);
1144        self
1145    }
1146
1147    /// Sets desired color grading LUT.
1148    pub fn with_color_grading_lut(mut self, lut: ColorGradingLut) -> Self {
1149        self.color_grading_lut = Some(lut);
1150        self
1151    }
1152
1153    /// Sets whether color grading should be enabled or not.
1154    pub fn with_color_grading_enabled(mut self, enabled: bool) -> Self {
1155        self.color_grading_enabled = enabled;
1156        self
1157    }
1158
1159    /// Sets desired exposure options.
1160    pub fn with_exposure(mut self, exposure: Exposure) -> Self {
1161        self.exposure = exposure;
1162        self
1163    }
1164
1165    /// Sets desired projection mode.
1166    pub fn with_projection(mut self, projection: Projection) -> Self {
1167        self.projection = projection;
1168        self
1169    }
1170
1171    /// Creates new instance of camera.
1172    pub fn build_camera(self) -> Camera {
1173        Camera {
1174            enabled: self.enabled.into(),
1175            base: self.base_builder.build_base(),
1176            projection: self.projection.into(),
1177            viewport: self.viewport.into(),
1178            // No need to calculate these matrices - they'll be automatically
1179            // recalculated before rendering.
1180            view_matrix: Matrix4::identity(),
1181            projection_matrix: Matrix4::identity(),
1182            sky_box: InheritableVariable::new_modified(match self.skybox {
1183                SkyBoxKind::Builtin => Some(SkyBoxKind::built_in_skybox().clone()),
1184                SkyBoxKind::None => None,
1185                SkyBoxKind::Specific(skybox) => Some(skybox),
1186            }),
1187            environment: self.environment.into(),
1188            exposure: self.exposure.into(),
1189            color_grading_lut: self.color_grading_lut.into(),
1190            color_grading_enabled: self.color_grading_enabled.into(),
1191        }
1192    }
1193
1194    /// Creates new instance of camera node.
1195    pub fn build_node(self) -> Node {
1196        Node::new(self.build_camera())
1197    }
1198
1199    /// Creates new instance of camera node and adds it to the graph.
1200    pub fn build(self, graph: &mut Graph) -> Handle<Node> {
1201        graph.add_node(self.build_node())
1202    }
1203}
1204
1205/// SkyBox builder is used to create new skybox in declarative manner.
1206pub struct SkyBoxBuilder {
1207    /// Texture for front face.
1208    pub front: Option<TextureResource>,
1209    /// Texture for back face.
1210    pub back: Option<TextureResource>,
1211    /// Texture for left face.
1212    pub left: Option<TextureResource>,
1213    /// Texture for right face.
1214    pub right: Option<TextureResource>,
1215    /// Texture for top face.
1216    pub top: Option<TextureResource>,
1217    /// Texture for bottom face.
1218    pub bottom: Option<TextureResource>,
1219}
1220
1221impl SkyBoxBuilder {
1222    /// Sets desired front face of cubemap.
1223    pub fn with_front(mut self, texture: TextureResource) -> Self {
1224        self.front = Some(texture);
1225        self
1226    }
1227
1228    /// Sets desired back face of cubemap.
1229    pub fn with_back(mut self, texture: TextureResource) -> Self {
1230        self.back = Some(texture);
1231        self
1232    }
1233
1234    /// Sets desired left face of cubemap.
1235    pub fn with_left(mut self, texture: TextureResource) -> Self {
1236        self.left = Some(texture);
1237        self
1238    }
1239
1240    /// Sets desired right face of cubemap.
1241    pub fn with_right(mut self, texture: TextureResource) -> Self {
1242        self.right = Some(texture);
1243        self
1244    }
1245
1246    /// Sets desired top face of cubemap.
1247    pub fn with_top(mut self, texture: TextureResource) -> Self {
1248        self.top = Some(texture);
1249        self
1250    }
1251
1252    /// Sets desired front face of cubemap.
1253    pub fn with_bottom(mut self, texture: TextureResource) -> Self {
1254        self.bottom = Some(texture);
1255        self
1256    }
1257
1258    /// Creates a new instance of skybox.
1259    pub fn build(self) -> Result<SkyBox, SkyBoxError> {
1260        let mut skybox = SkyBox {
1261            left: self.left,
1262            right: self.right,
1263            top: self.top,
1264            bottom: self.bottom,
1265            front: self.front,
1266            back: self.back,
1267            cubemap: None,
1268        };
1269
1270        skybox.create_cubemap()?;
1271
1272        Ok(skybox)
1273    }
1274}
1275
1276/// Skybox is a huge box around camera. Each face has its own texture, when textures are
1277/// properly made, there is no seams and you get good decoration which contains static
1278/// skies and/or some other objects (mountains, buildings, etc.). Usually skyboxes used
1279/// in outdoor scenes, however real use of it limited only by your imagination. Skybox
1280/// will be drawn first, none of objects could be drawn before skybox.
1281#[derive(Debug, Clone, Default, PartialEq, Reflect, Visit, Eq)]
1282pub struct SkyBox {
1283    /// Texture for front face.
1284    #[reflect(setter = "set_front")]
1285    pub(crate) front: Option<TextureResource>,
1286
1287    /// Texture for back face.
1288    #[reflect(setter = "set_back")]
1289    pub(crate) back: Option<TextureResource>,
1290
1291    /// Texture for left face.
1292    #[reflect(setter = "set_left")]
1293    pub(crate) left: Option<TextureResource>,
1294
1295    /// Texture for right face.
1296    #[reflect(setter = "set_right")]
1297    pub(crate) right: Option<TextureResource>,
1298
1299    /// Texture for top face.
1300    #[reflect(setter = "set_top")]
1301    pub(crate) top: Option<TextureResource>,
1302
1303    /// Texture for bottom face.
1304    #[reflect(setter = "set_bottom")]
1305    pub(crate) bottom: Option<TextureResource>,
1306
1307    /// Cubemap texture
1308    #[reflect(hidden)]
1309    #[visit(skip)]
1310    pub(crate) cubemap: Option<TextureResource>,
1311}
1312
1313uuid_provider!(SkyBox = "45f359f1-e26f-4ace-81df-097f63474c72");
1314
1315/// An error that may occur during skybox creation.
1316#[derive(Debug)]
1317pub enum SkyBoxError {
1318    /// Texture kind is not TextureKind::Rectangle
1319    UnsupportedTextureKind(TextureKind),
1320    /// Cube map was failed to build.
1321    UnableToBuildCubeMap,
1322    /// Input texture is not square.
1323    NonSquareTexture {
1324        /// Texture index.
1325        index: usize,
1326        /// Width of the faulty texture.
1327        width: u32,
1328        /// Height of the faulty texture.
1329        height: u32,
1330    },
1331    /// Some input texture differs in size or pixel kind.
1332    DifferentTexture {
1333        /// Actual width of the first valid texture in the input set.
1334        expected_width: u32,
1335        /// Actual height of the first valid texture in the input set.
1336        expected_height: u32,
1337        /// Actual pixel kind of the first valid texture in the input set.
1338        expected_pixel_kind: TexturePixelKind,
1339        /// Index of the faulty input texture.
1340        index: usize,
1341        /// Width of the faulty texture.
1342        actual_width: u32,
1343        /// Height of the faulty texture.
1344        actual_height: u32,
1345        /// Pixel kind of the faulty texture.
1346        actual_pixel_kind: TexturePixelKind,
1347    },
1348    /// Occurs when one of the input textures is either still loading or failed to load.
1349    TextureIsNotReady {
1350        /// Index of the faulty input texture.
1351        index: usize,
1352    },
1353}
1354
1355impl SkyBox {
1356    /// Returns cubemap texture
1357    pub fn cubemap(&self) -> Option<TextureResource> {
1358        self.cubemap.clone()
1359    }
1360
1361    /// Returns cubemap texture
1362    pub fn cubemap_ref(&self) -> Option<&TextureResource> {
1363        self.cubemap.as_ref()
1364    }
1365
1366    /// Validates input set of texture and checks if it possible to create a cube map from them.
1367    /// There are two main conditions for successful cube map creation:
1368    /// - All textures must have same width and height, and width must be equal to height.
1369    /// - All textures must have same pixel kind.
1370    pub fn validate(&self) -> Result<(), SkyBoxError> {
1371        struct TextureInfo {
1372            pixel_kind: TexturePixelKind,
1373            width: u32,
1374            height: u32,
1375        }
1376
1377        let mut first_info: Option<TextureInfo> = None;
1378
1379        for (index, texture) in self.textures().iter().enumerate() {
1380            if let Some(texture) = texture {
1381                if let Some(texture) = texture.state().data() {
1382                    if let TextureKind::Rectangle { width, height } = texture.kind() {
1383                        if width != height {
1384                            return Err(SkyBoxError::NonSquareTexture {
1385                                index,
1386                                width,
1387                                height,
1388                            });
1389                        }
1390
1391                        if let Some(first_info) = first_info.as_mut() {
1392                            if first_info.width != width
1393                                || first_info.height != height
1394                                || first_info.pixel_kind != texture.pixel_kind()
1395                            {
1396                                return Err(SkyBoxError::DifferentTexture {
1397                                    expected_width: first_info.width,
1398                                    expected_height: first_info.height,
1399                                    expected_pixel_kind: first_info.pixel_kind,
1400                                    index,
1401                                    actual_width: width,
1402                                    actual_height: height,
1403                                    actual_pixel_kind: texture.pixel_kind(),
1404                                });
1405                            }
1406                        } else {
1407                            first_info = Some(TextureInfo {
1408                                pixel_kind: texture.pixel_kind(),
1409                                width,
1410                                height,
1411                            });
1412                        }
1413                    }
1414                } else {
1415                    return Err(SkyBoxError::TextureIsNotReady { index });
1416                }
1417            }
1418        }
1419
1420        Ok(())
1421    }
1422
1423    /// Creates a cubemap using provided faces. If some face has not been provided corresponding side will be black.
1424    ///
1425    /// # Important notes.
1426    ///
1427    /// It will fail if provided face's kind is not TextureKind::Rectangle.
1428    pub fn create_cubemap(&mut self) -> Result<(), SkyBoxError> {
1429        self.validate()?;
1430
1431        let (kind, pixel_kind, bytes_per_face) =
1432            self.textures().iter().find(|face| face.is_some()).map_or(
1433                (
1434                    TextureKind::Rectangle {
1435                        width: 1,
1436                        height: 1,
1437                    },
1438                    TexturePixelKind::R8,
1439                    1,
1440                ),
1441                |face| {
1442                    let face = face.clone().unwrap();
1443                    let data = face.data_ref();
1444
1445                    (data.kind(), data.pixel_kind(), data.mip_level_data(0).len())
1446                },
1447            );
1448
1449        let (width, height) = match kind {
1450            TextureKind::Rectangle { width, height } => (width, height),
1451            _ => return Err(SkyBoxError::UnsupportedTextureKind(kind)),
1452        };
1453
1454        let mut data = Vec::<u8>::with_capacity(bytes_per_face * 6);
1455        for face in self.textures().iter() {
1456            if let Some(f) = face.clone() {
1457                data.extend(f.data_ref().mip_level_data(0));
1458            } else {
1459                let black_face_data = vec![0; bytes_per_face];
1460                data.extend(black_face_data);
1461            }
1462        }
1463
1464        let cubemap = TextureResource::from_bytes(
1465            TextureKind::Cube { width, height },
1466            pixel_kind,
1467            data,
1468            ResourceKind::Embedded,
1469        )
1470        .ok_or(SkyBoxError::UnableToBuildCubeMap)?;
1471
1472        let mut cubemap_ref = cubemap.data_ref();
1473        cubemap_ref.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
1474        cubemap_ref.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
1475        drop(cubemap_ref);
1476
1477        self.cubemap = Some(cubemap);
1478
1479        Ok(())
1480    }
1481
1482    /// Returns slice with all textures, where: 0 - Left, 1 - Right, 2 - Top, 3 - Bottom
1483    /// 4 - Front, 5 - Back.
1484    ///
1485    /// # Important notes.
1486    ///
1487    /// These textures are **not** used for rendering! The renderer uses cube map made of these
1488    /// textures. Public access for these textures is needed in case you need to read internals
1489    /// of the textures.
1490    pub fn textures(&self) -> [Option<TextureResource>; 6] {
1491        [
1492            self.left.clone(),
1493            self.right.clone(),
1494            self.top.clone(),
1495            self.bottom.clone(),
1496            self.front.clone(),
1497            self.back.clone(),
1498        ]
1499    }
1500
1501    /// Set new texture for the left side of the skybox.
1502    pub fn set_left(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1503        let prev = std::mem::replace(&mut self.left, texture);
1504        Log::verify(self.create_cubemap());
1505        prev
1506    }
1507
1508    /// Returns a texture that is used for left face of the cube map.
1509    ///
1510    /// # Important notes.
1511    ///
1512    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1513    pub fn left(&self) -> Option<TextureResource> {
1514        self.left.clone()
1515    }
1516
1517    /// Set new texture for the right side of the skybox.
1518    pub fn set_right(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1519        let prev = std::mem::replace(&mut self.right, texture);
1520        Log::verify(self.create_cubemap());
1521        prev
1522    }
1523
1524    /// Returns a texture that is used for right face of the cube map.
1525    ///
1526    /// # Important notes.
1527    ///
1528    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1529    pub fn right(&self) -> Option<TextureResource> {
1530        self.right.clone()
1531    }
1532
1533    /// Set new texture for the top side of the skybox.
1534    pub fn set_top(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1535        let prev = std::mem::replace(&mut self.top, texture);
1536        Log::verify(self.create_cubemap());
1537        prev
1538    }
1539
1540    /// Returns a texture that is used for top face of the cube map.
1541    ///
1542    /// # Important notes.
1543    ///
1544    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1545    pub fn top(&self) -> Option<TextureResource> {
1546        self.top.clone()
1547    }
1548
1549    /// Set new texture for the bottom side of the skybox.
1550    pub fn set_bottom(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1551        let prev = std::mem::replace(&mut self.bottom, texture);
1552        Log::verify(self.create_cubemap());
1553        prev
1554    }
1555
1556    /// Returns a texture that is used for bottom face of the cube map.
1557    ///
1558    /// # Important notes.
1559    ///
1560    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1561    pub fn bottom(&self) -> Option<TextureResource> {
1562        self.bottom.clone()
1563    }
1564
1565    /// Set new texture for the front side of the skybox.
1566    pub fn set_front(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1567        let prev = std::mem::replace(&mut self.front, texture);
1568        Log::verify(self.create_cubemap());
1569        prev
1570    }
1571
1572    /// Returns a texture that is used for front face of the cube map.
1573    ///
1574    /// # Important notes.
1575    ///
1576    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1577    pub fn front(&self) -> Option<TextureResource> {
1578        self.front.clone()
1579    }
1580
1581    /// Set new texture for the back side of the skybox.
1582    pub fn set_back(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
1583        let prev = std::mem::replace(&mut self.back, texture);
1584        Log::verify(self.create_cubemap());
1585        prev
1586    }
1587
1588    /// Returns a texture that is used for back face of the cube map.
1589    ///
1590    /// # Important notes.
1591    ///
1592    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
1593    pub fn back(&self) -> Option<TextureResource> {
1594        self.back.clone()
1595    }
1596}