Explorar el Código

Bugfix: sorting constraint computation in the proper order which should
decrease the amount of unwanted artifacts appearing in some models
during animations.

jmekaelthas hace 10 años
padre
commit
65c3ff668c
Se han modificado 20 ficheros con 421 adiciones y 69 borrados
  1. 26 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java
  2. 16 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  3. 23 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java
  4. 156 14
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  5. 6 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java
  6. 6 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  7. 21 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java
  8. 21 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  9. 10 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java
  10. 20 6
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java
  11. 19 11
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java
  12. 19 8
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java
  13. 12 1
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMaintainVolume.java
  14. 6 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java
  15. 10 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java
  16. 13 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java
  17. 11 4
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java
  18. 13 3
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java
  19. 7 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java
  20. 6 0
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java

+ 26 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java

@@ -67,9 +67,9 @@ public class BoneContext {
     private float                length;
     /** The bone's deform envelope. */
     private BoneEnvelope         boneEnvelope;
-    
+
     // The below data is used only for IK constraint computations.
-    
+
     /** The bone's stretch value. */
     private float                ikStretch;
     /** Bone's rotation minimum values. */
@@ -366,6 +366,30 @@ public class BoneContext {
         return (flag & flagMask) != 0;
     }
 
+    /**
+     * @return the root bone context of this bone context
+     */
+    public BoneContext getRoot() {
+        BoneContext result = this;
+        while (result.parent != null) {
+            result = result.parent;
+        }
+        return result;
+    }
+
+    /**
+     * @return a number of bones from this bone to its root
+     */
+    public int getDistanceFromRoot() {
+        int result = 0;
+        BoneContext boneContext = this;
+        while (boneContext.parent != null) {
+            boneContext = boneContext.parent;
+            ++result;
+        }
+        return result;
+    }
+
     @Override
     public String toString() {
         return "BoneContext: " + boneName;

+ 16 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java

@@ -62,7 +62,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
                 }
             }
         }
-        return true;
+        return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
     }
 
     @Override
@@ -70,4 +70,19 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
         super.apply(frame);
         blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms();
     }
+    
+    @Override
+    public Long getTargetOMA() {
+        if(targetOMA != null && subtargetName != null && !subtargetName.trim().isEmpty()) {
+            Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE);
+            if(nodeTarget != null) {
+                if(blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) {
+                    BoneContext boneContext = blenderContext.getBoneByName(targetOMA, subtargetName);
+                    return boneContext != null ? boneContext.getBoneOma() : 0L;
+                }
+                return targetOMA;
+            }
+        }
+        return 0L;
+    }
 }

+ 23 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java

@@ -116,14 +116,31 @@ public abstract class Constraint {
      */
     public abstract boolean validate();
 
+    /**
+     * @return the OMA of the target or 0 if no target is specified for the constraint
+     */
+    public abstract Long getTargetOMA();
+
     /**
      * Applies the constraint to owner (and in some cases can alter other bones of the skeleton).
      * @param frame
      *            the frame of the animation
      */
     public void apply(int frame) {
+        if (LOGGER.isLoggable(Level.FINEST)) {
+            LOGGER.log(Level.FINEST, "Applying constraint: {0} for frame {1}", new Object[] { name, frame });
+        }
         Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
-        constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float)ipo.calculateValue(frame));
+        constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float) ipo.calculateValue(frame));
+    }
+
+    /**
+     * @return determines if the definition of the constraint will change the bone in any way; in most cases
+     *         it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
+     *         computing to improve the computation speed and lower the computations complexity
+     */
+    public boolean isTrackToBeChanged() {
+        return constraintDefinition == null ? false : constraintDefinition.isTrackToBeChanged();
     }
 
     @Override
@@ -163,4 +180,9 @@ public abstract class Constraint {
         }
         return true;
     }
+
+    @Override
+    public String toString() {
+        return "Constraint(name = " + name + ", def = " + constraintDefinition + ")";
+    }
 }

+ 156 - 14
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java

@@ -1,6 +1,8 @@
 package com.jme3.scene.plugins.blender.constraints;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -132,7 +134,7 @@ public class SimulationNode {
                 }
             }
             Node node = blenderContext.getControlledNode(skeleton);
-            Long animatedNodeOMA = ((Number)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
+            Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
             animations = blenderContext.getAnimations(animatedNodeOMA);
         } else {
             animations = blenderContext.getAnimations(featureOMA);
@@ -206,7 +208,7 @@ public class SimulationNode {
                     int maxFrame = (int) animationTimeBoundaries[0];
                     float maxTime = animationTimeBoundaries[1];
 
-                    VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime);
+                    VirtualTrack vTrack = new VirtualTrack(spatial.getName(), maxFrame, maxTime);
                     for (Track track : animation.getTracks()) {
                         for (int frame = 0; frame < maxFrame; ++frame) {
                             spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]);
@@ -252,6 +254,8 @@ public class SimulationNode {
             if (animations != null) {
                 TempVars vars = TempVars.get();
                 AnimChannel animChannel = animControl.createChannel();
+
+                List<Bone> bonesWithConstraints = this.collectBonesWithConstraints(skeleton);
                 for (Animation animation : animations) {
                     float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation);
                     int maxFrame = (int) animationTimeBoundaries[0];
@@ -271,11 +275,8 @@ public class SimulationNode {
                         }
 
                         // ... and then apply constraints from the root bone to the last child ...
-                        for (Bone rootBone : skeleton.getRoots()) {
-                            if (skeleton.getBoneIndex(rootBone) > 0) {
-                                // ommit the 0 - indexed root bone as it is the bone added by importer
-                                this.applyConstraints(rootBone, alteredOmas, frame);
-                            }
+                        for (Bone rootBone : bonesWithConstraints) {
+                            this.applyConstraints(rootBone, alteredOmas, frame);
                         }
 
                         // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ...
@@ -283,7 +284,7 @@ public class SimulationNode {
                             BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
                             int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
                             if (!tracks.containsKey(boneIndex)) {
-                                tracks.put(boneIndex, new VirtualTrack(maxFrame, maxTime));
+                                tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
                             }
                         }
                         alteredOmas.clear();
@@ -292,12 +293,12 @@ public class SimulationNode {
                         for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
                             Bone bone = skeleton.getBone(trackEntry.getKey());
                             Transform startTransform = boneStartTransforms.get(bone);
-                            
+
                             // track contains differences between the frame position and bind positions of bones/spatials
                             Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation());
                             Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal();
                             Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale());
-                            
+
                             trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
                         }
                     }
@@ -349,9 +350,6 @@ public class SimulationNode {
                 alteredOmas.add(boneContext.getBoneOma());
             }
         }
-        for (Bone child : bone.getChildren()) {
-            this.applyConstraints(child, alteredOmas, frame);
-        }
     }
 
     /**
@@ -366,6 +364,150 @@ public class SimulationNode {
         }
     }
 
+    /**
+     * Collects the bones that will take part in constraint computations.
+     * The result will not include bones whose constraints will not change them or are invalid.
+     * The bones are sorted so that the constraint applying is done in the proper order.
+     * @param skeleton
+     *            the simulated skeleton
+     * @return a list of bones that will take part in constraints computations
+     */
+    private List<Bone> collectBonesWithConstraints(Skeleton skeleton) {
+        Map<BoneContext, List<Constraint>> bonesWithConstraints = new HashMap<BoneContext, List<Constraint>>();
+        for (int i = 1; i < skeleton.getBoneCount(); ++i) {// ommit the 0 - indexed root bone as it is the bone added by importer
+            Bone bone = skeleton.getBone(i);
+            BoneContext boneContext = blenderContext.getBoneContext(bone);
+            List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
+            if (constraints != null && constraints.size() > 0) {
+                bonesWithConstraints.put(boneContext, constraints);
+            }
+        }
+
+        // first sort out constraints that are not implemented or invalid or will not affect the bone's tracks
+        List<BoneContext> bonesToRemove = new ArrayList<BoneContext>(bonesWithConstraints.size());
+        for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
+            List<Constraint> validConstraints = new ArrayList<Constraint>(entry.getValue().size());
+            for (Constraint constraint : entry.getValue()) {// TODO: sprawdziæ czy wprowadza jakiekolwiek zmiany
+                if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) {
+                    validConstraints.add(constraint);
+                }
+            }
+            if (validConstraints.size() > 0) {
+                entry.setValue(validConstraints);
+            } else {
+                bonesToRemove.add(entry.getKey());
+            }
+        }
+        for (BoneContext boneContext : bonesToRemove) {
+            bonesWithConstraints.remove(boneContext);
+        }
+
+        List<BoneContext> bonesConstrainedWithoutTarget = new ArrayList<BoneContext>();
+        Set<Long> remainedOMAS = new HashSet<Long>();
+        // later move all bones with not dependant constraints to the front
+        bonesToRemove.clear();
+        for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
+            boolean hasDependantConstraints = false;
+            for (Constraint constraint : entry.getValue()) {
+                if (constraint.targetOMA != null) {
+                    hasDependantConstraints = true;
+                    break;
+                }
+            }
+
+            if (!hasDependantConstraints) {
+                bonesConstrainedWithoutTarget.add(entry.getKey());
+                bonesToRemove.add(entry.getKey());
+            } else {
+                remainedOMAS.add(entry.getKey().getBoneOma());
+            }
+        }
+        for (BoneContext boneContext : bonesToRemove) {
+            bonesWithConstraints.remove(boneContext);
+        }
+
+        this.sortBonesByChain(bonesConstrainedWithoutTarget);
+
+        // another step is to add those bones whose constraints depend only on bones already added to the result or to those
+        // that are not included neither in the result nor in the remaining map
+        // do this as long as bones are being moved to the result and the 'bonesWithConstraints' is not empty
+        List<BoneContext> bonesConstrainedWithTarget = new ArrayList<BoneContext>();
+        do {
+            bonesToRemove.clear();
+            for (Entry<BoneContext, List<Constraint>> entry : bonesWithConstraints.entrySet()) {
+                boolean unconstrainedBone = true;
+                for (Constraint constraint : entry.getValue()) {
+                    if (remainedOMAS.contains(constraint.getTargetOMA())) {
+                        unconstrainedBone = false;
+                        break;
+                    }
+                }
+                if (unconstrainedBone) {
+                    bonesToRemove.add(entry.getKey());
+                    bonesConstrainedWithTarget.add(entry.getKey());
+                }
+            }
+
+            for (BoneContext boneContext : bonesToRemove) {
+                bonesWithConstraints.remove(boneContext);
+                remainedOMAS.remove(boneContext.getBoneOma());
+            }
+        } while (bonesWithConstraints.size() > 0 && bonesToRemove.size() > 0);
+        this.sortBonesByChain(bonesConstrainedWithoutTarget);
+
+        // prepare the result
+        List<Bone> result = new ArrayList<Bone>();
+        for (BoneContext boneContext : bonesConstrainedWithoutTarget) {
+            result.add(boneContext.getBone());
+        }
+        for (BoneContext boneContext : bonesConstrainedWithTarget) {
+            result.add(boneContext.getBone());
+        }
+
+        // in the end prepare the mapping between bone OMA
+        if (bonesWithConstraints.size() > 0) {
+            LOGGER.warning("Some bones have loops in their constraints' definitions. The result might not be properly computed!");
+            for (BoneContext boneContext : bonesWithConstraints.keySet()) {
+                result.add(boneContext.getBone());
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * The method sorts the given bones from root to top.
+     * If the list contains bones from different branches then those branches will be listed
+     * one after another - which means that bones will be grouped by branches they belong to.
+     * @param bones
+     *            a list of bones
+     */
+    private void sortBonesByChain(List<BoneContext> bones) {
+        Map<BoneContext, List<BoneContext>> branches = new HashMap<BoneContext, List<BoneContext>>();
+
+        for (BoneContext bone : bones) {
+            BoneContext root = bone.getRoot();
+            List<BoneContext> list = branches.get(root);
+            if (list == null) {
+                list = new ArrayList<BoneContext>();
+                branches.put(root, list);
+            }
+            list.add(bone);
+        }
+
+        // sort the bones in each branch from root to leaf
+        bones.clear();
+        for (Entry<BoneContext, List<BoneContext>> entry : branches.entrySet()) {
+            Collections.sort(entry.getValue(), new Comparator<BoneContext>() {
+                @Override
+                public int compare(BoneContext o1, BoneContext o2) {
+                    return o1.getDistanceFromRoot() - o2.getDistanceFromRoot();
+                }
+            });
+            bones.addAll(entry.getValue());
+        }
+    }
+
     /**
      * Computes the maximum frame and time for the animation. Different tracks
      * can have different lengths so here the maximum one is being found.
@@ -376,7 +518,7 @@ public class SimulationNode {
      */
     private float[] computeAnimationTimeBoundaries(Animation animation) {
         int maxFrame = Integer.MIN_VALUE;
-        float maxTime = Float.MIN_VALUE;
+        float maxTime = -Float.MAX_VALUE;
         for (Track track : animation.getTracks()) {
             if (track instanceof BoneTrack) {
                 maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length);

+ 6 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java

@@ -32,4 +32,10 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public void apply(int frame) {
         LOGGER.warning("Applying constraints to skeleton is not supported.");
     }
+    
+    @Override
+    public Long getTargetOMA() {
+        LOGGER.warning("Constraints for skeleton are not supported.");
+        return null;
+    }
 }

+ 6 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java

@@ -22,6 +22,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
         if (targetOMA != null) {
             return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null;
         }
-        return true;
+        return constraintDefinition == null ? true : constraintDefinition.isTargetRequired();
+    }
+    
+    @Override
+    public Long getTargetOMA() {
+        return targetOMA;
     }
 }

+ 21 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java

@@ -16,6 +16,8 @@ import com.jme3.math.Vector3f;
  * @author Marcin Roguski (Kaelthas)
  */
 /* package */class VirtualTrack {
+    /** The name of the track (for debugging purposes). */
+    private String               name;
     /** The last frame for the track. */
     public int                   maxFrame;
     /** The max time for the track. */
@@ -35,7 +37,8 @@ import com.jme3.math.Vector3f;
      * @param maxTime
      *            the max time for the track
      */
-    public VirtualTrack(int maxFrame, float maxTime) {
+    public VirtualTrack(String name, int maxFrame, float maxTime) {
+        this.name = name;
         this.maxFrame = maxFrame;
         this.maxTime = maxTime;
     }
@@ -101,7 +104,7 @@ import com.jme3.math.Vector3f;
      */
     private float[] createTimes() {
         float[] times = new float[maxFrame];
-        float dT = maxTime / (float) maxFrame;
+        float dT = maxTime / maxFrame;
         float t = 0;
         for (int i = 0; i < maxFrame; ++i) {
             times[i] = t;
@@ -143,4 +146,20 @@ import com.jme3.math.Vector3f;
             list.add(element);
         }
     }
+
+    @Override
+    public String toString() {
+        StringBuilder result = new StringBuilder(2048);
+        result.append("TRACK: ").append(name).append('\n');
+        if (translations != null && translations.size() > 0) {
+            result.append("TRANSLATIONS: ").append(translations.toString()).append('\n');
+        }
+        if (rotations != null && rotations.size() > 0) {
+            result.append("ROTATIONS:    ").append(rotations.toString()).append('\n');
+        }
+        if (scales != null && scales.size() > 0) {
+            result.append("SCALES:       ").append(scales.toString()).append('\n');
+        }
+        return result.toString();
+    }
 }

+ 21 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java

@@ -28,6 +28,8 @@ public abstract class ConstraintDefinition {
     protected Long             ownerOMA;
     /** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */
     protected Set<Long>        alteredOmas;
+    /** The variable that determines if the constraint will alter the track in any way. */
+    protected boolean          trackToBeChanged = true;
 
     /**
      * Loads a constraint definition based on the constraint definition
@@ -52,6 +54,20 @@ public abstract class ConstraintDefinition {
         this.ownerOMA = ownerOMA;
     }
 
+    /**
+     * @return determines if the definition of the constraint will change the bone in any way; in most cases
+     *         it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint
+     *         computing to improve the computation speed and lower the computations complexity
+     */
+    public boolean isTrackToBeChanged() {
+        return trackToBeChanged;
+    }
+
+    /**
+     * @return determines if this constraint definition requires a defined target or not
+     */
+    public abstract boolean isTargetRequired();
+
     /**
      * 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
@@ -132,4 +148,9 @@ public abstract class ConstraintDefinition {
      *            the influence of the constraint (from range <0; 1>)
      */
     public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence);
+
+    @Override
+    public String toString() {
+        return this.getConstraintTypeName();
+    }
 }

+ 10 - 6
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java

@@ -26,18 +26,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
         mode = ((Number) constraintData.getFieldValue("mode")).intValue();
         dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
     }
-    
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null &&
-            blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
+        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
             // distance limit does not work on bones who are connected to their parent
             return;
         }
-        if(influence == 0 || targetTransform == null) {
-            return ;// no need to do anything
+        if (influence == 0 || targetTransform == null) {
+            return;// no need to do anything
         }
-        
+
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
 
         Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
@@ -73,6 +72,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
+
     @Override
     public String getConstraintTypeName() {
         return "Limit distance";

+ 20 - 6
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java

@@ -29,8 +29,6 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
     private int                bonesAffected;
     /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */
     private float              chainLength;
-    /** Tells if there is anything to compute at all. */
-    private boolean            needToCompute = true;
     /** Indicates if the tail of the bone should be used or not. */
     private boolean            useTail;
     /** The amount of iterations of the algorithm. */
@@ -43,23 +41,23 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         useTail = (flag & FLAG_USE_TAIL) != 0;
 
         if ((flag & FLAG_POSITION) == 0) {
-            needToCompute = false;
+            trackToBeChanged = false;
         }
 
-        if (needToCompute) {
+        if (trackToBeChanged) {
             alteredOmas = new HashSet<Long>();
         }
     }
 
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if (influence == 0 || !needToCompute || targetTransform == null) {
+        if (influence == 0 || !trackToBeChanged || targetTransform == null) {
             return;// no need to do anything
         }
         Quaternion q = new Quaternion();
         Vector3f t = targetTransform.getTranslation();
         List<BoneContext> bones = this.loadBones();
-        if(bones.size() == 0) {
+        if (bones.size() == 0) {
             return;// no need to do anything
         }
         float distanceFromTarget = Float.MAX_VALUE;
@@ -186,4 +184,20 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         }
         return bones;
     }
+
+    @Override
+    public boolean isTrackToBeChanged() {
+        if (trackToBeChanged) {
+            // need to check the bone structure too (when constructor was called not all of the bones might have been loaded yet)
+            // that is why it is also checked here
+            List<BoneContext> bones = this.loadBones();
+            trackToBeChanged = bones.size() > 0;
+        }
+        return trackToBeChanged;
+    }
+
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
 }

+ 19 - 11
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java

@@ -34,27 +34,30 @@ import com.jme3.scene.plugins.blender.file.Structure;
             int invZ = flag & LOCLIKE_Z_INVERT;
             // clear the other flags to swap them
             flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;
-            
+
             flag |= y << 1;
             flag |= invY << 1;
             flag |= z >> 1;
             flag |= invZ >> 1;
+
+            trackToBeChanged = (flag & LOCLIKE_X) != 0 || (flag & LOCLIKE_Y) != 0 || (flag & LOCLIKE_Z) != 0;
         }
     }
-    
+
+    @Override
+    public boolean isTrackToBeChanged() {
+        // location copy does not work on bones who are connected to their parent
+        return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
+    }
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null &&
-            blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
-            // location copy does not work on bones who are connected to their parent
+        if (influence == 0 || targetTransform == null || !this.isTrackToBeChanged()) {
             return;
         }
-        if(influence == 0 || targetTransform == null) {
-            return ;// no need to do anything
-        }
-        
+
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         Vector3f ownerLocation = ownerTransform.getTranslation();
         Vector3f targetLocation = targetTransform.getTranslation();
 
@@ -88,7 +91,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
             startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
             ownerLocation.addLocal(startLocation);
         }
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -96,4 +99,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Copy location";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
 }

+ 19 - 8
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java

@@ -52,18 +52,24 @@ import com.jme3.scene.plugins.blender.file.Structure;
             limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
             limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
         }
+
+        trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
+    }
+
+    @Override
+    public boolean isTrackToBeChanged() {
+        // location limit does not work on bones who are connected to their parent
+        return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT));
     }
-    
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null &&
-            blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) {
-            // location limit does not work on bones who are connected to their parent
-            return;
+        if (influence == 0 || !this.isTrackToBeChanged()) {
+            return;// no need to do anything
         }
-        
+
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         Vector3f translation = ownerTransform.getTranslation();
 
         if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) {
@@ -84,7 +90,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
         if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) {
             translation.z -= (translation.z - limits[2][1]) * influence;
         }
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -92,4 +98,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Limit location";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }

+ 12 - 1
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMaintainVolume.java

@@ -6,6 +6,11 @@ import com.jme3.scene.plugins.blender.BlenderContext;
 import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.file.Structure;
 
+/**
+ * This class represents 'Maintain volume' constraint type in blender.
+ * 
+ * @author Marcin Roguski (Kaelthas)
+ */
 public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
     private static final int FLAG_MASK_X = 0;
     private static final int FLAG_MASK_Y = 1;
@@ -16,11 +21,12 @@ public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
     public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
         super(constraintData, ownerOMA, blenderContext);
         volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue());
+        trackToBeChanged = volume != 1 && (flag & (FLAG_MASK_X | FLAG_MASK_Y | FLAG_MASK_Z)) != 0;
     }
 
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if (volume != 1 && influence > 0) {
+        if (trackToBeChanged && influence > 0) {
             // the maintain volume constraint is applied directly to object's scale, so no need to do it again
             // but in case of bones we need to make computations
             if (this.getOwner() instanceof Bone) {
@@ -47,4 +53,9 @@ public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition {
     public String getConstraintTypeName() {
         return "Maintain volume";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }

+ 6 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java

@@ -14,6 +14,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
         super(constraintData, ownerOMA, blenderContext);
+        trackToBeChanged = false;
     }
 
     @Override
@@ -25,4 +26,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Null";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }

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

@@ -25,15 +25,16 @@ import com.jme3.scene.plugins.blender.file.Structure;
 
     public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
         super(constraintData, ownerOMA, blenderContext);
+        trackToBeChanged = (flag & (ROTLIKE_X | ROTLIKE_Y | ROTLIKE_Z)) != 0;
     }
 
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if(influence == 0 || targetTransform == null) {
-            return ;// no need to do anything
+        if (influence == 0 || targetTransform == null || !trackToBeChanged) {
+            return;// no need to do anything
         }
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         Quaternion ownerRotation = ownerTransform.getRotation();
         ownerAngles = ownerRotation.toAngles(ownerAngles);
         targetAngles = targetTransform.getRotation().toAngles(targetAngles);
@@ -70,7 +71,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
             // ownerLocation.addLocal(startLocation);
             // TODO
         }
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -78,4 +79,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Copy rotation";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
 }

+ 13 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java

@@ -65,12 +65,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
          * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0];
          * limits[i][0] = limits[i][1]; limits[i][1] = temp; } }
          */
+
+        trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0;
     }
-    
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
+        if (influence == 0 || !trackToBeChanged) {
+            return;
+        }
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         ownerTransform.getRotation().toAngles(angles);
         // make sure that the rotations are always in range [0, 2PI)
         // TODO: same comment as in constructor
@@ -108,7 +113,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
             angles[2] -= difference;
         }
         ownerTransform.getRotation().fromAngles(angles);
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -116,4 +121,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Limit rotation";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }

+ 11 - 4
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java

@@ -27,16 +27,18 @@ import com.jme3.scene.plugins.blender.file.Structure;
                                                 // them
             flag |= y << 1;
             flag |= z >> 1;
+
+            trackToBeChanged = (flag & (SIZELIKE_X | SIZELIKE_Y | SIZELIKE_Z)) != 0;
         }
     }
-    
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if(influence == 0 || targetTransform == null) {
+        if (influence == 0 || targetTransform == null || !trackToBeChanged) {
             return;// no need to do anything
         }
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         Vector3f ownerScale = ownerTransform.getScale();
         Vector3f targetScale = targetTransform.getScale();
 
@@ -56,7 +58,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
             ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z;
         }
         ownerScale.addLocal(offset);
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -64,4 +66,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Copy scale";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
 }

+ 13 - 3
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java

@@ -50,12 +50,17 @@ import com.jme3.scene.plugins.blender.file.Structure;
             limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
             limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue();
         }
+
+        trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0;
     }
-    
+
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
+        if (influence == 0 || !trackToBeChanged) {
+            return;
+        }
         Transform ownerTransform = this.getOwnerTransform(ownerSpace);
-        
+
         Vector3f scale = ownerTransform.getScale();
         if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) {
             scale.x -= (scale.x - limits[0][0]) * influence;
@@ -75,7 +80,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
         if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) {
             scale.z -= (scale.z - limits[2][1]) * influence;
         }
-        
+
         this.applyOwnerTransform(ownerTransform, ownerSpace);
     }
 
@@ -83,4 +88,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
     public String getConstraintTypeName() {
         return "Limit scale";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }

+ 7 - 2
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java

@@ -35,8 +35,8 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
 
     @Override
     public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
-        if(influence == 0 || targetTransform == null) {
-            return ;// no need to do anything
+        if (influence == 0 || targetTransform == null) {
+            return;// no need to do anything
         }
         Object target = this.getTarget();// Bone or Node
         Object owner = this.getOwner();// Bone or Node
@@ -73,4 +73,9 @@ public class ConstraintDefinitionTransLike extends ConstraintDefinition {
     public String getConstraintTypeName() {
         return "Copy transforms";
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return true;
+    }
 }

+ 6 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java

@@ -16,6 +16,7 @@ import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
     public UnsupportedConstraintDefinition(String typeName) {
         super(null, null, null);
         this.typeName = typeName;
+        trackToBeChanged = false;
     }
 
     @Override
@@ -31,4 +32,9 @@ import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
     public String getConstraintTypeName() {
         return typeName;
     }
+
+    @Override
+    public boolean isTargetRequired() {
+        return false;
+    }
 }