Skip to main content

russimp_ng/
material.rs

1use crate::{sys::*, utils, utils::get_base_type_vec_from_raw, RussimpError, Russult};
2use derivative::Derivative;
3use num_enum::TryFromPrimitive;
4use num_traits::FromPrimitive;
5use std::{
6    cell::RefCell, collections::HashMap, ffi::CStr, mem::MaybeUninit, path::Path,
7    ptr::slice_from_raw_parts, rc::Rc,
8};
9use strum::IntoEnumIterator;
10use strum_macros::EnumIter;
11
12const EMBEDDED_TEXNAME_PREFIX: &str = "*";
13
14pub(crate) type Filename = String;
15
16#[derive(
17    Derivative, FromPrimitive, PartialEq, TryFromPrimitive, Clone, Eq, Hash, EnumIter, Copy,
18)]
19#[derivative(Debug)]
20#[repr(u32)]
21pub enum TextureType {
22    #[num_enum(default)]
23    None = aiTextureType_aiTextureType_NONE as _,
24    Diffuse = aiTextureType_aiTextureType_DIFFUSE as _,
25    Specular = aiTextureType_aiTextureType_SPECULAR as _,
26    Ambient = aiTextureType_aiTextureType_AMBIENT as _,
27    Emissive = aiTextureType_aiTextureType_EMISSIVE as _,
28    Height = aiTextureType_aiTextureType_HEIGHT as _,
29    Normals = aiTextureType_aiTextureType_NORMALS as _,
30    Shininess = aiTextureType_aiTextureType_SHININESS as _,
31    Opacity = aiTextureType_aiTextureType_OPACITY as _,
32    Displacement = aiTextureType_aiTextureType_DISPLACEMENT as _,
33    LightMap = aiTextureType_aiTextureType_LIGHTMAP as _,
34    Reflection = aiTextureType_aiTextureType_REFLECTION as _,
35    BaseColor = aiTextureType_aiTextureType_BASE_COLOR as _,
36    NormalCamera = aiTextureType_aiTextureType_NORMAL_CAMERA as _,
37    EmissionColor = aiTextureType_aiTextureType_EMISSION_COLOR as _,
38    Metalness = aiTextureType_aiTextureType_METALNESS as _,
39    Roughness = aiTextureType_aiTextureType_DIFFUSE_ROUGHNESS as _,
40    AmbientOcclusion = aiTextureType_aiTextureType_AMBIENT_OCCLUSION as _,
41    Unknown = aiTextureType_aiTextureType_UNKNOWN as _,
42    Sheen = aiTextureType_aiTextureType_SHEEN as _,
43    ClearCoat = aiTextureType_aiTextureType_CLEARCOAT as _,
44    Transmission = aiTextureType_aiTextureType_TRANSMISSION as _,
45    Force32bit = aiTextureType__aiTextureType_Force32Bit as _,
46    MayaBase = aiTextureType_aiTextureType_MAYA_BASE as _,
47    MayaSpecular = aiTextureType_aiTextureType_MAYA_SPECULAR as _,
48    MayaSpecularColor = aiTextureType_aiTextureType_MAYA_SPECULAR_COLOR as _,
49    MayaSpecularRoughness = aiTextureType_aiTextureType_MAYA_SPECULAR_ROUGHNESS as _,
50    Anisotropy = aiTextureType_aiTextureType_ANISOTROPY as _,
51    GltfMetallicRoughness = aiTextureType_aiTextureType_GLTF_METALLIC_ROUGHNESS as _,
52}
53
54#[derive(Derivative)]
55#[derivative(Debug)]
56pub struct Texture {
57    pub height: u32,
58    pub width: u32,
59    pub filename: String,
60    pub ach_format_hint: String,
61    #[derivative(Debug = "ignore")]
62    pub data: DataContent,
63}
64
65#[repr(C, packed)]
66#[derive(Derivative, Copy, Clone)]
67#[derivative(Debug)]
68pub struct Texel {
69    pub b: u8,
70    pub g: u8,
71    pub r: u8,
72    pub a: u8,
73}
74
75impl From<&aiTexel> for Texel {
76    fn from(texel: &aiTexel) -> Self {
77        Texel {
78            b: texel.b,
79            g: texel.g,
80            r: texel.r,
81            a: texel.a,
82        }
83    }
84}
85
86#[derive(Clone)]
87pub enum DataContent {
88    Texel(Vec<Texel>),
89    Bytes(Vec<u8>),
90}
91
92pub(crate) fn generate_materials(scene: &aiScene) -> Russult<Vec<Material>> {
93    let textures = get_base_type_vec_from_raw(scene.mTextures, scene.mNumTextures);
94    let materials = get_base_type_vec_from_raw(scene.mMaterials, scene.mNumMaterials);
95    let properties = create_material_properties(&materials);
96    let mut result = Vec::new();
97
98    let mut converted_textures: HashMap<usize, Rc<RefCell<Texture>>> = HashMap::new();
99
100    for (mat_index, &mat) in materials.iter().enumerate() {
101        let mut material_textures: HashMap<TextureType, Rc<RefCell<Texture>>> = HashMap::new();
102
103        for tex_type in TextureType::iter() {
104            let material_filenames = get_textures_of_type_from_material(mat, tex_type)?;
105
106            for material_filename in material_filenames {
107                let embedded_textures = get_embedded_texture(&material_filename, &textures);
108
109                if let Some(embedded_texture) = embedded_textures {
110                    if let Some(tex) = converted_textures.get(&embedded_texture) {
111                        material_textures.insert(tex_type, tex.clone());
112                    } else {
113                        let new_texture = create_texture_from(textures[embedded_texture], true);
114                        converted_textures
115                            .insert(embedded_texture, Rc::new(RefCell::new(new_texture)));
116                        material_textures.insert(
117                            tex_type,
118                            converted_textures.get(&embedded_texture).unwrap().clone(),
119                        );
120                    }
121                }
122            }
123        }
124
125        result.push(Material::new(
126            properties[mat_index].clone(),
127            material_textures,
128        ));
129    }
130
131    Ok(result)
132}
133
134fn get_textures_of_type_from_material(
135    material: &aiMaterial,
136    texture_type: TextureType,
137) -> Russult<Vec<Filename>> {
138    let texture_type_raw: aiTextureType = texture_type as _;
139
140    let mut vec = Vec::new();
141
142    for index in 0..unsafe { aiGetMaterialTextureCount(material, texture_type_raw) } {
143        vec.push(get_texture_filename(material, texture_type_raw, index)?);
144    }
145
146    Ok(vec)
147}
148
149fn get_texture_filename(
150    material: &aiMaterial,
151    texture_type: aiTextureType,
152    index: u32,
153) -> Russult<String> {
154    let mut path = MaybeUninit::uninit();
155    let mut texture_mapping = MaybeUninit::uninit();
156    let mut uv_index = MaybeUninit::uninit();
157    let mut blend = MaybeUninit::uninit();
158    let mut op = MaybeUninit::uninit();
159    let mut map_mode: [u32; 2] = [0, 0];
160
161    let mut flags = MaybeUninit::uninit();
162
163    if unsafe {
164        aiGetMaterialTexture(
165            material,
166            texture_type,
167            index,
168            path.as_mut_ptr(),
169            texture_mapping.as_mut_ptr(),
170            uv_index.as_mut_ptr(),
171            blend.as_mut_ptr(),
172            op.as_mut_ptr(),
173            map_mode.as_mut_ptr() as *mut _,
174            flags.as_mut_ptr(),
175        )
176    } == aiReturn_aiReturn_SUCCESS
177    {
178        let filename: String = unsafe { path.assume_init() }.into();
179
180        return Ok(filename);
181    }
182
183    Err(RussimpError::TextureNotFound)
184}
185
186fn create_texture_from(texture: &aiTexture, is_embedded: bool) -> Texture {
187    let ach_format_hint = unsafe { CStr::from_ptr(texture.achFormatHint.as_ptr()) }
188        .to_str()
189        .unwrap()
190        .to_string();
191
192    let data = if is_embedded {
193        let compressed_bytes =
194            slice_from_raw_parts(texture.pcData as *const u8, texture.mWidth as usize);
195        DataContent::Bytes(unsafe { compressed_bytes.as_ref() }.unwrap().to_vec())
196    } else {
197        DataContent::Texel(utils::get_vec(
198            texture.pcData,
199            texture.mWidth * texture.mHeight,
200        ))
201    };
202
203    Texture {
204        height: texture.mHeight,
205        width: texture.mWidth,
206        filename: texture.mFilename.into(),
207        ach_format_hint,
208        data,
209    }
210}
211
212fn get_embedded_texture(file_name: &str, textures: &Vec<&aiTexture>) -> Option<usize> {
213    if file_name.starts_with(EMBEDDED_TEXNAME_PREFIX) {
214        let temp = file_name.split_at(1).1.to_string();
215        let index = temp.parse::<usize>().unwrap();
216        if textures.len() <= index {
217            return None;
218        }
219
220        return Some(index);
221    }
222
223    let path = Path::new(file_name);
224    path.file_name()?;
225
226    for (tex_index, &texture) in textures.iter().enumerate() {
227        let texture_filename: String = texture.mFilename.into();
228        let texture_filepath = Path::new(texture_filename.as_str());
229
230        if let Some(texture_name) = texture_filepath.file_name() {
231            if let Some(name) = path.file_name() {
232                if texture_name.eq(name) {
233                    return Some(tex_index);
234                }
235            }
236        }
237    }
238
239    None
240}
241
242fn create_material_properties(materials: &Vec<&aiMaterial>) -> Vec<Vec<MaterialProperty>> {
243    let mut material_properties = Vec::new();
244
245    for &i in materials {
246        let properties = get_properties(i);
247
248        material_properties.push(properties);
249    }
250
251    material_properties
252}
253
254fn get_properties(material: &aiMaterial) -> Vec<MaterialProperty> {
255    let properties = get_base_type_vec_from_raw(material.mProperties, material.mNumProperties);
256    let mut result = Vec::new();
257
258    for item in properties {
259        let material_property = MaterialProperty::new(material, item);
260        result.push(material_property);
261    }
262
263    result
264}
265
266#[derive(Derivative, Clone)]
267#[derivative(Debug)]
268pub struct Material {
269    pub properties: Vec<MaterialProperty>,
270    pub textures: HashMap<TextureType, Rc<RefCell<Texture>>>,
271}
272
273impl Material {
274    fn new(
275        properties: Vec<MaterialProperty>,
276        textures: HashMap<TextureType, Rc<RefCell<Texture>>>,
277    ) -> Self {
278        Self {
279            properties,
280            textures,
281        }
282    }
283}
284
285#[derive(Derivative, Clone)]
286#[derivative(Debug)]
287pub struct MaterialProperty {
288    pub key: String,
289    pub data: PropertyTypeInfo,
290    pub index: usize,
291    pub semantic: TextureType,
292}
293
294trait MaterialPropertyCaster {
295    fn can_cast(&self) -> bool;
296    fn cast(&self) -> Russult<PropertyTypeInfo>;
297}
298
299struct StringPropertyContent<'a> {
300    property_info: &'a aiPropertyTypeInfo,
301    key: &'a aiString,
302    c_type: u32,
303    index: u32,
304    mat: &'a aiMaterial,
305}
306
307struct IntegerPropertyContent<'a> {
308    property_info: &'a aiPropertyTypeInfo,
309    key: &'a aiString,
310    c_type: u32,
311    index: u32,
312    mat: &'a aiMaterial,
313    data: &'a [u8],
314}
315
316struct FloatPropertyContent<'a> {
317    property_info: &'a aiPropertyTypeInfo,
318    key: &'a aiString,
319    c_type: u32,
320    index: u32,
321    mat: &'a aiMaterial,
322    data: &'a [u8],
323}
324
325struct BufferPropertyContent<'a> {
326    property_info: &'a aiPropertyTypeInfo,
327    data: &'a [u8],
328}
329
330impl<'a> MaterialPropertyCaster for BufferPropertyContent<'a> {
331    fn can_cast(&self) -> bool {
332        *self.property_info == aiPropertyTypeInfo_aiPTI_Buffer
333    }
334
335    fn cast(&self) -> Russult<PropertyTypeInfo> {
336        Ok(PropertyTypeInfo::Buffer(self.data.to_vec()))
337    }
338}
339
340impl<'a> MaterialPropertyCaster for IntegerPropertyContent<'a> {
341    fn can_cast(&self) -> bool {
342        *self.property_info == aiPropertyTypeInfo_aiPTI_Integer
343    }
344
345    fn cast(&self) -> Russult<PropertyTypeInfo> {
346        let data_len = self.data.len();
347        let mut max = data_len as u32 / 4;
348        let result: Vec<i32> = vec![0; max as usize];
349
350        if unsafe {
351            aiGetMaterialIntegerArray(
352                self.mat,
353                self.key.data.as_ptr(),
354                self.c_type,
355                self.index,
356                result.as_ptr() as *mut i32,
357                &mut max,
358            )
359        } == aiReturn_aiReturn_SUCCESS
360        {
361            return Ok(PropertyTypeInfo::IntegerArray(result));
362        }
363
364        let key_string: String = self.key.into();
365        Err(RussimpError::MeterialError(format!(
366            "Error while parsing {} to f32",
367            key_string
368        )))
369    }
370}
371
372impl<'a> MaterialPropertyCaster for FloatPropertyContent<'a> {
373    fn can_cast(&self) -> bool {
374        *self.property_info == aiPropertyTypeInfo_aiPTI_Float
375            || *self.property_info == aiPropertyTypeInfo_aiPTI_Double
376    }
377
378    fn cast(&self) -> Russult<PropertyTypeInfo> {
379        let data_len = self.data.len();
380        let mut max = data_len as u32
381            / if *self.property_info == aiPropertyTypeInfo_aiPTI_Double {
382                8
383            } else {
384                4
385            };
386        let result: Vec<f32> = vec![0.0; max as usize];
387
388        if unsafe {
389            aiGetMaterialFloatArray(
390                self.mat,
391                self.key.data.as_ptr(),
392                self.c_type,
393                self.index,
394                result.as_ptr() as *mut f32,
395                &mut max,
396            )
397        } == aiReturn_aiReturn_SUCCESS
398        {
399            return Ok(PropertyTypeInfo::FloatArray(result));
400        }
401
402        let key_string: String = self.key.into();
403        Err(RussimpError::MeterialError(format!(
404            "Error while parsing {} to f32",
405            key_string
406        )))
407    }
408}
409
410impl<'a> MaterialPropertyCaster for StringPropertyContent<'a> {
411    fn can_cast(&self) -> bool {
412        *self.property_info == aiPropertyTypeInfo_aiPTI_String
413    }
414
415    fn cast(&self) -> Russult<PropertyTypeInfo> {
416        let mut content = MaybeUninit::uninit();
417        if unsafe {
418            aiGetMaterialString(
419                self.mat,
420                self.key.data.as_ptr(),
421                self.c_type,
422                self.index,
423                content.as_mut_ptr(),
424            )
425        } == aiReturn_aiReturn_SUCCESS
426        {
427            let ans = unsafe { content.assume_init() };
428            return Ok(PropertyTypeInfo::String(ans.into()));
429        }
430
431        let key_string: String = self.key.into();
432        Err(RussimpError::MeterialError(format!(
433            "Error while parsing {} to string",
434            key_string
435        )))
436    }
437}
438
439#[derive(Derivative, PartialEq, Clone)]
440#[derivative(Debug)]
441#[repr(u32)]
442pub enum PropertyTypeInfo {
443    // Force32Bit, aiPropertyTypeInfo__aiPTI_Force32Bit Not sure how to handle this
444    Buffer(Vec<u8>),
445    IntegerArray(Vec<i32>),
446    FloatArray(Vec<f32>),
447    String(String),
448}
449
450impl MaterialProperty {
451    fn try_get_data_from_property(
452        material: &aiMaterial,
453        property: &aiMaterialProperty,
454    ) -> Russult<PropertyTypeInfo> {
455        let slice =
456            slice_from_raw_parts(property.mData as *const u8, property.mDataLength as usize);
457        let data = unsafe { slice.as_ref() }.unwrap();
458
459        let casters: Vec<Box<dyn MaterialPropertyCaster>> = vec![
460            Box::new(StringPropertyContent {
461                key: &property.mKey,
462                index: property.mIndex,
463                c_type: property.mSemantic,
464                mat: material,
465                property_info: &property.mType,
466            }),
467            Box::new(FloatPropertyContent {
468                key: &property.mKey,
469                index: property.mIndex,
470                c_type: property.mSemantic,
471                mat: material,
472                property_info: &property.mType,
473                data,
474            }),
475            Box::new(IntegerPropertyContent {
476                key: &property.mKey,
477                index: property.mIndex,
478                c_type: property.mSemantic,
479                mat: material,
480                property_info: &property.mType,
481                data,
482            }),
483            Box::new(BufferPropertyContent {
484                data,
485                property_info: &property.mType,
486            }),
487        ];
488
489        for caster in casters {
490            if caster.can_cast() {
491                let data = caster.cast()?;
492                return Ok(data);
493            }
494        }
495
496        Err(RussimpError::MeterialError(
497            "could not find caster for property type".to_string(),
498        ))
499    }
500
501    pub fn new(material: &aiMaterial, property: &aiMaterialProperty) -> MaterialProperty {
502        let data = Self::try_get_data_from_property(material, property).unwrap();
503
504        MaterialProperty {
505            key: property.mKey.into(),
506            data,
507            index: property.mIndex as usize,
508            semantic: FromPrimitive::from_u32(property.mSemantic).unwrap(),
509        }
510    }
511}
512
513#[cfg(test)]
514mod test {
515    const FILENAME_PROPERTY: &str = "$tex.file";
516
517    use crate::{
518        material::{DataContent, MaterialProperty, PropertyTypeInfo, TextureType},
519        utils,
520    };
521
522    #[test]
523    fn semantic_unwrap_panicking() {
524        use crate::{
525            scene::{PostProcess, Scene},
526            utils,
527        };
528
529        let box_file_path = utils::get_model("models/GLTF2/toycar_khronos/ToyCar.gltf");
530
531        Scene::from_file(
532            box_file_path.as_str(),
533            vec![PostProcess::ValidateDataStructure],
534        )
535        .unwrap();
536    }
537
538    #[test]
539    fn material_for_box() {
540        use crate::{
541            scene::{PostProcess, Scene},
542            utils,
543        };
544
545        let box_file_path = utils::get_model("models/BLEND/box.blend");
546
547        let scene = Scene::from_file(
548            box_file_path.as_str(),
549            vec![PostProcess::ValidateDataStructure],
550        )
551        .unwrap();
552
553        assert_eq!(1, scene.materials.len());
554        assert_eq!(41, scene.materials[0].properties.len());
555        assert_eq!(
556            "$mat.blend.mirror.glossAnisotropic",
557            scene.materials[0].properties[40].key.as_str()
558        );
559        assert_eq!(0, scene.materials[0].properties[40].index);
560
561        let ans_value = match &scene.materials[0].properties[40].data {
562            PropertyTypeInfo::Buffer(_) => 0.0,
563            PropertyTypeInfo::IntegerArray(_) => 0.0,
564            PropertyTypeInfo::FloatArray(x) => x[0],
565            PropertyTypeInfo::String(_) => 0.0,
566        };
567
568        assert_eq!(1.0, ans_value);
569        assert_eq!(
570            TextureType::None,
571            scene.materials[0].properties[40].semantic
572        );
573
574        assert_eq!(
575            &scene.materials[0].properties[0].data,
576            &PropertyTypeInfo::String("Material".into())
577        );
578    }
579
580    #[test]
581    fn material_for_wooden_table() {
582        use crate::{
583            scene::{PostProcess, Scene},
584            utils,
585        };
586
587        let table_file_path =
588            utils::get_model("models/GLTF2/round_wooden_table_01_4k/round_wooden_table_01_4k.gltf");
589
590        let scene = Scene::from_file(
591            table_file_path.as_str(),
592            vec![PostProcess::ValidateDataStructure],
593        )
594        .unwrap();
595
596        assert_eq!(
597            &scene.materials[0].properties[0].data,
598            &PropertyTypeInfo::String("round_wooden_table_01".into())
599        );
600        assert_eq!(
601            &scene.materials[0]
602                .properties
603                .iter()
604                .find(|prop| prop.key == "$tex.mappingfiltermin")
605                .unwrap()
606                .data,
607            &PropertyTypeInfo::Buffer(vec![3, 39, 0, 0])
608        );
609        assert_eq!(
610            &scene.materials[0]
611                .properties
612                .iter()
613                .find(|prop| prop.key == "$mat.shadingm")
614                .unwrap()
615                .data,
616            &PropertyTypeInfo::Buffer(vec![11, 0, 0, 0])
617        );
618    }
619
620    #[test]
621    fn debug_material() {
622        use crate::{
623            scene::{PostProcess, Scene},
624            utils,
625        };
626
627        let box_file_path = utils::get_model("models/BLEND/box.blend");
628
629        let scene = Scene::from_file(
630            box_file_path.as_str(),
631            vec![PostProcess::ValidateDataStructure],
632        )
633        .unwrap();
634
635        dbg!(&scene.materials);
636    }
637
638    #[test]
639    fn filenames_available_for_textures() {
640        use crate::scene::{PostProcess, Scene};
641
642        let current_directory_buf =
643            utils::get_model("models/GLTF2/BoxTextured-GLTF/BoxTextured.gltf");
644
645        let scene = Scene::from_file(
646            current_directory_buf.as_str(),
647            vec![PostProcess::ValidateDataStructure],
648        )
649        .unwrap();
650
651        assert_eq!(0, scene.materials[0].textures.len());
652        assert_eq!(0, scene.materials[1].textures.len());
653
654        let properties_first_material: Vec<&MaterialProperty> = scene.materials[0]
655            .properties
656            .iter()
657            .filter(|x| x.key.eq(&FILENAME_PROPERTY.to_string()))
658            .collect();
659        let properties_second_material: Vec<&MaterialProperty> = scene.materials[1]
660            .properties
661            .iter()
662            .filter(|x| x.key.eq(&FILENAME_PROPERTY.to_string()))
663            .collect();
664
665        assert!(properties_first_material
666            .iter()
667            .any(|&x| x.semantic == TextureType::Diffuse));
668        assert!(properties_first_material
669            .iter()
670            .any(|&x| x.semantic == TextureType::BaseColor));
671        assert_eq!(0, properties_second_material.len())
672    }
673
674    #[test]
675    fn read_embedded_texture_works_as_expected() {
676        use crate::{
677            material::TextureType::*,
678            scene::{PostProcess, Scene},
679        };
680
681        let current_directory_buf =
682            utils::get_model("models/GLTF2/BoxTextured-GLTF-Embedded/BoxTextured.gltf");
683
684        let scene = Scene::from_file(
685            current_directory_buf.as_str(),
686            vec![PostProcess::ValidateDataStructure],
687        )
688        .unwrap();
689
690        let texture = scene.materials[0].textures.get(&Diffuse).unwrap();
691
692        let temp = texture.borrow();
693
694        assert!(matches!(
695            &temp.data,
696            DataContent::Bytes(x) if !x.is_empty()
697        ));
698    }
699}