[SOLVED] Change a mesh skeleton?

Hi!

I’m trying to put together a wearable system for my avatar. The root model has just a skeleton (nothing visible at least), while each wearable piece (shirt, for example) was exported as a skinned model with the same skeleton as the main (and the shirt mesh).

I’m trying to figure out how to associate the skinned shirt with the skeleton of the root model. How is that done? It’s not as straight forward as shirt.skinInstance.bones = root.skinInstance.bones, is it?.

Can someone point me in the right direction?

Thanks!

Hello, not sure about that coz never used that yet but i guess for a non rigged model it could be similar to how you attach a weapon the the hand bone, but if you already have the skeleton in the shirt mesh i guess you will have 2 skeletons in 2 different models (character/shirt). But since i’m not sure myself i wait for a better reply too.

Right now API for working with bones is actually not exposed, but this should still be achievable, but only if you dig a bit inside of Skeleton stuff.

So you want to have probably a mesh-less or base model, which is animated. Then you want to have some other skinned meshes, wearables, but they would be not animated.

Then you need to somehow change Nodes on Skeleton from wearable model to the base model. So it will get attached to main skeleton.
This method essentially binds graph nodes to interpolated keys.
If you provide two skinned models: one base, and one wearable, and one animation for base. Making sure that wearable skin has some of bones with same names of base, I can try to put an example.
It might be quiet simple, but need to check.

Awesome.

I put together a public project with the pieces you asked for:

https://playcanvas.com/project/465615/overview/wearablestest

Thanks!

Thank you for an example, it is just perfect. So I’ve played a bit with things, and tried many different ways. Turns out simply setting bone worldTransform to base bone worldTransform - does the trick.

So here is a project with working example: https://playcanvas.com/project/465632/overview/wearableskeleton
Things to notice: both base and wearable should have animation component, to ensure skeleton and bones are created for them. But you will animate only base model, all wearables will update automatically.

Find bonus script to visualize meshInstances and bones :wink:

And actual script to bind wearable to base skeleton.

var Wearable = pc.createScript('wearable');

// entity that wearable relates to
Wearable.attributes.add('base', { type: 'entity' });

Wearable.prototype.initialize = function() {
    if (! this.base || ! this.entity.animation || ! this.base.animation)
        return;
    
    // index of own bones
    this.bonesIndex = { };
    
    // make an index of bones
    this.indexBones(this.entity.animation.skeleton.graph);
    
    // bind bones to base entity skeleton
    this.bindBones(this.base.animation.skeleton.graph);
};

Wearable.prototype.indexBones = function(node) {
    this.bonesIndex[node.name] = node;
    
    for(var i = 0; i < node.children.length; i++)
        this.indexBones(node.children[i]);
};

Wearable.prototype.bindBones = function(node) {
    if (this.bonesIndex[node.name])
        this.bonesIndex[node.name].worldTransform = node.worldTransform;

    for(var i = 0; i < node.children.length; i++)
        this.bindBones(node.children[i]);
};

Woot!

Do I need to do any disposal of the wearable bones/etc? If I have a bunch of these around (shirt/face/hair/pants/shoes/etc) * nPlayers, is there going to be animation/bone information no longer used that is part of each wearable?

Thanks again for the quick turn-around. The bone visualizer really helps.

Fantastic support!

I did thought about that extra bones are using now extra data. And unfortunately animation is one of the oldest part of engine that hasn’t been touched for long ago. I don’t really know how to dispose them, and not sure even you can.

So yes, there is some overhead of having that many models. But you probably need to test and see how it performs, it might be not a problem at all.

We are thinking a lot about new Animation system, that would be built from ground-up as new thing. It won’t be easy task, but we have loads of plans for it. But it won’t come any time soon.

I can live with that :slight_smile: Hopefully the additional overhead won’t be a problem.

I did run into one issue, though. I wrote a script for the base model that will allow it to load a wearable dynamically via a call to WearableBase.addWearable(“male-shirt.json”):

    // find the new wearable
    var asset = this.app.assets.find(wearableName, "model");
    asset.ready( function(wearableAsset) {
        // wearable is loaded and ready
        console.log(asset.name + " is ready...");   

        var wearableEntity = new pc.Entity();  
        // add it to our heirarchy
        this.entity.addChild(wearableEntity);  
        
        // add the loaded model
        wearableEntity.addComponent("model", {
            type: "asset",
            asset: wearableAsset
        });    
        
        // a wearable must have an animation component
        wearableEntity.addComponent("animation");
        
        // add the wearable script to the wearable
        wearableEntity.addComponent("script");
        wearableEntity.script.create("wearable", {
            attributes: {
                base: this.entity
            }
        });

It appears that when I load a model this way via the asset manager, there is no skeleton (or graph) on the model. This results in a failure here: (from your ‘wearables.js’):

    // make an index of bones
    this.indexBones(this.entity.animation.skeleton.graph);

This may be out of the scope of this question, but is there some other initialization I need to do when loading a model dynamically to ensure it has a skeleton (and skeleton.graph) so your code can index them?

Got it! You have to add the animation component before you add the model component. The add_model trigger is caught by the animation component and the skeleton/graph is built then. Hooray for open source and debuggers! You may want to put a check in there to see if a model was already added and retroactively apply.

I’ll close this thread, but I’ll push the two files up if anyone is interested in using them for their project.

Thanks again, Max!

1 Like

Disclaimer: I haven’t thoroughly tested this, YMMV!

wearable-base.js (applied to the empty base model that has a skeleton and animations)

Usage: MyBaseEntity.scripts.wearableBase.addWearable(WearableSlot.Shirt, ‘myShirt.json’);

var WearableBase = pc.createScript('wearableBase');

var WearableSlot = {
    Shirt : 0,
    Pants : 1,
    Shoes : 2,
    Head : 3,
    Hair : 4
};

// initialize code called once per entity
WearableBase.prototype.initialize = function() {
    if (!this.entity.animation) {
        // must have an animation component for wearables to access
        this.entity.addComponent("animation");
    }
    
    // keep track of what wearables we are wearing
    this.wearables = []; 

    this.addWearable(WearableSlot.Shirt, "male-shirt.json");
};


WearableBase.prototype.addWearable = function(slot, wearableName) {
    
    // remove any wearable that was in that slot
     if (this.wearables[slot]) {
        var w = this.wearables[slot];
        this.entity.removeChild(w);
        w.destroy();
        this.wearable[slot] = null;
    }
    
    // find the wearable model
    var wearableAsset = this.app.assets.find(wearableName, "model");
    if (!wearableAsset) {
        console.error('Asset Registry was unable to find ' + wearableName);
        return;
    }
    wearableAsset.ready( function(asset) {
        // wearable model is loaded and ready.  Create an entity and associate the model with it
        console.log(asset.name + " is ready...");   

        var wearableEntity = new pc.Entity();  
        // add it to our heirarchy
        this.entity.addChild(wearableEntity);  

        // a wearable must have an animation component
        wearableEntity.addComponent("animation");

        // add the loaded model
        wearableEntity.addComponent("model", {
            type: "asset",
            asset: asset
        });    
        
        
        // add wearable.js to the script component 
        // note that it has to be tagged as 'preload'!
        wearableEntity.addComponent("script");
        wearableEntity.script.create("wearable", {
            attributes: {
                base: this.entity
            }
        });
        // save away the wearable so we can limit to one per slot
        this.wearables[slot] = wearableEntity;

    }.bind(this));
    // kick off the load    
    this.app.assets.load(wearableAsset);    
};

wearable.js (this has to be marked as ‘preload’ so the wearable-base script can find it.)

var Wearable = pc.createScript('wearable');

// entity that wearable relates to
Wearable.attributes.add('base', { type: 'entity' });

Wearable.prototype.initialize = function() {
    // The wearable must reference a base entity that includes a skeleton for skinning
    if (! this.base) {
        console.warn("No base entity assigned to wearable. Ignoring skinning.");
        return;
    }

    // The wearable has to have an animation component for the wearable bones to bind to
    if (! this.base.animation) {
        this.base.addComponent("animation");
    }

    // The wearable has to have an animation component for the bones to bind from
    // Note: there shouldn't be an acutal animation playing, just the component
    if (!this.entity.animation) {
        this.entity.addComponent("animation");
    }
};

Wearable.prototype.postInitialize = function () {
    // index of own bones
    this.bonesIndex = { };
    
    // make an index of bones
    this.indexBones(this.entity.animation.skeleton.graph);
    
    // bind bones to base entity skeleton
    this.bindBones(this.base.animation.skeleton.graph);
};

Wearable.prototype.indexBones = function(node) {
    this.bonesIndex[node.name] = node;
    
    for(var i = 0; i < node.children.length; i++)
        this.indexBones(node.children[i]);
};

Wearable.prototype.bindBones = function(node) {
    if (this.bonesIndex[node.name])
        this.bonesIndex[node.name].worldTransform = node.worldTransform;

    for(var i = 0; i < node.children.length; i++)
        this.bindBones(node.children[i]);
};
1 Like

Hi,
I am trying to create “avatar” demo, where user can change clothes (top, pant etc.).
I have replicated this demo with downloaded character and skinned it with 3ds max biped system.
But the skinned meshes are not taking base (empty biped) biped’s animation.

Can anybody point out where is the issue.

https://playcanv.as/p/pJfP6UHd/

Cheers…

1 Like

I’ve been looking for something like this.

Were you able to get it running? @rknopf

Could you share with me the structure of how the meshes need to be prepared and uploaded in order to properly be swapped in real time?

UPDATE: Got it to work!

Hi,

I am looking for a demo project to make wearable character.

@UttavirojPrasert Hello and welcome!

https://playcanvas.com/project/730082/overview/gamepad-third-person-controller

I am not sure this is going to help you or not but here you can find way to attach weapon to hand…
Maybe it could help with wearable thing…