Sfoglia il codice sorgente

Merge pull request #637 from stephengold/master

fix SkeletonControl.getAttachmentsNode() for odd models (such as Jaime)
Stephen Gold 8 anni fa
parent
commit
3d07e165af

+ 65 - 8
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,11 @@ package com.jme3.animation;
 
 import com.jme3.export.*;
 import com.jme3.math.*;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.util.SafeArrayList;
 import com.jme3.util.TempVars;
 import com.jme3.util.clone.JmeCloneable;
 import com.jme3.util.clone.Cloner;
@@ -80,7 +84,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 +194,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 +513,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 +699,32 @@ 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.
-     */
-    Node getAttachmentsNode() {
+     * 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 boneIndex this bone's index in its skeleton (≥0)
+     * @param targets a list of geometries animated by this bone's skeleton (not
+     * null, unaffected)
+     */
+    Node getAttachmentsNode(int boneIndex, SafeArrayList<Geometry> targets) {
+        targetGeometry = null;
+        /*
+         * Search for a geometry animated by this particular bone.
+         */
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            if (mesh != null && mesh.isAnimatedByBone(boneIndex)) {
+                targetGeometry = geometry;
+                break;
+            }
+        }
+
         if (attachNode == null) {
             attachNode = new Node(name + "_attachnode");
             attachNode.setUserData("AttachedBone", this);
         }
+
         return attachNode;
     }
 
@@ -823,6 +878,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 +901,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));

+ 74 - 32
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;
@@ -71,10 +70,12 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
      * The skeleton of the model.
      */
     private Skeleton skeleton;
+
     /**
-     * List of targets which this controller effects.
+     * List of geometries affected by this control.
      */
-    private SafeArrayList<Mesh> targets = new SafeArrayList<Mesh>(Mesh.class);
+    private SafeArrayList<Geometry> targets = new SafeArrayList<Geometry>(Geometry.class);
+
     /**
      * Used to track when a mesh was updated. Meshes are only updated if they
      * are visible in at least one camera.
@@ -124,8 +125,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
         for (Material m : materials) {
             m.setInt("NumberOfBones", numBones);
         }
-        for (Mesh mesh : targets) {
-            if (mesh.isAnimated()) {
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            if (mesh != null && mesh.isAnimated()) {
                 mesh.prepareForAnim(false);
             }
         }
@@ -137,8 +139,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
                 m.clearParam("NumberOfBones");
             }
         }
-        for (Mesh mesh : targets) {
-            if (mesh.isAnimated()) {
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            if (mesh != null && mesh.isAnimated()) {
                 mesh.prepareForAnim(true);
             }
         }
@@ -210,15 +213,22 @@ 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 != null && mesh.isAnimated()) {
+            targets.add(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);
             }
@@ -236,10 +246,11 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
 
         offsetMatrices = skeleton.computeSkinningMatrices();
 
-        for (Mesh mesh : targets) {
-            // NOTE: This assumes that code higher up
-            // Already ensured those targets are animated
-            // otherwise a crash will happen in skin update
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            // NOTE: This assumes code higher up has
+            // already ensured this mesh is animated.
+            // Otherwise a crash will happen in skin update.
             softwareSkinUpdate(mesh, offsetMatrices);
         }     
     }
@@ -313,8 +324,9 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
 
     //only do this for software updates
     void resetToBind() {
-        for (Mesh mesh : targets) {
-            if (mesh.isAnimated()) {
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            if (mesh != null && mesh.isAnimated()) {
                 Buffer bwBuff = mesh.getBuffer(Type.BoneWeight).getData();
                 Buffer biBuff = mesh.getBuffer(Type.BoneIndex).getData();
                 if (!biBuff.hasArray() || !bwBuff.hasArray()) {
@@ -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,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
                     + "in the skeleton.");
         }
 
-        Node n = b.getAttachmentsNode();
-        Node model = (Node) spatial;
-        model.attachChild(n);
+        updateTargetsAndMaterials(spatial);
+        int boneIndex = skeleton.getBoneIndex(b);
+        Node n = b.getAttachmentsNode(boneIndex, targets);
+        /*
+         * 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;
     }
 
@@ -459,12 +486,20 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl
     }
 
     /**
-     * returns a copy of array of the targets meshes of this control
+     * Enumerate the target meshes of this control.
      *
-     * @return
+     * @return a new array
      */
-    public Mesh[] getTargets() {        
-        return targets.toArray(new Mesh[targets.size()]);
+    public Mesh[] getTargets() {
+        Mesh[] result = new Mesh[targets.size()];
+        int i = 0;
+        for (Geometry geometry : targets) {
+            Mesh mesh = geometry.getMesh();
+            result[i] = mesh;
+            i++;
+        }
+
+        return result;
     }
 
     /**
@@ -758,12 +793,19 @@ 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);
+        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;

+ 40 - 1
jme3-core/src/main/java/com/jme3/scene/Mesh.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
@@ -1408,6 +1408,45 @@ public class Mesh implements Savable, Cloneable, JmeCloneable {
                getBuffer(Type.HWBoneIndex) != null;
     }
 
+    /**
+     * Test whether the specified bone animates this mesh.
+     *
+     * @param boneIndex the bone's index in its skeleton
+     * @return true if the specified bone animates this mesh, otherwise false
+     */
+    public boolean isAnimatedByBone(int boneIndex) {
+        VertexBuffer biBuf = getBuffer(VertexBuffer.Type.BoneIndex);
+        VertexBuffer wBuf = getBuffer(VertexBuffer.Type.BoneWeight);
+        if (biBuf == null || wBuf == null) {
+            return false; // no bone animation data
+        }
+
+        ByteBuffer boneIndexBuffer = (ByteBuffer) biBuf.getData();
+        boneIndexBuffer.rewind();
+        int numBoneIndices = boneIndexBuffer.remaining();
+        assert numBoneIndices % 4 == 0 : numBoneIndices;
+        int numVertices = boneIndexBuffer.remaining() / 4;
+
+        FloatBuffer weightBuffer = (FloatBuffer) wBuf.getData();
+        weightBuffer.rewind();
+        int numWeights = weightBuffer.remaining();
+        assert numWeights == numVertices * 4 : numWeights;
+        /*
+         * Test each vertex to determine whether the bone affects it.
+         */
+        byte biByte = (byte) boneIndex; // bone indices wrap after 127
+        for (int vIndex = 0; vIndex < numVertices; vIndex++) {
+            for (int wIndex = 0; wIndex < 4; wIndex++) {
+                byte bIndex = boneIndexBuffer.get();
+                float weight = weightBuffer.get();
+                if (wIndex < maxNumWeights && bIndex == biByte && weight != 0f) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Sets the count of vertices used for each tessellation patch
      * @param patchVertexCount

+ 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);
+            }
+        }
+    }
+}