소스 검색

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

jmekaelthas 10 년 전
부모
커밋
65c3ff668c
20개의 변경된 파일421개의 추가작업 그리고 69개의 파일을 삭제
  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;
+    }
 }