use std::{fmt::Display, sync::LazyLock};
use crate::{
asset::{builtin::BuiltInResource, embedded_data_source, untyped::ResourceKind},
core::{log::Log, reflect::prelude::*, uuid_provider, visitor::prelude::*},
};
use fyrox_core::color::Color;
use fyrox_texture::{
CompressionOptions, Texture, TextureImportOptions, TextureKind, TextureMinificationFilter,
TexturePixelKind, TextureResource, TextureResourceExtension, TextureWrapMode,
};
use uuid::{uuid, Uuid};
#[derive(Debug, Clone, Default, PartialEq, Reflect, Visit, Eq)]
pub struct SkyBox {
#[reflect(setter = "set_front")]
pub(crate) front: Option<TextureResource>,
#[reflect(setter = "set_back")]
pub(crate) back: Option<TextureResource>,
#[reflect(setter = "set_left")]
pub(crate) left: Option<TextureResource>,
#[reflect(setter = "set_right")]
pub(crate) right: Option<TextureResource>,
#[reflect(setter = "set_top")]
pub(crate) top: Option<TextureResource>,
#[reflect(setter = "set_bottom")]
pub(crate) bottom: Option<TextureResource>,
#[reflect(hidden)]
#[visit(skip)]
pub(crate) cubemap: Option<TextureResource>,
}
uuid_provider!(SkyBox = "45f359f1-e26f-4ace-81df-097f63474c72");
impl SkyBox {
pub fn from_single_color(color: Color) -> Self {
let dark_gray_texture = TextureResource::from_bytes(
Uuid::new_v4(),
TextureKind::Rectangle {
width: 1,
height: 1,
},
TexturePixelKind::RGBA8,
vec![color.r, color.g, color.b, color.a],
ResourceKind::Embedded,
)
.unwrap();
SkyBoxBuilder::from_texture(&dark_gray_texture)
.build()
.unwrap()
}
pub fn cubemap(&self) -> Option<TextureResource> {
self.cubemap.clone()
}
pub fn cubemap_ref(&self) -> Option<&TextureResource> {
self.cubemap.as_ref()
}
pub fn validate(&self) -> Result<(), SkyBoxError> {
struct TextureInfo {
pixel_kind: TexturePixelKind,
width: u32,
height: u32,
}
let mut first_info: Option<TextureInfo> = None;
for (index, texture) in self.textures().iter().enumerate() {
if let Some(texture) = texture {
if let Some(texture) = texture.state().data() {
if let TextureKind::Rectangle { width, height } = texture.kind() {
if width != height {
return Err(SkyBoxError::NonSquareTexture {
index,
width,
height,
});
}
if let Some(first_info) = first_info.as_mut() {
if first_info.width != width
|| first_info.height != height
|| first_info.pixel_kind != texture.pixel_kind()
{
return Err(SkyBoxError::DifferentTexture {
expected_width: first_info.width,
expected_height: first_info.height,
expected_pixel_kind: first_info.pixel_kind,
index,
actual_width: width,
actual_height: height,
actual_pixel_kind: texture.pixel_kind(),
});
}
} else {
first_info = Some(TextureInfo {
pixel_kind: texture.pixel_kind(),
width,
height,
});
}
}
} else {
return Err(SkyBoxError::TextureIsNotReady { index });
}
}
}
Ok(())
}
pub fn create_cubemap(&mut self) -> Result<(), SkyBoxError> {
self.validate()?;
let (kind, pixel_kind, bytes_per_face) =
self.textures().iter().find(|face| face.is_some()).map_or(
(
TextureKind::Rectangle {
width: 1,
height: 1,
},
TexturePixelKind::R8,
1,
),
|face| {
let face = face.clone().unwrap();
let data = face.data_ref();
(data.kind(), data.pixel_kind(), data.mip_level_data(0).len())
},
);
let size = match kind {
TextureKind::Rectangle { width, height } => {
assert_eq!(width, height);
width
}
_ => return Err(SkyBoxError::UnsupportedTextureKind(kind)),
};
let mut data = Vec::<u8>::with_capacity(bytes_per_face * 6);
for face in self.textures().iter() {
if let Some(f) = face.clone() {
data.extend(f.data_ref().mip_level_data(0));
} else {
let black_face_data = vec![0; bytes_per_face];
data.extend(black_face_data);
}
}
let cubemap = TextureResource::from_bytes(
Uuid::new_v4(),
TextureKind::Cube { size },
pixel_kind,
data,
ResourceKind::Embedded,
)
.ok_or(SkyBoxError::UnableToBuildCubeMap)?;
let mut cubemap_ref = cubemap.data_ref();
cubemap_ref.set_s_wrap_mode(TextureWrapMode::ClampToEdge);
cubemap_ref.set_t_wrap_mode(TextureWrapMode::ClampToEdge);
drop(cubemap_ref);
self.cubemap = Some(cubemap);
Ok(())
}
pub fn textures(&self) -> [Option<TextureResource>; 6] {
[
self.left.clone(),
self.right.clone(),
self.top.clone(),
self.bottom.clone(),
self.front.clone(),
self.back.clone(),
]
}
pub fn set_left(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.left, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn left(&self) -> Option<TextureResource> {
self.left.clone()
}
pub fn set_right(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.right, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn right(&self) -> Option<TextureResource> {
self.right.clone()
}
pub fn set_top(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.top, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn top(&self) -> Option<TextureResource> {
self.top.clone()
}
pub fn set_bottom(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.bottom, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn bottom(&self) -> Option<TextureResource> {
self.bottom.clone()
}
pub fn set_front(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.front, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn front(&self) -> Option<TextureResource> {
self.front.clone()
}
pub fn set_back(&mut self, texture: Option<TextureResource>) -> Option<TextureResource> {
let prev = std::mem::replace(&mut self.back, texture);
Log::verify(self.create_cubemap());
prev
}
pub fn back(&self) -> Option<TextureResource> {
self.back.clone()
}
}
#[derive(Debug)]
pub enum SkyBoxError {
UnsupportedTextureKind(TextureKind),
UnableToBuildCubeMap,
NonSquareTexture {
index: usize,
width: u32,
height: u32,
},
DifferentTexture {
expected_width: u32,
expected_height: u32,
expected_pixel_kind: TexturePixelKind,
index: usize,
actual_width: u32,
actual_height: u32,
actual_pixel_kind: TexturePixelKind,
},
TextureIsNotReady {
index: usize,
},
}
impl std::error::Error for SkyBoxError {}
impl Display for SkyBoxError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SkyBoxError::UnsupportedTextureKind(texture_kind) => {
write!(f, "Unsupported texture kind: {texture_kind:?}")
}
SkyBoxError::UnableToBuildCubeMap => f.write_str("Cube map was failed to build."),
SkyBoxError::NonSquareTexture {
index,
width,
height,
} => write!(
f,
"Input texture is not square. Index: {index}, width: {width}, height: {height}"
),
SkyBoxError::DifferentTexture {
expected_width,
expected_height,
expected_pixel_kind,
index,
actual_width,
actual_height,
actual_pixel_kind,
} => write!(f, "Some input texture differs in size or pixel kind. Index: {index}. \
Expected width: {expected_width}, height: {expected_height}, kind: {expected_pixel_kind:?}. \
Actual width: {actual_width}, height: {actual_height}, kind: {actual_pixel_kind:?}."),
SkyBoxError::TextureIsNotReady { index } => write!(f, "Input texture is not loaded. Index: {index}"),
}
}
}
pub struct SkyBoxBuilder {
pub front: Option<TextureResource>,
pub back: Option<TextureResource>,
pub left: Option<TextureResource>,
pub right: Option<TextureResource>,
pub top: Option<TextureResource>,
pub bottom: Option<TextureResource>,
}
impl SkyBoxBuilder {
pub fn from_texture(texture: &TextureResource) -> Self {
Self {
front: Some(texture.clone()),
back: Some(texture.clone()),
left: Some(texture.clone()),
right: Some(texture.clone()),
top: Some(texture.clone()),
bottom: Some(texture.clone()),
}
}
pub fn with_front(mut self, texture: TextureResource) -> Self {
self.front = Some(texture);
self
}
pub fn with_back(mut self, texture: TextureResource) -> Self {
self.back = Some(texture);
self
}
pub fn with_left(mut self, texture: TextureResource) -> Self {
self.left = Some(texture);
self
}
pub fn with_right(mut self, texture: TextureResource) -> Self {
self.right = Some(texture);
self
}
pub fn with_top(mut self, texture: TextureResource) -> Self {
self.top = Some(texture);
self
}
pub fn with_bottom(mut self, texture: TextureResource) -> Self {
self.bottom = Some(texture);
self
}
pub fn build(self) -> Result<SkyBox, SkyBoxError> {
let mut skybox = SkyBox {
left: self.left,
right: self.right,
top: self.top,
bottom: self.bottom,
front: self.front,
back: self.back,
cubemap: None,
};
skybox.create_cubemap()?;
Ok(skybox)
}
}
fn load_texture(id: Uuid, data: &[u8]) -> TextureResource {
TextureResource::load_from_memory(
id,
ResourceKind::External,
data,
TextureImportOptions::default()
.with_compression(CompressionOptions::NoCompression)
.with_minification_filter(TextureMinificationFilter::Linear),
)
.ok()
.unwrap()
}
static BUILT_IN_SKYBOX_FRONT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Front Face",
embedded_data_source!("skybox/front.png"),
|data| load_texture(uuid!("f8d4519b-2947-4c83-9aa5-800a70ae918e"), data),
)
});
static BUILT_IN_SKYBOX_BACK: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Back Face",
embedded_data_source!("skybox/back.png"),
|data| load_texture(uuid!("28676705-58bd-440f-b0aa-ce42cf95be79"), data),
)
});
static BUILT_IN_SKYBOX_TOP: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Top Face",
embedded_data_source!("skybox/top.png"),
|data| load_texture(uuid!("03e38da7-53d1-48c0-87f8-2baf9869d61d"), data),
)
});
static BUILT_IN_SKYBOX_BOTTOM: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Bottom Face",
embedded_data_source!("skybox/bottom.png"),
|data| load_texture(uuid!("01684dc1-34b2-48b3-b8c2-30a7718cb9e7"), data),
)
});
static BUILT_IN_SKYBOX_LEFT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Left Face",
embedded_data_source!("skybox/left.png"),
|data| load_texture(uuid!("1725b779-7633-477a-a7b0-995c079c3202"), data),
)
});
static BUILT_IN_SKYBOX_RIGHT: LazyLock<BuiltInResource<Texture>> = LazyLock::new(|| {
BuiltInResource::new(
"Skybox Right Face",
embedded_data_source!("skybox/right.png"),
|data| load_texture(uuid!("5f74865a-3eae-4bff-8743-b9d1f7bb3c59"), data),
)
});
static BUILT_IN_SKYBOX: LazyLock<SkyBox> = LazyLock::new(SkyBoxKind::make_built_in_skybox);
impl SkyBoxKind {
fn make_built_in_skybox() -> SkyBox {
let front = BUILT_IN_SKYBOX_FRONT.resource();
let back = BUILT_IN_SKYBOX_BACK.resource();
let top = BUILT_IN_SKYBOX_TOP.resource();
let bottom = BUILT_IN_SKYBOX_BOTTOM.resource();
let left = BUILT_IN_SKYBOX_LEFT.resource();
let right = BUILT_IN_SKYBOX_RIGHT.resource();
SkyBoxBuilder {
front: Some(front),
back: Some(back),
left: Some(left),
right: Some(right),
top: Some(top),
bottom: Some(bottom),
}
.build()
.unwrap()
}
pub fn built_in_skybox() -> &'static SkyBox {
&BUILT_IN_SKYBOX
}
pub fn built_in_skybox_textures() -> [&'static BuiltInResource<Texture>; 6] {
[
&BUILT_IN_SKYBOX_FRONT,
&BUILT_IN_SKYBOX_BACK,
&BUILT_IN_SKYBOX_TOP,
&BUILT_IN_SKYBOX_BOTTOM,
&BUILT_IN_SKYBOX_LEFT,
&BUILT_IN_SKYBOX_RIGHT,
]
}
}
#[derive(Default)]
pub enum SkyBoxKind {
#[default]
Builtin,
None,
Specific(SkyBox),
}