Quellcode durchsuchen

fix SkeletonControl.getAttachmentNode() for odd models (such as Jaime)

Stephen Gold vor 8 Jahren
Ursprung
Commit
b2aa1ff9f1

+ 51 - 7
jme3-core/src/main/java/com/jme3/animation/Bone.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2017 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -33,7 +33,9 @@ package com.jme3.animation;
 
 import com.jme3.export.*;
 import com.jme3.math.*;
+import com.jme3.scene.Geometry;
 import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
 import com.jme3.util.TempVars;
 import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.clone.Cloner;
@@ -80,7 +82,10 @@ public final class Bone implements Savable, JmeCloneable {
      * The attachment node.
      */
     private Node attachNode;
-    
+    /**
+     * A geometry animated by this node, used when updating the attachments node.
+     */
+    private Geometry targetGeometry = null;
     /**
      * Bind transform is the local bind transform of this bone. (local space)
      */
@@ -187,7 +192,8 @@ public final class Bone implements Savable, JmeCloneable {
         this.children = cloner.clone(children);    
         
         this.attachNode = cloner.clone(attachNode);
-        
+        this.targetGeometry = cloner.clone(targetGeometry);
+
         this.bindPos = cloner.clone(bindPos);
         this.bindRot = cloner.clone(bindRot);
         this.bindScale = cloner.clone(bindScale);
@@ -505,9 +511,39 @@ public final class Bone implements Savable, JmeCloneable {
         }
 
         if (attachNode != null) {
+            updateAttachNode();
+        }
+    }
+
+    /**
+     * Update the local transform of the attachments node.
+     */
+    private void updateAttachNode() {
+        Node attachParent = attachNode.getParent();
+        if (attachParent == null || targetGeometry == null
+                || targetGeometry.getParent() == attachParent
+                && targetGeometry.getLocalTransform().isIdentity()) {
+            /*
+             * The animated meshes are in the same coordinate system as the
+             * attachments node: no further transforms are needed.
+             */
             attachNode.setLocalTranslation(modelPos);
             attachNode.setLocalRotation(modelRot);
             attachNode.setLocalScale(modelScale);
+
+        } else {
+            Spatial loopSpatial = targetGeometry;
+            Transform combined = new Transform(modelPos, modelRot, modelScale);
+            /*
+             * Climb the scene graph applying local transforms until the 
+             * attachments node's parent is reached.
+             */
+            while (loopSpatial != attachParent && loopSpatial != null) {
+                Transform localTransform = loopSpatial.getLocalTransform();
+                combined.combineWithParent(localTransform);
+                loopSpatial = loopSpatial.getParent();
+            }
+            attachNode.setLocalTransform(combined);
         }
     }
 
@@ -661,15 +697,21 @@ public final class Bone implements Savable, JmeCloneable {
     }
 
     /**
-     * Returns the attachment node.
-     * Attach models and effects to this node to make
-     * them follow this bone's motions.
+     * Access the attachments node of this bone. If this bone doesn't already
+     * have an attachments node, create one. Models and effects attached to the
+     * attachments node will follow this bone's motions.
+     *
+     * @param target a geometry animated by this bone, or null to indicate that
+     * all geometries affected by this bone have the same global transform as
+     * the attachment node's parent
      */
-    Node getAttachmentsNode() {
+    Node getAttachmentsNode(Geometry target) {
         if (attachNode == null) {
             attachNode = new Node(name + "_attachnode");
             attachNode.setUserData("AttachedBone", this);
         }
+        targetGeometry = target;
+
         return attachNode;
     }
 
@@ -823,6 +865,7 @@ public final class Bone implements Savable, JmeCloneable {
         }
         
         attachNode = (Node) input.readSavable("attachNode", null);
+        targetGeometry = (Geometry) input.readSavable("targetGeometry", null);
 
         localPos.set(bindPos);
         localRot.set(bindRot);
@@ -845,6 +888,7 @@ public final class Bone implements Savable, JmeCloneable {
 
         output.write(name, "name", null);
         output.write(attachNode, "attachNode", null);
+        output.write(targetGeometry, "targetGeometry", null);
         output.write(bindPos, "bindPos", null);
         output.write(bindRot, "bindRot", null);
         output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f));

+ 50 - 16
jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2017 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -52,7 +52,6 @@ import java.io.IOException;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
 import java.nio.FloatBuffer;
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
 import java.util.logging.Level;
@@ -75,6 +74,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
      * List of targets which this controller effects.
      */
     private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class);
+    /**
+     * Geometry with an animated mesh, for calculating attachments node
+     * transforms. A null means no geometry is subject to this control.
+     */
+    private Geometry targetGeometry = null;
     /**
      * Used to track when a mesh was updated. Meshes are only updated if they
      * are visible in at least one camera.
@@ -210,15 +214,23 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
         this.skeleton = skeleton;
     }
 
+    /**
+     * If specified the geometry has an animated mesh, add its mesh and material
+     * to the lists of animation targets.
+     */
+    private void findTargets(Geometry geometry) {
+        Mesh mesh = geometry.getMesh();
+        if (mesh.isAnimated()) {
+            targets.add(mesh);
+            targetGeometry = geometry;
+            materials.add(geometry.getMaterial());
+        }
+    }
+
     private void findTargets(Node node) {
         for (Spatial child : node.getChildren()) {
             if (child instanceof Geometry) {
-                Geometry geom = (Geometry) child;
-                Mesh mesh = geom.getMesh();
-                if (mesh.isAnimated()) {
-                    targets.add(mesh);
-                    materials.add(geom.getMaterial());
-                }
+                findTargets((Geometry) child);
             } else if (child instanceof Node) {
                 findTargets((Node) child);
             }
@@ -432,9 +444,13 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
     }
          
     /**
+     * Access the attachments node of the named bone. If the bone doesn't
+     * already have an attachments node, create one and attach it to the scene
+     * graph. Models and effects attached to the attachments node will follow
+     * the bone's motions.
      *
      * @param boneName the name of the bone
-     * @return the node attached to this bone
+     * @return the attachments node of the bone
      */
     public Node getAttachmentsNode(String boneName) {
         Bone b = skeleton.getBone(boneName);
@@ -443,9 +459,19 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
                     + "in the skeleton.");
         }
 
-        Node n = b.getAttachmentsNode();
-        Node model = (Node) spatial;
-        model.attachChild(n);
+        updateTargetsAndMaterials(spatial);
+        Node n = b.getAttachmentsNode(targetGeometry);
+        /*
+         * Select a node to parent the attachments node.
+         */
+        Node parent;
+        if (spatial instanceof Node) {
+            parent = (Node) spatial; // the usual case
+        } else {
+            parent = spatial.getParent();
+        }
+        parent.attachChild(n);
+
         return n;
     }
 
@@ -758,12 +784,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
         skeleton = (Skeleton) in.readSavable("skeleton", null);
     }
 
+    /**
+     * Update the lists of animation targets.
+     *
+     * @param spatial the controlled spatial
+     */
     private void updateTargetsAndMaterials(Spatial spatial) {
         targets.clear();
-        materials.clear();           
-        if (spatial != null && spatial instanceof Node) {
-            Node node = (Node) spatial;                        
-            findTargets(node);
+        targetGeometry = null;
+        materials.clear();
+
+        if (spatial instanceof Node) {
+            findTargets((Node) spatial);
+        } else if (spatial instanceof Geometry) {
+            findTargets((Geometry) spatial);
         }
     }
 }

+ 12 - 1
jme3-core/src/main/java/com/jme3/math/Transform.java

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2009-2012 jMonkeyEngine
+ * Copyright (c) 2009-2017 jMonkeyEngine
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -288,6 +288,17 @@ public final class Transform implements Savable, Cloneable, java.io.Serializable
         rot.set(0, 0, 0, 1);
     }
 
+    /**
+     * Test for exact identity.
+     *
+     * @return true if exactly equal to {@link #IDENTITY}, otherwise false
+     */
+    public boolean isIdentity() {
+        return translation.x == 0f && translation.y == 0f && translation.z == 0f
+                && scale.x == 1f && scale.y == 1f && scale.z == 1f
+                && rot.w == 1f && rot.x == 0f && rot.y == 0f && rot.z == 0f;
+    }
+
     @Override
     public int hashCode() {
         int hash = 7;

+ 127 - 0
jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java

@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2009-2017 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jme3test.model.anim;
+
+import com.jme3.animation.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.AnimEventListener;
+import com.jme3.animation.LoopMode;
+import com.jme3.animation.SkeletonControl;
+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.ColorRGBA;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.shape.Box;
+
+/**
+ * Simple application to an test attachments node on the Jaime model.
+ *
+ * Derived from {@link jme3test.model.anim.TestOgreAnim}.
+ */
+public class TestAttachmentsNode extends SimpleApplication
+        implements AnimEventListener, ActionListener {
+
+    public static void main(String[] args) {
+        TestAttachmentsNode app = new TestAttachmentsNode();
+        app.start();
+    }
+
+    private AnimChannel channel;
+    private AnimControl control;
+
+    @Override
+    public void simpleInitApp() {
+        flyCam.setMoveSpeed(10f);
+        cam.setLocation(new Vector3f(6.4f, 7.5f, 12.8f));
+        cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f));
+
+        DirectionalLight dl = new DirectionalLight();
+        dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal());
+        dl.setColor(ColorRGBA.White);
+        rootNode.addLight(dl);
+
+        Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o");
+        control = model.getControl(AnimControl.class);
+        SkeletonControl skeletonControl = model.getControl(SkeletonControl.class);
+
+        model.center();
+        model.setLocalScale(5f);
+
+        control.addListener(this);
+        channel = control.createChannel();
+        channel.setAnim("Idle");
+
+        Box box = new Box(0.3f, 0.02f, 0.02f);
+        Geometry saber = new Geometry("saber", box);
+        saber.move(0.4f, 0.05f, 0.01f);
+        Material red = assetManager.loadMaterial("Common/Materials/RedColor.j3m");
+        saber.setMaterial(red);
+        Node n = skeletonControl.getAttachmentsNode("hand.R");
+        n.attachChild(saber);
+        rootNode.attachChild(model);
+
+        inputManager.addListener(this, "Attack");
+        inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE));
+    }
+
+    @Override
+    public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
+        if (animName.equals("Punches")) {
+            channel.setAnim("Idle", 0.5f);
+            channel.setLoopMode(LoopMode.DontLoop);
+            channel.setSpeed(1f);
+        }
+    }
+
+    @Override
+    public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
+    }
+
+    @Override
+    public void onAction(String binding, boolean value, float tpf) {
+        if (binding.equals("Attack") && value) {
+            if (!channel.getAnimationName().equals("Punches")) {
+                channel.setAnim("Punches", 0.5f);
+                channel.setLoopMode(LoopMode.Cycle);
+                channel.setSpeed(0.5f);
+            }
+        }
+    }
+}