Explorar o código

Feature: added automatic action mapping instead of explicit mapping in
BlenderKey.

jmekaelthas %!s(int64=11) %!d(string=hai) anos
pai
achega
51215a352e

+ 68 - 143
jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java

@@ -33,11 +33,7 @@ package com.jme3.asset;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Queue;
 
 import com.jme3.animation.Animation;
@@ -66,70 +62,68 @@ import com.jme3.texture.Texture;
  */
 public class BlenderKey extends ModelKey {
 
-    protected static final int          DEFAULT_FPS               = 25;
+    protected static final int         DEFAULT_FPS               = 25;
     /**
      * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time
      * between the frames.
      */
-    protected int                       fps                       = DEFAULT_FPS;
+    protected int                      fps                       = DEFAULT_FPS;
     /**
      * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded.
      */
-    protected int                       featuresToLoad            = FeaturesToLoad.ALL;
+    protected int                      featuresToLoad            = FeaturesToLoad.ALL;
     /** This variable determines if assets that are not linked to the objects should be loaded. */
-    protected boolean                   loadUnlinkedAssets;
+    protected boolean                  loadUnlinkedAssets;
     /** The root path for all the assets. */
-    protected String                    assetRootPath;
+    protected String                   assetRootPath;
     /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */
-    protected boolean                   fixUpAxis                 = true;
+    protected boolean                  fixUpAxis                 = true;
     /** Generated textures resolution (PPU - Pixels Per Unit). */
-    protected int                       generatedTexturePPU       = 128;
+    protected int                      generatedTexturePPU       = 128;
     /**
      * The name of world settings that the importer will use. If not set or specified name does not occur in the file
      * then the first world settings in the file will be used.
      */
-    protected String                    usedWorld;
+    protected String                   usedWorld;
     /**
      * User's default material that is set fo objects that have no material definition in blender. The default value is
      * null. If the value is null the importer will use its own default material (gray color - like in blender).
      */
-    protected Material                  defaultMaterial;
+    protected Material                 defaultMaterial;
     /** Face cull mode. By default it is disabled. */
-    protected FaceCullMode              faceCullMode              = FaceCullMode.Back;
+    protected FaceCullMode             faceCullMode              = FaceCullMode.Back;
     /**
      * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded.
      * If set to -1 then the current layer will be loaded.
      */
-    protected int                       layersToLoad              = -1;
+    protected int                      layersToLoad              = -1;
     /** A variable that toggles the object custom properties loading. */
-    protected boolean                   loadObjectProperties      = true;
+    protected boolean                  loadObjectProperties      = true;
     /**
      * Maximum texture size. Might be dependant on the graphic card.
      * This value is taken from <b>org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE</b>.
      */
-    protected int                       maxTextureSize            = 8192;
+    protected int                      maxTextureSize            = 8192;
     /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */
-    protected boolean                   loadGeneratedTextures;
+    protected boolean                  loadGeneratedTextures;
     /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */
-    protected MipmapGenerationMethod    mipmapGenerationMethod    = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
+    protected MipmapGenerationMethod   mipmapGenerationMethod    = MipmapGenerationMethod.GENERATE_WHEN_NEEDED;
     /**
      * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated
      * textures will get their proper size.
      */
-    protected int                       skyGeneratedTextureSize   = 1000;
+    protected int                      skyGeneratedTextureSize   = 1000;
     /** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */
-    protected float                     skyGeneratedTextureRadius = 1;
+    protected float                    skyGeneratedTextureRadius = 1;
     /** The shape against which the generated texture for the sky will be created. */
-    protected SkyGeneratedTextureShape  skyGeneratedTextureShape  = SkyGeneratedTextureShape.SPHERE;
+    protected SkyGeneratedTextureShape skyGeneratedTextureShape  = SkyGeneratedTextureShape.SPHERE;
     /**
      * This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together
      * and textures that in the final result will never be visible - will be discarded.
      */
-    protected boolean                   optimiseTextures;
-    /** A map between node name and its animation names. */
-    protected Map<String, List<String>> nodeAnimationMap          = new HashMap<String, List<String>>();
-    /** A map between node name and its skeleton animation names. */
-    protected Map<String, List<String>> skeletonAnimationMap      = new HashMap<String, List<String>>();
+    protected boolean                  optimiseTextures;
+    /** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */
+    protected AnimationMatchMethod     animationMatchMethod      = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH;
 
     /**
      * Constructor used by serialization mechanisms.
@@ -446,6 +440,23 @@ public class BlenderKey extends ModelKey {
         return optimiseTextures;
     }
 
+    /**
+     * Sets the way the animations will be matched with skeletons.
+     * 
+     * @param animationMatchMethod
+     *            the way the animations will be matched with skeletons
+     */
+    public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) {
+        this.animationMatchMethod = animationMatchMethod;
+    }
+
+    /**
+     * @return the way the animations will be matched with skeletons
+     */
+    public AnimationMatchMethod getAnimationMatchMethod() {
+        return animationMatchMethod;
+    }
+
     /**
      * This mehtod sets the name of the WORLD data block taht should be used during file loading. By default the name is
      * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used
@@ -482,58 +493,6 @@ public class BlenderKey extends ModelKey {
         return defaultMaterial;
     }
 
-    /**
-     * Adds spatial animation name for specified node.
-     * @param nodeName
-     *            the name of the node
-     * @param animationName
-     *            the spatial animation name
-     */
-    public void addNodeAnimation(String nodeName, String animationName) {
-        List<String> animations = nodeAnimationMap.get(nodeName);
-        if (animations == null) {
-            animations = new ArrayList<String>();
-            nodeAnimationMap.put(nodeName, animations);
-        }
-        animations.add(animationName);
-    }
-
-    /**
-     * Returns all spatial animation names for the given node.
-     * @param nodeName
-     *            the name of the node
-     * @return all spatial animations names or null if none are defined
-     */
-    public List<String> getNodeAnimationNames(String nodeName) {
-        return nodeAnimationMap.get(nodeName);
-    }
-
-    /**
-     * Adds bone animation name for specified node.
-     * @param nodeName
-     *            the name of the node
-     * @param animationName
-     *            the bone animation name
-     */
-    public void addSkeletonAnimation(String nodeName, String animationName) {
-        List<String> animations = skeletonAnimationMap.get(nodeName);
-        if (animations == null) {
-            animations = new ArrayList<String>();
-            skeletonAnimationMap.put(nodeName, animations);
-        }
-        animations.add(animationName);
-    }
-
-    /**
-     * Returns all bone animation names for the given node.
-     * @param nodeName
-     *            the name of the node
-     * @return all bone animations names or null if none are defined
-     */
-    public List<String> getSkeletonAnimationNames(String nodeName) {
-        return skeletonAnimationMap.get(nodeName);
-    }
-
     @Override
     public void write(JmeExporter e) throws IOException {
         super.write(e);
@@ -553,30 +512,7 @@ public class BlenderKey extends ModelKey {
         oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f);
         oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE);
         oc.write(optimiseTextures, "optimise-textures", false);
-
-        if (nodeAnimationMap == null) {
-            oc.write(0, "node-anims-map-size", 0);
-        } else {
-            oc.write(nodeAnimationMap.size(), "node-anims-map-size", 0);
-            int counter = 0;
-            for (Entry<String, List<String>> entry : nodeAnimationMap.entrySet()) {
-                oc.write(entry.getKey(), "node-anim-" + counter, null);
-                oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "node-anims-" + counter, null);
-                ++counter;
-            }
-        }
-
-        if (skeletonAnimationMap == null) {
-            oc.write(0, "skeleton-anims-map-size", 0);
-        } else {
-            oc.write(skeletonAnimationMap.size(), "skeleton-anims-map-size", 0);
-            int counter = 0;
-            for (Entry<String, List<String>> entry : skeletonAnimationMap.entrySet()) {
-                oc.write(entry.getKey(), "skeleton-anim-" + counter, null);
-                oc.write(entry.getValue().toArray(new String[entry.getValue().size()]), "skeleton-anims-" + counter, null);
-                ++counter;
-            }
-        }
+        oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
     }
 
     @Override
@@ -598,32 +534,14 @@ public class BlenderKey extends ModelKey {
         skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f);
         skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE);
         optimiseTextures = ic.readBoolean("optimise-textures", false);
-
-        int animsSize = ic.readInt("node-anims-map-size", 0);
-        nodeAnimationMap = new HashMap<String, List<String>>(animsSize);
-        if (animsSize > 0) {
-            for (int i = 0; i < animsSize; ++i) {
-                String nodeName = ic.readString("node-anim-" + i, null);
-                String[] anims = ic.readStringArray("node-anims-" + i, null);
-                nodeAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
-            }
-        }
-
-        animsSize = ic.readInt("skeleton-anims-map-size", 0);
-        skeletonAnimationMap = new HashMap<String, List<String>>(animsSize);
-        if (animsSize > 0) {
-            for (int i = 0; i < animsSize; ++i) {
-                String nodeName = ic.readString("skeleton-anim-" + i, null);
-                String[] anims = ic.readStringArray("skeleton-anims-" + i, null);
-                skeletonAnimationMap.put(nodeName, new ArrayList<String>(Arrays.asList(anims)));// must create ArrayList because 'asList' method returns unmodifiable list
-            }
-        }
+        animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH);
     }
 
     @Override
     public int hashCode() {
         final int prime = 31;
         int result = super.hashCode();
+        result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode());
         result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode());
         result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode());
         result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode());
@@ -637,9 +555,7 @@ public class BlenderKey extends ModelKey {
         result = prime * result + (loadUnlinkedAssets ? 1231 : 1237);
         result = prime * result + maxTextureSize;
         result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode());
-        result = prime * result + (nodeAnimationMap == null ? 0 : nodeAnimationMap.hashCode());
         result = prime * result + (optimiseTextures ? 1231 : 1237);
-        result = prime * result + (skeletonAnimationMap == null ? 0 : skeletonAnimationMap.hashCode());
         result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius);
         result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode());
         result = prime * result + skyGeneratedTextureSize;
@@ -649,13 +565,13 @@ public class BlenderKey extends ModelKey {
 
     @Override
     public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof BlenderKey)) {
+        if (obj instanceof BlenderKey) {
             return false;
         }
         BlenderKey other = (BlenderKey) obj;
+        if (animationMatchMethod != other.animationMatchMethod) {
+            return false;
+        }
         if (assetRootPath == null) {
             if (other.assetRootPath != null) {
                 return false;
@@ -703,23 +619,9 @@ public class BlenderKey extends ModelKey {
         if (mipmapGenerationMethod != other.mipmapGenerationMethod) {
             return false;
         }
-        if (nodeAnimationMap == null) {
-            if (other.nodeAnimationMap != null) {
-                return false;
-            }
-        } else if (!nodeAnimationMap.equals(other.nodeAnimationMap)) {
-            return false;
-        }
         if (optimiseTextures != other.optimiseTextures) {
             return false;
         }
-        if (skeletonAnimationMap == null) {
-            if (other.skeletonAnimationMap != null) {
-                return false;
-            }
-        } else if (!skeletonAnimationMap.equals(other.skeletonAnimationMap)) {
-            return false;
-        }
         if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) {
             return false;
         }
@@ -773,6 +675,29 @@ public class BlenderKey extends ModelKey {
         CUBE, SPHERE;
     }
 
+    /**
+     * This enum describes which animations should be attached to which armature.
+     * Blender does not store the mapping between action and armature. That is why the importer
+     * will try to match those by comparing bone name of the armature with the channel names
+     * int the actions.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    public static enum AnimationMatchMethod {
+        /**
+         * Animation is matched with skeleton when at leas one bone name matches the name of the action channel.
+         * All the bones that do not have their corresponding channel in the animation will not get the proper tracks for
+         * this particulat animation.
+         * Also the channel will not be used for the animation if it does not find the proper bone name.
+         */
+        AT_LEAST_ONE_NAME_MATCH,
+        /**
+         * Animation is matched when all action names are covered by the target names (bone names or the name of the
+         * animated spatial.
+         */
+        ALL_NAMES_MATCH;
+    }
+
     /**
      * This class holds the loading results according to the given loading flag.
      * @author Marcin Roguski (Kaelthas)

+ 26 - 6
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -47,6 +47,7 @@ import com.jme3.asset.BlenderKey;
 import com.jme3.material.Material;
 import com.jme3.math.ColorRGBA;
 import com.jme3.scene.Node;
+import com.jme3.scene.plugins.blender.animations.BlenderAction;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.constraints.Constraint;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
@@ -76,7 +77,7 @@ public class BlenderContext {
     /** The asset manager. */
     private AssetManager                        assetManager;
     /** The blocks read from the file. */
-    protected List<FileBlockHeader> blocks;
+    protected List<FileBlockHeader>             blocks;
     /**
      * A map containing the file block headers. The key is the old memory address.
      */
@@ -114,6 +115,8 @@ public class BlenderContext {
     private Map<String, AbstractBlenderHelper>  helpers                = new HashMap<String, AbstractBlenderHelper>();
     /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */
     private Map<String, Map<Object, Object>>    markers                = new HashMap<String, Map<Object, Object>>();
+    /** A map of blender actions. The key is the action name and the value is the action itself. */
+    private Map<String, BlenderAction>          actions                = new HashMap<String, BlenderAction>();
 
     /**
      * This method sets the blender file version.
@@ -405,7 +408,7 @@ public class BlenderContext {
         }
         return result;
     }
-    
+
     /**
      * This method adds the animation for the specified OMA of its owner.
      * 
@@ -416,13 +419,13 @@ public class BlenderContext {
      */
     public void addAnimation(Long ownerOMA, Animation animation) {
         List<Animation> animList = animations.get(ownerOMA);
-        if(animList == null) {
+        if (animList == null) {
             animList = new ArrayList<Animation>();
             animations.put(ownerOMA, animList);
         }
         animList.add(animation);
     }
-    
+
     /**
      * This method returns the animation data for the specified owner.
      * 
@@ -534,14 +537,15 @@ public class BlenderContext {
     /**
      * Returns bone by given name.
      * 
-     * @param skeletonOMA the OMA of the skeleton where the bone will be searched
+     * @param skeletonOMA
+     *            the OMA of the skeleton where the bone will be searched
      * @param name
      *            the name of the bone
      * @return found bone or null if none bone of a given name exists
      */
     public BoneContext getBoneByName(Long skeletonOMA, String name) {
         for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
-            if(entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
+            if (entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) {
                 Bone bone = entry.getValue().getBone();
                 if (bone != null && name.equals(bone.getName())) {
                     return entry.getValue();
@@ -619,6 +623,22 @@ public class BlenderContext {
         return markersMap == null ? null : markersMap.get(feature);
     }
 
+    /**
+     * Adds blender action to the context.
+     * @param action
+     *            the action loaded from the blend file
+     */
+    public void addAction(BlenderAction action) {
+        actions.put(action.getName(), action);
+    }
+
+    /**
+     * @return a map of blender actions; the key is the action name and the value is action itself
+     */
+    public Map<String, BlenderAction> getActions() {
+        return actions;
+    }
+
     /**
      * This enum defines what loaded data type user wants to retreive. It can be
      * either filled structure or already converted data.

+ 90 - 127
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java

@@ -2,9 +2,10 @@ package com.jme3.scene.plugins.blender.animations;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -14,11 +15,11 @@ import com.jme3.animation.BoneTrack;
 import com.jme3.animation.Skeleton;
 import com.jme3.animation.SkeletonControl;
 import com.jme3.animation.SpatialTrack;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Vector3f;
+import com.jme3.asset.BlenderKey.AnimationMatchMethod;
 import com.jme3.scene.Node;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo;
 import com.jme3.scene.plugins.blender.curves.BezierCurve;
 import com.jme3.scene.plugins.blender.file.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
@@ -32,10 +33,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
  * @author Marcin Roguski (Kaelthas)
  */
 public class AnimationHelper extends AbstractBlenderHelper {
-    private static final Logger        LOGGER  = Logger.getLogger(AnimationHelper.class.getName());
-
-    /** A map of blender actions. */
-    private Map<String, BlenderAction> actions = new HashMap<String, BlenderAction>();
+    private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName());
 
     public AnimationHelper(String blenderVersion, BlenderContext blenderContext) {
         super(blenderVersion, blenderContext);
@@ -54,7 +52,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
             for (FileBlockHeader header : actionHeaders) {
                 Structure actionStructure = header.getStructure(blenderContext);
                 LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName());
-                actions.put(actionStructure.getName(), this.getTracks(actionStructure, blenderContext));
+                blenderContext.addAction(this.getTracks(actionStructure, blenderContext));
             }
         }
     }
@@ -63,24 +61,20 @@ public class AnimationHelper extends AbstractBlenderHelper {
      * The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file.
      * @param node
      *            the node to whom the animations will be applied
-     * @param animationNames
-     *            the names of the animations to be applied
+     * @param animationMatchMethod
+     *            the way animation should be matched with node
      */
-    public void applyAnimations(Node node, List<String> animationNames) {
-        if (animationNames != null && animationNames.size() > 0) {
+    public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) {
+        List<BlenderAction> actions = this.getActions(node, animationMatchMethod);
+        if (actions.size() > 0) {
             List<Animation> animations = new ArrayList<Animation>();
-            for (String animationName : animationNames) {
-                BlenderAction action = actions.get(animationName);
-                if (action != null) {
-                    SpatialTrack[] tracks = action.toTracks(node);
-                    if (tracks != null && tracks.length > 0) {
-                        Animation spatialAnimation = new Animation(animationName, action.getAnimationTime());
-                        spatialAnimation.setTracks(tracks);
-                        animations.add(spatialAnimation);
-                        blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
-                    }
-                } else {
-                    LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName);
+            for (BlenderAction action : actions) {
+                SpatialTrack[] tracks = action.toTracks(node);
+                if (tracks != null && tracks.length > 0) {
+                    Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime());
+                    spatialAnimation.setTracks(tracks);
+                    animations.add(spatialAnimation);
+                    blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation);
                 }
             }
 
@@ -103,28 +97,24 @@ public class AnimationHelper extends AbstractBlenderHelper {
      *            the node where the animations will be applied
      * @param skeleton
      *            the skeleton of the node
-     * @param animationNames
-     *            the names of the skeleton animations
+     * @param animationMatchMethod
+     *            the way animation should be matched with skeleton
      */
-    public void applyAnimations(Node node, Skeleton skeleton, List<String> animationNames) {
+    public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
         node.addControl(new SkeletonControl(skeleton));
         blenderContext.setNodeForSkeleton(skeleton, node);
+        List<BlenderAction> actions = this.getActions(skeleton, animationMatchMethod);
 
-        if (animationNames != null && animationNames.size() > 0) {
+        if (actions.size() > 0) {
             List<Animation> animations = new ArrayList<Animation>();
-            for (String animationName : animationNames) {
-                BlenderAction action = actions.get(animationName);
-                if (action != null) {
-                    BoneTrack[] tracks = action.toTracks(skeleton);
-                    if (tracks != null && tracks.length > 0) {
-                        Animation boneAnimation = new Animation(animationName, action.getAnimationTime());
-                        boneAnimation.setTracks(tracks);
-                        animations.add(boneAnimation);
-                        Long animatedNodeOMA = ((Number)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
-                        blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
-                    }
-                } else {
-                    LOGGER.log(Level.WARNING, "Cannot find animation named: {0}.", animationName);
+            for (BlenderAction action : actions) {
+                BoneTrack[] tracks = action.toTracks(skeleton);
+                if (tracks != null && tracks.length > 0) {
+                    Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime());
+                    boneAnimation.setTracks(tracks);
+                    animations.add(boneAnimation);
+                    Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
+                    blenderContext.addAnimation(animatedNodeOMA, boneAnimation);
                 }
             }
             if (animations.size() > 0) {
@@ -136,10 +126,10 @@ public class AnimationHelper extends AbstractBlenderHelper {
                 }
                 control.setAnimations(anims);
                 node.addControl(control);
-                
-                //make sure that SkeletonControl is added AFTER the AnimControl
+
+                // make sure that SkeletonControl is added AFTER the AnimControl
                 SkeletonControl skeletonControl = node.getControl(SkeletonControl.class);
-                if(skeletonControl != null) {
+                if (skeletonControl != null) {
                     node.removeControl(SkeletonControl.class);
                     node.addControl(skeletonControl);
                 }
@@ -230,7 +220,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
         LOGGER.log(Level.FINE, "Getting tracks!");
         Structure groups = (Structure) actionStructure.getFieldValue("groups");
         List<Structure> actionGroups = groups.evaluateListBase();// bActionGroup
-        BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps());
+        BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
         int lastFrame = 1;
         for (Structure actionGroup : actionGroups) {
             String name = actionGroup.getFieldValue("name").toString();
@@ -269,7 +259,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
         LOGGER.log(Level.FINE, "Getting tracks!");
         Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase");
         List<Structure> actionChannels = chanbase.evaluateListBase();// bActionChannel
-        BlenderAction blenderAction = new BlenderAction(blenderContext.getBlenderKey().getFps());
+        BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps());
         int lastFrame = 1;
         for (Structure bActionChannel : actionChannels) {
             String animatedFeatureName = bActionChannel.getFieldValue("name").toString();
@@ -277,7 +267,7 @@ public class AnimationHelper extends AbstractBlenderHelper {
             if (!p.isNull()) {
                 Structure ipoStructure = p.fetchData().get(0);
                 Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext);
-                if(ipo != null) {//this can happen when ipo with no curves appear in blender file
+                if (ipo != null) {// this can happen when ipo with no curves appear in blender file
                     lastFrame = Math.max(lastFrame, ipo.getLastFrame());
                     blenderAction.featuresTracks.put(animatedFeatureName, ipo);
                 }
@@ -321,104 +311,77 @@ public class AnimationHelper extends AbstractBlenderHelper {
         if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) {
             return Ipo.OB_ROT_X + arrayIndex;
         }
-        LOGGER.warning("Unknown curve rna path: " + rnaPath);
+        LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", rnaPath);
         return -1;
     }
 
     /**
-     * An abstract representation of animation. The data stored here is mainly a raw action data loaded from blender.
-     * It can later be transformed into bone or spatial animation and applied to the specified node.
-     * 
-     * @author Marcin Roguski (Kaelthas)
+     * The method returns the actions for the given skeleton. The actions represent armature animation in blender.
+     * @param skeleton
+     *            the skeleton we fetch the actions for
+     * @param animationMatchMethod
+     *            the method of animation matching
+     * @return a list of animations for the specified skeleton
      */
-    private static class BlenderAction {
-        /** Animation speed - frames per second. */
-        private int              fps;
-        /** The last frame of the animation (the last ipo curve node position is used as a last frame). */
-        private int              stopFrame;
-        /**
-         * Tracks of the features. In case of bone animation the keys are the names of the bones. In case of spatial animation - the node's name
-         * is used. A single ipo contains all tracks for location, rotation and scales.
-         */
-        private Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
+    private List<BlenderAction> getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) {
+        List<BlenderAction> result = new ArrayList<BlenderAction>();
 
-        public BlenderAction(int fps) {
-            this.fps = fps;
+        // first get a set of bone names
+        Set<String> boneNames = new HashSet<String>();
+        for (int i = 0; i < skeleton.getBoneCount(); ++i) {
+            String boneName = skeleton.getBone(i).getName();
+            if (boneName != null && boneName.length() > 0) {
+                boneNames.add(skeleton.getBone(i).getName());
+            }
         }
 
-        /**
-         * Converts the action into JME spatial animation tracks.
-         * @param node
-         *            the node that will be animated
-         * @return the spatial tracks for the node
-         */
-        public SpatialTrack[] toTracks(Node node) {
-            List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
-            for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
-                tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
+        // finding matches
+        Set<String> matchingNames = new HashSet<String>();
+        for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
+            // compute how many action tracks match the skeleton bones' names
+            for (String boneName : boneNames) {
+                if (actionEntry.getValue().hasTrackName(boneName)) {
+                    matchingNames.add(boneName);
+                }
+            }
+
+            BlenderAction action = null;
+            if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) {
+                action = actionEntry.getValue();
+            } else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) {
+                action = actionEntry.getValue();
             }
-            return tracks.toArray(new SpatialTrack[tracks.size()]);
-        }
 
-        /**
-         * Converts the action into JME bone animation tracks.
-         * @param skeleton
-         *            the skeleton that will be animated
-         * @return the bone tracks for the node
-         */
-        public BoneTrack[] toTracks(Skeleton skeleton) {
-            List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
-            for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
-                int boneIndex = skeleton.getBoneIndex(entry.getKey());
-                tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
+            if (action != null) {
+                // remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH
+                if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) {
+                    action = action.clone();
+                    action.removeTracksThatAreNotInTheCollection(matchingNames);
+                }
+                result.add(action);
             }
-            return tracks.toArray(new BoneTrack[tracks.size()]);
-        }
 
-        /**
-         * @return the time of animations (in seconds)
-         */
-        public float getAnimationTime() {
-            return (stopFrame - 1) / (float) fps;
+            matchingNames.clear();
         }
+        return result;
     }
 
     /**
-     * Ipo constant curve. This is a curve with only one value and no specified
-     * type. This type of ipo cannot be used to calculate tracks. It should only
-     * be used to calculate single value for a given frame.
-     * 
-     * @author Marcin Roguski (Kaelthas)
+     * The method returns the actions for the given node. The actions represent object animation in blender.
+     * @param node
+     *            the node we fetch the actions for
+     * @param animationMatchMethod
+     *            the method of animation matching
+     * @return a list of animations for the specified node
      */
-    private class ConstIpo extends Ipo {
-
-        /** The constant value of this ipo. */
-        private float constValue;
+    private List<BlenderAction> getActions(Node node, AnimationMatchMethod animationMatchMethod) {
+        List<BlenderAction> result = new ArrayList<BlenderAction>();
 
-        /**
-         * Constructor. Stores the constant value of this ipo.
-         * 
-         * @param constValue
-         *            the constant value of this ipo
-         */
-        public ConstIpo(float constValue) {
-            super(null, false, 0);// the version is not important here
-            this.constValue = constValue;
-        }
-
-        @Override
-        public float calculateValue(int frame) {
-            return constValue;
-        }
-
-        @Override
-        public float calculateValue(int frame, int curveIndex) {
-            return constValue;
-        }
-
-        @Override
-        public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
-            throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
+        for (Entry<String, BlenderAction> actionEntry : blenderContext.getActions().entrySet()) {
+            if (actionEntry.getValue().hasTrackName(node.getName())) {
+                result.add(actionEntry.getValue());
+            }
         }
+        return result;
     }
 }

+ 134 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java

@@ -0,0 +1,134 @@
+package com.jme3.scene.plugins.blender.animations;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SpatialTrack;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+
+/**
+ * An abstract representation of animation. The data stored here is mainly a
+ * raw action data loaded from blender. It can later be transformed into
+ * bone or spatial animation and applied to the specified node.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class BlenderAction implements Cloneable {
+    /** The action name. */
+    /* package */final String     name;
+    /** Animation speed - frames per second. */
+    /* package */int              fps;
+    /**
+     * The last frame of the animation (the last ipo curve node position is
+     * used as a last frame).
+     */
+    /* package */int              stopFrame;
+    /**
+     * Tracks of the features. In case of bone animation the keys are the
+     * names of the bones. In case of spatial animation - the node's name is
+     * used. A single ipo contains all tracks for location, rotation and
+     * scales.
+     */
+    /* package */Map<String, Ipo> featuresTracks = new HashMap<String, Ipo>();
+
+    public BlenderAction(String name, int fps) {
+        this.name = name;
+        this.fps = fps;
+    }
+
+    public void removeTracksThatAreNotInTheCollection(Collection<String> trackNames) {
+        Map<String, Ipo> newTracks = new HashMap<String, Ipo>();
+        for (String trackName : trackNames) {
+            if (featuresTracks.containsKey(trackName)) {
+                newTracks.put(trackName, featuresTracks.get(trackName));
+            }
+        }
+        featuresTracks = newTracks;
+    }
+
+    @Override
+    public BlenderAction clone() {
+        BlenderAction result = new BlenderAction(name, fps);
+        result.stopFrame = stopFrame;
+        result.featuresTracks = new HashMap<String, Ipo>(featuresTracks);
+        return result;
+    }
+
+    /**
+     * Converts the action into JME spatial animation tracks.
+     * 
+     * @param node
+     *            the node that will be animated
+     * @return the spatial tracks for the node
+     */
+    public SpatialTrack[] toTracks(Node node) {
+        List<SpatialTrack> tracks = new ArrayList<SpatialTrack>(featuresTracks.size());
+        for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
+            tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true));
+        }
+        return tracks.toArray(new SpatialTrack[tracks.size()]);
+    }
+
+    /**
+     * Converts the action into JME bone animation tracks.
+     * 
+     * @param skeleton
+     *            the skeleton that will be animated
+     * @return the bone tracks for the node
+     */
+    public BoneTrack[] toTracks(Skeleton skeleton) {
+        List<BoneTrack> tracks = new ArrayList<BoneTrack>(featuresTracks.size());
+        for (Entry<String, Ipo> entry : featuresTracks.entrySet()) {
+            int boneIndex = skeleton.getBoneIndex(entry.getKey());
+            tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ, 1, stopFrame, fps, false));
+        }
+        return tracks.toArray(new BoneTrack[tracks.size()]);
+    }
+
+    /**
+     * @return the name of the action
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @return the time of animations (in seconds)
+     */
+    public float getAnimationTime() {
+        return (stopFrame - 1) / (float) fps;
+    }
+
+    /**
+     * Determines if the current action has a track of a given name.
+     * CAUTION! The names are case sensitive.
+     * 
+     * @param name
+     *            the name of the track
+     * @return <B>true</b> if the track of a given name exists for the
+     *         action and <b>false</b> otherwise
+     */
+    public boolean hasTrackName(String name) {
+        return featuresTracks.containsKey(name);
+    }
+
+    /**
+     * @return the amount of tracks in current action
+     */
+    public int getTracksCount() {
+        return featuresTracks.size();
+    }
+
+    @Override
+    public String toString() {
+        return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]";
+    }
+}

+ 54 - 15
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java

@@ -170,7 +170,7 @@ public class Ipo {
                 for (int j = 0; j < bezierCurves.length; ++j) {
                     double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
                     switch (bezierCurves[j].getType()) {
-                    	// LOCATION
+                    // LOCATION
                         case AC_LOC_X:
                             translation[0] = (float) value;
                             break;
@@ -186,18 +186,18 @@ public class Ipo {
 
                         // EULER ROTATION
                         case OB_ROT_X:
-                        	eulerRotationUsed = true;
+                            eulerRotationUsed = true;
                             eulerRotation[0] = (float) value * degreeToRadiansFactor;
                             break;
                         case OB_ROT_Y:
-                        	eulerRotationUsed = true;
+                            eulerRotationUsed = true;
                             if (swapAxes && value != 0) {
                                 value = -value;
                             }
                             eulerRotation[yIndex] = (float) value * degreeToRadiansFactor;
                             break;
                         case OB_ROT_Z:
-                        	eulerRotationUsed = true;
+                            eulerRotationUsed = true;
                             eulerRotation[zIndex] = (float) value * degreeToRadiansFactor;
                             break;
 
@@ -214,15 +214,15 @@ public class Ipo {
 
                         // QUATERNION ROTATION (used with bone animation)
                         case AC_QUAT_W:
-                        	queternionRotationUsed = true;
+                            queternionRotationUsed = true;
                             quaternionRotation[3] = (float) value;
                             break;
                         case AC_QUAT_X:
-                        	queternionRotationUsed = true;
+                            queternionRotationUsed = true;
                             quaternionRotation[0] = (float) value;
                             break;
                         case AC_QUAT_Y:
-                        	queternionRotationUsed = true;
+                            queternionRotationUsed = true;
                             if (swapAxes && value != 0) {
                                 value = -value;
                             }
@@ -236,12 +236,12 @@ public class Ipo {
                     }
                 }
                 translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));
-                if(queternionRotationUsed) {
-                	rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
+                if (queternionRotationUsed) {
+                    rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]);
                 } else {
-                	rotations[index] = new Quaternion().fromAngles(eulerRotation);
+                    rotations[index] = new Quaternion().fromAngles(eulerRotation);
                 }
-                
+
                 scales[index] = new Vector3f(scale[0], scale[1], scale[2]);
             }
             if (spatialTrack) {
@@ -249,12 +249,51 @@ public class Ipo {
             } else {
                 calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales);
             }
-            
-            if(queternionRotationUsed && eulerRotationUsed) {
-            	LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
+
+            if (queternionRotationUsed && eulerRotationUsed) {
+                LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!");
             }
         }
-        
+
         return calculatedTrack;
     }
+
+    /**
+     * Ipo constant curve. This is a curve with only one value and no specified
+     * type. This type of ipo cannot be used to calculate tracks. It should only
+     * be used to calculate single value for a given frame.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    /* package */static class ConstIpo extends Ipo {
+
+        /** The constant value of this ipo. */
+        private float constValue;
+
+        /**
+         * Constructor. Stores the constant value of this ipo.
+         * 
+         * @param constValue
+         *            the constant value of this ipo
+         */
+        public ConstIpo(float constValue) {
+            super(null, false, 0);// the version is not important here
+            this.constValue = constValue;
+        }
+
+        @Override
+        public float calculateValue(int frame) {
+            return constValue;
+        }
+
+        @Override
+        public float calculateValue(int frame, int curveIndex) {
+            return constValue;
+        }
+
+        @Override
+        public BoneTrack calculateTrack(int boneIndex, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) {
+            throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!");
+        }
+    }
 }

+ 396 - 396
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java

@@ -1,396 +1,396 @@
-package com.jme3.scene.plugins.blender.modifiers;
-
-import java.nio.Buffer;
-import java.nio.ByteBuffer;
-import java.nio.FloatBuffer;
-import java.nio.IntBuffer;
-import java.nio.ShortBuffer;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import com.jme3.animation.Bone;
-import com.jme3.animation.Skeleton;
-import com.jme3.math.Matrix4f;
-import com.jme3.math.Vector3f;
-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.Format;
-import com.jme3.scene.VertexBuffer.Type;
-import com.jme3.scene.VertexBuffer.Usage;
-import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
-import com.jme3.scene.plugins.blender.animations.AnimationHelper;
-import com.jme3.scene.plugins.blender.animations.BoneContext;
-import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
-import com.jme3.scene.plugins.blender.file.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.Pointer;
-import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.blender.meshes.MeshContext;
-import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
-import com.jme3.util.BufferUtils;
-
-/**
- * This modifier allows to add bone animation to the object.
- * 
- * @author Marcin Roguski (Kaelthas)
- */
-/* package */class ArmatureModifier extends Modifier {
-    private static final Logger LOGGER                     = Logger.getLogger(ArmatureModifier.class.getName());
-    private static final int    MAXIMUM_WEIGHTS_PER_VERTEX = 4;                                                 // JME
-
-    private static final int    FLAG_VERTEX_GROUPS         = 0x01;
-    private static final int    FLAG_BONE_ENVELOPES        = 0x02;
-
-    private Structure           armatureObject;
-    private Skeleton            skeleton;
-    private Structure           meshStructure;
-    /** The wold transform matrix of the armature object. */
-    private Matrix4f            objectWorldMatrix;
-    /** Old memory address of the mesh that will have the skeleton applied. */
-    private Long                meshOMA;
-    /** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
-    private boolean             useVertexGroups;
-    /** The variable tells if the bones' envelopes should be used to assign verts to bones. */
-    private boolean             useBoneEnvelopes;
-
-    /**
-     * This constructor reads animation data from the object structore. The
-     * stored data is the AnimData and additional data is armature's OMA.
-     * 
-     * @param objectStructure
-     *            the structure of the object
-     * @param modifierStructure
-     *            the structure of the modifier
-     * @param blenderContext
-     *            the blender context
-     * @throws BlenderFileException
-     *             this exception is thrown when the blender file is somehow
-     *             corrupted
-     */
-    public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
-        Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
-        if (this.validate(modifierStructure, blenderContext)) {
-            Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
-            if (pArmatureObject.isNotNull()) {
-                int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
-                useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
-                useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
-                modifying = useBoneEnvelopes || useVertexGroups;
-                if (modifying) {// if neither option is used the modifier will not modify anything anyway
-                    armatureObject = pArmatureObject.fetchData().get(0);
-
-                    // load skeleton
-                    Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
-                    List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
-                    List<Bone> bonesList = new ArrayList<Bone>();
-                    for (int i = 0; i < bonebase.size(); ++i) {
-                        this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
-                    }
-                    bonesList.add(0, new Bone(""));
-                    Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
-                    skeleton = new Skeleton(bones);
-                    blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
-                    this.meshStructure = meshStructure;
-
-                    // read mesh indexes
-                    meshOMA = meshStructure.getOldMemoryAddress();
-
-                    if (useBoneEnvelopes) {
-                        ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-                        Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
-                        objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
-                    }
-                }
-            } else {
-                modifying = false;
-            }
-        }
-    }
-
-    /**
-     * This method builds the object's bones structure.
-     * 
-     * @param armatureObjectOMA
-     *            the OMa of the armature node
-     * @param boneStructure
-     *            the structure containing the bones' data
-     * @param parent
-     *            the parent bone
-     * @param result
-     *            the list where the newly created bone will be added
-     * @param spatialOMA
-     *            the OMA of the spatial that will own the skeleton
-     * @param blenderContext
-     *            the blender context
-     * @throws BlenderFileException
-     *             an exception is thrown when there is problem with the blender
-     *             file
-     */
-    private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
-        BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
-        bc.buildBone(result, spatialOMA, blenderContext);
-    }
-
-    @Override
-    @SuppressWarnings("unchecked")
-    public void apply(Node node, BlenderContext blenderContext) {
-        if (invalid) {
-            LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
-        }// if invalid, animData will be null
-        if (skeleton != null) {
-            // setting weights for bones
-            List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
-            MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
-            for (Geometry geom : geomList) {
-                int materialIndex = meshContext.getMaterialIndex(geom);
-                Mesh mesh = geom.getMesh();
-
-                MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
-                if (buffers != null) {
-                    mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
-                    mesh.setBuffer(buffers.verticesWeights);
-                    mesh.setBuffer(buffers.verticesWeightsIndices);
-
-                    LOGGER.fine("Generating bind pose and normal buffers.");
-                    mesh.generateBindPose(true);
-
-                    // change the usage type of vertex and normal buffers from
-                    // Static to Stream
-                    mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
-                    mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
-
-                    // creating empty buffers for HW skinning
-                    // the buffers will be setup if ever used.
-                    VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
-                    VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
-                    mesh.setBuffer(verticesWeightsHW);
-                    mesh.setBuffer(verticesWeightsIndicesHW);
-                }
-            }
-
-            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getSkeletonAnimationNames(node.getName()));
-            node.updateModelBound();
-        }
-    }
-
-    /**
-     * Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
-     * 
-     * @param meshContext
-     *            the mesh context
-     * @param skeleton
-     *            the current skeleton
-     * @param materialIndex
-     *            the material index
-     * @param mesh
-     *            the mesh we create the buffers for
-     * @param blenderContext
-     *            the blender context
-     * @return an instance that aggregates all needed data for the mesh
-     */
-    private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
-        int vertexListSize = meshContext.getVertexCount(materialIndex);
-        Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
-
-        Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
-        Buffer indexes = mesh.getBuffer(Type.Index).getData();
-        FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
-
-        int maximumWeightsPerVertex = 0;
-        if (useVertexGroups) {
-            LOGGER.fine("Attaching verts to bones using vertex groups.");
-            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
-                Bone bone = skeleton.getBone(boneIndex);
-                VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
-                if (vertexGroup != null) {
-                    vertexGroup.setBoneIndex(boneIndex);
-                    vertexGroups.put(bone.getName(), vertexGroup);
-                }
-            }
-        }
-
-        if (useBoneEnvelopes) {
-            LOGGER.fine("Attaching verts to bones using bone envelopes.");
-            Vector3f pos = new Vector3f();
-
-            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
-                Bone bone = skeleton.getBone(boneIndex);
-                BoneContext boneContext = blenderContext.getBoneContext(bone);
-                BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
-                if (boneEnvelope != null) {
-                    VertexGroup vertexGroup = vertexGroups.get(bone.getName());
-                    if (vertexGroup == null) {
-                        vertexGroup = new VertexGroup();
-                        vertexGroups.put(bone.getName(), vertexGroup);
-                    }
-                    vertexGroup.setBoneIndex(boneIndex);
-
-                    for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
-                        List<Integer> vertexIndices = entry.getValue();
-                        for (int j = 0; j < indexes.limit(); ++j) {
-                            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
-                            if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
-                                int ii = index * 3;
-                                pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
-                                // move the vertex to the global space position
-                                objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
-                                if (boneEnvelope.isInEnvelope(pos)) {
-                                    vertexGroup.addVertex(index, boneEnvelope.getWeight());
-                                } else if (boneIndex == 5) {
-                                    System.out.println("Si nie zaapa: " + pos);
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-
-        Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
-        if (vertexGroups.size() > 0) {
-            LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
-            for (VertexGroup vertexGroup : vertexGroups.values()) {
-                for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
-                    WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
-                    if (vertexWeights == null) {
-                        vertexWeights = new WeightsAndBoneIndexes();
-                        weights.put(entry.getKey(), vertexWeights);
-                    }
-                    vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
-                }
-            }
-
-            LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
-            for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
-                maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
-                entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
-            }
-
-            if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
-                LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
-                maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
-            }
-        }
-
-        if(maximumWeightsPerVertex == 0) {
-            LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
-            return null;
-        }
-        
-        LOGGER.fine("Preparing buffers for the mesh.");
-        FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
-        ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
-        for (int i = 0; i < indexes.limit(); ++i) {
-            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
-            WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
-            if (weightsAndBoneIndexes != null) {
-                int count = 0;
-                for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
-                    weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
-                    indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
-                    ++count;
-                }
-            } else {
-                // if no bone is assigned to this vertex then attach it to the 0-indexed root bone
-                weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
-                indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
-            }
-        }
-        VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
-        verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
-
-        VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
-        verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
-
-        return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
-    }
-
-    /**
-     * A class that gathers the data for mesh bone buffers.
-     * Added to increase code readability.
-     * 
-     * @author Marcin Roguski (Kaelthas)
-     */
-    private static class MeshWeightsData {
-        public final int          maximumWeightsPerVertex;
-        public final VertexBuffer verticesWeights;
-        public final VertexBuffer verticesWeightsIndices;
-
-        public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
-            this.maximumWeightsPerVertex = maximumWeightsPerVertex;
-            this.verticesWeights = verticesWeights;
-            this.verticesWeightsIndices = verticesWeightsIndices;
-        }
-    }
-
-    /**
-     * A map between the bone index and the bone's weight.
-     * 
-     * @author Marcin Roguski (Kaelthas)
-     */
-    private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
-        private static final long serialVersionUID = 2754299007299077459L;
-
-        /**
-         * The method normalizes the weights and bone indexes data.
-         * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
-         * Next it normalizes the weights so that the sum of all verts is 1.
-         * @param maximumSize
-         *            the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
-         */
-        public void normalize(int maximumSize) {
-            if (this.size() > maximumSize) {// select only the most significant weights
-                float lowestWeight = Float.MAX_VALUE;
-                int lowestWeightIndex = -1;
-                HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
-                for (Entry<Integer, Float> entry : this.entrySet()) {
-                    if (msw.size() < maximumSize) {
-                        msw.put(entry.getKey(), entry.getValue());
-                        if (entry.getValue() < lowestWeight) {
-                            lowestWeight = entry.getValue();
-                            lowestWeightIndex = entry.getKey();
-                        }
-                    } else if (entry.getValue() > lowestWeight) {
-                        msw.remove(lowestWeightIndex);
-                        msw.put(lowestWeightIndex, lowestWeight);
-
-                        // search again for the lowest weight
-                        lowestWeight = Float.MAX_VALUE;
-                        for (Entry<Integer, Float> e : msw.entrySet()) {
-                            if (e.getValue() < lowestWeight) {
-                                lowestWeight = e.getValue();
-                                lowestWeightIndex = e.getKey();
-                            }
-                        }
-                    }
-                }
-
-                // replace current weights with the given ones
-                this.clear();
-                this.putAll(msw);
-            }
-
-            // normalizing the weights so that the sum of the values is equal to '1'
-            float sum = 0;
-            for (Entry<Integer, Float> entry : this.entrySet()) {
-                sum += entry.getValue();
-            }
-
-            if (sum != 0 && sum != 1) {
-                for (Entry<Integer, Float> entry : this.entrySet()) {
-                    entry.setValue(entry.getValue() / sum);
-                }
-            }
-        }
-    }
-}
+package com.jme3.scene.plugins.blender.modifiers;
+
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.ShortBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
+import com.jme3.math.Matrix4f;
+import com.jme3.math.Vector3f;
+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.Format;
+import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.scene.VertexBuffer.Usage;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.animations.AnimationHelper;
+import com.jme3.scene.plugins.blender.animations.BoneContext;
+import com.jme3.scene.plugins.blender.animations.BoneEnvelope;
+import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
+import com.jme3.scene.plugins.blender.file.BlenderFileException;
+import com.jme3.scene.plugins.blender.file.Pointer;
+import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.meshes.MeshContext;
+import com.jme3.scene.plugins.blender.meshes.MeshContext.VertexGroup;
+import com.jme3.util.BufferUtils;
+
+/**
+ * This modifier allows to add bone animation to the object.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class ArmatureModifier extends Modifier {
+    private static final Logger LOGGER                     = Logger.getLogger(ArmatureModifier.class.getName());
+    private static final int    MAXIMUM_WEIGHTS_PER_VERTEX = 4;                                                 // JME
+
+    private static final int    FLAG_VERTEX_GROUPS         = 0x01;
+    private static final int    FLAG_BONE_ENVELOPES        = 0x02;
+
+    private Structure           armatureObject;
+    private Skeleton            skeleton;
+    private Structure           meshStructure;
+    /** The wold transform matrix of the armature object. */
+    private Matrix4f            objectWorldMatrix;
+    /** Old memory address of the mesh that will have the skeleton applied. */
+    private Long                meshOMA;
+    /** The variable tells if the vertex groups of the mesh should be used to assign verts to bones. */
+    private boolean             useVertexGroups;
+    /** The variable tells if the bones' envelopes should be used to assign verts to bones. */
+    private boolean             useBoneEnvelopes;
+
+    /**
+     * This constructor reads animation data from the object structore. The
+     * stored data is the AnimData and additional data is armature's OMA.
+     * 
+     * @param objectStructure
+     *            the structure of the object
+     * @param modifierStructure
+     *            the structure of the modifier
+     * @param blenderContext
+     *            the blender context
+     * @throws BlenderFileException
+     *             this exception is thrown when the blender file is somehow
+     *             corrupted
+     */
+    public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
+        Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData().get(0);
+        if (this.validate(modifierStructure, blenderContext)) {
+            Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object");
+            if (pArmatureObject.isNotNull()) {
+                int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue();
+                useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0;
+                useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0;
+                modifying = useBoneEnvelopes || useVertexGroups;
+                if (modifying) {// if neither option is used the modifier will not modify anything anyway
+                    armatureObject = pArmatureObject.fetchData().get(0);
+
+                    // load skeleton
+                    Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0);
+                    List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase();
+                    List<Bone> bonesList = new ArrayList<Bone>();
+                    for (int i = 0; i < bonebase.size(); ++i) {
+                        this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
+                    }
+                    bonesList.add(0, new Bone(""));
+                    Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
+                    skeleton = new Skeleton(bones);
+                    blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton);
+                    this.meshStructure = meshStructure;
+
+                    // read mesh indexes
+                    meshOMA = meshStructure.getOldMemoryAddress();
+
+                    if (useBoneEnvelopes) {
+                        ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
+                        Spatial object = (Spatial) blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
+                        objectWorldMatrix = constraintHelper.toMatrix(object.getWorldTransform(), new Matrix4f());
+                    }
+                }
+            } else {
+                modifying = false;
+            }
+        }
+    }
+
+    /**
+     * This method builds the object's bones structure.
+     * 
+     * @param armatureObjectOMA
+     *            the OMa of the armature node
+     * @param boneStructure
+     *            the structure containing the bones' data
+     * @param parent
+     *            the parent bone
+     * @param result
+     *            the list where the newly created bone will be added
+     * @param spatialOMA
+     *            the OMA of the spatial that will own the skeleton
+     * @param blenderContext
+     *            the blender context
+     * @throws BlenderFileException
+     *             an exception is thrown when there is problem with the blender
+     *             file
+     */
+    private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
+        BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
+        bc.buildBone(result, spatialOMA, blenderContext);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void apply(Node node, BlenderContext blenderContext) {
+        if (invalid) {
+            LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName());
+        }// if invalid, animData will be null
+        if (skeleton != null) {
+            // setting weights for bones
+            List<Geometry> geomList = (List<Geometry>) blenderContext.getLoadedFeature(meshOMA, LoadedFeatureDataType.LOADED_FEATURE);
+            MeshContext meshContext = blenderContext.getMeshContext(meshOMA);
+            for (Geometry geom : geomList) {
+                int materialIndex = meshContext.getMaterialIndex(geom);
+                Mesh mesh = geom.getMesh();
+
+                MeshWeightsData buffers = this.readVerticesWeightsData(meshContext, skeleton, materialIndex, mesh, blenderContext);
+                if (buffers != null) {
+                    mesh.setMaxNumWeights(buffers.maximumWeightsPerVertex);
+                    mesh.setBuffer(buffers.verticesWeights);
+                    mesh.setBuffer(buffers.verticesWeightsIndices);
+
+                    LOGGER.fine("Generating bind pose and normal buffers.");
+                    mesh.generateBindPose(true);
+
+                    // change the usage type of vertex and normal buffers from
+                    // Static to Stream
+                    mesh.getBuffer(Type.Position).setUsage(Usage.Stream);
+                    mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
+
+                    // creating empty buffers for HW skinning
+                    // the buffers will be setup if ever used.
+                    VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight);
+                    VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex);
+                    mesh.setBuffer(verticesWeightsHW);
+                    mesh.setBuffer(verticesWeightsIndicesHW);
+                }
+            }
+
+            AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
+            animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod());
+            node.updateModelBound();
+        }
+    }
+
+    /**
+     * Reads the vertices data and prepares appropriate buffers to be added to the mesh. There is a bone index buffer and weitghts buffer.
+     * 
+     * @param meshContext
+     *            the mesh context
+     * @param skeleton
+     *            the current skeleton
+     * @param materialIndex
+     *            the material index
+     * @param mesh
+     *            the mesh we create the buffers for
+     * @param blenderContext
+     *            the blender context
+     * @return an instance that aggregates all needed data for the mesh
+     */
+    private MeshWeightsData readVerticesWeightsData(MeshContext meshContext, Skeleton skeleton, int materialIndex, Mesh mesh, BlenderContext blenderContext) {
+        int vertexListSize = meshContext.getVertexCount(materialIndex);
+        Map<Integer, List<Integer>> vertexReferenceMap = meshContext.getVertexReferenceMap(materialIndex);
+
+        Map<String, VertexGroup> vertexGroups = new HashMap<String, VertexGroup>();
+        Buffer indexes = mesh.getBuffer(Type.Index).getData();
+        FloatBuffer positions = mesh.getFloatBuffer(Type.Position);
+
+        int maximumWeightsPerVertex = 0;
+        if (useVertexGroups) {
+            LOGGER.fine("Attaching verts to bones using vertex groups.");
+            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
+                Bone bone = skeleton.getBone(boneIndex);
+                VertexGroup vertexGroup = meshContext.getGroup(bone.getName());
+                if (vertexGroup != null) {
+                    vertexGroup.setBoneIndex(boneIndex);
+                    vertexGroups.put(bone.getName(), vertexGroup);
+                }
+            }
+        }
+
+        if (useBoneEnvelopes) {
+            LOGGER.fine("Attaching verts to bones using bone envelopes.");
+            Vector3f pos = new Vector3f();
+
+            for (int boneIndex = 1; boneIndex < skeleton.getBoneCount(); ++boneIndex) {// bone with index 0 is a root bone
+                Bone bone = skeleton.getBone(boneIndex);
+                BoneContext boneContext = blenderContext.getBoneContext(bone);
+                BoneEnvelope boneEnvelope = boneContext.getBoneEnvelope();
+                if (boneEnvelope != null) {
+                    VertexGroup vertexGroup = vertexGroups.get(bone.getName());
+                    if (vertexGroup == null) {
+                        vertexGroup = new VertexGroup();
+                        vertexGroups.put(bone.getName(), vertexGroup);
+                    }
+                    vertexGroup.setBoneIndex(boneIndex);
+
+                    for (Entry<Integer, List<Integer>> entry : vertexReferenceMap.entrySet()) {
+                        List<Integer> vertexIndices = entry.getValue();
+                        for (int j = 0; j < indexes.limit(); ++j) {
+                            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(j) : ((IntBuffer) indexes).get(j);
+                            if (vertexIndices.contains(index)) {// current geometry has the index assigned to the current mesh
+                                int ii = index * 3;
+                                pos.set(positions.get(ii), positions.get(ii + 1), positions.get(ii + 2));
+                                // move the vertex to the global space position
+                                objectWorldMatrix.mult(pos, pos);// TODO: optimize: check every vertex once and apply its references
+                                if (boneEnvelope.isInEnvelope(pos)) {
+                                    vertexGroup.addVertex(index, boneEnvelope.getWeight());
+                                } else if (boneIndex == 5) {
+                                    System.out.println("Si nie zaapa: " + pos);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        Map<Integer, WeightsAndBoneIndexes> weights = new HashMap<Integer, WeightsAndBoneIndexes>();// [vertex_index; [bone_index; weight]]
+        if (vertexGroups.size() > 0) {
+            LOGGER.fine("Gathering vertex groups information to prepare the buffers for the mesh.");
+            for (VertexGroup vertexGroup : vertexGroups.values()) {
+                for (Entry<Integer, Float> entry : vertexGroup.entrySet()) {
+                    WeightsAndBoneIndexes vertexWeights = weights.get(entry.getKey());
+                    if (vertexWeights == null) {
+                        vertexWeights = new WeightsAndBoneIndexes();
+                        weights.put(entry.getKey(), vertexWeights);
+                    }
+                    vertexWeights.put(vertexGroup.getBoneIndex(), entry.getValue());
+                }
+            }
+
+            LOGGER.log(Level.FINE, "Equalizing the amount of weights per vertex to {0} if any of them has more or less.", MAXIMUM_WEIGHTS_PER_VERTEX);
+            for (Entry<Integer, WeightsAndBoneIndexes> entry : weights.entrySet()) {
+                maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, entry.getValue().size());
+                entry.getValue().normalize(MAXIMUM_WEIGHTS_PER_VERTEX);
+            }
+
+            if (maximumWeightsPerVertex > MAXIMUM_WEIGHTS_PER_VERTEX) {
+                LOGGER.log(Level.WARNING, "{0} has vertices with more than 4 weights assigned. The model may not behave as it should.", meshStructure.getName());
+                maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX;// normalization already made at most 'MAXIMUM_WEIGHTS_PER_VERTEX' weights per vertex
+            }
+        }
+
+        if(maximumWeightsPerVertex == 0) {
+            LOGGER.fine("No vertex group data nor bone envelopes found to attach vertices to bones!");
+            return null;
+        }
+        
+        LOGGER.fine("Preparing buffers for the mesh.");
+        FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
+        ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
+        for (int i = 0; i < indexes.limit(); ++i) {
+            int index = indexes instanceof ShortBuffer ? ((ShortBuffer) indexes).get(i) : ((IntBuffer) indexes).get(i);
+            WeightsAndBoneIndexes weightsAndBoneIndexes = weights.get(index);
+            if (weightsAndBoneIndexes != null) {
+                int count = 0;
+                for (Entry<Integer, Float> entry : weightsAndBoneIndexes.entrySet()) {
+                    weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue());
+                    indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey().byteValue());
+                    ++count;
+                }
+            } else {
+                // if no bone is assigned to this vertex then attach it to the 0-indexed root bone
+                weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f);
+                indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
+            }
+        }
+        VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight);
+        verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData);
+
+        VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex);
+        verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData);
+
+        return new MeshWeightsData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices);
+    }
+
+    /**
+     * A class that gathers the data for mesh bone buffers.
+     * Added to increase code readability.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class MeshWeightsData {
+        public final int          maximumWeightsPerVertex;
+        public final VertexBuffer verticesWeights;
+        public final VertexBuffer verticesWeightsIndices;
+
+        public MeshWeightsData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) {
+            this.maximumWeightsPerVertex = maximumWeightsPerVertex;
+            this.verticesWeights = verticesWeights;
+            this.verticesWeightsIndices = verticesWeightsIndices;
+        }
+    }
+
+    /**
+     * A map between the bone index and the bone's weight.
+     * 
+     * @author Marcin Roguski (Kaelthas)
+     */
+    private static class WeightsAndBoneIndexes extends HashMap<Integer, Float> {
+        private static final long serialVersionUID = 2754299007299077459L;
+
+        /**
+         * The method normalizes the weights and bone indexes data.
+         * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle.
+         * Next it normalizes the weights so that the sum of all verts is 1.
+         * @param maximumSize
+         *            the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX)
+         */
+        public void normalize(int maximumSize) {
+            if (this.size() > maximumSize) {// select only the most significant weights
+                float lowestWeight = Float.MAX_VALUE;
+                int lowestWeightIndex = -1;
+                HashMap<Integer, Float> msw = new HashMap<Integer, Float>(maximumSize);// msw = Most Significant Weight
+                for (Entry<Integer, Float> entry : this.entrySet()) {
+                    if (msw.size() < maximumSize) {
+                        msw.put(entry.getKey(), entry.getValue());
+                        if (entry.getValue() < lowestWeight) {
+                            lowestWeight = entry.getValue();
+                            lowestWeightIndex = entry.getKey();
+                        }
+                    } else if (entry.getValue() > lowestWeight) {
+                        msw.remove(lowestWeightIndex);
+                        msw.put(lowestWeightIndex, lowestWeight);
+
+                        // search again for the lowest weight
+                        lowestWeight = Float.MAX_VALUE;
+                        for (Entry<Integer, Float> e : msw.entrySet()) {
+                            if (e.getValue() < lowestWeight) {
+                                lowestWeight = e.getValue();
+                                lowestWeightIndex = e.getKey();
+                            }
+                        }
+                    }
+                }
+
+                // replace current weights with the given ones
+                this.clear();
+                this.putAll(msw);
+            }
+
+            // normalizing the weights so that the sum of the values is equal to '1'
+            float sum = 0;
+            for (Entry<Integer, Float> entry : this.entrySet()) {
+                sum += entry.getValue();
+            }
+
+            if (sum != 0 && sum != 1) {
+                for (Entry<Integer, Float> entry : this.entrySet()) {
+                    entry.setValue(entry.getValue() / sum);
+                }
+            }
+        }
+    }
+}

+ 4 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -190,8 +190,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
                         LightHelper lightHelper = blenderContext.getHelper(LightHelper.class);
                         List<Structure> lampsArray = pLamp.fetchData();
                         result = lightHelper.toLight(lampsArray.get(0), blenderContext);
-                        if(result == null) {
-                            //probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
+                        if (result == null) {
+                            // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes
                             result = new Node(name);
                         }
                     }
@@ -218,7 +218,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
             if (objectType == ObjectType.ARMATURE) {
                 blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE);
             }
-            
+
             result.setLocalTransform(t);
             result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
             if (parent instanceof Node) {
@@ -245,7 +245,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
 
             LOGGER.fine("Applying animations to the object if such are defined.");
             AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class);
-            animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getNodeAnimationNames(name));
+            animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod());
 
             LOGGER.fine("Loading constraints connected with this object.");
             ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);