use color::{Rgb, Rgba, Hsv, ToRgb, color_space::LinearRgb};
use blender;
use na::{Mat4, Pnt3, Vec3, UnitQuat, one};
use crate::utils::{
self, trafo_from_parts, blackbody, lerp, Property, ObjectId, LibraryId,
};
use crate::texture;
use angle::Deg;
use hashbrown::HashMap;
use std::io;
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Clone,Debug,Copy,Eq,PartialEq)]
pub enum Type{
Lambert,
Standard,
Cloth,
Anisotropic,
Clearcoat,
}
bitflags! {
struct BlendFlag: u8 {
const MA_BL_HIDE_BACKFACE = (1 << 0);
const MA_BL_SS_REFRACTION = (1 << 1);
const MA_BL_CULL_BACKFACE = (1 << 2);
const MA_BL_TRANSLUCENCY = (1 << 3);
}
}
#[repr(u8)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug)]
pub enum BlendMode{
Opaque = 0,
Clip = 3,
Hashed = 4,
Blend = 5,
}
#[repr(u8)]
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Copy, Clone, Debug)]
pub enum ShadowMode{
None = 0,
Opaque = 1,
Clip = 2,
Hashed = 3,
}
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
#[derive(Clone)]
pub struct Material{
pub name: String,
pub albedo: Rgb<f32, LinearRgb>,
pub specular: Rgb<f32, LinearRgb>,
pub specular_tint: f32,
pub emissive: Rgb<f32, LinearRgb>,
pub subsurface: Option<Rgb<f32, LinearRgb>>,
pub subsurface_blur: f32,
pub subsurface_scale: f32,
pub clearcoat_factor: f32,
pub clearcoat_roughness: f32,
pub alpha: f32,
pub metallicness: f32,
pub anisotropy: f32,
pub anisotropic_rotation: f32,
pub roughness: f32,
pub normal_scale: f32,
pub texture: Option<crate::Image>,
pub normal_map: Option<crate::Image>,
pub occlusion_map: Option<crate::Image>,
pub metallic_roughness_map: Option<crate::Image>,
pub metallic_map: Option<crate::Image>,
pub roughness_map: Option<crate::Image>,
pub emissive_map: Option<crate::Image>,
pub anisotropy_map: Option<crate::Image>,
pub subsurface_map: Option<crate::Image>,
pub texture_matrix: Option<Mat4>,
pub normal_map_matrix: Option<Mat4>,
pub occlusion_map_matrix: Option<Mat4>,
pub metallic_roughness_map_matrix: Option<Mat4>,
pub metallic_map_matrix: Option<Mat4>,
pub roughness_map_matrix: Option<Mat4>,
pub emissive_map_matrix: Option<Mat4>,
pub anisotropy_map_matrix: Option<Mat4>,
pub subsurface_map_matrix: Option<Mat4>,
pub doublesided: bool,
pub ty: Type,
pub custom_properties: Vec<Property>,
pub blend_mode: Option<BlendMode>,
pub shadow_mode: Option<ShadowMode>,
}
enum ColorOrTexture{
Color(Rgba<f32, LinearRgb>),
Texture(crate::Image, Option<Mat4>),
}
fn parse_color_or_texture(
node: &blender::Object,
library_id: &LibraryId,
input_name: &str,
libraries: &HashMap<LibraryId, blender::File>,
images_data: &mut HashMap<ObjectId, texture::Data>) -> Result<ColorOrTexture, io::Error>
{
let input = node.get_list("inputs")
.unwrap()
.iter()
.find(|input| input.name().unwrap() == input_name)
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Couldn't find input node"))?;
if let Ok(from_node) = input.get_object("link").and_then(|link| link.get_object("fromnode")){
let name = from_node.get_str("idname").unwrap();
if name == "ShaderNodeTexImage" {
trace!("found texture");
let storage = from_node.get_object("storage").ok();
from_node.get_object("id")
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "Image node without image set"))
.map(|image| texture::parse_img_node(&image, library_id.clone(), storage, libraries, images_data))
.and_then(|tex| {
let vector = from_node.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == "Vector").unwrap();
let matrix = vector.get_object("link")
.and_then(|vector_link| vector_link.get_object("fromnode"))
.ok()
.and_then(|node| parse_tex_mappings(&node));
Ok(ColorOrTexture::Texture(tex?, matrix))
})
}else if name == "ShaderNodeHueSaturation"{
trace!("parsing hsv modifier");
let hue = *parse_f32(&from_node, "Hue").unwrap();
let saturation = *parse_f32(&from_node, "Saturation").unwrap();
let value = *parse_f32(&from_node, "Value").unwrap();
let factor = *parse_f32(&from_node, "Fac").unwrap();
match parse_color_or_texture(&from_node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(mut tex, mat)) => {
tex.modifiers.push((texture::Modifier::Hsv(hue, saturation, value), factor));
Ok(ColorOrTexture::Texture(tex, mat))
}
Ok(ColorOrTexture::Color(c)) => {
let hsv = Hsv::new(Deg(360.) * hue, saturation, value);
let rgb = hsv.to_rgb();
Ok(ColorOrTexture::Color(Rgba{c: c.c + rgb, a: c.a}))
}
err => err
}
}else if name == "ShaderNodeBlackbody" {
trace!("parsing blackbody");
let temperature = *parse_f32(&from_node, "Temperature").unwrap();
Ok(ColorOrTexture::Color(rgba_linear!(blackbody(temperature), 1.0)))
}else{
if is_link(&from_node, "Color") {
trace!("parsing unkown node with color link");
parse_color_or_texture(&from_node, library_id, "Color", libraries, images_data)
}else{
trace!("parsing unkown node with color value");
if node.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == "Color").is_some(){
Ok(ColorOrTexture::Color(parse_rgba(&node, "Color")))
}else{
Err(io::Error::new(io::ErrorKind::NotFound, "Couldn't find a color or texture in the node tree"))
}
}
}
}else{
trace!("parsing color value");
Ok(ColorOrTexture::Color(parse_rgba(&node, input_name)))
}
}
fn is_link(node: &blender::Object, input_name: &str) -> bool{
node.get_list("inputs").ok()
.and_then(|inputs| inputs.iter().find(|input| input.name().unwrap() == input_name))
.and_then(|input| input.get_object("link").ok())
.is_some()
}
fn parse_rgba(node: &blender::Object, input_name: &str) -> Rgba<f32, LinearRgb>{
let input = node.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == input_name).unwrap();
let color = input.get_object("default_value").unwrap();
let color_socket = color.cast_to("bNodeSocketValueRGBA").unwrap();
let color = color_socket.get_slice::<f32>("value").unwrap();
rgba_linear!(color[0], color[1], color[2], color[3])
}
fn parse_f32<'a>(node: &blender::Object<'a>, input_name: &str) -> Option<&'a f32>{
let obj = node.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == input_name).unwrap();
let obj = obj.get_object("default_value").unwrap();
let socket = obj.cast_to("bNodeSocketValueFloat").unwrap();
socket.get("value").ok()
}
fn parse_material_node(
node: &blender::Object,
library_id: &LibraryId,
material_name: &str,
libraries: &HashMap<LibraryId, blender::File>,
images_data: &mut HashMap<ObjectId, texture::Data>) -> Option<Material>
{
let mut material_nodes = false;
let mut albedo = rgb_linear!(1.0,1.0,1.0);
let mut specular = rgb_linear!(0.5, 0.5, 0.5);
let mut specular_tint = 0.0;
let mut emissive = rgb_linear!(0.0,0.0,0.0);
let mut emissive_map = None;
let mut emissive_map_matrix = None;
let mut subsurface = None;
let mut alpha = 1.0;
let mut metallicness = 0.0;
let mut roughness = 0.5;
let mut anisotropy = 0.0;
let mut anisotropic_rotation = 0.0;
let mut subsurface_blur = 0.0;
let mut subsurface_scale = 0.0;
let mut clearcoat_factor = 0.0;
let mut clearcoat_roughness = 0.5;
let mut normal_scale = 1.0;
let mut anisotropy_map = None;
let mut anisotropy_map_matrix = None;
let mut texture = None;
let mut occlusion_map = None;
let mut metallic_roughness_map = None;
let mut roughness_map = None;
let mut metallic_map = None;
let mut occlusion_map_matrix = None;
let mut metallic_roughness_map_matrix = None;
let mut metallic_map_matrix = None;
let mut roughness_map_matrix = None;
let mut texture_matrix = None;
let mut normal_map = None;
let mut normal_map_matrix = None;
let mut subsurface_map = None;
let mut subsurface_map_matrix = None;
let mut doublesided = false;
let mut ty = Type::Standard;
let name = node.get_str("idname").unwrap();
if name == "ShaderNodeBsdfGlossy"{
trace!("parsing glossy material {}", material_name);
roughness = *parse_f32(&node, "Roughness").unwrap();
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
specular = color.c;
alpha = color.a;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
metallicness = 1.0;
material_nodes = true;
}else if name == "ShaderNodeBsdfDiffuse"{
trace!("parsing diffuse material {}", material_name);
roughness = *parse_f32(&node, "Roughness").unwrap();
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
alpha = color.a;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
metallicness = 0.0;
material_nodes = true;
ty = Type::Lambert;
}else if name == "ShaderNodeBsdfVelvet"{
trace!("parsing velvet material {}", material_name);
roughness = *parse_f32(&node, "Sigma").unwrap();
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = rgb_linear!(0., 0., 0.);
specular = color.c;
alpha = color.a;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
metallicness = 0.0;
material_nodes = true;
ty = Type::Cloth;
}else if name == "ShaderNodeEmission" {
trace!("parsing emission material {}", material_name);
let strength = *parse_f32(&node, "Strength").unwrap();
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
trace!("with texture");
emissive_map = Some(tex);
emissive_map_matrix = mat;
emissive = rgb_linear!(1.0, 1.0, 1.0);
}
Ok(ColorOrTexture::Color(color)) => {
trace!("with color");
emissive = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
emissive = emissive * strength;
albedo = rgb_linear!(0., 0., 0.);
metallicness = 0.0;
material_nodes = true;
}else if name == "ShaderNodeSubsurfaceScattering"{
trace!("parsing subsurface material {}", material_name);
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
subsurface_map = Some(tex);
subsurface_map_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
trace!("with subsurface color {:?}", color.c);
subsurface = Some(color.c);
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
subsurface_blur = *parse_f32(&node, "Texture Blur").unwrap();
subsurface_scale = *parse_f32(&node, "Scale").unwrap();
trace!("scale {}", subsurface_scale);
material_nodes = true;
}else if name == "ShaderNodeGroup" &&
node.get_object("id").unwrap()
.name().unwrap()
.starts_with("NTglTF Metallic Roughness")
{
trace!("parsing glTF material {}", material_name);
roughness = *parse_f32(&node, "RoughnessFactor").unwrap();
metallicness = *parse_f32(&node, "MetallicFactor").unwrap();
normal_scale = *parse_f32(&node, "NormalScale").unwrap();
doublesided = if *parse_f32(&node, "DoubleSided").unwrap() > 0. { true } else { false };
match parse_color_or_texture(&node, library_id, "BaseColor", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
let color_factor = parse_rgba(&node, "BaseColorFactor").c;
albedo.r *= color_factor[0];
albedo.g *= color_factor[1];
albedo.b *= color_factor[2];
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "MetallicRoughness", libraries, images_data){
metallic_roughness_map = Some(tex);
metallic_roughness_map_matrix = matrix;
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Occlusion", libraries, images_data){
occlusion_map = Some(tex);
occlusion_map_matrix = matrix;
}
emissive = parse_rgba(&node, "EmissiveFactor").c;
match parse_color_or_texture(&node, library_id, "Emissive", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
emissive_map = Some(tex);
emissive_map_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
emissive[0] *= color.c.r;
emissive[1] *= color.c.g;
emissive[2] *= color.c.b;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
material_nodes = true;
}else if name == "ShaderNodeBsdfPrincipled"{
trace!("parsing Principled material {}", material_name);
if is_link(&node, "Roughness") {
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Roughness", libraries, images_data){
roughness_map = Some(tex);
roughness_map_matrix = matrix;
roughness = 1.;
}
}else{
roughness = *parse_f32(&node, "Roughness").unwrap();
}
if is_link(&node, "Metallic") {
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Metallic", libraries, images_data){
metallic_map = Some(tex);
metallic_map_matrix = matrix;
metallicness = 1.;
}
}else{
metallicness = *parse_f32(&node, "Metallic").unwrap();
}
doublesided = false;
match parse_color_or_texture(&node, library_id, "Base Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if is_link(&node, "Specular") {
error!("Can't parse Principled BSDF with specular texture")
}else{
let color = *parse_f32(&node, "Specular").unwrap();
specular = rgb_linear!(color, color, color);
}
specular_tint = *parse_f32(&node, "Specular Tint").unwrap();
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
anisotropy = *parse_f32(&node, "Anisotropic").unwrap();
if is_link(&node, "Anisotropic Rotation"){
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Anisotropic Rotation", libraries, images_data){
anisotropy_map = Some(tex);
anisotropy_map_matrix = matrix;
}else{
error!("Found color as anisotropy rotation")
}
}else{
anisotropic_rotation = *parse_f32(&node, "Anisotropic Rotation").unwrap();
}
if anisotropy > 0. {
ty = Type::Anisotropic;
}
let subsurface_factor = *parse_f32(&node, "Subsurface").unwrap();
if subsurface_factor > 0. {
match parse_color_or_texture(&node, library_id, "Subsurface Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
subsurface_map = Some(tex);
subsurface_map_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
trace!("with subsurface color {:?}", color.c);
subsurface = Some(color.c);
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
subsurface_blur = 1. - subsurface_factor;
}
clearcoat_factor = *parse_f32(&node, "Clearcoat").unwrap();
if clearcoat_factor > 0. {
ty = Type::Clearcoat;
clearcoat_roughness = *parse_f32(&node, "Clearcoat Roughness").unwrap();
}
match parse_color_or_texture(&node, library_id, "Emission", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
emissive_map = Some(tex);
emissive_map_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
emissive = color.c;
}
Err(err) => {
warn!("Error parsing Emission node tree: {}", err)
}
}
material_nodes = true;
}else if name == "ShaderNodeBsdfAnisotropic"{
trace!("parsing anisotropic material {}", material_name);
if is_link(&node, "Roughness") {
error!("Can't parse roughness map in Anisotropy BDSF material yet")
}else{
roughness = *parse_f32(&node, "Roughness").unwrap();
}
if is_link(&node, "Rotation"){
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Rotation", libraries, images_data){
anisotropy_map = Some(tex);
anisotropy_map_matrix = matrix;
}else{
error!("Found color as anisotropy rotation")
}
}else{
anisotropic_rotation = *parse_f32(&node, "Rotation").unwrap();
}
anisotropy = *parse_f32(&node, "Anisotropy").unwrap();
metallicness = 1.0;
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
ty = Type::Anisotropic;
material_nodes = true;
}else if name == "ShaderNodeMixShader"{
trace!("parsing mixer");
let mut shaders = node.get_list("inputs").unwrap()
.iter()
.filter(|input| input.name().unwrap() == "Shader");
let shader1 = shaders.next().unwrap()
.get_object("link")
.and_then(|link| link.get_object("fromnode")).ok()
.and_then(|node| parse_material_node(&node, library_id, material_name, libraries, images_data));
let shader2 = shaders.next().unwrap()
.get_object("link")
.and_then(|link| link.get_object("fromnode")).ok()
.and_then(|node| parse_material_node(&node, library_id, material_name, libraries, images_data));
let factor = *parse_f32(node, "Fac").unwrap();
match (shader1, shader2){
(Some(shader1), Some(shader2)) => {
albedo = lerp(shader1.albedo, shader2.albedo, factor);
specular = lerp(shader1.specular, shader2.specular, factor);
emissive = lerp(shader1.emissive, shader2.emissive, factor);
alpha = lerp(shader1.alpha, shader2.alpha, factor);
metallicness = lerp(shader1.metallicness, shader2.metallicness, factor);
roughness = lerp(shader1.roughness, shader2.roughness, factor);
doublesided = shader1.doublesided | shader2.doublesided;
texture = shader1.texture.or(shader2.texture);
normal_map = shader1.normal_map.or(shader2.normal_map);
occlusion_map = shader1.occlusion_map.or(shader2.occlusion_map);
metallic_roughness_map = shader1.metallic_roughness_map.or(shader2.metallic_roughness_map);
metallic_map = shader1.metallic_map.or(shader2.metallic_map);
roughness_map = shader1.roughness_map.or(shader2.roughness_map);
emissive_map = shader1.emissive_map.or(shader2.emissive_map);
texture_matrix = shader1.texture_matrix.or(shader2.texture_matrix);
normal_map_matrix = shader1.normal_map_matrix.or(shader2.normal_map_matrix);
occlusion_map_matrix = shader1.occlusion_map_matrix.or(shader2.occlusion_map_matrix);
metallic_roughness_map_matrix = shader1.metallic_roughness_map_matrix.or(shader2.metallic_roughness_map_matrix);
emissive_map_matrix = shader1.emissive_map_matrix.or(shader2.emissive_map_matrix);
subsurface = match (shader1.subsurface, shader2.subsurface) {
(Some(sub1), Some(sub2)) => Some(lerp(sub1, sub2, factor)),
(Some(sub1), None) => Some(sub1),
(None, Some(sub2)) => Some(sub2),
(None, None) => None,
};
subsurface_blur = match (shader1.subsurface, shader2.subsurface) {
(Some(_), Some(_)) => lerp(shader1.subsurface_blur, shader2.subsurface_blur, factor),
(Some(_), None) => shader1.subsurface_blur,
(None, Some(_)) => shader2.subsurface_blur,
(None, None) => 0.0,
};
subsurface_scale = match (shader1.subsurface, shader2.subsurface) {
(Some(_), Some(_)) => lerp(shader1.subsurface_scale, shader2.subsurface_scale, factor),
(Some(_), None) => shader1.subsurface_scale,
(None, Some(_)) => shader2.subsurface_scale,
(None, None) => 0.0,
};
trace!("mixer with subsurface color {:?}", subsurface);
if shader1.ty != Type::Standard {
ty = shader1.ty;
}else if shader2.ty != Type::Standard {
ty = shader2.ty;
}
material_nodes = true;
}
(Some(shader1), None) => {
trace!("No second shader in mixer");
return Some(shader1);
}
(None, Some(shader2)) => {
trace!("No first shader in mixer");
return Some(shader2);
}
_ => error!("mix shader with no recognizable inputs"),
}
}else if name == "ShaderNodeAddShader"{
trace!("parsing add node");
let mut shaders = node.get_list("inputs").unwrap()
.iter()
.filter(|input| input.name().unwrap() == "Shader");
let shader1 = shaders.next().unwrap()
.get_object("link")
.and_then(|link| link.get_object("fromnode")).ok()
.and_then(|node| parse_material_node(&node, library_id, material_name, libraries, images_data));
let shader2 = shaders.next().unwrap()
.get_object("link")
.and_then(|link| link.get_object("fromnode")).ok()
.and_then(|node| parse_material_node(&node, library_id, material_name, libraries, images_data));
match (shader1, shader2){
(Some(shader1), Some(shader2)) => {
albedo = shader1.albedo + shader2.albedo;
specular = shader1.specular + shader2.specular;
emissive = shader1.emissive + shader2.emissive;
alpha = shader1.alpha + shader2.alpha;
metallicness = shader1.metallicness + shader2.metallicness;
roughness = shader1.roughness + shader2.roughness;
doublesided = shader1.doublesided | shader2.doublesided;
texture = shader1.texture.or(shader2.texture);
normal_map = shader1.normal_map.or(shader2.normal_map);
occlusion_map = shader1.occlusion_map.or(shader2.occlusion_map);
metallic_roughness_map = shader1.metallic_roughness_map.or(shader2.metallic_roughness_map);
metallic_map = shader1.metallic_map.or(shader2.metallic_map);
roughness_map = shader1.roughness_map.or(shader2.roughness_map);
emissive_map = shader1.emissive_map.or(shader2.emissive_map);
texture_matrix = shader1.texture_matrix.or(shader2.texture_matrix);
normal_map_matrix = shader1.normal_map_matrix.or(shader2.normal_map_matrix);
occlusion_map_matrix = shader1.occlusion_map_matrix.or(shader2.occlusion_map_matrix);
metallic_roughness_map_matrix = shader1.metallic_roughness_map_matrix.or(shader2.metallic_roughness_map_matrix);
emissive_map_matrix = shader1.emissive_map_matrix.or(shader2.emissive_map_matrix);
subsurface = match (shader1.subsurface, shader2.subsurface) {
(Some(sub1), Some(sub2)) => Some(sub1 + sub2),
(Some(sub1), None) => Some(sub1),
(None, Some(sub2)) => Some(sub2),
(None, None) => None,
};
subsurface_blur = match (shader1.subsurface, shader2.subsurface) {
(Some(_), Some(_)) => shader1.subsurface_blur + shader2.subsurface_blur,
(Some(_), None) => shader1.subsurface_blur,
(None, Some(_)) => shader2.subsurface_blur,
(None, None) => 0.0,
};
subsurface_scale = match (shader1.subsurface, shader2.subsurface) {
(Some(_), Some(_)) => shader1.subsurface_scale + shader2.subsurface_scale,
(Some(_), None) => shader1.subsurface_scale,
(None, Some(_)) => shader2.subsurface_scale,
(None, None) => 0.0,
};
trace!("mixer with subsurface color {:?}", subsurface);
if shader1.ty != Type::Standard {
ty = shader1.ty;
}else if shader2.ty != Type::Standard {
ty = shader2.ty;
}
material_nodes = true;
}
(Some(shader1), None) => {
trace!("No second shader in mixer");
return Some(shader1);
}
(None, Some(shader2)) => {
trace!("No first shader in mixer");
return Some(shader2);
}
_ => error!("mix shader with no recognizable inputs"),
}
}else if name == "ShaderNodeBsdfTransparent"{
alpha = 0.;
material_nodes = true;
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
}else if name == "ShaderNodeBsdfTranslucent"{
alpha = 0.;
material_nodes = true;
match parse_color_or_texture(&node, library_id, "Color", libraries, images_data){
Ok(ColorOrTexture::Texture(tex, mat)) => {
texture = Some(tex);
texture_matrix = mat;
}
Ok(ColorOrTexture::Color(color)) => {
albedo = color.c;
}
Err(err) => {
warn!("Error parsing color node tree: {}", err)
}
}
if let Ok(ColorOrTexture::Texture(tex, matrix)) = parse_color_or_texture(&node, library_id, "Normal", libraries, images_data){
normal_map = Some(tex);
normal_map_matrix = matrix;
}
}else{
error!("Found unkown shader {}", name);
}
if material_nodes {
Some(Material {
name: material_name.to_owned(),
albedo,
specular,
specular_tint,
emissive,
subsurface,
alpha,
metallicness,
roughness,
anisotropy,
anisotropic_rotation,
subsurface_blur,
subsurface_scale,
clearcoat_factor,
clearcoat_roughness,
normal_scale,
texture,
normal_map,
occlusion_map,
metallic_roughness_map,
metallic_map,
roughness_map,
emissive_map,
anisotropy_map,
subsurface_map,
texture_matrix,
normal_map_matrix,
occlusion_map_matrix,
metallic_roughness_map_matrix,
metallic_map_matrix,
roughness_map_matrix,
emissive_map_matrix,
anisotropy_map_matrix,
subsurface_map_matrix,
doublesided,
ty,
custom_properties: vec![],
blend_mode: None,
shadow_mode: None,
})
}else{
None
}
}
pub fn parse_material(
material_obj: blender::Object,
library_id: LibraryId,
libraries: &HashMap<LibraryId, blender::File>,
images_data: &mut HashMap<ObjectId, texture::Data>) -> Result<Material, blender::Error>
{
let material_name = material_obj.name().unwrap().to_owned();
trace!("Found {}", material_name);
let material = if let Ok(nodetree) = material_obj.get_object("nodetree"){
if let Some(node) = nodetree.get_list("nodes").unwrap().iter()
.filter(|node| node.name().unwrap().starts_with("Material Output"))
.filter_map(|node| node.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == "Surface"))
.filter_map(|surface| surface.get_object("link").and_then(|link| link.get_object("fromnode")).ok())
.next()
{
parse_material_node(&node, &library_id, &material_name, libraries, images_data)
}else{
error!("Couldn't find Material Ouput Node with a Surface input link");
None
}
}else{
None
};
let blend_flag = material_obj.get::<BlendFlag>("blend_flag").ok();
let doublesided = blend_flag
.map(|blend_flag| !blend_flag.contains(BlendFlag::MA_BL_CULL_BACKFACE));
let blend_mode = blend_flag
.and_then(|blend_flag| if blend_flag.contains(BlendFlag::MA_BL_TRANSLUCENCY){
material_obj.get::<BlendMode>("blend_mode").ok().copied()
}else{
None
});
let shadow_mode = material_obj.get::<ShadowMode>("blend_shadow").ok().copied();
if let Some(mut material) = material{
if let Some(doublesided) = doublesided {
material.doublesided |= doublesided;
}
material.blend_mode = blend_mode;
material.shadow_mode = shadow_mode;
Ok(material)
}else{
let emissive = rgb_linear!(0.0,0.0,0.0);
let metallicness = 0.0;
let occlusion_map = None;
let metallic_roughness_map = None;
let metallic_map = None;
let roughness_map = None;
let occlusion_map_matrix = None;
let metallic_roughness_map_matrix = None;
let metallic_map_matrix = None;
let roughness_map_matrix = None;
let emissive_map = None;
let emissive_map_matrix = None;
let anisotropy_map = None;
let anisotropy_map_matrix = None;
let texture_matrix = None;
let normal_map = None;
let normal_map_matrix = None;
let subsurface_map = None;
let subsurface_map_matrix = None;
let doublesided = doublesided.unwrap_or(false);
let r = *material_obj.get::<f32>("r").unwrap();
let g = *material_obj.get::<f32>("g").unwrap();
let b = *material_obj.get::<f32>("b").unwrap();
let alpha = *material_obj.get::<f32>("alpha").unwrap();
let albedo = rgb_linear!(r,g,b);
let subsurface = None;
let ty = Type::Standard;
let specr = *material_obj.get::<f32>("specr").unwrap();
let specg = *material_obj.get::<f32>("specg").unwrap();
let specb = *material_obj.get::<f32>("specb").unwrap();
let specular = rgb_linear!(specr, specg, specb);
let shininess: f32 = *material_obj.get("spec").unwrap();
let roughness = (2.0 / (shininess + 2.0)).sqrt().sqrt();
let subsurface_blur = 0.0;
let subsurface_scale = 0.0;
let clearcoat_factor = 0.0;
let clearcoat_roughness = 0.0;
let specular_tint = 0.0;
let normal_scale = 1.0;
let anisotropy = 0.;
let anisotropic_rotation = 0.;
let mut textures = parse_material_textures(
&material_obj,
library_id,
libraries,
images_data
)?;
let texture = if !textures.is_empty(){
textures.drain(0..1).next()
}else{
None
};
Ok(Material{
name: material_name,
albedo,
specular,
specular_tint,
emissive,
subsurface,
alpha,
metallicness,
roughness,
anisotropy,
anisotropic_rotation,
subsurface_blur,
subsurface_scale,
clearcoat_factor,
clearcoat_roughness,
normal_scale,
texture,
normal_map,
occlusion_map,
metallic_roughness_map,
metallic_map,
roughness_map,
emissive_map,
anisotropy_map,
subsurface_map,
texture_matrix,
normal_map_matrix,
occlusion_map_matrix,
metallic_roughness_map_matrix,
metallic_map_matrix,
roughness_map_matrix,
emissive_map_matrix,
anisotropy_map_matrix,
subsurface_map_matrix,
doublesided,
ty,
custom_properties: utils::custom_properties(&material_obj),
blend_mode,
shadow_mode,
})
}
}
pub fn parse_all_materials(
libraries: &HashMap<LibraryId, blender::File>,
images_data: &mut HashMap<ObjectId, texture::Data>) -> Result<HashMap<ObjectId, Material>, blender::Error>
{
let mut materials = HashMap::new();
for (lib_name, library) in libraries {
for material in library.objects("Material"){
let material = parse_material(
material,
lib_name.clone(),
libraries,
images_data
)?;
let material_id = ObjectId::new(lib_name.clone(), &material.name);
materials.insert(material_id, material);
}
}
Ok(materials)
}
fn parse_material_textures(
material: &blender::Object,
library_id: LibraryId,
libraries: &HashMap<LibraryId, blender::File>,
images_data: &mut HashMap<ObjectId, texture::Data>) -> Result<Vec<crate::Image>, blender::Error>
{
let textures = material.get_object_slice("mtex").unwrap_or(Vec::new());
textures.iter().filter_map(|mtex| {
mtex.get_object("tex")
.and_then(|tex| tex.get_object("ima"))
.map(|img| texture::parse_img_node(&img, library_id.clone(), None, libraries, images_data)
.map_err(|err| blender::Error(format!("Couldn't load texture {}", err)))
)
.ok()
}).collect()
}
fn parse_tex_mappings(mapping: &blender::Object) -> Option<Mat4>{
let name = mapping.name().unwrap();
if name.starts_with("Mapping"){
let tex_mapping = mapping.get_object("storage").ok()?;
if tex_mapping.structure().name() == "TexMapping"{
let loc: Pnt3 = *tex_mapping.get("loc").unwrap();
let rot: Vec3 = *tex_mapping.get("rot").unwrap();
let scale: Vec3 = *tex_mapping.get("size").unwrap();
let rot = UnitQuat::from_scaled_axis(rot);
let texture_mat = trafo_from_parts(&loc, &rot, &scale);
let vector = mapping.get_list("inputs").unwrap().iter().find(|input| input.name().unwrap() == "Vector").unwrap();
if let Ok(vector_link) = vector.get_object("link"){
if let Ok(node) = vector_link.get_object("fromnode"){
Some(parse_tex_mappings(&node).unwrap_or(one()) * texture_mat)
}else{
Some(texture_mat)
}
}else{
Some(texture_mat)
}
}else{
None
}
}else{
None
}
}
#[allow(dead_code)]
#[derive(Debug,Clone,Copy)]
enum NodeType{
Output = 1,
Material = 100,
RGB = 101,
Value = 102,
MixRGB = 103,
ValToRGB = 104,
RGBToBW = 105,
Texture = 106,
Normal = 107,
Geometry = 108,
Mapping = 109,
CurveVec = 110,
CurveRGB = 111,
Camera = 114,
Math = 115,
VectMath = 116,
Squeeze = 117,
MaterialExt = 118,
Invert = 119,
SepRGB = 120,
CombRGB = 121,
HueSat = 122,
Dynamic = 123,
OuputMaterial = 124,
OutputWorld = 125,
OutputLamp = 126,
Fresnel = 127,
MixShader = 128,
Attribute = 129,
Background = 130,
BSDFAnisotropic = 131,
BSDFDiffuse = 132,
BSDFGlossy = 133,
BSDFGlass = 134,
BSDFTranslucent = 137,
BSDFTransparent = 138,
BSDFVelvet = 139,
Emission = 140,
NewGeometry = 141,
LightPath = 142,
TexImage = 143,
TexSky = 145,
TexGradient = 146,
TexVoronoi = 147,
TexMagic = 148,
TexWave = 149,
TexNoise = 150,
TexMusgrave = 152,
TexCoord = 155,
AddShader = 156,
TexEnvironment = 157,
OutputTexture = 158,
HoldOut = 159,
LayerWeight = 160,
VolumeAbsorption = 161,
VolumeScatter = 162,
Gamma = 163,
TexChecker = 164,
BrightContrast = 165,
LightFalloff = 166,
ObjectInfo = 167,
ParticleInfo = 168,
TexBrick = 169,
Bump = 170,
Script = 171,
AmbientOclussion = 172,
BSDFRefraction = 173,
Tangent = 174,
NormalMap = 175,
HairInfo = 176,
SubsurfaceScattering = 177,
Wireframe = 178,
BSDFToon = 179,
Wavelength = 180,
Blackbody = 181,
VectTransform = 182,
SepHSV = 183,
CombHSV = 184,
BSDFHair = 185,
Lamp = 186,
UVMamp = 187,
SepXYZ = 188,
CombXYZ = 189,
OoutputLineStyle = 190,
UVALongStrike = 191,
TexPointDensity = 192,
}