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 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}