Skip to main content

fyrox_impl/resource/gltf/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! [GltfLoader] enables the importing of *.gltf and *.glb files in the glTF format.
22//! This requires the "gltf" feature.
23use 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
284/// This object performs the loading of files in glTF format with extension "gltf" or "glb".
285pub struct GltfLoader {
286    /// ResourceManager is needed so that textures and mesh data can be loaded from additional resources.
287    /// The glTF format allows for other assets to be referenced by file path.
288    pub resource_manager: ResourceManager,
289    /// Import options control where this loader should search for additional resources.
290    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            // Our first bone. Set the inv_bind_pose of the main node.
627            // We only create children if we later find an inv_bind_pose that does not match this.
628            let new_node = import_node(node, pair.bone.inv_bind_pose, imports)?;
629            new_handle = Some(graph.add_node(new_node));
630            // Record that we have seen this inv_bind_pose.
631            bones.push(pair);
632        } else if let Some(p) = bones.iter().find(|p| p.bone == pair.bone) {
633            // We have a previously existing bone with the exact same inv_bind_pose.
634            // Do not record having seen this inv_bind_pose.
635            // Let the already recorded inv_bind_pose stand in for all bones with this inv_bind_pose.
636            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                // We have a child for the skin of the previously existing bone. Re-use that child for this skin.
642                bone_children.push(SkinNodePair {
643                    skin_index: pair.skin_index,
644                    node: prev_pair.node,
645                });
646            }
647            // Otherwise, the previously existing bone must be using the main node, so do nothing.
648        } else {
649            // We have a never-seen-before inv_bind_pose, so create a child for that inv_bind_pose.
650            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            // Record that we have seen this inv_bind_pose.
661            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}