Prechádzať zdrojové kódy

Refactoring: large changes in constraints system (see the proper topic on the forum for further changes)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10581 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
Kae..pl 12 rokov pred
rodič
commit
29dd973122
23 zmenil súbory, kde vykonal 1234 pridanie a 1026 odobranie
  1. 44 71
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
  2. 20 13
      engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
  3. 11 15
      engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
  4. 15 114
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  5. 54 74
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java
  6. 228 236
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  7. 415 0
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  8. 5 8
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java
  9. 9 177
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  10. 146 0
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java
  11. 24 218
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  12. 14 2
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java
  13. 9 8
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java
  14. 22 5
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java
  15. 16 3
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java
  16. 8 2
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java
  17. 10 4
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java
  18. 35 6
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java
  19. 12 4
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java
  20. 10 3
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java
  21. 21 16
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java
  22. 60 17
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  23. 46 30
      engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

+ 44 - 71
engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java

@@ -46,8 +46,8 @@ import com.jme3.asset.AssetManager;
 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.BoneContext;
-import com.jme3.scene.plugins.blender.animations.Ipo;
 import com.jme3.scene.plugins.blender.constraints.Constraint;
 import com.jme3.scene.plugins.blender.file.BlenderInputStream;
 import com.jme3.scene.plugins.blender.file.DnaBlockData;
@@ -101,11 +101,6 @@ public class BlenderContext {
     private Map<String, Object[]>               loadedFeaturesByName   = new HashMap<String, Object[]>();
     /** A stack that hold the parent structure of currently loaded feature. */
     private Stack<Structure>                    parentStack            = new Stack<Structure>();
-    /**
-     * A map storing loaded ipos. The key is the ipo's owner old memory address
-     * and the value is the ipo.
-     */
-    private Map<Long, Ipo>                      loadedIpos             = new HashMap<Long, Ipo>();
     /** A list of modifiers for the specified object. */
     protected Map<Long, List<Modifier>>         modifiers              = new HashMap<Long, List<Modifier>>();
     /** A list of constraints for the specified object. */
@@ -114,6 +109,8 @@ public class BlenderContext {
     private Map<Long, AnimData>                 animData               = new HashMap<Long, AnimData>();
     /** Loaded skeletons. */
     private Map<Long, Skeleton>                 skeletons              = new HashMap<Long, Skeleton>();
+    /** A map between skeleton and node it modifies. */
+    private Map<Skeleton, Node>                 nodesWithSkeletons     = new HashMap<Skeleton, Node>();
     /** A map of mesh contexts. */
     protected Map<Long, MeshContext>            meshContexts           = new HashMap<Long, MeshContext>();
     /** A map of bone contexts. */
@@ -345,25 +342,6 @@ public class BlenderContext {
         return null;
     }
 
-    /**
-     * This method returns the feature of a given name. If the feature is not
-     * yet loaded then null is returned.
-     * 
-     * @param featureName
-     *            the name of the feature
-     * @param loadedFeatureDataType
-     *            the type of data we want to retreive it can be either filled
-     *            structure or already converted feature
-     * @return loaded feature or null if it was not yet loaded
-     */
-    public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) {
-        Object[] result = loadedFeaturesByName.get(featureName);
-        if (result != null) {
-            return result[loadedFeatureDataType.getIndex()];
-        }
-        return null;
-    }
-
     /**
      * This method clears the saved features stored in the features map.
      */
@@ -408,38 +386,6 @@ public class BlenderContext {
         }
     }
 
-    /**
-     * This method adds new ipo curve for the feature.
-     * 
-     * @param ownerOMA
-     *            the OMA of blender feature that owns the ipo
-     * @param ipo
-     *            the ipo to be added
-     */
-    public void addIpo(Long ownerOMA, Ipo ipo) {
-        loadedIpos.put(ownerOMA, ipo);
-    }
-
-    /**
-     * This method removes the ipo curve from the feature.
-     * 
-     * @param ownerOma
-     *            the OMA of blender feature that owns the ipo
-     */
-    public Ipo removeIpo(Long ownerOma) {
-        return loadedIpos.remove(ownerOma);
-    }
-
-    /**
-     * This method returns the ipo curve of the feature.
-     * 
-     * @param ownerOMA
-     *            the OMA of blender feature that owns the ipo
-     */
-    public Ipo getIpo(Long ownerOMA) {
-        return loadedIpos.get(ownerOMA);
-    }
-
     /**
      * This method adds a new modifier to the list.
      * 
@@ -499,18 +445,6 @@ public class BlenderContext {
         objectConstraints.addAll(constraints);
     }
 
-    /**
-     * This method returns constraints for the object specified by its old
-     * memory address. If no modifiers are found - <b>null</b> is returned.
-     * 
-     * @param objectOMA
-     *            object's old memory address
-     * @return the list of object's modifiers or null
-     */
-    public List<Constraint> getConstraints(Long objectOMA) {
-        return objectOMA == null ? null : constraints.get(objectOMA);
-    }
-
     /**
      * @return all available constraints
      */
@@ -557,6 +491,30 @@ public class BlenderContext {
         this.skeletons.put(skeletonOMA, skeleton);
     }
 
+    /**
+     * The method stores a binding between the skeleton and the proper armature
+     * node.
+     * 
+     * @param skeleton
+     *            the skeleton
+     * @param node
+     *            the armature node
+     */
+    public void setNodeForSkeleton(Skeleton skeleton, Node node) {
+        nodesWithSkeletons.put(skeleton, node);
+    }
+
+    /**
+     * This method returns the armature node that is defined for the skeleton.
+     * 
+     * @param skeleton
+     *            the skeleton
+     * @return the armature node that defines the skeleton in blender
+     */
+    public Node getControlledNode(Skeleton skeleton) {
+        return nodesWithSkeletons.get(skeleton);
+    }
+
     /**
      * This method returns the skeleton for the specified OMA of its owner.
      * 
@@ -635,6 +593,22 @@ public class BlenderContext {
         return null;
     }
 
+    /**
+     * Returns bone context for the given bone.
+     * 
+     * @param bone
+     *            the bone
+     * @return the bone's bone context
+     */
+    public BoneContext getBoneContext(Bone bone) {
+        for (Entry<Long, BoneContext> entry : boneContexts.entrySet()) {
+            if (entry.getValue().getBone().equals(bone)) {
+                return entry.getValue();
+            }
+        }
+        throw new IllegalStateException("Cannot find context for bone: " + bone);
+    }
+
     /**
      * This metod returns the default material.
      * 
@@ -658,7 +632,6 @@ public class BlenderContext {
         loadedFeatures.clear();
         loadedFeaturesByName.clear();
         parentStack.clear();
-        loadedIpos.clear();
         modifiers.clear();
         constraints.clear();
         animData.clear();
@@ -672,7 +645,7 @@ public class BlenderContext {
      * This enum defines what loaded data type user wants to retreive. It can be
      * either filled structure or already converted data.
      * 
-     * @author Marcin Roguski
+     * @author Marcin Roguski (Kaelthas)
      */
     public static enum LoadedFeatureDataType {
 

+ 20 - 13
engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java

@@ -5,6 +5,7 @@ import java.util.List;
 import java.util.Map;
 
 import com.jme3.animation.Bone;
+import com.jme3.animation.Skeleton;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Vector3f;
@@ -19,6 +20,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
  * @author Marcin Roguski (Kaelthas)
  */
 public class BoneContext {
+    private BlenderContext    blenderContext;
     /** The OMA of the bone's armature object. */
     private Long              armatureObjectOMA;
     /** The structure of the bone. */
@@ -30,7 +32,7 @@ public class BoneContext {
     /** The parent context. */
     private BoneContext       parent;
     /** The children of this context. */
-    private List<BoneContext> children      = new ArrayList<BoneContext>();
+    private List<BoneContext> children = new ArrayList<BoneContext>();
     /** Created bone (available after calling 'buildBone' method). */
     private Bone              bone;
     /** The bone's rest matrix. */
@@ -74,21 +76,22 @@ public class BoneContext {
      */
     private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException {
         this.parent = parent;
+        this.blenderContext = blenderContext;
         this.boneStructure = boneStructure;
         this.armatureObjectOMA = armatureObjectOMA;
         boneName = boneStructure.getFieldValue("name").toString();
         length = ((Number) boneStructure.getFieldValue("length")).floatValue();
         ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
         armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
-        
-        //compute the bone's rest matrix
+
+        // compute the bone's rest matrix
         restMatrix = armatureMatrix.clone();
         inverseTotalTransformation = restMatrix.invert();
-        if(parent != null) {
+        if (parent != null) {
             restMatrix = parent.inverseTotalTransformation.mult(restMatrix);
         }
-        
-        //create the children
+
+        // create the children
         List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
         for (Structure child : childbase) {
             this.children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
@@ -97,7 +100,6 @@ public class BoneContext {
         blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
     }
 
-
     /**
      * This method builds the bone. It recursively builds the bone's children.
      * 
@@ -118,14 +120,12 @@ public class BoneContext {
         boneOMAs.put(bone, boneOMA);
         blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
 
-        ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
-
         Vector3f poseLocation = restMatrix.toTranslationVector();
         Quaternion rotation = restMatrix.toRotationQuat().normalizeLocal();
-        Vector3f scale = objectHelper.getScale(restMatrix);
-        if(parent == null) {
-            Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal(); 
-            scale.multLocal(objectHelper.getScale(objectToArmatureMatrix));            
+        Vector3f scale = restMatrix.toScaleVector();
+        if (parent == null) {
+            Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal();
+            scale.multLocal(objectToArmatureMatrix.toScaleVector());
             rotationQuaternion.multLocal(poseLocation.addLocal(objectToArmatureMatrix.toTranslationVector()));
             rotation.multLocal(rotationQuaternion);
         }
@@ -165,4 +165,11 @@ public class BoneContext {
     public Long getArmatureObjectOMA() {
         return armatureObjectOMA;
     }
+
+    /**
+     * @return the skeleton the bone of this context belongs to
+     */
+    public Skeleton getSkeleton() {
+        return blenderContext.getSkeleton(armatureObjectOMA);
+    }
 }

+ 11 - 15
engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java

@@ -40,7 +40,10 @@ public class Ipo {
     private Track               calculatedTrack;
     /** This variable indicates if the Y asxis is the UP axis or not. */
     protected boolean           fixUpAxis;
-    /** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */
+    /**
+     * Depending on the blender version rotations are stored in degrees or
+     * radians so we need to know the version that is used.
+     */
     protected final int         blenderVersion;
 
     /**
@@ -158,7 +161,8 @@ public class Ipo {
             // calculating track data
             for (int frame = startFrame; frame <= stopFrame; ++frame) {
                 int index = frame - startFrame;
-                times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames;
+                times[index] = index * timeBetweenFrames;// start + (frame - 1)
+                                                         // * timeBetweenFrames;
                 for (int j = 0; j < bezierCurves.length; ++j) {
                     double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
                     switch (bezierCurves[j].getType()) {
@@ -168,7 +172,7 @@ public class Ipo {
                             break;
                         case AC_LOC_Y:
                             if (fixUpAxis) {
-                                translation[2] = (float) -value;
+                                translation[2] = value == 0.0f ? 0 : (float) -value;
                             } else {
                                 translation[1] = (float) value;
                             }
@@ -185,7 +189,7 @@ public class Ipo {
                             break;
                         case OB_ROT_Y:
                             if (fixUpAxis) {
-                                objectRotation[2] = (float) -value * degreeToRadiansFactor;
+                                objectRotation[2] = value == 0.0f ? 0 : (float) -value * degreeToRadiansFactor;
                             } else {
                                 objectRotation[1] = (float) value * degreeToRadiansFactor;
                             }
@@ -199,11 +203,7 @@ public class Ipo {
                             scale[0] = (float) value;
                             break;
                         case AC_SIZE_Y:
-                            if (fixUpAxis) {
-                                scale[2] = (float) value;
-                            } else {
-                                scale[1] = (float) value;
-                            }
+                            scale[fixUpAxis ? 2 : 1] = (float) value;
                             break;
                         case AC_SIZE_Z:
                             scale[fixUpAxis ? 1 : 2] = (float) value;
@@ -220,17 +220,13 @@ public class Ipo {
                             break;
                         case AC_QUAT_Y:
                             if (fixUpAxis) {
-                                quaternionRotation[2] = -(float) value;
+                                quaternionRotation[2] = value == 0.0f ? 0 : -(float) value;
                             } else {
                                 quaternionRotation[1] = (float) value;
                             }
                             break;
                         case AC_QUAT_Z:
-                            if (fixUpAxis) {
-                                quaternionRotation[1] = (float) value;
-                            } else {
-                                quaternionRotation[2] = (float) value;
-                            }
+                            quaternionRotation[fixUpAxis ? 1 : 2] = (float) value;
                             break;
                         default:
                             LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());

+ 15 - 114
engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java

@@ -3,10 +3,6 @@ package com.jme3.scene.plugins.blender.constraints;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.animation.Animation;
-import com.jme3.animation.Bone;
-import com.jme3.animation.BoneTrack;
-import com.jme3.animation.Track;
 import com.jme3.math.Transform;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
@@ -14,13 +10,12 @@ import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
 import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
 import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.ogre.AnimData;
 
 /**
  * Constraint applied on the bone.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class BoneConstraint extends Constraint {
@@ -47,12 +42,14 @@ import com.jme3.scene.plugins.ogre.AnimData;
     }
 
     @Override
-    protected boolean validate() {
+    public boolean validate() {
         if (targetOMA != null) {
             Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
-            // the second part of the if expression verifies if the found node (if any) is an armature node
+            // the second part of the if expression verifies if the found node
+            // (if any) is an armature node
             if (nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) {
-                // if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton
+                // if the target is not an object node then it is an Armature,
+                // so make sure the bone is in the current skeleton
                 BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
                 if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) {
                     LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name);
@@ -62,120 +59,24 @@ import com.jme3.scene.plugins.ogre.AnimData;
                 isNodeTarget = true;
             }
         }
-
         return true;
     }
 
     @Override
-    public void performBakingOperation() {
-        Bone owner = blenderContext.getBoneContext(ownerOMA).getBone();
-
+    public void apply(int frame) {
+        BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
+        Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace);
         if (targetOMA != null) {
             if (isNodeTarget) {
-                Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE);
-                this.prepareTracksForApplyingConstraints();
-                AnimData animData = blenderContext.getAnimData(ownerOMA);
-                if (animData != null) {
-                    for (Animation animation : animData.anims) {
-                        Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
-                        Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext);
-
-                        Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
-                        Track targetTrack = constraintHelper.getTrack(target, animation);
-
-                        constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo);
-                    }
-                }
+                Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
+                constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
             } else {
-                BoneContext boneContext = blenderContext.getBoneByName(subtargetName);
-                Bone target = boneContext.getBone();
-                this.targetOMA = boneContext.getBoneOma();
-
-                this.prepareTracksForApplyingConstraints();
-                AnimData animData = blenderContext.getAnimData(ownerOMA);
-                if (animData != null) {
-                    for (Animation animation : animData.anims) {
-                        Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
-                        Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target);
-
-                        Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
-                        Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation);
-
-                        constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo);
-                    }
-                }
+                Transform targetTransform = constraintHelper.getTransform(targetOMA, subtargetName, targetSpace);
+                constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
             }
         } else {
-            this.prepareTracksForApplyingConstraints();
-            AnimData animData = blenderContext.getAnimData(ownerOMA);
-            if (animData != null) {
-                for (Animation animation : animData.anims) {
-                    Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner);
-                    Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation);
-
-                    constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo);
-                }
-            }
-        }
-    }
-
-    @Override
-    protected void prepareTracksForApplyingConstraints() {
-        Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA };
-        Space[] spaces = new Space[] { ownerSpace, targetSpace };
-
-        // creating animations for current objects if at least on of their parents have an animation
-        for (int i = 0; i < bonesOMAs.length; ++i) {
-            Long oma = bonesOMAs[i];
-            if (this.hasAnimation(oma)) {
-                Bone currentBone = blenderContext.getBoneContext(oma).getBone();
-                Bone parent = currentBone.getParent();
-                boolean foundAnimation = false;
-                AnimData animData = null;
-                while (parent != null && !foundAnimation) {
-                    BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
-                    foundAnimation = this.hasAnimation(boneContext.getBoneOma());
-                    animData = blenderContext.getAnimData(boneContext.getBoneOma());
-                    parent = parent.getParent();
-                }
-
-                if (foundAnimation) {
-                    this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData);
-                }
-            }
-        }
-
-        // creating animation for owner if it doesn't have one already and if the target has it
-        if (!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) {
-            AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
-            this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData);
-        }
-    }
-
-    /**
-     * The method determines if the bone has animations.
-     * 
-     * @param animOwnerOMA
-     *            OMA of the animation's owner
-     * @return <b>true</b> if the target has animations and <b>false</b> otherwise
-     */
-    protected boolean hasAnimation(Long animOwnerOMA) {
-        AnimData animData = blenderContext.getAnimData(animOwnerOMA);
-        if (animData != null) {
-            if (!isNodeTarget) {
-                Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone();
-                int boneIndex = animData.skeleton.getBoneIndex(bone);
-                for (Animation animation : animData.anims) {
-                    for (Track track : animation.getTracks()) {
-                        if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) {
-                            return true;
-                        }
-                    }
-                }
-            } else {
-                return true;
-            }
+            constraintDefinition.bake(ownerTransform, null, this.ipo.calculateValue(frame));
         }
-        return false;
+        constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform);
     }
 }

+ 54 - 74
engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java

@@ -1,17 +1,9 @@
 package com.jme3.scene.plugins.blender.constraints;
 
-import java.util.Arrays;
-import java.util.List;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import com.jme3.animation.Animation;
-import com.jme3.animation.BoneTrack;
-import com.jme3.math.Quaternion;
-import com.jme3.math.Transform;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition;
@@ -19,7 +11,6 @@ import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefiniti
 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.ogre.AnimData;
 
 /**
  * The implementation of a constraint.
@@ -55,6 +46,8 @@ public abstract class Constraint {
      *            the constraint's structure (bConstraint clss in blender 2.49).
      * @param ownerOMA
      *            the old memory address of the constraint owner
+     * @param ownerType
+     *            the type of the constraint owner
      * @param influenceIpo
      *            the ipo curve of the influence factor
      * @param blenderContext
@@ -69,101 +62,88 @@ public abstract class Constraint {
         Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
         if (pData.isNotNull()) {
             Structure data = pData.fetchData(blenderContext.getInputStream()).get(0);
-            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext);
+            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext);
             Pointer pTar = (Pointer) data.getFieldValue("tar");
             if (pTar != null && pTar.isNotNull()) {
                 this.targetOMA = pTar.getOldMemoryAddress();
                 this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
                 Object subtargetValue = data.getFieldValue("subtarget");
-                if (subtargetValue != null) {// not all constraint data have the subtarget field
+                if (subtargetValue != null) {// not all constraint data have the
+                                             // subtarget field
                     subtargetName = subtargetValue.toString();
                 }
             }
         } else {
             // Null constraint has no data, so create it here
-            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext);
+            constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext);
         }
         this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
         this.ipo = influenceIpo;
         this.ownerOMA = ownerOMA;
         this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
+        LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition });
     }
 
     /**
-     * This method bakes the required sontraints into its owner. It checks if the constraint is invalid
-     * or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking
-     * order is kept.
+     * @return <b>true</b> if the constraint is implemented and <b>false</b>
+     *         otherwise
      */
-    public void bake() {
-        if (!this.validate()) {
-            LOGGER.warning("The constraint " + name + " is invalid and will not be applied.");
-        } else if (!baked) {
-            if (targetOMA != null) {
-                List<Constraint> targetConstraints = blenderContext.getConstraints(targetOMA);
-                if (targetConstraints != null && targetConstraints.size() > 0) {
-                    LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name);
-                    for (Constraint targetConstraint : targetConstraints) {
-                        targetConstraint.bake();
-                    }
-                }
-            }
-
-            LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name);
-            this.performBakingOperation();
-            baked = true;
-        }
+    public boolean isImplemented() {
+        return constraintDefinition == null ? true : constraintDefinition.isImplemented();
     }
 
     /**
-     * Performs validation before baking. Checks factors that can prevent constraint from baking that could not be
-     * checked during constraint loading.
+     * @return the name of the constraint type, similar to the constraint name
+     *         used in Blender
      */
-    protected abstract boolean validate();
+    public String getConstraintTypeName() {
+        return constraintDefinition.getConstraintTypeName();
+    }
 
     /**
-     * This method should be overwridden and perform the baking opertion.
+     * Performs validation before baking. Checks factors that can prevent
+     * constraint from baking that could not be checked during constraint
+     * loading.
      */
-    protected abstract void performBakingOperation();
+    public abstract boolean validate();
 
-    /**
-     * This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has -
-     * the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints.
-     * For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner
-     * that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has.
-     */
-    protected abstract void prepareTracksForApplyingConstraints();
-    
-    /**
-     * The method applies bone's current position to all of the traces of the
-     * given animations.
-     * 
-     * @param boneContext
-     *            the bone context
-     * @param space
-     *            the bone's evaluation space
-     * @param referenceAnimData
-     *            the object containing the animations
-     */
-    protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) {
-        ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-        Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone());
-
-        AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma());
+    public abstract void apply(int frame);
 
-        for (Animation animation : referenceAnimData.anims) {
-            BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0];
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        result = prime * result + ((ownerOMA == null) ? 0 : ownerOMA.hashCode());
+        return result;
+    }
 
-            float[] times = parentTrack.getTimes();
-            Vector3f[] translations = new Vector3f[times.length];
-            Quaternion[] rotations = new Quaternion[times.length];
-            Vector3f[] scales = new Vector3f[times.length];
-            Arrays.fill(translations, transform.getTranslation());
-            Arrays.fill(rotations, transform.getRotation());
-            Arrays.fill(scales, transform.getScale());
-            for (Animation anim : animData.anims) {
-                anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales));
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        Constraint other = (Constraint) obj;
+        if (name == null) {
+            if (other.name != null) {
+                return false;
+            }
+        } else if (!name.equals(other.name)) {
+            return false;
+        }
+        if (ownerOMA == null) {
+            if (other.ownerOMA != null) {
+                return false;
             }
+        } else if (!ownerOMA.equals(other.ownerOMA)) {
+            return false;
         }
-        blenderContext.setAnimData(boneContext.getBoneOma(), animData);
+        return true;
     }
 }

+ 228 - 236
engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java

@@ -1,51 +1,65 @@
 package com.jme3.scene.plugins.blender.constraints;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.logging.Logger;
 
-import com.jme3.animation.Animation;
 import com.jme3.animation.Bone;
-import com.jme3.animation.BoneTrack;
 import com.jme3.animation.Skeleton;
-import com.jme3.animation.SpatialTrack;
-import com.jme3.animation.Track;
+import com.jme3.math.FastMath;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
 import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
+import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
 import com.jme3.scene.plugins.blender.animations.IpoHelper;
 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
-import com.jme3.scene.plugins.blender.file.DynamicArray;
 import com.jme3.scene.plugins.blender.file.Pointer;
 import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class should be used for constraint calculations.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 public class ConstraintHelper extends AbstractBlenderHelper {
-    private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName());
+    private static final Logger     LOGGER                      = Logger.getLogger(ConstraintHelper.class.getName());
+
+    private static final Quaternion POS_POSE_SPACE_QUATERNION   = new Quaternion(new float[] { FastMath.PI, 0, 0 });
+    private static final Quaternion NEG_POSE_SPACE_QUATERNION   = new Quaternion(new float[] { -FastMath.PI, 0, 0 });
+    private static final Quaternion POS_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { FastMath.HALF_PI, 0, 0 });
+    private static final Quaternion NEG_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { -FastMath.HALF_PI, 0, 0 });
+
+    private BlenderContext          blenderContext;
 
     /**
-     * Helper constructor. It's main task is to generate the affection functions. These functions are common to all
-     * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall
-     * consider refactoring. The constructor parses the given blender version and stores the result. Some
-     * functionalities may differ in different blender versions.
+     * Helper constructor. It's main task is to generate the affection
+     * functions. These functions are common to all ConstraintHelper instances.
+     * Unfortunately this constructor might grow large. If it becomes too large
+     * - I shall consider refactoring. The constructor parses the given blender
+     * version and stores the result. Some functionalities may differ in
+     * different blender versions.
+     * 
      * @param blenderVersion
      *            the version read from the blend file
+     * @param blenderContext
+     *            the blender context
      * @param fixUpAxis
      *            a variable that indicates if the Y asxis is the UP axis or not
      */
     public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) {
         super(blenderVersion, fixUpAxis);
+        this.blenderContext = blenderContext;
     }
 
     /**
@@ -95,7 +109,8 @@ public class ConstraintHelper extends AbstractBlenderHelper {
                 List<Constraint> constraintsList = new ArrayList<Constraint>();
                 Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress());
 
-                // the name is read directly from structure because bone might not yet be loaded
+                // the name is read directly from structure because bone might
+                // not yet be loaded
                 String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
                 List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);
                 for (Structure constraint : constraints) {
@@ -130,7 +145,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
                     ipo = ipoHelper.fromValue(enforce);
                 }
 
-                constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
+                constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext));
             }
             blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
         }
@@ -155,7 +170,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      * @throws BlenderFileException
      *             thrown when problems with blender file occured
      */
-    private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
+    private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
         if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
             return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
         } else if ("Armature".equalsIgnoreCase(dataType)) {
@@ -172,266 +187,243 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      *            the blender context
      */
     public void bakeConstraints(BlenderContext blenderContext) {
+        List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>();
         for (Constraint constraint : blenderContext.getAllConstraints()) {
-            constraint.bake();
-        }
-    }
+            boolean constraintUsed = false;
+            for (SimulationNode node : simulationRootNodes) {
+                if (node.contains(constraint)) {
+                    constraintUsed = true;
+                    break;
+                }
+            }
 
-    /**
-     * The method returns track for bone.
-     * 
-     * @param bone
-     *            the bone
-     * @param skeleton
-     *            the bone's skeleton
-     * @param animation
-     *            the bone's animation
-     * @return track for the given bone that was found among the given
-     *         animations or null if none is found
-     */
-    /* package */BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) {
-        int boneIndex = skeleton.getBoneIndex(bone);
-        for (Track track : animation.getTracks()) {
-            if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) {
-                return (BoneTrack) track;
+            if (!constraintUsed) {
+                if (constraint instanceof BoneConstraint) {
+                    BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
+                    simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext));
+                } else if (constraint instanceof SpatialConstraint) {
+                    Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
+                    while (spatial.getParent() != null) {
+                        spatial = spatial.getParent();
+                    }
+                    simulationRootNodes.add(new SimulationNode((Long) spatial.getUserData("oma"), blenderContext));
+                } else {
+                    throw new IllegalStateException("Unsupported constraint type: " + constraint);
+                }
             }
         }
-        return null;
-    }
 
-    /**
-     * The method returns track for spatial.
-     * 
-     * @param bone
-     *            the spatial
-     * @param animation
-     *            the spatial's animation
-     * @return track for the given spatial that was found among the given
-     *         animations or null if none is found
-     */
-    /* package */SpatialTrack getTrack(Spatial spatial, Animation animation) {
-        Track[] tracks = animation.getTracks();
-        if (tracks != null && tracks.length == 1) {
-            return (SpatialTrack) tracks[0];
+        for (SimulationNode node : simulationRootNodes) {
+            node.simulate();
         }
-        return null;
     }
 
     /**
-     * This method returns the transform read directly from the blender
-     * structure. This can be used to read transforms from one of the object
-     * types: <li>Spatial <li>Camera <li>Light
+     * The method retreives the transform from a feature in a given space.
      * 
+     * @param oma
+     *            the OMA of the feature (spatial or armature node)
+     * @param subtargetName
+     *            the feature's subtarget (bone in a case of armature's node)
      * @param space
-     *            the space where transform is evaluated
-     * @param spatialOMA
-     *            the OMA of the object
-     * @param blenderContext
-     *            the blender context
-     * @return the object's transform in a given space
+     *            the space the transform is evaluated to
+     * @return thensform of a feature in a given space
      */
-    @SuppressWarnings("unchecked")
-    /* package */Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) {
-        switch (space) {
-            case CONSTRAINT_SPACE_LOCAL:
-                Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE);
-
-                DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc"));
-                Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue());
-                DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot"));
-                Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() });
-                DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size"));
-                Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue());
-
-                if (blenderContext.getBlenderKey().isFixUpAxis()) {
-                    float y = loc.y;
-                    loc.y = loc.z;
-                    loc.z = -y;
-
-                    y = rot.getY();
-                    float z = rot.getZ();
-                    rot.set(rot.getX(), z, -y, rot.getW());
-
-                    y = size.y;
-                    size.y = size.z;
-                    size.z = y;
-                }
+    public Transform getTransform(Long oma, String subtargetName, Space space) {
+        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
+        boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null;
+        if (isArmature) {
+            BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName);
+            Bone bone = targetBoneContext.getBone();
+
+            switch (space) {
+                case CONSTRAINT_SPACE_WORLD:
+                    Transform t = new Transform(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale());
+                    System.out.println("A: " + Arrays.toString(t.getRotation().toAngles(null)));
+                    return t;
+                case CONSTRAINT_SPACE_LOCAL:
+                    Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
+                    localTransform.setScale(bone.getLocalScale());
+                    return localTransform;
+                case CONSTRAINT_SPACE_POSE:
+                    Node nodeWithAnimationControl = blenderContext.getControlledNode(targetBoneContext.getSkeleton());
+                    Matrix4f m = this.toMatrix(nodeWithAnimationControl.getWorldTransform());
+                    Matrix4f boneAgainstModifiedNodeMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
+                    Matrix4f boneWorldMatrix = m.multLocal(boneAgainstModifiedNodeMatrix);
+
+                    Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform()).invertLocal();
+                    Matrix4f r2 = armatureWorldMatrix.multLocal(boneWorldMatrix);
+
+                    Vector3f loc2 = r2.toTranslationVector();
+                    Quaternion rot2 = r2.toRotationQuat().normalizeLocal().multLocal(POS_POSE_SPACE_QUATERNION);
+                    Vector3f scl2 = r2.toScaleVector();
+
+                    return new Transform(loc2, rot2, scl2);
+                case CONSTRAINT_SPACE_PARLOCAL:
+                    Matrix4f parentLocalMatrix = Matrix4f.IDENTITY;
+                    if (bone.getParent() != null) {
+                        Bone parent = bone.getParent();
+                        parentLocalMatrix = this.toMatrix(parent.getLocalPosition(), parent.getLocalRotation(), parent.getLocalScale());
+                    } else {// we need to clone it because otherwise we could
+                            // spoil the IDENTITY matrix
+                        parentLocalMatrix = parentLocalMatrix.clone();
+                    }
+                    Matrix4f boneLocalMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
+                    Matrix4f result = parentLocalMatrix.multLocal(boneLocalMatrix);
 
-                Transform result = new Transform(loc, rot);
-                result.setScale(size);
-                return result;
-            case CONSTRAINT_SPACE_WORLD:// TODO: get it from the object structure ???
-                Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE);
-                if (feature instanceof Spatial) {
+                    Vector3f loc = result.toTranslationVector();
+                    Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(NEG_PARLOC_SPACE_QUATERNION);
+                    Vector3f scl = result.toScaleVector();
+                    return new Transform(loc, rot, scl);
+                default:
+                    throw new IllegalStateException("Unknown space type: " + space);
+            }
+        } else {
+            switch (space) {
+                case CONSTRAINT_SPACE_LOCAL:
+                    return ((Spatial) feature).getLocalTransform();
+                case CONSTRAINT_SPACE_WORLD:
                     return ((Spatial) feature).getWorldTransform();
-                } else if (feature instanceof Skeleton) {
-                    LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null.");
-                    return null;
-                } else {
-                    throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light).");
-                }
-            default:
-                throw new IllegalStateException("Invalid space type for target object: " + space.toString());
+                case CONSTRAINT_SPACE_PARLOCAL:
+                case CONSTRAINT_SPACE_POSE:
+                    throw new IllegalStateException("Nodes can have only Local and World spaces applied!");
+                default:
+                    throw new IllegalStateException("Unknown space type: " + space);
+            }
         }
     }
 
     /**
-     * The method returns the transform for the given bone computed in the given
+     * Applies transform to a feature (bone or spatial). Computations transform
+     * the given transformation from the given space to the feature's local
      * space.
      * 
+     * @param oma
+     *            the OMA of the feature we apply transformation to
+     * @param subtargetName
+     *            the name of the feature's subtarget (bone in case of armature)
      * @param space
-     *            the computation space
-     * @param bone
-     *            the bone we get the transform from
-     * @return the transform of the given bone
-     */
-    /* package */Transform getBoneTransform(Space space, Bone bone) {
-        switch (space) {
-            case CONSTRAINT_SPACE_LOCAL:
-                Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
-                localTransform.setScale(bone.getLocalScale());
-                return localTransform;
-            case CONSTRAINT_SPACE_WORLD:
-                Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation());
-                worldTransform.setScale(bone.getWorldBindScale());
-                return worldTransform;
-            case CONSTRAINT_SPACE_POSE:
-                Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
-                poseTransform.setScale(bone.getLocalScale());
-                return poseTransform;
-            case CONSTRAINT_SPACE_PARLOCAL:
-                Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
-                parentLocalTransform.setScale(bone.getLocalScale());
-                return parentLocalTransform;
-            default:
-                throw new IllegalStateException("Invalid space type for target object: " + space.toString());
-        }
-    }
-
-    /**
-     * The method applies the transform for the given spatial, computed in the
-     * given space.
-     * 
-     * @param spatial
-     *            the spatial we apply the transform for
-     * @param space
-     *            the computation space
+     *            the space in which the given transform is to be applied
      * @param transform
-     *            the transform being applied
+     *            the transform we apply
      */
-    /* package */void applyTransform(Spatial spatial, Space space, Transform transform) {
-        switch (space) {
-            case CONSTRAINT_SPACE_LOCAL:
-                Transform ownerLocalTransform = spatial.getLocalTransform();
-                ownerLocalTransform.getTranslation().addLocal(transform.getTranslation());
-                ownerLocalTransform.getRotation().multLocal(transform.getRotation());
-                ownerLocalTransform.getScale().multLocal(transform.getScale());
-                break;
-            case CONSTRAINT_SPACE_WORLD:
-                Matrix4f m = this.getParentWorldTransformMatrix(spatial);
-                m.invertLocal();
-                Matrix4f matrix = this.toMatrix(transform);
-                m.multLocal(matrix);
-
-                float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20);
-                float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21);
-                float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22);
-
-                transform.setTranslation(m.toTranslationVector());
-                transform.setRotation(m.toRotationQuat());
-                transform.setScale(scaleX, scaleY, scaleZ);
-                spatial.setLocalTransform(transform);
-                break;
-            case CONSTRAINT_SPACE_PARLOCAL:
-            case CONSTRAINT_SPACE_POSE:
-                throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object.");
-            default:
-                throw new IllegalStateException("Invalid space type for target object: " + space.toString());
+    public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
+        Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
+        boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null;
+        if (isArmature) {
+            Skeleton skeleton = blenderContext.getSkeleton(oma);
+            BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName);
+            Bone bone = targetBoneContext.getBone();
+
+            Node nodeControlledBySkeleton = blenderContext.getControlledNode(skeleton);
+            Transform nodeTransform = nodeControlledBySkeleton.getWorldTransform();
+            Matrix4f invertedNodeMatrix = this.toMatrix(nodeTransform).invertLocal();
+
+            switch (space) {
+                case CONSTRAINT_SPACE_LOCAL:
+                    bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
+                    break;
+                case CONSTRAINT_SPACE_WORLD:
+                    System.out.println("B: " + Arrays.toString(transform.getRotation().toAngles(null)));
+                    Matrix4f boneMatrix = this.toMatrix(transform);
+                    Bone parent = bone.getParent();
+                    if (parent != null) {
+                        Matrix4f invertedParentWorldMatrix = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale()).invertLocal();
+                        boneMatrix = invertedParentWorldMatrix.multLocal(boneMatrix);
+                    }
+
+                    boneMatrix = invertedNodeMatrix.multLocal(boneMatrix);
+                    bone.setBindTransforms(boneMatrix.toTranslationVector(), boneMatrix.toRotationQuat(), boneMatrix.toScaleVector());
+                    break;
+                case CONSTRAINT_SPACE_POSE:
+                    Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform());
+                    Matrix4f bonePoseMatrix = this.toMatrix(transform);
+                    Matrix4f boneWorldMatrix = armatureWorldMatrix.multLocal(bonePoseMatrix);
+                    // now compute bone's local matrix
+                    Matrix4f boneLocalMatrix = invertedNodeMatrix.multLocal(boneWorldMatrix);
+
+                    Vector3f loc2 = boneLocalMatrix.toTranslationVector();
+                    Quaternion rot2 = boneLocalMatrix.toRotationQuat().normalizeLocal().multLocal(new Quaternion(NEG_POSE_SPACE_QUATERNION));
+                    Vector3f scl2 = boneLocalMatrix.toScaleVector();
+                    bone.setBindTransforms(loc2, rot2, scl2);
+                    break;
+                case CONSTRAINT_SPACE_PARLOCAL:
+                    Matrix4f parentLocalMatrix = Matrix4f.IDENTITY;
+                    if (bone.getParent() != null) {
+                        parentLocalMatrix = this.toMatrix(bone.getParent().getLocalPosition(), bone.getParent().getLocalRotation(), bone.getParent().getLocalScale());
+                        parentLocalMatrix.invertLocal();
+                    } else {// we need to clone it because otherwise we could
+                            // spoil the IDENTITY matrix
+                        parentLocalMatrix = parentLocalMatrix.clone();
+                    }
+                    Matrix4f m = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale());
+                    Matrix4f result = parentLocalMatrix.multLocal(m);
+                    Vector3f loc = result.toTranslationVector();
+                    Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(POS_PARLOC_SPACE_QUATERNION);
+                    Vector3f scl = result.toScaleVector();
+                    bone.setBindTransforms(loc, rot, scl);
+                    break;
+                default:
+                    throw new IllegalStateException("Invalid space type for target object: " + space.toString());
+            }
+        } else if (feature instanceof Spatial) {
+            Spatial spatial = (Spatial) feature;
+            switch (space) {
+                case CONSTRAINT_SPACE_LOCAL:
+                    spatial.getLocalTransform().set(transform);
+                    break;
+                case CONSTRAINT_SPACE_WORLD:
+                    if (spatial.getParent() == null) {
+                        spatial.setLocalTransform(transform);
+                    } else {
+                        Transform parentWorldTransform = spatial.getParent().getWorldTransform();
+
+                        Matrix4f parentMatrix = this.toMatrix(parentWorldTransform).invertLocal();
+                        Matrix4f m = this.toMatrix(transform);
+                        m = m.multLocal(parentMatrix);
+
+                        transform.setTranslation(m.toTranslationVector());
+                        transform.setRotation(m.toRotationQuat());
+                        transform.setScale(m.toScaleVector());
+
+                        spatial.setLocalTransform(transform);
+                    }
+                    break;
+                default:
+                    throw new IllegalStateException("Invalid space type for spatial object: " + space.toString());
+            }
+        } else {
+            throw new IllegalStateException("Constrained transformation can be applied only to Bone or Spatial feature!");
         }
     }
 
     /**
-     * The method applies the transform for the given bone, computed in the
-     * given space.
+     * Converts given transform to the matrix.
      * 
-     * @param bone
-     *            the bone we apply the transform for
-     * @param space
-     *            the computation space
      * @param transform
-     *            the transform being applied
-     */
-    /* package */void applyTransform(Bone bone, Space space, Transform transform) {
-        switch (space) {
-            case CONSTRAINT_SPACE_LOCAL:
-                bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
-                break;
-            case CONSTRAINT_SPACE_WORLD:
-                Matrix4f m = this.getParentWorldTransformMatrix(bone);
-                // m.invertLocal();
-                transform.setTranslation(m.mult(transform.getTranslation()));
-                transform.setRotation(m.mult(transform.getRotation(), null));
-                transform.setScale(transform.getScale());
-                bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
-                // float x = FastMath.HALF_PI/2;
-                // float y = -FastMath.HALF_PI;
-                // float z = -FastMath.HALF_PI/2;
-                // bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1));
-                break;
-            case CONSTRAINT_SPACE_PARLOCAL:
-                Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation());
-                Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation());
-                bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale());
-                break;
-            case CONSTRAINT_SPACE_POSE:
-                bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
-                break;
-            default:
-                throw new IllegalStateException("Invalid space type for target object: " + space.toString());
-        }
-    }
-
-    /**
-     * @return world transform matrix of the feature's parent or identity matrix
-     *         if the feature has no parent
-     */
-    private Matrix4f getParentWorldTransformMatrix(Spatial spatial) {
-        Matrix4f result = new Matrix4f();
-        if (spatial.getParent() != null) {
-            Transform t = spatial.getParent().getWorldTransform();
-            result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix());
-        }
-        return result;
-    }
-
-    /**
-     * @return world transform matrix of the feature's parent or identity matrix
-     *         if the feature has no parent
+     *            the transform to be converted
+     * @return 4x4 matrix that represents the given transform
      */
-    private Matrix4f getParentWorldTransformMatrix(Bone bone) {
-        Matrix4f result = new Matrix4f();
-        Bone parent = bone.getParent();
-        if (parent != null) {
-            result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix());
+    private Matrix4f toMatrix(Transform transform) {
+        Matrix4f result = Matrix4f.IDENTITY;
+        if (transform != null) {
+            result = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale());
         }
         return result;
     }
 
     /**
-     * Converts given transform to the matrix.
+     * Converts given transformation parameters into the matrix.
      * 
      * @param transform
      *            the transform to be converted
-     * @return 4x4 matri that represents the given transform
+     * @return 4x4 matrix that represents the given transformation parameters
      */
-    private Matrix4f toMatrix(Transform transform) {
-        Matrix4f result = Matrix4f.IDENTITY;
-        if (transform != null) {
-            result = new Matrix4f();
-            result.setTranslation(transform.getTranslation());
-            result.setRotationQuaternion(transform.getRotation());
-            result.setScale(transform.getScale());
-        }
+    private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale) {
+        Matrix4f result = new Matrix4f();
+        result.setTranslation(position);
+        result.setRotationQuaternion(rotation);
+        result.setScale(scale);
         return result;
     }
 
@@ -447,7 +439,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      */
     public static enum Space {
 
-        CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID;
+        CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL;
 
         /**
          * This method returns the enum instance when given the appropriate
@@ -468,7 +460,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
                 case 3:
                     return CONSTRAINT_SPACE_PARLOCAL;
                 default:
-                    return CONSTRAINT_SPACE_INVALID;
+                    throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!");
             }
         }
     }

+ 415 - 0
engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java

@@ -0,0 +1,415 @@
+package com.jme3.scene.plugins.blender.constraints;
+
+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.AnimChannel;
+import com.jme3.animation.AnimControl;
+import com.jme3.animation.Animation;
+import com.jme3.animation.Bone;
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.Skeleton;
+import com.jme3.animation.SpatialTrack;
+import com.jme3.animation.Track;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+import com.jme3.scene.Node;
+import com.jme3.scene.Spatial;
+import com.jme3.scene.plugins.blender.BlenderContext;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
+import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
+import com.jme3.scene.plugins.blender.animations.BoneContext;
+import com.jme3.util.TempVars;
+
+/**
+ * A node that represents either spatial or bone in constraint simulation. The
+ * node is applied its translation, rotation and scale for each frame of its
+ * animation. Then the constraints are applied that will eventually alter it.
+ * After that the feature's transformation is stored in VirtualTrack which is
+ * converted to new bone or spatial track at the very end.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+public class SimulationNode {
+    private static final Logger       LOGGER       = Logger.getLogger(SimulationNode.class.getName());
+
+    /** The name of the node (for debugging purposes). */
+    private String                    name;
+    /** A list of children for the node (either bones or child spatials). */
+    private List<SimulationNode>      children     = new ArrayList<SimulationNode>();
+    /** A virtual track for each of the nodes. */
+    private Map<String, VirtualTrack> virtualTrack = new HashMap<String, VirtualTrack>();
+    /** A list of constraints that the current node has. */
+    private List<Constraint>          constraints;
+    /** A list of node's animations. */
+    private List<Animation>           animations;
+
+    /** The nodes spatial (if null then the boneContext should be set). */
+    private Spatial                   spatial;
+    /** The skeleton of the bone (not null if the node simulated the bone). */
+    private Skeleton                  skeleton;
+    /** Animation controller for the node's feature. */
+    private AnimControl               animControl;
+
+    /**
+     * The star transform of a spatial. Needed to properly reset the spatial to
+     * its start position.
+     */
+    private Transform                 spatialStartTransform;
+    /** Star transformations for bones. Needed to properly reset the bones. */
+    private Map<Bone, Transform>      boneStartTransforms;
+
+    /**
+     * Builds the nodes tree for the given feature. The feature (bone or
+     * spatial) is found by its OMA. The feature must be a root bone or a root
+     * spatial.
+     * 
+     * @param featureOMA
+     *            the OMA of either bone or spatial
+     * @param blenderContext
+     *            the blender context
+     */
+    public SimulationNode(Long featureOMA, BlenderContext blenderContext) {
+        this(featureOMA, blenderContext, true);
+    }
+
+    /**
+     * Creates the node for the feature.
+     * 
+     * @param featureOMA
+     *            the OMA of either bone or spatial
+     * @param blenderContext
+     *            the blender context
+     * @param rootNode
+     *            indicates if the feature is a root bone or root spatial or not
+     */
+    private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
+        Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE);
+        if (spatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) {
+            this.skeleton = blenderContext.getSkeleton(featureOMA);
+
+            Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton);
+            this.animControl = nodeWithAnimationControl.getControl(AnimControl.class);
+
+            boneStartTransforms = new HashMap<Bone, Transform>();
+            for (int i = 0; i < skeleton.getBoneCount(); ++i) {
+                Bone bone = skeleton.getBone(i);
+                boneStartTransforms.put(bone, new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation(), bone.getWorldBindScale()));
+            }
+        } else {
+            if (rootNode && spatial.getParent() != null) {
+                throw new IllegalStateException("Given spatial must be a root node!");
+            }
+            this.spatial = spatial;
+            this.spatialStartTransform = spatial.getLocalTransform().clone();
+        }
+
+        this.name = '>' + spatial.getName() + '<';
+
+        constraints = this.findConstraints(featureOMA, blenderContext);
+        if (constraints == null) {
+            constraints = new ArrayList<Constraint>();
+        }
+
+        // add children nodes
+        if (skeleton != null) {
+            // bone with index 0 is a root bone and should not be considered
+            // here
+            for (int i = 1; i < skeleton.getBoneCount(); ++i) {
+                BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(i));
+                List<Constraint> boneConstraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
+                if (boneConstraints != null) {
+                    constraints.addAll(boneConstraints);
+                }
+            }
+
+            // each bone of the skeleton has the same anim data applied
+            BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1));
+            Long boneOma = boneContext.getBoneOma();
+            animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims;
+        } else {
+            animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims;
+            for (Spatial child : spatial.getChildren()) {
+                if (child instanceof Node) {
+                    children.add(new SimulationNode((Long) child.getUserData("oma"), blenderContext, false));
+                }
+            }
+        }
+
+        LOGGER.info("Removing invalid constraints.");
+        List<Constraint> validConstraints = new ArrayList<Constraint>(constraints.size());
+        for (Constraint constraint : this.constraints) {
+            if (constraint.validate()) {
+                validConstraints.add(constraint);
+            } else {
+                LOGGER.log(Level.WARNING, "Constraint {0} is invalid and will not be applied.", constraint.name);
+            }
+        }
+        this.constraints = validConstraints;
+    }
+
+    /**
+     * Tells if the node already contains the given constraint (so that it is
+     * not applied twice).
+     * 
+     * @param constraint
+     *            the constraint to be checked
+     * @return <b>true</b> if the constraint already is stored in the node and
+     *         <b>false</b> otherwise
+     */
+    public boolean contains(Constraint constraint) {
+        boolean result = false;
+        if (constraints != null && constraints.size() > 0) {
+            for (Constraint c : constraints) {
+                if (c.equals(constraint)) {
+                    return true;
+                }
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Resets the node's feature to its starting transformation.
+     */
+    private void reset() {
+        if (spatial != null) {
+            spatial.setLocalTransform(spatialStartTransform);
+            for (SimulationNode child : children) {
+                child.reset();
+            }
+        } else if (skeleton != null) {
+            for (Entry<Bone, Transform> entry : boneStartTransforms.entrySet()) {
+                Transform t = entry.getValue();
+                entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
+            }
+            skeleton.reset();
+        }
+    }
+
+    /**
+     * Simulates the spatial node.
+     */
+    private void simulateSpatial() {
+        if (constraints != null && constraints.size() > 0) {
+            boolean applyStaticConstraints = true;
+            if (animations != null) {
+                for (Animation animation : animations) {
+                    float[] animationTimeBoundaries = computeAnimationTimeBoundaries(animation);
+                    int maxFrame = (int) animationTimeBoundaries[0];
+                    float maxTime = animationTimeBoundaries[1];
+
+                    VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime);
+                    virtualTrack.put(animation.getName(), vTrack);
+                    for (Track track : animation.getTracks()) {
+                        for (int frame = 0; frame < maxFrame; ++frame) {
+                            spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]);
+                            spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]);
+                            spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]);
+
+                            for (Constraint constraint : constraints) {
+                                constraint.apply(frame);
+                                vTrack.setTransform(frame, spatial.getLocalTransform());
+                            }
+                        }
+                        Track newTrack = vTrack.getAsSpatialTrack();
+                        if (newTrack != null) {
+                            animation.removeTrack(track);
+                            animation.addTrack(newTrack);
+                        }
+                        applyStaticConstraints = false;
+                    }
+                }
+            }
+
+            // if there are no animations then just constraint the static
+            // object's transformation
+            if (applyStaticConstraints) {
+                for (Constraint constraint : constraints) {
+                    constraint.apply(0);
+                }
+            }
+        }
+
+        for (SimulationNode child : children) {
+            child.simulate();
+        }
+    }
+
+    /**
+     * Simulates the bone node.
+     */
+    private void simulateSkeleton() {
+        if (constraints != null && constraints.size() > 0) {
+            boolean applyStaticConstraints = true;
+
+            if (animations != null) {
+                TempVars vars = TempVars.get();
+                AnimChannel animChannel = animControl.createChannel();
+                for (Animation animation : animations) {
+                    float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
+                    int maxFrame = (int) animationTimeBoundaries[0];
+                    float maxTime = animationTimeBoundaries[1];
+
+                    Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
+                    Map<Integer, Transform> previousTransforms = new HashMap<Integer, Transform>();
+                    for (int frame = 0; frame < maxFrame; ++frame) {
+                        this.reset();// this MUST be done here, otherwise
+                                     // setting next frame of animation will
+                                     // lead to possible errors
+                        // first set proper time for all bones in all the tracks
+                        // ...
+                        for (Track track : animation.getTracks()) {
+                            float time = ((BoneTrack) track).getTimes()[frame];
+                            Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex();
+
+                            track.setTime(time, 1, animControl, animChannel, vars);
+                            skeleton.updateWorldVectors();
+
+                            Transform previousTransform = previousTransforms.get(boneIndex);
+                            if (previousTransform == null) {
+                                Bone bone = skeleton.getBone(boneIndex);
+                                previousTransform = new Transform();
+                                previousTransform.setTranslation(bone.getLocalPosition());
+                                previousTransform.setRotation(bone.getLocalRotation());
+                                previousTransform.setScale(bone.getLocalScale());
+                                previousTransforms.put(boneIndex, previousTransform);
+                            }
+                        }
+
+                        // ... and then apply constraints ...
+                        for (Constraint constraint : constraints) {
+                            constraint.apply(frame);
+                        }
+
+                        // ... and fill in another frame in the result track
+                        for (Track track : animation.getTracks()) {
+                            Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex();
+                            Bone bone = skeleton.getBone(boneIndex);
+
+                            // take the initial transform of a bone
+                            Transform previousTransform = previousTransforms.get(boneIndex);
+
+                            VirtualTrack vTrack = tracks.get(boneIndex);
+                            if (vTrack == null) {
+                                vTrack = new VirtualTrack(maxFrame, maxTime);
+                                tracks.put(boneIndex, vTrack);
+                            }
+
+                            Vector3f bonePositionDifference = bone.getLocalPosition().subtract(previousTransform.getTranslation());
+                            Quaternion boneRotationDifference = bone.getLocalRotation().mult(previousTransform.getRotation().inverse()).normalizeLocal();
+                            Vector3f boneScaleDifference = bone.getLocalScale().divide(previousTransform.getScale());
+                            if (frame > 0) {
+                                bonePositionDifference = vTrack.translations.get(frame - 1).add(bonePositionDifference);
+                                boneRotationDifference = vTrack.rotations.get(frame - 1).mult(boneRotationDifference);
+                                boneScaleDifference = vTrack.scales.get(frame - 1).mult(boneScaleDifference);
+                            }
+                            vTrack.setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
+
+                            previousTransform.setTranslation(bone.getLocalPosition());
+                            previousTransform.setRotation(bone.getLocalRotation());
+                            previousTransform.setScale(bone.getLocalScale());
+                        }
+                    }
+
+                    for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
+                        Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey());
+                        if (newTrack != null) {
+                            for (Track track : animation.getTracks()) {
+                                if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) {
+                                    animation.removeTrack(track);
+                                    animation.addTrack(newTrack);
+                                    break;
+                                }
+                            }
+                        }
+                        applyStaticConstraints = false;
+                    }
+                }
+                vars.release();
+                animControl.clearChannels();
+                this.reset();
+            }
+
+            // if there are no animations then just constraint the static
+            // object's transformation
+            if (applyStaticConstraints) {
+                for (Constraint constraint : constraints) {
+                    constraint.apply(0);
+                }
+            }
+        }
+    }
+
+    /**
+     * Simulates the node.
+     */
+    public void simulate() {
+        this.reset();
+        if (spatial != null) {
+            this.simulateSpatial();
+        } else {
+            this.simulateSkeleton();
+        }
+        this.reset();
+    }
+
+    /**
+     * Computes the maximum frame and time for the animation. Different tracks
+     * can have different lengths so here the maximum one is being found.
+     * 
+     * @param animation
+     *            the animation
+     * @return maximum frame and time of the animation
+     */
+    private float[] computeAnimationTimeBoundaries(Animation animation) {
+        int maxFrame = Integer.MIN_VALUE;
+        float maxTime = Float.MIN_VALUE;
+        for (Track track : animation.getTracks()) {
+            if (track instanceof BoneTrack) {
+                maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);
+                maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]);
+            } else if (track instanceof SpatialTrack) {
+                maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length);
+                maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]);
+            } else {
+                throw new IllegalStateException("Unsupported track type for simuation: " + track);
+            }
+        }
+        return new float[] { maxFrame, maxTime };
+    }
+
+    /**
+     * Finds constraints for the node's features.
+     * 
+     * @param ownerOMA
+     *            the feature's OMA
+     * @param blenderContext
+     *            the blender context
+     * @return a list of feature's constraints or empty list if none were found
+     */
+    private List<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
+        List<Constraint> result = new ArrayList<Constraint>();
+        for (Constraint constraint : blenderContext.getAllConstraints()) {
+            if (constraint.ownerOMA.longValue() == ownerOMA.longValue()) {
+                if (constraint.isImplemented()) {
+                    result.add(constraint);
+                } else {
+                    LOGGER.log(Level.WARNING, "Constraint named: ''{0}'' of type ''{1}'' is not implemented and will NOT be applied!", new Object[] { constraint.name, constraint.getConstraintTypeName() });
+                }
+            }
+        }
+        return result.size() > 0 ? result : null;
+    }
+
+    @Override
+    public String toString() {
+        return name;
+    }
+}

+ 5 - 8
engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java

@@ -23,16 +23,13 @@ import com.jme3.scene.plugins.blender.file.Structure;
     }
 
     @Override
-    public void performBakingOperation() {
-        LOGGER.warning("Applying constraints to skeleton is not supported.");
+    public boolean validate() {
+        LOGGER.warning("Constraints for skeleton are not supported.");
+        return false;
     }
 
     @Override
-    protected boolean validate() {
-        return true;
-    }
-
-    @Override
-    protected void prepareTracksForApplyingConstraints() {
+    public void apply(int frame) {
+        LOGGER.warning("Applying constraints to skeleton is not supported.");
     }
 }

+ 9 - 177
engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java

@@ -1,204 +1,36 @@
 package com.jme3.scene.plugins.blender.constraints;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.logging.Logger;
-
-import com.jme3.animation.AnimControl;
-import com.jme3.animation.Animation;
-import com.jme3.animation.Bone;
-import com.jme3.animation.BoneTrack;
-import com.jme3.animation.SpatialTrack;
-import com.jme3.animation.Track;
-import com.jme3.math.Quaternion;
 import com.jme3.math.Transform;
-import com.jme3.math.Vector3f;
-import com.jme3.scene.Spatial;
 import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
-import com.jme3.scene.plugins.blender.animations.ArmatureHelper;
-import com.jme3.scene.plugins.blender.animations.BoneContext;
 import com.jme3.scene.plugins.blender.animations.Ipo;
-import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.scene.plugins.ogre.AnimData;
 
 /**
- * Constraint applied on the spatial objects.
- * This includes: nodes, cameras nodes and light nodes.
+ * Constraint applied on the spatial objects. This includes: nodes, cameras
+ * nodes and light nodes.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class SpatialConstraint extends Constraint {
-    private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName());
-
-    /** The owner of the constraint. */
-    private Spatial             owner;
-    /** The target of the constraint. */
-    private Object              target;
-
     public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
         super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
     }
 
     @Override
-    protected boolean validate() {
+    public boolean validate() {
         if (targetOMA != null) {
             return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null;
         }
         return true;
     }
-    
-    @Override
-    public void performBakingOperation() {
-        this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
-        this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null;
-        this.prepareTracksForApplyingConstraints();
-
-        // apply static constraint
-        Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext);
-        Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null;
-        constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo);
-        constraintHelper.applyTransform(owner, ownerSpace, ownerTransform);
-
-        // apply dynamic constraint
-        AnimData animData = blenderContext.getAnimData(ownerOMA);
-        if (animData != null) {
-            for (Animation animation : animData.anims) {
-                SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation);
-
-                AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
-                SpatialTrack targetTrack = null;
-                if (targetAnimData != null) {
-                    targetTrack = constraintHelper.getTrack((Spatial) target, targetAnimData.anims.get(0));
-                }
-
-                constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo);
-            }
-        }
-    }
 
     @Override
-    protected void prepareTracksForApplyingConstraints() {
-        Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA };
-        Space[] spaces = new Space[] { ownerSpace, targetSpace };
-
-        // creating animations for current objects if at least on of their parents have an animation
-        for (int i = 0; i < spatialsOMAs.length; ++i) {
-            Long oma = spatialsOMAs[i];
-            if (oma != null && oma > 0L) {
-                AnimData animData = blenderContext.getAnimData(oma);
-                if (animData == null) {
-                    Spatial currentSpatial = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
-                    if (currentSpatial != null) {
-                        if (currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {// look for it among bones
-                            BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName);
-                            Bone currentBone = currentBoneContext.getBone();
-                            Bone parent = currentBone.getParent();
-                            boolean foundAnimation = false;
-                            while (parent != null && !foundAnimation) {
-                                BoneContext boneContext = blenderContext.getBoneByName(parent.getName());
-                                foundAnimation = this.hasAnimation(boneContext.getBoneOma());
-                                animData = blenderContext.getAnimData(boneContext.getBoneOma());
-                                parent = parent.getParent();
-                            }
-                            if (foundAnimation) {
-                                this.applyAnimData(currentBoneContext, spaces[i], animData);
-                            }
-                        } else {
-                            Spatial parent = currentSpatial.getParent();
-                            while (parent != null && animData == null) {
-                                Structure parentStructure = (Structure) blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE);
-                                if (parentStructure == null) {
-                                    parent = null;
-                                } else {
-                                    Long parentOma = parentStructure.getOldMemoryAddress();
-                                    animData = blenderContext.getAnimData(parentOma);
-                                    parent = parent.getParent();
-                                }
-                            }
-
-                            if (animData != null) {// create anim data for the current object
-                                this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0));
-                            }
-                        }
-                    } else {
-                        LOGGER.warning("Couldn't find target object for constraint: " + name + ". Make sure that the target is on layer that is defined to be loaded in blender key!");
-                    }
-                }
-            }
-        }
-
-        // creating animation for owner if it doesn't have one already and if the target has it
-        AnimData animData = blenderContext.getAnimData(ownerOMA);
-        if (animData == null) {
-            AnimData targetAnimData = blenderContext.getAnimData(targetOMA);
-            if (targetAnimData != null) {
-                this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0));
-            }
-        }
-    }
-
-    /**
-     * The method determines if the bone has animations.
-     * 
-     * @param boneOMA
-     *            OMA of the animation's owner
-     * @return <b>true</b> if the target has animations and <b>false</b> otherwise
-     */
-    protected boolean hasAnimation(Long boneOMA) {
-        AnimData animData = blenderContext.getAnimData(boneOMA);
-        if (animData != null) {
-            Bone bone = blenderContext.getBoneContext(boneOMA).getBone();
-            int boneIndex = animData.skeleton.getBoneIndex(bone);
-            for (Animation animation : animData.anims) {
-                for (Track track : animation.getTracks()) {
-                    if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-    
-    /**
-     * This method applies spatial transform on each frame of the given
-     * animations.
-     * 
-     * @param spatial
-     *            the spatial
-     * @param spatialOma
-     *            the OMA of the given spatial
-     * @param space
-     *            the space we compute the transform in
-     * @param referenceAnimation
-     *            the object containing the animations
-     */
-    private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) {
-        ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
-        Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext);
-
-        SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0];
-
-        HashMap<String, Animation> anims = new HashMap<String, Animation>(1);
-        Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength());
-        anims.put(spatial.getName(), animation);
-
-        float[] times = parentTrack.getTimes();
-        Vector3f[] translations = new Vector3f[times.length];
-        Quaternion[] rotations = new Quaternion[times.length];
-        Vector3f[] scales = new Vector3f[times.length];
-        Arrays.fill(translations, transform.getTranslation());
-        Arrays.fill(rotations, transform.getRotation());
-        Arrays.fill(scales, transform.getScale());
-        animation.addTrack(new SpatialTrack(times, translations, rotations, scales));
-
-        AnimControl control = new AnimControl(null);
-        control.setAnimations(anims);
-        spatial.addControl(control);
-
-        blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList<Animation>(anims.values())));
+    public void apply(int frame) {
+        Transform ownerTransform = constraintHelper.getTransform(ownerOMA, null, ownerSpace);
+        Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
+        constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
+        constraintHelper.applyTransform(ownerOMA, subtargetName, ownerSpace, ownerTransform);
     }
 }

+ 146 - 0
engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java

@@ -0,0 +1,146 @@
+package com.jme3.scene.plugins.blender.constraints;
+
+import java.util.ArrayList;
+
+import com.jme3.animation.BoneTrack;
+import com.jme3.animation.SpatialTrack;
+import com.jme3.math.Quaternion;
+import com.jme3.math.Transform;
+import com.jme3.math.Vector3f;
+
+/**
+ * A virtual track that stores computed frames after constraints are applied.
+ * Not all the frames need to be inserted. If there are lacks then the class
+ * will fill the gaps.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
+/* package */class VirtualTrack {
+    /** The last frame for the track. */
+    public int                   maxFrame;
+    /** The max time for the track. */
+    public float                 maxTime;
+    /** Translations of the track. */
+    public ArrayList<Vector3f>   translations;
+    /** Rotations of the track. */
+    public ArrayList<Quaternion> rotations;
+    /** Scales of the track. */
+    public ArrayList<Vector3f>   scales;
+
+    /**
+     * Constructs the object storing the maximum frame and time.
+     * 
+     * @param maxFrame
+     *            the last frame for the track
+     * @param maxTime
+     *            the max time for the track
+     */
+    public VirtualTrack(int maxFrame, float maxTime) {
+        this.maxFrame = maxFrame;
+        this.maxTime = maxTime;
+    }
+
+    /**
+     * Sets the transform for the given frame.
+     * 
+     * @param frameIndex
+     *            the frame for which the transform will be set
+     * @param transform
+     *            the transformation to be set
+     */
+    public void setTransform(int frameIndex, Transform transform) {
+        if (translations == null) {
+            translations = this.createList(Vector3f.ZERO, frameIndex);
+        }
+        this.append(translations, Vector3f.ZERO, frameIndex - translations.size());
+        translations.add(transform.getTranslation().clone());
+
+        if (rotations == null) {
+            rotations = this.createList(Quaternion.IDENTITY, frameIndex);
+        }
+        this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size());
+        rotations.add(transform.getRotation().clone());
+
+        if (scales == null) {
+            scales = this.createList(Vector3f.UNIT_XYZ, frameIndex);
+        }
+        this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size());
+        scales.add(transform.getScale().clone());
+    }
+
+    /**
+     * Returns the track as a bone track.
+     * 
+     * @param targetBoneIndex
+     *            the bone index
+     * @return the bone track
+     */
+    public BoneTrack getAsBoneTrack(int targetBoneIndex) {
+        if (translations == null && rotations == null && scales == null) {
+            return null;
+        }
+        return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
+    }
+
+    /**
+     * Returns the track as a spatial track.
+     * 
+     * @return the spatial track
+     */
+    public SpatialTrack getAsSpatialTrack() {
+        if (translations == null && rotations == null && scales == null) {
+            return null;
+        }
+        return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame]));
+    }
+
+    /**
+     * The method creates times for the track based on the given maximum values.
+     * 
+     * @return the times for the track
+     */
+    private float[] createTimes() {
+        float[] times = new float[maxFrame];
+        float dT = maxTime / (float) maxFrame;
+        float t = 0;
+        for (int i = 0; i < maxFrame; ++i) {
+            times[i] = t;
+            t += dT;
+        }
+        return times;
+    }
+
+    /**
+     * Helper method that creates a list of a given size filled with given
+     * elements.
+     * 
+     * @param element
+     *            the element to be put into the list
+     * @param count
+     *            the list size
+     * @return the list
+     */
+    private <T> ArrayList<T> createList(T element, int count) {
+        ArrayList<T> result = new ArrayList<T>(count);
+        for (int i = 0; i < count; ++i) {
+            result.add(element);
+        }
+        return result;
+    }
+
+    /**
+     * Appends the element to the given list.
+     * 
+     * @param list
+     *            the list where the element will be appended
+     * @param element
+     *            the element to be appended
+     * @param count
+     *            how many times the element will be appended
+     */
+    private <T> void append(ArrayList<T> list, T element, int count) {
+        for (int i = 0; i < count; ++i) {
+            list.add(element);
+        }
+    }
+}

+ 24 - 218
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java

@@ -1,243 +1,49 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
-import java.io.IOException;
-
-import com.jme3.animation.AnimChannel;
-import com.jme3.animation.AnimControl;
-import com.jme3.animation.BoneTrack;
-import com.jme3.animation.SpatialTrack;
-import com.jme3.animation.Track;
-import com.jme3.export.JmeExporter;
-import com.jme3.export.JmeImporter;
-import com.jme3.math.FastMath;
-import com.jme3.math.Quaternion;
 import com.jme3.math.Transform;
-import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
-import com.jme3.scene.plugins.blender.animations.Ipo;
+import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
 import com.jme3.scene.plugins.blender.file.Structure;
-import com.jme3.util.TempVars;
 
 public abstract class ConstraintDefinition {
-    protected int flag;
+    protected int          flag;
+    private Object         owner;
+    private BlenderContext blenderContext;
+    private Long           ownerOMA;
 
-    public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) {
+    public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
         if (constraintData != null) {// Null constraint has no data
             Number flag = (Number) constraintData.getFieldValue("flag");
             if (flag != null) {
                 this.flag = flag.intValue();
             }
         }
-    }
-
-    public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) {
-        TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null;
-        TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null;
-
-        // uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie
-        this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0));
-        if (ownerWrapperTrack != null) {
-            float[] ownerTimes = ownerWrapperTrack.getTimes();
-            Vector3f[] translations = ownerWrapperTrack.getTranslations();
-            Quaternion[] rotations = ownerWrapperTrack.getRotations();
-            Vector3f[] scales = ownerWrapperTrack.getScales();
-
-            float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes();
-            Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations();
-            Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations();
-            Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales();
-            Vector3f translation = new Vector3f(), scale = new Vector3f();
-            Quaternion rotation = new Quaternion();
-
-            Transform ownerTemp = new Transform(), targetTemp = new Transform();
-            for (int i = 0; i < ownerTimes.length; ++i) {
-                float t = ownerTimes[i];
-                ownerTemp.setTranslation(translations[i]);
-                ownerTemp.setRotation(rotations[i]);
-                ownerTemp.setScale(scales[i]);
-                if (targetWrapperTrack == null) {
-                    this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i));
-                } else {
-                    // getting the values that are the interpolation of the target track for the time 't'
-                    this.interpolate(targetTranslations, targetTimes, t, translation);
-                    this.interpolate(targetRotations, targetTimes, t, rotation);
-                    this.interpolate(targetScales, targetTimes, t, scale);
-
-                    targetTemp.setTranslation(translation);
-                    targetTemp.setRotation(rotation);
-                    targetTemp.setScale(scale);
-
-                    this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i));
-                }
-                // need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array
-                translations[i] = ownerTemp.getTranslation().clone();
-                rotations[i] = ownerTemp.getRotation().clone();
-                scales[i] = ownerTemp.getScale().clone();
-            }
-            ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales);
-        }
-    }
-
-    protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
-
-    private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) {
-        int index = 0;
-        for (int i = 1; i < targetTimes.length; ++i) {
-            if (targetTimes[i] < currentTime) {
-                ++index;
-            } else {
-                break;
-            }
-        }
-        if (index >= targetTimes.length - 1) {
-            result.set(targetVectors[targetTimes.length - 1]);
-        } else {
-            float delta = targetTimes[index + 1] - targetTimes[index];
-            if (delta == 0.0f) {
-                result.set(targetVectors[index + 1]);
-            } else {
-                float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
-                FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result);
-            }
-        }
-    }
-
-    private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) {
-        int index = 0;
-        for (int i = 1; i < targetTimes.length; ++i) {
-            if (targetTimes[i] < currentTime) {
-                ++index;
-            } else {
-                break;
-            }
-        }
-        if (index >= targetTimes.length - 1) {
-            result.set(targetQuaternions[targetTimes.length - 1]);
-        } else {
-            float delta = targetTimes[index + 1] - targetTimes[index];
-            if (delta == 0.0f) {
-                result.set(targetQuaternions[index + 1]);
-            } else {
-                float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]);
-                result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale);
-            }
-        }
+        this.blenderContext = blenderContext;
+        this.ownerOMA = ownerOMA;
     }
 
     /**
-     * This class holds either the bone track or spatial track. Is made to improve
-     * code readability.
+     * This method is here because we have no guarantee that the owner is loaded
+     * when constraint is being created. So use it to get the owner when it is
+     * needed for computations.
      * 
-     * @author Marcin Roguski (Kaelthas)
+     * @return the owner of the constraint or null if none is set
      */
-    private static class TrackWrapper implements Track {
-        /** The spatial track. */
-        private SpatialTrack spatialTrack;
-        /** The bone track. */
-        private BoneTrack    boneTrack;
-
-        /**
-         * Constructs the object using the given track. The track must be of one of the types: <li>BoneTrack <li>SpatialTrack
-         * 
-         * @param track
-         *            the animation track
-         */
-        public TrackWrapper(Track track) {
-            if (track instanceof SpatialTrack) {
-                this.spatialTrack = (SpatialTrack) track;
-            } else if (track instanceof BoneTrack) {
-                this.boneTrack = (BoneTrack) track;
-            } else {
-                throw new IllegalStateException("Unknown track type!");
+    public Object getOwner() {
+        if (ownerOMA != null && owner == null) {
+            owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
+            if (owner == null) {
+                throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
             }
         }
+        return owner;
+    }
 
-        /**
-         * @return the array of rotations of this track
-         */
-        public Quaternion[] getRotations() {
-            if (boneTrack != null) {
-                return boneTrack.getRotations();
-            }
-            return spatialTrack.getRotations();
-        }
-
-        /**
-         * @return the array of scales for this track
-         */
-        public Vector3f[] getScales() {
-            if (boneTrack != null) {
-                return boneTrack.getScales();
-            }
-            return spatialTrack.getScales();
-        }
-
-        /**
-         * @return the arrays of time for this track
-         */
-        public float[] getTimes() {
-            if (boneTrack != null) {
-                return boneTrack.getTimes();
-            }
-            return spatialTrack.getTimes();
-        }
-
-        /**
-         * @return the array of translations of this track
-         */
-        public Vector3f[] getTranslations() {
-            if (boneTrack != null) {
-                return boneTrack.getTranslations();
-            }
-            return spatialTrack.getTranslations();
-        }
-
-        /**
-         * Set the translations, rotations and scales for this bone track
-         * 
-         * @param times
-         *            a float array with the time of each frame
-         * @param translations
-         *            the translation of the bone for each frame
-         * @param rotations
-         *            the rotation of the bone for each frame
-         * @param scales
-         *            the scale of the bone for each frame
-         */
-        public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) {
-            if (boneTrack != null) {
-                boneTrack.setKeyframes(times, translations, rotations, scales);
-            } else {
-                spatialTrack.setKeyframes(times, translations, rotations, scales);
-            }
-        }
-
-        public void write(JmeExporter ex) throws IOException {
-            // no need to implement this one (the TrackWrapper is used internally and never serialized)
-        }
-
-        public void read(JmeImporter im) throws IOException {
-            // no need to implement this one (the TrackWrapper is used internally and never serialized)
-        }
-
-        public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) {
-            if (boneTrack != null) {
-                boneTrack.setTime(time, weight, control, channel, vars);
-            } else {
-                spatialTrack.setTime(time, weight, control, channel, vars);
-            }
-        }
+    public boolean isImplemented() {
+        return true;
+    }
 
-        public float getLength() {
-            return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength();
-        }
+    public abstract String getConstraintTypeName();
 
-        @Override
-        public TrackWrapper clone() {
-            if (boneTrack != null) {
-                return new TrackWrapper(boneTrack.clone());
-            }
-            return new TrackWrapper(spatialTrack.clone());
-        }
-    }
+    public abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
 }

+ 14 - 2
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java

@@ -1,5 +1,6 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
+import com.jme3.animation.Bone;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
@@ -7,6 +8,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Dist limit' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
@@ -17,14 +19,19 @@ import com.jme3.scene.plugins.blender.file.Structure;
     protected int            mode;
     protected float          dist;
 
-    public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
         mode = ((Number) constraintData.getFieldValue("mode")).intValue();
         dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
     }
 
     @Override
     public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
+        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) {
+            // distance limit does not work on bones who have parent
+            return;
+        }
+
         Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
         float currentDistance = v.length();
 
@@ -56,4 +63,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
                 throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
         }
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Limit distance";
+    }
 }

+ 9 - 8
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java

@@ -40,7 +40,7 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
 import com.jme3.scene.plugins.blender.file.Structure;
 
 public class ConstraintDefinitionFactory {
-    private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
+    private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES      = new HashMap<String, Class<? extends ConstraintDefinition>>();
     static {
         CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
         CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
@@ -51,8 +51,8 @@ public class ConstraintDefinitionFactory {
         CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class);
         CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
     }
-    
-    private static final Map<String, String> UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>();
+
+    private static final Map<String, String>                                UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>();
     static {
         UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action");
         UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of");
@@ -84,22 +84,23 @@ public class ConstraintDefinitionFactory {
      * This method creates the constraint instance.
      * 
      * @param constraintStructure
-     *            the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created.
+     *            the constraint's structure (bConstraint clss in blender 2.49).
+     *            If the value is null the NullConstraint is created.
      * @param blenderContext
      *            the blender context
      * @throws BlenderFileException
      *             this exception is thrown when the blender file is somehow
      *             corrupted
      */
-    public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException {
+    public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException {
         if (constraintStructure == null) {
-            return new ConstraintDefinitionNull(null, blenderContext);
+            return new ConstraintDefinitionNull(null, ownerOMA, blenderContext);
         }
         String constraintClassName = constraintStructure.getType();
         Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
         if (constraintDefinitionClass != null) {
             try {
-                return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext);
+                return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
             } catch (IllegalArgumentException e) {
                 throw new BlenderFileException(e.getLocalizedMessage(), e);
             } catch (SecurityException e) {
@@ -113,7 +114,7 @@ public class ConstraintDefinitionFactory {
             }
         } else {
             String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
-            if(constraintName != null) {
+            if (constraintName != null) {
                 return new UnsupportedConstraintDefinition(constraintName);
             } else {
                 throw new BlenderFileException("Unknown constraint type: " + constraintClassName);

+ 22 - 5
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java

@@ -1,5 +1,6 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
+import com.jme3.animation.Bone;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
@@ -7,27 +8,32 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Loc like' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
     private static final int LOCLIKE_X        = 0x01;
     private static final int LOCLIKE_Y        = 0x02;
     private static final int LOCLIKE_Z        = 0x04;
-    // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender
+    // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in
+    // blender
     private static final int LOCLIKE_X_INVERT = 0x10;
     private static final int LOCLIKE_Y_INVERT = 0x20;
     private static final int LOCLIKE_Z_INVERT = 0x40;
     private static final int LOCLIKE_OFFSET   = 0x80;
 
-    public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
         if (blenderContext.getBlenderKey().isFixUpAxis()) {
             // swapping Y and X limits flag in the bitwise flag
             int y = flag & LOCLIKE_Y;
             int invY = flag & LOCLIKE_Y_INVERT;
             int z = flag & LOCLIKE_Z;
             int invZ = flag & LOCLIKE_Z_INVERT;
-            flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the other flags to swap them
+            flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the
+                                                                  // other flags
+                                                                  // to swap
+                                                                  // them
             flag |= y << 1;
             flag |= invY << 1;
             flag |= z >> 1;
@@ -37,12 +43,18 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     @Override
     public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
+        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) {
+            // cannot copy the location of a bone attached to its parent,
+            // Blender forbids that
+            return;
+        }
         Vector3f ownerLocation = ownerTransform.getTranslation();
         Vector3f targetLocation = targetTransform.getTranslation();
 
         Vector3f startLocation = ownerTransform.getTranslation().clone();
         Vector3f offset = Vector3f.ZERO;
-        if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location
+        if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to
+                                           // the copied location
             offset = startLocation;
         }
 
@@ -71,4 +83,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
             ownerLocation.addLocal(startLocation);
         }
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Copy location";
+    }
 }

+ 16 - 3
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java

@@ -1,5 +1,6 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
+import com.jme3.animation.Bone;
 import com.jme3.math.Transform;
 import com.jme3.math.Vector3f;
 import com.jme3.scene.plugins.blender.BlenderContext;
@@ -7,6 +8,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Loc limit' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
@@ -19,8 +21,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     protected float[][]      limits     = new float[3][2];
 
-    public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
         if (blenderContext.getBlenderKey().isFixUpAxis()) {
             limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
             limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
@@ -34,7 +36,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
             int ymax = flag & LIMIT_YMAX;
             int zmin = flag & LIMIT_ZMIN;
             int zmax = flag & LIMIT_ZMAX;
-            flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
+            flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
+                                            // them
             flag |= ymin << 2;
             flag |= ymax << 2;
             flag |= zmin >> 2;
@@ -51,6 +54,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     @Override
     public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
+        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) {
+            // location limit does not work on bones who have parent
+            return;
+        }
+
         Vector3f translation = ownerTransform.getTranslation();
 
         if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
@@ -72,4 +80,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
             translation.z -= (translation.z - limits[2][1]) * influence;
         }
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Limit location";
+    }
 }

+ 8 - 2
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java

@@ -6,16 +6,22 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Null' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionNull extends ConstraintDefinition {
 
-    public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
     }
 
     @Override
     public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
         // null constraint does nothing so no need to implement this one
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Null";
+    }
 }

+ 10 - 4
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java

@@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Rot like' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionRotLike extends ConstraintDefinition {
@@ -21,8 +22,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
     private transient float[] ownerAngles      = new float[3];
     private transient float[] targetAngles     = new float[3];
 
-    public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
     }
 
     @Override
@@ -33,7 +34,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
         Quaternion startRotation = ownerRotation.clone();
         Quaternion offset = Quaternion.IDENTITY;
-        if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to the copied rotation
+        if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to
+                                           // the copied rotation
             offset = startRotation;
         }
 
@@ -58,10 +60,14 @@ import com.jme3.scene.plugins.blender.file.Structure;
         ownerRotation.fromAngles(ownerAngles).multLocal(offset);
 
         if (influence < 1.0f) {
-
             // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
             // ownerLocation.addLocal(startLocation);
             // TODO
         }
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Copy rotation";
+    }
 }

+ 35 - 6
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java

@@ -18,13 +18,13 @@ import com.jme3.scene.plugins.blender.file.Structure;
     private transient float[][] limits     = new float[3][2];
     private transient float[]   angles     = new float[3];
 
-    public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
-        if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null */) {// FIXME: !!!!!!!!
+    public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
+        if (blenderContext.getBlenderKey().isFixUpAxis()) {
             limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
             limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
-            limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue();
-            limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue();
+            limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
+            limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
             limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
             limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
 
@@ -45,15 +45,38 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
         // until blender 2.49 the rotations values were stored in degrees
         if (blenderContext.getBlenderVersion() <= 249) {
-            for (int i = 0; i < limits.length; ++i) {
+            for (int i = 0; i < 3; ++i) {
                 limits[i][0] *= FastMath.DEG_TO_RAD;
                 limits[i][1] *= FastMath.DEG_TO_RAD;
             }
         }
+
+        // make sure that the limits are always in range [0, 2PI)
+        // TODO: left it here because it is essential to make sure all cases
+        // work poperly
+        // but will do it a little bit later ;)
+        /*
+         * for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int
+         * multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if
+         * (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor +
+         * 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make
+         * sure the lower limit is not greater than the upper one
+         * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
+         * limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
+         */
     }
 
     @Override
     public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
+        ownerTransform.getRotation().toAngles(angles);
+        // make sure that the rotations are always in range [0, 2PI)
+        // TODO: same comment as in constructor
+        /*
+         * for (int i = 0; i < 3; ++i) { int multFactor =
+         * (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) {
+         * angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i]
+         * -= FastMath.TWO_PI * multFactor; } }
+         */
         if ((flag & LIMIT_XROT) != 0) {
             float difference = 0.0f;
             if (angles[0] < limits[0][0]) {
@@ -81,5 +104,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
             }
             angles[2] -= difference;
         }
+        ownerTransform.getRotation().fromAngles(angles);
+    }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Limit rotation";
     }
 }

+ 12 - 4
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java

@@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Size like' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition {
@@ -15,13 +16,14 @@ import com.jme3.scene.plugins.blender.file.Structure;
     private static final int SIZELIKE_Z     = 0x04;
     private static final int LOCLIKE_OFFSET = 0x80;
 
-    public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
         if (blenderContext.getBlenderKey().isFixUpAxis()) {
             // swapping Y and X limits flag in the bitwise flag
             int y = flag & SIZELIKE_Y;
             int z = flag & SIZELIKE_Z;
-            flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap them
+            flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap
+                                                // them
             flag |= y << 1;
             flag |= z >> 1;
         }
@@ -33,7 +35,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
         Vector3f targetScale = targetTransform.getScale();
 
         Vector3f offset = Vector3f.ZERO;
-        if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the copied scale
+        if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the
+                                           // copied scale
             offset = ownerScale.clone();
         }
 
@@ -48,4 +51,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
         }
         ownerScale.addLocal(offset);
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Copy scale";
+    }
 }

+ 10 - 3
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java

@@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
 /**
  * This class represents 'Size limit' constraint type in blender.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition {
@@ -19,8 +20,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     protected transient float[][] limits     = new float[3][2];
 
-    public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) {
-        super(constraintData, blenderContext);
+    public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
+        super(constraintData, ownerOMA, blenderContext);
         if (blenderContext.getBlenderKey().isFixUpAxis()) {
             limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
             limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
@@ -34,7 +35,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
             int ymax = flag & LIMIT_YMAX;
             int zmin = flag & LIMIT_ZMIN;
             int zmax = flag & LIMIT_ZMAX;
-            flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them
+            flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap
+                                            // them
             flag |= ymin << 2;
             flag |= ymax << 2;
             flag |= zmin >> 2;
@@ -72,4 +74,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
             scale.z -= (scale.z - limits[2][1]) * influence;
         }
     }
+
+    @Override
+    public String getConstraintTypeName() {
+        return "Limit scale";
+    }
 }

+ 21 - 16
engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java

@@ -1,28 +1,33 @@
 package com.jme3.scene.plugins.blender.constraints.definitions;
 
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
 import com.jme3.math.Transform;
 
 /**
- * This class represents a constraint that is defined by blender but not supported by either importer
- * ot jme. It only wirtes down a warning when baking is called.
+ * This class represents a constraint that is defined by blender but not
+ * supported by either importer ot jme. It only wirtes down a warning when
+ * baking is called.
  * 
  * @author Marcin Roguski (Kaelthas)
  */
-/* package */ class UnsupportedConstraintDefinition extends ConstraintDefinition {
-    private static final Logger LOGGER = Logger.getLogger(UnsupportedConstraintDefinition.class.getName());
-    
-    private String name;
-    
-    public UnsupportedConstraintDefinition(String name) {
-        super(null, null);
-        this.name = name;
+/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition {
+    private String typeName;
+
+    public UnsupportedConstraintDefinition(String typeName) {
+        super(null, null, null);
+        this.typeName = typeName;
+    }
+
+    @Override
+    public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
     }
-    
+
+    @Override
+    public boolean isImplemented() {
+        return false;
+    }
+
     @Override
-    protected void bake(Transform ownerTransform, Transform targetTransform, float influence) {
-        LOGGER.log(Level.WARNING, "'{0}' constraint NOT implemented!", name);
+    public String getConstraintTypeName() {
+        return typeName;
     }
 }

+ 60 - 17
engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java

@@ -44,7 +44,8 @@ import com.jme3.util.BufferUtils;
  */
 /* 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 limitation
+    private static final int    MAXIMUM_WEIGHTS_PER_VERTEX = 4;                                                 // JME
+                                                                                                                 // limitation
 
     private Skeleton            skeleton;
     private Structure           objectStructure;
@@ -71,7 +72,9 @@ import com.jme3.util.BufferUtils;
      */
     public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
         Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
-        Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
+        Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert
+                                                                        // =
+                                                                        // DeformVERTices
 
         // if pDvert==null then there are not vertex groups and no need to load
         // skeleton (untill bone envelopes are supported)
@@ -109,7 +112,8 @@ import com.jme3.util.BufferUtils;
                 // read animations
                 ArrayList<Animation> animations = new ArrayList<Animation>();
                 List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00));
-                if (actionHeaders != null) {// it may happen that the model has armature with no actions
+                if (actionHeaders != null) {// it may happen that the model has
+                                            // armature with no actions
                     for (FileBlockHeader header : actionHeaders) {
                         Structure actionStructure = header.getStructure(blenderContext);
                         String actionName = actionStructure.getName();
@@ -202,7 +206,8 @@ import com.jme3.util.BufferUtils;
                     if (bindPoseBuffer != null) {
                         mesh.setBuffer(bindPoseBuffer);
                     }
-                    // change the usage type of vertex and normal buffers from Static to Stream
+                    // 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);
                 }
@@ -226,7 +231,9 @@ import com.jme3.util.BufferUtils;
         }
         node.addControl(control);
         node.addControl(new SkeletonControl(animData.skeleton));
-        
+
+        blenderContext.setNodeForSkeleton(skeleton, node);
+
         return node;
     }
 
@@ -282,23 +289,50 @@ import com.jme3.util.BufferUtils;
      *             this exception is thrown when the blend file structure is
      *             somehow invalid or corrupted
      */
-    private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) throws BlenderFileException {
+    private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)
+            throws BlenderFileException {
         bonesGroups[0] = 0;
-        Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices
+        Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert
+                                                                        // =
+                                                                        // DeformVERTices
         FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
         ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
 
         if (pDvert.isNotNull()) {// assigning weights and bone indices
             boolean warnAboutTooManyVertexWeights = false;
-            List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender)
+            List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size()
+                                                                                       // ==
+                                                                                       // verticesAmount
+                                                                                       // (one
+                                                                                       // dvert
+                                                                                       // per
+                                                                                       // vertex
+                                                                                       // in
+                                                                                       // blender)
             int vertexIndex = 0;
             // use tree map to sort weights from the lowest to the highest ones
             TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>();
 
             for (Structure dvert : dverts) {
-                List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here
+                List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we
+                                                                                                   // fetch
+                                                                                                   // the
+                                                                                                   // referenced
+                                                                                                   // vertices
+                                                                                                   // here
                 if (vertexIndices != null) {
-                    int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME)
+                    int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total
+                                                                                           // amount
+                                                                                           // of
+                                                                                           // weights
+                                                                                           // assignet
+                                                                                           // to
+                                                                                           // the
+                                                                                           // vertex
+                                                                                           // (max.
+                                                                                           // 4
+                                                                                           // in
+                                                                                           // JME)
                     Pointer pDW = (Pointer) dvert.getFieldValue("dw");
                     if (totweight > 0 && groupToBoneIndexMap != null) {
                         weightToIndexMap.clear();
@@ -307,25 +341,32 @@ import com.jme3.util.BufferUtils;
                         for (Structure deformWeight : dw) {
                             Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
                             float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue();
-                            // boneIndex == null: it here means that we came accross group that has no bone attached to, so simply ignore it
-                            // if weight == 0 and weightIndex == 0 then ignore the weight (do not set weight = 0 as a first weight)
+                            // boneIndex == null: it here means that we came
+                            // accross group that has no bone attached to, so
+                            // simply ignore it
+                            // if weight == 0 and weightIndex == 0 then ignore
+                            // the weight (do not set weight = 0 as a first
+                            // weight)
                             if (boneIndex != null && (weight > 0.0f || weightIndex > 0)) {
                                 if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) {
                                     if (weight == 0.0f) {
                                         boneIndex = Integer.valueOf(0);
                                     }
-                                    // we apply the weight to all referenced vertices
+                                    // we apply the weight to all referenced
+                                    // vertices
                                     for (Integer index : vertexIndices) {
                                         weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
                                         indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
                                     }
                                     weightToIndexMap.put(weight, weightIndex);
                                     bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1);
-                                } else if (weight > 0) {// if weight is zero the simply ignore it
+                                } else if (weight > 0) {// if weight is zero the
+                                                        // simply ignore it
                                     warnAboutTooManyVertexWeights = true;
                                     Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry();
                                     if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) {
-                                        // we apply the weight to all referenced vertices
+                                        // we apply the weight to all referenced
+                                        // vertices
                                         for (Integer index : vertexIndices) {
                                             weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight);
                                             indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue());
@@ -338,7 +379,8 @@ import com.jme3.util.BufferUtils;
                             }
                         }
                     } else {
-                        // 0.0 weight indicates, do not transform this vertex, but keep it in bind pose.
+                        // 0.0 weight indicates, do not transform this vertex,
+                        // but keep it in bind pose.
                         for (Integer index : vertexIndices) {
                             weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f);
                             indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
@@ -354,7 +396,8 @@ import com.jme3.util.BufferUtils;
         } else {
             // always bind all vertices to 0-indexed bone
             // this bone makes the model look normally if vertices have no bone
-            // assigned and it is used in object animation, so if we come accross object
+            // assigned and it is used in object animation, so if we come
+            // accross object
             // animation we can use the 0-indexed bone for this
             for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
                 // we apply the weight to all referenced vertices

+ 46 - 30
engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@@ -37,6 +37,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.jme3.asset.BlenderKey.FeaturesToLoad;
+import com.jme3.math.FastMath;
 import com.jme3.math.Matrix4f;
 import com.jme3.math.Quaternion;
 import com.jme3.math.Transform;
@@ -65,6 +66,7 @@ import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
 
 /**
  * A class that is used in object calculations.
+ * 
  * @author Marcin Roguski (Kaelthas)
  */
 public class ObjectHelper extends AbstractBlenderHelper {
@@ -83,8 +85,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
     protected static final int  OBJECT_TYPE_ARMATURE = 25;
 
     /**
-     * This constructor parses the given blender version and stores the result. Some functionalities may differ in
-     * different blender versions.
+     * This constructor parses the given blender version and stores the result.
+     * Some functionalities may differ in different blender versions.
+     * 
      * @param blenderVersion
      *            the version read from the blend file
      * @param fixUpAxis
@@ -95,7 +98,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
     }
 
     /**
-     * This method reads the given structure and createn an object that represents the data.
+     * This method reads the given structure and createn an object that
+     * represents the data.
+     * 
      * @param objectStructure
      *            the object's structure
      * @param blenderContext
@@ -205,7 +210,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
                     }
                     break;
                 case OBJECT_TYPE_ARMATURE:
-                    // need to create an empty node to properly create parent-children relationships between nodes
+                    // need to create an empty node to properly create
+                    // parent-children relationships between nodes
                     Node armature = new Node(name);
                     armature.setLocalTransform(t);
                     armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE);
@@ -223,9 +229,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
         }
 
         if (result != null) {
-            result.updateModelBound();// I prefer do compute bounding box here than read it from the file
+            result.updateModelBound();// I prefer do compute bounding box here
+                                      // than read it from the file
 
             blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
+            // TODO: this data is only to help during loading, shall I remove it
+            // after all the loading is done ???
+            result.setUserData("oma", objectStructure.getOldMemoryAddress());
 
             // applying modifiers
             LOGGER.log(Level.FINE, "Reading and applying object's modifiers.");
@@ -242,7 +252,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
             // reading custom properties
             if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
                 Properties properties = this.loadProperties(objectStructure, blenderContext);
-                // the loaded property is a group property, so we need to get each value and set it to Spatial
+                // the loaded property is a group property, so we need to get
+                // each value and set it to Spatial
                 if (result instanceof Spatial && properties != null && properties.getValue() != null) {
                     this.applyProperties(result, properties);
                 }
@@ -250,9 +261,11 @@ public class ObjectHelper extends AbstractBlenderHelper {
         }
         return result;
     }
-    
+
     /**
-     * This method calculates local transformation for the object. Parentage is taken under consideration.
+     * This method calculates local transformation for the object. Parentage is
+     * taken under consideration.
+     * 
      * @param objectStructure
      *            the object's structure
      * @return objects transformation relative to its parent
@@ -277,16 +290,16 @@ public class ObjectHelper extends AbstractBlenderHelper {
 
         Vector3f translation = localMatrix.toTranslationVector();
         Quaternion rotation = localMatrix.toRotationQuat();
-        Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
+        Vector3f scale = parentInv.toScaleVector().multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue());
 
         if (fixUpAxis) {
             float y = translation.y;
             translation.y = translation.z;
-            translation.z = -y;
+            translation.z = y == 0 ? 0 : -y;
 
             y = rotation.getY();
             float z = rotation.getZ();
-            rotation.set(rotation.getX(), z, -y, rotation.getW());
+            rotation.set(rotation.getX(), z, y == 0 ? 0 : -y, rotation.getW());
 
             y = scale.y;
             scale.y = scale.z;
@@ -301,7 +314,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
 
     /**
      * This method returns the matrix of a given name for the given structure.
-     * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file.
+     * The matrix is NOT transformed if Y axis is up - the raw data is loaded
+     * from the blender file.
+     * 
      * @param structure
      *            the structure with matrix data
      * @param matrixName
@@ -315,6 +330,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
     /**
      * This method returns the matrix of a given name for the given structure.
      * It takes up axis into consideration.
+     * 
      * @param structure
      *            the structure with matrix data
      * @param matrixName
@@ -325,7 +341,11 @@ public class ObjectHelper extends AbstractBlenderHelper {
     public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
         Matrix4f result = new Matrix4f();
         DynamicArray<Number> obmat = (DynamicArray<Number>) structure.getFieldValue(matrixName);
-        int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the matrix must be square
+        int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the
+                                                                               // matrix
+                                                                               // must
+                                                                               // be
+                                                                               // square
         for (int i = 0; i < rowAndColumnSize; ++i) {
             for (int j = 0; j < rowAndColumnSize; ++j) {
                 result.set(i, j, obmat.get(j, i).floatValue());
@@ -334,15 +354,15 @@ public class ObjectHelper extends AbstractBlenderHelper {
         if (applyFixUpAxis && fixUpAxis) {
             Vector3f translation = result.toTranslationVector();
             Quaternion rotation = result.toRotationQuat();
-            Vector3f scale = this.getScale(result);
+            Vector3f scale = result.toScaleVector();
 
             float y = translation.y;
             translation.y = translation.z;
-            translation.z = -y;
+            translation.z = y == 0 ? 0 : -y;
 
             y = rotation.getY();
             float z = rotation.getZ();
-            rotation.set(rotation.getX(), z, -y, rotation.getW());
+            rotation.set(rotation.getX(), z, y == 0 ? 0 : -y, rotation.getW());
 
             y = scale.y;
             scale.y = scale.z;
@@ -353,21 +373,17 @@ public class ObjectHelper extends AbstractBlenderHelper {
             result.setRotationQuaternion(rotation);
             result.setScale(scale);
         }
-        return result;
-    }
 
-    /**
-     * This method returns the scale from the given matrix.
-     * 
-     * @param matrix
-     *            the transformation matrix
-     * @return the scale from the given matrix
-     */
-    public Vector3f getScale(Matrix4f matrix) {
-        float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20);
-        float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21);
-        float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
-        return new Vector3f(scaleX, scaleY, scaleZ);
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 4; ++j) {
+                float value = result.get(i, j);
+                if (Math.abs(value) <= FastMath.FLT_EPSILON) {
+                    result.set(i, j, 0);
+                }
+            }
+        }
+
+        return result;
     }
 
     @Override