How to trim excess duration time after an animation is finished?

Hi I’m new to ThreeJS and I’m working on displaying a model and playing its animation on loop. I was able to get it to work but I ran into a problem which I think is from the model itself.

The animation I want to play is “Action_Crawl”. It has 86 frames but the total duration is 22s!!? The real crawling action only takes about 2s and after that the model just freezes for the rest of the duration before doing another loop.

How can I trim off the excess 20s?


Im not sure what the times in green arrow mean.

Here’s what I’ve tried so far:

  • Set the action duration to 2s but it just speeds up everything

  • Opened the Action_Crawl.FBX in blender to confirm the animation only lasts 86 frames (I don’t know Blender I just installed it yesterday out of curiousity)

This model has a lot of animations and all of them have the same problem with excessive time. Is this something I can change in js or does it requires modification inside the model? It comes with glb, gltf, and all of the fbx files for every animation.

Thank you for reading.

Do it in Blender, or if you do it in JS, then you can use THREE.AnimationUtils.subclip

See line 60 in example below

2 Likes

Hey thank you for your tip. I was able to create a subclip and turned it into an action. Now I’m trying to play this new action but it’s not working. I did some research and I think the animation mixer needs to be updated? I’ve been trying to update it but I don’t think I’m doing it right. What am I missing?

import React, { useEffect, useRef } from 'react'
import { useGLTF, useAnimations } from '@react-three/drei'
import { AnimationMixer, AnimationUtils } from 'three'

export default function Model(props) {
  const group = useRef()
  const { nodes, materials, animations } = useGLTF('/raccoon.gltf')
  const { actions } = useAnimations(animations, group)

  useEffect(() => {
    // Create animmation mixer
    const mixer = new AnimationMixer(group.current);
    // Get the clip of the action
    console.log(actions.Action_Crawl)
    const crawlClip = actions.Action_Crawl.getClip();
    // Create sub clip
    const crawl_subClip = AnimationUtils.subclip(crawlClip, 'crawl_trim', 1, 20);
    // Convert sub clip into Action
    const crawl_subAction = mixer.clipAction(crawl_subClip);
    // Play action
    console.log(crawl_subAction)
    crawl_subAction.play();

    // HOW TO UPDATE ANIMATION MIXER?
    const updateMixer = () => {
      mixer.update(0.1); // You can adjust the delta time as needed
    };
    return () => {
      mixer.stopAllAction();
    };
  })

  return (
    <group ref={group} {...props} dispose={null}>
      <group name="Sketchfab_Scene">
        <group name="Sketchfab_model" rotation={[-Math.PI / 2, 0, 0]}>
          <group name="d694477a516e4500907a8e651d27b4d1fbx" rotation={[Math.PI / 2, 0, 0]}>
            <group name="Object_2">
              <group name="RootNode">
                <group name="CG" position={[0, 33.046, 0]} rotation={[-1.605, -0.004, -1.449]}>
                  <group name="Object_5">
                    <primitive object={nodes._rootJoint} />
                    <group name="Object_63" rotation={[-Math.PI / 2, 0, 0]} />
                    <skinnedMesh name="Object_64" geometry={nodes.Object_64.geometry} material={materials.Material_28} skeleton={nodes.Object_64.skeleton} />
                  </group>
                </group>
                <group name="Raccoon_Poly_Art" rotation={[-Math.PI / 2, 0, 0]} />
              </group>
            </group>
          </group>
        </group>
      </group>
    </group>
  )
}

useGLTF.preload('/raccoon.gltf')

I also printed the original action and the trimmed action to check. The trimmed action has LocalRoot = null, unlike the original one. Is this a problem?

I’m new to three js so I’m a bit lost. Any advice is really appreciated. Thank you again for your time!

I see two potential reasons. But this is just a guess, since your code is not written in vanilla three.js. I’m not familiar with react three code skip.

Try to create your subclip from an animation, not from a clip. Set a duration too:
THREE.AnimationUtils.subclip( myAnimation, 'crawl_trim', 1, 20).setDuration(3).play();

then make sure your action is enabled
crawl_subAction.enabled = true;

1 Like

Here is a R3F verson of my 1st example.

I had some difficulty getting this to work using useAnimations, so I created a THREE.AnimationMixer like yourself.

Note that useAnimations manages and returns it’s own mixer. And you can access it like this.

const mixer = useAnimations(animations, group)

But it doesnt seem to expose the AnimationMixer.clipAction method. Or atleast I wasn’t able to figure out how to use it.

So I created my own mixer. You can then update your own mixer in the a useFrame.

1 Like

Thank you! I was able solve it using your and @seanwasere 's suggestion.

For this line I also added true

AnimationUtils.subclip( myAnimation, 'crawl_trim', 1, 20, true)

Cheers!