Can't play multiple clips even though I loop them

I’ve got two animations I need to animate.
One of them is a skeleton, the other is not. (If that matters).
My code only plays the animation that isn’t bone-based.


This code only loops the propeller and ignores the flag


This is where I loop the _mixer variable.

image

I’ve tried troubleshooting this by console logging the clips and they are both found, so I dont understand why only one of them runs.

When loaded the object into a gtlf viewer all animations worked so I know theres nothing wrong on the object.

Could you take a look at this @Mugen87 ? I would appreciate it

Try to create just a single animation mixer for an object. Not per animation.

I’ve tried rewriting the code so it matches the skinning-blending-animation-example on the threejs docs and this is the new code I wrote


And this is the information I logged

As you can see both clips get found and logged, but for some reason the flag still won’t animate. I have been stuck on this issue for a week now and cannot figure out why it doesnt work. Desperate for help!


Finally got a error that might help solve this… It cannot read the ‘uuid’ of the clip that belongs to the flag

This object right here

Please remove the console.log() statements. You call clipAction() with the same animation clip two times. This is not intended and I’m not aware about the side effects.

If this does not help, please share a live example that demonstrates the issue.

Hello Mugen, thanks for your reply!

What do you mean by that I call clipAction() with the same animation two times? The first time I call it I call the second clip in the array, and the second time I call the ninth clip.


All I did was to follow a official three js example. This script also “calls” the clipAction with the same animation clip. If it does not, whats the difference?

cheers

You call clipAction() two times with the same animation clip.

If you would not make screenshots of our code (that is actually no good style in forums) but copy/paste it with proper formatting, I could easily refer to it.

Thanks for the feedback regarding the scrnshots, I’ll have that in mind!

I feel like I haven’t presented my issue thoroughly enough and that may be why you have a hard time helping me. I’ll give this another shot and I’ll try to create a clearer post.

This is my modelLoader module. It handles all the GLTF assets

import * as THREE from "https://cdn.jsdelivr.net/npm/three@0.117/build/three.module.js";
import { loadingManager, loadedJSONdata, scene, clock } from "../app.js";

import { GLTFLoader } from "https://cdn.jsdelivr.net/npm/three@0.117/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "https://cdn.jsdelivr.net/npm/three@0.117/examples/jsm/loaders/DRACOLoader.js";

export let modelAnimationMixers = [];
export let modelAnimations = [];
export let frontFoam;

//let video = document.getElementById("video"); // link video HMTL element to JS
let video = document.getElementById("video"); // link video HMTL element to JS
let launchBtn = document.getElementById("launch");
video.play();

launchBtn.addEventListener("launch", function () {
  video.currentTime = 3;
});

let videoTexture = new THREE.VideoTexture(video); // create texture for video

export let FoamAnimMat = new THREE.MeshBasicMaterial({
  alphaMap: videoTexture,
  alphaTest: 0.45,
  side: THREE.DoubleSide,
  fog: false
});

export function loadModels() {
  const gltfLoader = new GLTFLoader(loadingManager); // This comes from GLTFLoader.js.
  const dracoLoader = new DRACOLoader(loadingManager);
  // Specify path to a folder containing WASM/JS decoding libraries.
  dracoLoader.setDecoderPath(
    "https://cdn.8thwall.com/web/aframe/draco-decoder/"
  );
  //loader.setDecoderPath('https://unpkg.com/three/examples/js/libs/draco/');

  //loader.setDecoderConfig( { type: 'js' } );
  // Optional: Pre-fetch Draco WASM/JS module.
  dracoLoader.preload();
  gltfLoader.setDRACOLoader(dracoLoader);

  let modelScale;
  const modelFiles = loadedJSONdata.ModelFiles;

  if (loadedJSONdata.ModelScale) {
    modelScale = new THREE.Vector3(
      loadedJSONdata.ModelScale,
      loadedJSONdata.ModelScale,
      loadedJSONdata.ModelScale
    );
  } else {
    modelScale = new THREE.Vector3(1, 1, 1);
  }

  let i = 0;

  function loadModelFile(url) {
    gltfLoader.load(url, (gltf) => {
      const loadedObject = gltf.scene;
      //console.log("loggar loadedObject som " + loadedObject.scale);

      // loop throught the object and see if there are any camera animations

      loadedObject.traverse((obj) => {
        //console.log(obj);

        if (obj.isSkinnedMesh) obj.castShadow = true;

        obj.frustumCulled = false;
        if (
          obj.name.includes("glas") ||
          obj.name.includes("Glas") ||
          obj.name.includes("Mesh_112")
        ) {
          obj.material = new THREE.MeshBasicMaterial({
            color: 0x000,
            transparent: true,
            opacity: 0.75,
            side: THREE.DoubleSide,
            depthWrite: false,
            depthTest: true,
            fog: false
            //envMap: envMap
          });

          //console.log("obj is", obj);
          obj.castShadow = false;
          obj.receiveShadow = false;
          obj.renderOrder = 900;
        } else if (
          obj.name.includes("VILJANtop") ||
          obj.name.includes("VILJANflaglow") ||
          obj.name.includes("VILJANgardiner") ||
          obj.name.includes("VILJANinside") ||
          obj.name.includes("VILJANpersienner") ||
          obj.name.includes("VILJANsmall") ||
          obj.name.includes("VILJANtyg")
        ) {
          //obj.material.fog = false;
          // MeshBasicMaterial behövs tillfälligt för att ha en fungerande lightmap. Fixa!
          /* */
          // console.log(obj);

          let newMat = new THREE.MeshBasicMaterial({
            map: obj.material.map,
            lightMap: obj.material.emissiveMap,
            fog: false,
            side: THREE.DoubleSide
          });

          obj.material.dispose();
          obj.material = newMat;

          if (obj.name.includes("VILJANflaglow")) {
            obj.visible = false;
          }

          //console.log(obj);
        } else if (
          obj.name.includes("VILJANbottom") ||
          obj.name.includes("VILJANproppeller")
        ) {
          //console.log(obj);
          let newMat = new THREE.MeshBasicMaterial({
            map: obj.material.map,
            lightMap: obj.material.emissiveMap,
            fog: true,
            side: THREE.DoubleSide
          });
          obj.material.dispose();
          obj.material = newMat;
          //console.log(obj);
        } else if (obj.name.includes("foam_front_short002")) {
          obj.material.fog = false;
          frontFoam = obj;
          frontFoam.material.map.blending = 2;
          frontFoam.material.emissive = { r: 1, g: 1, b: 1 };
          frontFoam.material.emissiveIntensity = 2;
          //console.log("console log 505", obj);
        } else if (obj.name.includes("backfoam_plane")) {
          // console.log("console.log from 507", obj);

          obj.material = FoamAnimMat;

          let objCopy = obj.clone();
          objCopy.position.y = -0.06;
          objCopy.material.flipY = false;
          scene.add(objCopy);
          //obj.fog = false;
          //obj.alphaTest = 1;
          obj.position.y = 0.06;
        } else if (obj.name.includes("underwater")) {
          //obj.position.y = 15;
        } else if (obj.name.includes("Islands")) {
          //obj.position.y = 15;
          //console.log(obj);
          /*
          obj.material = new THREE.MeshBasicMaterial({
            map: obj.material.map,
            fog: false
          }); */
          obj.material.fog = false;
        }
        obj.castShadow = true;
        obj.receiveShadow = true;
      });

      let skeleton = new THREE.SkeletonHelper(loadedObject);
      let propellerAction, flagAction, actions;

      skeleton.visible = true;
      scene.add(skeleton);
      //console.log("180", skeleton);

      const mixer = new THREE.AnimationMixer(gltf.scene);
      const clips = gltf.animations;

      console.log(clips);

      flagAction = mixer.clipAction(clips[1]);
      propellerAction = mixer.clipAction(clips[8]);

      //console.log(mixer.clipAction(clips[1]));
      //console.log(mixer.clipAction(clips[8]));

      actions = [flagAction, propellerAction];

      function activateAllActions() {
        actions.forEach(function (action) {
          action.play();
          // console.log("198", action);
        });
      }

      function animateBoat() {
        requestAnimationFrame(animateBoat);
        let mixerUpdateDelta = clock.getDelta();
        mixer.update(mixerUpdateDelta);
      }
      activateAllActions();
      animateBoat();

      loadedObject.scale.set(modelScale.x, modelScale.y, modelScale.z);

      scene.add(loadedObject);

      if (i < modelFiles.length - 1) {
        // When the model has been loaded and added, load the next one
        i++;
        loadModelFile(modelFiles[i].src);
      }
    });
  }

  //Load first model in array
  loadModelFile(modelFiles[i].src);
}

The actual animations code stems from line 169 - 202.

let skeleton = new THREE.SkeletonHelper(loadedObject);
      let propellerAction, flagAction, actions;

      skeleton.visible = true;
      scene.add(skeleton);
      //console.log("180", skeleton);

      const mixer = new THREE.AnimationMixer(gltf.scene);
      const clips = gltf.animations;

      console.log(clips);

      flagAction = mixer.clipAction(clips[1]);
      propellerAction = mixer.clipAction(clips[8]);

      //console.log(mixer.clipAction(clips[1]));
      //console.log(mixer.clipAction(clips[8]));

      actions = [flagAction, propellerAction];

      function activateAllActions() {
        actions.forEach(function (action) {
          action.play();
          // console.log("198", action);
        });
      }

      function animateBoat() {
        requestAnimationFrame(animateBoat);
        let mixerUpdateDelta = clock.getDelta();
        mixer.update(mixerUpdateDelta);
      }
      activateAllActions();
      animateBoat();

When I rewrite my code I used this threejs example

This is the code from the example


skeleton = new THREE.SkeletonHelper( model );
skeleton.visible = false;
scene.add( skeleton );

//

createPanel();


//

const animations = gltf.animations;

mixer = new THREE.AnimationMixer( model );

idleAction = mixer.clipAction( animations[ 0 ] );
walkAction = mixer.clipAction( animations[ 3 ] );
runAction = mixer.clipAction( animations[ 1 ] );

actions = [ idleAction, walkAction, runAction ];

activateAllActions();

animate();

Only difference is(that I have noticed) is that the developers activateAllActions and animate function is outside of the gltfloader, whereas mine is inside. I tried moving it out before but I code some scope-errors.
This is the functions

function activateAllActions() {

	setWeight( idleAction, settings[ 'modify idle weight' ] );
	setWeight( walkAction, settings[ 'modify walk weight' ] );
	setWeight( runAction, settings[ 'modify run weight' ] );

	actions.forEach( function ( action ) {

		action.play();

	} );
}
function animate() {

// Render loop

requestAnimationFrame( animate );

idleWeight = idleAction.getEffectiveWeight();
walkWeight = walkAction.getEffectiveWeight();
runWeight = runAction.getEffectiveWeight();

// Update the panel values if weights are modified from "outside" (by crossfadings)

updateWeightSliders();

// Enable/disable crossfade controls according to current weight values

updateCrossFadeControls();

// Get the time elapsed since the last frame, used for mixer update (if not in single step mode)

let mixerUpdateDelta = clock.getDelta();

// If in single step mode, make one step and then do nothing (until the user clicks again)

if ( singleStepMode ) {

	mixerUpdateDelta = sizeOfNextStep;
	sizeOfNextStep = 0;

}

// Update the animation mixer, the stats panel, and render this frame

mixer.update( mixerUpdateDelta );

stats.update();

renderer.render( scene, camera );

}

As you can see, I(have tried to) declare(d) the variables containing the mixer.clipAction the same way and I call it the same way. Our functions are also very much alike.

When I try to run this, I get the following error

sse-hooks.f742b80f43c5a2e0e619b0d97b5886cd.js:1 TypeError: Cannot read properties of undefined (reading 'uuid')
    at AnimationMixer.clipAction (three.module.js:45818:48)
    at modelLoader.js:181:26
    at GLTFLoader.js:148:7
    at GLTFLoader.js:1527:4

line 181 refers to flagAction = mixer.clipAction(clips[1]);
The working clip is just rotating object (the propeller) whilst the flagClip has bones.

Now… Question is, how do I solve this? I’ve been at it for a week now and I’ve developed a tunnel-vision, the solution might be easier than I think but I am so stuck and I can’t see it.

I hope this post was better, cheers!

Thanks again for sticking with me and giving great feedback.

1 Like

Any chances to share your glTF asset in this topic? I’ve would try to play both animations on my local computer so I can check if there is an issue in three.js.

I’ve rooted down the issue, it has to do with the clips uuid. The issue stems from a certain mesh thats animated and that is the flag. I seperated the flag from the rest of the model, exported it and tried animate it by itself in the project. It didn’t work, as I thought. So the issue stems from this model.
You can look at it here, I have no idea whats wrong with it

The three.js editor as well as the three.js based gltf-viewer are able to playback the animation. So it is not an issue with three.js or the glTF asset.

Do you mind using webgl_skinning_simple as a code template? webgl_animation_skinning_blending is unnecessarily complex for a starter example.

Hello,

I did as you asked and it still didn’t work, altough I got a new error. It stated that I need a dracoloader instead.

Error: THREE.GLTFLoader: No DRACOLoader instance provided.
    GLTFDracoMeshCompressionExtension http://localhost:5555/examples/jsm/loaders/GLTFLoader.js:1469
    parse http://localhost:5555/examples/jsm/loaders/GLTFLoader.js:368
    load http://localhost:5555/examples/jsm/loaders/GLTFLoader.js:205
    load http://localhost:5555/build/three.module.js:40014
GLTFLoader.js:185:13

Since I can’t find any threejs example that uses both a draco and gltf loader togheter (as I do in my project), I rewrote the script in webgl_skinning_simple into this

		<script type="module">

			import * as THREE from 'three';

			import Stats from 'three/addons/libs/stats.module.js';

			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';


			let stats, mixer, camera, scene, renderer, clock;
			const loadingManager = new THREE.LoadingManager();


			init();
			animate();

			function init() {

				const container = document.createElement( 'div' );
				document.body.appendChild( container );

				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.set( 18, 6, 18 );

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xa0a0a0 );
				scene.fog = new THREE.Fog( 0xa0a0a0, 70, 100 );

				clock = new THREE.Clock();

				// ground

				const geometry = new THREE.PlaneGeometry( 500, 500 );
				const material = new THREE.MeshPhongMaterial( { color: 0x999999, depthWrite: false } );

				const ground = new THREE.Mesh( geometry, material );
				ground.position.set( 0, - 5, 0 );
				ground.rotation.x = - Math.PI / 2;
				ground.receiveShadow = true;
				scene.add( ground );

				const grid = new THREE.GridHelper( 500, 100, 0x000000, 0x000000 );
				grid.position.y = - 5;
				grid.material.opacity = 0.2;
				grid.material.transparent = true;
				scene.add( grid );

				// lights

				const hemiLight = new THREE.HemisphereLight( 0xffffff, 0x444444, 0.6 );
				hemiLight.position.set( 0, 200, 0 );
				scene.add( hemiLight );

				const dirLight = new THREE.DirectionalLight( 0xffffff, 0.8 );
				dirLight.position.set( 0, 20, 10 );
				dirLight.castShadow = true;
				dirLight.shadow.camera.top = 18;
				dirLight.shadow.camera.bottom = - 10;
				dirLight.shadow.camera.left = - 12;
				dirLight.shadow.camera.right = 12;
				scene.add( dirLight );

				//

		const loader = new GLTFLoader();
		const dracoLoader = new DRACOLoader();

		dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
		dracoLoader.preload();
  				loader.setDRACOLoader(dracoLoader);

				loader.load( './models/gltf/flag.glb', function ( gltf ) {

					scene.add( gltf.scene );

					gltf.scene.traverse( function ( child ) {

						if ( child.isSkinnedMesh ) child.castShadow = true;

					} );

					mixer = new THREE.AnimationMixer( gltf.scene );
					mixer.clipAction( gltf.animations[ 0 ] ).play();

				} );

				//

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				renderer.shadowMap.enabled = true;
				container.appendChild( renderer.domElement );

				//

				stats = new Stats();
				container.appendChild( stats.dom );

				const controls = new OrbitControls( camera, renderer.domElement );
				controls.enablePan = false;
				controls.minDistance = 5;
				controls.maxDistance = 50;

			}

			function animate() {

				requestAnimationFrame( animate );

				if ( mixer ) mixer.update( clock.getDelta() );

				render();
				stats.update();

			}

			function render() {

				renderer.render( scene, camera );

			}

		</script>

The main difference being these highlighted parts

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';

and

const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();

dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/');
dracoLoader.preload();
loader.setDRACOLoader(dracoLoader);

and now the flag works, and it gets animated aswell.
image

I guess all that is left to figure out the difference between this script and the one I’m running on my project… Something is clearly wrong on my end.

I solved it.
I apply a new material to the object in my script as it gets exported a MeshStandardMaterial(I need to do this so that it matches the blender file as accurately as possible)

                    obj.name.includes("VILJANtop") ||
                    //obj.name.includes("VILJANflaglow") ||
                    obj.name.includes("VILJANgardiner") ||
                    obj.name.includes("VILJANinside") ||
                    obj.name.includes("VILJANpersienner") ||
                    obj.name.includes("VILJANsmall") ||
                    obj.name.includes("VILJANtyg")
                  ) {
                    let newMat = new THREE.MeshBasicMaterial({
                      map: obj.material.map,
                      lightMap: obj.material.emissiveMap,
                      fog: false,
                      side: THREE.DoubleSide
                    });

and for some reason, it totally displaces the object and ruins the scope somehow. It hinders me from accessing the uuid, the clip, etc… It is really weird. So I commented it out for the time being.