1use crate::asset::io::ResourceIo;
24use crate::asset::loader;
25use crate::asset::manager::ResourceManager;
26use crate::asset::options;
27use crate::asset::state::LoadError;
28use crate::core::algebra::{Matrix4, Unit};
29use crate::core::log::Log;
30use crate::core::pool::Handle;
31use crate::core::TypeUuidProvider;
32use crate::graph::NodeMapping;
33use crate::graph::SceneGraph;
34use crate::gui::core::io::FileError;
35use crate::material::MaterialResource;
36use crate::resource::model::{MaterialSearchOptions, Model, ModelImportOptions};
37use crate::resource::texture::{TextureError, TextureResource};
38use crate::scene::animation::{AnimationContainer, AnimationPlayerBuilder};
39use crate::scene::base::BaseBuilder;
40use crate::scene::graph::Graph;
41use crate::scene::mesh::surface::{BlendShape, Surface, SurfaceResource};
42use crate::scene::mesh::{Mesh, MeshBuilder};
43use crate::scene::node::Node;
44use crate::scene::pivot::PivotBuilder;
45use crate::scene::transform::TransformBuilder;
46use crate::scene::Scene;
47use gltf::json;
48use gltf::Document;
49use gltf::Gltf;
50use std::fmt::Display;
51use std::path::{Path, PathBuf};
52use std::sync::Arc;
53use uuid::Uuid;
54
55mod animation;
56mod iter;
57pub mod material;
58mod node_names;
59mod simplify;
60mod surface;
61mod uri;
62
63use animation::import_animations;
64use fyrox_resource::untyped::ResourceKind;
65use material::*;
66pub use surface::SurfaceDataError;
67use surface::{build_surface_data, BlendShapeInfoContainer, GeometryStatistics};
68pub use uri::{parse_uri, Scheme, Uri};
69
70type Result<T> = std::result::Result<T, GltfLoadError>;
71
72const TARGET_NAMES_KEY: &str = "targetNames";
73
74#[derive(Debug)]
75#[allow(dead_code)]
76enum GltfLoadError {
77 InvalidIndex,
78 InvalidPath,
79 UnsupportedURI(Box<str>),
80 MissingEmbeddedBin,
81 Gltf(gltf::Error),
82 Texture(TextureError),
83 File(FileError),
84 Base64(base64::DecodeError),
85 Load(LoadError),
86 Material(GltfMaterialError),
87 Surface(SurfaceDataError),
88 JSON(json::Error),
89}
90
91impl std::error::Error for GltfLoadError {}
92
93impl Display for GltfLoadError {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 match self {
96 GltfLoadError::InvalidIndex => f.write_str("Invalid index"),
97 GltfLoadError::InvalidPath => f.write_str("Invalid path"),
98 GltfLoadError::UnsupportedURI(uri) => write!(f, "Unsupported URL {uri:?}"),
99 GltfLoadError::MissingEmbeddedBin => f.write_str("Missing embedded bin"),
100 GltfLoadError::Gltf(error) => Display::fmt(error, f),
101 GltfLoadError::Texture(error) => Display::fmt(error, f),
102 GltfLoadError::File(error) => Display::fmt(error, f),
103 GltfLoadError::Base64(error) => Display::fmt(error, f),
104 GltfLoadError::Load(error) => Display::fmt(error, f),
105 GltfLoadError::Material(error) => Display::fmt(error, f),
106 GltfLoadError::Surface(error) => Display::fmt(error, f),
107 GltfLoadError::JSON(error) => Display::fmt(error, f),
108 }
109 }
110}
111
112impl From<json::Error> for GltfLoadError {
113 fn from(error: json::Error) -> Self {
114 GltfLoadError::JSON(error)
115 }
116}
117
118impl From<gltf::Error> for GltfLoadError {
119 fn from(error: gltf::Error) -> Self {
120 GltfLoadError::Gltf(error)
121 }
122}
123
124impl From<TextureError> for GltfLoadError {
125 fn from(error: TextureError) -> Self {
126 GltfLoadError::Texture(error)
127 }
128}
129
130impl From<FileError> for GltfLoadError {
131 fn from(error: FileError) -> Self {
132 GltfLoadError::File(error)
133 }
134}
135
136impl From<LoadError> for GltfLoadError {
137 fn from(error: LoadError) -> Self {
138 GltfLoadError::Load(error)
139 }
140}
141
142impl From<base64::DecodeError> for GltfLoadError {
143 fn from(error: base64::DecodeError) -> Self {
144 GltfLoadError::Base64(error)
145 }
146}
147
148impl From<GltfMaterialError> for GltfLoadError {
149 fn from(error: GltfMaterialError) -> Self {
150 GltfLoadError::Material(error)
151 }
152}
153
154impl From<SurfaceDataError> for GltfLoadError {
155 fn from(error: SurfaceDataError) -> Self {
156 GltfLoadError::Surface(error)
157 }
158}
159
160fn decode_base64(source: &str) -> Result<Vec<u8>> {
161 Ok(uri::decode_base64(source)?)
162}
163
164struct MeshData {
165 surfaces: Vec<Surface>,
166 blend_shapes: Vec<BlendShape>,
167}
168
169struct NodeFamily {
170 main_node: Handle<Node>,
171 bone_children: Vec<SkinNodePair>,
172}
173
174struct SkinNodePair {
175 skin_index: usize,
176 node: Handle<Node>,
177}
178
179type SkinData = Vec<SkinBone>;
180
181#[derive(PartialEq, Debug, Clone)]
182struct SkinBone {
183 pub node_index: usize,
184 pub inv_bind_pose: Matrix4<f32>,
185}
186
187impl From<(usize, Matrix4<f32>)> for SkinBone {
188 fn from(pair: (usize, Matrix4<f32>)) -> Self {
189 let (node_index, inv_bind_pose) = pair;
190 SkinBone {
191 node_index,
192 inv_bind_pose,
193 }
194 }
195}
196
197#[derive(Clone, Copy)]
198struct SkinBonePair<'a> {
199 skin_index: usize,
200 bone: &'a SkinBone,
201}
202
203struct SkinBoneIter<'a> {
204 skin_index: usize,
205 bone_index: usize,
206 skin_list: &'a [SkinData],
207}
208
209impl<'a> SkinBoneIter<'a> {
210 fn new(skin_list: &'a [SkinData]) -> SkinBoneIter<'a> {
211 SkinBoneIter {
212 skin_index: 0,
213 bone_index: 0,
214 skin_list,
215 }
216 }
217}
218
219impl<'a> Iterator for SkinBoneIter<'a> {
220 type Item = SkinBonePair<'a>;
221
222 fn next(&mut self) -> Option<Self::Item> {
223 loop {
224 if self.skin_index >= self.skin_list.len() {
225 return None;
226 }
227 let skin: &SkinData = &self.skin_list[self.skin_index];
228 if self.bone_index >= skin.len() {
229 self.bone_index = 0;
230 self.skin_index += 1;
231 } else {
232 let bone = &skin[self.bone_index];
233 self.bone_index += 1;
234 return Some(SkinBonePair {
235 skin_index: self.skin_index,
236 bone,
237 });
238 }
239 }
240 }
241}
242
243struct ImportContext {
244 io: Arc<dyn ResourceIo>,
245 resource_manager: ResourceManager,
246 model_path: PathBuf,
247 search_options: MaterialSearchOptions,
248}
249
250impl ImportContext {
251 fn as_texture_context(&self) -> TextureContext {
252 TextureContext {
253 resource_manager: &self.resource_manager,
254 model_path: &self.model_path,
255 search_options: &self.search_options,
256 }
257 }
258}
259
260#[derive(Default)]
261struct ImportResults {
262 buffers: Option<Vec<Vec<u8>>>,
263 textures: Option<Vec<TextureResource>>,
264 materials: Option<Vec<MaterialResource>>,
265 skins: Option<Vec<SkinData>>,
266 meshes: Option<Vec<MeshData>>,
267 families: Option<Vec<NodeFamily>>,
268}
269
270impl ImportResults {
271 fn get_buffer_data_access<'s>(
272 &'s self,
273 ) -> impl Clone + Fn(gltf::Buffer<'_>) -> Option<&'s [u8]> {
274 |b| {
275 self.buffers
276 .as_ref()
277 .unwrap()
278 .get(b.index())
279 .map(Vec::as_slice)
280 }
281 }
282}
283
284pub struct GltfLoader {
286 pub resource_manager: ResourceManager,
289 pub default_import_options: ModelImportOptions,
291}
292
293impl loader::ResourceLoader for GltfLoader {
294 fn extensions(&self) -> &[&str] {
295 &["gltf", "glb"]
296 }
297
298 fn data_type_uuid(&self) -> crate::core::type_traits::prelude::Uuid {
299 Model::type_uuid()
300 }
301
302 fn load(&self, path: PathBuf, io: Arc<dyn ResourceIo>) -> loader::BoxedLoaderFuture {
303 let resource_manager = self.resource_manager.clone();
304 let default_import_options = self.default_import_options.clone();
305
306 Box::pin(async move {
307 let import_options = options::try_get_import_settings(&path, io.as_ref())
308 .await
309 .unwrap_or(default_import_options);
310
311 let model = load(path, io, resource_manager, import_options)
312 .await
313 .map_err(LoadError::new)?;
314
315 Ok(loader::LoaderPayload::new(model))
316 })
317 }
318
319 fn try_load_import_settings(
320 &self,
321 resource_path: PathBuf,
322 io: Arc<dyn ResourceIo>,
323 ) -> loader::BoxedImportOptionsLoaderFuture {
324 Box::pin(async move {
325 options::try_get_import_settings_opaque::<ModelImportOptions>(&resource_path, &*io)
326 .await
327 })
328 }
329
330 fn default_import_options(&self) -> Option<Box<dyn options::BaseImportOptions>> {
331 Some(Box::<ModelImportOptions>::default())
332 }
333}
334
335async fn load(
336 path: PathBuf,
337 io: Arc<dyn ResourceIo>,
338 resource_manager: ResourceManager,
339 options: ModelImportOptions,
340) -> Result<Model> {
341 let mut scene = Scene::new();
342 let context = ImportContext {
343 io,
344 resource_manager,
345 model_path: path.clone(),
346 search_options: options.material_search_options,
347 };
348 let root_name = path
349 .file_name()
350 .ok_or(GltfLoadError::InvalidPath)?
351 .to_string_lossy();
352 let root = scene.graph.get_root();
353 scene.graph[root].set_name(root_name.clone());
354 import_from_path(&mut scene.graph, &context).await?;
355 node_names::resolve_name_conflicts(context.model_path.as_path(), &mut scene.graph);
356 Ok(Model::new(NodeMapping::UseNames, scene))
357}
358
359async fn import_from_path(graph: &mut Graph, context: &ImportContext) -> Result<()> {
360 let file: Vec<u8> = context.io.load_file(context.model_path.as_path()).await?;
361 import_from_slice(file.as_slice(), graph, context).await
362}
363
364async fn import_from_slice(slice: &[u8], graph: &mut Graph, context: &ImportContext) -> Result<()> {
365 let gltf: Gltf = Gltf::from_slice(slice)?;
366 let doc = gltf.document;
367 let data = gltf.blob;
368 let mut imports: ImportResults = ImportResults {
369 buffers: Some(import_buffers(&doc, data, context).await?),
370 ..Default::default()
371 };
372 let buffers: &[Vec<u8>] = imports.buffers.as_ref().unwrap().as_slice();
373 let images: Vec<SourceImage> = import_images(&doc, buffers)?;
374 imports.textures =
375 Some(import_textures(&doc, images.as_slice(), context.as_texture_context()).await?);
376 let textures = imports.textures.as_ref().unwrap().as_slice();
377 imports.materials = Some(import_materials(&doc, textures).await?);
378 let materials = imports.materials.as_ref().unwrap().as_slice();
379 imports.skins = Some(import_skins(&doc, &imports)?);
380 imports.meshes = Some(import_meshes(
381 &doc,
382 &context.model_path,
383 materials,
384 buffers,
385 )?);
386 imports.families = Some(import_nodes(&doc, graph, &imports)?);
387 link_child_nodes(&doc, graph, &imports)?;
388 let node_handles: Vec<Handle<Node>> = imports
389 .families
390 .as_ref()
391 .unwrap()
392 .iter()
393 .map(|f| f.main_node)
394 .collect();
395 let animations = import_animations(&doc, &node_handles, graph, buffers);
396 if !animations.is_empty() {
397 let mut anim_con = AnimationContainer::new();
398 for animation in animations {
399 anim_con.add(animation);
400 }
401 AnimationPlayerBuilder::new(BaseBuilder::new().with_name("AnimationPlayer"))
402 .with_animations(anim_con)
403 .build(graph);
404 }
405 Ok(())
406}
407
408async fn import_buffers(
409 gltf: &Document,
410 mut data_chunk: Option<Vec<u8>>,
411 context: &ImportContext,
412) -> Result<Vec<Vec<u8>>> {
413 let mut result: Vec<Vec<u8>> = Vec::with_capacity(gltf.buffers().len());
414 for buf in gltf.buffers() {
415 match buf.source() {
416 gltf::buffer::Source::Bin => match data_chunk.take() {
417 Some(data) => result.push(data),
418 None => {
419 return Err(GltfLoadError::MissingEmbeddedBin);
420 }
421 },
422 gltf::buffer::Source::Uri(uri) => result.push(load_bin_from_uri(uri, context).await?),
423 }
424 }
425 Ok(result)
426}
427
428async fn load_bin_from_uri(uri: &str, context: &ImportContext) -> Result<Vec<u8>> {
429 let parsed_uri = uri::parse_uri(uri);
430 match parsed_uri.scheme {
431 uri::Scheme::Data if parsed_uri.data.is_some() => {
432 Ok(decode_base64(parsed_uri.data.unwrap())?)
433 }
434 uri::Scheme::None => load_external_bin(uri, context).await,
435 _ => Err(GltfLoadError::UnsupportedURI(uri.into())),
436 }
437}
438
439async fn load_external_bin(path: &str, context: &ImportContext) -> Result<Vec<u8>> {
440 let parent = context
441 .model_path
442 .parent()
443 .ok_or(GltfLoadError::InvalidPath)?
444 .to_owned();
445 let path = parent.join(path);
446 Ok(context.io.load_file(&path).await?)
447}
448
449fn import_meshes(
450 gltf: &Document,
451 path: &Path,
452 mats: &[MaterialResource],
453 bufs: &[Vec<u8>],
454) -> Result<Vec<MeshData>> {
455 let mut result: Vec<MeshData> = Vec::with_capacity(gltf.nodes().len());
456 let mut stats = GeometryStatistics::default();
457 for node in gltf.nodes() {
458 if let Some(mesh) = node.mesh() {
459 result.push(import_mesh(mesh, mats, bufs, &mut stats)?);
460 }
461 }
462 if cfg!(feature = "mesh_analysis") {
463 if stats.repeated_index_count > 0 {
464 Log::err(format!(
465 "{}: Model has triangles with repeated vertices: {}",
466 path.to_string_lossy(),
467 stats.repeated_index_count
468 ));
469 }
470 let min_length = stats.min_edge_length();
471 if min_length == 0.0 {
472 Log::err(format!(
473 "{}: Mesh has a triangle with a zero-length edge!",
474 path.to_string_lossy()
475 ));
476 } else if min_length <= f32::EPSILON {
477 Log::err(format!(
478 "{}: Mesh has a triangle with edge length: {}",
479 path.to_string_lossy(),
480 min_length
481 ));
482 } else {
483 Log::info(format!(
484 "{}: Smallest triangle edge: {}",
485 path.to_string_lossy(),
486 min_length
487 ));
488 }
489 }
490 Ok(result)
491}
492
493fn import_mesh(
494 mesh: gltf::Mesh,
495 mats: &[MaterialResource],
496 bufs: &[Vec<u8>],
497 stats: &mut GeometryStatistics,
498) -> Result<MeshData> {
499 let morph_info = import_morph_info(&mesh)?;
500 let mut surfs: Vec<Surface> = Vec::with_capacity(mesh.primitives().len());
501 let mut blend_shapes: Option<Vec<BlendShape>> = None;
502 for prim in mesh.primitives() {
503 if let Some((surf, shapes)) = import_surface(prim, &morph_info, mats, bufs, stats)? {
504 surfs.push(surf);
505 blend_shapes.get_or_insert(shapes);
506 }
507 }
508 Ok(MeshData {
509 surfaces: surfs,
510 blend_shapes: blend_shapes.unwrap_or_default(),
511 })
512}
513
514fn import_morph_info(mesh: &gltf::Mesh) -> Result<BlendShapeInfoContainer> {
515 let weights: &[f32] = mesh.weights().unwrap_or_default();
516 let weights: Vec<f32> = weights.iter().map(|w| w * 100.0).collect();
517 let extras = mesh.extras();
518 let names = if let Some(extras) = extras {
519 let extras: json::Value = json::deserialize::from_str(extras.get())?;
520 match extras {
521 json::Value::Object(map) => {
522 if let Some(names) = map.get(TARGET_NAMES_KEY) {
523 match names {
524 json::Value::Array(names) => {
525 values_to_strings(names.as_slice()).unwrap_or_default()
526 }
527 _ => Vec::default(),
528 }
529 } else {
530 Vec::default()
531 }
532 }
533 _ => Vec::default(),
534 }
535 } else {
536 Vec::default()
537 };
538 if extras.is_some() && names.is_empty() {
539 Log::warn(format!(
540 "glTF: Unable to extract blend shape names from JSON: {}",
541 extras.as_ref().unwrap().get()
542 ));
543 }
544 Ok(BlendShapeInfoContainer::new(names, weights))
545}
546
547fn values_to_strings(values: &[json::Value]) -> Option<Vec<String>> {
548 let mut result: Vec<String> = Vec::with_capacity(values.len());
549 for v in values {
550 if let json::Value::String(str) = v {
551 result.push(str.clone());
552 } else {
553 return None;
554 }
555 }
556 Some(result)
557}
558
559fn import_surface(
560 prim: gltf::Primitive,
561 morph_info: &BlendShapeInfoContainer,
562 mats: &[MaterialResource],
563 bufs: &[Vec<u8>],
564 stats: &mut GeometryStatistics,
565) -> Result<Option<(Surface, Vec<BlendShape>)>> {
566 if let Some(data) = build_surface_data(&prim, morph_info, bufs, stats)? {
567 let mut blend_shapes = Vec::new();
568 if let Some(shape_con) = data.blend_shapes_container.as_ref() {
569 blend_shapes.clone_from(&shape_con.blend_shapes)
570 }
571 let mut surf = Surface::new(SurfaceResource::new_ok(
572 Uuid::new_v4(),
573 ResourceKind::External,
574 data,
575 ));
576 if let Some(mat_index) = prim.material().index() {
577 surf.set_material(
578 mats.get(mat_index)
579 .ok_or(GltfLoadError::InvalidIndex)?
580 .clone(),
581 );
582 Ok(Some((surf, blend_shapes)))
583 } else {
584 Ok(None)
585 }
586 } else {
587 Ok(None)
588 }
589}
590
591fn import_nodes(
592 doc: &gltf::Document,
593 graph: &mut Graph,
594 imports: &ImportResults,
595) -> Result<Vec<NodeFamily>> {
596 let skins: &[SkinData] = imports.skins.as_ref().unwrap().as_slice();
597 let mut result: Vec<NodeFamily> = Vec::with_capacity(doc.nodes().len());
598 for node in doc.nodes() {
599 result.push(build_node_family(&node, skins, graph, imports)?);
600 }
601 for node in doc.nodes() {
602 let family: &NodeFamily = result
603 .get(node.index())
604 .ok_or(GltfLoadError::InvalidIndex)?;
605 if let Some(mesh) = graph[family.main_node].cast_mut::<Mesh>() {
606 assign_bones_to_surfaces(node, mesh, result.as_slice())?;
607 }
608 }
609 Ok(result)
610}
611
612fn build_node_family(
613 node: &gltf::Node,
614 skins: &[SkinData],
615 graph: &mut Graph,
616 imports: &ImportResults,
617) -> Result<NodeFamily> {
618 let node_index = node.index();
619 let skin_iter = SkinBoneIter::new(skins).filter(move |sb| sb.bone.node_index == node_index);
620 let mut bones: Vec<SkinBonePair> = Vec::new();
621 let mut new_handle: Option<Handle<Node>> = None;
622 let mut bone_children: Vec<SkinNodePair> = Vec::new();
623 let name = node.name().unwrap_or("");
624 for pair in skin_iter {
625 if bones.is_empty() {
626 let new_node = import_node(node, pair.bone.inv_bind_pose, imports)?;
629 new_handle = Some(graph.add_node(new_node));
630 bones.push(pair);
632 } else if let Some(p) = bones.iter().find(|p| p.bone == pair.bone) {
633 let prev_skin_index = p.skin_index;
637 if let Some(prev_pair) = bone_children
638 .iter()
639 .find(|b| b.skin_index == prev_skin_index)
640 {
641 bone_children.push(SkinNodePair {
643 skin_index: pair.skin_index,
644 node: prev_pair.node,
645 });
646 }
647 } else {
649 let skin_index = pair.skin_index;
651 let base_builder = BaseBuilder::new()
652 .with_name(format!("{name}:{skin_index}"))
653 .with_inv_bind_pose_transform(pair.bone.inv_bind_pose);
654 let handle: Handle<Node> = graph.add_node(PivotBuilder::new(base_builder).build_node());
655 bone_children.push(SkinNodePair {
656 skin_index,
657 node: handle,
658 });
659 graph.link_nodes(handle, new_handle.unwrap());
660 bones.push(pair);
662 }
663 }
664 if let Some(handle) = new_handle {
665 Ok(NodeFamily {
666 main_node: handle,
667 bone_children,
668 })
669 } else {
670 Ok(NodeFamily {
671 main_node: graph.add_node(import_node(node, Matrix4::identity(), imports)?),
672 bone_children: Vec::new(),
673 })
674 }
675}
676
677fn assign_bones_to_surfaces(
678 node: gltf::Node,
679 mesh: &mut Mesh,
680 families: &[NodeFamily],
681) -> Result<()> {
682 if let Some(skin) = node.skin() {
683 let skin_index = skin.index();
684 let mut bones: Vec<Handle<Node>> = Vec::with_capacity(skin.joints().len());
685 for joint in skin.joints() {
686 let joint_family = families
687 .get(joint.index())
688 .ok_or(GltfLoadError::InvalidIndex)?;
689 let bone_children = &joint_family.bone_children;
690 let handle =
691 if let Some(pair) = bone_children.iter().find(|p| p.skin_index == skin_index) {
692 pair.node
693 } else {
694 joint_family.main_node
695 };
696 bones.push(handle);
697 }
698 for surf in mesh.surfaces_mut() {
699 surf.bones.set_value_and_mark_modified(bones.clone());
700 }
701 }
702 Ok(())
703}
704
705fn import_node(
706 node: &gltf::Node,
707 inv_bind_pose: Matrix4<f32>,
708 imports: &ImportResults,
709) -> Result<Node> {
710 let meshes: &[MeshData] = imports.meshes.as_ref().unwrap().as_slice();
711 let trans = node.transform().decomposed();
712 let trans_builder: TransformBuilder = TransformBuilder::new()
713 .with_local_position(trans.0.into())
714 .with_local_rotation(Unit::new_normalize(trans.1.into()))
715 .with_local_scale(trans.2.into());
716 let name = node.name().unwrap_or("");
717 let base_builder = BaseBuilder::new()
718 .with_name(name)
719 .with_local_transform(trans_builder.build())
720 .with_inv_bind_pose_transform(inv_bind_pose);
721 if let Some(mesh) = node.mesh() {
722 let mut mesh_builder = MeshBuilder::new(base_builder);
723 let mesh = meshes
724 .get(mesh.index())
725 .ok_or(GltfLoadError::InvalidIndex)?;
726 mesh_builder = mesh_builder.with_blend_shapes(mesh.blend_shapes.clone());
727 mesh_builder = mesh_builder.with_surfaces(mesh.surfaces.clone());
728 Ok(mesh_builder.build_node())
729 } else {
730 Ok(PivotBuilder::new(base_builder).build_node())
731 }
732}
733
734fn link_child_nodes(doc: &Document, graph: &mut Graph, imports: &ImportResults) -> Result<()> {
735 let families: &[NodeFamily] = imports.families.as_ref().unwrap().as_slice();
736 for node in doc.nodes() {
737 let parent_family = families
738 .get(node.index())
739 .ok_or(GltfLoadError::InvalidIndex)?;
740 for child in node.children() {
741 let child_family = families
742 .get(child.index())
743 .ok_or(GltfLoadError::InvalidIndex)?;
744 graph.link_nodes(child_family.main_node, parent_family.main_node);
745 }
746 }
747 Ok(())
748}
749
750fn import_skins(doc: &gltf::Document, imports: &ImportResults) -> Result<Vec<SkinData>> {
751 let mut result: Vec<SkinData> = Vec::with_capacity(doc.skins().len());
752 for skin in doc.skins() {
753 let bone_node_indices = skin.joints().map(|n| n.index());
754 let bone_pairs = {
755 let reader = skin.reader(imports.get_buffer_data_access());
756 if let Some(iter) = reader.read_inverse_bind_matrices() {
757 bone_node_indices
758 .zip(iter.map(Matrix4::from))
759 .map(SkinBone::from)
760 .collect()
761 } else {
762 bone_node_indices
763 .zip(std::iter::repeat(Matrix4::identity()))
764 .map(SkinBone::from)
765 .collect()
766 }
767 };
768 result.push(bone_pairs);
769 }
770 Ok(result)
771}