Преглед изворни кода

Adds a MigrationUtil to migrate from old system to new system

Nehon пре 7 година
родитељ
комит
a4267393e1

+ 9 - 2
jme3-core/src/main/java/com/jme3/anim/AnimComposer.java

@@ -4,8 +4,7 @@ import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
 import com.jme3.scene.control.AbstractControl;
 
-import java.util.HashMap;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Created by Nehon on 20/12/2017.
@@ -65,6 +64,14 @@ public class AnimComposer extends AbstractControl {
         time = 0;
     }
 
+    public Collection<AnimClip> getAnimClips() {
+        return Collections.unmodifiableCollection(animClipMap.values());
+    }
+
+    public Collection<String> getAnimClipsNames() {
+        return Collections.unmodifiableCollection(animClipMap.keySet());
+    }
+
     @Override
     protected void controlUpdate(float tpf) {
         if (currentAnimClip != null) {

+ 1 - 1
jme3-core/src/main/java/com/jme3/anim/Armature.java

@@ -169,7 +169,7 @@ public class Armature implements JmeCloneable, Savable {
     }
 
     /**
-     * Saves the current Armature state as it's bind pose.
+     * Saves the current Armature state as its bind pose.
      */
     public void setBindPose() {
         //make sure all bones are updated

+ 0 - 11
jme3-core/src/main/java/com/jme3/anim/Joint.java

@@ -34,11 +34,6 @@ public class Joint implements Savable, JmeCloneable {
      * Or relative to the model's origin for the root joint.
      */
     private Transform localTransform = new Transform();
-    /**
-     * The base transform of the joint in local space.
-     * Those transform are the joint's initial value.
-     */
-    private Transform baseLocalTransform = new Transform();
 
     /**
      * The transform of the joint in model space. Relative to the origin of the model.
@@ -134,7 +129,6 @@ public class Joint implements Savable, JmeCloneable {
         //Note that the whole Armature must be updated before calling this method.
         getModelTransform().toTransformMatrix(inverseModelBindMatrix);
         inverseModelBindMatrix.invertLocal();
-        baseLocalTransform.set(localTransform);
     }
 
     protected void resetToBindPose() {
@@ -267,8 +261,6 @@ public class Joint implements Savable, JmeCloneable {
         this.children = cloner.clone(children);
         this.attachedNode = cloner.clone(attachedNode);
         this.targetGeometry = cloner.clone(targetGeometry);
-
-        this.baseLocalTransform = cloner.clone(baseLocalTransform);
         this.localTransform = cloner.clone(localTransform);
         this.jointModelTransform = cloner.clone(jointModelTransform);
         this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix);
@@ -283,8 +275,6 @@ public class Joint implements Savable, JmeCloneable {
         name = input.readString("name", null);
         attachedNode = (Node) input.readSavable("attachedNode", null);
         targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
-        baseLocalTransform = (Transform) input.readSavable("baseLocalTransforms", baseLocalTransform);
-        localTransform.set(baseLocalTransform);
         inverseModelBindMatrix = (Matrix4f) input.readSavable("inverseModelBindMatrix", inverseModelBindMatrix);
         jointModelTransform = (JointModelTransform) input.readSavable("jointModelTransform", null);
 
@@ -301,7 +291,6 @@ public class Joint implements Savable, JmeCloneable {
         output.write(name, "name", null);
         output.write(attachedNode, "attachedNode", null);
         output.write(targetGeometry, "targetGeometry", null);
-        output.write(baseLocalTransform, "baseLocalTransform", new Transform());
         output.write(inverseModelBindMatrix, "inverseModelBindMatrix", new Matrix4f());
         output.writeSavableArrayList(children, "children", null);
         output.write(jointModelTransform, "jointModelTransform", null);

+ 170 - 5
jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java

@@ -1,24 +1,189 @@
 package com.jme3.anim.util;
 
-import com.jme3.animation.AnimControl;
-import com.jme3.scene.SceneGraphVisitor;
-import com.jme3.scene.Spatial;
+import com.jme3.anim.*;
+import com.jme3.animation.*;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.*;
+
+import java.util.*;
 
 public class AnimMigrationUtils {
 
+    private static AnimControlVisitor animControlVisitor = new AnimControlVisitor();
+    private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor();
+
+
     public static Spatial migrate(Spatial source) {
-        //source.depthFirstTraversal();
+        Map<Skeleton, Armature> skeletonArmatureMap = new HashMap<>();
+        animControlVisitor.setMappings(skeletonArmatureMap);
+        source.depthFirstTraversal(animControlVisitor);
+        skeletonControlVisitor.setMappings(skeletonArmatureMap);
+        source.depthFirstTraversal(skeletonControlVisitor);
         return source;
     }
 
-    private class AnimControlVisitor implements SceneGraphVisitor {
+    private static class AnimControlVisitor implements SceneGraphVisitor {
+
+        Map<Skeleton, Armature> skeletonArmatureMap;
 
         @Override
         public void visit(Spatial spatial) {
             AnimControl control = spatial.getControl(AnimControl.class);
             if (control != null) {
+                AnimComposer composer = new AnimComposer();
+                Skeleton skeleton = control.getSkeleton();
+                if (skeleton == null) {
+                    //only bone anim for now
+                    return;
+                }
+
+                Joint[] joints = new Joint[skeleton.getBoneCount()];
+                for (int i = 0; i < skeleton.getBoneCount(); i++) {
+                    Bone b = skeleton.getBone(i);
+                    Joint j = joints[i];
+                    if (j == null) {
+                        j = fromBone(b);
+                        joints[i] = j;
+                    }
+                    for (Bone bone : b.getChildren()) {
+                        int index = skeleton.getBoneIndex(bone);
+                        Joint joint = joints[index];
+                        if (joint == null) {
+                            joint = fromBone(bone);
+                        }
+                        j.addChild(joint);
+                        joints[index] = joint;
+                    }
+                }
+
+                Armature armature = new Armature(joints);
+                armature.setBindPose();
+                skeletonArmatureMap.put(skeleton, armature);
+
+                for (String animName : control.getAnimationNames()) {
+                    Animation anim = control.getAnim(animName);
+                    AnimClip clip = new AnimClip(animName);
+                    Joint[] staticJoints = new Joint[joints.length];
+                    System.arraycopy(joints, 0, staticJoints, 0, joints.length);
+                    for (Track track : anim.getTracks()) {
+                        if (track instanceof BoneTrack) {
+                            BoneTrack boneTrack = (BoneTrack) track;
+                            int index = boneTrack.getTargetBoneIndex();
+                            Bone bone = skeleton.getBone(index);
+                            Joint joint = joints[index];
+                            JointTrack jointTrack = fromBoneTrack(boneTrack, bone, joint);
+                            clip.addTrack(jointTrack);
+                            //this joint is animated let's remove it from the static joints
+                            staticJoints[index] = null;
+                        }
+                    }
+
+                    for (int i = 0; i < staticJoints.length; i++) {
+                        Joint j = staticJoints[i];
+                        if (j != null) {
+                            // joint has no track , we create one with the default pose
+                            float[] times = new float[]{0};
+                            Vector3f[] translations = new Vector3f[]{j.getLocalTranslation()};
+                            Quaternion[] rotations = new Quaternion[]{j.getLocalRotation()};
+                            Vector3f[] scales = new Vector3f[]{j.getLocalScale()};
+                            JointTrack track = new JointTrack(j, times, translations, rotations, scales);
+                            clip.addTrack(track);
+                        }
+                    }
+
+                    composer.addAnimClip(clip);
+                }
+                spatial.removeControl(control);
+                spatial.addControl(composer);
+            }
+        }
+
+        public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
+            this.skeletonArmatureMap = skeletonArmatureMap;
+        }
+    }
+
+    private static class SkeletonControlVisitor implements SceneGraphVisitor {
+
+        Map<Skeleton, Armature> skeletonArmatureMap;
+
+        @Override
+        public void visit(Spatial spatial) {
+            SkeletonControl control = spatial.getControl(SkeletonControl.class);
+            if (control != null) {
+                Armature armature = skeletonArmatureMap.get(control.getSkeleton());
+                SkinningControl skinningControl = new SkinningControl(armature);
+                Map<String, List<Spatial>> attachedSpatials = new HashMap<>();
+                for (int i = 0; i < control.getSkeleton().getBoneCount(); i++) {
+                    Bone b = control.getSkeleton().getBone(i);
+                    Node n = control.getAttachmentsNode(b.getName());
+                    n.removeFromParent();
+                    if (!n.getChildren().isEmpty()) {
+                        attachedSpatials.put(b.getName(), n.getChildren());
+                    }
+                }
+                spatial.removeControl(control);
+                spatial.addControl(skinningControl);
+                for (String name : attachedSpatials.keySet()) {
+                    List<Spatial> spatials = attachedSpatials.get(name);
+                    for (Spatial child : spatials) {
+                        skinningControl.getAttachmentsNode(name).attachChild(child);
+                    }
+                }
 
             }
         }
+
+        public void setMappings(Map<Skeleton, Armature> skeletonArmatureMap) {
+            this.skeletonArmatureMap = skeletonArmatureMap;
+        }
     }
+
+    public static JointTrack fromBoneTrack(BoneTrack boneTrack, Bone bone, Joint joint) {
+        float[] times = new float[boneTrack.getTimes().length];
+        int length = times.length;
+        System.arraycopy(boneTrack.getTimes(), 0, times, 0, length);
+        //translation
+        Vector3f[] translations = new Vector3f[length];
+        if (boneTrack.getTranslations() != null) {
+            for (int i = 0; i < boneTrack.getTranslations().length; i++) {
+                Vector3f oldTrans = boneTrack.getTranslations()[i];
+                Vector3f newTrans = new Vector3f();
+                newTrans.set(bone.getBindPosition()).addLocal(oldTrans);
+                translations[i] = newTrans;
+            }
+        }
+        //rotation
+        Quaternion[] rotations = new Quaternion[length];
+        if (boneTrack.getRotations() != null) {
+            for (int i = 0; i < boneTrack.getRotations().length; i++) {
+                Quaternion oldRot = boneTrack.getRotations()[i];
+                Quaternion newRot = new Quaternion();
+                newRot.set(bone.getBindRotation()).multLocal(oldRot);
+                rotations[i] = newRot;
+            }
+        }
+        //scale
+        Vector3f[] scales = new Vector3f[length];
+        if (boneTrack.getScales() != null) {
+            for (int i = 0; i < boneTrack.getScales().length; i++) {
+                Vector3f oldScale = boneTrack.getScales()[i];
+                Vector3f newScale = new Vector3f();
+                newScale.set(bone.getBindScale()).multLocal(oldScale);
+                scales[i] = newScale;
+            }
+        }
+
+        return new JointTrack(joint, times, translations, rotations, scales);
+    }
+
+    private static Joint fromBone(Bone b) {
+        Joint j = new Joint(b.getName());
+        j.setLocalTranslation(b.getBindPosition());
+        j.setLocalRotation(b.getBindRotation());
+        j.setLocalScale(b.getBindScale());
+        return j;
+    }
+
 }

+ 25 - 10
jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java

@@ -6,8 +6,7 @@ package com.jme3.scene.debug.custom;
 
 import com.jme3.anim.*;
 import com.jme3.app.Application;
-import com.jme3.app.state.AbstractAppState;
-import com.jme3.app.state.AppStateManager;
+import com.jme3.app.state.BaseAppState;
 import com.jme3.collision.CollisionResults;
 import com.jme3.input.MouseInput;
 import com.jme3.input.controls.ActionListener;
@@ -22,15 +21,17 @@ import java.util.*;
 /**
  * @author Nehon
  */
-public class ArmatureDebugAppState extends AbstractAppState {
+public class ArmatureDebugAppState extends BaseAppState {
 
     private Node debugNode = new Node("debugNode");
     private Map<Armature, ArmatureDebugger> armatures = new HashMap<>();
     private Map<Armature, Joint> selectedBones = new HashMap<>();
     private Application app;
+    ViewPort vp;
+
     @Override
-    public void initialize(AppStateManager stateManager, Application app) {
-        ViewPort vp = app.getRenderManager().createMainView("debug", app.getCamera());
+    protected void initialize(Application app) {
+        vp = app.getRenderManager().createMainView("debug", app.getCamera());
         vp.attachScene(debugNode);
         vp.setClearDepth(true);
         this.app = app;
@@ -39,12 +40,26 @@ public class ArmatureDebugAppState extends AbstractAppState {
         }
         app.getInputManager().addListener(actionListener, "shoot");
         app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
-        super.initialize(stateManager, app);
-
 
         debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
 
         debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f)));
+        vp.setEnabled(false);
+    }
+
+    @Override
+    protected void cleanup(Application app) {
+
+    }
+
+    @Override
+    protected void onEnable() {
+        vp.setEnabled(true);
+    }
+
+    @Override
+    protected void onDisable() {
+        vp.setEnabled(false);
     }
 
     @Override
@@ -53,13 +68,13 @@ public class ArmatureDebugAppState extends AbstractAppState {
         debugNode.updateGeometricState();
     }
 
-    public ArmatureDebugger addArmature(SkinningControl skinningControl) {
+    public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) {
         Armature armature = skinningControl.getArmature();
         Spatial forSpatial = skinningControl.getSpatial();
-        return addArmature(armature, forSpatial);
+        return addArmatureFrom(armature, forSpatial);
     }
 
-    public ArmatureDebugger addArmature(Armature armature, Spatial forSpatial) {
+    public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) {
 
         ArmatureDebugger ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature);
         ad.setLocalTransform(forSpatial.getWorldTransform());

+ 147 - 0
jme3-examples/src/main/java/jme3test/model/anim/TestAnimMigration.java

@@ -0,0 +1,147 @@
+package jme3test.model.anim;
+
+import com.jme3.anim.AnimComposer;
+import com.jme3.anim.SkinningControl;
+import com.jme3.anim.util.AnimMigrationUtils;
+import com.jme3.app.ChaseCameraAppState;
+import com.jme3.app.SimpleApplication;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.light.AmbientLight;
+import com.jme3.light.DirectionalLight;
+import com.jme3.math.*;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.debug.custom.ArmatureDebugAppState;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Created by Nehon on 18/12/2017.
+ */
+public class TestAnimMigration extends SimpleApplication {
+
+    ArmatureDebugAppState debugAppState;
+    AnimComposer composer;
+    Queue<String> anims = new LinkedList<>();
+    boolean playAnim = true;
+
+    public static void main(String... argv) {
+        TestAnimMigration app = new TestAnimMigration();
+        app.start();
+    }
+
+    @Override
+    public void simpleInitApp() {
+        setTimer(new EraseTimer());
+        //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
+        viewPort.setBackgroundColor(ColorRGBA.DarkGray);
+        rootNode.addLight(new DirectionalLight(new Vector3f(-1, -1, -1).normalizeLocal()));
+        rootNode.addLight(new AmbientLight(ColorRGBA.DarkGray));
+
+        Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
+        //Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
+        //Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml");
+        //Spatial model = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml");
+
+        AnimMigrationUtils.migrate(model);
+
+        rootNode.attachChild(model);
+
+
+        debugAppState = new ArmatureDebugAppState();
+        stateManager.attach(debugAppState);
+
+        setupModel(model);
+
+        flyCam.setEnabled(false);
+
+        Node target = new Node("CamTarget");
+        //target.setLocalTransform(model.getLocalTransform());
+        target.move(0, 1, 0);
+        ChaseCameraAppState chaseCam = new ChaseCameraAppState();
+        chaseCam.setTarget(target);
+        getStateManager().attach(chaseCam);
+        chaseCam.setInvertHorizontalAxis(true);
+        chaseCam.setInvertVerticalAxis(true);
+        chaseCam.setZoomSpeed(0.5f);
+        chaseCam.setMinVerticalRotation(-FastMath.HALF_PI);
+        chaseCam.setRotationSpeed(3);
+        chaseCam.setDefaultDistance(3);
+        chaseCam.setMinDistance(0.01f);
+        chaseCam.setZoomSpeed(0.01f);
+        chaseCam.setDefaultVerticalRotation(0.3f);
+
+        initInputs();
+    }
+
+    public void initInputs() {
+        inputManager.addMapping("toggleAnim", new KeyTrigger(KeyInput.KEY_RETURN));
+
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed) {
+                    playAnim = !playAnim;
+                    if (playAnim) {
+                        String anim = anims.poll();
+                        anims.add(anim);
+                        composer.setCurrentAnimClip(anim);
+                        System.err.println(anim);
+                    } else {
+                        composer.reset();
+                    }
+                }
+            }
+        }, "toggleAnim");
+        inputManager.addMapping("nextAnim", new KeyTrigger(KeyInput.KEY_RIGHT));
+        inputManager.addListener(new ActionListener() {
+            @Override
+            public void onAction(String name, boolean isPressed, float tpf) {
+                if (isPressed && composer != null) {
+                    String anim = anims.poll();
+                    anims.add(anim);
+                    composer.setCurrentAnimClip(anim);
+                    System.err.println(anim);
+                }
+            }
+        }, "nextAnim");
+    }
+
+    private void setupModel(Spatial model) {
+        if (composer != null) {
+            return;
+        }
+        composer = model.getControl(AnimComposer.class);
+        if (composer != null) {
+
+            SkinningControl sc = model.getControl(SkinningControl.class);
+            debugAppState.addArmatureFrom(sc);
+
+            anims.clear();
+            for (String name : composer.getAnimClipsNames()) {
+                anims.add(name);
+            }
+            if (anims.isEmpty()) {
+                return;
+            }
+            if (playAnim) {
+                String anim = anims.poll();
+                anims.add(anim);
+                composer.setCurrentAnimClip(anim);
+                System.err.println(anim);
+            }
+
+        } else {
+            if (model instanceof Node) {
+                Node n = (Node) model;
+                for (Spatial child : n.getChildren()) {
+                    setupModel(child);
+                }
+            }
+        }
+
+    }
+}

+ 8 - 30
jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java

@@ -6,7 +6,6 @@ import com.jme3.app.SimpleApplication;
 import com.jme3.input.KeyInput;
 import com.jme3.input.controls.ActionListener;
 import com.jme3.input.controls.KeyTrigger;
-import com.jme3.light.DirectionalLight;
 import com.jme3.material.Material;
 import com.jme3.math.*;
 import com.jme3.scene.*;
@@ -37,6 +36,7 @@ public class TestArmature extends SimpleApplication {
         //cam.setFrustumPerspective(90f, (float) cam.getWidth() / cam.getHeight(), 0.01f, 10f);
         viewPort.setBackgroundColor(ColorRGBA.DarkGray);
 
+        //create armature
         Joint root = new Joint("Root_Joint");
         j1 = new Joint("Joint_1");
         j2 = new Joint("Joint_2");
@@ -52,9 +52,9 @@ public class TestArmature extends SimpleApplication {
 
         final Armature armature = new Armature(joints);
 //        armature.setModelTransformClass(SeparateJointModelTransform.class);
-
         armature.setBindPose();
 
+        //create animations
         AnimClip clip = new AnimClip("anim");
         float[] times = new float[]{0, 2, 4};
         Quaternion[] rotations = new Quaternion[]{
@@ -83,15 +83,18 @@ public class TestArmature extends SimpleApplication {
         clip.addTrack(track1);
         clip.addTrack(track2);
 
+        //create the animComposer control
         final AnimComposer composer = new AnimComposer();
         composer.addAnimClip(clip);
 
+        //create the SkinningControl
         SkinningControl ac = new SkinningControl(armature);
         ac.setHardwareSkinningPreferred(false);
         Node node = new Node("Test Armature");
 
         rootNode.attachChild(node);
 
+        //Create the mesh to deform.
         Geometry cylinder = new Geometry("cylinder", createMesh());
         Material m = new Material(assetManager, "Common/MatDefs/Misc/fakeLighting.j3md");
         m.setColor("Color", ColorRGBA.randomColor());
@@ -103,14 +106,9 @@ public class TestArmature extends SimpleApplication {
         composer.setCurrentAnimClip("anim");
 
         ArmatureDebugAppState debugAppState = new ArmatureDebugAppState();
-        debugAppState.addArmature(ac);
+        debugAppState.addArmatureFrom(ac);
         stateManager.attach(debugAppState);
 
-        rootNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal()));
-
-        rootNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.7f, 0.7f, 0.7f, 1.0f)));
-
-
         flyCam.setEnabled(false);
 
         ChaseCameraAppState chaseCam = new ChaseCameraAppState();
@@ -132,12 +130,10 @@ public class TestArmature extends SimpleApplication {
             @Override
             public void onAction(String name, boolean isPressed, float tpf) {
                 if (isPressed) {
-                    play = false;
                     composer.reset();
                     armature.resetToBindPose();
 
                 } else {
-                    play = true;
                     composer.setCurrentAnimClip("anim");
                 }
             }
@@ -202,12 +198,10 @@ public class TestArmature extends SimpleApplication {
 
         c.updateCounts();
         c.updateBound();
-        //the mesh has some skinning let's create needed buffers for HW skinning
-        //creating empty buffers for HW skinning
-        //the buffers will be setup if ever used.
+
         VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight);
         VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex);
-        //setting usage to cpuOnly so that the buffer is not send empty to the GPU
+
         indicesHW.setUsage(VertexBuffer.Usage.CpuOnly);
         weightsHW.setUsage(VertexBuffer.Usage.CpuOnly);
         c.setBuffer(weightsHW);
@@ -220,20 +214,4 @@ public class TestArmature extends SimpleApplication {
     }
 
 
-    float time = 0;
-    boolean play = true;
-
-    @Override
-    public void simpleUpdate(float tpf) {
-
-
-//        if (play == false) {
-//            return;
-//        }
-//        time += tpf;
-//        float rot = FastMath.sin(time);
-//        j1.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
-//        j2.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI * rot, Vector3f.UNIT_Z));
-
-    }
 }