1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
/// Material data for a mesh
///
/// # Blender
///
/// When exporting from Blender we read this data from the first Principled BSDF node in the
/// node editor for the material
///
/// https://docs.blender.org/manual/en/latest/render/cycles/nodes/types/shaders/principled.html
#[derive(Debug, Serialize, Deserialize, PartialEq, Default, Clone)]
pub struct PrincipledBSDF {
    /// [r, g, b]
    pub(crate) base_color: MaterialInput<[f32; 3], String>,
    /// roughness
    pub(crate) roughness: MaterialInput<f32, (String, Channel)>,
    /// metallic
    pub(crate) metallic: MaterialInput<f32, (String, Channel)>,
    /// The filename for the material's normal map
    pub(crate) normal_map: Option<String>,
}

/// An input to a material property.
///
/// This can either be some uniform value that will get used across all vertices / fragments
/// in your shader, or a texture.
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum MaterialInput<U, I> {
    /// Some value that is uniform across all vertices / fragments in your mesh.
    Uniform(U),
    /// The name of the texture image (excluding the full path) from an image texture node
    /// in Blender's material editor.
    ///
    /// So a texture stored at /Users/me/hello-world.png
    /// becomes MaterialInput::Texture("hello-world.png".to_string())
    ///
    /// This means that it is important to have different texture names across all unique textures
    /// in your application.
    ///
    /// ## Note
    ///
    /// This is different from the other built in texture nodes, such as brick texture and
    /// sky texture. We do not currently support these. If these would be useful for you,
    /// open an issue!
    ///
    /// ## Examples
    ///
    /// ```
    /// // Metalness can be read from the green channel of metal.jpg
    /// use blender_mesh::{MaterialInput, Channel};
    /// let metalness: MaterialInput<f32, (String, Channel)> =
    ///     MaterialInput::ImageTexture((String::from("metal.jpg"), Channel::Green));
    /// ```
    ImageTexture(I),
}

/// An individual channel within an image.
/// Red, Green, or Blue.
#[derive(Debug, Serialize, Deserialize, PartialEq, Copy, Clone)]
pub enum Channel {
    #[serde(rename = "R")]
    Red,
    #[serde(rename = "G")]
    Green,
    #[serde(rename = "B")]
    Blue,
}

impl<U, I> Default for MaterialInput<U, I>
where
    U: Default,
{
    fn default() -> Self {
        MaterialInput::Uniform(U::default())
    }
}

impl PrincipledBSDF {
    /// Create a new physically-based material.
    pub fn new(
        base_color: MaterialInput<[f32; 3], String>,
        roughness: MaterialInput<f32, (String, Channel)>,
        metallic: MaterialInput<f32, (String, Channel)>,
        normal_map: Option<String>,
    ) -> Self {
        PrincipledBSDF {
            base_color,
            roughness,
            metallic,
            normal_map,
        }
    }

    /// The base_color of the material.
    ///
    /// https://docs.blender.org/api/blender2.8/bpy.types.Material.html#bpy.types.Material.diffuse_color
    #[inline]
    pub fn base_color(&self) -> &MaterialInput<[f32; 3], String> {
        &self.base_color
    }

    /// The roughness of the material.
    ///
    /// https://docs.blender.org/api/blender2.8/bpy.types.Material.html#bpy.types.Material.roughness
    #[inline]
    pub fn roughness(&self) -> &MaterialInput<f32, (String, Channel)> {
        &self.roughness
    }

    /// How metallic the material is. Most materials should be 0.0 or 1.0.
    ///
    /// https://docs.blender.org/api/blender2.8/bpy.types.Material.html#bpy.types.Material.metallic
    #[inline]
    pub fn metallic(&self) -> &MaterialInput<f32, (String, Channel)> {
        &self.metallic
    }

    /// The normal map
    #[inline]
    pub fn normal_map(&self) -> Option<&String> {
        self.normal_map.as_ref()
    }
}