Browse Source

Bone animation :
- Split of the AnimControl in two parts :
- The AnimControl that handles skeleton transformation via tha animation data
- The SkeletonControl that handles the skinning of the mesh using skeleton transformations
- Ensured backward compatibility with old j3o files, and changed the ogre mesh loader to create the controls properly
- Reverted change http://code.google.com/p/jmonkeyengine/source/detail?r=7142 transforms passed to the setUserTransform methods must be considered as increments to current transform
- Fixed some issues in the ragdollControl and test case (still WIP don't use it)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@7163 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

rem..om 14 years ago
parent
commit
dc030c897f

+ 101 - 227
engine/src/core/com/jme3/animation/AnimControl.java

@@ -29,31 +29,21 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.animation;
 
-import com.jme3.bullet.control.RagdollControl;
 import com.jme3.export.JmeExporter;
 import com.jme3.export.JmeImporter;
 import com.jme3.export.InputCapsule;
 import com.jme3.export.OutputCapsule;
 import com.jme3.export.Savable;
-import com.jme3.math.FastMath;
-import com.jme3.math.Matrix4f;
 import com.jme3.renderer.RenderManager;
 import com.jme3.renderer.ViewPort;
-import com.jme3.scene.Geometry;
 import com.jme3.scene.Mesh;
 import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
-import com.jme3.scene.VertexBuffer;
-import com.jme3.scene.VertexBuffer.Type;
 import com.jme3.scene.control.AbstractControl;
 import com.jme3.scene.control.Control;
-import com.jme3.util.TempVars;
 import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -83,26 +73,23 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
     /**
      * List of targets which this controller effects.
      */
-    Mesh[] targets;
-
+    //  Mesh[] targets;
     /**
      * Skeleton object must contain corresponding data for the targets' weight buffers.
      */
     Skeleton skeleton;
-
+    /** only used for backward compatibility */
+    @Deprecated
+    private SkeletonControl skeletonControl;
     /**
      * List of animations
      */
     HashMap<String, BoneAnimation> animationMap;
-
     /**
      * Animation channels
      */
-    transient ArrayList<AnimChannel> channels
-            = new ArrayList<AnimChannel>();
-
-    transient ArrayList<AnimEventListener> listeners
-            = new ArrayList<AnimEventListener>();
+    transient ArrayList<AnimChannel> channels = new ArrayList<AnimChannel>();
+    transient ArrayList<AnimEventListener> listeners = new ArrayList<AnimEventListener>();
 
     /**
      * Create a new <code>AnimControl</code> that will animate the given skins
@@ -114,12 +101,22 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * properly set BoneIndex and BoneWeight buffers.
      * @param skeleton The skeleton structure represents a bone hierarchy
      * to be animated.
+     * @deprecated AnimControl doesnt' hande the skinning anymore, use AnimControl(Skeleton skeleton);
+     * Then create a SkeletonControl(Node model, Mesh[] meshes, Skeleton skeleton);
+     * and add it to the spatial.
      */
-    public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton){
+    @Deprecated
+    public AnimControl(Node model, Mesh[] meshes, Skeleton skeleton) {
         super(model);
 
         this.skeleton = skeleton;
-        this.targets = meshes;
+
+        skeletonControl = new SkeletonControl(model, meshes, this.skeleton);
+        reset();
+    }
+
+    public AnimControl(Skeleton skeleton) {
+        this.skeleton = skeleton;
         reset();
     }
 
@@ -131,32 +128,11 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
     public AnimControl() {
     }
 
-    public Control cloneForSpatial(Spatial spatial){
+    public Control cloneForSpatial(Spatial spatial) {
         try {
-            Node clonedNode = (Node) spatial;
             AnimControl clone = (AnimControl) super.clone();
-            clone.spatial  = spatial;
+            clone.spatial = spatial;
             clone.skeleton = new Skeleton(skeleton);
-            Mesh[] meshes = new Mesh[targets.length];
-            for (int i = 0; i < meshes.length; i++) {
-                meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh();
-            }
-            for (int i = meshes.length; i < clonedNode.getQuantity(); i++){
-                // go through attachment nodes, apply them to correct bone
-                Spatial child = clonedNode.getChild(i);
-                if (child instanceof Node){
-                    Node clonedAttachNode = (Node) child;
-                    Bone originalBone     = (Bone) clonedAttachNode.getUserData("AttachedBone");
-
-                    if (originalBone != null){
-                        Bone clonedBone       = clone.skeleton.getBone(originalBone.getName());
-
-                        clonedAttachNode.setUserData("AttachedBone", clonedBone);
-                        clonedBone.setAttachmentsNode(clonedAttachNode);
-                    }
-                }
-            }
-            clone.targets = meshes;
             clone.channels = new ArrayList<AnimChannel>();
             return clone;
         } catch (CloneNotSupportedException ex) {
@@ -169,7 +145,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * will be capable of playing. The animations should be compatible
      * with the skeleton given in the constructor.
      */
-    public void setAnimations(HashMap<String, BoneAnimation> animations){
+    public void setAnimations(HashMap<String, BoneAnimation> animations) {
         animationMap = animations;
     }
 
@@ -179,7 +155,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * @return The animation corresponding to the given name, or null, if no
      * such named animation exists.
      */
-    public BoneAnimation getAnim(String name){
+    public BoneAnimation getAnim(String name) {
         return animationMap.get(name);
     }
 
@@ -188,7 +164,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * <code>AnimControl</code>.
      * @param anim The animation to add.
      */
-    public void addAnim(BoneAnimation anim){
+    public void addAnim(BoneAnimation anim) {
         animationMap.put(anim.getName(), anim);
     }
 
@@ -196,23 +172,34 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * Remove an animation so that it is no longer available for playing.
      * @param anim The animation to remove.
      */
-    public void removeAnim(BoneAnimation anim){
-        if (!animationMap.containsKey(anim.getName()))
-            throw new IllegalArgumentException("Given animation does not exist " +
-                                               "in this AnimControl");
+    public void removeAnim(BoneAnimation anim) {
+        if (!animationMap.containsKey(anim.getName())) {
+            throw new IllegalArgumentException("Given animation does not exist "
+                    + "in this AnimControl");
+        }
 
         animationMap.remove(anim.getName());
     }
 
+    /**
+     * 
+     * @param boneName the name of the bone
+     * @return the node attached to this bone
+     * @deprecated use SkeletonControl.getAttachementNode instead.
+     */
+    @Deprecated
     public Node getAttachmentsNode(String boneName) {
         Bone b = skeleton.getBone(boneName);
-        if (b == null)
-            throw new IllegalArgumentException("Given bone name does not exist " +
-                                               "in the skeleton.");
+        if (b == null) {
+            throw new IllegalArgumentException("Given bone name does not exist "
+                    + "in the skeleton.");
+        }
 
         Node n = b.getAttachmentsNode();
-        Node model = (Node) spatial;
-        model.attachChild(n);
+        if (spatial != null) {
+            Node model = (Node) spatial;
+            model.attachChild(n);
+        }
         return n;
     }
 
@@ -222,7 +209,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * 
      * @return A new animation channel for this <code>AnimControl</code>.
      */
-    public AnimChannel createChannel(){
+    public AnimChannel createChannel() {
         AnimChannel channel = new AnimChannel(this);
         channels.add(channel);
         return channel;
@@ -236,7 +223,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      *
      * @throws IndexOutOfBoundsException If no channel exists at the given index.
      */
-    public AnimChannel getChannel(int index){
+    public AnimChannel getChannel(int index) {
         return channels.get(index);
     }
 
@@ -246,7 +233,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      *
      * @see AnimControl#createChannel()
      */
-    public int getNumChannels(){
+    public int getNumChannels() {
         return channels.size();
     }
 
@@ -255,7 +242,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      *
      * @see AnimControl#createChannel()
      */
-    public void clearChannels(){
+    public void clearChannels() {
         channels.clear();
     }
 
@@ -269,19 +256,23 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
     /**
      * @return The targets, or skins, being influenced by this
      * <code>AnimControl</code>.
+     * @deprecated use SkeletonControl.getTargets() instead
+     * get the SkeletonControl doing spatial.getControl(SkeletonControl.class);
      */
+    @Deprecated
     public Mesh[] getTargets() {
-        return targets;
+        return skeletonControl.getTargets();
     }
 
     /**
      * Adds a new listener to receive animation related events.
      * @param listener The listener to add.
      */
-    public void addListener(AnimEventListener listener){
-        if (listeners.contains(listener))
-            throw new IllegalArgumentException("The given listener is already " +
-                                               "registed at this AnimControl");
+    public void addListener(AnimEventListener listener) {
+        if (listeners.contains(listener)) {
+            throw new IllegalArgumentException("The given listener is already "
+                    + "registed at this AnimControl");
+        }
 
         listeners.add(listener);
     }
@@ -291,10 +282,11 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * @param listener
      * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
      */
-    public void removeListener(AnimEventListener listener){
-        if (!listeners.remove(listener))
-            throw new IllegalArgumentException("The given listener is not " +
-                                               "registed at this AnimControl");
+    public void removeListener(AnimEventListener listener) {
+        if (!listeners.remove(listener)) {
+            throw new IllegalArgumentException("The given listener is not "
+                    + "registed at this AnimControl");
+        }
     }
 
     /**
@@ -302,53 +294,36 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      *
      * @see AnimControl#addListener(com.jme3.animation.AnimEventListener)
      */
-    public void clearListeners(){
+    public void clearListeners() {
         listeners.clear();
     }
 
-    void notifyAnimChange(AnimChannel channel, String name){
-        for (int i = 0; i < listeners.size(); i++){
+    void notifyAnimChange(AnimChannel channel, String name) {
+        for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onAnimChange(this, channel, name);
         }
     }
 
-    void notifyAnimCycleDone(AnimChannel channel, String name){
-        for (int i = 0; i < listeners.size(); i++){
+    void notifyAnimCycleDone(AnimChannel channel, String name) {
+        for (int i = 0; i < listeners.size(); i++) {
             listeners.get(i).onAnimCycleDone(this, channel, name);
         }
     }
 
-    final void reset(){
-        resetToBind();
-        if (skeleton != null){
-            skeleton.resetAndUpdate();
+    @Override
+    public void setSpatial(Spatial spatial) {
+        super.setSpatial(spatial);
+
+        //Backward compatibility.
+        if (skeletonControl != null) {
+            spatial.addControl(skeletonControl);
         }
+
     }
 
-    void resetToBind(){
-        for (int i = 0; i < targets.length; i++){
-            Mesh mesh = targets[i];
-            if (targets[i].getBuffer(Type.BindPosePosition) != null){
-                VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
-                ByteBuffer bib = (ByteBuffer) bi.getData();
-                if (!bib.hasArray())
-                    mesh.prepareForAnim(true); // prepare for software animation
-                    
-                VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
-                VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
-                VertexBuffer pos = mesh.getBuffer(Type.Position);
-                VertexBuffer norm = mesh.getBuffer(Type.Normal);
-                FloatBuffer pb = (FloatBuffer) pos.getData();
-                FloatBuffer nb = (FloatBuffer) norm.getData();
-                FloatBuffer bpb = (FloatBuffer) bindPos.getData();
-                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
-                pb.clear();
-                nb.clear();
-                bpb.clear();
-                bnb.clear();
-                pb.put(bpb).clear();
-                nb.put(bnb).clear();
-            }
+    final void reset() {
+        if (skeleton != null) {
+            skeleton.resetAndUpdate();
         }
     }
 
@@ -356,7 +331,7 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * @return The names of all animations that this <code>AnimControl</code>
      * can play.
      */
-    public Collection<String> getAnimationNames(){
+    public Collection<String> getAnimationNames() {
         return animationMap.keySet();
     }
 
@@ -365,160 +340,59 @@ public final class AnimControl extends AbstractControl implements Savable, Clone
      * @param name The name of the animation
      * @return The length of time, in seconds, of the named animation.
      */
-    public float getAnimationLength(String name){
+    public float getAnimationLength(String name) {
         BoneAnimation a = animationMap.get(name);
-        if (a == null)
-            throw new IllegalArgumentException("The animation " + name +
-                                               " does not exist in this AnimControl");
+        if (a == null) {
+            throw new IllegalArgumentException("The animation " + name
+                    + " does not exist in this AnimControl");
+        }
 
         return a.getLength();
     }
-    
-    private RagdollControl ragdoll=null;
-
-    public void setRagdoll(RagdollControl ragdoll) {
-        this.ragdoll = ragdoll;
-    }
-    
 
     @Override
     protected void controlUpdate(float tpf) {
-        resetToBind(); // reset morph meshes to bind pose
         skeleton.reset(); // reset skeleton to bind pose
 
-        for (int i = 0; i < channels.size(); i++){
+        for (int i = 0; i < channels.size(); i++) {
             channels.get(i).update(tpf);
         }
 
         skeleton.updateWorldVectors();
-        // here update the targets vertices if no hardware skinning supported
-
-        if(ragdoll!=null){
-            ragdoll.update(tpf);
-        }
-        
-        Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
-
-        // if hardware skinning is supported, the matrices and weight buffer
-        // will be sent by the SkinningShaderLogic object assigned to the shader
-        for (int i = 0; i < targets.length; i++){
-            // only update targets with bone-vertex assignments
-            if (targets[i].getBuffer(Type.BoneIndex) != null)
-                softwareSkinUpdate(targets[i], offsetMatrices);
-        }
     }
 
     @Override
     protected void controlRender(RenderManager rm, ViewPort vp) {
     }
 
-    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices){
-        int maxWeightsPerVert = mesh.getMaxNumWeights();
-        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
-
-        // NOTE: This code assumes the vertex buffer is in bind pose
-        // resetToBind() has been called this frame
-        VertexBuffer vb = mesh.getBuffer(Type.Position);
-        FloatBuffer fvb = (FloatBuffer) vb.getData();
-        fvb.rewind();
-
-        VertexBuffer nb = mesh.getBuffer(Type.Normal);
-        FloatBuffer fnb = (FloatBuffer) nb.getData();
-        fnb.rewind();
-
-        // get boneIndexes and weights for mesh
-        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
-        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
-
-        ib.rewind();
-        wb.rewind();
-
-        float[] weights = wb.array();
-        byte[] indices = ib.array();
-        int idxWeights = 0;
-
-        TempVars vars = TempVars.get();
-        float[] posBuf = vars.skinPositions;
-        float[] normBuf = vars.skinNormals;
-
-        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float)posBuf.length));
-        int bufLength = posBuf.length * 3;
-        for (int i = iterations-1; i >= 0; i--){
-            // read next set of positions and normals from native buffer
-            bufLength = Math.min(posBuf.length, fvb.remaining());
-            fvb.get(posBuf, 0, bufLength);
-            fnb.get(normBuf, 0, bufLength);
-            int verts = bufLength / 3;
-            int idxPositions = 0;
-
-            // iterate vertices and apply skinning transform for each effecting bone
-            for (int vert = verts - 1; vert >= 0; vert--){
-                float nmx = normBuf[idxPositions];
-                float vtx = posBuf[idxPositions++];
-                float nmy = normBuf[idxPositions];
-                float vty = posBuf[idxPositions++];
-                float nmz = normBuf[idxPositions];
-                float vtz = posBuf[idxPositions++];
-
-                float rx=0, ry=0, rz=0, rnx=0, rny=0, rnz=0;
-
-                for (int w = maxWeightsPerVert - 1; w >= 0; w--){
-                    float weight = weights[idxWeights];
-                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
-
-                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
-                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
-                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
-
-                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
-                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
-                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
-                }
-
-                idxWeights += fourMinusMaxWeights;
-
-                idxPositions -= 3;
-                normBuf[idxPositions] = rnx;
-                posBuf[idxPositions++] = rx;
-                normBuf[idxPositions] = rny;
-                posBuf[idxPositions++] = ry;
-                normBuf[idxPositions] = rnz;
-                posBuf[idxPositions++] = rz;
-            }
-
-
-            fvb.position(fvb.position()-bufLength);
-            fvb.put(posBuf, 0, bufLength);
-            fnb.position(fnb.position()-bufLength);
-            fnb.put(normBuf, 0, bufLength);
-        }
-
-        vb.updateData(fvb);
-        nb.updateData(fnb);
-
-//        mesh.updateBound();
-    }
-
     @Override
-    public void write(JmeExporter ex) throws IOException{
+    public void write(JmeExporter ex) throws IOException {
         super.write(ex);
         OutputCapsule oc = ex.getCapsule(this);
-        oc.write(targets, "targets", null);
         oc.write(skeleton, "skeleton", null);
         oc.writeStringSavableMap(animationMap, "animations", null);
     }
 
     @Override
-    public void read(JmeImporter im) throws IOException{
+    public void read(JmeImporter im) throws IOException {
         super.read(im);
         InputCapsule in = im.getCapsule(this);
-        Savable[] sav = in.readSavableArray("targets", null);
-        if (sav != null){
-            targets = new Mesh[sav.length];
-            System.arraycopy(sav, 0, targets, 0, sav.length);
-        }
         skeleton = (Skeleton) in.readSavable("skeleton", null);
         animationMap = (HashMap<String, BoneAnimation>) in.readStringSavableMap("animations", null);
-    }
 
+
+        //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
+        //if we find a target mesh array the AnimControl creates the SkeletonControl for old files and add it to the spatial.        
+        //When backward compatibility won't be needed anymore this can deleted        
+        Savable[] sav = in.readSavableArray("targets", null);
+        if (sav != null) {
+            Mesh[] tg = null;
+            tg = new Mesh[sav.length];
+            System.arraycopy(sav, 0, tg, 0, sav.length);
+            skeletonControl = new SkeletonControl((Node) spatial, tg, skeleton);
+            spatial.addControl(skeletonControl);
+        }
+        //------
+
+    }
 }

+ 2 - 1
engine/src/core/com/jme3/animation/Bone.java

@@ -347,6 +347,7 @@ public final class Bone implements Savable {
 
     /**
      * Set user transform.
+     * The transforms are used as increments to current translations
      * @see setUserControl
      */
     public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
@@ -360,7 +361,7 @@ public final class Bone implements Savable {
 
         localPos.addLocal(translation);
         localRot = localRot.mult(rotation);
-        localScale.multLocal(scale);
+        localScale.addLocal(scale);
     }
 
     /**

+ 274 - 0
engine/src/core/com/jme3/animation/SkeletonControl.java

@@ -0,0 +1,274 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jme3.animation;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.FastMath;
+import com.jme3.math.Matrix4f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.renderer.ViewPort;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Mesh;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.VertexBuffer;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.control.AbstractControl;
+import com.jme3.scene.control.Control;
+import com.jme3.util.TempVars;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+
+/**
+ *
+ * @author Nehon
+ */
+public class SkeletonControl extends AbstractControl implements Savable, Cloneable {
+
+    /**
+     * The skelrton of the model
+     */
+    private Skeleton skeleton;
+    /**
+     * List of targets which this controller effects.
+     */
+    Mesh[] targets;
+
+    public SkeletonControl() {
+    }
+
+    public SkeletonControl(Node model, Mesh[] targets, Skeleton skeleton) {
+        super(model);
+        this.skeleton = skeleton;
+        this.targets = targets;
+    }
+
+    @Override
+    protected void controlUpdate(float tpf) {
+        resetToBind(); // reset morph meshes to bind pose 
+
+        Matrix4f[] offsetMatrices = skeleton.computeSkinningMatrices();
+
+        // if hardware skinning is supported, the matrices and weight buffer
+        // will be sent by the SkinningShaderLogic object assigned to the shader
+        for (int i = 0; i < targets.length; i++) {
+            // only update targets with bone-vertex assignments
+            if (targets[i].getBuffer(Type.BoneIndex) != null) {
+                softwareSkinUpdate(targets[i], offsetMatrices);
+            }
+        }
+    }
+
+    void resetToBind() {
+        for (int i = 0; i < targets.length; i++) {
+            Mesh mesh = targets[i];
+            if (targets[i].getBuffer(Type.BindPosePosition) != null) {
+                VertexBuffer bi = mesh.getBuffer(Type.BoneIndex);
+                ByteBuffer bib = (ByteBuffer) bi.getData();
+                if (!bib.hasArray()) {
+                    mesh.prepareForAnim(true); // prepare for software animation
+                }
+                VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition);
+                VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal);
+                VertexBuffer pos = mesh.getBuffer(Type.Position);
+                VertexBuffer norm = mesh.getBuffer(Type.Normal);
+                FloatBuffer pb = (FloatBuffer) pos.getData();
+                FloatBuffer nb = (FloatBuffer) norm.getData();
+                FloatBuffer bpb = (FloatBuffer) bindPos.getData();
+                FloatBuffer bnb = (FloatBuffer) bindNorm.getData();
+                pb.clear();
+                nb.clear();
+                bpb.clear();
+                bnb.clear();
+                pb.put(bpb).clear();
+                nb.put(bnb).clear();
+            }
+        }
+    }
+
+    private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) {
+        int maxWeightsPerVert = mesh.getMaxNumWeights();
+        int fourMinusMaxWeights = 4 - maxWeightsPerVert;
+
+        // NOTE: This code assumes the vertex buffer is in bind pose
+        // resetToBind() has been called this frame
+        VertexBuffer vb = mesh.getBuffer(Type.Position);
+        FloatBuffer fvb = (FloatBuffer) vb.getData();
+        fvb.rewind();
+
+        VertexBuffer nb = mesh.getBuffer(Type.Normal);
+        FloatBuffer fnb = (FloatBuffer) nb.getData();
+        fnb.rewind();
+
+        // get boneIndexes and weights for mesh
+        ByteBuffer ib = (ByteBuffer) mesh.getBuffer(Type.BoneIndex).getData();
+        FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData();
+
+        ib.rewind();
+        wb.rewind();
+
+        float[] weights = wb.array();
+        byte[] indices = ib.array();
+        int idxWeights = 0;
+
+        TempVars vars = TempVars.get();
+        float[] posBuf = vars.skinPositions;
+        float[] normBuf = vars.skinNormals;
+
+        int iterations = (int) FastMath.ceil(fvb.capacity() / ((float) posBuf.length));
+        int bufLength = posBuf.length * 3;
+        for (int i = iterations - 1; i >= 0; i--) {
+            // read next set of positions and normals from native buffer
+            bufLength = Math.min(posBuf.length, fvb.remaining());
+            fvb.get(posBuf, 0, bufLength);
+            fnb.get(normBuf, 0, bufLength);
+            int verts = bufLength / 3;
+            int idxPositions = 0;
+
+            // iterate vertices and apply skinning transform for each effecting bone
+            for (int vert = verts - 1; vert >= 0; vert--) {
+                float nmx = normBuf[idxPositions];
+                float vtx = posBuf[idxPositions++];
+                float nmy = normBuf[idxPositions];
+                float vty = posBuf[idxPositions++];
+                float nmz = normBuf[idxPositions];
+                float vtz = posBuf[idxPositions++];
+
+                float rx = 0, ry = 0, rz = 0, rnx = 0, rny = 0, rnz = 0;
+
+                for (int w = maxWeightsPerVert - 1; w >= 0; w--) {
+                    float weight = weights[idxWeights];
+                    Matrix4f mat = offsetMatrices[indices[idxWeights++]];
+
+                    rx += (mat.m00 * vtx + mat.m01 * vty + mat.m02 * vtz + mat.m03) * weight;
+                    ry += (mat.m10 * vtx + mat.m11 * vty + mat.m12 * vtz + mat.m13) * weight;
+                    rz += (mat.m20 * vtx + mat.m21 * vty + mat.m22 * vtz + mat.m23) * weight;
+
+                    rnx += (nmx * mat.m00 + nmy * mat.m01 + nmz * mat.m02) * weight;
+                    rny += (nmx * mat.m10 + nmy * mat.m11 + nmz * mat.m12) * weight;
+                    rnz += (nmx * mat.m20 + nmy * mat.m21 + nmz * mat.m22) * weight;
+                }
+
+                idxWeights += fourMinusMaxWeights;
+
+                idxPositions -= 3;
+                normBuf[idxPositions] = rnx;
+                posBuf[idxPositions++] = rx;
+                normBuf[idxPositions] = rny;
+                posBuf[idxPositions++] = ry;
+                normBuf[idxPositions] = rnz;
+                posBuf[idxPositions++] = rz;
+            }
+
+
+            fvb.position(fvb.position() - bufLength);
+            fvb.put(posBuf, 0, bufLength);
+            fnb.position(fnb.position() - bufLength);
+            fnb.put(normBuf, 0, bufLength);
+        }
+
+        vb.updateData(fvb);
+        nb.updateData(fnb);
+
+//        mesh.updateBound();
+    }
+
+    final void reset() {
+        resetToBind();
+    }
+
+    @Override
+    protected void controlRender(RenderManager rm, ViewPort vp) {
+    }
+
+    public Control cloneForSpatial(Spatial spatial) {
+        Node clonedNode = (Node) spatial;
+        AnimControl ctrl = spatial.getControl(AnimControl.class);
+        SkeletonControl clone = new SkeletonControl();
+
+        clone.skeleton = ctrl.getSkeleton();
+        Mesh[] meshes = new Mesh[targets.length];
+        for (int i = 0; i < meshes.length; i++) {
+            meshes[i] = ((Geometry) clonedNode.getChild(i)).getMesh();
+        }
+        for (int i = meshes.length; i < clonedNode.getQuantity(); i++) {
+            // go through attachment nodes, apply them to correct bone
+            Spatial child = clonedNode.getChild(i);
+            if (child instanceof Node) {
+                Node clonedAttachNode = (Node) child;
+                Bone originalBone = (Bone) clonedAttachNode.getUserData("AttachedBone");
+
+                if (originalBone != null) {
+                    Bone clonedBone = clone.skeleton.getBone(originalBone.getName());
+
+                    clonedAttachNode.setUserData("AttachedBone", clonedBone);
+                    clonedBone.setAttachmentsNode(clonedAttachNode);
+                }
+            }
+        }
+        clone.targets = meshes;
+        return clone;
+
+    }
+
+    /**
+     * 
+     * @param boneName the name of the bone
+     * @return the node attached to this bone    
+     */
+    public Node getAttachmentsNode(String boneName) {
+        Bone b = skeleton.getBone(boneName);
+        if (b == null) {
+            throw new IllegalArgumentException("Given bone name does not exist "
+                    + "in the skeleton.");
+        }
+
+        Node n = b.getAttachmentsNode();
+        Node model = (Node) spatial;
+        model.attachChild(n);
+        return n;
+    }
+
+    public Skeleton getSkeleton() {
+        return skeleton;
+    }
+
+    public void setSkeleton(Skeleton skeleton) {
+        this.skeleton = skeleton;
+    }
+
+    public Mesh[] getTargets() {
+        return targets;
+    }
+
+    public void setTargets(Mesh[] targets) {
+        this.targets = targets;
+    }
+
+    @Override
+    public void write(JmeExporter ex) throws IOException {
+        super.write(ex);
+        OutputCapsule oc = ex.getCapsule(this);
+        oc.write(targets, "targets", null);
+        oc.write(skeleton, "skeleton", null);
+    }
+
+    @Override
+    public void read(JmeImporter im) throws IOException {
+        super.read(im);
+        InputCapsule in = im.getCapsule(this);
+        Savable[] sav = in.readSavableArray("targets", null);
+        if (sav != null) {
+            targets = new Mesh[sav.length];
+            System.arraycopy(sav, 0, targets, 0, sav.length);
+        }
+        skeleton = (Skeleton) in.readSavable("skeleton", null);
+    }
+}

+ 144 - 146
engine/src/core/com/jme3/scene/Spatial.java

@@ -29,7 +29,6 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.scene;
 
 import com.jme3.bounding.BoundingVolume;
@@ -64,7 +63,6 @@ import java.util.LinkedList;
 import java.util.Queue;
 import java.util.logging.Logger;
 
-
 /**
  * <code>Spatial</code> defines the base class for scene graph nodes. It
  * maintains a link to a parent, it's local transforms and the world's
@@ -80,77 +78,59 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
     private static final Logger logger = Logger.getLogger(Spatial.class.getName());
 
     public enum CullHint {
+
         /** 
          * Do whatever our parent does. If no parent, we'll default to dynamic.
          */
         Inherit,
-
         /**
          * Do not draw if we are not at least partially within the view frustum
          * of the renderer's camera.
          */
         Dynamic,
-
         /** 
          * Always cull this from view.
          */
         Always,
-
         /**
          * Never cull this from view. Note that we will still get culled if our
          * parent is culled.
          */
         Never;
     }
-
     /**
      * Refresh flag types
      */
     protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms
-                               RF_BOUND = 0x02,
-                               RF_LIGHTLIST = 0x04; // changes in light lists
-
+            RF_BOUND = 0x02,
+            RF_LIGHTLIST = 0x04; // changes in light lists
     protected CullHint cullHint = CullHint.Inherit;
-
     /** 
      * Spatial's bounding volume relative to the world.
      */
     protected BoundingVolume worldBound;
-
     /**
      * LightList
      */
     protected LightList localLights;
-
     protected transient LightList worldLights;
-
     /** 
      * This spatial's name.
      */
     protected String name;
-
     // scale values
     protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects;
-
     protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit;
-
     protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit;
-
     public transient float queueDistance = Float.NEGATIVE_INFINITY;
-
     protected Transform localTransform;
-
     protected Transform worldTransform;
-
     protected ArrayList<Control> controls = new ArrayList<Control>(1);
-
     protected HashMap<String, Savable> userData = null;
-
     /** 
      * Spatial's parent, or null if it has none.
      */
     protected transient Node parent;
-
     /**
      * Refresh flags. Indicate what data of the spatial need to be
      * updated to reflect the correct state.
@@ -187,12 +167,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * Indicate that the transform of this spatial has changed and that
      * a refresh is required.
      */
-    protected void setTransformRefresh(){
+    protected void setTransformRefresh() {
         refreshFlags |= RF_TRANSFORM;
         setBoundRefresh();
     }
 
-    protected void setLightListRefresh(){
+    protected void setLightListRefresh() {
         refreshFlags |= RF_LIGHTLIST;
     }
 
@@ -200,14 +180,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * Indicate that the bounding of this spatial has changed and that
      * a refresh is required.
      */
-    protected void setBoundRefresh(){
+    protected void setBoundRefresh() {
         refreshFlags |= RF_BOUND;
 
         // XXX: Replace with a recursive call?
         Spatial p = parent;
-        while (p != null){
-            if ((p.refreshFlags & RF_BOUND) != 0)
+        while (p != null) {
+            if ((p.refreshFlags & RF_BOUND) != 0) {
                 return;
+            }
 
             p.refreshFlags |= RF_BOUND;
             p = p.parent;
@@ -225,20 +206,20 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @return true if inside or intersecting camera frustum
      * (should be rendered), false if outside.
      */
-    public boolean checkCulling(Camera cam){
-        if (refreshFlags != 0){
+    public boolean checkCulling(Camera cam) {
+        if (refreshFlags != 0) {
             throw new IllegalStateException("Scene graph is not properly updated for rendering.\n"
-                                          + "Make sure scene graph state was not changed after\n"
-                                          + " rootNode.updateGeometricState() call. \n"
-                                          + "Problem spatial name: "+getName());
+                    + "Make sure scene graph state was not changed after\n"
+                    + " rootNode.updateGeometricState() call. \n"
+                    + "Problem spatial name: " + getName());
         }
 
         CullHint cm = getCullHint();
         assert cm != CullHint.Inherit;
-        if (cm == Spatial.CullHint.Always){
+        if (cm == Spatial.CullHint.Always) {
             setLastFrustumIntersection(Camera.FrustumIntersect.Outside);
             return false;
-        } else if (cm == Spatial.CullHint.Never){
+        } else if (cm == Spatial.CullHint.Never) {
             setLastFrustumIntersection(Camera.FrustumIntersect.Intersects);
             return true;
         }
@@ -252,9 +233,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
                 return cam.containsGui(getWorldBound());
             } else {
                 int state = cam.getPlaneState();
-                
+
                 frustrumIntersects = cam.contains(getWorldBound());
-                
+
                 cam.setPlaneState(state);
             }
         }
@@ -281,7 +262,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         return name;
     }
 
-    public LightList getLocalLightList(){
+    public LightList getLocalLightList() {
         return localLights;
     }
 
@@ -328,7 +309,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return the world transform.
      */
-    public Transform getWorldTransform(){
+    public Transform getWorldTransform() {
         checkDoTransformUpdate();
         return worldTransform;
     }
@@ -387,7 +368,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         assert vars.lock();
         Vector3f compVecA = vars.vect4;
         assert vars.unlock();
-        
+
         compVecA.set(position).subtractLocal(worldTranslation);
         getLocalRotation().lookAt(compVecA, upVector);
 
@@ -397,7 +378,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
     /**
      * Should be overriden by Node and Geometry.
      */
-    protected void updateWorldBound(){
+    protected void updateWorldBound() {
         // the world bound of a leaf is the same as it's model bound
         // for a node, the world bound is a combination of all it's children
         // bounds
@@ -405,15 +386,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         refreshFlags &= ~RF_BOUND;
     }
 
-    protected void updateWorldLightList(){
-        if (parent == null){
+    protected void updateWorldLightList() {
+        if (parent == null) {
             worldLights.update(localLights, null);
             refreshFlags &= ~RF_LIGHTLIST;
-        }else{
-            if ((parent.refreshFlags & RF_LIGHTLIST) == 0){
+        } else {
+            if ((parent.refreshFlags & RF_LIGHTLIST) == 0) {
                 worldLights.update(localLights, parent.worldLights);
                 refreshFlags &= ~RF_LIGHTLIST;
-            }else{
+            } else {
                 assert false;
             }
         }
@@ -423,11 +404,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * Should only be called from updateGeometricState().
      * In most cases should not be subclassed.
      */
-    protected void updateWorldTransforms(){
-        if (parent == null){
+    protected void updateWorldTransforms() {
+        if (parent == null) {
             worldTransform.set(localTransform);
             refreshFlags &= ~RF_TRANSFORM;
-        }else{
+        } else {
             // check if transform for parent is updated
             assert ((parent.refreshFlags & RF_TRANSFORM) == 0);
             worldTransform.set(localTransform);
@@ -436,23 +417,24 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         }
     }
 
-    void checkDoTransformUpdate(){
-        if ( (refreshFlags & RF_TRANSFORM) == 0 )
+    void checkDoTransformUpdate() {
+        if ((refreshFlags & RF_TRANSFORM) == 0) {
             return;
+        }
 
-        if (parent == null){
+        if (parent == null) {
             worldTransform.set(localTransform);
             refreshFlags &= ~RF_TRANSFORM;
-        }else{
+        } else {
             TempVars vars = TempVars.get();
             assert vars.lock();
 
             Spatial[] stack = vars.spatialStack;
             Spatial rootNode = this;
             int i = 0;
-            while (true){
+            while (true) {
                 Spatial hisParent = rootNode.parent;
-                if (hisParent == null){
+                if (hisParent == null) {
                     rootNode.worldTransform.set(rootNode.localTransform);
                     rootNode.refreshFlags &= ~RF_TRANSFORM;
                     i--;
@@ -461,7 +443,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
 
                 stack[i] = rootNode;
 
-                if ( (hisParent.refreshFlags & RF_TRANSFORM) == 0 ){
+                if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) {
                     break;
                 }
 
@@ -471,7 +453,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
 
             assert vars.unlock();
 
-            for (int j = i; j >= 0; j--){
+            for (int j = i; j >= 0; j--) {
                 rootNode = stack[j];
                 //rootNode.worldTransform.set(rootNode.localTransform);
                 //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform);
@@ -481,17 +463,18 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         }
     }
 
-    void checkDoBoundUpdate(){
-        if ( (refreshFlags & RF_BOUND) == 0 )
+    void checkDoBoundUpdate() {
+        if ((refreshFlags & RF_BOUND) == 0) {
             return;
+        }
 
         checkDoTransformUpdate();
 
         // Go to children recursively and update their bound
-        if (this instanceof Node){
+        if (this instanceof Node) {
             Node node = (Node) this;
             int len = node.getQuantity();
-            for (int i = 0; i < len; i++){
+            for (int i = 0; i < len; i++) {
                 Spatial child = node.getChild(i);
                 child.checkDoBoundUpdate();
             }
@@ -501,11 +484,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         updateWorldBound();
     }
 
-    private void runControlUpdate(float tpf){
-        if (controls.size() == 0)
+    private void runControlUpdate(float tpf) {
+        if (controls.size() == 0) {
             return;
+        }
 
-        for (int i = 0; i < controls.size(); i++){
+        for (int i = 0; i < controls.size(); i++) {
             controls.get(i).update(tpf);
         }
     }
@@ -520,11 +504,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      * @see Spatial#getControl(java.lang.Class) 
      */
-    public void runControlRender(RenderManager rm, ViewPort vp){
-        if (controls.size() == 0)
+    public void runControlRender(RenderManager rm, ViewPort vp) {
+        if (controls.size() == 0) {
             return;
+        }
 
-        for (int i = 0; i < controls.size(); i++){
+        for (int i = 0; i < controls.size(); i++) {
             controls.get(i).render(rm, vp);
         }
     }
@@ -535,7 +520,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Spatial#removeControl(java.lang.Class) 
      */
-    public void addControl(Control control){
+    public void addControl(Control control) {
         controls.add(control);
         control.setSpatial(this);
     }
@@ -545,9 +530,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Spatial#addControl(com.jme3.scene.control.Control) 
      */
-    public void removeControl(Class<? extends Control> controlType){
-        for (int i = 0; i < controls.size(); i++){
-            if (controlType.isAssignableFrom(controls.get(i).getClass())){
+    public void removeControl(Class<? extends Control> controlType) {
+        for (int i = 0; i < controls.size(); i++) {
+            if (controlType.isAssignableFrom(controls.get(i).getClass())) {
                 Control control = controls.remove(i);
                 control.setSpatial(null);
             }
@@ -563,11 +548,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * 
      * @see Spatial#addControl(com.jme3.scene.control.Control) 
      */
-    public boolean removeControl(Control control){
+    public boolean removeControl(Control control) {
         boolean result = controls.remove(control);
-        if (result)
+        if (result) {
             control.setSpatial(null);
-        
+        }
+
         return result;
     }
 
@@ -580,9 +566,9 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Spatial#addControl(com.jme3.scene.control.Control) 
      */
-    public <T extends Control> T getControl(Class<T> controlType){
-        for (int i = 0; i < controls.size(); i++){
-            if (controlType.isAssignableFrom(controls.get(i).getClass())){
+    public <T extends Control> T getControl(Class<T> controlType) {
+        for (int i = 0; i < controls.size(); i++) {
+            if (controlType.isAssignableFrom(controls.get(i).getClass())) {
                 return (T) controls.get(i);
             }
         }
@@ -600,7 +586,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      */
-    public Control getControl(int index){
+    public Control getControl(int index) {
         return controls.get(index);
     }
 
@@ -609,11 +595,10 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      * @see Spatial#removeControl(java.lang.Class) 
      */
-    public int getNumControls(){
+    public int getNumControls() {
         return controls.size();
     }
 
-
     /**
      * <code>updateLogicalState</code> calls the <code>update()</code> method
      * for all controls attached to this Spatial.
@@ -622,7 +607,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Spatial#addControl(com.jme3.scene.control.Control)
      */
-    public void updateLogicalState(float tpf){
+    public void updateLogicalState(float tpf) {
         runControlUpdate(tpf);
     }
 
@@ -638,19 +623,19 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see Spatial#getWorldTransform()
      * @see Spatial#getWorldBound()
      */
-    public void updateGeometricState(){
+    public void updateGeometricState() {
         // assume that this Spatial is a leaf, a proper implementation
         // for this method should be provided by Node.
 
         // NOTE: Update world transforms first because
         // bound transform depends on them.
-        if ((refreshFlags & RF_LIGHTLIST) != 0){
+        if ((refreshFlags & RF_LIGHTLIST) != 0) {
             updateWorldLightList();
         }
-        if ((refreshFlags & RF_TRANSFORM) != 0){
+        if ((refreshFlags & RF_TRANSFORM) != 0) {
             updateWorldTransforms();
         }
-        if ((refreshFlags & RF_BOUND) != 0){
+        if ((refreshFlags & RF_BOUND) != 0) {
             updateWorldBound();
         }
 
@@ -846,7 +831,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * spatial.
      */
     public void setLocalTranslation(float x, float y, float z) {
-        this.localTransform.setTranslation(x,y,z);
+        this.localTransform.setTranslation(x, y, z);
         this.worldTransform.setTranslation(this.localTransform.getTranslation());
         setTransformRefresh();
     }
@@ -866,7 +851,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return the local transform of this spatial.
      */
-    public Transform getLocalTransform(){
+    public Transform getLocalTransform() {
         return localTransform;
     }
 
@@ -876,7 +861,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @param material The material to set.
      */
-    public void setMaterial(Material material){
+    public void setMaterial(Material material) {
     }
 
     /**
@@ -885,7 +870,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @param light The light to add.
      */
-    public void addLight(Light light){
+    public void addLight(Light light) {
         localLights.add(light);
         setLightListRefresh();
     }
@@ -896,7 +881,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @param light The light to remove.
      * @see Spatial#addLight(com.jme3.light.Light) 
      */
-    public void removeLight(Light light){
+    public void removeLight(Light light) {
         localLights.remove(light);
         setLightListRefresh();
     }
@@ -906,7 +891,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial move(float x, float y, float z){
+    public Spatial move(float x, float y, float z) {
         this.localTransform.getTranslation().addLocal(x, y, z);
         this.worldTransform.setTranslation(this.localTransform.getTranslation());
         setTransformRefresh();
@@ -919,7 +904,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial move(Vector3f offset){
+    public Spatial move(Vector3f offset) {
         this.localTransform.getTranslation().addLocal(offset);
         this.worldTransform.setTranslation(this.localTransform.getTranslation());
         setTransformRefresh();
@@ -932,8 +917,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial scale(float s){
-        return scale(s,s,s);
+    public Spatial scale(float s) {
+        return scale(s, s, s);
     }
 
     /**
@@ -941,8 +926,8 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial scale(float x, float y, float z){
-        this.localTransform.getScale().multLocal(x,y,z);
+    public Spatial scale(float x, float y, float z) {
+        this.localTransform.getScale().multLocal(x, y, z);
         this.worldTransform.setScale(this.localTransform.getScale());
         setTransformRefresh();
 
@@ -954,7 +939,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial rotate(Quaternion rot){
+    public Spatial rotate(Quaternion rot) {
         this.localTransform.getRotation().multLocal(rot);
         this.worldTransform.setRotation(this.localTransform.getRotation());
         setTransformRefresh();
@@ -968,7 +953,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial rotate(float yaw, float roll, float pitch){
+    public Spatial rotate(float yaw, float roll, float pitch) {
         assert TempVars.get().lock();
         Quaternion q = TempVars.get().quat1;
         q.fromAngles(yaw, roll, pitch);
@@ -982,7 +967,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * Centers the spatial in the origin of the world bound.
      * @return The spatial on which this method is called, e.g <code>this</code>.
      */
-    public Spatial center(){
+    public Spatial center() {
         Vector3f worldTrans = getWorldTranslation();
         Vector3f worldCenter = getWorldBound().getCenter();
 
@@ -998,15 +983,15 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * the cullmode of it's parent.
      */
     public CullHint getCullHint() {
-        if (cullHint != CullHint.Inherit)
+        if (cullHint != CullHint.Inherit) {
             return cullHint;
-        else if (parent != null)
+        } else if (parent != null) {
             return parent.getCullHint();
-        else
+        } else {
             return CullHint.Dynamic;
+        }
     }
 
-
     /**
      * Returns this spatial's renderqueue bucket. If the mode is set to inherit,
      * then the spatial gets its renderqueue bucket from its parent.
@@ -1014,12 +999,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @return The spatial's current renderqueue mode.
      */
     public RenderQueue.Bucket getQueueBucket() {
-        if (queueBucket != RenderQueue.Bucket.Inherit)
+        if (queueBucket != RenderQueue.Bucket.Inherit) {
             return queueBucket;
-        else if (parent != null)
+        } else if (parent != null) {
             return parent.getQueueBucket();
-        else
+        } else {
             return RenderQueue.Bucket.Opaque;
+        }
     }
 
     /**
@@ -1030,12 +1016,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see ShadowMode
      */
     public RenderQueue.ShadowMode getShadowMode() {
-        if (shadowMode != RenderQueue.ShadowMode.Inherit)
+        if (shadowMode != RenderQueue.ShadowMode.Inherit) {
             return shadowMode;
-        else if (parent != null)
+        } else if (parent != null) {
             return parent.getShadowMode();
-        else
+        } else {
             return ShadowMode.Off;
+        }
     }
 
     /**
@@ -1044,7 +1031,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @param lod The lod level to set.
      */
-    public void setLodLevel(int lod){
+    public void setLodLevel(int lod) {
     }
 
     /**
@@ -1083,11 +1070,12 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @see Mesh#cloneForAnim() 
      */
-    public Spatial clone(boolean cloneMaterial){
-        try{
+    public Spatial clone(boolean cloneMaterial) {
+        try {
             Spatial clone = (Spatial) super.clone();
-            if (worldBound != null)
+            if (worldBound != null) {
                 clone.worldBound = worldBound.clone();
+            }
             clone.worldLights = worldLights.clone();
             clone.localLights = localLights.clone();
 
@@ -1098,11 +1086,11 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
             clone.worldTransform = worldTransform.clone();
             clone.localTransform = localTransform.clone();
 
-            if (clone instanceof Node){
+            if (clone instanceof Node) {
                 Node node = (Node) this;
                 Node nodeClone = (Node) clone;
                 nodeClone.children = new ArrayList<Spatial>();
-                for (Spatial child : node.children){
+                for (Spatial child : node.children) {
                     Spatial childClone = child.clone(cloneMaterial);
                     childClone.parent = nodeClone;
                     nodeClone.children.add(childClone);
@@ -1115,16 +1103,16 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
             clone.setLightListRefresh();
 
             clone.controls = new ArrayList<Control>();
-            for (int i = 0; i < controls.size(); i++){
-                clone.controls.add( controls.get(i).cloneForSpatial(clone) );
+            for (int i = 0; i < controls.size(); i++) {
+                clone.controls.add(controls.get(i).cloneForSpatial(clone));
             }
 
-            if (userData != null){
+            if (userData != null) {
                 clone.userData = (HashMap<String, Savable>) userData.clone();
             }
 
             return clone;
-        }catch (CloneNotSupportedException ex){
+        } catch (CloneNotSupportedException ex) {
             throw new AssertionError();
         }
     }
@@ -1142,7 +1130,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see Mesh#cloneForAnim() 
      */
     @Override
-    public Spatial clone(){
+    public Spatial clone() {
         return clone(true);
     }
 
@@ -1155,33 +1143,36 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      */
     public abstract Spatial deepClone();
 
-    public void setUserData(String key, Object data){
-        if (userData == null)
+    public void setUserData(String key, Object data) {
+        if (userData == null) {
             userData = new HashMap<String, Savable>();
+        }
 
-        if (data instanceof Savable){
+        if (data instanceof Savable) {
             userData.put(key, (Savable) data);
-        }else{
+        } else {
             userData.put(key, new UserData(UserData.getObjectType(data), data));
         }
     }
 
-    public Object getUserData(String key){
-        if (userData == null)
+    public Object getUserData(String key) {
+        if (userData == null) {
             return null;
+        }
 
         Savable s = userData.get(key);
-        if (s instanceof UserData){
-            return ((UserData)s).getValue();
-        }else{
+        if (s instanceof UserData) {
+            return ((UserData) s).getValue();
+        } else {
             return s;
         }
     }
 
-    public Collection<String> getUserDataKeys(){
-        if (userData != null)
+    public Collection<String> getUserDataKeys() {
+        if (userData != null) {
             return userData.keySet();
-        
+        }
+
         return Collections.EMPTY_SET;
     }
 
@@ -1202,12 +1193,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      * @see java.util.regex.Pattern
      */
     public boolean matches(Class<? extends Spatial> spatialSubclass,
-                                String nameRegex) {
-        if (spatialSubclass != null && !spatialSubclass.isInstance(this))
+            String nameRegex) {
+        if (spatialSubclass != null && !spatialSubclass.isInstance(this)) {
             return false;
-      
-        if (nameRegex != null && (name == null || !name.matches(nameRegex)))
+        }
+
+        if (nameRegex != null && (name == null || !name.matches(nameRegex))) {
             return false;
+        }
 
         return true;
     }
@@ -1232,16 +1225,22 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         worldBound = (BoundingVolume) ic.readSavable("world_bound", null);
         cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit);
         queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class,
-                                    RenderQueue.Bucket.Inherit);
+                RenderQueue.Bucket.Inherit);
         shadowMode = ic.readEnum("shadow_mode", ShadowMode.class,
-                                    ShadowMode.Inherit);
+                ShadowMode.Inherit);
 
         localTransform = (Transform) ic.readSavable("transform", Transform.Identity);
-        
+
         localLights = (LightList) ic.readSavable("lights", null);
         localLights.setOwner(this);
 
-        controls = ic.readSavableArrayList("controlsList", null);
+        //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split
+        //the AnimControl creates the SkeletonControl for old files and add it to the spatial.
+        //The SkeletonControl must be the last in the stack so we add the list of all other control before it.
+        //When backward compatibility won't be needed anymore this can be replaced by : 
+        //controls = ic.readSavableArrayList("controlsList", null));
+        controls.addAll(0, ic.readSavableArrayList("controlsList", null));
+
         userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null);
     }
 
@@ -1315,7 +1314,7 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
      *
      * @param shadowMode The local shadow mode to set.
      */
-    public void setShadowMode(RenderQueue.ShadowMode shadowMode){
+    public void setShadowMode(RenderQueue.ShadowMode shadowMode) {
         this.shadowMode = shadowMode;
     }
 
@@ -1398,13 +1397,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
         store.setTranslation(getWorldTranslation());
         return store;
     }
-    
+
     /**
      * Visit each scene graph element ordered by DFS
      * @param visitor
      */
     public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
-    
+
     /**
      * Visit each scene graph element ordered by BFS
      * @param visitor
@@ -1412,14 +1411,13 @@ public abstract class Spatial implements Savable, Cloneable, Collidable {
     public void breadthFirstTraversal(SceneGraphVisitor visitor) {
         Queue<Spatial> queue = new LinkedList<Spatial>();
         queue.add(this);
-        
+
         while (!queue.isEmpty()) {
             Spatial s = queue.poll();
             visitor.visit(s);
             s.breadthFirstTraversal(visitor, queue);
         }
     }
-    
+
     protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
 }
-

+ 10 - 0
engine/src/jbullet/com/jme3/bullet/control/RagdollControl.java

@@ -3,6 +3,7 @@ package com.jme3.bullet.control;
 import com.jme3.animation.AnimControl;
 import com.jme3.animation.Bone;
 import com.jme3.animation.Skeleton;
+import com.jme3.animation.SkeletonControl;
 import com.jme3.asset.AssetManager;
 import com.jme3.bullet.PhysicsSpace;
 import com.jme3.bullet.collision.shapes.BoxCollisionShape;
@@ -107,6 +108,15 @@ public class RagdollControl implements PhysicsControl {
 
     public void setSpatial(Spatial model) {
         targetModel = model;
+        
+        //HACK ALERT change this
+        //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack
+        //Find a proper way to order the controls.
+        SkeletonControl sc = model.getControl(SkeletonControl.class);
+        model.removeControl(sc);
+        model.addControl(sc);
+        //---- 
+        
         removeFromPhysicsSpace();
         clearData();
         // put into bind pose and compute bone transforms in model space

+ 227 - 213
engine/src/ogre/com/jme3/scene/plugins/ogre/MeshLoader.java

@@ -29,11 +29,11 @@
  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
-
 package com.jme3.scene.plugins.ogre;
 
 import com.jme3.animation.AnimControl;
 import com.jme3.animation.BoneAnimation;
+import com.jme3.animation.SkeletonControl;
 import com.jme3.asset.AssetInfo;
 import com.jme3.asset.AssetKey;
 import com.jme3.asset.AssetLoader;
@@ -81,15 +81,12 @@ import static com.jme3.util.xml.SAXUtil.*;
 public class MeshLoader extends DefaultHandler implements AssetLoader {
 
     private static final Logger logger = Logger.getLogger(MeshLoader.class.getName());
-
     public static boolean AUTO_INTERLEAVE = true;
     public static boolean HARDWARE_SKINNING = false;
-
     private String meshName;
     private String folderName;
     private AssetManager assetManager;
     private MaterialList materialList;
-
     private ShortBuffer sb;
     private IntBuffer ib;
     private FloatBuffer fb;
@@ -105,16 +102,14 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     private boolean bigindices = false;
     private int vertCount;
     private int triCount;
-
     private List<Geometry> geoms = new ArrayList<Geometry>();
     private List<Boolean> usesSharedGeom = new ArrayList<Boolean>();
     private IntMap<List<VertexBuffer>> lodLevels = new IntMap<List<VertexBuffer>>();
     private AnimData animData;
-
     private ByteBuffer indicesData;
     private FloatBuffer weightsFloatData;
 
-    public MeshLoader(){
+    public MeshLoader() {
         super();
     }
 
@@ -150,44 +145,44 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     public void endDocument() {
     }
 
-    private void pushFace(String v1, String v2, String v3) throws SAXException{
+    private void pushFace(String v1, String v2, String v3) throws SAXException {
         int i1 = parseInt(v1);
 
         // TODO: fan/strip support
         int i2 = parseInt(v2);
         int i3 = parseInt(v3);
-        if (ib != null){
+        if (ib != null) {
             ib.put(i1).put(i2).put(i3);
-        }else{
-            sb.put((short)i1).put((short)i2).put((short)i3);
+        } else {
+            sb.put((short) i1).put((short) i2).put((short) i3);
         }
     }
 
-    private void startFaces(String count) throws SAXException{
+    private void startFaces(String count) throws SAXException {
         int numFaces = parseInt(count);
         int numIndices;
 
-        if (mesh.getMode() == Mesh.Mode.Triangles){
+        if (mesh.getMode() == Mesh.Mode.Triangles) {
             //mesh.setTriangleCount(numFaces);
             numIndices = numFaces * 3;
-        }else{
+        } else {
             throw new SAXException("Triangle strip or fan not supported!");
         }
 
         int numVerts;
-        if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size()-1)){
+        if (usesSharedGeom.size() > 0 && usesSharedGeom.get(geoms.size() - 1)) {
 //            sharedgeom.getMesh().updateCounts();
             numVerts = sharedmesh.getVertexCount();
-        }else{
+        } else {
 //            mesh.updateCounts();
             numVerts = mesh.getVertexCount();
         }
         vb = new VertexBuffer(VertexBuffer.Type.Index);
-        if (!bigindices){
+        if (!bigindices) {
             sb = BufferUtils.createShortBuffer(numIndices);
             ib = null;
             vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
-        }else{
+        } else {
             ib = BufferUtils.createIntBuffer(numIndices);
             sb = null;
             vb.setupData(Usage.Static, 3, Format.UnsignedInt, ib);
@@ -195,77 +190,81 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         mesh.setBuffer(vb);
     }
 
-    private void applyMaterial(Geometry geom, String matName){
+    private void applyMaterial(Geometry geom, String matName) {
         Material mat = null;
-        if (matName.endsWith(".j3m")){
+        if (matName.endsWith(".j3m")) {
             // load as native jme3 material instance
             mat = assetManager.loadMaterial(matName);
-        }else{
-            if (materialList != null){
+        } else {
+            if (materialList != null) {
                 mat = materialList.get(matName);
             }
-            if (mat == null){
+            if (mat == null) {
                 logger.log(Level.WARNING, "Material {0} not found. Applying default material", matName);
                 mat = (Material) assetManager.loadAsset(new AssetKey("Common/Materials/RedColor.j3m"));
             }
         }
-        
-        if (mat == null)
+
+        if (mat == null) {
             throw new RuntimeException("Cannot locate material named " + matName);
+        }
 
-        if (mat.isTransparent())
+        if (mat.isTransparent()) {
             geom.setQueueBucket(Bucket.Transparent);
+        }
 //        else
 //            geom.setShadowMode(ShadowMode.CastAndReceive);
-        
+
 //        if (mat.isReceivesShadows())
-            
-            
+
+
         geom.setMaterial(mat);
     }
 
-    private void startMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException{
+    private void startMesh(String matName, String usesharedvertices, String use32bitIndices, String opType) throws SAXException {
         mesh = new Mesh();
-        if (opType == null || opType.equals("triangle_list")){
+        if (opType == null || opType.equals("triangle_list")) {
             mesh.setMode(Mesh.Mode.Triangles);
-        }else if (opType.equals("triangle_strip")){
+        } else if (opType.equals("triangle_strip")) {
             mesh.setMode(Mesh.Mode.TriangleStrip);
-        }else if (opType.equals("triangle_fan")){
+        } else if (opType.equals("triangle_fan")) {
             mesh.setMode(Mesh.Mode.TriangleFan);
         }
 
         bigindices = parseBool(use32bitIndices, false);
         boolean sharedverts = parseBool(usesharedvertices, false);
-        if (sharedverts){
+        if (sharedverts) {
             usesSharedGeom.add(true);
             // import vertexbuffers from shared geom
             IntMap<VertexBuffer> sharedBufs = sharedmesh.getBuffers();
-            for (Entry<VertexBuffer> entry : sharedBufs){
-                    mesh.setBuffer(entry.getValue());
+            for (Entry<VertexBuffer> entry : sharedBufs) {
+                mesh.setBuffer(entry.getValue());
             }
             // this mesh is shared!
-        }else{
+        } else {
             usesSharedGeom.add(false);
         }
 
-        if (meshName == null)
-            geom = new Geometry("OgreSubmesh-"+(++geomIdx), mesh);
-        else
-            geom = new Geometry(meshName+"-geom-"+(++geomIdx), mesh);
+        if (meshName == null) {
+            geom = new Geometry("OgreSubmesh-" + (++geomIdx), mesh);
+        } else {
+            geom = new Geometry(meshName + "-geom-" + (++geomIdx), mesh);
+        }
 
         applyMaterial(geom, matName);
         geoms.add(geom);
     }
 
-    private void startSharedGeom(String vertexcount) throws SAXException{
+    private void startSharedGeom(String vertexcount) throws SAXException {
         sharedmesh = new Mesh();
         vertCount = parseInt(vertexcount);
 //        sharedmesh.setVertexCount(vertCount);
 
-        if (meshName == null)
+        if (meshName == null) {
             sharedgeom = new Geometry("Ogre-SharedGeom", sharedmesh);
-        else
-            sharedgeom = new Geometry(meshName+"-sharedgeom", sharedmesh);
+        } else {
+            sharedgeom = new Geometry(meshName + "-sharedgeom", sharedmesh);
+        }
 
         sharedgeom.setCullHint(CullHint.Always);
         geoms.add(sharedgeom);
@@ -275,7 +274,7 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         mesh = sharedmesh;
     }
 
-    private void startGeometry(String vertexcount) throws SAXException{
+    private void startGeometry(String vertexcount) throws SAXException {
         vertCount = parseInt(vertexcount);
 //        mesh.setVertexCount(vertCount);
     }
@@ -284,51 +283,51 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
      * Normalizes weights if needed and finds largest amount of weights used
      * for all vertices in the buffer.
      */
-    private void endBoneAssigns(){
-        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){
+    private void endBoneAssigns() {
+        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
             return;
         }
 
         //int vertCount = mesh.getVertexCount();
         int maxWeightsPerVert = 0;
         weightsFloatData.rewind();
-        for (int v = 0; v < vertCount; v++){
+        for (int v = 0; v < vertCount; v++) {
             float w0 = weightsFloatData.get(),
-                  w1 = weightsFloatData.get(),
-                  w2 = weightsFloatData.get(),
-                  w3 = weightsFloatData.get();
+                    w1 = weightsFloatData.get(),
+                    w2 = weightsFloatData.get(),
+                    w3 = weightsFloatData.get();
 
-            if (w3 != 0){
+            if (w3 != 0) {
                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 4);
-            }else if (w2 != 0){
+            } else if (w2 != 0) {
                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 3);
-            }else if (w1 != 0){
+            } else if (w1 != 0) {
                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 2);
-            }else if (w0 != 0){
+            } else if (w0 != 0) {
                 maxWeightsPerVert = Math.max(maxWeightsPerVert, 1);
             }
 
             float sum = w0 + w1 + w2 + w3;
-            if (sum != 1f){
-                weightsFloatData.position(weightsFloatData.position()-4);
+            if (sum != 1f) {
+                weightsFloatData.position(weightsFloatData.position() - 4);
                 // compute new vals based on sum
                 float sumToB = 1f / sum;
-                weightsFloatData.put( w0 * sumToB );
-                weightsFloatData.put( w1 * sumToB );
-                weightsFloatData.put( w2 * sumToB );
-                weightsFloatData.put( w3 * sumToB );
+                weightsFloatData.put(w0 * sumToB);
+                weightsFloatData.put(w1 * sumToB);
+                weightsFloatData.put(w2 * sumToB);
+                weightsFloatData.put(w3 * sumToB);
             }
         }
         weightsFloatData.rewind();
 
         weightsFloatData = null;
         indicesData = null;
-        
+
         mesh.setMaxNumWeights(maxWeightsPerVert);
     }
 
-    private void startBoneAssigns(){
-        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)){
+    private void startBoneAssigns() {
+        if (mesh != sharedmesh && usesSharedGeom.get(geoms.size() - 1)) {
             // will use bone assignments from shared mesh (?)
             return;
         }
@@ -338,53 +337,53 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         // each vertex has
         // - 4 bone weights
         // - 4 bone indices
-        if (HARDWARE_SKINNING){
+        if (HARDWARE_SKINNING) {
             weightsFloatData = BufferUtils.createFloatBuffer(vertCount * 4);
             indicesData = BufferUtils.createByteBuffer(vertCount * 4);
-        }else{
+        } else {
             // create array-backed buffers if software skinning for access speed
             weightsFloatData = FloatBuffer.allocate(vertCount * 4);
             indicesData = ByteBuffer.allocate(vertCount * 4);
         }
-        
+
         VertexBuffer weights = new VertexBuffer(Type.BoneWeight);
         VertexBuffer indices = new VertexBuffer(Type.BoneIndex);
 
         Usage usage = HARDWARE_SKINNING ? Usage.Static : Usage.CpuOnly;
         weights.setupData(usage, 4, Format.Float, weightsFloatData);
         indices.setupData(usage, 4, Format.UnsignedByte, indicesData);
-        
+
         mesh.setBuffer(weights);
         mesh.setBuffer(indices);
     }
 
-    private void startVertexBuffer(Attributes attribs) throws SAXException{
-        if (parseBool(attribs.getValue("positions"), false)){
+    private void startVertexBuffer(Attributes attribs) throws SAXException {
+        if (parseBool(attribs.getValue("positions"), false)) {
             vb = new VertexBuffer(Type.Position);
             fb = BufferUtils.createFloatBuffer(vertCount * 3);
             vb.setupData(Usage.Static, 3, Format.Float, fb);
             mesh.setBuffer(vb);
         }
-        if (parseBool(attribs.getValue("normals"), false)){
+        if (parseBool(attribs.getValue("normals"), false)) {
             vb = new VertexBuffer(Type.Normal);
             fb = BufferUtils.createFloatBuffer(vertCount * 3);
             vb.setupData(Usage.Static, 3, Format.Float, fb);
             mesh.setBuffer(vb);
         }
-        if (parseBool(attribs.getValue("colours_diffuse"), false)){
+        if (parseBool(attribs.getValue("colours_diffuse"), false)) {
             vb = new VertexBuffer(Type.Color);
             fb = BufferUtils.createFloatBuffer(vertCount * 4);
             vb.setupData(Usage.Static, 4, Format.Float, fb);
             mesh.setBuffer(vb);
         }
-        if (parseBool(attribs.getValue("tangents"), false)){
+        if (parseBool(attribs.getValue("tangents"), false)) {
             int dimensions = parseInt(attribs.getValue("tangent_dimensions"), 3);
             vb = new VertexBuffer(Type.Tangent);
             fb = BufferUtils.createFloatBuffer(vertCount * dimensions);
             vb.setupData(Usage.Static, dimensions, Format.Float, fb);
             mesh.setBuffer(vb);
         }
-        if (parseBool(attribs.getValue("binormals"), false)){
+        if (parseBool(attribs.getValue("binormals"), false)) {
             vb = new VertexBuffer(Type.Binormal);
             fb = BufferUtils.createFloatBuffer(vertCount * 3);
             vb.setupData(Usage.Static, 3, Format.Float, fb);
@@ -392,17 +391,19 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         }
 
         int texCoords = parseInt(attribs.getValue("texture_coords"), 0);
-        for (int i = 0; i < texCoords; i++){
+        for (int i = 0; i < texCoords; i++) {
             int dims = parseInt(attribs.getValue("texture_coord_dimensions_" + i), 2);
-            if (dims < 1 || dims > 4)
+            if (dims < 1 || dims > 4) {
                 throw new SAXException("Texture coord dimensions must be 1 <= dims <= 4");
+            }
 
-            if (i >= 2)
+            if (i >= 2) {
                 throw new SAXException("More than 2 texture coordinates not supported");
+            }
 
-            if (i == 0){
+            if (i == 0) {
                 vb = new VertexBuffer(Type.TexCoord);
-            }else{
+            } else {
                 vb = new VertexBuffer(Type.TexCoord2);
             }
             fb = BufferUtils.createFloatBuffer(vertCount * dims);
@@ -411,51 +412,47 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         }
     }
 
-    private void startVertex(){
+    private void startVertex() {
         texCoordIdx = 0;
     }
 
-    private void pushAttrib(Type type, Attributes attribs) throws SAXException{
+    private void pushAttrib(Type type, Attributes attribs) throws SAXException {
         try {
             FloatBuffer buf = (FloatBuffer) mesh.getBuffer(type).getData();
-            buf.put(parseFloat(attribs.getValue("x")))
-           .put(parseFloat(attribs.getValue("y")))
-           .put(parseFloat(attribs.getValue("z")));
-        } catch (Exception ex){
-           throw new SAXException("Failed to push attrib", ex);
+            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
+        } catch (Exception ex) {
+            throw new SAXException("Failed to push attrib", ex);
         }
     }
 
-    private void pushTangent(Attributes attribs) throws SAXException{
+    private void pushTangent(Attributes attribs) throws SAXException {
         try {
             VertexBuffer tangentBuf = mesh.getBuffer(Type.Tangent);
             FloatBuffer buf = (FloatBuffer) tangentBuf.getData();
-            buf.put(parseFloat(attribs.getValue("x")))
-           .put(parseFloat(attribs.getValue("y")))
-           .put(parseFloat(attribs.getValue("z")));
-            if (tangentBuf.getNumComponents() == 4){
+            buf.put(parseFloat(attribs.getValue("x"))).put(parseFloat(attribs.getValue("y"))).put(parseFloat(attribs.getValue("z")));
+            if (tangentBuf.getNumComponents() == 4) {
                 buf.put(parseFloat(attribs.getValue("w")));
             }
-        } catch (Exception ex){
-           throw new SAXException("Failed to push attrib", ex);
+        } catch (Exception ex) {
+            throw new SAXException("Failed to push attrib", ex);
         }
     }
 
-    private void pushTexCoord(Attributes attribs) throws SAXException{
-        if (texCoordIdx >= 2)
+    private void pushTexCoord(Attributes attribs) throws SAXException {
+        if (texCoordIdx >= 2) {
             return; // TODO: More than 2 texcoords
-
+        }
         Type type = texCoordIdx == 0 ? Type.TexCoord : Type.TexCoord2;
-        
+
         VertexBuffer tcvb = mesh.getBuffer(type);
         FloatBuffer buf = (FloatBuffer) tcvb.getData();
 
         buf.put(parseFloat(attribs.getValue("u")));
-        if (tcvb.getNumComponents() >= 2){
+        if (tcvb.getNumComponents() >= 2) {
             buf.put(parseFloat(attribs.getValue("v")));
-            if (tcvb.getNumComponents() >= 3){
+            if (tcvb.getNumComponents() >= 3) {
                 buf.put(parseFloat(attribs.getValue("w")));
-                if (tcvb.getNumComponents() == 4){
+                if (tcvb.getNumComponents() == 4) {
                     buf.put(parseFloat(attribs.getValue("x")));
                 }
             }
@@ -464,36 +461,38 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         texCoordIdx++;
     }
 
-    private void pushColor(Attributes attribs) throws SAXException{
+    private void pushColor(Attributes attribs) throws SAXException {
         FloatBuffer buf = (FloatBuffer) mesh.getBuffer(Type.Color).getData();
         String value = parseString(attribs.getValue("value"));
         String[] vals = value.split(" ");
-        if (vals.length != 3 && vals.length != 4)
+        if (vals.length != 3 && vals.length != 4) {
             throw new SAXException("Color value must contain 3 or 4 components");
+        }
 
         ColorRGBA color = new ColorRGBA();
         color.r = parseFloat(vals[0]);
         color.g = parseFloat(vals[1]);
         color.b = parseFloat(vals[2]);
-        if (vals.length == 3)
+        if (vals.length == 3) {
             color.a = 1f;
-        else
+        } else {
             color.a = parseFloat(vals[3]);
-        
+        }
+
         buf.put(color.r).put(color.g).put(color.b).put(color.a);
     }
 
-    private void startLodFaceList(String submeshindex, String numfaces){
+    private void startLodFaceList(String submeshindex, String numfaces) {
         int index = Integer.parseInt(submeshindex);
         int faceCount = Integer.parseInt(numfaces);
-        
+
         vb = new VertexBuffer(VertexBuffer.Type.Index);
         sb = BufferUtils.createShortBuffer(faceCount * 3);
         ib = null;
         vb.setupData(Usage.Static, 3, Format.UnsignedShort, sb);
 
         List<VertexBuffer> levels = lodLevels.get(index);
-        if (levels == null){
+        if (levels == null) {
             levels = new ArrayList<VertexBuffer>();
             Mesh submesh = geoms.get(index).getMesh();
             levels.add(submesh.getBuffer(Type.Index));
@@ -503,13 +502,13 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         levels.add(vb);
     }
 
-    private void startLevelOfDetail(String numlevels){
+    private void startLevelOfDetail(String numlevels) {
 //        numLevels = Integer.parseInt(numlevels);
     }
 
-    private void endLevelOfDetail(){
+    private void endLevelOfDetail() {
         // set the lod data for each mesh
-        for (Entry<List<VertexBuffer>> entry : lodLevels){
+        for (Entry<List<VertexBuffer>> entry : lodLevels) {
             Mesh m = geoms.get(entry.getKey()).getMesh();
             List<VertexBuffer> levels = entry.getValue();
             VertexBuffer[] levelArray = new VertexBuffer[levels.size()];
@@ -518,11 +517,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         }
     }
 
-    private void startLodGenerated(String depthsqr){
+    private void startLodGenerated(String depthsqr) {
 //        dist = Float.parseFloat(depthsqr);
     }
 
-    private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException{
+    private void pushBoneAssign(String vertIndex, String boneIndex, String weight) throws SAXException {
         int vert = parseInt(vertIndex);
         float w = parseFloat(weight);
         byte bone = (byte) parseInt(boneIndex);
@@ -532,95 +531,101 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
 
         int i;
         // see which weights are unused for a given bone
-        for (i = vert * 4; i < vert * 4 + 4; i++){
+        for (i = vert * 4; i < vert * 4 + 4; i++) {
             float v = weightsFloatData.get(i);
-            if (v == 0)
+            if (v == 0) {
                 break;
+            }
         }
 
         weightsFloatData.put(i, w);
         indicesData.put(i, bone);
     }
 
-    private void startSkeleton(String name){
+    private void startSkeleton(String name) {
         animData = (AnimData) assetManager.loadAsset(folderName + name + ".xml");
         //TODO:workaround for meshxml / mesh.xml
-        if(animData==null)
+        if (animData == null) {
             animData = (AnimData) assetManager.loadAsset(folderName + name + "xml");
+        }
     }
 
-    private void startSubmeshName(String indexStr, String nameStr){
+    private void startSubmeshName(String indexStr, String nameStr) {
         int index = Integer.parseInt(indexStr);
         geoms.get(index).setName(nameStr);
     }
 
     @Override
-    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException{
-        if (ignoreUntilEnd != null)
+    public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException {
+        if (ignoreUntilEnd != null) {
             return;
+        }
 
-        if (qName.equals("texcoord")){
+        if (qName.equals("texcoord")) {
             pushTexCoord(attribs);
-        }else if (qName.equals("vertexboneassignment")){
+        } else if (qName.equals("vertexboneassignment")) {
             pushBoneAssign(attribs.getValue("vertexindex"),
-                           attribs.getValue("boneindex"),
-                           attribs.getValue("weight"));
-        }else if (qName.equals("face")){
+                    attribs.getValue("boneindex"),
+                    attribs.getValue("weight"));
+        } else if (qName.equals("face")) {
             pushFace(attribs.getValue("v1"),
-                     attribs.getValue("v2"),
-                     attribs.getValue("v3"));
-        }else if (qName.equals("position")){
+                    attribs.getValue("v2"),
+                    attribs.getValue("v3"));
+        } else if (qName.equals("position")) {
             pushAttrib(Type.Position, attribs);
-        }else if (qName.equals("normal")){
+        } else if (qName.equals("normal")) {
             pushAttrib(Type.Normal, attribs);
-        }else if (qName.equals("tangent")){
+        } else if (qName.equals("tangent")) {
             pushTangent(attribs);
-        }else if (qName.equals("binormal")){
+        } else if (qName.equals("binormal")) {
             pushAttrib(Type.Binormal, attribs);
-        }else if (qName.equals("colour_diffuse")){
+        } else if (qName.equals("colour_diffuse")) {
             pushColor(attribs);
-        }else if (qName.equals("vertex")){
-            startVertex(); 
-        }else if (qName.equals("faces")){
+        } else if (qName.equals("vertex")) {
+            startVertex();
+        } else if (qName.equals("faces")) {
             startFaces(attribs.getValue("count"));
-        }else if (qName.equals("geometry")){
+        } else if (qName.equals("geometry")) {
             String count = attribs.getValue("vertexcount");
-            if (count == null)
+            if (count == null) {
                 count = attribs.getValue("count");
+            }
 
             startGeometry(count);
-        }else if (qName.equals("vertexbuffer")){
+        } else if (qName.equals("vertexbuffer")) {
             startVertexBuffer(attribs);
-        }else if (qName.equals("lodfacelist")){
+        } else if (qName.equals("lodfacelist")) {
             startLodFaceList(attribs.getValue("submeshindex"),
-                             attribs.getValue("numfaces"));
-        }else if (qName.equals("lodgenerated")){
+                    attribs.getValue("numfaces"));
+        } else if (qName.equals("lodgenerated")) {
             startLodGenerated(attribs.getValue("fromdepthsquared"));
-        }else if (qName.equals("levelofdetail")){
+        } else if (qName.equals("levelofdetail")) {
             startLevelOfDetail(attribs.getValue("numlevels"));
-        }else if (qName.equals("boneassignments")){
+        } else if (qName.equals("boneassignments")) {
             startBoneAssigns();
-        }else if (qName.equals("submesh")){
+        } else if (qName.equals("submesh")) {
             startMesh(attribs.getValue("material"),
-                      attribs.getValue("usesharedvertices"),
-                      attribs.getValue("use32bitindexes"),
-                      attribs.getValue("operationtype"));
-        }else if (qName.equals("sharedgeometry")){
+                    attribs.getValue("usesharedvertices"),
+                    attribs.getValue("use32bitindexes"),
+                    attribs.getValue("operationtype"));
+        } else if (qName.equals("sharedgeometry")) {
             String count = attribs.getValue("vertexcount");
-            if (count == null)
+            if (count == null) {
                 count = attribs.getValue("count");
+            }
 
-            if (count != null && !count.equals("0"))
+            if (count != null && !count.equals("0")) {
                 startSharedGeom(count);
-        }else if (qName.equals("submeshes")){
+            }
+        } else if (qName.equals("submeshes")) {
             // ok
-        }else if (qName.equals("skeletonlink")){
+        } else if (qName.equals("skeletonlink")) {
             startSkeleton(attribs.getValue("name"));
-        }else if (qName.equals("submeshname")){
+        } else if (qName.equals("submeshname")) {
             startSubmeshName(attribs.getValue("index"), attribs.getValue("name"));
-        }else if (qName.equals("mesh")){
+        } else if (qName.equals("mesh")) {
             // ok
-        }else{
+        } else {
             logger.log(Level.WARNING, "Unknown tag: {0}. Ignoring.", qName);
             ignoreUntilEnd = qName;
         }
@@ -628,56 +633,59 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
 
     @Override
     public void endElement(String uri, String name, String qName) {
-        if (ignoreUntilEnd != null){
-            if (ignoreUntilEnd.equals(qName))
+        if (ignoreUntilEnd != null) {
+            if (ignoreUntilEnd.equals(qName)) {
                 ignoreUntilEnd = null;
+            }
 
             return;
         }
 
-        if (qName.equals("submesh")){
+        if (qName.equals("submesh")) {
             bigindices = false;
             geom = null;
             mesh = null;
-        }else if (qName.equals("submeshes")){
+        } else if (qName.equals("submeshes")) {
             // IMPORTANT: restore sharedgeoemtry, for use with shared boneweights
             geom = sharedgeom;
             mesh = sharedmesh;
-        }else if (qName.equals("faces")){
-            if (ib != null)
+        } else if (qName.equals("faces")) {
+            if (ib != null) {
                 ib.flip();
-            else
+            } else {
                 sb.flip();
-            
+            }
+
             vb = null;
             ib = null;
             sb = null;
-        }else if (qName.equals("vertexbuffer")){
+        } else if (qName.equals("vertexbuffer")) {
             fb = null;
             vb = null;
-        }else if (qName.equals("geometry")
-               || qName.equals("sharedgeometry")){
+        } else if (qName.equals("geometry")
+                || qName.equals("sharedgeometry")) {
             // finish writing to buffers
             IntMap<VertexBuffer> bufs = mesh.getBuffers();
-            for (Entry<VertexBuffer> entry : bufs){
+            for (Entry<VertexBuffer> entry : bufs) {
                 Buffer data = entry.getValue().getData();
-                if (data.position() != 0)
+                if (data.position() != 0) {
                     data.flip();
+                }
             }
             mesh.updateBound();
             mesh.setStatic();
 
-            if (qName.equals("sharedgeometry")){
+            if (qName.equals("sharedgeometry")) {
                 geom = null;
                 mesh = null;
             }
-        }else if (qName.equals("lodfacelist")){
+        } else if (qName.equals("lodfacelist")) {
             sb.flip();
             vb = null;
             sb = null;
-        }else if (qName.equals("levelofdetail")){
+        } else if (qName.equals("levelofdetail")) {
             endLevelOfDetail();
-        }else if (qName.equals("boneassignments")){
+        } else if (qName.equals("boneassignments")) {
             endBoneAssigns();
         }
     }
@@ -686,9 +694,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
     public void characters(char ch[], int start, int length) {
     }
 
-    private void createBindPose(Mesh mesh){
+    private void createBindPose(Mesh mesh) {
         VertexBuffer pos = mesh.getBuffer(Type.Position);
-        if (pos == null || mesh.getBuffer(Type.BoneIndex) == null){
+        if (pos == null || mesh.getBuffer(Type.BoneIndex) == null) {
             // ignore, this mesh doesn't have positional data
             // or it doesn't have bone-vertex assignments, so its not animated
             return;
@@ -696,9 +704,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
 
         VertexBuffer bindPos = new VertexBuffer(Type.BindPosePosition);
         bindPos.setupData(Usage.CpuOnly,
-                          3,
-                          Format.Float,
-                          BufferUtils.clone(pos.getData()));
+                3,
+                Format.Float,
+                BufferUtils.clone(pos.getData()));
         mesh.setBuffer(bindPos);
 
         // XXX: note that this method also sets stream mode
@@ -706,111 +714,118 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
         pos.setUsage(Usage.Stream);
 
         VertexBuffer norm = mesh.getBuffer(Type.Normal);
-        if (norm != null){
+        if (norm != null) {
             VertexBuffer bindNorm = new VertexBuffer(Type.BindPoseNormal);
             bindNorm.setupData(Usage.CpuOnly,
-                              3,
-                              Format.Float,
-                              BufferUtils.clone(norm.getData()));
+                    3,
+                    Format.Float,
+                    BufferUtils.clone(norm.getData()));
             mesh.setBuffer(bindNorm);
             norm.setUsage(Usage.Stream);
         }
     }
 
-    private Node compileModel(){
+    private Node compileModel() {
         String nodeName;
-        if (meshName == null)
-            nodeName = "OgreMesh"+(++nodeIdx);
-        else
-            nodeName = meshName+"-ogremesh";
+        if (meshName == null) {
+            nodeName = "OgreMesh" + (++nodeIdx);
+        } else {
+            nodeName = meshName + "-ogremesh";
+        }
 
         Node model = new Node(nodeName);
-        if (animData != null){
+        if (animData != null) {
             ArrayList<Mesh> newMeshes = new ArrayList<Mesh>(geoms.size());
 
             // generate bind pose for mesh and add to skin-list
             // ONLY if not using shared geometry
             // This includes the shared geoemtry itself actually
-            for (int i = 0; i < geoms.size(); i++){
+            for (int i = 0; i < geoms.size(); i++) {
                 Geometry g = geoms.get(i);
                 Mesh m = geoms.get(i).getMesh();
                 boolean useShared = usesSharedGeom.get(i);
                 // create bind pose
-                if (!useShared){
+                if (!useShared) {
                     createBindPose(m);
                     newMeshes.add(m);
-                }else{
-                    VertexBuffer bindPos  = sharedmesh.getBuffer(Type.BindPosePosition);
+                } else {
+                    VertexBuffer bindPos = sharedmesh.getBuffer(Type.BindPosePosition);
                     VertexBuffer bindNorm = sharedmesh.getBuffer(Type.BindPoseNormal);
                     VertexBuffer boneIndex = sharedmesh.getBuffer(Type.BoneIndex);
                     VertexBuffer boneWeight = sharedmesh.getBuffer(Type.BoneWeight);
-                    
-                    if (bindPos != null)
+
+                    if (bindPos != null) {
                         m.setBuffer(bindPos);
+                    }
 
-                    if (bindNorm != null)
+                    if (bindNorm != null) {
                         m.setBuffer(bindNorm);
+                    }
 
-                    if (boneIndex != null)
+                    if (boneIndex != null) {
                         m.setBuffer(boneIndex);
+                    }
 
-                    if (boneWeight != null)
+                    if (boneWeight != null) {
                         m.setBuffer(boneWeight);
+                    }
                 }
             }
             Mesh[] meshes = new Mesh[newMeshes.size()];
-            for (int i = 0; i < meshes.length; i++)
+            for (int i = 0; i < meshes.length; i++) {
                 meshes[i] = newMeshes.get(i);
+            }
 
             HashMap<String, BoneAnimation> anims = new HashMap<String, BoneAnimation>();
             ArrayList<BoneAnimation> animList = animData.anims;
-            for (int i = 0; i < animList.size(); i++){
+            for (int i = 0; i < animList.size(); i++) {
                 BoneAnimation anim = animList.get(i);
                 anims.put(anim.getName(), anim);
             }
 
-            AnimControl ctrl = new AnimControl(model,
-                                               meshes,
-                                               animData.skeleton);
+            //AnimControl ctrl = new AnimControl(model, meshes, animData.skeleton);            
+            AnimControl ctrl = new AnimControl(animData.skeleton);
             ctrl.setAnimations(anims);
             model.addControl(ctrl);
+            SkeletonControl skeletonControl = new SkeletonControl(model, meshes, animData.skeleton);
+            model.addControl(skeletonControl);
         }
 
-        for (int i = 0; i < geoms.size(); i++){
+        for (int i = 0; i < geoms.size(); i++) {
             Geometry g = geoms.get(i);
             Mesh m = g.getMesh();
-            if (sharedmesh != null && usesSharedGeom.get(i)){
+            if (sharedmesh != null && usesSharedGeom.get(i)) {
                 m.setBound(sharedmesh.getBound().clone());
             }
             model.attachChild(geoms.get(i));
         }
-        
+
         return model;
     }
 
     public Object load(AssetInfo info) throws IOException {
-        try{
+        try {
             AssetKey key = info.getKey();
             meshName = key.getName();
             folderName = key.getFolder();
             String ext = key.getExtension();
             meshName = meshName.substring(0, meshName.length() - ext.length() - 1);
-            if (folderName != null && folderName.length() > 0){
+            if (folderName != null && folderName.length() > 0) {
                 meshName = meshName.substring(folderName.length());
             }
             assetManager = info.getManager();
 
-            OgreMeshKey meshKey = null;       
-            if (key instanceof OgreMeshKey){
+            OgreMeshKey meshKey = null;
+            if (key instanceof OgreMeshKey) {
                 meshKey = (OgreMeshKey) key;
                 materialList = meshKey.getMaterialList();
-            }else{             
+            } else {
                 try {
                     materialList = (MaterialList) assetManager.loadAsset(folderName + meshName + ".material");
                 } catch (AssetNotFoundException e) {
                     logger.log(Level.WARNING, "Cannot locate {0}{1}.material for model {2}{3}.{4}", new Object[]{folderName, meshName, folderName, meshName, ext});
                 }
-                
+
             }
 
             XMLReader xr = XMLReaderFactory.createXMLReader();
@@ -821,12 +836,11 @@ public class MeshLoader extends DefaultHandler implements AssetLoader {
             r.close();
 
             return compileModel();
-        }catch (SAXException ex){
+        } catch (SAXException ex) {
             IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml");
             ioEx.initCause(ex);
             throw ioEx;
         }
 
     }
-
 }

+ 7 - 7
engine/src/test/jme3test/bullet/TestBoneRagdoll.java

@@ -87,8 +87,8 @@ public class TestBoneRagdoll  extends SimpleApplication {
         setupLight();
 
         model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
-//           model.setLocalTranslation(5,5,5);
-//        model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X));
+      //     model.setLocalTranslation(5,5,5);
+      //  model.setLocalRotation(new Quaternion().fromAngleAxis(FastMath.HALF_PI, Vector3f.UNIT_X));
 
         //debug view
         AnimControl control= model.getControl(AnimControl.class);
@@ -105,12 +105,12 @@ public class TestBoneRagdoll  extends SimpleApplication {
       //  ragdoll.setEnabled(true);
       //  ragdoll.attachDebugShape(assetManager);
         
-        ragdoll.setSpatial(model);
-        ragdoll.setPhysicsSpace(getPhysicsSpace());
-        control.setRagdoll(ragdoll);
+//        ragdoll.setSpatial(model);
+//        ragdoll.setPhysicsSpace(getPhysicsSpace());
+//        control.setRagdoll(ragdoll);
         
-//        model.addControl(ragdoll);
-//        getPhysicsSpace().add(ragdoll);
+        model.addControl(ragdoll);
+        getPhysicsSpace().add(ragdoll);
         speed = 1f;
 
         rootNode.attachChild(model);

+ 4 - 4
engine/src/test/jme3test/model/anim/TestOgreComplexAnim.java

@@ -131,11 +131,11 @@ public class TestOgreComplexAnim extends SimpleApplication {
         q.fromAngles(0, angle, 0);
 
         b.setUserControl(true);
-        b.setUserTransforms(Vector3f.ZERO, q, new Vector3f(angle, angle, angle));
+        b.setUserTransforms(Vector3f.ZERO, q, Vector3f.ZERO);
         
-//        b2.setUserControl(true);
-//        b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(angle, angle, angle));
-//       
+        b2.setUserControl(true);
+        b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(angle, angle, angle));
+  
   
     }