Skip to main content

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