Three.js: geometry.clone() doesn't copy reference to bones/skinweights/animations etc.

Created on 11 Jan 2015  Â·  16Comments  Â·  Source: mrdoob/three.js

There are a few things which geometry.clone() doesn't copy a reference to, which it should.

.bones
.skinWeights
.skinIndices
.animations
.animation

are all left without references to the original, which leads to skinning being broken unless the references to those are manually copied. I think references should be passed as it's less confusing, but I don't think they should be deep copies, or this would lead to mostly wasted memory.

Enhancement

Most helpful comment

Following up here, https://github.com/mrdoob/three.js/pull/14494 includes a utility function to help with cloning skinned meshes:

var copy = SkeletonUtils.clone( original );

The object given as a parameter may be a SkinnedMesh or an Object3D or Scene containing one or more SkinnedMesh instances. Any bones referenced by the SkinnedMesh(es) must also be children of the given object.

All 16 comments

I desperately need that enhancement. I am preparing a demo for the Monaco Anime Game International Conferences (MAGIC) hosted on Mars 21th 2015. The demo uses cloned geometry animations and I stumbled on that issue today while preparing the multiuser functions. Should I retrieve the enhancement from the dev sources or should I wait for three. j's v71?

Cordially,
j3zusla

PS: Anyone is going to the MAGIC

Encountering this issue as well. I have animations working using AnimationMixer, but when I clone the mesh I can't get them to work on the cloned mesh. I also try cloning the material and geometry separately and adding the manual references to the objects mentioned by QuaziKb but still nothing. Any suggestions would be greatly appreciated. Using r77 dev

Hello austingray, have you tried your code using r76? When you are mentioning "clone", I suppose you are using the geometry.clone(), etc.. native THREE.js functions. If this is the case, I don't believe it works with skinning poses yet. You will need to reimport your mesh and create separated containers to host your second skinning object.

Hey so it does work with .clone() on the SkinnedMesh - I was forgetting to call .update() in my render loop! Definitely time to call it a night after that...

Great news. It means the enhancement was made. Could you share your code to help out others in the same case scenario?

Keep in mind it's convoluted cause i'm pulling out of context, basically I have an init function where the ObjectLoader loads the model and saves the mesh to a variable. Then when a player joins, it checks their name against the scene and if a mesh with that name doesn't exist, it clones the original and adds it to the scene.

var init = function() {
    loader = new THREE.ObjectLoader();
    loader.load(
        'src/to/model.json',
        function ( object ) {
            reference = object.children[0];
        }
    );
}

var onJoin = function(name) {
    if (typeof reference !== 'undefined') {

        //char ref array
        characters.count = [];
        characters.count.push(name);
        //char obj array
        characters.group[name] = {};
        //characters mesh
        characters.group[name].mesh = reference.clone();
        characters.group[name].mesh.material.skinning = true;
        characters.group[name].mesh.name = name;
        scene.add(characters.group[name].mesh);
        //helper
        characters.group[name].helper = new THREE.SkeletonHelper( characters.group[name].mesh );
        characters.group[name].helper.material.linewidth = 1;
        characters.group[name].helper.visible = true;
        three.scene.add( characters.group[name].helper );
        //mixer and actions
        characters.group[name].mixer = new THREE.AnimationMixer( characters.group[name].mesh );
        characters.group[name].action = {}
        characters.group[name].action.walk = characters.group[name].mixer.clipAction( characters.group[name].mesh.geometry.animations[0] );

    }
}

var render = function() {

    delta = 0.75 * clock.getDelta();

    if (characters.count) {
        for (var i = 0; i < characters.count.length; i++ ) {
            var name = characters.count[i];
            characters.group[name].mixer.update( delta );
            characters.group[name].helper.update();
        }
    }

    renderer.render( scene, camera );
    requestAnimationFrame( render );

}

var serverUpdates = function(serverUpdate) {

    //just putting this here to show where .play() is called in my example
    for (var i = 0; i < serverUpdate.chars.length; i++) {
        scene.traverse( function (object) {
            if (object.name === serverUpdate.chars[i].name) {
                if (serverUpdate.chars[i].walking) {
                    characters.group[serverUpdate.chars[i].name].action.play();
                } else {
                    characters.group[serverUpdate.chars[i].name].action.stop();
                }
            }
        }
    }

}

I imagine I'll catch flack for my use of associative arrays. Any help in structuring this apparatus is more than welcome :)

Thank you for the code austingray! In your example, the imported object is entirely cloned, which avoids going through the loader again. The enhancement in question is for the "geometry.clone()" function that should provide performance advantages; I don't believe this function was implemented yet. Anyway, your code is still a good example.

Right, sorry for twisting the topic. I attempted to do geometry.clone() and saw the same problem as QuaziKb so that enhancement in fact has not been implemented as you said.

Any news on making a deep copy of the geometry? Just got stuck on this

Much of this was implemented by @Mugen87 recently, in https://github.com/mrdoob/three.js/commit/5f6d8e02 (r84).

If there are any remaining issues with cloning geometry, example models would be helpful.

Stumbled on this as well today. As a quickfix until it's supported I created a clone this way instead (es6 features):

let geometryFromGlobal = globalAssets.getGeometry(this.characterType)
    let clonedGeometry = geometryFromGlobal.clone()
    let bones = JSON.parse(JSON.stringify(geometryFromGlobal.bones))
    let skinWeights = JSON.parse(JSON.stringify(geometryFromGlobal.skinWeights))
    let skinIndices = JSON.parse(JSON.stringify(geometryFromGlobal.skinIndices))
    skinWeights = skinWeights.map(x => { return new THREE.Vector4().copy(x) })
    skinIndices = skinIndices.map(x => { return new THREE.Vector4().copy(x) })
    Object.assign(clonedGeometry, {bones, skinWeights, skinIndices })

    let clonedMesh = new THREE.SkinnedMesh(clonedGeometry, globalAssets.getMaterial(this.characterType).clone())

Cloning an FBX model from Mixamo also breaks with the same issue: Skeleton/bones aren't cloned. If you attempt to clone them manually, the entire model isn't rendered at all.

R87. Using the latest FBXLoader that was updated 8 days ago at time of writing.

This would be a huge help, I also have a templating/pool system of sorts. Running face first into this problem implementing animations now.

@inear solution works, thanks. I had to add

clonedGeometry.animations = refToOriginalMesh.geometry.animations

as well. Deep copy does not work in that case for some reason but ref does.

I just find this issue happed in my game today. I make a pool for my game object, and copy that when I need more one, but the animation is not copyed, case a bug in my game 😢
Update:
If I reference animations array to the original one, the cloned mesh is not show , this is also reported in https://github.com/mrdoob/three.js/issues/11574

Proposed fix: https://github.com/mrdoob/three.js/pull/14494.

This provides a way of cloning the SkinnedMesh, but does not duplicate .animations because there isn't any single documented place to put animations... they're not necessarily associated with a geometry. And in any case, clips don't actually need to be cloned —

mixer.clipAction( clip, mesh1 ).play();
mixer.clipACtion( clip, mesh2 ).play();

Following up here, https://github.com/mrdoob/three.js/pull/14494 includes a utility function to help with cloning skinned meshes:

var copy = SkeletonUtils.clone( original );

The object given as a parameter may be a SkinnedMesh or an Object3D or Scene containing one or more SkinnedMesh instances. Any bones referenced by the SkinnedMesh(es) must also be children of the given object.

Was this page helpful?
0 / 5 - 0 ratings