Gltf animation question

Hello Folks,

i am missing something very basic , while the gltf object with the animation loads , the animation does not play , i know its been asked several times in this forum, and have looked through most of them, but could not get this working, any help would be appreciated ,
and here is the snippet,


      const gltfInteractionTest = new GLTFLoader(manager);

      var dummyTest = new THREE.Object3D();

      gltfInteractionTest.load('../models/testAnim.glb', function (gltfITest) {
          
        gltfITest.scene.traverse( function ( child ) {

          if (child.isMesh) {

            instancedGeoIntTest = new THREE.InstancedMesh(child.geometry, child.material, 1);
            instancedGeoIntTest.setMatrixAt(0, dummyTest.matrix);
            instancedGeoIntTest.position.z = 1;
            instancedGeoIntTest.position.y = 0;
            instancedGeoIntTest.position.x = 0;
              
            var animations = gltfITest.animations;
            mixerGaurd = new THREE.AnimationMixer(gltfITest.scene);
            var action = mixerGaurd.clipAction(animations[0]); // access first animation clip
            action.loop = THREE.LoopRepeat;
            action.reset().play();
            scene.add(instancedGeoIntTest);
                  
        }
        
       });
        
     });

and in the animation loop i have this,
 if ( instancedGeoIntTest ) mixerGaurd.update( time.getDelta() ); 

thanks

anyone ? any pointers to why the animation is not playing , it is playing in gltf browsers ,
any help would be appreciated

thanks

According to this discussion the problem seems to be that three.js doesn’t support instanced animated models: InstancedMesh animations, like with SkinnedMesh?

I’m interested about that too. I tried to make instances of an existing animated model of mine and while the main model was moving, the instanced ones were not .

I remember a few years ago there was a three.js example with an army of monsters that were marching. Obviously they were instances, but that example doesn’t exist anymore, nor anything similar AFAIK.

Perhaps you can find it via loading older versions of three.js via npm, but three.js has changed, so it might not be useful. Unless you extend three.js yourself to do that.

What do the moderators say about that?

EDIT: in order to be useful, the animation should be optionally applied selectively (different parts to different instances), or with some form of de-synchronization (play-time offset).

Thanks for the info provided, moderators have not answered yet , hoping that they would at some point ,

would be very useful to have animated models on instances

@dllb is correct. InstancedMesh can not be animated yet.

Unfortunately, there are still some open points in context of instancing with different priorities. One important issue which I try to solve first is a proper bounding box computation, see here. I know this is not super relevant for your use case but improper bounding boxes affects all users of InstancedMesh right now.

Are you referring to: three.js webgl - morphtargets - MD2 controls?

This example does not use instancing. Maybe you can find the example you have mentioned under this link: three.js / examples

It leads to the example page of r90. By modifying the URL, you can go even further back in time.

1 Like

Thanks Michael,

saw the example provided, it uses md2 loader, i have not used this file format for loading objects,
and reckon glb if the smallest file format if understood this correctly,
so until you guys figure this out , will use glb, instead of instanced glb,

Hey Guys,

not using instances for animation, something is not in place, could not figure out what it is ?

here is the glb file ,

and here is how is load it , its a normal gltf loader method
and this is in the animation update

if ( mixerGaurd ) mixerGaurd.update( time.getDelta() );
        const gltfInteractionTest = new GLTFLoader(manager);
  
        gltfInteractionTest.load('../models/castleGaurd.glb', function (gltfITest) {
       

        gltfITest.scene.traverse( function ( child ) {
  
          if (child.isMesh) {
  
            GeoIntTest = gltfITest.scene;
            GeoIntTest.rotation.y = 3.141;
            //animationGaurd = gltfITest.animations;
            mixerGaurd = new THREE.AnimationMixer(gltfITest.scene);
            var action = mixerGaurd.clipAction(gltfITest.animations[0]); // access first animation clip
            action.setLoop( THREE.LoopRepeat );
            action.play();
            child.material.roughness = 0.9;
            child.material.metalness = 0.5;               
            scene.add(GeoIntTest);
                  
        }
        
        });
        
        });

castleGaurd.glb (273.3 KB)

@innovate
Your function doesn’t seem to miss something …except that it desperately needs clean up :slight_smile:
So, the problem is obviously on the previous setup.

Make sure you have imported these:

import { GLTFLoader } from './node_modules/three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from './node_modules/three/examples/jsm/loaders/DRACOLoader.js';

then you need to define:

var time=new THREE.Clock(); 
mixerGaurd=null; clips=null;
const gltfInteractionTest = new GLTFLoader();//this replaces your line
gltfInteractionTest.setDRACOLoader( new DRACOLoader() );

And you should be OK.
(BTW, you should hit F12 and watch for errors in console)

@dIIb , thanks for the suggestion and @Mugen87 for the pointers,

yep definitely needed a clean up , i was importing gltfloader, and getting an instance of the clock module , the problem was as you had pointed out cleaning up the rubble that was my code stack :slightly_smiling_face: , and also i was doing some grouping down the line to add to the woes of the uncleaned code ,

it was working , as you had said earlier,

so below is the code which is a working sample of glb animation , anyone chasing their own tail like me, might find it useful

now to make it work amongst the other code :slight_smile:


import * as THREE from '../build/three.module.js';

import { GLTFLoader } from 'https://threejsfundamentals.org/threejs/resources/threejs/r125/examples/jsm/loaders/GLTFLoader.js';

import { OrbitControls } from 'https://threejsfundamentals.org/threejs/resources/threejs/r125/examples/jsm/controls/OrbitControls.js';


let scene, time, camera, renderer, directionalLight;

let controls, instancedGeoIntTest;

var mixerGaurd ;

time = new THREE.Clock();
let t = time.getElapsedTime();
   
function init() {

    scene = new THREE.Scene();

    const fov = 75;
    const aspect = 2;  // the canvas default
    const near = 1;
    const far = 1000000;

    camera = new THREE.PerspectiveCamera(fov,window.innerWidth/window.innerHeight,near,far);
    camera.position.set(0, 0, 5);
  
    //canvas = document.querySelector('#canvasId');
    renderer = new THREE.WebGLRenderer({antialias:true});
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

   //orbital control
  
    controls = new OrbitControls(camera,renderer.domElement);
    controls.minDistance = 1;
    controls.maxDistance = 5000;

    const gltfInteractionTest = new GLTFLoader();
    
    gltfInteractionTest.load('../models/castleGaurd.glb', function (gltfITest) {

    gltfITest.scene.traverse( function ( child ) {

    if (child.isMesh) {

        instancedGeoIntTest = gltfITest.scene;
        mixerGaurd = new THREE.AnimationMixer(gltfITest.scene);
        var action = mixerGaurd.clipAction(gltfITest.animations[0]); 
        action.setLoop( THREE.LoopRepeat );
        action.play();
        child.material.roughness = 0.9;
        child.material.metalness = 0.5;               
        scene.add(instancedGeoIntTest);        
    }

    });

    });

  //setup Lights

    directionalLight = new THREE.DirectionalLight(0xffffff,1.25);
    directionalLight.position.set(0, 0, 4);
    scene.add(directionalLight);
 
    animate();
}

function animate() {
  
    requestAnimationFrame(animate);
    renderer.render(scene, camera);
    if ( mixerGaurd ) mixerGaurd.update( time.getDelta() );
  
}
  
init();

@innovate
If you don’t need the draco loader that means that you’re not using draco’s excellent geometry compression, which makes models considerably lighter. With Draco your 273kb model becomes 153kb - 56% of the initial model:
castleGuardCOMP.glb (153.3 KB)

@dIIb , did a read through about the draco compression , brilliant stuff , the thing is i tried compressing the glb , using two methods stated below, because in the docs it says it compresses at the expense of client compression time,
since id like the load time on the client side to be exactly the same as the server side,

what i did was try to compress the geometry ,

A: using gltf-pipeline -d (mentioned by @donmccurdy ) command line
gives me a compressed file , but does not display the geometry

B:Blender has an option to compress geometry while exporting glb, using the same draco compression,
tried several settings even brought this down to 126kb , but again the geometry does not display

would like to know a bit more about the compression method and why it does not display the geometry, after compression,
even the file you had attached does not display

this forum is a rhodium mine :slight_smile: , brilliant stuff

@innovate are you including draco loader and decoder path? Have a look at this thread…

1 Like

@innovate,
I’m absorbed in coding rather than processing models for a long time now, so I used Blender to compress your model -otherwise, bat files and commands is the best way for optimum results.

So here’s the easy, imperfect, old way using Blender, that still works:

  1. Open the .blend file bellow -it contains your model:
    GLB_COMP.blend (2.8 MB).

  2. Click on your model.
    In the lower panel of the ‘Shading’ tab, you’ll notice the ‘glTF Metallic Roughness’ node. That node is key for compression compatibility with three.js and isn’t default in Blender -I have “appened” it from an older Blender file.
    So, if you delete the existing model, the next model you load or create will replace it usually with ‘Principled BSDF’. In that case you have to delete ‘Principled BSDF’, click ‘Add’ then search for ‘gltf’ to add it and connect it.
    You can use this file as a template to compress other models you create/load

  3. With the object(s) selected, click file/export/gltf 2.0:
    Select “limit to selected”, “compression” (6) and “animation”,
    and preferably select only the elements what your model contains.

  4. Search in the ‘node_modules’ for these files:

draco_wasm_wrapper.js
draco_decoder.wasm

and copy them in the root (aside html), then draco will work -sorry I forgot them.

*** I too will have to dig eventually into the Khronos documentation, threads, tutorials, choose the best commands, etc, in order to find the newer, possibly better methods.
Unfortunately there is a salad of confusing and overwhelming information, and using commands (instead of having a GUI) is of another ancient era, as usability still comes last in the development priorities, even today… On the positive side, we’re here to fix this and make a better world, right? :sunglasses:)

2 Likes

@dIIb, @forerunrun , this is an absolute wicked forum , thanks for the help @dIIb,

as usual i have been daft , i compressed it and did not load the draco loader (of course it will not work) and load it back ,

:slight_smile:

1 Like

@dllb , works like a charm :+1:

and the glb size is 150kb from 273kb , i can reduce it further if i wanted to , now i know how this works :slight_smile:

const gltfInteractionTest = new GLTFLoader();

    gltfInteractionTest.setDRACOLoader(new DRACOLoader());
    gltfInteractionTest.load('../models/castleGaurdTest.glb', function (gltfITest) {

    gltfITest.scene.traverse( function ( child ) {

    if (child.isMesh) {

        instancedGeoIntTest = gltfITest.scene;

        mixerGaurd = new THREE.AnimationMixer(gltfITest.scene);

        var action = mixerGaurd.clipAction(gltfITest.animations[0]); 

        action.setLoop( THREE.LoopRepeat );

        action.play();

        child.material.roughness = 0.9;

        child.material.metalness = 0.5;               

        scene.add(instancedGeoIntTest);        

    }

    });

    });

continuing the saga…:slight_smile:

have got the mouse click on animated gltf working, but not quiet , seems to respond to click at random areas on the model do not know why i could not get consistent mouse click , with the same casteGaurd.glb compressed gltf object,

any help would be appreciated, @dIIb, @mugen87, @forerunrun, @donmccurdy

const gltfInteractionTest = new GLTFLoader();

    gltfInteractionTest.setDRACOLoader(new DRACOLoader());
    
    gltfInteractionTest.load('../models/castleGaurd.glb', function (gltfITest) {

    gltfITest.scene.traverse( function ( child ) {

    if (child.isMesh) {
        var horseObject = gltfITest.scene;
        meshes.push(child);
        mixerGaurd = new THREE.AnimationMixer(gltfITest.scene);
        action = mixerGaurd.clipAction(gltfITest.animations[0]);
        action.setLoop(THREE.LoopOnce);
        action.play();
        child.material.roughness = 0.9;
        child.material.metalness = 0.5;               
        scene.add(horseObject);
            }

        });

    });

// mouse click

    function animElemenets(event) {
        
        var rayAnimElement = new THREE.Raycaster();
        rayAnimElement.setFromCamera(mouse, camera);
        var animElementHit = rayAnimElement.intersectObjects(meshes);
        if (animElementHit.length > 0 ) {
            console.log("interactHit.. ", animElementHit[0].object.name);
            if (animElementHit[0].object.isMesh) {
                
                if (action !== null) {
                    action.stop();
                    action.play();
                }
            }
        }
    }
 
    document.addEventListener("pointerdown", animElemenets, false);

Thanks

Do you update your mouse coordinates anywhere in your pointer down event?

@forerunrun , i did this in the pointerdown event , dosent seem to work

function animElemenets(event) {
        
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
        
        var rayAnimElement = new THREE.Raycaster();
        rayAnimElement.setFromCamera(mouse, camera);
        var animElementHit = rayAnimElement.intersectObjects(meshes);
        if (animElementHit.length > 0 ) {
            console.log("interactHit.. ", animElementHit[0].object.name);
            if (animElementHit[0].object.isMesh) {
                
                if (action !== null) {
                    action.stop();
                    action.play();
                }
            }
        }
    }

Just 2 cents that it might not run on ms edge at all.

@threejsman ,thanks for 2 cents :slight_smile: ,

i am testing this on chrome , seems to work inconsistently,
anyone? , moderators who can help ?