1use std::{fmt::Display, sync::LazyLock};
24
25use crate::{
26 asset::{builtin::BuiltInResource, embedded_data_source, untyped::ResourceKind},
27 core::{log::Log, reflect::prelude::*, uuid_provider, visitor::prelude::*},
28};
29use fyrox_core::color::Color;
30use fyrox_texture::{
31 CompressionOptions, Texture, TextureImportOptions, TextureKind, TextureMinificationFilter,
32 TexturePixelKind, TextureResource, TextureResourceExtension, TextureWrapMode,
33};
34use uuid::{uuid, Uuid};
35
36#[derive(Debug, Clone, Default, PartialEq, Reflect, Visit, Eq)]
42pub struct SkyBox {
43 #[reflect(setter = "set_front")]
45 pub(crate) front: Option<TextureResource>,
46
47 #[reflect(setter = "set_back")]
49 pub(crate) back: Option<TextureResource>,
50
51 #[reflect(setter = "set_left")]
53 pub(crate) left: Option<TextureResource>,
54
55 #[reflect(setter = "set_right")]
57 pub(crate) right: Option<TextureResource>,
58
59 #[reflect(setter = "set_top")]
61 pub(crate) top: Option<TextureResource>,
62
63 #[reflect(setter = "set_bottom")]
65 pub(crate) bottom: Option<TextureResource>,
66
67 #[reflect(hidden)]
69 #[visit(skip)]
70 pub(crate) cubemap: Option<TextureResource>,
71}
72
73uuid_provider!(SkyBox = "45f359f1-e26f-4ace-81df-097f63474c72");
74
75impl SkyBox {
76 pub fn from_single_color(color: Color) -> Self {
78 let dark_gray_texture = TextureResource::from_bytes(
79 Uuid::new_v4(),
80 TextureKind::Rectangle {
81 width: 1,
82 height: 1,
83 },
84 TexturePixelKind::RGBA8,
85 vec![color.r, color.g, color.b, color.a],
86 ResourceKind::Embedded,
87 )
88 .unwrap();
89
90 SkyBoxBuilder::from_texture(&dark_gray_texture)
91 .build()
92 .unwrap()
93 }
94
95 pub fn cubemap(&self) -> Option<TextureResource> {
97 self.cubemap.clone()
98 }
99
100 pub fn cubemap_ref(&self) -> Option<&TextureResource> {
102 self.cubemap.as_ref()
103 }
104
105 pub fn validate(&self) -> Result<(), SkyBoxError> {
110 struct TextureInfo {
111 pixel_kind: TexturePixelKind,
112 width: u32,
113 height: u32,
114 }
115
116 let mut first_info: Option<TextureInfo> = None;
117
118 for (index, texture) in self.textures().iter().enumerate() {
119 if let Some(texture) = texture {
120 if let Some(texture) = texture.state().data() {
121 if let TextureKind::Rectangle { width, height } = texture.kind() {
122 if width != height {
123 return Err(SkyBoxError::NonSquareTexture {
124 index,
125 width,
126 height,
127 });
128 }
129
130 if let Some(first_info) = first_info.as_mut() {
131 if first_info.width != width
132 || first_info.height != height
133 || first_info.pixel_kind != texture.pixel_kind()
134 {
135 return Err(SkyBoxError::DifferentTexture {
136 expected_width: first_info.width,
137 expected_height: first_info.height,
138 expected_pixel_kind: first_info.pixel_kind,
139 index,
140 actual_width: width,
141 actual_height: height,
142 actual_pixel_kind: texture.pixel_kind(),
143 });
144 }
145 } else {
146 first_info = Some(TextureInfo {
147 pixel_kind: texture.pixel_kind(),
148 width,
149 height,
150 });
151 }
152 }
153 } else {
154 return Err(SkyBoxError::TextureIsNotReady { index });
155 }
156 }
157 }
158
159 Ok(())
160 }
161
162 pub fn create_cubemap(&mut self) -> Result<(), SkyBoxError> {
168 self.validate()?;
169
170 let (kind, pixel_kind, bytes_per_face) =
171 self.textures().iter().find(|face| face.is_some()).map_or(
172 (
173 TextureKind::Rectangle {
174 width: 1,
175 height: 1,
176 },
177 TexturePixelKind::R8,
178 1,
179 ),
180 |face| {
181 let face = face.clone().unwrap();
182 let data = face.data_ref();
183
184 (data.kind(), data.pixel_kind(), data.mip_level_data(0).len())
185 },
186 );
187
188 let size = match kind {
189 TextureKind::Rectangle { width, height } => {
190 assert_eq!(width, height);
191 width
192 }
193 _ => return Err(SkyBoxError::UnsupportedTextureKind(kind)),
194 };
195
196 let mut data = Vec::<u8>::with_capacity(bytes_per_face * 6);
197 for face in self.textures().iter() {
198 if let Some(f) = face.clone() {
199 data.extend(f.data_ref().mip_level_data(0));
200 } else {
201 let black_face_data = vec![0; bytes_per_face];
202 data.extend(black_face_data);
203 }
204 }
205
206 let cubemap = TextureResource::from_bytes(
207 Uuid::new_v4(),
208 TextureKind::Cube { size },
209 pixel_kind,
210 data,
211 ResourceKind::Embedded,
212 )
213 .ok_or(SkyBoxError::UnableToBuildCubeMap)?;
214
215 let mut cubemap_ref = cubemap.data_ref();
216 cubemap_ref.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
217 cubemap_ref.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
218 drop(cubemap_ref);
219
220 self.cubemap = Some(cubemap);
221
222 Ok(())
223 }
224
225 pub fn textures(&self) -> [Option<TextureResource>; 6] {
234 [
235 self.left.clone(),
236 self.right.clone(),
237 self.top.clone(),
238 self.bottom.clone(),
239 self.front.clone(),
240 self.back.clone(),
241 ]
242 }
243
244 pub fn set_left(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
246 let prev = std::mem::replace(&mut self.left, texture);
247 Log::verify(self.create_cubemap());
248 prev
249 }
250
251 pub fn left(&self) -> Option<TextureResource> {
257 self.left.clone()
258 }
259
260 pub fn set_right(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
262 let prev = std::mem::replace(&mut self.right, texture);
263 Log::verify(self.create_cubemap());
264 prev
265 }
266
267 pub fn right(&self) -> Option<TextureResource> {
273 self.right.clone()
274 }
275
276 pub fn set_top(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
278 let prev = std::mem::replace(&mut self.top, texture);
279 Log::verify(self.create_cubemap());
280 prev
281 }
282
283 pub fn top(&self) -> Option<TextureResource> {
289 self.top.clone()
290 }
291
292 pub fn set_bottom(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
294 let prev = std::mem::replace(&mut self.bottom, texture);
295 Log::verify(self.create_cubemap());
296 prev
297 }
298
299 pub fn bottom(&self) -> Option<TextureResource> {
305 self.bottom.clone()
306 }
307
308 pub fn set_front(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
310 let prev = std::mem::replace(&mut self.front, texture);
311 Log::verify(self.create_cubemap());
312 prev
313 }
314
315 pub fn front(&self) -> Option<TextureResource> {
321 self.front.clone()
322 }
323
324 pub fn set_back(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
326 let prev = std::mem::replace(&mut self.back, texture);
327 Log::verify(self.create_cubemap());
328 prev
329 }
330
331 pub fn back(&self) -> Option<TextureResource> {
337 self.back.clone()
338 }
339}
340
341#[derive(Debug)]
343pub enum SkyBoxError {
344 UnsupportedTextureKind(TextureKind),
346 UnableToBuildCubeMap,
348 NonSquareTexture {
350 index: usize,
352 width: u32,
354 height: u32,
356 },
357 DifferentTexture {
359 expected_width: u32,
361 expected_height: u32,
363 expected_pixel_kind: TexturePixelKind,
365 index: usize,
367 actual_width: u32,
369 actual_height: u32,
371 actual_pixel_kind: TexturePixelKind,
373 },
374 TextureIsNotReady {
376 index: usize,
378 },
379}
380
381impl std::error::Error for SkyBoxError {}
382
383impl Display for SkyBoxError {
384 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385 match self {
386 SkyBoxError::UnsupportedTextureKind(texture_kind) => {
387 write!(f, "Unsupported texture kind: {texture_kind:?}")
388 }
389 SkyBoxError::UnableToBuildCubeMap => f.write_str("Cube map was failed to build."),
390 SkyBoxError::NonSquareTexture {
391 index,
392 width,
393 height,
394 } => write!(
395 f,
396 "Input texture is not square. Index: {index}, width: {width}, height: {height}"
397 ),
398 SkyBoxError::DifferentTexture {
399 expected_width,
400 expected_height,
401 expected_pixel_kind,
402 index,
403 actual_width,
404 actual_height,
405 actual_pixel_kind,
406 } => write!(f, "Some input texture differs in size or pixel kind. Index: {index}. \
407 Expected width: {expected_width}, height: {expected_height}, kind: {expected_pixel_kind:?}. \
408 Actual width: {actual_width}, height: {actual_height}, kind: {actual_pixel_kind:?}."),
409 SkyBoxError::TextureIsNotReady { index } => write!(f, "Input texture is not loaded. Index: {index}"),
410 }
411 }
412}
413
414pub struct SkyBoxBuilder {
416 pub front: Option<TextureResource>,
418 pub back: Option<TextureResource>,
420 pub left: Option<TextureResource>,
422 pub right: Option<TextureResource>,
424 pub top: Option<TextureResource>,
426 pub bottom: Option<TextureResource>,
428}
429
430impl SkyBoxBuilder {
431 pub fn from_texture(texture: &TextureResource) -> Self {
433 Self {
434 front: Some(texture.clone()),
435 back: Some(texture.clone()),
436 left: Some(texture.clone()),
437 right: Some(texture.clone()),
438 top: Some(texture.clone()),
439 bottom: Some(texture.clone()),
440 }
441 }
442
443 pub fn with_front(mut self, texture: TextureResource) -> Self {
445 self.front = Some(texture);
446 self
447 }
448
449 pub fn with_back(mut self, texture: TextureResource) -> Self {
451 self.back = Some(texture);
452 self
453 }
454
455 pub fn with_left(mut self, texture: TextureResource) -> Self {
457 self.left = Some(texture);
458 self
459 }
460
461 pub fn with_right(mut self, texture: TextureResource) -> Self {
463 self.right = Some(texture);
464 self
465 }
466
467 pub fn with_top(mut self, texture: TextureResource) -> Self {
469 self.top = Some(texture);
470 self
471 }
472
473 pub fn with_bottom(mut self, texture: TextureResource) -> Self {
475 self.bottom = Some(texture);
476 self
477 }
478
479 pub fn build(self) -> Result<SkyBox, SkyBoxError> {
481 let mut skybox = SkyBox {
482 left: self.left,
483 right: self.right,
484 top: self.top,
485 bottom: self.bottom,
486 front: self.front,
487 back: self.back,
488 cubemap: None,
489 };
490
491 skybox.create_cubemap()?;
492
493 Ok(skybox)
494 }
495}
496
497fn load_texture(id: Uuid, data: &[u8]) -> TextureResource {
498 TextureResource::load_from_memory(
499 id,
500 ResourceKind::External,
501 data,
502 TextureImportOptions::default()
503 .with_compression(CompressionOptions::NoCompression)
504 .with_minification_filter(TextureMinificationFilter::Linear),
505 )
506 .ok()
507 .unwrap()
508}
509
510static BUILT_IN_SKYBOX_FRONT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
511 BuiltInResource::new(
512 "__BUILT_IN_SKYBOX_FRONT",
513 embedded_data_source!("skybox/front.png"),
514 |data| load_texture(uuid!("f8d4519b-2947-4c83-9aa5-800a70ae918e"), data),
515 )
516});
517
518static BUILT_IN_SKYBOX_BACK: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
519 BuiltInResource::new(
520 "__BUILT_IN_SKYBOX_BACK",
521 embedded_data_source!("skybox/back.png"),
522 |data| load_texture(uuid!("28676705-58bd-440f-b0aa-ce42cf95be79"), data),
523 )
524});
525
526static BUILT_IN_SKYBOX_TOP: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
527 BuiltInResource::new(
528 "__BUILT_IN_SKYBOX_TOP",
529 embedded_data_source!("skybox/top.png"),
530 |data| load_texture(uuid!("03e38da7-53d1-48c0-87f8-2baf9869d61d"), data),
531 )
532});
533
534static BUILT_IN_SKYBOX_BOTTOM: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
535 BuiltInResource::new(
536 "__BUILT_IN_SKYBOX_BOTTOM",
537 embedded_data_source!("skybox/bottom.png"),
538 |data| load_texture(uuid!("01684dc1-34b2-48b3-b8c2-30a7718cb9e7"), data),
539 )
540});
541
542static BUILT_IN_SKYBOX_LEFT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
543 BuiltInResource::new(
544 "__BUILT_IN_SKYBOX_LEFT",
545 embedded_data_source!("skybox/left.png"),
546 |data| load_texture(uuid!("1725b779-7633-477a-a7b0-995c079c3202"), data),
547 )
548});
549
550static BUILT_IN_SKYBOX_RIGHT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
551 BuiltInResource::new(
552 "__BUILT_IN_SKYBOX_RIGHT",
553 embedded_data_source!("skybox/right.png"),
554 |data| load_texture(uuid!("5f74865a-3eae-4bff-8743-b9d1f7bb3c59"), data),
555 )
556});
557
558static BUILT_IN_SKYBOX: LazyLock<SkyBox> = LazyLock::new(SkyBoxKind::make_built_in_skybox);
559
560impl SkyBoxKind {
561 fn make_built_in_skybox() -> SkyBox {
562 let front = BUILT_IN_SKYBOX_FRONT.resource();
563 let back = BUILT_IN_SKYBOX_BACK.resource();
564 let top = BUILT_IN_SKYBOX_TOP.resource();
565 let bottom = BUILT_IN_SKYBOX_BOTTOM.resource();
566 let left = BUILT_IN_SKYBOX_LEFT.resource();
567 let right = BUILT_IN_SKYBOX_RIGHT.resource();
568
569 SkyBoxBuilder {
570 front: Some(front),
571 back: Some(back),
572 left: Some(left),
573 right: Some(right),
574 top: Some(top),
575 bottom: Some(bottom),
576 }
577 .build()
578 .unwrap()
579 }
580
581 pub fn built_in_skybox() -> &'static SkyBox {
583 &BUILT_IN_SKYBOX
584 }
585
586 pub fn built_in_skybox_textures() -> [&'static BuiltInResource<Texture>; 6] {
589 [
590 &BUILT_IN_SKYBOX_FRONT,
591 &BUILT_IN_SKYBOX_BACK,
592 &BUILT_IN_SKYBOX_TOP,
593 &BUILT_IN_SKYBOX_BOTTOM,
594 &BUILT_IN_SKYBOX_LEFT,
595 &BUILT_IN_SKYBOX_RIGHT,
596 ]
597 }
598}
599
600#[derive(Default)]
602pub enum SkyBoxKind {
603 #[default]
605 Builtin,
606 None,
608 Specific(SkyBox),
610}