1#![allow(clippy::unnecessary_cast)]
4
5use crate::{
6 error::{Error, Result},
7 ffi,
8 ptr::SharedPtr,
9 scene::Scene,
10 sys,
11 types::{
12 Color3D, Color4D, Vector2D, Vector3D, Vector4D, ai_string_to_str, ai_string_to_string,
13 },
14};
15use std::borrow::Cow;
16use std::ffi::CStr;
17use std::ffi::CString;
18
19pub mod material_keys {
21 use std::ffi::CStr;
22
23 macro_rules! cstr {
24 ($lit:literal) => {
25 unsafe { CStr::from_bytes_with_nul_unchecked(concat!($lit, "\0").as_bytes()) }
26 };
27 }
28
29 pub const NAME: &CStr = cstr!("?mat.name");
31 pub const COLOR_DIFFUSE: &CStr = cstr!("$clr.diffuse");
33 pub const COLOR_AMBIENT: &CStr = cstr!("$clr.ambient");
35 pub const COLOR_SPECULAR: &CStr = cstr!("$clr.specular");
37 pub const COLOR_EMISSIVE: &CStr = cstr!("$clr.emissive");
39 pub const COLOR_TRANSPARENT: &CStr = cstr!("$clr.transparent");
41 pub const COLOR_REFLECTIVE: &CStr = cstr!("$clr.reflective");
43 pub const SHININESS: &CStr = cstr!("$mat.shininess");
45 pub const SHININESS_STRENGTH: &CStr = cstr!("$mat.shinpercent");
47 pub const OPACITY: &CStr = cstr!("$mat.opacity");
49 pub const TRANSPARENCYFACTOR: &CStr = cstr!("$mat.transparencyfactor");
51 pub const BUMPSCALING: &CStr = cstr!("$mat.bumpscaling");
53 pub const REFRACTI: &CStr = cstr!("$mat.refracti");
55 pub const REFLECTIVITY: &CStr = cstr!("$mat.reflectivity");
57 pub const SHADING_MODEL: &CStr = cstr!("$mat.shadingm");
59 pub const BLEND_FUNC: &CStr = cstr!("$mat.blend");
61 pub const TWOSIDED: &CStr = cstr!("$mat.twosided");
63
64 pub const BASE_COLOR: &CStr = cstr!("$clr.base");
67 pub const METALLIC_FACTOR: &CStr = cstr!("$mat.metallicFactor");
69 pub const ROUGHNESS_FACTOR: &CStr = cstr!("$mat.roughnessFactor");
71 pub const SPECULAR_FACTOR: &CStr = cstr!("$mat.specularFactor");
73 pub const GLOSSINESS_FACTOR: &CStr = cstr!("$mat.glossinessFactor");
75 pub const SHEEN_COLOR_FACTOR: &CStr = cstr!("$clr.sheen.factor");
77 pub const SHEEN_ROUGHNESS_FACTOR: &CStr = cstr!("$mat.sheen.roughnessFactor");
79 pub const CLEARCOAT_FACTOR: &CStr = cstr!("$mat.clearcoat.factor");
81 pub const CLEARCOAT_ROUGHNESS_FACTOR: &CStr = cstr!("$mat.clearcoat.roughnessFactor");
83 pub const TRANSMISSION_FACTOR: &CStr = cstr!("$mat.transmission.factor");
85 pub const VOLUME_THICKNESS_FACTOR: &CStr = cstr!("$mat.volume.thicknessFactor");
87 pub const VOLUME_ATTENUATION_DISTANCE: &CStr = cstr!("$mat.volume.attenuationDistance");
89 pub const VOLUME_ATTENUATION_COLOR: &CStr = cstr!("$mat.volume.attenuationColor");
91 pub const EMISSIVE_INTENSITY: &CStr = cstr!("$mat.emissiveIntensity");
93 pub const ANISOTROPY_FACTOR: &CStr = cstr!("$mat.anisotropyFactor");
95 pub const ANISOTROPY_ROTATION: &CStr = cstr!("$mat.anisotropyRotation");
97}
98
99#[derive(Clone)]
101pub struct Material {
102 scene: Scene,
103 material_ptr: SharedPtr<sys::aiMaterial>,
104}
105
106#[derive(Debug, Clone)]
108pub struct MaterialStringRef {
109 value: sys::aiString,
110}
111
112impl MaterialStringRef {
113 pub fn as_str(&self) -> Cow<'_, str> {
115 ai_string_to_str(&self.value)
116 }
117
118 pub fn as_bytes(&self) -> &[u8] {
120 let len = (self.value.length as usize).min(self.value.data.len());
121 ffi::slice_from_ptr_len(self, self.value.data.as_ptr() as *const u8, len)
122 }
123
124 #[cfg(feature = "raw-sys")]
126 pub fn as_raw(&self) -> &sys::aiString {
127 &self.value
128 }
129
130 pub fn to_string_lossy(&self) -> String {
132 ai_string_to_string(&self.value)
133 }
134}
135
136impl std::fmt::Display for MaterialStringRef {
137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
138 f.write_str(&ai_string_to_string(&self.value))
139 }
140}
141
142impl Material {
143 pub(crate) fn from_sys_ptr(scene: Scene, material_ptr: *mut sys::aiMaterial) -> Option<Self> {
144 let material_ptr = SharedPtr::new(material_ptr as *const sys::aiMaterial)?;
145 Some(Self {
146 scene,
147 material_ptr,
148 })
149 }
150
151 #[allow(dead_code)]
152 pub(crate) fn as_raw_sys(&self) -> *const sys::aiMaterial {
153 self.material_ptr.as_ptr()
154 }
155
156 #[cfg(feature = "raw-sys")]
158 pub fn as_raw(&self) -> *const sys::aiMaterial {
159 self.as_raw_sys()
160 }
161
162 #[inline]
163 fn raw(&self) -> &sys::aiMaterial {
164 self.material_ptr.as_ref()
165 }
166
167 pub fn name(&self) -> String {
169 self.name_ref().map(|s| s.to_string()).unwrap_or_default()
170 }
171
172 pub fn name_ref(&self) -> Option<MaterialStringRef> {
174 self.get_string_property_ref(material_keys::NAME)
175 }
176
177 pub fn get_string_property_ref(&self, key: &CStr) -> Option<MaterialStringRef> {
179 let mut ai_string = sys::aiString::default();
180
181 let result = unsafe {
182 sys::aiGetMaterialString(
183 self.as_raw_sys(),
184 key.as_ptr(),
185 0, 0, &mut ai_string,
188 )
189 };
190
191 if result == sys::aiReturn::aiReturn_SUCCESS {
192 Some(MaterialStringRef { value: ai_string })
193 } else {
194 None
195 }
196 }
197
198 pub fn get_string_property(&self, key: &CStr) -> Option<String> {
200 self.get_string_property_ref(key).map(|s| s.to_string())
201 }
202
203 pub fn get_string_property_str(&self, key: &str) -> Result<Option<String>> {
205 let c_key = CString::new(key)
206 .map_err(|_| Error::invalid_parameter("material key contains NUL byte".to_string()))?;
207 Ok(self.get_string_property(c_key.as_c_str()))
208 }
209
210 pub fn get_float_property(&self, key: &CStr) -> Option<f32> {
212 let mut value = 0.0f32;
213 let mut max = 1u32;
214
215 let result = unsafe {
216 sys::aiGetMaterialFloatArray(
217 self.as_raw_sys(),
218 key.as_ptr(),
219 0, 0, &mut value,
222 &mut max,
223 )
224 };
225
226 if result == sys::aiReturn::aiReturn_SUCCESS && max > 0 {
227 Some(value)
228 } else {
229 None
230 }
231 }
232
233 pub fn get_float_property_str(&self, key: &str) -> Result<Option<f32>> {
235 let c_key = CString::new(key)
236 .map_err(|_| Error::invalid_parameter("material key contains NUL byte".to_string()))?;
237 Ok(self.get_float_property(c_key.as_c_str()))
238 }
239
240 pub fn get_integer_property(&self, key: &CStr) -> Option<i32> {
242 let mut value = 0i32;
243 let mut max = 1u32;
244
245 let result = unsafe {
246 sys::aiGetMaterialIntegerArray(
247 self.as_raw_sys(),
248 key.as_ptr(),
249 0, 0, &mut value,
252 &mut max,
253 )
254 };
255
256 if result == sys::aiReturn::aiReturn_SUCCESS && max > 0 {
257 Some(value)
258 } else {
259 None
260 }
261 }
262
263 pub fn get_integer_property_str(&self, key: &str) -> Result<Option<i32>> {
265 let c_key = CString::new(key)
266 .map_err(|_| Error::invalid_parameter("material key contains NUL byte".to_string()))?;
267 Ok(self.get_integer_property(c_key.as_c_str()))
268 }
269
270 pub fn get_color_property(&self, key: &CStr) -> Option<Color4D> {
272 let mut color = sys::aiColor4D {
273 r: 0.0,
274 g: 0.0,
275 b: 0.0,
276 a: 1.0,
277 };
278
279 let result = unsafe {
280 sys::aiGetMaterialColor(
281 self.as_raw_sys(),
282 key.as_ptr(),
283 0, 0, &mut color,
286 )
287 };
288
289 if result == sys::aiReturn::aiReturn_SUCCESS {
290 Some(Color4D::new(color.r, color.g, color.b, color.a))
291 } else {
292 None
293 }
294 }
295
296 pub fn get_color_property_str(&self, key: &str) -> Result<Option<Color4D>> {
298 let c_key = CString::new(key)
299 .map_err(|_| Error::invalid_parameter("material key contains NUL byte".to_string()))?;
300 Ok(self.get_color_property(c_key.as_c_str()))
301 }
302
303 pub fn diffuse_color(&self) -> Option<Color3D> {
305 self.get_color_property(material_keys::COLOR_DIFFUSE)
306 .map(|c| Color3D::new(c.x, c.y, c.z))
307 }
308
309 pub fn specular_color(&self) -> Option<Color3D> {
311 self.get_color_property(material_keys::COLOR_SPECULAR)
312 .map(|c| Color3D::new(c.x, c.y, c.z))
313 }
314
315 pub fn ambient_color(&self) -> Option<Color3D> {
317 self.get_color_property(material_keys::COLOR_AMBIENT)
318 .map(|c| Color3D::new(c.x, c.y, c.z))
319 }
320
321 pub fn emissive_color(&self) -> Option<Color3D> {
323 self.get_color_property(material_keys::COLOR_EMISSIVE)
324 .map(|c| Color3D::new(c.x, c.y, c.z))
325 }
326
327 pub fn transparent_color(&self) -> Option<Color3D> {
329 self.get_color_property(material_keys::COLOR_TRANSPARENT)
330 .map(|c| Color3D::new(c.x, c.y, c.z))
331 }
332
333 pub fn reflective_color(&self) -> Option<Color3D> {
335 self.get_color_property(material_keys::COLOR_REFLECTIVE)
336 .map(|c| Color3D::new(c.x, c.y, c.z))
337 }
338
339 pub fn shininess(&self) -> Option<f32> {
341 self.get_float_property(material_keys::SHININESS)
342 }
343
344 pub fn shininess_strength(&self) -> Option<f32> {
346 self.get_float_property(material_keys::SHININESS_STRENGTH)
347 }
348
349 pub fn base_color(&self) -> Option<Color4D> {
351 self.get_color_property(material_keys::BASE_COLOR)
352 }
353
354 pub fn metallic_factor(&self) -> Option<f32> {
356 self.get_float_property(material_keys::METALLIC_FACTOR)
357 }
358
359 pub fn roughness_factor(&self) -> Option<f32> {
361 self.get_float_property(material_keys::ROUGHNESS_FACTOR)
362 }
363
364 pub fn glossiness_factor(&self) -> Option<f32> {
366 self.get_float_property(material_keys::GLOSSINESS_FACTOR)
367 }
368
369 pub fn specular_factor(&self) -> Option<f32> {
371 self.get_float_property(material_keys::SPECULAR_FACTOR)
372 }
373
374 pub fn sheen_color_factor(&self) -> Option<Color4D> {
376 self.get_color_property(material_keys::SHEEN_COLOR_FACTOR)
377 }
378
379 pub fn sheen_roughness_factor(&self) -> Option<f32> {
381 self.get_float_property(material_keys::SHEEN_ROUGHNESS_FACTOR)
382 }
383
384 pub fn clearcoat_factor(&self) -> Option<f32> {
386 self.get_float_property(material_keys::CLEARCOAT_FACTOR)
387 }
388
389 pub fn clearcoat_roughness_factor(&self) -> Option<f32> {
391 self.get_float_property(material_keys::CLEARCOAT_ROUGHNESS_FACTOR)
392 }
393
394 pub fn transmission_factor(&self) -> Option<f32> {
396 self.get_float_property(material_keys::TRANSMISSION_FACTOR)
397 }
398
399 pub fn volume_thickness_factor(&self) -> Option<f32> {
401 self.get_float_property(material_keys::VOLUME_THICKNESS_FACTOR)
402 }
403
404 pub fn volume_attenuation_distance(&self) -> Option<f32> {
406 self.get_float_property(material_keys::VOLUME_ATTENUATION_DISTANCE)
407 }
408
409 pub fn volume_attenuation_color(&self) -> Option<Color3D> {
411 self.get_color_property(material_keys::VOLUME_ATTENUATION_COLOR)
412 .map(|c| Color3D::new(c.x, c.y, c.z))
413 }
414
415 pub fn emissive_intensity(&self) -> Option<f32> {
417 self.get_float_property(material_keys::EMISSIVE_INTENSITY)
418 }
419
420 pub fn anisotropy_factor(&self) -> Option<f32> {
422 self.get_float_property(material_keys::ANISOTROPY_FACTOR)
423 }
424
425 pub fn anisotropy_rotation(&self) -> Option<f32> {
427 self.get_float_property(material_keys::ANISOTROPY_ROTATION)
428 }
429
430 pub fn opacity(&self) -> Option<f32> {
432 self.get_float_property(material_keys::OPACITY)
433 }
434
435 pub fn transparency_factor(&self) -> Option<f32> {
437 self.get_float_property(material_keys::TRANSPARENCYFACTOR)
438 }
439
440 pub fn bump_scaling(&self) -> Option<f32> {
442 self.get_float_property(material_keys::BUMPSCALING)
443 }
444
445 pub fn refraction_index(&self) -> Option<f32> {
447 self.get_float_property(material_keys::REFRACTI)
448 }
449
450 pub fn reflectivity(&self) -> Option<f32> {
452 self.get_float_property(material_keys::REFLECTIVITY)
453 }
454
455 pub fn shading_model(&self) -> Option<i32> {
457 self.get_integer_property(material_keys::SHADING_MODEL)
458 }
459
460 pub fn shading_model_enum(&self) -> Option<ShadingModel> {
462 self.shading_model()
463 .map(|v| ShadingModel::from_raw(v as u32))
464 }
465
466 pub fn property_info(
472 &self,
473 key: &CStr,
474 semantic: Option<TextureType>,
475 index: u32,
476 ) -> Option<MaterialPropertyInfo> {
477 let prop_ptr = self.property_ptr(key, semantic, index)?;
478 Some(MaterialPropertyRef::from_ptr(self.scene.clone(), prop_ptr).into_info())
479 }
480
481 pub fn property_info_str(
483 &self,
484 key: &str,
485 semantic: Option<TextureType>,
486 index: u32,
487 ) -> Option<MaterialPropertyInfo> {
488 let c_key = CString::new(key).ok()?;
489 self.property_info(c_key.as_c_str(), semantic, index)
490 }
491
492 pub fn property_type(
494 &self,
495 key: &CStr,
496 semantic: Option<TextureType>,
497 index: u32,
498 ) -> Option<PropertyTypeInfo> {
499 self.property_info(key, semantic, index)
500 .map(|p| p.type_info)
501 }
502
503 pub fn property_type_str(
505 &self,
506 key: &str,
507 semantic: Option<TextureType>,
508 index: u32,
509 ) -> Option<PropertyTypeInfo> {
510 let c_key = CString::new(key).ok()?;
511 self.property_type(c_key.as_c_str(), semantic, index)
512 }
513
514 fn property_ptr(
515 &self,
516 key: &CStr,
517 semantic: Option<TextureType>,
518 index: u32,
519 ) -> Option<*const sys::aiMaterialProperty> {
520 let mut prop_ptr: *const sys::aiMaterialProperty = std::ptr::null();
521 let ok = unsafe {
522 sys::aiGetMaterialProperty(
523 self.as_raw_sys(),
524 key.as_ptr(),
525 semantic.map(|t| t.to_sys() as u32).unwrap_or(0),
526 index,
527 &mut prop_ptr,
528 ) == sys::aiReturn::aiReturn_SUCCESS
529 };
530 (ok && !prop_ptr.is_null()).then_some(prop_ptr)
531 }
532
533 pub fn get_property_raw_ref(
535 &self,
536 key: &CStr,
537 semantic: Option<TextureType>,
538 index: u32,
539 ) -> Option<&[u8]> {
540 let prop_ptr = self.property_ptr(key, semantic, index)?;
541 let prop = ffi::ref_from_ptr(self, prop_ptr)?;
542 Some(ffi::slice_from_ptr_len(
543 self,
544 prop.mData as *const u8,
545 prop.mDataLength as usize,
546 ))
547 }
548
549 pub fn get_property_raw(
551 &self,
552 key: &CStr,
553 semantic: Option<TextureType>,
554 index: u32,
555 ) -> Option<Vec<u8>> {
556 self.get_property_raw_ref(key, semantic, index)
557 .map(|raw| raw.to_vec())
558 }
559
560 pub fn get_property_raw_str(
562 &self,
563 key: &str,
564 semantic: Option<TextureType>,
565 index: u32,
566 ) -> Option<Vec<u8>> {
567 let c_key = CString::new(key).ok()?;
568 self.get_property_raw(c_key.as_c_str(), semantic, index)
569 }
570
571 pub fn get_property_i32_array(
573 &self,
574 key: &CStr,
575 semantic: Option<TextureType>,
576 index: u32,
577 ) -> Option<Vec<i32>> {
578 let info = self.property_info(key, semantic, index)?;
579 let elem_size = match info.type_info {
581 PropertyTypeInfo::Integer => std::mem::size_of::<i32>(),
582 PropertyTypeInfo::Float => std::mem::size_of::<f32>(),
583 PropertyTypeInfo::Double => std::mem::size_of::<f64>(),
584 _ => return None,
585 };
586 if elem_size == 0 {
587 return None;
588 }
589 let count = (info.data_length as usize) / elem_size;
590 let mut out = vec![0i32; count];
591 let mut max = count as u32;
592 let result = unsafe {
593 sys::aiGetMaterialIntegerArray(
594 self.as_raw_sys(),
595 key.as_ptr(),
596 semantic.map(|t| t.to_sys() as u32).unwrap_or(0),
597 index,
598 out.as_mut_ptr(),
599 &mut max,
600 )
601 };
602 if result == sys::aiReturn::aiReturn_SUCCESS {
603 out.truncate(max as usize);
604 Some(out)
605 } else {
606 None
607 }
608 }
609
610 pub fn get_property_i32_array_str(
612 &self,
613 key: &str,
614 semantic: Option<TextureType>,
615 index: u32,
616 ) -> Option<Vec<i32>> {
617 let c_key = CString::new(key).ok()?;
618 self.get_property_i32_array(c_key.as_c_str(), semantic, index)
619 }
620
621 pub fn get_property_f32_array(
623 &self,
624 key: &CStr,
625 semantic: Option<TextureType>,
626 index: u32,
627 ) -> Option<Vec<f32>> {
628 let info = self.property_info(key, semantic, index)?;
629 match info.type_info {
630 PropertyTypeInfo::Float | PropertyTypeInfo::Double | PropertyTypeInfo::Integer => {
631 let elem_size = match info.type_info {
634 PropertyTypeInfo::Float => std::mem::size_of::<f32>(),
635 PropertyTypeInfo::Double => std::mem::size_of::<f64>(),
636 PropertyTypeInfo::Integer => std::mem::size_of::<i32>(),
637 _ => return None,
638 };
639 let count = (info.data_length as usize) / elem_size;
640 let mut out = vec![0f32; count];
641 let mut max = count as u32;
642 let result = unsafe {
643 sys::aiGetMaterialFloatArray(
644 self.as_raw_sys(),
645 key.as_ptr(),
646 semantic.map(|t| t.to_sys() as u32).unwrap_or(0),
647 index,
648 out.as_mut_ptr(),
649 &mut max,
650 )
651 };
652 if result == sys::aiReturn::aiReturn_SUCCESS {
653 out.truncate(max as usize);
654 return Some(out);
655 }
656 self.get_property_f64_array(key, semantic, index)
658 .map(|v| v.into_iter().map(|x| x as f32).collect())
659 }
660 _ => None,
661 }
662 }
663
664 pub fn get_property_f32_array_str(
666 &self,
667 key: &str,
668 semantic: Option<TextureType>,
669 index: u32,
670 ) -> Option<Vec<f32>> {
671 let c_key = CString::new(key).ok()?;
672 self.get_property_f32_array(c_key.as_c_str(), semantic, index)
673 }
674
675 pub fn get_property_f64_array(
678 &self,
679 key: &CStr,
680 semantic: Option<TextureType>,
681 index: u32,
682 ) -> Option<Vec<f64>> {
683 let info = self.property_info(key, semantic, index)?;
684 let raw = self.get_property_raw_ref(key, semantic, index)?;
685 match info.type_info {
686 PropertyTypeInfo::Double => {
687 let sz = std::mem::size_of::<f64>();
688 if sz == 0 || raw.len() % sz != 0 {
689 return None;
690 }
691 let mut out = Vec::with_capacity(raw.len() / sz);
692 for chunk in raw.chunks_exact(sz) {
693 let mut arr = [0u8; 8];
694 arr.copy_from_slice(chunk);
695 out.push(f64::from_ne_bytes(arr));
696 }
697 Some(out)
698 }
699 PropertyTypeInfo::Float => {
700 let sz = std::mem::size_of::<f32>();
701 if sz == 0 || raw.len() % sz != 0 {
702 return None;
703 }
704 let mut out = Vec::with_capacity(raw.len() / sz);
705 for chunk in raw.chunks_exact(sz) {
706 let mut arr = [0u8; 4];
707 arr.copy_from_slice(chunk);
708 out.push(f32::from_ne_bytes(arr) as f64);
709 }
710 Some(out)
711 }
712 PropertyTypeInfo::Integer => {
713 let sz = std::mem::size_of::<i32>();
714 if sz == 0 || raw.len() % sz != 0 {
715 return None;
716 }
717 let mut out = Vec::with_capacity(raw.len() / sz);
718 for chunk in raw.chunks_exact(sz) {
719 let mut arr = [0u8; 4];
720 arr.copy_from_slice(chunk);
721 out.push(i32::from_ne_bytes(arr) as f64);
722 }
723 Some(out)
724 }
725 _ => None,
726 }
727 }
728
729 pub fn get_property_f64_array_str(
731 &self,
732 key: &str,
733 semantic: Option<TextureType>,
734 index: u32,
735 ) -> Option<Vec<f64>> {
736 let c_key = CString::new(key).ok()?;
737 self.get_property_f64_array(c_key.as_c_str(), semantic, index)
738 }
739
740 pub fn all_properties(&self) -> Vec<MaterialPropertyInfo> {
742 self.all_properties_iter().collect()
743 }
744
745 pub fn all_properties_iter(&self) -> impl Iterator<Item = MaterialPropertyInfo> + '_ {
747 self.properties().map(MaterialPropertyRef::into_info)
748 }
749
750 pub fn properties(&self) -> MaterialPropertyIterator {
752 let m = self.raw();
753 MaterialPropertyIterator {
754 scene: self.scene.clone(),
755 props: SharedPtr::new(m.mProperties as *const *const sys::aiMaterialProperty),
756 count: m.mNumProperties as usize,
757 index: 0,
758 }
759 }
760
761 pub fn is_two_sided(&self) -> bool {
763 self.get_integer_property(material_keys::TWOSIDED)
764 .map(|v| v != 0)
765 .unwrap_or(false)
766 }
767
768 pub fn is_unlit(&self) -> bool {
770 matches!(self.shading_model_enum(), Some(ShadingModel::NoShading))
771 }
772
773 pub fn blend_mode(&self) -> Option<BlendMode> {
775 self.get_integer_property(material_keys::BLEND_FUNC)
776 .map(|v| BlendMode::from_raw(v as u32))
777 }
778
779 pub fn texture_count(&self, texture_type: TextureType) -> usize {
781 unsafe { sys::aiGetMaterialTextureCount(self.as_raw_sys(), texture_type.to_sys()) as usize }
782 }
783
784 pub fn texture_ref(&self, texture_type: TextureType, index: usize) -> Option<TextureInfoRef> {
786 if index >= self.texture_count(texture_type) {
787 return None;
788 }
789
790 unsafe {
791 let mut path = sys::aiString::default();
792 let mut mapping = std::mem::MaybeUninit::<sys::aiTextureMapping>::uninit();
793 let mut uv_index = std::mem::MaybeUninit::<u32>::uninit();
794 let mut blend = std::mem::MaybeUninit::<f32>::uninit();
795 let mut op = std::mem::MaybeUninit::<sys::aiTextureOp>::uninit();
796 let mut map_mode: [sys::aiTextureMapMode; 3] =
799 [sys::aiTextureMapMode::aiTextureMapMode_Wrap; 3];
800 let mut tex_flags: u32 = 0;
801
802 let result = sys::aiGetMaterialTexture(
803 self.as_raw_sys(),
804 texture_type.to_sys(),
805 index as u32,
806 &mut path,
807 mapping.as_mut_ptr(),
808 uv_index.as_mut_ptr(),
809 blend.as_mut_ptr(),
810 op.as_mut_ptr(),
811 map_mode.as_mut_ptr() as *mut _,
812 &mut tex_flags as *mut u32,
813 );
814
815 if result != sys::aiReturn::aiReturn_SUCCESS {
816 return None;
817 }
818
819 let mapping_val = mapping.assume_init();
820 let uv_index_val = uv_index.assume_init();
821 let blend_val = blend.assume_init();
822 let op_val = op.assume_init();
823
824 let mut uv_transform = std::mem::MaybeUninit::<sys::aiUVTransform>::uninit();
826 let uv_key: &CStr = c"$tex.uvtrafo";
827 let uv_ok = sys::aiGetMaterialUVTransform(
828 self.as_raw_sys(),
829 uv_key.as_ptr(),
830 texture_type.to_sys() as u32,
831 index as u32,
832 uv_transform.as_mut_ptr(),
833 ) == sys::aiReturn::aiReturn_SUCCESS;
834
835 let uv_transform = if uv_ok {
836 let t = uv_transform.assume_init();
837 Some(UVTransform {
838 translation: Vector2D::new(t.mTranslation.x, t.mTranslation.y),
839 scaling: Vector2D::new(t.mScaling.x, t.mScaling.y),
840 rotation: t.mRotation,
841 })
842 } else {
843 None
844 };
845
846 let axis = {
848 let key: &CStr = c"$tex.mapaxis";
849 let mut prop_ptr: *const sys::aiMaterialProperty = std::ptr::null();
850 let ok = sys::aiGetMaterialProperty(
851 self.as_raw_sys(),
852 key.as_ptr(),
853 texture_type.to_sys() as u32,
854 index as u32,
855 &mut prop_ptr,
856 ) == sys::aiReturn::aiReturn_SUCCESS;
857 if ok {
858 if let Some(prop) = ffi::ref_from_ptr(self, prop_ptr) {
859 MaterialPropertyData::from_sys(prop)
860 .and_then(|d| d.read_ne_f32_array::<3>(0))
861 .map(|[x, y, z]| Vector3D::new(x, y, z))
862 } else {
863 None
864 }
865 } else {
866 None
867 }
868 };
869
870 Some(TextureInfoRef {
871 path,
872 mapping: TextureMapping::from_raw(mapping_val),
873 uv_index: uv_index_val,
874 blend_factor: blend_val,
875 operation: TextureOperation::from_raw(op_val),
876 map_modes: [
877 TextureMapMode::from_raw(map_mode[0]),
878 TextureMapMode::from_raw(map_mode[1]),
879 TextureMapMode::from_raw(map_mode[2]),
880 ],
881 flags: TextureFlags::from_bits_truncate(tex_flags),
882 uv_transform,
883 axis,
884 })
885 }
886 }
887
888 pub fn texture_refs(
890 &self,
891 texture_type: TextureType,
892 ) -> impl Iterator<Item = TextureInfoRef> + '_ {
893 let count = self.texture_count(texture_type);
894 (0..count).filter_map(move |i| self.texture_ref(texture_type, i))
895 }
896
897 pub fn texture(&self, texture_type: TextureType, index: usize) -> Option<TextureInfo> {
899 self.texture_ref(texture_type, index)
900 .map(TextureInfoRef::into_owned)
901 }
902}
903
904#[derive(Debug, Clone, Copy, PartialEq, Eq)]
906#[repr(u32)]
907pub enum TextureType {
908 Diffuse = sys::aiTextureType::aiTextureType_DIFFUSE as u32,
910 Specular = sys::aiTextureType::aiTextureType_SPECULAR as u32,
912 Ambient = sys::aiTextureType::aiTextureType_AMBIENT as u32,
914 Emissive = sys::aiTextureType::aiTextureType_EMISSIVE as u32,
916 Height = sys::aiTextureType::aiTextureType_HEIGHT as u32,
918 Normals = sys::aiTextureType::aiTextureType_NORMALS as u32,
920 Shininess = sys::aiTextureType::aiTextureType_SHININESS as u32,
922 Opacity = sys::aiTextureType::aiTextureType_OPACITY as u32,
924 Displacement = sys::aiTextureType::aiTextureType_DISPLACEMENT as u32,
926 Lightmap = sys::aiTextureType::aiTextureType_LIGHTMAP as u32,
928 Reflection = sys::aiTextureType::aiTextureType_REFLECTION as u32,
930 BaseColor = sys::aiTextureType::aiTextureType_BASE_COLOR as u32,
932 NormalCamera = sys::aiTextureType::aiTextureType_NORMAL_CAMERA as u32,
934 EmissionColor = sys::aiTextureType::aiTextureType_EMISSION_COLOR as u32,
936 Metalness = sys::aiTextureType::aiTextureType_METALNESS as u32,
938 DiffuseRoughness = sys::aiTextureType::aiTextureType_DIFFUSE_ROUGHNESS as u32,
940 AmbientOcclusion = sys::aiTextureType::aiTextureType_AMBIENT_OCCLUSION as u32,
942 Unknown = sys::aiTextureType::aiTextureType_UNKNOWN as u32,
944 Sheen = sys::aiTextureType::aiTextureType_SHEEN as u32,
946 Clearcoat = sys::aiTextureType::aiTextureType_CLEARCOAT as u32,
948 Transmission = sys::aiTextureType::aiTextureType_TRANSMISSION as u32,
950 MayaBase = sys::aiTextureType::aiTextureType_MAYA_BASE as u32,
952 MayaSpecular = sys::aiTextureType::aiTextureType_MAYA_SPECULAR as u32,
954 MayaSpecularColor = sys::aiTextureType::aiTextureType_MAYA_SPECULAR_COLOR as u32,
956 MayaSpecularRoughness = sys::aiTextureType::aiTextureType_MAYA_SPECULAR_ROUGHNESS as u32,
958 Anisotropy = sys::aiTextureType::aiTextureType_ANISOTROPY as u32,
960 GltfMetallicRoughness = sys::aiTextureType::aiTextureType_GLTF_METALLIC_ROUGHNESS as u32,
962}
963
964#[derive(Debug, Clone, Copy, PartialEq, Eq)]
966pub enum ShadingModel {
967 Flat,
969 Gouraud,
971 Phong,
973 Blinn,
975 Toon,
977 OrenNayar,
979 Minnaert,
981 CookTorrance,
983 NoShading,
985 Fresnel,
987 PbrSpecularGlossiness,
989 PbrMetallicRoughness,
991 Unknown(u32),
993}
994
995impl ShadingModel {
996 fn from_raw(v: u32) -> Self {
997 use sys::aiShadingMode;
998 match v {
999 x if x == aiShadingMode::aiShadingMode_Flat as u32 => ShadingModel::Flat,
1000 x if x == aiShadingMode::aiShadingMode_Gouraud as u32 => ShadingModel::Gouraud,
1001 x if x == aiShadingMode::aiShadingMode_Phong as u32 => ShadingModel::Phong,
1002 x if x == aiShadingMode::aiShadingMode_Blinn as u32 => ShadingModel::Blinn,
1003 x if x == aiShadingMode::aiShadingMode_Toon as u32 => ShadingModel::Toon,
1004 x if x == aiShadingMode::aiShadingMode_OrenNayar as u32 => ShadingModel::OrenNayar,
1005 x if x == aiShadingMode::aiShadingMode_Minnaert as u32 => ShadingModel::Minnaert,
1006 x if x == aiShadingMode::aiShadingMode_CookTorrance as u32 => {
1007 ShadingModel::CookTorrance
1008 }
1009 x if x == aiShadingMode::aiShadingMode_NoShading as u32 => ShadingModel::NoShading,
1010 x if x == aiShadingMode::aiShadingMode_Fresnel as u32 => ShadingModel::Fresnel,
1011 x if x == aiShadingMode::aiShadingMode_PBR_BRDF as u32 => {
1012 ShadingModel::PbrSpecularGlossiness
1013 }
1014 other => ShadingModel::Unknown(other),
1015 }
1016 }
1017}
1018
1019#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1021pub enum PropertyTypeInfo {
1022 Float,
1024 Double,
1026 String,
1028 Integer,
1030 Buffer,
1032 Unknown(u32),
1034}
1035
1036impl PropertyTypeInfo {
1037 fn from_sys(t: sys::aiPropertyTypeInfo) -> Self {
1038 match t {
1039 sys::aiPropertyTypeInfo::aiPTI_Float => Self::Float,
1040 sys::aiPropertyTypeInfo::aiPTI_Double => Self::Double,
1041 sys::aiPropertyTypeInfo::aiPTI_String => Self::String,
1042 sys::aiPropertyTypeInfo::aiPTI_Integer => Self::Integer,
1043 sys::aiPropertyTypeInfo::aiPTI_Buffer => Self::Buffer,
1044 other => Self::Unknown(other as u32),
1045 }
1046 }
1047}
1048
1049#[derive(Debug, Clone)]
1051pub struct MaterialPropertyInfo {
1052 pub key: String,
1054 pub semantic: Option<TextureType>,
1056 pub index: u32,
1058 pub data_length: u32,
1060 pub type_info: PropertyTypeInfo,
1062}
1063
1064impl MaterialPropertyInfo {
1065 fn from_ref(p: MaterialPropertyRef) -> Self {
1066 let semantic = p.semantic();
1067 Self {
1068 key: p.key_string(),
1069 semantic,
1070 index: p.index(),
1071 data_length: p.data().len() as u32,
1072 type_info: p.type_info(),
1073 }
1074 }
1075}
1076
1077#[derive(Debug, Clone)]
1079pub struct MaterialPropertyRef {
1080 #[allow(dead_code)]
1081 scene: Scene,
1082 prop_ptr: SharedPtr<sys::aiMaterialProperty>,
1083}
1084
1085struct MaterialPropertyData<'a> {
1089 bytes: &'a [u8],
1090}
1091
1092impl<'a> MaterialPropertyData<'a> {
1093 unsafe fn from_sys(prop: &'a sys::aiMaterialProperty) -> Option<Self> {
1096 let bytes =
1097 ffi::slice_from_ptr_len_opt(prop, prop.mData as *const u8, prop.mDataLength as usize)?;
1098 Some(Self { bytes })
1099 }
1100
1101 fn read_ne_u32(&self, offset: usize) -> Option<u32> {
1102 let raw: [u8; 4] = self
1103 .bytes
1104 .get(offset..offset.checked_add(4)?)?
1105 .try_into()
1106 .ok()?;
1107 Some(u32::from_ne_bytes(raw))
1108 }
1109
1110 fn read_ne_i32(&self, offset: usize) -> Option<i32> {
1111 let raw: [u8; 4] = self
1112 .bytes
1113 .get(offset..offset.checked_add(4)?)?
1114 .try_into()
1115 .ok()?;
1116 Some(i32::from_ne_bytes(raw))
1117 }
1118
1119 fn read_ne_f32(&self, offset: usize) -> Option<f32> {
1120 let raw: [u8; 4] = self
1121 .bytes
1122 .get(offset..offset.checked_add(4)?)?
1123 .try_into()
1124 .ok()?;
1125 Some(f32::from_ne_bytes(raw))
1126 }
1127
1128 fn read_ne_f64(&self, offset: usize) -> Option<f64> {
1129 let raw: [u8; 8] = self
1130 .bytes
1131 .get(offset..offset.checked_add(8)?)?
1132 .try_into()
1133 .ok()?;
1134 Some(f64::from_ne_bytes(raw))
1135 }
1136
1137 fn read_ne_f32_array<const N: usize>(&self, offset: usize) -> Option<[f32; N]> {
1138 let need = 4usize.checked_mul(N)?;
1139 let slice = self.bytes.get(offset..offset.checked_add(need)?)?;
1140 let mut out = [0.0f32; N];
1141 for (i, slot) in out.iter_mut().enumerate() {
1142 let start = i * 4;
1143 let raw: [u8; 4] = slice[start..start + 4].try_into().ok()?;
1144 *slot = f32::from_ne_bytes(raw);
1145 }
1146 Some(out)
1147 }
1148
1149 fn read_ne_f64_array<const N: usize>(&self, offset: usize) -> Option<[f64; N]> {
1150 let need = 8usize.checked_mul(N)?;
1151 let slice = self.bytes.get(offset..offset.checked_add(need)?)?;
1152 let mut out = [0.0f64; N];
1153 for (i, slot) in out.iter_mut().enumerate() {
1154 let start = i * 8;
1155 let raw: [u8; 8] = slice[start..start + 8].try_into().ok()?;
1156 *slot = f64::from_ne_bytes(raw);
1157 }
1158 Some(out)
1159 }
1160
1161 fn decode_ai_string(&self) -> Option<sys::aiString> {
1162 let declared_len = self.read_ne_u32(0)? as usize;
1163 let payload = self.bytes.get(4..)?;
1164 let max = (sys::AI_MAXLEN as usize).saturating_sub(1);
1165 let copy_len = declared_len.min(payload.len()).min(max);
1166
1167 let mut value = sys::aiString {
1168 length: copy_len as u32,
1169 data: [0; sys::AI_MAXLEN as usize],
1170 };
1171
1172 for (dst, src) in value.data[..copy_len].iter_mut().zip(&payload[..copy_len]) {
1173 *dst = *src as std::os::raw::c_char;
1174 }
1175 value.data[copy_len] = 0;
1176 Some(value)
1177 }
1178}
1179
1180impl MaterialPropertyRef {
1181 fn from_ptr(scene: Scene, prop_ptr: *const sys::aiMaterialProperty) -> Self {
1182 debug_assert!(!prop_ptr.is_null());
1183 let prop_ptr = unsafe { SharedPtr::new_unchecked(prop_ptr) };
1184 Self { scene, prop_ptr }
1185 }
1186
1187 #[inline]
1188 fn raw(&self) -> &sys::aiMaterialProperty {
1189 self.prop_ptr.as_ref()
1190 }
1191
1192 pub fn key_str(&self) -> Cow<'_, str> {
1194 ai_string_to_str(&self.raw().mKey)
1195 }
1196
1197 pub fn key_bytes(&self) -> &[u8] {
1199 let s = &self.raw().mKey;
1200 let len = (s.length as usize).min(s.data.len());
1201 ffi::slice_from_ptr_len(self, s.data.as_ptr() as *const u8, len)
1202 }
1203
1204 pub fn key_string(&self) -> String {
1206 ai_string_to_string(&self.raw().mKey)
1207 }
1208
1209 pub fn semantic(&self) -> Option<TextureType> {
1211 TextureType::from_u32(self.raw().mSemantic)
1212 }
1213
1214 pub fn index(&self) -> u32 {
1216 self.raw().mIndex
1217 }
1218
1219 pub fn type_info(&self) -> PropertyTypeInfo {
1221 PropertyTypeInfo::from_sys(self.raw().mType)
1222 }
1223
1224 pub fn data(&self) -> &[u8] {
1226 let p = self.raw();
1227 ffi::slice_from_ptr_len(self, p.mData as *const u8, p.mDataLength as usize)
1228 }
1229
1230 pub fn data_i32(&self) -> Option<&[i32]> {
1232 (self.type_info() == PropertyTypeInfo::Integer)
1233 .then(|| self.data_cast_slice_opt())
1234 .flatten()
1235 }
1236
1237 pub fn data_u32(&self) -> Option<&[u32]> {
1242 (self.type_info() == PropertyTypeInfo::Integer)
1243 .then(|| self.data_cast_slice_opt())
1244 .flatten()
1245 }
1246
1247 pub fn data_f32(&self) -> Option<&[f32]> {
1249 (self.type_info() == PropertyTypeInfo::Float)
1250 .then(|| self.data_cast_slice_opt())
1251 .flatten()
1252 }
1253
1254 pub fn data_f64(&self) -> Option<&[f64]> {
1256 (self.type_info() == PropertyTypeInfo::Double)
1257 .then(|| self.data_cast_slice_opt())
1258 .flatten()
1259 }
1260
1261 pub fn string_ref(&self) -> Option<MaterialStringRef> {
1267 if self.type_info() != PropertyTypeInfo::String {
1268 return None;
1269 }
1270 let p = self.raw();
1271 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1272 let value = d.decode_ai_string()?;
1273 Some(MaterialStringRef { value })
1274 }
1275
1276 pub fn as_i32(&self) -> Option<i32> {
1278 if self.type_info() != PropertyTypeInfo::Integer {
1279 return None;
1280 }
1281 let p = self.raw();
1282 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1283 d.read_ne_i32(0)
1284 }
1285
1286 pub fn as_u32(&self) -> Option<u32> {
1288 u32::try_from(self.as_i32()?).ok()
1289 }
1290
1291 pub fn as_bool(&self) -> Option<bool> {
1293 Some(self.as_i32()? != 0)
1294 }
1295
1296 pub fn as_f32(&self) -> Option<f32> {
1298 if self.type_info() != PropertyTypeInfo::Float {
1299 return None;
1300 }
1301 let p = self.raw();
1302 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1303 d.read_ne_f32(0)
1304 }
1305
1306 pub fn as_f64(&self) -> Option<f64> {
1308 if self.type_info() != PropertyTypeInfo::Double {
1309 return None;
1310 }
1311 let p = self.raw();
1312 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1313 d.read_ne_f64(0)
1314 }
1315
1316 pub fn as_f32_array<const N: usize>(&self) -> Option<[f32; N]> {
1318 if self.type_info() != PropertyTypeInfo::Float {
1319 return None;
1320 }
1321 let p = self.raw();
1322 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1323 d.read_ne_f32_array::<N>(0)
1324 }
1325
1326 pub fn as_f64_array<const N: usize>(&self) -> Option<[f64; N]> {
1328 if self.type_info() != PropertyTypeInfo::Double {
1329 return None;
1330 }
1331 let p = self.raw();
1332 let d = unsafe { MaterialPropertyData::from_sys(p) }?;
1333 d.read_ne_f64_array::<N>(0)
1334 }
1335
1336 pub fn as_vec2(&self) -> Option<Vector2D> {
1338 let [x, y] = self.as_f32_array::<2>()?;
1339 Some(Vector2D::new(x, y))
1340 }
1341
1342 pub fn as_vec3(&self) -> Option<Vector3D> {
1344 let [x, y, z] = self.as_f32_array::<3>()?;
1345 Some(Vector3D::new(x, y, z))
1346 }
1347
1348 pub fn as_vec4(&self) -> Option<Vector4D> {
1350 let [x, y, z, w] = self.as_f32_array::<4>()?;
1351 Some(Vector4D::new(x, y, z, w))
1352 }
1353
1354 pub fn as_color3(&self) -> Option<Color3D> {
1356 self.as_vec3()
1357 }
1358
1359 pub fn as_color4(&self) -> Option<Color4D> {
1361 self.as_vec4()
1362 }
1363
1364 fn data_cast_slice_opt<T>(&self) -> Option<&[T]> {
1365 let p = self.raw();
1366 let len = p.mDataLength as usize;
1367 let size = std::mem::size_of::<T>();
1368 let align = std::mem::align_of::<T>();
1369
1370 if len == 0 {
1371 return Some(&[]);
1372 }
1373 if p.mData.is_null() {
1374 return None;
1375 }
1376
1377 let ptr = p.mData as *const u8;
1378 if size == 0 {
1379 return Some(&[]);
1380 }
1381 if (ptr as usize) % align != 0 || len % size != 0 {
1382 return None;
1383 }
1384 Some(ffi::slice_from_ptr_len(self, ptr as *const T, len / size))
1385 }
1386
1387 fn into_info(self) -> MaterialPropertyInfo {
1388 MaterialPropertyInfo::from_ref(self)
1389 }
1390}
1391
1392pub struct MaterialPropertyIterator {
1394 scene: Scene,
1395 props: Option<SharedPtr<*const sys::aiMaterialProperty>>,
1396 count: usize,
1397 index: usize,
1398}
1399
1400impl Iterator for MaterialPropertyIterator {
1401 type Item = MaterialPropertyRef;
1402
1403 fn next(&mut self) -> Option<Self::Item> {
1404 let props = self.props?;
1405 let slice = crate::ffi::slice_from_ptr_len_opt(&(), props.as_ptr(), self.count)?;
1406 while self.index < slice.len() {
1407 let ptr = slice[self.index];
1408 self.index += 1;
1409 if ptr.is_null() {
1410 continue;
1411 }
1412 return Some(MaterialPropertyRef::from_ptr(self.scene.clone(), ptr));
1413 }
1414 None
1415 }
1416}
1417
1418impl TextureType {
1419 #[inline]
1420 fn to_sys(self) -> sys::aiTextureType {
1421 match self {
1422 Self::Diffuse => sys::aiTextureType::aiTextureType_DIFFUSE,
1423 Self::Specular => sys::aiTextureType::aiTextureType_SPECULAR,
1424 Self::Ambient => sys::aiTextureType::aiTextureType_AMBIENT,
1425 Self::Emissive => sys::aiTextureType::aiTextureType_EMISSIVE,
1426 Self::Height => sys::aiTextureType::aiTextureType_HEIGHT,
1427 Self::Normals => sys::aiTextureType::aiTextureType_NORMALS,
1428 Self::Shininess => sys::aiTextureType::aiTextureType_SHININESS,
1429 Self::Opacity => sys::aiTextureType::aiTextureType_OPACITY,
1430 Self::Displacement => sys::aiTextureType::aiTextureType_DISPLACEMENT,
1431 Self::Lightmap => sys::aiTextureType::aiTextureType_LIGHTMAP,
1432 Self::Reflection => sys::aiTextureType::aiTextureType_REFLECTION,
1433 Self::BaseColor => sys::aiTextureType::aiTextureType_BASE_COLOR,
1434 Self::NormalCamera => sys::aiTextureType::aiTextureType_NORMAL_CAMERA,
1435 Self::EmissionColor => sys::aiTextureType::aiTextureType_EMISSION_COLOR,
1436 Self::Metalness => sys::aiTextureType::aiTextureType_METALNESS,
1437 Self::DiffuseRoughness => sys::aiTextureType::aiTextureType_DIFFUSE_ROUGHNESS,
1438 Self::AmbientOcclusion => sys::aiTextureType::aiTextureType_AMBIENT_OCCLUSION,
1439 Self::Unknown => sys::aiTextureType::aiTextureType_UNKNOWN,
1440 Self::Sheen => sys::aiTextureType::aiTextureType_SHEEN,
1441 Self::Clearcoat => sys::aiTextureType::aiTextureType_CLEARCOAT,
1442 Self::Transmission => sys::aiTextureType::aiTextureType_TRANSMISSION,
1443 Self::MayaBase => sys::aiTextureType::aiTextureType_MAYA_BASE,
1444 Self::MayaSpecular => sys::aiTextureType::aiTextureType_MAYA_SPECULAR,
1445 Self::MayaSpecularColor => sys::aiTextureType::aiTextureType_MAYA_SPECULAR_COLOR,
1446 Self::MayaSpecularRoughness => {
1447 sys::aiTextureType::aiTextureType_MAYA_SPECULAR_ROUGHNESS
1448 }
1449 Self::Anisotropy => sys::aiTextureType::aiTextureType_ANISOTROPY,
1450 Self::GltfMetallicRoughness => {
1451 sys::aiTextureType::aiTextureType_GLTF_METALLIC_ROUGHNESS
1452 }
1453 }
1454 }
1455
1456 pub fn from_u32(v: u32) -> Option<Self> {
1458 Some(match v {
1459 x if x == sys::aiTextureType::aiTextureType_DIFFUSE as u32 => Self::Diffuse,
1460 x if x == sys::aiTextureType::aiTextureType_SPECULAR as u32 => Self::Specular,
1461 x if x == sys::aiTextureType::aiTextureType_AMBIENT as u32 => Self::Ambient,
1462 x if x == sys::aiTextureType::aiTextureType_EMISSIVE as u32 => Self::Emissive,
1463 x if x == sys::aiTextureType::aiTextureType_HEIGHT as u32 => Self::Height,
1464 x if x == sys::aiTextureType::aiTextureType_NORMALS as u32 => Self::Normals,
1465 x if x == sys::aiTextureType::aiTextureType_SHININESS as u32 => Self::Shininess,
1466 x if x == sys::aiTextureType::aiTextureType_OPACITY as u32 => Self::Opacity,
1467 x if x == sys::aiTextureType::aiTextureType_DISPLACEMENT as u32 => Self::Displacement,
1468 x if x == sys::aiTextureType::aiTextureType_LIGHTMAP as u32 => Self::Lightmap,
1469 x if x == sys::aiTextureType::aiTextureType_REFLECTION as u32 => Self::Reflection,
1470 x if x == sys::aiTextureType::aiTextureType_BASE_COLOR as u32 => Self::BaseColor,
1471 x if x == sys::aiTextureType::aiTextureType_NORMAL_CAMERA as u32 => Self::NormalCamera,
1472 x if x == sys::aiTextureType::aiTextureType_EMISSION_COLOR as u32 => {
1473 Self::EmissionColor
1474 }
1475 x if x == sys::aiTextureType::aiTextureType_METALNESS as u32 => Self::Metalness,
1476 x if x == sys::aiTextureType::aiTextureType_DIFFUSE_ROUGHNESS as u32 => {
1477 Self::DiffuseRoughness
1478 }
1479 x if x == sys::aiTextureType::aiTextureType_AMBIENT_OCCLUSION as u32 => {
1480 Self::AmbientOcclusion
1481 }
1482 x if x == sys::aiTextureType::aiTextureType_UNKNOWN as u32 => Self::Unknown,
1483 x if x == sys::aiTextureType::aiTextureType_SHEEN as u32 => Self::Sheen,
1484 x if x == sys::aiTextureType::aiTextureType_CLEARCOAT as u32 => Self::Clearcoat,
1485 x if x == sys::aiTextureType::aiTextureType_TRANSMISSION as u32 => Self::Transmission,
1486 x if x == sys::aiTextureType::aiTextureType_MAYA_BASE as u32 => Self::MayaBase,
1487 x if x == sys::aiTextureType::aiTextureType_MAYA_SPECULAR as u32 => Self::MayaSpecular,
1488 x if x == sys::aiTextureType::aiTextureType_MAYA_SPECULAR_COLOR as u32 => {
1489 Self::MayaSpecularColor
1490 }
1491 x if x == sys::aiTextureType::aiTextureType_MAYA_SPECULAR_ROUGHNESS as u32 => {
1492 Self::MayaSpecularRoughness
1493 }
1494 x if x == sys::aiTextureType::aiTextureType_ANISOTROPY as u32 => Self::Anisotropy,
1495 x if x == sys::aiTextureType::aiTextureType_GLTF_METALLIC_ROUGHNESS as u32 => {
1496 Self::GltfMetallicRoughness
1497 }
1498 _ => return None,
1499 })
1500 }
1501}
1502
1503#[cfg(test)]
1504mod material_property_data_tests {
1505 use super::*;
1506
1507 fn make_prop_with_data(mut data: Vec<u8>) -> (sys::aiMaterialProperty, Vec<u8>) {
1508 let prop = sys::aiMaterialProperty {
1509 mDataLength: data.len() as u32,
1510 mData: data.as_mut_ptr().cast::<std::os::raw::c_char>(),
1511 ..Default::default()
1512 };
1513 (prop, data)
1514 }
1515
1516 #[test]
1517 fn decode_ai_string_clamps_to_payload_and_adds_terminator() {
1518 let mut data = Vec::new();
1519 data.extend_from_slice(&999u32.to_ne_bytes()); data.extend_from_slice(b"abc"); let (prop, _data_owner) = make_prop_with_data(data);
1523 let decoded = unsafe { MaterialPropertyData::from_sys(&prop) }
1524 .and_then(|d| d.decode_ai_string())
1525 .unwrap();
1526
1527 assert_eq!(decoded.length, 3);
1528 let bytes: Vec<u8> = decoded.data[..3].iter().map(|c| *c as u8).collect();
1529 assert_eq!(bytes, b"abc");
1530 assert_eq!(decoded.data[3] as u8, 0);
1531 }
1532
1533 #[test]
1534 fn read_ne_primitives_and_arrays_from_bytes() {
1535 let mut data = Vec::new();
1536 data.extend_from_slice(&42i32.to_ne_bytes());
1537 data.extend_from_slice(&1.0f32.to_ne_bytes());
1538 data.extend_from_slice(&2.0f32.to_ne_bytes());
1539 data.extend_from_slice(&3.5f32.to_ne_bytes());
1540 data.extend_from_slice(&9.0f64.to_ne_bytes());
1541
1542 let (prop, _data_owner) = make_prop_with_data(data);
1543 let d = unsafe { MaterialPropertyData::from_sys(&prop) }.unwrap();
1544
1545 assert_eq!(d.read_ne_i32(0), Some(42));
1546 assert_eq!(d.read_ne_f32_array::<3>(4), Some([1.0, 2.0, 3.5]));
1547 assert_eq!(d.read_ne_f64(4 + 12), Some(9.0));
1548 assert_eq!(d.read_ne_u32(9999), None);
1549 }
1550}
1551
1552#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1554pub enum BlendMode {
1555 Default,
1557 Additive,
1559 Unknown(u32),
1561}
1562
1563impl BlendMode {
1564 fn from_raw(v: u32) -> Self {
1565 match v {
1566 x if x == sys::aiBlendMode::aiBlendMode_Default as u32 => BlendMode::Default,
1567 x if x == sys::aiBlendMode::aiBlendMode_Additive as u32 => BlendMode::Additive,
1568 other => BlendMode::Unknown(other),
1569 }
1570 }
1571}
1572
1573#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1575pub enum PbrWorkflow {
1576 MetallicRoughness,
1578 SpecularGlossiness,
1580 Unknown,
1582}
1583
1584impl Material {
1585 pub fn pbr_workflow(&self) -> PbrWorkflow {
1587 if self.metallic_factor().is_some() || self.roughness_factor().is_some() {
1588 PbrWorkflow::MetallicRoughness
1589 } else if self.glossiness_factor().is_some() || self.specular_factor().is_some() {
1590 PbrWorkflow::SpecularGlossiness
1591 } else {
1592 PbrWorkflow::Unknown
1593 }
1594 }
1595
1596 pub fn base_color_texture(&self, index: usize) -> Option<TextureInfo> {
1599 self.texture(TextureType::BaseColor, index)
1600 }
1601
1602 pub fn metallic_roughness_texture(&self) -> Option<TextureInfo> {
1604 self.texture(TextureType::GltfMetallicRoughness, 0)
1606 }
1607
1608 pub fn emission_texture(&self, index: usize) -> Option<TextureInfo> {
1610 self.texture(TextureType::EmissionColor, index)
1611 }
1612
1613 pub fn normal_texture(&self, index: usize) -> Option<TextureInfo> {
1615 self.texture(TextureType::Normals, index)
1616 }
1617
1618 pub fn sheen_color_texture(&self) -> Option<TextureInfo> {
1620 self.texture(TextureType::Sheen, 0)
1622 }
1623
1624 pub fn sheen_roughness_texture(&self) -> Option<TextureInfo> {
1626 self.texture(TextureType::Sheen, 1)
1628 }
1629
1630 pub fn clearcoat_texture(&self) -> Option<TextureInfo> {
1632 self.texture(TextureType::Clearcoat, 0)
1633 }
1634
1635 pub fn clearcoat_roughness_texture(&self) -> Option<TextureInfo> {
1637 self.texture(TextureType::Clearcoat, 1)
1638 }
1639
1640 pub fn clearcoat_normal_texture(&self) -> Option<TextureInfo> {
1642 self.texture(TextureType::Clearcoat, 2)
1643 }
1644
1645 pub fn transmission_texture(&self) -> Option<TextureInfo> {
1647 self.texture(TextureType::Transmission, 0)
1648 }
1649
1650 pub fn volume_thickness_texture(&self) -> Option<TextureInfo> {
1652 self.texture(TextureType::Transmission, 1)
1654 }
1655
1656 pub fn anisotropy_texture(&self) -> Option<TextureInfo> {
1658 self.texture(TextureType::Anisotropy, 0)
1659 }
1660
1661 pub fn albedo_texture(&self, index: usize) -> Option<TextureInfo> {
1663 self.base_color_texture(index)
1664 }
1665
1666 pub fn metallic_texture(&self, index: usize) -> Option<TextureInfo> {
1668 self.texture(TextureType::Metalness, index)
1669 }
1670
1671 pub fn roughness_texture(&self, index: usize) -> Option<TextureInfo> {
1673 self.texture(TextureType::DiffuseRoughness, index)
1674 }
1675
1676 pub fn ambient_occlusion_texture(&self, index: usize) -> Option<TextureInfo> {
1678 self.texture(TextureType::AmbientOcclusion, index)
1679 }
1680
1681 pub fn lightmap_texture(&self, index: usize) -> Option<TextureInfo> {
1683 self.texture(TextureType::Lightmap, index)
1684 }
1685
1686 pub fn displacement_texture(&self, index: usize) -> Option<TextureInfo> {
1688 self.texture(TextureType::Displacement, index)
1689 }
1690
1691 pub fn reflection_texture(&self, index: usize) -> Option<TextureInfo> {
1693 self.texture(TextureType::Reflection, index)
1694 }
1695
1696 pub fn opacity_texture(&self, index: usize) -> Option<TextureInfo> {
1698 self.texture(TextureType::Opacity, index)
1699 }
1700
1701 pub fn height_texture(&self, index: usize) -> Option<TextureInfo> {
1703 self.texture(TextureType::Height, index)
1704 }
1705
1706 pub fn specular_texture(&self, index: usize) -> Option<TextureInfo> {
1708 self.texture(TextureType::Specular, index)
1709 }
1710
1711 pub fn glossiness_texture(&self, index: usize) -> Option<TextureInfo> {
1713 self.texture(TextureType::Shininess, index)
1714 }
1715
1716 pub fn emissive_texture(&self, index: usize) -> Option<TextureInfo> {
1718 self.texture(TextureType::EmissionColor, index)
1719 }
1720}
1721
1722#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1724pub enum TextureMapping {
1725 UV,
1727 Sphere,
1729 Cylinder,
1731 Box,
1733 Plane,
1735 Other(u32),
1737}
1738
1739impl TextureMapping {
1740 fn from_raw(value: sys::aiTextureMapping) -> Self {
1741 let value_u32 = value as u32;
1742 match value_u32 {
1743 v if v == sys::aiTextureMapping::aiTextureMapping_UV as u32 => Self::UV,
1744 v if v == sys::aiTextureMapping::aiTextureMapping_SPHERE as u32 => Self::Sphere,
1745 v if v == sys::aiTextureMapping::aiTextureMapping_CYLINDER as u32 => Self::Cylinder,
1746 v if v == sys::aiTextureMapping::aiTextureMapping_BOX as u32 => Self::Box,
1747 v if v == sys::aiTextureMapping::aiTextureMapping_PLANE as u32 => Self::Plane,
1748 other => Self::Other(other),
1749 }
1750 }
1751}
1752
1753#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1755pub enum TextureOperation {
1756 Multiply,
1758 Add,
1760 Subtract,
1762 Divide,
1764 SmoothAdd,
1766 SignedAdd,
1768 Other(u32),
1770}
1771
1772impl TextureOperation {
1773 fn from_raw(value: sys::aiTextureOp) -> Self {
1774 let value_u32 = value as u32;
1775 match value_u32 {
1776 v if v == sys::aiTextureOp::aiTextureOp_Multiply as u32 => Self::Multiply,
1777 v if v == sys::aiTextureOp::aiTextureOp_Add as u32 => Self::Add,
1778 v if v == sys::aiTextureOp::aiTextureOp_Subtract as u32 => Self::Subtract,
1779 v if v == sys::aiTextureOp::aiTextureOp_Divide as u32 => Self::Divide,
1780 v if v == sys::aiTextureOp::aiTextureOp_SmoothAdd as u32 => Self::SmoothAdd,
1781 v if v == sys::aiTextureOp::aiTextureOp_SignedAdd as u32 => Self::SignedAdd,
1782 other => Self::Other(other),
1783 }
1784 }
1785}
1786
1787#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1789pub enum TextureMapMode {
1790 Wrap,
1792 Clamp,
1794 Mirror,
1796 Decal,
1798 Other(u32),
1800}
1801
1802impl TextureMapMode {
1803 fn from_raw(value: sys::aiTextureMapMode) -> Self {
1804 let value_u32 = value as u32;
1805 match value_u32 {
1806 v if v == sys::aiTextureMapMode::aiTextureMapMode_Wrap as u32 => Self::Wrap,
1807 v if v == sys::aiTextureMapMode::aiTextureMapMode_Clamp as u32 => Self::Clamp,
1808 v if v == sys::aiTextureMapMode::aiTextureMapMode_Mirror as u32 => Self::Mirror,
1809 v if v == sys::aiTextureMapMode::aiTextureMapMode_Decal as u32 => Self::Decal,
1810 other => Self::Other(other),
1811 }
1812 }
1813}
1814
1815#[derive(Debug, Clone)]
1817pub struct TextureInfoRef {
1818 path: sys::aiString,
1819 pub mapping: TextureMapping,
1821 pub uv_index: u32,
1823 pub blend_factor: f32,
1825 pub operation: TextureOperation,
1827 pub map_modes: [TextureMapMode; 3],
1829 pub flags: TextureFlags,
1831 pub uv_transform: Option<UVTransform>,
1833 pub axis: Option<Vector3D>,
1835}
1836
1837impl TextureInfoRef {
1838 pub fn path_str(&self) -> Cow<'_, str> {
1840 ai_string_to_str(&self.path)
1841 }
1842
1843 pub fn path_bytes(&self) -> &[u8] {
1845 let len = (self.path.length as usize).min(self.path.data.len());
1846 ffi::slice_from_ptr_len(self, self.path.data.as_ptr() as *const u8, len)
1847 }
1848
1849 #[cfg(feature = "raw-sys")]
1851 pub fn path_raw(&self) -> &sys::aiString {
1852 &self.path
1853 }
1854
1855 pub fn into_owned(self) -> TextureInfo {
1857 TextureInfo {
1858 path: ai_string_to_string(&self.path),
1859 mapping: self.mapping,
1860 uv_index: self.uv_index,
1861 blend_factor: self.blend_factor,
1862 operation: self.operation,
1863 map_modes: self.map_modes,
1864 flags: self.flags,
1865 uv_transform: self.uv_transform,
1866 axis: self.axis,
1867 }
1868 }
1869
1870 pub fn to_owned(&self) -> TextureInfo {
1872 self.clone().into_owned()
1873 }
1874}
1875
1876pub struct TextureInfo {
1878 pub path: String,
1880 pub mapping: TextureMapping,
1882 pub uv_index: u32,
1884 pub blend_factor: f32,
1886 pub operation: TextureOperation,
1888 pub map_modes: [TextureMapMode; 3],
1890 pub flags: TextureFlags,
1892 pub uv_transform: Option<UVTransform>,
1894 pub axis: Option<Vector3D>,
1896}
1897
1898#[derive(Debug, Clone, Copy)]
1900pub struct UVTransform {
1901 pub translation: Vector2D,
1903 pub scaling: Vector2D,
1905 pub rotation: f32,
1907}
1908
1909bitflags::bitflags! {
1910 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1912 pub struct TextureFlags: u32 {
1913 const INVERT = sys::aiTextureFlags::aiTextureFlags_Invert as u32;
1915 const USE_ALPHA = sys::aiTextureFlags::aiTextureFlags_UseAlpha as u32;
1917 const IGNORE_ALPHA = sys::aiTextureFlags::aiTextureFlags_IgnoreAlpha as u32;
1919 }
1920}
1921
1922