fyrox_material/
lib.rs

1//! Material is a set of parameters for a shader. This module contains everything related to materials.
2//!
3//! See [Material struct docs](self::Material) for more info.
4
5#![warn(missing_docs)]
6
7use crate::shader::{ShaderResource, ShaderResourceExtension};
8use fxhash::FxHashMap;
9use fyrox_core::{
10    algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
11    color::Color,
12    io::FileError,
13    parking_lot::Mutex,
14    reflect::prelude::*,
15    sstorage::ImmutableString,
16    uuid::{uuid, Uuid},
17    visitor::{prelude::*, RegionGuard},
18    SafeLock, TypeUuidProvider,
19};
20use fyrox_graphics::gpu_program::SamplerFallback;
21use fyrox_resource::{
22    io::ResourceIo,
23    manager::{BuiltInResource, ResourceManager},
24    state::ResourceState,
25    untyped::ResourceKind,
26    Resource, ResourceData,
27};
28use fyrox_texture::TextureResource;
29use lazy_static::lazy_static;
30use std::{
31    any::Any,
32    error::Error,
33    fmt::{Display, Formatter},
34    path::Path,
35    sync::Arc,
36};
37use strum_macros::{AsRefStr, EnumString, VariantNames};
38
39pub mod loader;
40pub mod shader;
41
42/// A texture binding.
43#[derive(Default, Debug, Visit, Clone, Reflect, TypeUuidProvider)]
44#[type_uuid(id = "e1642a47-d372-4840-a8eb-f16350f436f8")]
45pub struct MaterialTextureBinding {
46    /// Actual value of the texture binding. Could be [`None`], in this case fallback value of the
47    /// shader will be used.
48    pub value: Option<TextureResource>,
49}
50
51/// A value of a resource binding that will be used for rendering.
52///
53/// # Limitations
54///
55/// There is a limited set of possible types that can be passed to a shader, most of them are
56/// just simple data types.
57#[derive(Debug, Visit, Clone, Reflect, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
58#[type_uuid(id = "2df8f1e5-0075-4d0d-9860-70fc27d3e165")]
59pub enum MaterialResourceBinding {
60    /// A texture.
61    Texture(MaterialTextureBinding),
62    /// A group of properties.
63    PropertyGroup(MaterialPropertyGroup),
64}
65
66impl Default for MaterialResourceBinding {
67    fn default() -> Self {
68        Self::PropertyGroup(Default::default())
69    }
70}
71
72impl MaterialResourceBinding {
73    /// Tries to extract a texture from the resource binding.
74    pub fn as_texture(&self) -> Option<TextureResource> {
75        if let Self::Texture(binding) = self {
76            binding.value.clone()
77        } else {
78            None
79        }
80    }
81}
82
83/// Property group stores a bunch of named values of a fixed set of types, that will be used for
84/// rendering with some shader.
85#[derive(Default, Debug, Visit, Clone, Reflect)]
86pub struct MaterialPropertyGroup {
87    properties: FxHashMap<ImmutableString, MaterialProperty>,
88}
89
90impl MaterialPropertyGroup {
91    /// Searches for a property with given name.
92    ///
93    /// # Complexity
94    ///
95    /// O(1)
96    ///
97    /// # Examples
98    ///
99    /// ```no_run
100    /// # use fyrox_core::sstorage::ImmutableString;
101    /// # use fyrox_material::MaterialPropertyGroup;
102    ///
103    /// let mut group = MaterialPropertyGroup::default();
104    /// let color = group.property_ref("diffuseColor").unwrap().as_color();
105    /// ```
106    pub fn property_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialProperty> {
107        let name = name.into();
108        self.properties.get(&name)
109    }
110
111    /// Searches for a property with given name.
112    ///
113    /// # Complexity
114    ///
115    /// O(1)
116    ///
117    /// # Examples
118    ///
119    /// ```no_run
120    /// # use fyrox_core::color::Color;
121    /// # use fyrox_core::sstorage::ImmutableString;
122    /// # use fyrox_material::{MaterialProperty, MaterialPropertyGroup};
123    ///
124    /// let mut group = MaterialPropertyGroup::default();
125    /// *group.property_mut("diffuseColor").unwrap() = MaterialProperty::Color(Color::RED);
126    /// ```
127    pub fn property_mut(
128        &mut self,
129        name: impl Into<ImmutableString>,
130    ) -> Option<&mut MaterialProperty> {
131        let name = name.into();
132        self.properties.get_mut(&name)
133    }
134
135    /// Sets new value of the property with given name.
136    ///
137    /// # Type checking
138    ///
139    /// This method does not check if the property exists in the shader nor its type. Validation
140    /// happens in the renderer, when it tries to use the property group. This is made for performance
141    /// reasons.
142    ///
143    /// # Example
144    ///
145    /// ```no_run
146    /// # use fyrox_material::{Material, MaterialPropertyGroup};
147    /// # use fyrox_core::color::Color;
148    ///
149    /// let mut group = MaterialPropertyGroup::default();
150    /// group.set_property("diffuseColor", Color::WHITE);
151    /// ```
152    pub fn set_property(
153        &mut self,
154        name: impl Into<ImmutableString>,
155        new_value: impl Into<MaterialProperty>,
156    ) {
157        self.properties.insert(name.into(), new_value.into());
158    }
159
160    /// Removes the property from the group. The renderer will use shader defaults for this property.
161    pub fn unset_property(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialProperty> {
162        self.properties.remove(&name.into())
163    }
164
165    /// Returns immutable reference to internal property storage.
166    pub fn properties(&self) -> &FxHashMap<ImmutableString, MaterialProperty> {
167        &self.properties
168    }
169}
170
171/// A set of possible material property types.
172#[derive(Debug, Visit, Clone, Reflect, AsRefStr, EnumString, VariantNames, TypeUuidProvider)]
173#[type_uuid(id = "1c25018d-ab6e-4dca-99a6-e3d9639bc33c")]
174pub enum MaterialProperty {
175    /// Real number.
176    Float(f32),
177
178    /// Real number array.
179    FloatArray(Vec<f32>),
180
181    /// Integer number.
182    Int(i32),
183
184    /// Integer number array.
185    IntArray(Vec<i32>),
186
187    /// Natural number.
188    UInt(u32),
189
190    /// Natural number array.
191    UIntArray(Vec<u32>),
192
193    /// Two-dimensional vector.
194    Vector2(Vector2<f32>),
195
196    /// Two-dimensional vector array.
197    Vector2Array(Vec<Vector2<f32>>),
198
199    /// Three-dimensional vector.
200    Vector3(Vector3<f32>),
201
202    /// Three-dimensional vector array.
203    Vector3Array(Vec<Vector3<f32>>),
204
205    /// Four-dimensional vector.
206    Vector4(Vector4<f32>),
207
208    /// Four-dimensional vector array.
209    Vector4Array(Vec<Vector4<f32>>),
210
211    /// 2x2 Matrix.
212    Matrix2(Matrix2<f32>),
213
214    /// 2x2 Matrix array.
215    Matrix2Array(Vec<Matrix2<f32>>),
216
217    /// 3x3 Matrix.
218    Matrix3(Matrix3<f32>),
219
220    /// 3x3 Matrix array.
221    Matrix3Array(Vec<Matrix3<f32>>),
222
223    /// 4x4 Matrix.
224    Matrix4(Matrix4<f32>),
225
226    /// 4x4 Matrix array.
227    Matrix4Array(Vec<Matrix4<f32>>),
228
229    /// Boolean value.
230    Bool(bool),
231
232    /// An sRGB color.
233    Color(Color),
234}
235
236macro_rules! impl_from {
237    ($variant:ident => $value_type:ty) => {
238        impl From<$value_type> for MaterialProperty {
239            fn from(value: $value_type) -> Self {
240                Self::$variant(value)
241            }
242        }
243    };
244}
245
246impl_from!(Float => f32);
247impl_from!(FloatArray => Vec<f32>);
248impl_from!(Int => i32);
249impl_from!(IntArray => Vec<i32>);
250impl_from!(UInt => u32);
251impl_from!(UIntArray => Vec<u32>);
252impl_from!(Vector2 => Vector2<f32>);
253impl_from!(Vector2Array => Vec<Vector2<f32>>);
254impl_from!(Vector3 => Vector3<f32>);
255impl_from!(Vector3Array => Vec<Vector3<f32>>);
256impl_from!(Vector4 => Vector4<f32>);
257impl_from!(Vector4Array => Vec<Vector4<f32>>);
258impl_from!(Matrix2 => Matrix2<f32>);
259impl_from!(Matrix2Array => Vec<Matrix2<f32>>);
260impl_from!(Matrix3 => Matrix3<f32>);
261impl_from!(Matrix3Array => Vec<Matrix3<f32>>);
262impl_from!(Matrix4 => Matrix4<f32>);
263impl_from!(Matrix4Array => Vec<Matrix4<f32>>);
264impl_from!(Bool => bool);
265impl_from!(Color => Color);
266
267/// A set of possible material property types.
268#[derive(Debug, Copy, Clone)]
269pub enum MaterialPropertyRef<'a> {
270    /// Real number.
271    Float(&'a f32),
272
273    /// Real number array.
274    FloatArray(&'a [f32]),
275
276    /// Integer number.
277    Int(&'a i32),
278
279    /// Integer number array.
280    IntArray(&'a [i32]),
281
282    /// Natural number.
283    UInt(&'a u32),
284
285    /// Natural number array.
286    UIntArray(&'a [u32]),
287
288    /// Two-dimensional vector.
289    Vector2(&'a Vector2<f32>),
290
291    /// Two-dimensional vector array.
292    Vector2Array(&'a [Vector2<f32>]),
293
294    /// Three-dimensional vector.
295    Vector3(&'a Vector3<f32>),
296
297    /// Three-dimensional vector array.
298    Vector3Array(&'a [Vector3<f32>]),
299
300    /// Four-dimensional vector.
301    Vector4(&'a Vector4<f32>),
302
303    /// Four-dimensional vector array.
304    Vector4Array(&'a [Vector4<f32>]),
305
306    /// 2x2 Matrix.
307    Matrix2(&'a Matrix2<f32>),
308
309    /// 2x2 Matrix array.
310    Matrix2Array(&'a [Matrix2<f32>]),
311
312    /// 3x3 Matrix.
313    Matrix3(&'a Matrix3<f32>),
314
315    /// 3x3 Matrix array.
316    Matrix3Array(&'a [Matrix3<f32>]),
317
318    /// 4x4 Matrix.
319    Matrix4(&'a Matrix4<f32>),
320
321    /// 4x4 Matrix array.
322    Matrix4Array(&'a [Matrix4<f32>]),
323
324    /// Boolean value.
325    Bool(&'a bool),
326
327    /// An sRGB color.
328    Color(&'a Color),
329}
330
331impl MaterialProperty {
332    /// Maps the inner value of the property to its respective reference.
333    pub fn as_ref(&self) -> MaterialPropertyRef<'_> {
334        match self {
335            MaterialProperty::Float(v) => MaterialPropertyRef::Float(v),
336            MaterialProperty::FloatArray(v) => MaterialPropertyRef::FloatArray(v),
337            MaterialProperty::Int(v) => MaterialPropertyRef::Int(v),
338            MaterialProperty::IntArray(v) => MaterialPropertyRef::IntArray(v),
339            MaterialProperty::UInt(v) => MaterialPropertyRef::UInt(v),
340            MaterialProperty::UIntArray(v) => MaterialPropertyRef::UIntArray(v),
341            MaterialProperty::Vector2(v) => MaterialPropertyRef::Vector2(v),
342            MaterialProperty::Vector2Array(v) => MaterialPropertyRef::Vector2Array(v),
343            MaterialProperty::Vector3(v) => MaterialPropertyRef::Vector3(v),
344            MaterialProperty::Vector3Array(v) => MaterialPropertyRef::Vector3Array(v),
345            MaterialProperty::Vector4(v) => MaterialPropertyRef::Vector4(v),
346            MaterialProperty::Vector4Array(v) => MaterialPropertyRef::Vector4Array(v),
347            MaterialProperty::Matrix2(v) => MaterialPropertyRef::Matrix2(v),
348            MaterialProperty::Matrix2Array(v) => MaterialPropertyRef::Matrix2Array(v),
349            MaterialProperty::Matrix3(v) => MaterialPropertyRef::Matrix3(v),
350            MaterialProperty::Matrix3Array(v) => MaterialPropertyRef::Matrix3Array(v),
351            MaterialProperty::Matrix4(v) => MaterialPropertyRef::Matrix4(v),
352            MaterialProperty::Matrix4Array(v) => MaterialPropertyRef::Matrix4Array(v),
353            MaterialProperty::Bool(v) => MaterialPropertyRef::Bool(v),
354            MaterialProperty::Color(v) => MaterialPropertyRef::Color(v),
355        }
356    }
357}
358
359macro_rules! impl_from_ref {
360    ($variant:ident => $value_type:ty) => {
361        impl<'a> From<&'a $value_type> for MaterialPropertyRef<'a> {
362            fn from(value: &'a $value_type) -> Self {
363                Self::$variant(value)
364            }
365        }
366    };
367}
368
369impl_from_ref!(Float => f32);
370impl_from_ref!(FloatArray => [f32]);
371impl_from_ref!(Int => i32);
372impl_from_ref!(IntArray => [i32]);
373impl_from_ref!(UInt => u32);
374impl_from_ref!(UIntArray => [u32]);
375impl_from_ref!(Vector2 => Vector2<f32>);
376impl_from_ref!(Vector2Array => [Vector2<f32>]);
377impl_from_ref!(Vector3 => Vector3<f32>);
378impl_from_ref!(Vector3Array => [Vector3<f32>]);
379impl_from_ref!(Vector4 => Vector4<f32>);
380impl_from_ref!(Vector4Array => [Vector4<f32>]);
381impl_from_ref!(Matrix2 => Matrix2<f32>);
382impl_from_ref!(Matrix2Array => [Matrix2<f32>]);
383impl_from_ref!(Matrix3 => Matrix3<f32>);
384impl_from_ref!(Matrix3Array => [Matrix3<f32>]);
385impl_from_ref!(Matrix4 => Matrix4<f32>);
386impl_from_ref!(Matrix4Array => [Matrix4<f32>]);
387impl_from_ref!(Bool => bool);
388impl_from_ref!(Color => Color);
389
390impl From<Option<TextureResource>> for MaterialResourceBinding {
391    fn from(value: Option<TextureResource>) -> Self {
392        Self::Texture(MaterialTextureBinding { value })
393    }
394}
395
396impl From<TextureResource> for MaterialResourceBinding {
397    fn from(value: TextureResource) -> Self {
398        Self::Texture(MaterialTextureBinding { value: Some(value) })
399    }
400}
401
402macro_rules! define_as {
403    ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
404        $(#[$meta])*
405        pub fn $name(&self) -> Option<$ty> {
406            if let MaterialProperty::$variant(v) = self {
407                Some(*v)
408            } else {
409                None
410            }
411        }
412    };
413}
414
415macro_rules! define_as_ref {
416    ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
417        $(#[$meta])*
418        pub fn $name(&self) -> Option<&$ty> {
419            if let MaterialProperty::$variant(v) = self {
420                Some(v)
421            } else {
422                None
423            }
424        }
425    };
426}
427
428impl MaterialProperty {
429    define_as!(
430        /// Tries to unwrap property value as float.
431        as_float = Float -> f32
432    );
433    define_as_ref!(
434        /// Tries to unwrap property value as float array.
435        as_float_array = FloatArray -> [f32]
436    );
437    define_as!(
438        /// Tries to unwrap property value as integer.
439        as_int = Int -> i32
440    );
441    define_as_ref!(
442        /// Tries to unwrap property value as integer array.
443        as_int_array = IntArray -> [i32]
444    );
445    define_as!(
446        /// Tries to unwrap property value as unsigned integer.
447        as_uint = UInt -> u32
448    );
449    define_as_ref!(
450        /// Tries to unwrap property value as unsigned integer array.
451        as_uint_array = UIntArray -> [u32]
452    );
453    define_as!(
454        /// Tries to unwrap property value as boolean.
455        as_bool = Bool -> bool
456    );
457    define_as!(
458        /// Tries to unwrap property value as color.
459        as_color = Color -> Color
460    );
461    define_as!(
462        /// Tries to unwrap property value as two-dimensional vector.
463        as_vector2 = Vector2 -> Vector2<f32>
464    );
465    define_as_ref!(
466        /// Tries to unwrap property value as two-dimensional vector array.
467        as_vector2_array = Vector2Array -> [Vector2<f32>]
468    );
469    define_as!(
470        /// Tries to unwrap property value as three-dimensional vector.
471        as_vector3 = Vector3 -> Vector3<f32>
472    );
473    define_as_ref!(
474        /// Tries to unwrap property value as three-dimensional vector array.
475        as_vector3_array = Vector3Array -> [Vector3<f32>]
476    );
477    define_as!(
478        /// Tries to unwrap property value as four-dimensional vector.
479        as_vector4 = Vector4 -> Vector4<f32>
480    );
481    define_as_ref!(
482        /// Tries to unwrap property value as four-dimensional vector array.
483        as_vector4_array = Vector4Array -> [Vector4<f32>]
484    );
485    define_as!(
486        /// Tries to unwrap property value as 2x2 matrix.
487        as_matrix2 = Matrix2 -> Matrix2<f32>
488    );
489    define_as_ref!(
490        /// Tries to unwrap property value as 2x2 matrix array.
491        as_matrix2_array = Matrix2Array -> [Matrix2<f32>]
492    );
493    define_as!(
494        /// Tries to unwrap property value as 3x3 matrix.
495        as_matrix3 = Matrix3 -> Matrix3<f32>
496    );
497    define_as_ref!(
498        /// Tries to unwrap property value as 3x3 matrix array.
499        as_matrix3_array = Matrix3Array -> [Matrix3<f32>]
500    );
501    define_as!(
502        /// Tries to unwrap property value as 4x4 matrix.
503        as_matrix4 = Matrix4 -> Matrix4<f32>
504    );
505    define_as_ref!(
506        /// Tries to unwrap property value as 4x4 matrix array.
507        as_matrix4_array = Matrix4Array -> [Matrix4<f32>]
508    );
509}
510
511impl Default for MaterialProperty {
512    fn default() -> Self {
513        Self::Float(0.0)
514    }
515}
516
517/// Material defines a set of resource bindings for a shader. It could be textures and property groups.
518/// Textures contains graphical information (such as diffuse, normal, height, emission, etc. maps),
519/// while property groups contains numerical values (matrices, vectors, numbers, etc.).
520///
521/// Each resource binding can be changed in runtime giving you the ability to create animated materials.
522/// However, in practice most materials are static, this means that once it created, it won't be
523/// changed anymore.
524///
525/// Please keep in mind that the actual "rules" of drawing an entity are stored in the shader,
526/// **material is only a storage** for specific uses of the shader.
527///
528/// Multiple materials can share the same shader, for example standard shader covers 95% of most
529/// common use cases, and it is shared across multiple materials. The only difference are property
530/// values, for example you can draw multiple cubes using the same shader, but with different
531/// textures.
532///
533/// Material itself can be shared across multiple places as well as the shader. This gives you the
534/// ability to render multiple objects with the same material efficiently.
535///
536/// # Performance
537///
538/// It is very important to re-use materials as much as possible, because the amount of materials used
539/// per frame significantly correlates with performance. The more unique materials you have per frame,
540/// the more work has to be done by the renderer and the video driver to render a frame and the more time
541/// the frame will require for rendering, thus lowering your FPS.
542///
543/// # Examples
544///
545/// A material can only be created using a shader instance, every material must have a shader. The
546/// shader provides information about its resources. Default values of each property defined in the
547/// shader.
548///
549/// ## Standard material
550///
551/// Usually standard shader is enough for most cases, [`Material`] even has a [`Material::standard()`]
552/// method to create a material with standard shader:
553///
554/// ```no_run
555/// # use fyrox_resource::manager::ResourceManager;
556/// # use fyrox_core::sstorage::ImmutableString;
557/// # use fyrox_material::{
558/// #     Material, MaterialProperty,
559/// #     shader::{ShaderResource, SamplerFallback},
560/// # };
561/// # use fyrox_texture::Texture;
562///
563/// fn create_brick_material(resource_manager: ResourceManager) -> Material {
564///     let mut material = Material::standard();
565///
566///     material.bind(
567///         "diffuseTexture",
568///         resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
569///     );
570///
571///     material
572/// }
573/// ```
574///
575/// As you can see it is pretty simple with standard material, all you need is to set values to desired
576/// properties and you good to go. All you need to do is to apply the material, for example it could be
577/// mesh surface or some other place that supports materials. For the full list of properties of the
578/// standard shader see [shader module docs](self::shader).
579///
580/// ## Custom material
581///
582/// Custom materials is a bit more complex, you need to get a shader instance using the resource manager
583/// (or create a shader in-place by embedding its source code in the binary) and then create the material
584/// and populate it with a set of property values.
585///
586/// ```no_run
587/// # use fyrox_core::{sstorage::ImmutableString, algebra::Vector3};
588/// # use fyrox_resource::manager::ResourceManager;
589/// # use fyrox_material::{Material, MaterialProperty, shader::Shader};
590///
591/// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
592///     let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
593///
594///     // Here we assume that the material really has the properties defined below.
595///     let mut material = Material::from_shader(shader);
596///
597///     material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
598///
599///     material
600/// }
601/// ```
602///
603/// As you can see it is slightly more complex that with the standard shader. The main difference here is
604/// that we using resource manager to get shader instance, and then we just use the instance to create
605/// material instance. Then we populate properties as usual.
606#[derive(Debug, Clone, Reflect)]
607pub struct Material {
608    shader: ShaderResource,
609    resource_bindings: FxHashMap<ImmutableString, MaterialResourceBinding>,
610}
611
612#[derive(Debug, Visit, Clone, Reflect)]
613enum OldMaterialProperty {
614    Float(f32),
615    FloatArray(Vec<f32>),
616    Int(i32),
617    IntArray(Vec<i32>),
618    UInt(u32),
619    UIntArray(Vec<u32>),
620    Vector2(Vector2<f32>),
621    Vector2Array(Vec<Vector2<f32>>),
622    Vector3(Vector3<f32>),
623    Vector3Array(Vec<Vector3<f32>>),
624    Vector4(Vector4<f32>),
625    Vector4Array(Vec<Vector4<f32>>),
626    Matrix2(Matrix2<f32>),
627    Matrix2Array(Vec<Matrix2<f32>>),
628    Matrix3(Matrix3<f32>),
629    Matrix3Array(Vec<Matrix3<f32>>),
630    Matrix4(Matrix4<f32>),
631    Matrix4Array(Vec<Matrix4<f32>>),
632    Bool(bool),
633    Color(Color),
634    Sampler {
635        value: Option<TextureResource>,
636        fallback: SamplerFallback,
637    },
638}
639
640impl Default for OldMaterialProperty {
641    fn default() -> Self {
642        Self::Float(0.0)
643    }
644}
645
646impl Visit for Material {
647    fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
648        let mut region = visitor.enter_region(name)?;
649
650        let mut shader = if region.is_reading() {
651            // It is very important to give a proper default state to the shader resource
652            // here. Its standard default is set to shared "Standard" shader. If it is left
653            // as is, deserialization will modify the "Standard" shader and this will lead
654            // to "amazing" results and hours of debugging.
655            ShaderResource::default()
656        } else {
657            self.shader.clone()
658        };
659        shader.visit("Shader", &mut region)?;
660        self.shader = shader;
661
662        if region.is_reading() {
663            // Backward compatibility.
664            let mut old_properties = FxHashMap::<ImmutableString, OldMaterialProperty>::default();
665            if old_properties.visit("Properties", &mut region).is_ok() {
666                for (name, old_property) in &old_properties {
667                    if let OldMaterialProperty::Sampler { value, .. } = old_property {
668                        self.bind(
669                            name.clone(),
670                            MaterialResourceBinding::Texture(MaterialTextureBinding {
671                                value: value.clone(),
672                            }),
673                        )
674                    }
675                }
676
677                let properties = self.try_get_or_insert_property_group("properties");
678
679                for (name, old_property) in old_properties {
680                    match old_property {
681                        OldMaterialProperty::Float(v) => properties.set_property(name, v),
682                        OldMaterialProperty::FloatArray(v) => properties.set_property(name, v),
683                        OldMaterialProperty::Int(v) => properties.set_property(name, v),
684                        OldMaterialProperty::IntArray(v) => properties.set_property(name, v),
685                        OldMaterialProperty::UInt(v) => properties.set_property(name, v),
686                        OldMaterialProperty::UIntArray(v) => properties.set_property(name, v),
687                        OldMaterialProperty::Vector2(v) => properties.set_property(name, v),
688                        OldMaterialProperty::Vector2Array(v) => properties.set_property(name, v),
689                        OldMaterialProperty::Vector3(v) => properties.set_property(name, v),
690                        OldMaterialProperty::Vector3Array(v) => properties.set_property(name, v),
691                        OldMaterialProperty::Vector4(v) => properties.set_property(name, v),
692                        OldMaterialProperty::Vector4Array(v) => properties.set_property(name, v),
693                        OldMaterialProperty::Matrix2(v) => properties.set_property(name, v),
694                        OldMaterialProperty::Matrix2Array(v) => properties.set_property(name, v),
695                        OldMaterialProperty::Matrix3(v) => properties.set_property(name, v),
696                        OldMaterialProperty::Matrix3Array(v) => properties.set_property(name, v),
697                        OldMaterialProperty::Matrix4(v) => properties.set_property(name, v),
698                        OldMaterialProperty::Matrix4Array(v) => properties.set_property(name, v),
699                        OldMaterialProperty::Bool(v) => properties.set_property(name, v),
700                        OldMaterialProperty::Color(v) => properties.set_property(name, v),
701                        _ => (),
702                    };
703                }
704            } else {
705                self.resource_bindings
706                    .visit("ResourceBindings", &mut region)?;
707            }
708        } else {
709            self.resource_bindings
710                .visit("ResourceBindings", &mut region)?;
711        }
712
713        Ok(())
714    }
715}
716
717impl Default for Material {
718    fn default() -> Self {
719        Material::standard()
720    }
721}
722
723impl TypeUuidProvider for Material {
724    fn type_uuid() -> Uuid {
725        uuid!("0e54fe44-0c58-4108-a681-d6eefc88c234")
726    }
727}
728
729impl ResourceData for Material {
730    fn type_uuid(&self) -> Uuid {
731        <Self as TypeUuidProvider>::type_uuid()
732    }
733
734    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
735        let mut visitor = Visitor::new();
736        self.visit("Material", &mut visitor)?;
737        visitor.save_ascii_to_file(path)?;
738        Ok(())
739    }
740
741    fn can_be_saved(&self) -> bool {
742        true
743    }
744
745    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
746        Some(Box::new(self.clone()))
747    }
748}
749
750/// A set of possible errors that can occur when working with materials.
751#[derive(Debug)]
752pub enum MaterialError {
753    /// Unable to read data source.
754    Visit(VisitError),
755}
756
757impl From<VisitError> for MaterialError {
758    fn from(value: VisitError) -> Self {
759        Self::Visit(value)
760    }
761}
762
763impl From<FileError> for MaterialError {
764    fn from(value: FileError) -> Self {
765        Self::Visit(VisitError::FileLoadError(value))
766    }
767}
768
769impl Display for MaterialError {
770    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
771        match self {
772            MaterialError::Visit(e) => {
773                write!(f, "Failed to visit data source. Reason: {e:?}")
774            }
775        }
776    }
777}
778
779impl Material {
780    /// Creates a new instance of material with the standard shader.
781    ///
782    /// # Example
783    ///
784    /// ```no_run
785    /// # use fyrox_core::sstorage::ImmutableString;
786    /// # use fyrox_texture::Texture;
787    /// # use fyrox_material::{Material, MaterialProperty, shader::{ShaderResource, SamplerFallback}};
788    /// # use fyrox_resource::manager::ResourceManager;
789    ///
790    /// fn create_brick_material(resource_manager: ResourceManager) -> Material {
791    ///     let mut material = Material::standard();
792    ///
793    ///     material.bind(
794    ///         "diffuseTexture",
795    ///         resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
796    ///     );
797    ///
798    ///     material
799    /// }
800    /// ```
801    pub fn standard() -> Self {
802        Self::from_shader(ShaderResource::standard())
803    }
804
805    /// Creates new instance of standard 2D material.
806    pub fn standard_2d() -> Self {
807        Self::from_shader(ShaderResource::standard_2d())
808    }
809
810    /// Creates new instance of standard 2D material.
811    pub fn standard_particle_system() -> Self {
812        Self::from_shader(ShaderResource::standard_particle_system())
813    }
814
815    /// Creates new instance of standard sprite material.
816    pub fn standard_sprite() -> Self {
817        Self::from_shader(ShaderResource::standard_sprite())
818    }
819
820    /// Creates new instance of standard material that renders both sides of a face.
821    pub fn standard_two_sides() -> Self {
822        Self::from_shader(ShaderResource::standard_twosides())
823    }
824
825    /// Creates new instance of standard terrain material.
826    pub fn standard_terrain() -> Self {
827        Self::from_shader(ShaderResource::standard_terrain())
828    }
829
830    /// Creates new instance of standard tile material.
831    pub fn standard_tile() -> Self {
832        Self::from_shader(ShaderResource::standard_tile())
833    }
834
835    /// Creates new instance of standard widget material.
836    pub fn standard_widget() -> Self {
837        Self::from_shader(ShaderResource::standard_widget())
838    }
839
840    /// Creates a new material instance with given shader. By default, a material does not store any
841    /// resource bindings. In this case the renderer will use shader default values for rendering.
842    /// Materials could be considered as container with values that overwrites shader values.
843    ///
844    /// # Example
845    ///
846    /// ```no_run
847    /// # use fyrox_core::{sstorage::ImmutableString, algebra::Vector3};
848    /// # use fyrox_resource::manager::ResourceManager;
849    /// # use fyrox_material::{Material, MaterialProperty, shader::Shader};
850    ///
851    /// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
852    ///     let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
853    ///
854    ///     // Here we assume that the material really has the properties defined below.
855    ///     let mut material = Material::from_shader(shader);
856    ///
857    ///     material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
858    ///
859    ///     material
860    /// }
861    /// ```
862    pub fn from_shader(shader: ShaderResource) -> Self {
863        Self {
864            shader,
865            resource_bindings: Default::default(),
866        }
867    }
868
869    /// Loads a material from file.
870    pub async fn from_file<P>(
871        path: P,
872        io: &dyn ResourceIo,
873        resource_manager: ResourceManager,
874    ) -> Result<Self, MaterialError>
875    where
876        P: AsRef<Path>,
877    {
878        let content = io.load_file(path.as_ref()).await?;
879        let mut material = Material {
880            shader: Default::default(),
881            resource_bindings: Default::default(),
882        };
883        let mut visitor = Visitor::load_from_memory(&content)?;
884        visitor.blackboard.register(Arc::new(resource_manager));
885        material.visit("Material", &mut visitor)?;
886        Ok(material)
887    }
888
889    /// Searches for a resource binding with the given name and returns immutable reference to it
890    /// (if any).
891    ///
892    /// # Complexity
893    ///
894    /// O(1)
895    pub fn binding_ref(
896        &self,
897        name: impl Into<ImmutableString>,
898    ) -> Option<&MaterialResourceBinding> {
899        let name = name.into();
900        self.resource_bindings.get(&name)
901    }
902
903    /// Searches for a resource binding with the given name and returns mutable reference to it,
904    /// allowing you to modify the value.
905    ///
906    /// # Complexity
907    ///
908    /// O(1)
909    pub fn binding_mut(
910        &mut self,
911        name: impl Into<ImmutableString>,
912    ) -> Option<&mut MaterialResourceBinding> {
913        let name = name.into();
914        self.resource_bindings.get_mut(&name)
915    }
916
917    /// Searches for a texture with the given name.
918    pub fn texture_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialTextureBinding> {
919        if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_ref(name) {
920            Some(binding)
921        } else {
922            None
923        }
924    }
925
926    /// Searches for a texture with the given name.
927    pub fn texture_mut(
928        &mut self,
929        name: impl Into<ImmutableString>,
930    ) -> Option<&mut MaterialTextureBinding> {
931        if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_mut(name) {
932            Some(binding)
933        } else {
934            None
935        }
936    }
937
938    /// Searches for a property group binding with the given name and returns immutable reference to it
939    /// (if any).
940    ///
941    /// # Complexity
942    ///
943    /// O(1)
944    ///
945    /// # Examples
946    ///
947    /// ```no_run
948    /// # use fyrox_core::sstorage::ImmutableString;
949    /// # use fyrox_material::Material;
950    ///
951    /// let mut material = Material::standard();
952    ///
953    /// let color = material
954    ///     .property_group_ref("properties")
955    ///     .unwrap()
956    ///     .property_ref("diffuseColor")
957    ///     .unwrap()
958    ///     .as_color();
959    /// ```
960    pub fn property_group_ref(
961        &self,
962        name: impl Into<ImmutableString>,
963    ) -> Option<&MaterialPropertyGroup> {
964        self.binding_ref(name).and_then(|binding| match binding {
965            MaterialResourceBinding::Texture { .. } => None,
966            MaterialResourceBinding::PropertyGroup(group) => Some(group),
967        })
968    }
969
970    /// Searches for a property group binding with the given name and returns immutable reference to it
971    /// (if any).
972    ///
973    /// # Complexity
974    ///
975    /// O(1)
976    ///
977    /// # Examples
978    ///
979    /// ```no_run
980    /// # use fyrox_core::color::Color;
981    /// # use fyrox_core::sstorage::ImmutableString;
982    /// # use fyrox_material::Material;
983    ///
984    /// let mut material = Material::standard();
985    ///
986    /// let color = material
987    ///     .property_group_mut("properties")
988    ///     .unwrap()
989    ///     .set_property("diffuseColor", Color::RED);
990    /// ```
991    pub fn property_group_mut(
992        &mut self,
993        name: impl Into<ImmutableString>,
994    ) -> Option<&mut MaterialPropertyGroup> {
995        self.binding_mut(name).and_then(|binding| match binding {
996            MaterialResourceBinding::Texture { .. } => None,
997            MaterialResourceBinding::PropertyGroup(group) => Some(group),
998        })
999    }
1000
1001    /// Tries to find a property group with the given name, creates a new group with the given name
1002    /// if there's no such group.
1003    pub fn try_get_or_insert_property_group(
1004        &mut self,
1005        name: impl Into<ImmutableString>,
1006    ) -> &mut MaterialPropertyGroup {
1007        let name = name.into();
1008        if let MaterialResourceBinding::PropertyGroup(group) = self
1009            .resource_bindings
1010            .entry(name.clone())
1011            .or_insert_with(|| {
1012                MaterialResourceBinding::PropertyGroup(MaterialPropertyGroup::default())
1013            })
1014        {
1015            group
1016        } else {
1017            panic!("There's already a material resource binding with {name}!");
1018        }
1019    }
1020
1021    /// Sets new value of the resource binding with given name.
1022    ///
1023    /// # Type checking
1024    ///
1025    /// A new value must have the same type as in shader, otherwise an error will be generated at
1026    /// attempt to render something with this material.
1027    ///
1028    /// # Example
1029    ///
1030    /// ```no_run
1031    /// # use fyrox_material::{Material, MaterialProperty};
1032    /// # use fyrox_core::color::Color;
1033    /// # use fyrox_core::sstorage::ImmutableString;
1034    ///
1035    /// let mut material = Material::standard();
1036    ///
1037    /// material.set_property("diffuseColor", Color::WHITE);
1038    /// ```
1039    pub fn bind(
1040        &mut self,
1041        name: impl Into<ImmutableString>,
1042        new_value: impl Into<MaterialResourceBinding>,
1043    ) {
1044        self.resource_bindings.insert(name.into(), new_value.into());
1045    }
1046
1047    /// Tries to remove a resource bound to the given name.
1048    pub fn unbind(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialResourceBinding> {
1049        self.resource_bindings.remove(&name.into())
1050    }
1051
1052    /// Sets new value of the property with given name to the property group with `properties` name.
1053    /// It is a standard property group, that could be used to store pretty much any values.
1054    ///
1055    /// # Type checking
1056    ///
1057    /// A new value must have the same type as in shader, otherwise an error will be generated at
1058    /// attempt to render something with this material.
1059    ///
1060    /// # Example
1061    ///
1062    /// ```no_run
1063    /// # use fyrox_material::{Material, MaterialProperty};
1064    /// # use fyrox_core::color::Color;
1065    /// # use fyrox_core::sstorage::ImmutableString;
1066    ///
1067    /// let mut material = Material::standard();
1068    ///
1069    /// material.set_property("diffuseColor", Color::WHITE);
1070    ///
1071    /// // A full equivalent of the above:
1072    /// material.try_get_or_insert_property_group("properties")
1073    ///         .set_property("diffuseColor", Color::WHITE);
1074    /// ```
1075    pub fn set_property(
1076        &mut self,
1077        name: impl Into<ImmutableString>,
1078        new_value: impl Into<MaterialProperty>,
1079    ) {
1080        self.try_get_or_insert_property_group("properties")
1081            .set_property(name, new_value);
1082    }
1083
1084    /// Returns a reference to current shader.
1085    pub fn shader(&self) -> &ShaderResource {
1086        &self.shader
1087    }
1088
1089    /// Returns immutable reference to internal property storage.
1090    pub fn bindings(&self) -> &FxHashMap<ImmutableString, MaterialResourceBinding> {
1091        &self.resource_bindings
1092    }
1093
1094    /// Tries to find a sampler with the given name and returns its texture (if any).
1095    pub fn texture(&self, name: impl Into<ImmutableString>) -> Option<TextureResource> {
1096        self.resource_bindings.get(&name.into()).and_then(|v| {
1097            if let MaterialResourceBinding::Texture(binding) = v {
1098                binding.value.clone()
1099            } else {
1100                None
1101            }
1102        })
1103    }
1104}
1105
1106/// Material resource is a material instance that can be used across multiple objects. It is useful
1107/// when you need to have multiple objects that have the same material.
1108///
1109/// Material resource is also tells a renderer that this material can be used for efficient rendering -
1110/// the renderer will be able to optimize rendering when it knows that multiple objects share the
1111/// same material.
1112pub type MaterialResource = Resource<Material>;
1113
1114/// Extension methods for material resource.
1115pub trait MaterialResourceExtension {
1116    /// Creates a new material resource.
1117    ///
1118    /// # Hot Reloading
1119    ///
1120    /// You must use this method to create materials, if you want hot reloading to be reliable and
1121    /// prevent random crashes. Unlike [`Resource::new_ok`], this method ensures that correct vtable
1122    /// is used.
1123    fn new(material: Material) -> Self;
1124
1125    /// Creates a deep copy of the material resource.
1126    fn deep_copy(&self) -> MaterialResource;
1127
1128    /// Creates a deep copy of the material resource and marks it as procedural.
1129    fn deep_copy_as_embedded(&self) -> MaterialResource {
1130        let material = self.deep_copy();
1131        let mut header = material.header();
1132        header.kind.make_embedded();
1133        drop(header);
1134        material
1135    }
1136}
1137
1138impl MaterialResourceExtension for MaterialResource {
1139    #[inline(never)] // Prevents vtable mismatch when doing hot reloading.
1140    fn new(material: Material) -> Self {
1141        Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, material)
1142    }
1143
1144    fn deep_copy(&self) -> MaterialResource {
1145        let material_state = self.header();
1146        match material_state.state {
1147            ResourceState::Unloaded => self.resource_uuid().into(),
1148            ResourceState::Pending { .. } => {
1149                MaterialResource::new_pending(self.resource_uuid(), ResourceKind::External)
1150            }
1151            ResourceState::LoadError {
1152                ref error,
1153                ref path,
1154            } => MaterialResource::new_load_error(
1155                ResourceKind::External,
1156                path.clone(),
1157                error.clone(),
1158            ),
1159            ResourceState::Ok { ref data } => MaterialResource::new_ok(
1160                Uuid::new_v4(),
1161                ResourceKind::Embedded,
1162                (&**data as &dyn Any)
1163                    .downcast_ref::<Material>()
1164                    .unwrap()
1165                    .clone(),
1166            ),
1167        }
1168    }
1169}
1170
1171#[doc(hidden)]
1172pub fn visit_old_material(region: &mut RegionGuard) -> Option<MaterialResource> {
1173    let mut old_material = Arc::new(Mutex::new(Material::default()));
1174    if let Ok(mut inner) = region.enter_region("Material") {
1175        if old_material.visit("Value", &mut inner).is_ok() {
1176            return Some(MaterialResource::new_ok(
1177                Uuid::new_v4(),
1178                Default::default(),
1179                old_material.safe_lock().clone(),
1180            ));
1181        }
1182    }
1183    None
1184}
1185
1186#[doc(hidden)]
1187pub fn visit_old_texture_as_material<F>(
1188    region: &mut RegionGuard,
1189    make_default_material: F,
1190) -> Option<MaterialResource>
1191where
1192    F: FnOnce() -> Material,
1193{
1194    let mut old_texture: Option<TextureResource> = None;
1195    if let Ok(mut inner) = region.enter_region("Texture") {
1196        if old_texture.visit("Value", &mut inner).is_ok() {
1197            let mut material = make_default_material();
1198            material.bind("diffuseTexture", old_texture);
1199            return Some(MaterialResource::new_ok(
1200                Uuid::new_v4(),
1201                Default::default(),
1202                material,
1203            ));
1204        }
1205    }
1206    None
1207}
1208
1209lazy_static! {
1210    /// Standard PBR material. Keep in mind that this material is global, any modification
1211    /// of it will reflect on every other usage of it.
1212    pub static ref STANDARD: BuiltInResource<Material> = BuiltInResource::new_no_source("__StandardMaterial",
1213        MaterialResource::new_ok(
1214            uuid!("fac37721-d1b8-422e-ae0c-83196ecd0a26"),
1215            ResourceKind::External,
1216            Material::from_shader(ShaderResource::standard()),
1217        )
1218    );
1219
1220    /// Standard 2D material. Keep in mind that this material is global, any modification
1221    /// of it will reflect on every other usage of it.
1222    pub static ref STANDARD_2D: BuiltInResource<Material> = BuiltInResource::new_no_source("__Standard2DMaterial",
1223        MaterialResource::new_ok(
1224            uuid!("fe78a0d0-d059-4156-bc63-c3d2e36ad4b6"),
1225            ResourceKind::External,
1226            Material::from_shader(ShaderResource::standard_2d()),
1227        )
1228    );
1229
1230    /// Standard particle system material. Keep in mind that this material is global, any modification
1231    /// of it will reflect on every other usage of it.
1232    pub static ref STANDARD_PARTICLE_SYSTEM: BuiltInResource<Material> = BuiltInResource::new_no_source(
1233        "__StandardParticleSystemMaterial",
1234        MaterialResource::new_ok(
1235            uuid!("5bebe6e5-4aeb-496f-88f6-abe2b1ac798b"),
1236            ResourceKind::External,
1237            Material::from_shader(ShaderResource::standard_particle_system(),),
1238        )
1239    );
1240
1241    /// Standard sprite material. Keep in mind that this material is global, any modification
1242    /// of it will reflect on every other usage of it.
1243    pub static ref STANDARD_SPRITE: BuiltInResource<Material> = BuiltInResource::new_no_source(
1244        "__StandardSpriteMaterial",
1245        MaterialResource::new_ok(
1246            uuid!("3e331786-baae-412b-9d99-7370174bca43"),
1247            ResourceKind::External,
1248            Material::from_shader(ShaderResource::standard_sprite()),
1249        )
1250    );
1251
1252    /// Standard terrain material. Keep in mind that this material is global, any modification
1253    /// of it will reflect on every other usage of it.
1254    pub static ref STANDARD_TERRAIN: BuiltInResource<Material> = BuiltInResource::new_no_source(
1255        "__StandardTerrainMaterial",
1256        MaterialResource::new_ok(
1257            uuid!("0e407e22-41ad-4763-9adb-9d2e86351ece"),
1258            ResourceKind::External,
1259            Material::from_shader(ShaderResource::standard_terrain()),
1260        )
1261    );
1262
1263    /// Standard two-sided material. Keep in mind that this material is global, any modification
1264    /// of it will reflect on every other usage of it.
1265    pub static ref STANDARD_TWOSIDES: BuiltInResource<Material> = BuiltInResource::new_no_source(
1266        "__StandardTwoSidesMaterial",
1267        MaterialResource::new_ok(
1268            uuid!("24115321-7766-495c-bc3a-75db2f73d26d"),
1269            ResourceKind::External,
1270           Material::from_shader(ShaderResource::standard_twosides()),
1271        )
1272    );
1273
1274    /// Standard widget material. Keep in mind that this material is global, any modification
1275    /// of it will reflect on every other usage of it.
1276    pub static ref STANDARD_WIDGET: BuiltInResource<Material> = BuiltInResource::new_no_source(
1277        "__StandardWidgetMaterial",
1278        MaterialResource::new_ok(
1279            uuid!("e5d61a6f-5c94-4137-b303-1ae29cfff6e7"),
1280            ResourceKind::External,
1281           Material::from_shader(ShaderResource::standard_widget()),
1282        )
1283    );
1284}