Monday 18 May 2020

Clone of a SkinnedMesh (loaded Model) in THREE.JS with animations

One of my biggest annoyances with using loaded models in THREE.JS was the failure to clone them, and more importantly, animate the clone.  Without the ability to clone a skinned mesh or loaded model and then independently animate it, I was forced to keep loading the model from file.  This was very slow and resulted in load loading times.

I tried everything...  I discovered a clone method on SkeletonUtils which would supposedly work. but I just got errors or a mesh that would not animate.  I even found functions written and published by other people, but these also did not seem to work.  I did my best to optimise the load from file to speed things up, but it was useless.

So, I created my own function to perform the clone operation...  Being an ammeter with only a simple understanding of the THREE.JS object model, I set about writing the function to clone any Object3D.  I had observed that many of the models I was working with featured a hierarchy of objects of types Group, SkinnedMesh or Bone (others currently unsupported, but probably a simple change.)  I then used comparison techniques and trial and error until things started to work.

I then created a function specific to the SkinnedMesh type, because I needed to copy some important properties only relevant to this type.  Finally, I created a function to copy any userData.

The functions are a work-in-progress and might need a tweak to work with other models, but it dramatically helped my project.

Here is the three functions I ended up with...

    public cloneObject3D(objTHREE.Object3DparentTHREE.Object3D): THREE.Object3D {
        let retTHREE.Object3D = null;
        switch(obj.type) {
            case "Group"ret = new THREE.Group(); break;
            case "SkinnedMesh"ret = this.cloneSkinnedMesh(obj as THREE.SkinnedMeshparent); break;
            case "Bone"ret = (obj as THREE.Bone).clone(); break;
            defaultconsole.log('Unknown Clone Type: ' + obj.type); ret = new THREE.Object3D();
        }
        obj.children.forEach((c,i=> {
            ret.add(this.cloneObject3D(c,ret));
        });
        if (obj["ID"]) { ret["ID"] = obj["ID"]; }
        ret.name = obj.name;
        ret.position.set(obj.position.x,obj.position.y,obj.position.z);
        ret.rotation.set(obj.rotation.x,obj.rotation.y,obj.rotation.z);
        ret.scale.set(obj.scale.xobj.scale.y,obj.scale.z);
        ret.quaternion.set(obj.quaternion.x,obj.quaternion.y,obj.quaternion.z,obj.quaternion.w);
        ret.matrixWorldNeedsUpdate = obj.matrixWorldNeedsUpdate;
        ret.userData = this.cloneAny(obj.userData);
        return ret;
    }

    public cloneSkinnedMesh(objTHREE.SkinnedMeshparentTHREE.Object3D): THREE.SkinnedMesh {
        let geo = obj.geometry.clone();
        let mat = obj .material;
        mat = (obj.material as THREE.MeshPhongMaterial).clone();
        let ret = new THREE.SkinnedMesh(geo,mat); 
        let bones = new Array<THREE.Bone>();
        
        obj.skeleton.bones.forEach((b=> { 
            let nb = parent.getObjectByName(b.nameas THREE.Bone;
            bones.push(nb); 
        });

        ret.skeleton = new THREE.Skeleton(bones);

        obj.skeleton.boneMatrices.forEach((bm,bmi=> {
            ret.skeleton.boneMatrices[bmi] = bm;
        });
        obj.skeleton.boneInverses.forEach((bi,bii=> {
            ret.skeleton.boneInverses[bii] = bi;
        });
        obj.bindMatrix.elements.forEach((e,ei=> {
            ret.bindMatrix.elements[ei] = e;
        });
        obj.bindMatrixInverse.elements.forEach((e,ei=> {
            ret.bindMatrixInverse.elements[ei] = e;
        });
                
        return ret;
    }

    public cloneAny(objany): any {
        let ret = {};
        Object.getOwnPropertyNames(obj).forEach((pn=> {
            ret[pn] = obj[pn];
        });
        return ret;
    }

No comments:

Post a Comment