Synchronize animation

Hello all, I am trying to synchronize animations between multiple Spatial(s) that have have identical skeletons and animations (the bones and the transformations associated with them during animation, as well as the naming of the transformations, as well as the name of the bones)

My current thought process is to create two animation channels and manually synchronize their animations, although I am hoping there may be a way to attach one Spatial’s Skeleton to the other’s AnimationControl so that I am able to animate all of the (identical) children Skeletons simultaneously.

What are your thoughts on how to approach this?

Maybe something to keep in mind: I need to be able to swap Spatials to be animated (they will all have all identical Skeletons and Animations) .

After reading through the forums I saw this post:

This looks useful but I’m not sure how I’d make use of it. It seems that Momoko is saying that you can create a Node, extract the AnimationControl from a spatial and assign it to that Node, then attach the Spatials and they will be synchronized? I think I am probably not understanding this correctly, can someone explain what they’re saying in a different way potentially? Thanks all.

I believe the cleanest solution would be to attach all the models to a single Node with a single AnimControl and a single SkeletonControl, then delete the per-model controls. Perhaps that’s what Momoko was suggesting.

1 Like

@sgold, If I delete the per-model controls then the spatials will lose their vertex weights I believe and not deform correctly with the animation but instead just follow the transformation of the node they are assigned to.

I was reading through this thread: [SOLVED] Problem with attaching animated model to bone attachmentNode - #4 by pspeed and read this comment here:

@pspeed, do you mean that I should keep the per-model SkeletonControl and AnimControl and then call

node2.setLocalTranslation(node1.getLocalTranslation());//where node1 is the attachment node
node2.setLocalRotation(node1.getLocalRotation());

and manually manage the animations of each?

I have no idea… that was a long time ago. :slight_smile:

1 Like

I’m not sure about how to make the spatials share a single animation and Skeleton control without affecting the animation.

But if you don’t manage to figure that out, you can always use the getTime() and setTime() methods every frame to manually sync up all of the animation control so that their animations match.
https://javadoc.jmonkeyengine.org/com/jme3/animation/AnimChannel.html#getTime--

I never hit any performance issues doing it this way, but im also not sure how many animation controls and channels you’re trying to sync

1 Like

Is your belief based on experience or imagination?

In JME, vertex weights are stored in meshes, not controls, so I don’t see how removing controls could cause them to be lost.

1 Like

This is how the model’s mesh should be transformed during an animation:

This is how the mesh is transformed during the same animation but after deleting it’s original SkeletonControl and AnimControl and adding it to the other model’s Node that should have identical Controls as the initial mesh

I think I’ll just stick with keeping the Controls separate and unmodified, and manually manage the animations if they need to be separate. Thanks, as usual, for all of the information everyone; I really appreciate the help.

1 Like

FWIW, here’s what I had in mind:

package mygame;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.SkeletonControl;
import com.jme3.app.SimpleApplication;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Box;

public class Main extends SimpleApplication {

    final private Node animNode = new Node("animNode");

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

    @Override
    public void simpleInitApp() {
        flyCam.setDragToRotate(true);
        flyCam.setMoveSpeed(4f);
        cam.setLocation(new Vector3f(0f, 4.54f, 6.33f));
        cam.setRotation(new Quaternion(0f, 0.95f, -0.31f, 0f));

        viewPort.setBackgroundColor(ColorRGBA.Gray);
        addLighting();
        addBox();
        rootNode.attachChild(animNode);

        Node original = loadModel();
        /*
         * Remove AnimControl and SkeletonControl from the original model.
         */
        AnimControl ac = original.getControl(AnimControl.class);
        original.removeControl(ac);
        SkeletonControl sc = original.getControl(SkeletonControl.class);
        original.removeControl(sc);
        /*
         * Add 48 models (without controls) to the animNode.
         */
        for (int x = -3; x <= 3; x++) {
            for (int z = -3; z <= 3; z++) {
                if (x != 0 || z != 0) {
                    Node model = loadModel();
                    model.removeControl(AnimControl.class);
                    model.removeControl(SkeletonControl.class);
                    animNode.attachChild(model);
                    model.move(x, 0f, z);
                }
            }
        }
        /*
         * Add the original model (without controls) to the animNode.
         */
        animNode.attachChild(original);
        /*
         * Add the AnimControl and SkeletonControl to the animNode.
         */
        animNode.addControl(ac);
        animNode.addControl(sc);
        /*
         * A single AnimChannel controls all 49 models.
         */
        AnimChannel animChannel = ac.createChannel();
        animChannel.setAnim("Dance");
    }

    /**
     * Add a large static box to serve as a platform.
     */
    private void addBox() {
        float halfExtent = 50f; // mesh units
        Mesh mesh = new Box(halfExtent, halfExtent, halfExtent);
        Geometry geometry = new Geometry("box", mesh);
        rootNode.attachChild(geometry);

        geometry.move(0f, -halfExtent, 0f);
        ColorRGBA color = new ColorRGBA(0.1f, 0.4f, 0.1f, 1f);
        String shadedMaterialAssetPath
                = "Common/MatDefs/Light/Lighting.j3md";
        Material material = new Material(assetManager, shadedMaterialAssetPath);
        material.setBoolean("UseMaterialColors", true);
        material.setColor("Ambient", color.clone());
        material.setColor("Diffuse", color.clone());
        geometry.setMaterial(material);
    }

    /**
     * Add lighting and shadows to the scene.
     */
    private void addLighting() {
        ColorRGBA ambientColor = new ColorRGBA(0.7f, 0.7f, 0.7f, 1f);
        AmbientLight ambient = new AmbientLight(ambientColor);
        rootNode.addLight(ambient);

        Vector3f direction = new Vector3f(1f, -2f, -1f).normalizeLocal();
        DirectionalLight sun = new DirectionalLight(direction);
        rootNode.addLight(sun);
    }

    private Node loadModel() {
        Node model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
        model.scale(0.224f);
        model.move(0f, 1.112f, 0f);
        return model;
    }
}

2 Likes

Ok, this thread just became interesting.

1 Like