How to clone a gltf?

I want to use several instances of a model, defined in a glb file, in a game.

Rather than get the glb file over the network multiple times, I want to just get it once and then clone it. But I can’t see how to do that cloning.

Here is what I’m trying so far:

    const CHARACTER_PATH = "/models/character.glb";                                                                                                                                        
                                                                                                                                                                                                
    let myGltf, myClone;                                                                                                                                                                        
                                                                                                                                                                                                
    //Promisified version of GLTFLoader.load()                                                                                                                                                  
    function loadGLTF(url) {                                                                                                                                                                    
      return new Promise((resolve, reject) => {                                                                                                                                                 
        new GLTFLoader().load(                                                                                                                                                                  
          url,                                                                                                                                                                                  
          (gltf) => resolve(gltf),                                                                                                                                                              
          undefined,                  //loading progress unused                                                                                                                                 
          (error) => reject(error)                                                                                                                                                              
        );                                                                                                                                                                                      
      });                                                                                                                                                                                       
    }                                                                                                                                                                                           
                                                                                                                                                                                                
    loadGLTF(CHARACTER_PATH)                                                                                                                                                                    
      .then((gltf) => {                                                                                                                                                                         
        myGltf = gltf;                                       //ok                                                                                                                                                                          
//      myClone = structuredClone(myGltf);  //DomException                                                                                                                                      
      })                                                                                                                                                                                        
      .catch((error) => {                                                                                                                                                                       
        console.log(error);                                                                                                                                                                     
      }); 

I can’t just use a clone of gltf.scene because the animations are in gltf.animations.

Any help appreciated.

The objects returned by the GLTFLoader are standard three.js class instances like THREE.Object3D and THREE.Mesh, you can use Object3D#clone() or other methods on them. Animations shouldn’t be an issue as long as your animations are on named objects, just used the cloned object as the ‘root’ in your AnimationMixer.

The one exception is if you’re using skinning/armature animation. In that case to keep bones associated with the SkinnedMesh skeleton correctly you’d need to clone the root object(s) with SkeletonUtils.clone instead.

3 Likes

Thanks Don,

use Object3D#clone() or other methods on them

I can’t call .clone() on the gltf in the loader:

    const CHARACTER_PATH = "/models/character.glb";                                                                                                                                        
                                                                                                                                                                                                
    let myGltf, myClone;                                                                                                                                                                        
                                                                                                                                                                                                
    //Promisified version of GLTFLoader.load()                                                                                                                                                  
    function loadGLTF(url) {                                                                                                                                                                    
      return new Promise((resolve, reject) => {                                                                                                                                                 
        new GLTFLoader().load(                                                                                                                                                                  
          url,                                                                                                                                                                                  
          (gltf) => resolve(gltf),                                                                                                                                                              
          undefined,                  //loading progress unused                                                                                                                                 
          (error) => reject(error)                                                                                                                                                              
        );                                                                                                                                                                                      
      });                                                                                                                                                                                       
    }                                                                                                                                                                                           
                                                                                                                                                                                                
    loadGLTF(CHARACTER_PATH)                                                                                                                                                                    
      .then((gltf) => {                                                                                                                                                                         
        myGltf = gltf;                  //ok                                                                                                                                                                
        myClone = gltf.clone();  //TypeError: gltf.clone is not a function                                                                                                                                                            
      })                                                                                                                                                                                        
      .catch((error) => {                                                                                                                                                                       
        console.log(error);                                                                                                                                                                     
      });      

Could you clarify what I call .clone() on?

I see character in your path name, so it will most likely be a skeleton model… Try this approach:

import * as SkeletonUtils from 'three/addons/utils/SkeletonUtils.js';

//....

const CHARACTER_PATH = "https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb";
  let myGltf, myClone;

  function loadGLTF(url) {
  return new Promise((resolve, reject) => {
    new GLTFLoader().load(
      url,
      (gltf) => resolve(gltf),
      undefined,                                            
      (error) => reject(error)
    );
  });
}

loadGLTF(CHARACTER_PATH)
  .then((gltf) => {
    scene.add(gltf.scene);

    const clone = SkeletonUtils.clone(gltf.scene);
    gltf.scene.position.x = -2;
    gltf.scene.position.y = -2;
    clone.position.x = 2;
    clone.position.y = -2;
    scene.add(clone);
  })
  .catch((error) => {
    console.log(error);
  });

2 Likes

The top-level result is a plain object containing various data, not all Object3D scene graph objects. To clone the default scene you’d select and clone that only:

const scene = gltf.scene;
const scene2 = scene.clone();

There’s no need to clone AnimationClip objects, they can be reused as-is.

More details on the response in the GLTFLoader docs.

1 Like

Maybe that’s the bit I was missing.

In the game, suppose I have 10 characters running around. Each character has its own AnimationMixer.

But, all I need is a single copy of the animations array?

For a particular character, I can take an AnimationClip from animations and use that character’s mixer.clipAction() to generate an AnimationAction?

I now have a ‘global’ set of AnimationClip objects but each character has it’s own set of AnimationAction objects.

And it works!

Thanks to Don & Lukasz.

That’s fine yes – The clips are reusable, the actions are specific to that character/object.

The exception is that if the objects in your glTF file are unnamed (which is not common) then three.js will create AnimationClip objects that target those objects by UUID. Then the cloned objects would have different UUIDs, so the animation wouldn’t play on them. But as long as your objects have names, the cloned objects will have the same names, the animation finds those names within the root passed to the AnimationMixer, and everything works fine.

1 Like