fyrox_impl/scene/
skybox.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//! Skybox is a huge box around a camera. See [`SkyBox`] docs for more info.
22
23use crate::{
24    asset::{builtin::BuiltInResource, embedded_data_source, untyped::ResourceKind},
25    core::{log::Log, reflect::prelude::*, uuid_provider, visitor::prelude::*},
26};
27use fyrox_core::color::Color;
28use fyrox_texture::{
29    CompressionOptions, Texture, TextureImportOptions, TextureKind, TextureMinificationFilter,
30    TexturePixelKind, TextureResource, TextureResourceExtension, TextureWrapMode,
31};
32use lazy_static::lazy_static;
33use uuid::{uuid, Uuid};
34
35/// Skybox is a huge box around a camera. Each face has its own texture, when textures are
36/// properly made, there is no seams and you get good decoration which contains static
37/// skies and/or some other objects (mountains, buildings, etc.). Usually skyboxes used
38/// in outdoor scenes, however real use of it limited only by your imagination. Skybox
39/// will be drawn first, none of objects could be drawn before skybox.
40#[derive(Debug, Clone, Default, PartialEq, Reflect, Visit, Eq)]
41pub struct SkyBox {
42    /// Texture for front face.
43    #[reflect(setter = "set_front")]
44    pub(crate) front: Option<TextureResource>,
45
46    /// Texture for back face.
47    #[reflect(setter = "set_back")]
48    pub(crate) back: Option<TextureResource>,
49
50    /// Texture for left face.
51    #[reflect(setter = "set_left")]
52    pub(crate) left: Option<TextureResource>,
53
54    /// Texture for right face.
55    #[reflect(setter = "set_right")]
56    pub(crate) right: Option<TextureResource>,
57
58    /// Texture for top face.
59    #[reflect(setter = "set_top")]
60    pub(crate) top: Option<TextureResource>,
61
62    /// Texture for bottom face.
63    #[reflect(setter = "set_bottom")]
64    pub(crate) bottom: Option<TextureResource>,
65
66    /// Cubemap texture
67    #[reflect(hidden)]
68    #[visit(skip)]
69    pub(crate) cubemap: Option<TextureResource>,
70}
71
72uuid_provider!(SkyBox = "45f359f1-e26f-4ace-81df-097f63474c72");
73
74impl SkyBox {
75    /// Creates a new sky box from a single color.
76    pub fn from_single_color(color: Color) -> Self {
77        let dark_gray_texture = TextureResource::from_bytes(
78            Uuid::new_v4(),
79            TextureKind::Rectangle {
80                width: 1,
81                height: 1,
82            },
83            TexturePixelKind::RGBA8,
84            vec![color.r, color.g, color.b, color.a],
85            ResourceKind::Embedded,
86        )
87        .unwrap();
88
89        SkyBoxBuilder::from_texture(&dark_gray_texture)
90            .build()
91            .unwrap()
92    }
93
94    /// Returns cubemap texture
95    pub fn cubemap(&self) -> Option<TextureResource> {
96        self.cubemap.clone()
97    }
98
99    /// Returns cubemap texture
100    pub fn cubemap_ref(&self) -> Option<&TextureResource> {
101        self.cubemap.as_ref()
102    }
103
104    /// Validates input set of texture and checks if it possible to create a cube map from them.
105    /// There are two main conditions for successful cube map creation:
106    /// - All textures must have same width and height, and width must be equal to height.
107    /// - All textures must have same pixel kind.
108    pub fn validate(&self) -> Result<(), SkyBoxError> {
109        struct TextureInfo {
110            pixel_kind: TexturePixelKind,
111            width: u32,
112            height: u32,
113        }
114
115        let mut first_info: Option<TextureInfo> = None;
116
117        for (index, texture) in self.textures().iter().enumerate() {
118            if let Some(texture) = texture {
119                if let Some(texture) = texture.state().data() {
120                    if let TextureKind::Rectangle { width, height } = texture.kind() {
121                        if width != height {
122                            return Err(SkyBoxError::NonSquareTexture {
123                                index,
124                                width,
125                                height,
126                            });
127                        }
128
129                        if let Some(first_info) = first_info.as_mut() {
130                            if first_info.width != width
131                                || first_info.height != height
132                                || first_info.pixel_kind != texture.pixel_kind()
133                            {
134                                return Err(SkyBoxError::DifferentTexture {
135                                    expected_width: first_info.width,
136                                    expected_height: first_info.height,
137                                    expected_pixel_kind: first_info.pixel_kind,
138                                    index,
139                                    actual_width: width,
140                                    actual_height: height,
141                                    actual_pixel_kind: texture.pixel_kind(),
142                                });
143                            }
144                        } else {
145                            first_info = Some(TextureInfo {
146                                pixel_kind: texture.pixel_kind(),
147                                width,
148                                height,
149                            });
150                        }
151                    }
152                } else {
153                    return Err(SkyBoxError::TextureIsNotReady { index });
154                }
155            }
156        }
157
158        Ok(())
159    }
160
161    /// Creates a cubemap using provided faces. If some face has not been provided corresponding side will be black.
162    ///
163    /// # Important notes.
164    ///
165    /// It will fail if provided face's kind is not TextureKind::Rectangle.
166    pub fn create_cubemap(&mut self) -> Result<(), SkyBoxError> {
167        self.validate()?;
168
169        let (kind, pixel_kind, bytes_per_face) =
170            self.textures().iter().find(|face| face.is_some()).map_or(
171                (
172                    TextureKind::Rectangle {
173                        width: 1,
174                        height: 1,
175                    },
176                    TexturePixelKind::R8,
177                    1,
178                ),
179                |face| {
180                    let face = face.clone().unwrap();
181                    let data = face.data_ref();
182
183                    (data.kind(), data.pixel_kind(), data.mip_level_data(0).len())
184                },
185            );
186
187        let size = match kind {
188            TextureKind::Rectangle { width, height } => {
189                assert_eq!(width, height);
190                width
191            }
192            _ => return Err(SkyBoxError::UnsupportedTextureKind(kind)),
193        };
194
195        let mut data = Vec::<u8>::with_capacity(bytes_per_face * 6);
196        for face in self.textures().iter() {
197            if let Some(f) = face.clone() {
198                data.extend(f.data_ref().mip_level_data(0));
199            } else {
200                let black_face_data = vec![0; bytes_per_face];
201                data.extend(black_face_data);
202            }
203        }
204
205        let cubemap = TextureResource::from_bytes(
206            Uuid::new_v4(),
207            TextureKind::Cube { size },
208            pixel_kind,
209            data,
210            ResourceKind::Embedded,
211        )
212        .ok_or(SkyBoxError::UnableToBuildCubeMap)?;
213
214        let mut cubemap_ref = cubemap.data_ref();
215        cubemap_ref.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
216        cubemap_ref.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
217        drop(cubemap_ref);
218
219        self.cubemap = Some(cubemap);
220
221        Ok(())
222    }
223
224    /// Returns slice with all textures, where: 0 - Left, 1 - Right, 2 - Top, 3 - Bottom
225    /// 4 - Front, 5 - Back.
226    ///
227    /// # Important notes.
228    ///
229    /// These textures are **not** used for rendering! The renderer uses cube map made of these
230    /// textures. Public access for these textures is needed in case you need to read internals
231    /// of the textures.
232    pub fn textures(&self) -> [Option<TextureResource>; 6] {
233        [
234            self.left.clone(),
235            self.right.clone(),
236            self.top.clone(),
237            self.bottom.clone(),
238            self.front.clone(),
239            self.back.clone(),
240        ]
241    }
242
243    /// Set new texture for the left side of the skybox.
244    pub fn set_left(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
245        let prev = std::mem::replace(&mut self.left, texture);
246        Log::verify(self.create_cubemap());
247        prev
248    }
249
250    /// Returns a texture that is used for left face of the cube map.
251    ///
252    /// # Important notes.
253    ///
254    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
255    pub fn left(&self) -> Option<TextureResource> {
256        self.left.clone()
257    }
258
259    /// Set new texture for the right side of the skybox.
260    pub fn set_right(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
261        let prev = std::mem::replace(&mut self.right, texture);
262        Log::verify(self.create_cubemap());
263        prev
264    }
265
266    /// Returns a texture that is used for right face of the cube map.
267    ///
268    /// # Important notes.
269    ///
270    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
271    pub fn right(&self) -> Option<TextureResource> {
272        self.right.clone()
273    }
274
275    /// Set new texture for the top side of the skybox.
276    pub fn set_top(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
277        let prev = std::mem::replace(&mut self.top, texture);
278        Log::verify(self.create_cubemap());
279        prev
280    }
281
282    /// Returns a texture that is used for top face of the cube map.
283    ///
284    /// # Important notes.
285    ///
286    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
287    pub fn top(&self) -> Option<TextureResource> {
288        self.top.clone()
289    }
290
291    /// Set new texture for the bottom side of the skybox.
292    pub fn set_bottom(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
293        let prev = std::mem::replace(&mut self.bottom, texture);
294        Log::verify(self.create_cubemap());
295        prev
296    }
297
298    /// Returns a texture that is used for bottom face of the cube map.
299    ///
300    /// # Important notes.
301    ///
302    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
303    pub fn bottom(&self) -> Option<TextureResource> {
304        self.bottom.clone()
305    }
306
307    /// Set new texture for the front side of the skybox.
308    pub fn set_front(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
309        let prev = std::mem::replace(&mut self.front, texture);
310        Log::verify(self.create_cubemap());
311        prev
312    }
313
314    /// Returns a texture that is used for front face of the cube map.
315    ///
316    /// # Important notes.
317    ///
318    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
319    pub fn front(&self) -> Option<TextureResource> {
320        self.front.clone()
321    }
322
323    /// Set new texture for the back side of the skybox.
324    pub fn set_back(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
325        let prev = std::mem::replace(&mut self.back, texture);
326        Log::verify(self.create_cubemap());
327        prev
328    }
329
330    /// Returns a texture that is used for back face of the cube map.
331    ///
332    /// # Important notes.
333    ///
334    /// This textures is not used for rendering! The renderer uses cube map made of face textures.
335    pub fn back(&self) -> Option<TextureResource> {
336        self.back.clone()
337    }
338}
339
340/// An error that may occur during skybox creation.
341#[derive(Debug)]
342pub enum SkyBoxError {
343    /// Texture kind is not TextureKind::Rectangle
344    UnsupportedTextureKind(TextureKind),
345    /// Cube map was failed to build.
346    UnableToBuildCubeMap,
347    /// Input texture is not square.
348    NonSquareTexture {
349        /// Texture index.
350        index: usize,
351        /// Width of the faulty texture.
352        width: u32,
353        /// Height of the faulty texture.
354        height: u32,
355    },
356    /// Some input texture differs in size or pixel kind.
357    DifferentTexture {
358        /// Actual width of the first valid texture in the input set.
359        expected_width: u32,
360        /// Actual height of the first valid texture in the input set.
361        expected_height: u32,
362        /// Actual pixel kind of the first valid texture in the input set.
363        expected_pixel_kind: TexturePixelKind,
364        /// Index of the faulty input texture.
365        index: usize,
366        /// Width of the faulty texture.
367        actual_width: u32,
368        /// Height of the faulty texture.
369        actual_height: u32,
370        /// Pixel kind of the faulty texture.
371        actual_pixel_kind: TexturePixelKind,
372    },
373    /// Occurs when one of the input textures is either still loading or failed to load.
374    TextureIsNotReady {
375        /// Index of the faulty input texture.
376        index: usize,
377    },
378}
379
380/// SkyBox builder is used to create new skybox in declarative manner.
381pub struct SkyBoxBuilder {
382    /// Texture for front face.
383    pub front: Option<TextureResource>,
384    /// Texture for back face.
385    pub back: Option<TextureResource>,
386    /// Texture for left face.
387    pub left: Option<TextureResource>,
388    /// Texture for right face.
389    pub right: Option<TextureResource>,
390    /// Texture for top face.
391    pub top: Option<TextureResource>,
392    /// Texture for bottom face.
393    pub bottom: Option<TextureResource>,
394}
395
396impl SkyBoxBuilder {
397    /// Creates a new builder, where each texture for each side of the sky box is the same.
398    pub fn from_texture(texture: &TextureResource) -> Self {
399        Self {
400            front: Some(texture.clone()),
401            back: Some(texture.clone()),
402            left: Some(texture.clone()),
403            right: Some(texture.clone()),
404            top: Some(texture.clone()),
405            bottom: Some(texture.clone()),
406        }
407    }
408
409    /// Sets desired front face of cubemap.
410    pub fn with_front(mut self, texture: TextureResource) -> Self {
411        self.front = Some(texture);
412        self
413    }
414
415    /// Sets desired back face of cubemap.
416    pub fn with_back(mut self, texture: TextureResource) -> Self {
417        self.back = Some(texture);
418        self
419    }
420
421    /// Sets desired left face of cubemap.
422    pub fn with_left(mut self, texture: TextureResource) -> Self {
423        self.left = Some(texture);
424        self
425    }
426
427    /// Sets desired right face of cubemap.
428    pub fn with_right(mut self, texture: TextureResource) -> Self {
429        self.right = Some(texture);
430        self
431    }
432
433    /// Sets desired top face of cubemap.
434    pub fn with_top(mut self, texture: TextureResource) -> Self {
435        self.top = Some(texture);
436        self
437    }
438
439    /// Sets desired front face of cubemap.
440    pub fn with_bottom(mut self, texture: TextureResource) -> Self {
441        self.bottom = Some(texture);
442        self
443    }
444
445    /// Creates a new instance of skybox.
446    pub fn build(self) -> Result<SkyBox, SkyBoxError> {
447        let mut skybox = SkyBox {
448            left: self.left,
449            right: self.right,
450            top: self.top,
451            bottom: self.bottom,
452            front: self.front,
453            back: self.back,
454            cubemap: None,
455        };
456
457        skybox.create_cubemap()?;
458
459        Ok(skybox)
460    }
461}
462
463fn load_texture(id: Uuid, data: &[u8]) -> TextureResource {
464    TextureResource::load_from_memory(
465        id,
466        ResourceKind::External,
467        data,
468        TextureImportOptions::default()
469            .with_compression(CompressionOptions::NoCompression)
470            .with_minification_filter(TextureMinificationFilter::Linear),
471    )
472    .ok()
473    .unwrap()
474}
475
476lazy_static! {
477    static ref BUILT_IN_SKYBOX_FRONT: BuiltInResource<Texture> = BuiltInResource::new(
478        "__BUILT_IN_SKYBOX_FRONT",
479        embedded_data_source!("skybox/front.png"),
480        |data| { load_texture(uuid!("f8d4519b-2947-4c83-9aa5-800a70ae918e"), data) }
481    );
482    static ref BUILT_IN_SKYBOX_BACK: BuiltInResource<Texture> = BuiltInResource::new(
483        "__BUILT_IN_SKYBOX_BACK",
484        embedded_data_source!("skybox/back.png"),
485        |data| { load_texture(uuid!("28676705-58bd-440f-b0aa-ce42cf95be79"), data) }
486    );
487    static ref BUILT_IN_SKYBOX_TOP: BuiltInResource<Texture> = BuiltInResource::new(
488        "__BUILT_IN_SKYBOX_TOP",
489        embedded_data_source!("skybox/top.png"),
490        |data| { load_texture(uuid!("03e38da7-53d1-48c0-87f8-2baf9869d61d"), data) }
491    );
492    static ref BUILT_IN_SKYBOX_BOTTOM: BuiltInResource<Texture> = BuiltInResource::new(
493        "__BUILT_IN_SKYBOX_BOTTOM",
494        embedded_data_source!("skybox/bottom.png"),
495        |data| { load_texture(uuid!("01684dc1-34b2-48b3-b8c2-30a7718cb9e7"), data) }
496    );
497    static ref BUILT_IN_SKYBOX_LEFT: BuiltInResource<Texture> = BuiltInResource::new(
498        "__BUILT_IN_SKYBOX_LEFT",
499        embedded_data_source!("skybox/left.png"),
500        |data| { load_texture(uuid!("1725b779-7633-477a-a7b0-995c079c3202"), data) }
501    );
502    static ref BUILT_IN_SKYBOX_RIGHT: BuiltInResource<Texture> = BuiltInResource::new(
503        "__BUILT_IN_SKYBOX_RIGHT",
504        embedded_data_source!("skybox/right.png"),
505        |data| { load_texture(uuid!("5f74865a-3eae-4bff-8743-b9d1f7bb3c59"), data) }
506    );
507    static ref BUILT_IN_SKYBOX: SkyBox = SkyBoxKind::make_built_in_skybox();
508}
509
510impl SkyBoxKind {
511    fn make_built_in_skybox() -> SkyBox {
512        let front = BUILT_IN_SKYBOX_FRONT.resource();
513        let back = BUILT_IN_SKYBOX_BACK.resource();
514        let top = BUILT_IN_SKYBOX_TOP.resource();
515        let bottom = BUILT_IN_SKYBOX_BOTTOM.resource();
516        let left = BUILT_IN_SKYBOX_LEFT.resource();
517        let right = BUILT_IN_SKYBOX_RIGHT.resource();
518
519        SkyBoxBuilder {
520            front: Some(front),
521            back: Some(back),
522            left: Some(left),
523            right: Some(right),
524            top: Some(top),
525            bottom: Some(bottom),
526        }
527        .build()
528        .unwrap()
529    }
530
531    /// Returns a references to built-in sky box.
532    pub fn built_in_skybox() -> &'static SkyBox {
533        &BUILT_IN_SKYBOX
534    }
535
536    /// Returns an array with references to the textures being used in built-in sky box. The order is:
537    /// front, back, top, bottom, left, right.
538    pub fn built_in_skybox_textures() -> [&'static BuiltInResource<Texture>; 6] {
539        [
540            &BUILT_IN_SKYBOX_FRONT,
541            &BUILT_IN_SKYBOX_BACK,
542            &BUILT_IN_SKYBOX_TOP,
543            &BUILT_IN_SKYBOX_BOTTOM,
544            &BUILT_IN_SKYBOX_LEFT,
545            &BUILT_IN_SKYBOX_RIGHT,
546        ]
547    }
548}
549
550/// A fixed set of possible sky boxes, that can be selected when building [`super::camera::Camera`] scene node.
551#[derive(Default)]
552pub enum SkyBoxKind {
553    /// Uses built-in sky box. This is default sky box.
554    #[default]
555    Builtin,
556    /// No sky box. Surroundings will be filled with back buffer clear color.
557    None,
558    /// Specific skybox. One can be built using [`SkyBoxBuilder`].
559    Specific(SkyBox),
560}