#![warn(missing_docs)]
use crate::{
asset::{
io::ResourceIo,
manager::{BuiltInResource, ResourceManager},
state::ResourceState,
untyped::ResourceKind,
Resource, ResourceData,
},
core::{
algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
color::Color,
io::FileLoadError,
parking_lot::Mutex,
reflect::prelude::*,
sstorage::ImmutableString,
uuid::{uuid, Uuid},
visitor::{prelude::*, RegionGuard},
TypeUuidProvider,
},
material::shader::{SamplerFallback, ShaderResource, ShaderResourceExtension},
resource::texture::TextureResource,
};
use fxhash::FxHashMap;
use fyrox_core::Downcast;
use lazy_static::lazy_static;
use std::{
error::Error,
fmt::{Display, Formatter},
path::Path,
sync::Arc,
};
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);
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>,
}
#[derive(Debug, Visit, Clone, Reflect)]
enum OldMaterialProperty {
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),
Sampler {
value: Option<TextureResource>,
fallback: SamplerFallback,
},
}
impl Default for OldMaterialProperty {
fn default() -> Self {
Self::Float(0.0)
}
}
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;
if region.is_reading() {
let mut old_properties = FxHashMap::<ImmutableString, OldMaterialProperty>::default();
if old_properties.visit("Properties", &mut region).is_ok() {
for (name, old_property) in &old_properties {
if let OldMaterialProperty::Sampler { value, .. } = old_property {
self.bind(
name.clone(),
MaterialResourceBinding::Texture(MaterialTextureBinding {
value: value.clone(),
}),
)
}
}
let properties = self.try_get_or_insert_property_group("properties");
for (name, old_property) in old_properties {
match old_property {
OldMaterialProperty::Float(v) => properties.set_property(name, v),
OldMaterialProperty::FloatArray(v) => properties.set_property(name, v),
OldMaterialProperty::Int(v) => properties.set_property(name, v),
OldMaterialProperty::IntArray(v) => properties.set_property(name, v),
OldMaterialProperty::UInt(v) => properties.set_property(name, v),
OldMaterialProperty::UIntArray(v) => properties.set_property(name, v),
OldMaterialProperty::Vector2(v) => properties.set_property(name, v),
OldMaterialProperty::Vector2Array(v) => properties.set_property(name, v),
OldMaterialProperty::Vector3(v) => properties.set_property(name, v),
OldMaterialProperty::Vector3Array(v) => properties.set_property(name, v),
OldMaterialProperty::Vector4(v) => properties.set_property(name, v),
OldMaterialProperty::Vector4Array(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix2(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix2Array(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix3(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix3Array(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix4(v) => properties.set_property(name, v),
OldMaterialProperty::Matrix4Array(v) => properties.set_property(name, v),
OldMaterialProperty::Bool(v) => properties.set_property(name, v),
OldMaterialProperty::Color(v) => properties.set_property(name, v),
_ => (),
};
}
} else {
self.resource_bindings
.visit("ResourceBindings", &mut region)?;
}
} else {
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_binary(path)?;
Ok(())
}
fn can_be_saved(&self) -> bool {
true
}
}
#[derive(Debug)]
pub enum MaterialError {
Visit(VisitError),
}
impl From<VisitError> for MaterialError {
fn from(value: VisitError) -> Self {
Self::Visit(value)
}
}
impl From<FileLoadError> for MaterialError {
fn from(value: FileLoadError) -> 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:?}")
}
}
}
}
lazy_static! {
pub static ref STANDARD: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__StandardMaterial".into(),
Material::from_shader(ShaderResource::standard()),
)
);
pub static ref STANDARD_2D: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__Standard2DMaterial".into(),
Material::from_shader(ShaderResource::standard_2d()),
)
);
pub static ref STANDARD_PARTICLE_SYSTEM: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__StandardParticleSystemMaterial".into(),
Material::from_shader(ShaderResource::standard_particle_system(),),
)
);
pub static ref STANDARD_SPRITE: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__StandardSpriteMaterial".into(),
Material::from_shader(ShaderResource::standard_sprite()),
)
);
pub static ref STANDARD_TERRAIN: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__StandardTerrainMaterial".into(),
Material::from_shader(ShaderResource::standard_terrain()),
)
);
pub static ref STANDARD_TWOSIDES: BuiltInResource<Material> = BuiltInResource::new_no_source(
MaterialResource::new_ok(
"__StandardTwoSidesMaterial".into(),
Material::from_shader(ShaderResource::standard_twosides()),
)
);
}
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 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(ref 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(ResourceKind::Embedded, material)
}
fn deep_copy(&self) -> MaterialResource {
let material_state = self.header();
let kind = material_state.kind.clone();
match material_state.state {
ResourceState::Pending { .. } => MaterialResource::new_pending(kind),
ResourceState::LoadError { ref error } => {
MaterialResource::new_load_error(kind.clone(), error.clone())
}
ResourceState::Ok(ref material) => MaterialResource::new_ok(
kind,
Downcast::as_any(&**material)
.downcast_ref::<Material>()
.unwrap()
.clone(),
),
}
}
}
pub(crate) fn visit_old_material(region: &mut RegionGuard) -> Option<MaterialResource> {
let mut old_material = Arc::new(Mutex::new(Material::default()));
if let Ok(mut inner) = region.enter_region("Material") {
if old_material.visit("Value", &mut inner).is_ok() {
return Some(MaterialResource::new_ok(
Default::default(),
old_material.lock().clone(),
));
}
}
None
}
pub(crate) fn visit_old_texture_as_material<F>(
region: &mut RegionGuard,
make_default_material: F,
) -> Option<MaterialResource>
where
F: FnOnce() -> Material,
{
let mut old_texture: Option<TextureResource> = None;
if let Ok(mut inner) = region.enter_region("Texture") {
if old_texture.visit("Value", &mut inner).is_ok() {
let mut material = make_default_material();
material.bind("diffuseTexture", old_texture);
return Some(MaterialResource::new_ok(Default::default(), material));
}
}
None
}