(Solved) Binding different models into Character

I suppose you could create a character node for everything to be glued to, than attach both the character model and gloves model to it and they’d overlap seamlessly. Than just call the animation on both of them simultaneously if they use the same bone structure. A bit cumbersome as your working two animations at once, but it might work for what you’re saying.

So I guess you may just animate the characted fully geared in blender with a separate mesh for every cloth. You then put everything in the same node and control what’s shown by attaching/detaching the clothes or culling them.
I’m not doing exactly that in my game but it works, at least for the hair of my characters :smile:.

This is how my models are set out

character -
---Arms.mesh.xml           <Whole Skeleton
---Hands.mesh.xml          <Whole Skeleton
---Head.mesh.xml           <Whole Skeleton
---Pelvis.mesh.xml         <Whole Skeleton
---Legs.mesh.xml           <Whole Skeleton
---Feet.mesh.xml           <Whole Skeleton
---Upperbody.mesh.xml 	   <Whole Skeleton + animation
---WholeCharacter.mesh.xml <Whole Skeleton + animation

*Armor -
---boots.mesh.xml      <Whole Skeleton
---cuirass.mesh.xml    <Whole Skeleton
---gauntlets.mesh.xml  <Whole Skeleton
---greaves.mesh.xml    <Whole Skeleton
---healmet.mesh.xml    <Whole Skeleton


--ironArmor
--furArmor
--steelArmor
... (another 15 armor sets)

So animation re-targeting would really be the best option here, the skeleton for every model is the same (i.e all the boots.mesh.xml models have the bones for the hands and spine and everything)

I don’t know how much work it means for you but it shouldn’t be too long : import all the parts in blender, animate them as a whole character, put everything (anim control + skel control) under the same node in jme, and attach or detach those elements to hide or show them.

each object would still be a separate file, and you’d just load in the one you wanted at that particular moment of gameplay. No culling involved though, as you load in the specific model.

charNode.attachChild(charcter);
charNode.attachChild(Gauntlets);
//or for leather gloves
charNode.attachChild(greaves);

and since each model shares the same skeleton, call your AnimChanel.setAnim(“random_anim”) on both of them, and they deform together in the same node space. (parallel animations)

I have this

Main class:

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }

    @Override
    public void simpleInitApp() {
        flyCam.setMoveSpeed(10f);
        cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));
        cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 1000f);
        
        DirectionalLight dl = new DirectionalLight();
        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
        dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f));
        rootNode.addLight(dl);
        
        String[] parts = {"Models/character/Head.mesh.xml"        /*0 = Head*/,
                          "Models/character/UpperBody.mesh.xml"   /*1 = UpperBody*/,
                          "Models/character/Arms.mesh.xml"        /*2 = Arms*/,
                          "Models/character/Hand.mesh.xml"        /*3 = Hands*/,
                          "Models/character/LowerBody_1.mesh.xml" /*4 = Pelvis*/,
                          "Models/character/LowerBody_0.mesh.xml" /*5 = Legs*/,
                          "Models/character/Foot.mesh.xml"        /*6 = Feet*/};
        Player.Bodyparts = parts;
        
       // Player.Bodyparts[0] = "Models/_MedNeutral.mesh.xml";/*0 = Head*/
       // Player.Bodyparts[1] = "Models/UpperBody.mesh.xml";/*1 = UpperBody*/
       // Player.Bodyparts[2] = "Models/Arms.mesh.xml";/*2 = Arms*/
       // Player.Bodyparts[3] = "Models/Hand.mesh.xml";/*3 = Hands*/
       // Player.Bodyparts[4] = "Models/LowerBody_1.mesh.xml";/*4 = Pelvis*/
       // Player.Bodyparts[5] = "Models/LowerBody_0.mesh.xml";/*5 = Legs*/
       // Player.Bodyparts[6] = "Models/Foot.mesh.xml";/*6 = Feet*/
        
        Player.createPlayer(assetManager, rootNode);
    }

    @Override
    public void simpleUpdate(float tpf){
    }
}

Player class

package mygame;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.LoopMode;
import com.jme3.animation.SkeletonControl;
import com.jme3.asset.AssetManager;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.debug.SkeletonDebugger;


public class Player {
    
    static Node Head, UpperBody, Arms, Hands,/*lowerBody1=Pelvis*/ Pelvis, /*lowerNody0=Legs*/ Legs, Feet;
    static String[] Bodyparts = new String[7];
    static AnimControl[] control = new AnimControl[7];
    static SkeletonDebugger skeletonDebug;
    
    public static void createPlayer(AssetManager assetManager, Node rootNode){
     
        Head = (Node) assetManager.loadModel(Bodyparts[0]);
        UpperBody = (Node) assetManager.loadModel(Bodyparts[1]);
        Arms = (Node) assetManager.loadModel(Bodyparts[2]);
        Hands = (Node) assetManager.loadModel(Bodyparts[3]);
        Pelvis = (Node) assetManager.loadModel(Bodyparts[4]);
        Legs = (Node) assetManager.loadModel(Bodyparts[5]);
        Feet = (Node) assetManager.loadModel(Bodyparts[6]);
        
        UpperBody.attachChild(Head);
        UpperBody.attachChild(Arms);
        UpperBody.attachChild(Hands);
        UpperBody.attachChild(Pelvis);
        UpperBody.attachChild(Legs);
        UpperBody.attachChild(Feet);
        
        control[0] = Head.getControl(AnimControl.class);
        control[1] = UpperBody.getControl(AnimControl.class);
        control[2] = Arms.getControl(AnimControl.class);
        control[3] = Hands.getControl(AnimControl.class);
        control[4] = Pelvis.getControl(AnimControl.class);
        control[5] = Legs.getControl(AnimControl.class);
        control[6] = Feet.getControl(AnimControl.class);
        
        AnimChannel HeadChannel = control[0].createChannel();
        AnimChannel UpperBodyChannel = control[1].createChannel();
        AnimChannel ArmsChannel = control[2].createChannel();
        AnimChannel HandsChannel = control[3].createChannel();
        AnimChannel PelvisChannel = control[4].createChannel();
        AnimChannel LegsChannel = control[5].createChannel();
        AnimChannel FeetChannel = control[6].createChannel();
        
        HeadChannel.addAllBones();
        HeadChannel.setAnim("my_animation");
        HeadChannel.setSpeed(1);
        HeadChannel.setLoopMode(LoopMode.Loop);
        
        UpperBodyChannel.addAllBones();
        UpperBodyChannel.setAnim("my_animation");
        UpperBodyChannel.setSpeed(1);
        UpperBodyChannel.setLoopMode(LoopMode.Loop);
        
        ArmsChannel.addAllBones();
        ArmsChannel.setAnim("my_animation");
        ArmsChannel.setSpeed(1);
        ArmsChannel.setLoopMode(LoopMode.Loop);
        
        HandsChannel.addAllBones();
        HandsChannel.setAnim("my_animation");
        HandsChannel.setSpeed(1F);
        HandsChannel.setLoopMode(LoopMode.Loop);
        
        PelvisChannel.addAllBones();
        PelvisChannel.setAnim("my_animation");
        PelvisChannel.setSpeed(1);
        PelvisChannel.setLoopMode(LoopMode.Loop);
        
        LegsChannel.addAllBones();
        LegsChannel.setAnim("my_animation");
        LegsChannel.setSpeed(1);
        LegsChannel.setLoopMode(LoopMode.Loop);
        
        FeetChannel.addAllBones();
        FeetChannel.setAnim("my_animation");
        FeetChannel.setSpeed(1);
        FeetChannel.setLoopMode(LoopMode.Loop);
    
        showSkeleton(assetManager, control[1]);
        
        UpperBody.attachChild(skeletonDebug);
        
        UpperBody.setLocalTranslation(new Vector3f(6.4013605f, 7.488437f, 12.843031f));
        UpperBody.rotate(0F, 3F, 0F);
        
        rootNode.attachChild(UpperBody);
    }
    
    private static void showSkeleton(AssetManager assetManager, AnimControl control){
        skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton());
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.getAdditionalRenderState().setWireframe(true);
        mat.setColor("Color", ColorRGBA.Green);
        mat.getAdditionalRenderState().setDepthTest(false);
        skeletonDebug.setMaterial(mat);
    }
}

This is pretty much exactly how I want it, I just need to retarget the animations for the UpperBody.mesh.xml to the other loaded models, instead of having 100 models with 40 animations each, I could have just one model with 40 animations, and 99 models with just skeletons.

I don’t believe that there’s a way to retarget animations in jmonkey, (as you can in unreal4) yet there might be a way to fake it. (kinda) since all of your models share the same bone structure, you can replace the .skeleton.xml files in all of your asset folders with ones that contain the animation, and make the animation calls on each attached object simultaneously. This way, you’d be calling separate animations on separate models still, but your ‘retargeting’ is just replacing all of the skeletons. (The mesh.xml is the file that tells JME which vertexes are parented to which bones, so as long as the names of your bones remain constant you can swap out the skeletons.)

1 Like

There is an experimental project about it. Allows to retarget a BVH animations to an arbitrary skeleton.

if the skeleton is exactly the same there is no need to retarget btw…you just have to add the animation the the AnimControl of the second model.

Thanks, this worked! :smile:

How could I do that?

Let’s say you have 2 models with the exact same skeleton (not just the names of the bones, the exact same).
you’ll have 2 models with an AnimControl each.

You just have to do :

Animation theAnim = model1.getControl(AnimControl.class).getAnim("nameOfTheAnim");
model2.getControl(AnimControl.class).addAnim("nameOfTheAnim", theAnim);
1 Like

If you know both models have the same skeleton, then they can be controlled by the same AnimControl by attaching the geometries of both under the node with the AnimControl.