Cloning a skinned mesh

Hello there! Been a long time haha!

I’ve been trying to duplicate a gltf model in a scene with .clone() but it doesn’t seem to work.
Am i doing something wrong here?

http://jsfiddle.net/ddbTy/2436/

No, you are not doing something wrong. The problem is that cloning of skinned meshes is not supported so far, see

But there is PR in work that should solve your problem. If you have to clone skinned meshes for your project, you can already use the respective code.

3 Likes

It doesn’t work currently, but I found this function which worked well enough for my game.

1 Like

http://jsfiddle.net/ddbTy/2445/

tried it but seems to be throwing Skeleton is not defined error.

Hmm. What object are you cloning? the scene? the GLTF file? an Object3D?

the gltf file

Hmm. I don’t know. It looks like you are doing everything right. One suggestion. Don’t ever use the original GLTF, just the clones. I don’t know what the mixer does internally but it may modify stuff.

I ran into this when trying to do this myself, the Skeleton error waverider404 is having from the gist is just due to a name spacing issue

ie. new Skeleton(orderedCloneBones, skeleton.boneInverses), may need to be THREE.Skeleton(orderedCloneBones, skeleton.boneInverses), depending on how the library is being loaded

didnt work still, do you have a better working version?

SkeletonUtils.clone() will be available in the examples with the next release R103 :tada:

Use it when you have to clone an instance of THREE.SkinnedMesh().

5 Likes

yesssir!! finally

Currently loading the three.min.js from https://threejs.org/build/three.min.js

and loading it in HTML like so
<script src=“https://threejs.org/build/three.min.js”></script>

but I am unable to see THREE.SkeletonUtils … am I supposed to be loading in other things as well?

Can’t call on SkeletonUtils.clone or THREE.SkeletonUtils.clone
“Cannot read property ‘clone’ of undefined”

It seems like I’m on v103 …
THREE.WebGLRenderer 103

SkeletonUtils is not part of the core build – like loaders, it’s in examples/js/*. I’d recommend using a versioned URL, rather than the build hosted on the threejs website (which will change with each new release). For example: https://cdn.jsdelivr.net/npm/three@v0.103.0/examples/js/utils/SkeletonUtils.js

3 Likes

Just curious, why is it not skinnedMesh.clone()?

Technical obstacles. clone() recursively copies everything in the scene hierarchy, one object at a time. The SkinnedMesh would need to clone the bones of its Skeleton at that step, which is challenging if they’re children of an object that hasn’t been cloned yet, or not in the subtree being cloned at all, etc.

I don’t think there’s any perfect way to implement it within the .clone() API, but maybe some of the remaining edge cases could be fixed there too.

4 Likes

@donmccurdy
I don’t think there’s a perfect operation for clone that will solve every use case but it would be nice to have have the result do something reasonable by default. Discussion in Issue 17299 and solution in PR 17370 I think showed promising results so I’d hope that it can be addressed at some point.

I use clone to duplicate and cache objects from model loaders and the like so it would be nice to not have to have special cases for these types of situations.

2 Likes

The cache may be a good solution, yes. :+1:

1 Like

@donmccurdy,

Thanks a lot for the clever solution, I made some slight changes to your utility function.

function resetClonedSkinnedMeshes(source, clone) {
  const clonedMeshes = [];
  const meshSources = {};
  const boneClones = {};

  parallelTraverse(source, clone, function (sourceNode, clonedNode) {
    if (sourceNode.isSkinnedMesh) {
      meshSources[clonedNode.uuid] = sourceNode;
      clonedMeshes.push(clonedNode);
    }
    if (sourceNode.isBone) boneClones[sourceNode.uuid] = clonedNode;
  });

  for (let i = 0, l = clonedMeshes.length; i < l; i++) {
    const clone = clonedMeshes[i];
    const sourceMesh = meshSources[clone.uuid];
    const sourceBones = sourceMesh.skeleton.bones;

    clone.skeleton = sourceMesh.skeleton.clone();
    clone.bindMatrix.copy(sourceMesh.bindMatrix);

    clone.skeleton.bones = sourceBones.map(function (bone) {
      return boneClones[bone.uuid];
    });

    clone.bind(clone.skeleton, clone.bindMatrix);
  }
}

Usage :

  // Clone the parent object as you would normally do
  const clone = parentObject.clone();

  // Reset the cloned skinnedMeshes (if any)
  resetClonedSkinnedMeshes(parentObject, clone);

With this approach you don’t have to check whether the parent object or the scene contains skinned meshes and treat it as a special case.

Here is a working example from your example (changes at line 117,118)

Thanks again.

1 Like