Search code examples
javascriptnode.jstypescriptgltf

Trying to get only the animations from a GLTF file


I would like to obtain from every kind of gltfAsset only the animations in it to create an animation file. Someone know if a library can help me or if there is a easiest way to do it ?

private async createAnimationAssets(props: Props, animations: Animation[]): Promise<CreateMetaAssetResponseDto[]> {
    const animationAssets = await Promise.all(
      animations.map(async (animation) => {
        return new Promise(async (resolve, reject) => {
          try {
            const asset = await this.assetRepository.create(
              new MetaBuilderAssetListItem({
                createdAt: this.dateService.now(),
                modifiedAt: this.dateService.now(),
                id: undefined,
                projectId: props.projectId,
                name: animation.name,
                type: "animation",
                exclude: false,
              })
            );
            if (!asset.id) return reject();
            const assetData: MetaAsset = MetaBuilderAssetMapper.defaultAsset({
              ...props,
              name: animation.name || "",
              assetId: asset.id,
              source: false,
              type: "animation",
            });
            if (props.parent) await this.insertParentPath(assetData, props.parent);
            // créer le fichier glb ui ne contient que l'animation
            const gltfAsset = fsExtra.readFileSync(`temp/${assetData.file?.filename}`);

            // const gltfAnimations = 

            gltfPipeline.gltfToGlb(gltfAnimations).then(function (results: any) {
              fsExtra.writeFileSync(`${animation.name}.glb`, results.glb);
            });
            console.log(`gltfAsset: ${gltfAsset}`);
            // lire les meta-données de mon fichier
            assetData.file = {
              filename: animation.name,
              size: 0,
              hash: assetData.file?.hash,
            };
            // console.log(assetData);
            await this.sharedbSocket.insertDoc(`assets`, asset.id, assetData);
            const response: CreateMetaAssetResponseDto = {
              ...assetData,
              id: +asset.id,
              uniqueId: +asset.id,
              createdAt: asset.createdAt,
              modifiedAt: asset.modifiedAt,
            };
            console.log(response);
            this.sharedbSocket.emitAssetCreated(response);
            return resolve(response);
          } catch (err) {
            console.error(err);
            return reject(err);
          }
        });
      })
    );
    return animationAssets.filter((asset) => asset) as CreateMetaAssetResponseDto[];
  }

if you need any additional information to help me do not hesitate to say me !


Solution

  • I'd suggest using glTF Transform to extract data from, or modify, glTF files in a Node.js environment.

    Start by reading the file with NodeIO:

    import { NodeIO } from '@gltf-transform/core';
    import { KHRONOS_EXTENSIONS } from '@gltf-transform/extensions';
    
    const io = new NodeIO().registerExtensions( KHRONOS_EXTENSIONS );
    
    // (a) read from disk
    const document = await io.read('path/to/scene.glb');
    
    // (b) read from memory
    const document = await io.readBinary(glb); // glb is Uint8Array or Buffer
    

    Next we'll make type definitions to hold the new animation data, if you're using TypeScript.

    interface Clip {
        name: string,
        tracks: Track[],
    }
    
    interface Track {
        targetObject: string,
        targetProperty: string, // 'translation', 'rotation', 'scale', 'weights'
        interpolation: string, // 'LINEAR', 'CUBICSPLINE', 'STEP'
        times: number[],
        values: number[]
    }
    

    Then, parse the animation data from the glTF Animations, into these structures:

    const clips = [] as Clip[];
    
    for (const animation of document.getRoot().listAnimations()) {
        const tracks = [];
    
        for (const channel of animation.listChannels()) {
            const sampler = channel.getSampler();
            tracks.push({
                targetObject: channel.getTargetNode().getName(),
                targetProperty: channel.getTargetPath(),
                interpolation: sampler.getInterpolation(),
                times: Array.from(sampler.getInput().getArray()),
                values: Array.from(sampler.getOutput().getArray())
            } as Track)
        }
    
        clips.push({ name: animation.getName(), tracks: tracks } as Clip);
    }
    
    console.log(clips);
    

    You can do whatever you want with that clips data - it represents the raw keyframes, with each track identifying the object it affects by name. This does require that your glTF file have unique names for animated objects.

    If you'd rather not write raw data, but just have a new glTF file containing nothing but the animation, you can also do that instead:

    import { prune } from '@gltf-transform/functions';
    
    // remove all mesh data
    for (const mesh of document.getRoot().listMeshes()) {
        for (const prim of mesh.listPrimitives()) {
            prim.dispose();
        }
    }
    
    // clean up unused materials, textures, ...
    await document.transform(prune());
    
    // (a) write to disk
    await io.write('path/to/output.glb', document);
    
    // (b) write to bytes
    const bytes = await io.writeBinary(document);