1use 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#[derive(Debug, Clone, Default, PartialEq, Reflect, Visit, Eq)]
41pub struct SkyBox {
42 #[reflect(setter = "set_front")]
44 pub(crate) front: Option<TextureResource>,
45
46 #[reflect(setter = "set_back")]
48 pub(crate) back: Option<TextureResource>,
49
50 #[reflect(setter = "set_left")]
52 pub(crate) left: Option<TextureResource>,
53
54 #[reflect(setter = "set_right")]
56 pub(crate) right: Option<TextureResource>,
57
58 #[reflect(setter = "set_top")]
60 pub(crate) top: Option<TextureResource>,
61
62 #[reflect(setter = "set_bottom")]
64 pub(crate) bottom: Option<TextureResource>,
65
66 #[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 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 pub fn cubemap(&self) -> Option<TextureResource> {
96 self.cubemap.clone()
97 }
98
99 pub fn cubemap_ref(&self) -> Option<&TextureResource> {
101 self.cubemap.as_ref()
102 }
103
104 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 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 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 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 pub fn left(&self) -> Option<TextureResource> {
256 self.left.clone()
257 }
258
259 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 pub fn right(&self) -> Option<TextureResource> {
272 self.right.clone()
273 }
274
275 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 pub fn top(&self) -> Option<TextureResource> {
288 self.top.clone()
289 }
290
291 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 pub fn bottom(&self) -> Option<TextureResource> {
304 self.bottom.clone()
305 }
306
307 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 pub fn front(&self) -> Option<TextureResource> {
320 self.front.clone()
321 }
322
323 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 pub fn back(&self) -> Option<TextureResource> {
336 self.back.clone()
337 }
338}
339
340#[derive(Debug)]
342pub enum SkyBoxError {
343 UnsupportedTextureKind(TextureKind),
345 UnableToBuildCubeMap,
347 NonSquareTexture {
349 index: usize,
351 width: u32,
353 height: u32,
355 },
356 DifferentTexture {
358 expected_width: u32,
360 expected_height: u32,
362 expected_pixel_kind: TexturePixelKind,
364 index: usize,
366 actual_width: u32,
368 actual_height: u32,
370 actual_pixel_kind: TexturePixelKind,
372 },
373 TextureIsNotReady {
375 index: usize,
377 },
378}
379
380pub struct SkyBoxBuilder {
382 pub front: Option<TextureResource>,
384 pub back: Option<TextureResource>,
386 pub left: Option<TextureResource>,
388 pub right: Option<TextureResource>,
390 pub top: Option<TextureResource>,
392 pub bottom: Option<TextureResource>,
394}
395
396impl SkyBoxBuilder {
397 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 pub fn with_front(mut self, texture: TextureResource) -> Self {
411 self.front = Some(texture);
412 self
413 }
414
415 pub fn with_back(mut self, texture: TextureResource) -> Self {
417 self.back = Some(texture);
418 self
419 }
420
421 pub fn with_left(mut self, texture: TextureResource) -> Self {
423 self.left = Some(texture);
424 self
425 }
426
427 pub fn with_right(mut self, texture: TextureResource) -> Self {
429 self.right = Some(texture);
430 self
431 }
432
433 pub fn with_top(mut self, texture: TextureResource) -> Self {
435 self.top = Some(texture);
436 self
437 }
438
439 pub fn with_bottom(mut self, texture: TextureResource) -> Self {
441 self.bottom = Some(texture);
442 self
443 }
444
445 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 pub fn built_in_skybox() -> &'static SkyBox {
533 &BUILT_IN_SKYBOX
534 }
535
536 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#[derive(Default)]
552pub enum SkyBoxKind {
553 #[default]
555 Builtin,
556 None,
558 Specific(SkyBox),
560}