#![warn(missing_docs)]
use crate::shader::{ShaderResource, ShaderResourceExtension};
use fxhash::FxHashMap;
use fyrox_core::{
algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
color::Color,
io::FileError,
reflect::prelude::*,
sstorage::ImmutableString,
uuid::{uuid, Uuid},
visitor::prelude::*,
TypeUuidProvider,
};
use fyrox_resource::{
io::ResourceIo,
manager::{BuiltInResource, ResourceManager},
state::ResourceState,
untyped::ResourceKind,
Resource, ResourceData,
};
use fyrox_texture::TextureResource;
use std::{
any::Any,
error::Error,
fmt::{Display, Formatter},
path::Path,
sync::{Arc, LazyLock},
};
use strum_macros::{AsRefStr, EnumString, VariantNames};
pub mod loader;
pub mod shader;
#[derive(Default, Debug, Visit, Clone, Reflect, TypeUuidProvider)]
#[type_uuid(id = "e1642a47-d372-4840-a8eb-f16350f436f8")]
pub struct MaterialTextureBinding {
pub value: Option<TextureResource>,
}
#[derive(Debug, Visit, Clone, Reflect, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
#[type_uuid(id = "2df8f1e5-0075-4d0d-9860-70fc27d3e165")]
pub enum MaterialResourceBinding {
Texture(MaterialTextureBinding),
PropertyGroup(MaterialPropertyGroup),
}
impl Default for MaterialResourceBinding {
fn default() -> Self {
Self::PropertyGroup(Default::default())
}
}
impl MaterialResourceBinding {
pub fn as_texture(&self) -> Option<TextureResource> {
if let Self::Texture(binding) = self {
binding.value.clone()
} else {
None
}
}
}
#[derive(Default, Debug, Visit, Clone, Reflect)]
pub struct MaterialPropertyGroup {
properties: FxHashMap<ImmutableString, MaterialProperty>,
}
impl MaterialPropertyGroup {
pub fn property_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialProperty> {
let name = name.into();
self.properties.get(&name)
}
pub fn property_mut(
&mut self,
name: impl Into<ImmutableString>,
) -> Option<&mut MaterialProperty> {
let name = name.into();
self.properties.get_mut(&name)
}
pub fn set_property(
&mut self,
name: impl Into<ImmutableString>,
new_value: impl Into<MaterialProperty>,
) {
self.properties.insert(name.into(), new_value.into());
}
pub fn unset_property(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialProperty> {
self.properties.remove(&name.into())
}
pub fn properties(&self) -> &FxHashMap<ImmutableString, MaterialProperty> {
&self.properties
}
}
#[derive(Debug, Visit, Clone, Reflect, AsRefStr, EnumString, VariantNames, TypeUuidProvider)]
#[type_uuid(id = "1c25018d-ab6e-4dca-99a6-e3d9639bc33c")]
pub enum MaterialProperty {
Float(f32),
FloatArray(Vec<f32>),
Int(i32),
IntArray(Vec<i32>),
UInt(u32),
UIntArray(Vec<u32>),
Vector2(Vector2<f32>),
Vector2Array(Vec<Vector2<f32>>),
Vector3(Vector3<f32>),
Vector3Array(Vec<Vector3<f32>>),
Vector4(Vector4<f32>),
Vector4Array(Vec<Vector4<f32>>),
Matrix2(Matrix2<f32>),
Matrix2Array(Vec<Matrix2<f32>>),
Matrix3(Matrix3<f32>),
Matrix3Array(Vec<Matrix3<f32>>),
Matrix4(Matrix4<f32>),
Matrix4Array(Vec<Matrix4<f32>>),
Bool(bool),
Color(Color),
}
macro_rules! impl_from {
($variant:ident => $value_type:ty) => {
impl From<$value_type> for MaterialProperty {
fn from(value: $value_type) -> Self {
Self::$variant(value)
}
}
};
}
impl_from!(Float => f32);
impl_from!(FloatArray => Vec<f32>);
impl_from!(Int => i32);
impl_from!(IntArray => Vec<i32>);
impl_from!(UInt => u32);
impl_from!(UIntArray => Vec<u32>);
impl_from!(Vector2 => Vector2<f32>);
impl_from!(Vector2Array => Vec<Vector2<f32>>);
impl_from!(Vector3 => Vector3<f32>);
impl_from!(Vector3Array => Vec<Vector3<f32>>);
impl_from!(Vector4 => Vector4<f32>);
impl_from!(Vector4Array => Vec<Vector4<f32>>);
impl_from!(Matrix2 => Matrix2<f32>);
impl_from!(Matrix2Array => Vec<Matrix2<f32>>);
impl_from!(Matrix3 => Matrix3<f32>);
impl_from!(Matrix3Array => Vec<Matrix3<f32>>);
impl_from!(Matrix4 => Matrix4<f32>);
impl_from!(Matrix4Array => Vec<Matrix4<f32>>);
impl_from!(Bool => bool);
impl_from!(Color => Color);
#[derive(Debug, Copy, Clone)]
pub enum MaterialPropertyRef<'a> {
Float(&'a f32),
FloatArray(&'a [f32]),
Int(&'a i32),
IntArray(&'a [i32]),
UInt(&'a u32),
UIntArray(&'a [u32]),
Vector2(&'a Vector2<f32>),
Vector2Array(&'a [Vector2<f32>]),
Vector3(&'a Vector3<f32>),
Vector3Array(&'a [Vector3<f32>]),
Vector4(&'a Vector4<f32>),
Vector4Array(&'a [Vector4<f32>]),
Matrix2(&'a Matrix2<f32>),
Matrix2Array(&'a [Matrix2<f32>]),
Matrix3(&'a Matrix3<f32>),
Matrix3Array(&'a [Matrix3<f32>]),
Matrix4(&'a Matrix4<f32>),
Matrix4Array(&'a [Matrix4<f32>]),
Bool(&'a bool),
Color(&'a Color),
}
impl MaterialProperty {
pub fn as_ref(&self) -> MaterialPropertyRef<'_> {
match self {
MaterialProperty::Float(v) => MaterialPropertyRef::Float(v),
MaterialProperty::FloatArray(v) => MaterialPropertyRef::FloatArray(v),
MaterialProperty::Int(v) => MaterialPropertyRef::Int(v),
MaterialProperty::IntArray(v) => MaterialPropertyRef::IntArray(v),
MaterialProperty::UInt(v) => MaterialPropertyRef::UInt(v),
MaterialProperty::UIntArray(v) => MaterialPropertyRef::UIntArray(v),
MaterialProperty::Vector2(v) => MaterialPropertyRef::Vector2(v),
MaterialProperty::Vector2Array(v) => MaterialPropertyRef::Vector2Array(v),
MaterialProperty::Vector3(v) => MaterialPropertyRef::Vector3(v),
MaterialProperty::Vector3Array(v) => MaterialPropertyRef::Vector3Array(v),
MaterialProperty::Vector4(v) => MaterialPropertyRef::Vector4(v),
MaterialProperty::Vector4Array(v) => MaterialPropertyRef::Vector4Array(v),
MaterialProperty::Matrix2(v) => MaterialPropertyRef::Matrix2(v),
MaterialProperty::Matrix2Array(v) => MaterialPropertyRef::Matrix2Array(v),
MaterialProperty::Matrix3(v) => MaterialPropertyRef::Matrix3(v),
MaterialProperty::Matrix3Array(v) => MaterialPropertyRef::Matrix3Array(v),
MaterialProperty::Matrix4(v) => MaterialPropertyRef::Matrix4(v),
MaterialProperty::Matrix4Array(v) => MaterialPropertyRef::Matrix4Array(v),
MaterialProperty::Bool(v) => MaterialPropertyRef::Bool(v),
MaterialProperty::Color(v) => MaterialPropertyRef::Color(v),
}
}
}
macro_rules! impl_from_ref {
($variant:ident => $value_type:ty) => {
impl<'a> From<&'a $value_type> for MaterialPropertyRef<'a> {
fn from(value: &'a $value_type) -> Self {
Self::$variant(value)
}
}
};
}
impl_from_ref!(Float => f32);
impl_from_ref!(FloatArray => [f32]);
impl_from_ref!(Int => i32);
impl_from_ref!(IntArray => [i32]);
impl_from_ref!(UInt => u32);
impl_from_ref!(UIntArray => [u32]);
impl_from_ref!(Vector2 => Vector2<f32>);
impl_from_ref!(Vector2Array => [Vector2<f32>]);
impl_from_ref!(Vector3 => Vector3<f32>);
impl_from_ref!(Vector3Array => [Vector3<f32>]);
impl_from_ref!(Vector4 => Vector4<f32>);
impl_from_ref!(Vector4Array => [Vector4<f32>]);
impl_from_ref!(Matrix2 => Matrix2<f32>);
impl_from_ref!(Matrix2Array => [Matrix2<f32>]);
impl_from_ref!(Matrix3 => Matrix3<f32>);
impl_from_ref!(Matrix3Array => [Matrix3<f32>]);
impl_from_ref!(Matrix4 => Matrix4<f32>);
impl_from_ref!(Matrix4Array => [Matrix4<f32>]);
impl_from_ref!(Bool => bool);
impl_from_ref!(Color => Color);
impl From<Option<TextureResource>> for MaterialResourceBinding {
fn from(value: Option<TextureResource>) -> Self {
Self::Texture(MaterialTextureBinding { value })
}
}
impl From<TextureResource> for MaterialResourceBinding {
fn from(value: TextureResource) -> Self {
Self::Texture(MaterialTextureBinding { value: Some(value) })
}
}
macro_rules! define_as {
($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
$(#[$meta])*
pub fn $name(&self) -> Option<$ty> {
if let MaterialProperty::$variant(v) = self {
Some(*v)
} else {
None
}
}
};
}
macro_rules! define_as_ref {
($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
$(#[$meta])*
pub fn $name(&self) -> Option<&$ty> {
if let MaterialProperty::$variant(v) = self {
Some(v)
} else {
None
}
}
};
}
impl MaterialProperty {
define_as!(
as_float = Float -> f32
);
define_as_ref!(
as_float_array = FloatArray -> [f32]
);
define_as!(
as_int = Int -> i32
);
define_as_ref!(
as_int_array = IntArray -> [i32]
);
define_as!(
as_uint = UInt -> u32
);
define_as_ref!(
as_uint_array = UIntArray -> [u32]
);
define_as!(
as_bool = Bool -> bool
);
define_as!(
as_color = Color -> Color
);
define_as!(
as_vector2 = Vector2 -> Vector2<f32>
);
define_as_ref!(
as_vector2_array = Vector2Array -> [Vector2<f32>]
);
define_as!(
as_vector3 = Vector3 -> Vector3<f32>
);
define_as_ref!(
as_vector3_array = Vector3Array -> [Vector3<f32>]
);
define_as!(
as_vector4 = Vector4 -> Vector4<f32>
);
define_as_ref!(
as_vector4_array = Vector4Array -> [Vector4<f32>]
);
define_as!(
as_matrix2 = Matrix2 -> Matrix2<f32>
);
define_as_ref!(
as_matrix2_array = Matrix2Array -> [Matrix2<f32>]
);
define_as!(
as_matrix3 = Matrix3 -> Matrix3<f32>
);
define_as_ref!(
as_matrix3_array = Matrix3Array -> [Matrix3<f32>]
);
define_as!(
as_matrix4 = Matrix4 -> Matrix4<f32>
);
define_as_ref!(
as_matrix4_array = Matrix4Array -> [Matrix4<f32>]
);
}
impl Default for MaterialProperty {
fn default() -> Self {
Self::Float(0.0)
}
}
#[derive(Debug, Clone, Reflect)]
pub struct Material {
shader: ShaderResource,
resource_bindings: FxHashMap<ImmutableString, MaterialResourceBinding>,
}
impl Visit for Material {
fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
let mut region = visitor.enter_region(name)?;
let mut shader = if region.is_reading() {
ShaderResource::default()
} else {
self.shader.clone()
};
shader.visit("Shader", &mut region)?;
self.shader = shader;
self.resource_bindings
.visit("ResourceBindings", &mut region)?;
Ok(())
}
}
impl Default for Material {
fn default() -> Self {
Material::standard()
}
}
impl TypeUuidProvider for Material {
fn type_uuid() -> Uuid {
uuid!("0e54fe44-0c58-4108-a681-d6eefc88c234")
}
}
impl ResourceData for Material {
fn type_uuid(&self) -> Uuid {
<Self as TypeUuidProvider>::type_uuid()
}
fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
let mut visitor = Visitor::new();
self.visit("Material", &mut visitor)?;
visitor.save_ascii_to_file(path)?;
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
Some(Box::new(self.clone()))
}
}
#[derive(Debug)]
pub enum MaterialError {
Visit(VisitError),
}
impl From<VisitError> for MaterialError {
fn from(value: VisitError) -> Self {
Self::Visit(value)
}
}
impl From<FileError> for MaterialError {
fn from(value: FileError) -> Self {
Self::Visit(VisitError::FileLoadError(value))
}
}
impl Display for MaterialError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
MaterialError::Visit(e) => {
write!(f, "Failed to visit data source. Reason: {e:?}")
}
}
}
}
impl Material {
pub fn standard() -> Self {
Self::from_shader(ShaderResource::standard())
}
pub fn standard_2d() -> Self {
Self::from_shader(ShaderResource::standard_2d())
}
pub fn standard_particle_system() -> Self {
Self::from_shader(ShaderResource::standard_particle_system())
}
pub fn standard_sprite() -> Self {
Self::from_shader(ShaderResource::standard_sprite())
}
pub fn standard_two_sides() -> Self {
Self::from_shader(ShaderResource::standard_twosides())
}
pub fn standard_terrain() -> Self {
Self::from_shader(ShaderResource::standard_terrain())
}
pub fn standard_tile() -> Self {
Self::from_shader(ShaderResource::standard_tile())
}
pub fn standard_widget() -> Self {
Self::from_shader(ShaderResource::standard_widget())
}
pub fn from_shader(shader: ShaderResource) -> Self {
Self {
shader,
resource_bindings: Default::default(),
}
}
pub async fn from_file<P>(
path: P,
io: &dyn ResourceIo,
resource_manager: ResourceManager,
) -> Result<Self, MaterialError>
where
P: AsRef<Path>,
{
let content = io.load_file(path.as_ref()).await?;
let mut material = Material {
shader: Default::default(),
resource_bindings: Default::default(),
};
let mut visitor = Visitor::load_from_memory(&content)?;
visitor.blackboard.register(Arc::new(resource_manager));
material.visit("Material", &mut visitor)?;
Ok(material)
}
pub fn binding_ref(
&self,
name: impl Into<ImmutableString>,
) -> Option<&MaterialResourceBinding> {
let name = name.into();
self.resource_bindings.get(&name)
}
pub fn binding_mut(
&mut self,
name: impl Into<ImmutableString>,
) -> Option<&mut MaterialResourceBinding> {
let name = name.into();
self.resource_bindings.get_mut(&name)
}
pub fn texture_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialTextureBinding> {
if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_ref(name) {
Some(binding)
} else {
None
}
}
pub fn texture_mut(
&mut self,
name: impl Into<ImmutableString>,
) -> Option<&mut MaterialTextureBinding> {
if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_mut(name) {
Some(binding)
} else {
None
}
}
pub fn property_group_ref(
&self,
name: impl Into<ImmutableString>,
) -> Option<&MaterialPropertyGroup> {
self.binding_ref(name).and_then(|binding| match binding {
MaterialResourceBinding::Texture { .. } => None,
MaterialResourceBinding::PropertyGroup(group) => Some(group),
})
}
pub fn property_group_mut(
&mut self,
name: impl Into<ImmutableString>,
) -> Option<&mut MaterialPropertyGroup> {
self.binding_mut(name).and_then(|binding| match binding {
MaterialResourceBinding::Texture { .. } => None,
MaterialResourceBinding::PropertyGroup(group) => Some(group),
})
}
pub fn try_get_or_insert_property_group(
&mut self,
name: impl Into<ImmutableString>,
) -> &mut MaterialPropertyGroup {
let name = name.into();
if let MaterialResourceBinding::PropertyGroup(group) = self
.resource_bindings
.entry(name.clone())
.or_insert_with(|| {
MaterialResourceBinding::PropertyGroup(MaterialPropertyGroup::default())
})
{
group
} else {
panic!("There's already a material resource binding with {name}!");
}
}
pub fn bind(
&mut self,
name: impl Into<ImmutableString>,
new_value: impl Into<MaterialResourceBinding>,
) {
self.resource_bindings.insert(name.into(), new_value.into());
}
pub fn unbind(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialResourceBinding> {
self.resource_bindings.remove(&name.into())
}
pub fn set_property(
&mut self,
name: impl Into<ImmutableString>,
new_value: impl Into<MaterialProperty>,
) {
self.try_get_or_insert_property_group("properties")
.set_property(name, new_value);
}
pub fn shader(&self) -> &ShaderResource {
&self.shader
}
pub fn bindings(&self) -> &FxHashMap<ImmutableString, MaterialResourceBinding> {
&self.resource_bindings
}
pub fn texture(&self, name: impl Into<ImmutableString>) -> Option<TextureResource> {
self.resource_bindings.get(&name.into()).and_then(|v| {
if let MaterialResourceBinding::Texture(binding) = v {
binding.value.clone()
} else {
None
}
})
}
}
pub type MaterialResource = Resource<Material>;
pub trait MaterialResourceExtension {
fn new(material: Material) -> Self;
fn deep_copy(&self) -> MaterialResource;
fn deep_copy_as_embedded(&self) -> MaterialResource {
let material = self.deep_copy();
let mut header = material.header();
header.kind.make_embedded();
drop(header);
material
}
}
impl MaterialResourceExtension for MaterialResource {
#[inline(never)] fn new(material: Material) -> Self {
Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, material)
}
fn deep_copy(&self) -> MaterialResource {
let material_state = self.header();
match material_state.state {
ResourceState::Unloaded => self.resource_uuid().into(),
ResourceState::Pending { .. } => {
MaterialResource::new_pending(self.resource_uuid(), ResourceKind::External)
}
ResourceState::LoadError {
ref error,
ref path,
} => MaterialResource::new_load_error(
ResourceKind::External,
path.clone(),
error.clone(),
),
ResourceState::Ok { ref data } => MaterialResource::new_ok(
Uuid::new_v4(),
ResourceKind::Embedded,
(&**data as &dyn Any)
.downcast_ref::<Material>()
.unwrap()
.clone(),
),
}
}
}
pub static STANDARD: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Default Material",
MaterialResource::new_ok(
uuid!("fac37721-d1b8-422e-ae0c-83196ecd0a26"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard()),
),
)
});
pub static STANDARD_2D: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"2D Material",
MaterialResource::new_ok(
uuid!("fe78a0d0-d059-4156-bc63-c3d2e36ad4b6"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_2d()),
),
)
});
pub static STANDARD_PARTICLE_SYSTEM: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Particle SystemMaterial",
MaterialResource::new_ok(
uuid!("5bebe6e5-4aeb-496f-88f6-abe2b1ac798b"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_particle_system()),
),
)
});
pub static STANDARD_SPRITE: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Sprite Material",
MaterialResource::new_ok(
uuid!("3e331786-baae-412b-9d99-7370174bca43"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_sprite()),
),
)
});
pub static STANDARD_TERRAIN: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Terrain Material",
MaterialResource::new_ok(
uuid!("0e407e22-41ad-4763-9adb-9d2e86351ece"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_terrain()),
),
)
});
pub static STANDARD_TWOSIDES: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Two Sides Material",
MaterialResource::new_ok(
uuid!("24115321-7766-495c-bc3a-75db2f73d26d"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_twosides()),
),
)
});
pub static STANDARD_WIDGET: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
BuiltInResource::new_no_source(
"Widget Material",
MaterialResource::new_ok(
uuid!("e5d61a6f-5c94-4137-b303-1ae29cfff6e7"),
ResourceKind::External,
Material::from_shader(ShaderResource::standard_widget()),
),
)
});