GLTF Export ignores binary option and does not include animations

Hello All,

I’m confused… it seems the following code should export a glb file w/animation, however it exports a gltf file without the animations - ignoring the binary = true parameter. This suggests to me that either (a) I’m doing it wrong, or (b) it might be another edge case for the exporter. I’m hoping for (a) :wink:

Any help would be most appreciated!

<!DOCTYPE html>
<html lang="en">

<head>
    <title>three.js webgl - exporter - gltf</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
</head>

<body>
    <div id="info">
        <button id="export_scene">Export Scene</button>
    </div>

    <script type="importmap">
        {
            "imports": {
                "three": "./three.module.js",
                "three/addons/": "./jsm/"
            }
        }
    </script>

    <script type="module">

        import * as THREE from './three.module.js';
        import { GLTFExporter } from './GLTFExporter.js';

        function exportGLTF(input, animationClip) {
            const gltfExporter = new GLTFExporter();

            const options = {
                binary: true,
                maxTextureSize: 4096,
                animations: [animationClip],
                includeCustomExtensions: true
            };

            console.log(options);
            gltfExporter.parse(input, 
            function (result) {

                if (result instanceof ArrayBuffer) {

                    saveArrayBuffer(result, 'scene.glb');

                } else {

                    const output = JSON.stringify(result, null, 2);
                    console.log(output);
                    saveString(output, 'scene.gltf');

                }

            }, options);

        }

        document.getElementById('export_scene').addEventListener('click', function () {
            exportGLTF(scene, clip);
        });

        const link = document.createElement('a');
        link.style.display = 'none';
        document.body.appendChild(link); // Firefox workaround, see #6594

        function save(blob, filename) {
            link.href = URL.createObjectURL(blob);
            link.download = filename;
            link.click();
        }

        function saveString(text, filename) {
            save(new Blob([text], { type: 'text/plain' }), filename);
        }

        function saveArrayBuffer(buffer, filename) {
            save(new Blob([buffer], { type: 'application/octet-stream' }), filename);
        }

        let clock;
        let camera, geometry, scene, renderer, mixer, clip;
        let gridHelper, sphere, smallSphere;

        init();
        animate();

        function init() {
            scene = new THREE.Scene();
            scene.name = 'scene';

            // Perspective Camera
            camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 2000);
            camera.position.set(10, 300, 0);

            camera.name = "PerspectiveCamera";
            scene.add(camera);


            // DirectLight
            const dirLight = new THREE.DirectionalLight(0xffffff, 1);
            dirLight.target.position.set(0, 0, - 1);
            dirLight.add(dirLight.target);
            dirLight.lookAt(- 1, - 1, 0);
            dirLight.name = 'DirectionalLight';
            scene.add(dirLight);


            const material2 = new THREE.MeshBasicMaterial({ color: 0xaaffff });
            smallSphere = new THREE.Mesh(new THREE.SphereGeometry(20, 10, 10), material2);
            smallSphere.position.set(80, 0, 0);
            smallSphere.name = "SmallSphere";
            scene.add(smallSphere);

            // POSITION
            const positionKF = new THREE.VectorKeyframeTrack('SmallSphere.position', [0, 1, 2], [0,0,0,100,100,100,0,0,0]);


            // Clip
            clip = new THREE.AnimationClip('SmallSphereMove', 2, [positionKF]);

            // Mixer
            mixer = new THREE.AnimationMixer(smallSphere);
            const clipAction = mixer.clipAction(clip);
            clipAction.play();

            // Clock
            clock = new THREE.Clock();

            // Renderer
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            document.body.appendChild(renderer.domElement);

            window.addEventListener('resize', onWindowResize);
        }

        function onWindowResize() {
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function animate() {
            requestAnimationFrame(animate);
            render();
        }

        function render() {
            const timer = Date.now() * 0.0001;

            const delta = clock.getDelta();

            if (mixer) {
                mixer.update(delta);
            }

            camera.position.x = Math.cos(timer) * 400;
            camera.position.z = Math.sin(timer) * 400;

            camera.lookAt(scene.position);
            renderer.render(scene, camera);
        }

    </script>

</body>

</html>

Can you please try it with the three.js editor: three.js editor

Importing an animated glTF asset and exporting as glb (binary) works as expected. The resulting asset does not lose its animations.

If this workflow works for you, it’s not an issue with GLTFExporter.

It may also be possible to create animations that GLTFExporter cannot handle, but without a demo I probably can’t advise on that.