Friday 22 May 2020

R2D2 Animated and in THREE.JS

My efforts have payed off, I have been successful in exporting my R2D2 from Blender to THREE.JS and Animating him.  I have also learnt a lot more about the THREE.JS animation system.

Monday 18 May 2020

I made R2D2 (in Blender..)

I'm not creative or artistic.  I have don't see myself making interior designs or 3D rendered works of art.  But I am increasingly using 3D models in my visualisation work (and playing with AR.)  So I'm highly dependant upton the 3D models I can get for free or purchase cheaply.

This is a problem for me...  As I have a very small budget and many of the things I've wantted are way out of my budget.  And so, I needed to learn how to make some of the models I need myself.

So far, I have made a Donut, an Anvil, a Chair, a Coke Can, Titanic and R2D2.  I can see that I will always be an ammeter at 3D modeling, but I'm happy with my results.

I had no real need for R2D2, but I felt he was an interesting subject to learn and practice my skils I'd learnd from YouTube (most specificly Blender Guru.)  

I made him twice, the first time was based on reference photos from Google Images, this was ok, but I cut a lot of corners...  The second attempt was based on images and blueprints from https://astromech.net/.  He's not entirly acurate, and Not fully finished, but he will do and I have learnt alot.

R2D2 3D Model

My reason for modeling R2D2 was to improve my skills.  I had attempted a few modeling projects for objects I wantted, but they failed (attempt 2 coming soon). I ultimatly wanted to animate R2D2, but that's not currently going very well either.

For now, I'm just happy with the model as an early result in my 3D modeling skills.

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;
    }