Переглянути джерело

Bugfix: fixed a bug that caused ugly artifacts to appear sometimes while
computing constraints. The fix also improved the constraints computation
speed at least several times :)

jmekaelthas 10 роки тому
батько
коміт
c9eadcc762

+ 16 - 21
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java

@@ -2,8 +2,10 @@ package com.jme3.scene.plugins.blender.constraints;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.logging.Logger;
 
 import com.jme3.animation.Bone;
@@ -170,31 +172,24 @@ public class ConstraintHelper extends AbstractBlenderHelper {
      *            the blender context
      */
     public void bakeConstraints(BlenderContext blenderContext) {
-        List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>();
+        Set<Long> owners = new HashSet<Long>();
         for (Constraint constraint : blenderContext.getAllConstraints()) {
-            boolean constraintUsed = false;
-            for (SimulationNode node : simulationRootNodes) {
-                if (node.contains(constraint)) {
-                    constraintUsed = true;
-                    break;
-                }
-            }
-
-            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, LoadedDataType.FEATURE);
-                    while (spatial.getParent() != null) {
-                        spatial = spatial.getParent();
-                    }
-                    simulationRootNodes.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial), blenderContext));
-                } else {
-                    throw new IllegalStateException("Unsupported constraint type: " + constraint);
+            if(constraint instanceof BoneConstraint) {
+                BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
+                owners.add(boneContext.getArmatureObjectOMA());
+            } else {
+                Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE);
+                while (spatial.getParent() != null) {
+                    spatial = spatial.getParent();
                 }
+                owners.add((Long)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial));
             }
         }
+        
+        List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>(owners.size());
+        for(Long ownerOMA : owners) {
+            simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext));
+        }
 
         for (SimulationNode node : simulationRootNodes) {
             node.simulate();

+ 97 - 269
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java

@@ -1,15 +1,12 @@
 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;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.logging.Level;
 import java.util.logging.Logger;
 
 import com.jme3.animation.AnimChannel;
@@ -43,14 +40,13 @@ import com.jme3.util.TempVars;
 public class SimulationNode {
     private static final Logger  LOGGER   = Logger.getLogger(SimulationNode.class.getName());
 
+    private Long featureOMA;
     /** The blender context. */
     private BlenderContext       blenderContext;
     /** 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 list of constraints that the current node has. */
-    private List<Constraint>     constraints;
     /** A list of node's animations. */
     private List<Animation>      animations;
 
@@ -68,7 +64,7 @@ public class SimulationNode {
     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
@@ -94,6 +90,7 @@ public class SimulationNode {
      *            indicates if the feature is a root bone or root spatial or not
      */
     private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) {
+        this.featureOMA = featureOMA;
         this.blenderContext = blenderContext;
         Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE);
         if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) {
@@ -117,22 +114,8 @@ public class SimulationNode {
 
         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);
-                }
-            }
             Node node = blenderContext.getControlledNode(skeleton);
             Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue();
             animations = blenderContext.getAnimations(animatedNodeOMA);
@@ -144,38 +127,6 @@ public class SimulationNode {
                 }
             }
         }
-
-        LOGGER.info("Removing invalid constraints.");
-        List<Constraint> validConstraints = new ArrayList<Constraint>(constraints.size());
-        for (Constraint constraint : constraints) {
-            if (constraint.validate()) {
-                validConstraints.add(constraint);
-            } else {
-                LOGGER.log(Level.WARNING, "Constraint {0} is invalid and will not be applied.", constraint.name);
-            }
-        }
-        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;
     }
 
     /**
@@ -191,6 +142,7 @@ public class SimulationNode {
             for (Entry<Bone, Transform> entry : boneStartTransforms.entrySet()) {
                 Transform t = entry.getValue();
                 entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale());
+                entry.getKey().updateModelTransforms();
             }
             skeleton.reset();
         }
@@ -200,7 +152,9 @@ public class SimulationNode {
      * Simulates the spatial node.
      */
     private void simulateSpatial() {
+        List<Constraint> constraints = blenderContext.getConstraints(featureOMA);
         if (constraints != null && constraints.size() > 0) {
+            LOGGER.fine("Simulating spatial.");
             boolean applyStaticConstraints = true;
             if (animations != null) {
                 for (Animation animation : animations) {
@@ -248,83 +202,86 @@ public class SimulationNode {
      * Simulates the bone node.
      */
     private void simulateSkeleton() {
-        if (constraints != null && constraints.size() > 0) {
-            Set<Long> alteredOmas = new HashSet<Long>();
-
-            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];
-                    float maxTime = animationTimeBoundaries[1];
-
-                    Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
-                    for (int frame = 0; frame < maxFrame; ++frame) {
-                        // this MUST be done here, otherwise setting next frame of animation will
-                        // lead to possible errors
-                        this.reset();
+        LOGGER.fine("Simulating skeleton.");
+        Set<Long> alteredOmas = new HashSet<Long>();
+
+        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];
+                float maxTime = animationTimeBoundaries[1];
+
+                Map<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
+                for (int frame = 0; frame < maxFrame; ++frame) {
+                    // this MUST be done here, otherwise setting next frame of animation will
+                    // lead to possible errors
+                    this.reset();
+
+                    // first set proper time for all bones in all the tracks ...
+                    for (Track track : animation.getTracks()) {
+                        float time = ((BoneTrack) track).getTimes()[frame];
+                        track.setTime(time, 1, animControl, animChannel, vars);
+                        skeleton.updateWorldVectors();
+                    }
 
-                        // first set proper time for all bones in all the tracks ...
-                        for (Track track : animation.getTracks()) {
-                            float time = ((BoneTrack) track).getTimes()[frame];
-                            track.setTime(time, 1, animControl, animChannel, vars);
-                            skeleton.updateWorldVectors();
+                    // ... and then apply constraints from the root bone to the last child ...
+                    Set<Long> applied = new HashSet<Long>();
+                    for (Bone rootBone : skeleton.getRoots()) {
+                        // ignore the 0-indexed bone
+                        if (skeleton.getBoneIndex(rootBone) > 0) {
+                            this.applyConstraints(rootBone, alteredOmas, applied, frame);
                         }
+                    }
 
-                        // ... and then apply constraints from the root bone to the last child ...
-                        for (Bone rootBone : bonesWithConstraints) {
-                            this.applyConstraints(rootBone, alteredOmas, frame);
+                    // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ...
+                    for (Long boneOMA : alteredOmas) {
+                        BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
+                        int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
+                        if (!tracks.containsKey(boneIndex)) {
+                            tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
                         }
+                    }
+                    alteredOmas.clear();
 
-                        // ... add virtual tracks if neccessary, for bones that were altered but had no tracks before ...
-                        for (Long boneOMA : alteredOmas) {
-                            BoneContext boneContext = blenderContext.getBoneContext(boneOMA);
-                            int boneIndex = skeleton.getBoneIndex(boneContext.getBone());
-                            if (!tracks.containsKey(boneIndex)) {
-                                tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime));
-                            }
-                        }
-                        alteredOmas.clear();
-
-                        // ... and fill in another frame in the result track
-                        for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
-                            Bone bone = skeleton.getBone(trackEntry.getKey());
-                            Transform startTransform = boneStartTransforms.get(bone);
+                    // ... and fill in another frame in the result track
+                    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());
+                        // 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));
-                        }
+                        trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));
                     }
+                }
 
-                    for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
-                        Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey());
-                        if (newTrack != null) {
-                            boolean trackReplaced = false;
-                            for (Track track : animation.getTracks()) {
-                                if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) {
-                                    animation.removeTrack(track);
-                                    animation.addTrack(newTrack);
-                                    trackReplaced = true;
-                                    break;
-                                }
-                            }
-                            if (!trackReplaced) {
+                for (Entry<Integer, VirtualTrack> trackEntry : tracks.entrySet()) {
+                    Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey());
+                    if (newTrack != null) {
+                        boolean trackReplaced = false;
+                        for (Track track : animation.getTracks()) {
+                            if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) {
+                                animation.removeTrack(track);
                                 animation.addTrack(newTrack);
+                                trackReplaced = true;
+                                break;
                             }
                         }
+                        if (!trackReplaced) {
+                            animation.addTrack(newTrack);
+                        }
                     }
                 }
-                vars.release();
-                animControl.clearChannels();
-                this.reset();
             }
+            vars.release();
+            animControl.clearChannels();
+            this.reset();
         }
     }
 
@@ -338,16 +295,32 @@ public class SimulationNode {
      * @param frame
      *            the current frame of the animation
      */
-    private void applyConstraints(Bone bone, Set<Long> alteredOmas, int frame) {
+    private void applyConstraints(Bone bone, Set<Long> alteredOmas, Set<Long> applied, int frame) {
         BoneContext boneContext = blenderContext.getBoneContext(bone);
-        List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
-        if (constraints != null && constraints.size() > 0) {
-            for (Constraint constraint : constraints) {
-                constraint.apply(frame);
-                if (constraint.getAlteredOmas() != null) {
-                    alteredOmas.addAll(constraint.getAlteredOmas());
+        if(!applied.contains(boneContext.getBoneOma())) {
+            List<Constraint> constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
+            if (constraints != null && constraints.size() > 0) {
+                // TODO: BEWARE OF INFINITE LOOPS !!!!!!!!!!!!!!!!!!!!!!!!!!
+                for (Constraint constraint : constraints) {
+                    if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) {
+                        // first apply constraints of the target bone
+                        BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA());
+                        this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame);
+                    }
+                    constraint.apply(frame);
+                    if (constraint.getAlteredOmas() != null) {
+                        alteredOmas.addAll(constraint.getAlteredOmas());
+                    }
+                    alteredOmas.add(boneContext.getBoneOma());
                 }
-                alteredOmas.add(boneContext.getBoneOma());
+            }
+            applied.add(boneContext.getBoneOma());
+        }
+        
+        List<Bone> children = bone.getChildren();
+        if (children != null && children.size() > 0) {
+            for (Bone child : bone.getChildren()) {
+                this.applyConstraints(child, alteredOmas, applied, frame);
             }
         }
     }
@@ -364,150 +337,6 @@ 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.
@@ -547,11 +376,10 @@ public class SimulationNode {
         List<Constraint> constraints = blenderContext.getConstraints(ownerOMA);
         if (constraints != null) {
             for (Constraint constraint : constraints) {
-                if (constraint.isImplemented() && constraint.validate()) {
+                if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) {
                     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() });
                 }
+                // TODO: add proper warnings to some map or set so that they are not logged on every frame
             }
         }
         return result.size() > 0 ? result : null;

+ 30 - 25
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java

@@ -6,13 +6,15 @@ import java.util.HashSet;
 import java.util.List;
 
 import com.jme3.animation.Bone;
-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.constraints.ConstraintHelper.Space;
 import com.jme3.scene.plugins.blender.file.Structure;
+import com.jme3.scene.plugins.blender.math.DQuaternion;
+import com.jme3.scene.plugins.blender.math.DTransform;
+import com.jme3.scene.plugins.blender.math.Vector3d;
 
 /**
  * The Inverse Kinematics constraint.
@@ -28,7 +30,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
     /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */
     private int                bonesAffected;
     /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */
-    private float              chainLength;
+    private double              chainLength;
     /** Indicates if the tail of the bone should be used or not. */
     private boolean            useTail;
     /** The amount of iterations of the algorithm. */
@@ -54,13 +56,13 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         if (influence == 0 || !trackToBeChanged || targetTransform == null) {
             return;// no need to do anything
         }
-        Quaternion q = new Quaternion();
-        Vector3f t = targetTransform.getTranslation();
+        DQuaternion q = new DQuaternion();
+        Vector3d t = new Vector3d(targetTransform.getTranslation());
         List<BoneContext> bones = this.loadBones();
         if (bones.size() == 0) {
             return;// no need to do anything
         }
-        float distanceFromTarget = Float.MAX_VALUE;
+        double distanceFromTarget = Double.MAX_VALUE;
 
         int iterations = this.iterations;
         if (bones.size() == 1) {
@@ -70,21 +72,21 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
             // in this case only one iteration will be needed, computed from the root to top bone
             BoneContext rootBone = bones.get(bones.size() - 1);
             Transform rootBoneTransform = constraintHelper.getTransform(rootBone.getArmatureObjectOMA(), rootBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-            if (t.distance(rootBoneTransform.getTranslation()) >= chainLength) {
+            if (t.distance(new Vector3d(rootBoneTransform.getTranslation())) >= chainLength) {
                 Collections.reverse(bones);
 
                 for (BoneContext boneContext : bones) {
                     Bone bone = boneContext.getBone();
-                    Transform boneTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD);
+                    DTransform boneTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD));
 
-                    Vector3f e = boneTransform.getTranslation().add(boneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(boneContext.getLength()));// effector
-                    Vector3f j = boneTransform.getTranslation(); // current join position
+                    Vector3d e = boneTransform.getTranslation().add(boneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(boneContext.getLength()));// effector
+                    Vector3d j = boneTransform.getTranslation(); // current join position
 
-                    Vector3f currentDir = e.subtractLocal(j).normalizeLocal();
-                    Vector3f target = t.subtract(j).normalizeLocal();
-                    float angle = currentDir.angleBetween(target);
+                    Vector3d currentDir = e.subtractLocal(j).normalizeLocal();
+                    Vector3d target = t.subtract(j).normalizeLocal();
+                    double angle = currentDir.angleBetween(target);
                     if (angle != 0) {
-                        Vector3f cross = currentDir.crossLocal(target).normalizeLocal();
+                        Vector3d cross = currentDir.crossLocal(target).normalizeLocal();
                         q.fromAngleAxis(angle, cross);
                         if (boneContext.isLockX()) {
                             q.set(0, q.getY(), q.getZ(), q.getW());
@@ -97,7 +99,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
                         }
 
                         boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation()));
-                        constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform);
+                        constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform.toTransform());
                     }
                 }
 
@@ -109,17 +111,17 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
         for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
             for (BoneContext boneContext : bones) {
                 Bone bone = boneContext.getBone();
-                Transform topBoneTransform = constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-                Transform boneWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD);
+                DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
+                DTransform boneWorldTransform = new DTransform(constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD));
 
-                Vector3f e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(topBone.getLength()));// effector
-                Vector3f j = boneWorldTransform.getTranslation(); // current join position
+                Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
+                Vector3d j = boneWorldTransform.getTranslation(); // current join position
 
-                Vector3f currentDir = e.subtractLocal(j).normalizeLocal();
-                Vector3f target = t.subtract(j).normalizeLocal();
-                float angle = currentDir.angleBetween(target);
+                Vector3d currentDir = e.subtractLocal(j).normalizeLocal();
+                Vector3d target = t.subtract(j).normalizeLocal();
+                double angle = currentDir.angleBetween(target);
                 if (angle != 0) {
-                    Vector3f cross = currentDir.crossLocal(target).normalizeLocal();
+                    Vector3d cross = currentDir.crossLocal(target).normalizeLocal();
                     q.fromAngleAxis(angle, cross);
 
                     if (boneContext.isLockX()) {
@@ -133,12 +135,15 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
                     }
 
                     boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation()));
-                    constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform);
+                    constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform.toTransform());
+                } else {
+                    iterations = 0;
+                    break;
                 }
             }
 
-            Transform topBoneTransform = constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
-            Vector3f e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(topBone.getLength()));// effector
+            DTransform topBoneTransform = new DTransform(constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD));
+            Vector3d e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector
             distanceFromTarget = e.distance(t);
         }
     }

+ 453 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java

@@ -0,0 +1,453 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.math;
+
+import java.io.IOException;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.Quaternion;
+
+/**
+ * <code>DQuaternion</code> defines a single example of a more general class of
+ * hypercomplex numbers. DQuaternions extends a rotation in three dimensions to a
+ * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth
+ * continuous rotation.
+ * 
+ * <code>DQuaternion</code> is defined by four double point numbers: {x y z w}.
+ * 
+ * This class's only purpose is to give better accuracy in floating point operations during computations.
+ * This is made by copying the original Quaternion class from jme3 core and leaving only required methods and basic computation methods, so that
+ * the class is smaller and easier to maintain.
+ * Should any other methods be needed, they will be added.
+ * 
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class DQuaternion implements Savable, Cloneable, java.io.Serializable {
+    private static final long       serialVersionUID = 5009180713885017539L;
+
+    /**
+     * Represents the identity quaternion rotation (0, 0, 0, 1).
+     */
+    public static final DQuaternion IDENTITY         = new DQuaternion();
+    public static final DQuaternion DIRECTION_Z      = new DQuaternion();
+    public static final DQuaternion ZERO             = new DQuaternion(0, 0, 0, 0);
+    protected double                x, y, z, w = 1;
+
+    /**
+     * Constructor instantiates a new <code>DQuaternion</code> object
+     * initializing all values to zero, except w which is initialized to 1.
+     *
+     */
+    public DQuaternion() {
+    }
+
+    /**
+     * Constructor instantiates a new <code>DQuaternion</code> object from the
+     * given list of parameters.
+     *
+     * @param x
+     *            the x value of the quaternion.
+     * @param y
+     *            the y value of the quaternion.
+     * @param z
+     *            the z value of the quaternion.
+     * @param w
+     *            the w value of the quaternion.
+     */
+    public DQuaternion(double x, double y, double z, double w) {
+        this.set(x, y, z, w);
+    }
+
+    public DQuaternion(Quaternion q) {
+        this(q.getX(), q.getY(), q.getZ(), q.getW());
+    }
+
+    public Quaternion toQuaternion() {
+        return new Quaternion((float) x, (float) y, (float) z, (float) w);
+    }
+
+    public double getX() {
+        return x;
+    }
+
+    public double getY() {
+        return y;
+    }
+
+    public double getZ() {
+        return z;
+    }
+
+    public double getW() {
+        return w;
+    }
+
+    /**
+     * sets the data in a <code>DQuaternion</code> object from the given list
+     * of parameters.
+     *
+     * @param x
+     *            the x value of the quaternion.
+     * @param y
+     *            the y value of the quaternion.
+     * @param z
+     *            the z value of the quaternion.
+     * @param w
+     *            the w value of the quaternion.
+     * @return this
+     */
+    public DQuaternion set(double x, double y, double z, double w) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        this.w = w;
+        return this;
+    }
+
+    /**
+     * Sets the data in this <code>DQuaternion</code> object to be equal to the
+     * passed <code>DQuaternion</code> object. The values are copied producing
+     * a new object.
+     *
+     * @param q
+     *            The DQuaternion to copy values from.
+     * @return this
+     */
+    public DQuaternion set(DQuaternion q) {
+        x = q.x;
+        y = q.y;
+        z = q.z;
+        w = q.w;
+        return this;
+    }
+
+    /**
+     * Sets this DQuaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1).
+     */
+    public void loadIdentity() {
+        x = y = z = 0;
+        w = 1;
+    }
+
+    /**
+     * <code>fromAngleAxis</code> sets this quaternion to the values specified
+     * by an angle and an axis of rotation. This method creates an object, so
+     * use fromAngleNormalAxis if your axis is already normalized.
+     *
+     * @param angle
+     *            the angle to rotate (in radians).
+     * @param axis
+     *            the axis of rotation.
+     * @return this quaternion
+     */
+    public DQuaternion fromAngleAxis(double angle, Vector3d axis) {
+        Vector3d normAxis = axis.normalize();
+        this.fromAngleNormalAxis(angle, normAxis);
+        return this;
+    }
+
+    /**
+     * <code>fromAngleNormalAxis</code> sets this quaternion to the values
+     * specified by an angle and a normalized axis of rotation.
+     *
+     * @param angle
+     *            the angle to rotate (in radians).
+     * @param axis
+     *            the axis of rotation (already normalized).
+     */
+    public DQuaternion fromAngleNormalAxis(double angle, Vector3d axis) {
+        if (axis.x == 0 && axis.y == 0 && axis.z == 0) {
+            this.loadIdentity();
+        } else {
+            double halfAngle = 0.5f * angle;
+            double sin = Math.sin(halfAngle);
+            w = Math.cos(halfAngle);
+            x = sin * axis.x;
+            y = sin * axis.y;
+            z = sin * axis.z;
+        }
+        return this;
+    }
+
+    /**
+     * <code>add</code> adds the values of this quaternion to those of the
+     * parameter quaternion. The result is returned as a new quaternion.
+     *
+     * @param q
+     *            the quaternion to add to this.
+     * @return the new quaternion.
+     */
+    public DQuaternion add(DQuaternion q) {
+        return new DQuaternion(x + q.x, y + q.y, z + q.z, w + q.w);
+    }
+
+    /**
+     * <code>add</code> adds the values of this quaternion to those of the
+     * parameter quaternion. The result is stored in this DQuaternion.
+     *
+     * @param q
+     *            the quaternion to add to this.
+     * @return This DQuaternion after addition.
+     */
+    public DQuaternion addLocal(DQuaternion q) {
+        x += q.x;
+        y += q.y;
+        z += q.z;
+        w += q.w;
+        return this;
+    }
+
+    /**
+     * <code>subtract</code> subtracts the values of the parameter quaternion
+     * from those of this quaternion. The result is returned as a new
+     * quaternion.
+     *
+     * @param q
+     *            the quaternion to subtract from this.
+     * @return the new quaternion.
+     */
+    public DQuaternion subtract(DQuaternion q) {
+        return new DQuaternion(x - q.x, y - q.y, z - q.z, w - q.w);
+    }
+
+    /**
+     * <code>subtract</code> subtracts the values of the parameter quaternion
+     * from those of this quaternion. The result is stored in this DQuaternion.
+     *
+     * @param q
+     *            the quaternion to subtract from this.
+     * @return This DQuaternion after subtraction.
+     */
+    public DQuaternion subtractLocal(DQuaternion q) {
+        x -= q.x;
+        y -= q.y;
+        z -= q.z;
+        w -= q.w;
+        return this;
+    }
+
+    /**
+     * <code>mult</code> multiplies this quaternion by a parameter quaternion.
+     * The result is returned as a new quaternion. It should be noted that
+     * quaternion multiplication is not commutative so q * p != p * q.
+     *
+     * @param q
+     *            the quaternion to multiply this quaternion by.
+     * @return the new quaternion.
+     */
+    public DQuaternion mult(DQuaternion q) {
+        return this.mult(q, null);
+    }
+
+    /**
+     * <code>mult</code> multiplies this quaternion by a parameter quaternion.
+     * The result is returned as a new quaternion. It should be noted that
+     * quaternion multiplication is not commutative so q * p != p * q.
+     *
+     * It IS safe for q and res to be the same object.
+     * It IS NOT safe for this and res to be the same object.
+     *
+     * @param q
+     *            the quaternion to multiply this quaternion by.
+     * @param res
+     *            the quaternion to store the result in.
+     * @return the new quaternion.
+     */
+    public DQuaternion mult(DQuaternion q, DQuaternion res) {
+        if (res == null) {
+            res = new DQuaternion();
+        }
+        double qw = q.w, qx = q.x, qy = q.y, qz = q.z;
+        res.x = x * qw + y * qz - z * qy + w * qx;
+        res.y = -x * qz + y * qw + z * qx + w * qy;
+        res.z = x * qy - y * qx + z * qw + w * qz;
+        res.w = -x * qx - y * qy - z * qz + w * qw;
+        return res;
+    }
+
+    /**
+     * <code>mult</code> multiplies this quaternion by a parameter vector. The
+     * result is returned as a new vector.
+     *
+     * @param v
+     *            the vector to multiply this quaternion by.
+     * @return the new vector.
+     */
+    public Vector3d mult(Vector3d v) {
+        return this.mult(v, null);
+    }
+
+    /**
+     * Multiplies this DQuaternion by the supplied quaternion. The result is
+     * stored in this DQuaternion, which is also returned for chaining. Similar
+     * to this *= q.
+     *
+     * @param q
+     *            The DQuaternion to multiply this one by.
+     * @return This DQuaternion, after multiplication.
+     */
+    public DQuaternion multLocal(DQuaternion q) {
+        double x1 = x * q.w + y * q.z - z * q.y + w * q.x;
+        double y1 = -x * q.z + y * q.w + z * q.x + w * q.y;
+        double z1 = x * q.y - y * q.x + z * q.w + w * q.z;
+        w = -x * q.x - y * q.y - z * q.z + w * q.w;
+        x = x1;
+        y = y1;
+        z = z1;
+        return this;
+    }
+
+    /**
+     * <code>mult</code> multiplies this quaternion by a parameter vector. The
+     * result is returned as a new vector.
+     * 
+     * @param v
+     *            the vector to multiply this quaternion by.
+     * @param store
+     *            the vector to store the result in. It IS safe for v and store
+     *            to be the same object.
+     * @return the result vector.
+     */
+    public Vector3d mult(Vector3d v, Vector3d store) {
+        if (store == null) {
+            store = new Vector3d();
+        }
+        if (v.x == 0 && v.y == 0 && v.z == 0) {
+            store.set(0, 0, 0);
+        } else {
+            double vx = v.x, vy = v.y, vz = v.z;
+            store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x * vx + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y * y * vx;
+            store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w * z * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x * x * vy;
+            store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w * y * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w * w * vz;
+        }
+        return store;
+    }
+
+    /**
+     *
+     * <code>toString</code> creates the string representation of this <code>DQuaternion</code>. The values of the quaternion are displaced (x,
+     * y, z, w), in the following manner: <br>
+     * (x, y, z, w)
+     *
+     * @return the string representation of this object.
+     * @see java.lang.Object#toString()
+     */
+    @Override
+    public String toString() {
+        return "(" + x + ", " + y + ", " + z + ", " + w + ")";
+    }
+
+    /**
+     * <code>equals</code> determines if two quaternions are logically equal,
+     * that is, if the values of (x, y, z, w) are the same for both quaternions.
+     *
+     * @param o
+     *            the object to compare for equality
+     * @return true if they are equal, false otherwise.
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof DQuaternion)) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        DQuaternion comp = (DQuaternion) o;
+        if (Double.compare(x, comp.x) != 0) {
+            return false;
+        }
+        if (Double.compare(y, comp.y) != 0) {
+            return false;
+        }
+        if (Double.compare(z, comp.z) != 0) {
+            return false;
+        }
+        if (Double.compare(w, comp.w) != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 
+     * <code>hashCode</code> returns the hash code value as an integer and is
+     * supported for the benefit of hashing based collection classes such as
+     * Hashtable, HashMap, HashSet etc.
+     * 
+     * @return the hashcode for this instance of DQuaternion.
+     * @see java.lang.Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        long hash = 37;
+        hash = 37 * hash + Double.doubleToLongBits(x);
+        hash = 37 * hash + Double.doubleToLongBits(y);
+        hash = 37 * hash + Double.doubleToLongBits(z);
+        hash = 37 * hash + Double.doubleToLongBits(w);
+        return (int) hash;
+
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule cap = e.getCapsule(this);
+        cap.write(x, "x", 0);
+        cap.write(y, "y", 0);
+        cap.write(z, "z", 0);
+        cap.write(w, "w", 1);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule cap = e.getCapsule(this);
+        x = cap.readFloat("x", 0);
+        y = cap.readFloat("y", 0);
+        z = cap.readFloat("z", 0);
+        w = cap.readFloat("w", 1);
+    }
+
+    @Override
+    public DQuaternion clone() {
+        try {
+            return (DQuaternion) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(); // can not happen
+        }
+    }
+}

+ 170 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.jme3.scene.plugins.blender.math;
+
+import com.jme3.export.*;
+import com.jme3.math.Transform;
+
+import java.io.IOException;
+
+/**
+ * Started Date: Jul 16, 2004<br>
+ * <br>
+ * Represents a translation, rotation and scale in one object.
+ * 
+ * This class's only purpose is to give better accuracy in floating point operations during computations.
+ * This is made by copying the original Transfrom class from jme3 core and removing unnecessary methods so that
+ * the class is smaller and easier to maintain.
+ * Should any other methods be needed, they will be added.
+ * 
+ * @author Jack Lindamood
+ * @author Joshua Slack
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class DTransform implements Savable, Cloneable, java.io.Serializable {
+    private static final long serialVersionUID = 7812915425940606722L;
+
+    private DQuaternion       rotation;
+    private Vector3d          translation;
+    private Vector3d          scale;
+
+    public DTransform(Transform transform) {
+        translation = new Vector3d(transform.getTranslation());
+        rotation = new DQuaternion(transform.getRotation());
+        scale = new Vector3d(transform.getScale());
+    }
+
+    public Transform toTransform() {
+        return new Transform(translation.toVector3f(), rotation.toQuaternion(), scale.toVector3f());
+    }
+
+    /**
+     * Sets this translation to the given value.
+     * @param trans
+     *            The new translation for this matrix.
+     * @return this
+     */
+    public DTransform setTranslation(Vector3d trans) {
+        translation.set(trans);
+        return this;
+    }
+
+    /**
+     * Sets this rotation to the given DQuaternion value.
+     * @param rot
+     *            The new rotation for this matrix.
+     * @return this
+     */
+    public DTransform setRotation(DQuaternion rot) {
+        rotation.set(rot);
+        return this;
+    }
+
+    /**
+     * Sets this scale to the given value.
+     * @param scale
+     *            The new scale for this matrix.
+     * @return this
+     */
+    public DTransform setScale(Vector3d scale) {
+        this.scale.set(scale);
+        return this;
+    }
+
+    /**
+     * Sets this scale to the given value.
+     * @param scale
+     *            The new scale for this matrix.
+     * @return this
+     */
+    public DTransform setScale(float scale) {
+        this.scale.set(scale, scale, scale);
+        return this;
+    }
+
+    /**
+     * Return the translation vector in this matrix.
+     * @return translation vector.
+     */
+    public Vector3d getTranslation() {
+        return translation;
+    }
+
+    /**
+     * Return the rotation quaternion in this matrix.
+     * @return rotation quaternion.
+     */
+    public DQuaternion getRotation() {
+        return rotation;
+    }
+
+    /**
+     * Return the scale vector in this matrix.
+     * @return scale vector.
+     */
+    public Vector3d getScale() {
+        return scale;
+    }
+
+    @Override
+    public String toString() {
+        return this.getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" + "[ " + rotation.x + ", " + rotation.y + ", " + rotation.z + ", " + rotation.w + "]\n" + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]";
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(rotation, "rot", new DQuaternion());
+        capsule.write(translation, "translation", Vector3d.ZERO);
+        capsule.write(scale, "scale", Vector3d.UNIT_XYZ);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+
+        rotation = (DQuaternion) capsule.readSavable("rot", new DQuaternion());
+        translation = (Vector3d) capsule.readSavable("translation", Vector3d.ZERO);
+        scale = (Vector3d) capsule.readSavable("scale", Vector3d.UNIT_XYZ);
+    }
+
+    @Override
+    public DTransform clone() {
+        try {
+            DTransform tq = (DTransform) super.clone();
+            tq.rotation = rotation.clone();
+            tq.scale = scale.clone();
+            tq.translation = translation.clone();
+            return tq;
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError();
+        }
+    }
+}

+ 867 - 0
jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java

@@ -0,0 +1,867 @@
+/*
+ * Copyright (c) 2009-2012 jMonkeyEngine
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
+ *   may be used to endorse or promote products derived from this software
+ *   without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.jme3.scene.plugins.blender.math;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.logging.Logger;
+
+import com.jme3.export.InputCapsule;
+import com.jme3.export.JmeExporter;
+import com.jme3.export.JmeImporter;
+import com.jme3.export.OutputCapsule;
+import com.jme3.export.Savable;
+import com.jme3.math.FastMath;
+import com.jme3.math.Vector3f;
+
+/*
+ * -- Added *Local methods to cut down on object creation - JS
+ */
+
+/**
+ * <code>Vector3d</code> defines a Vector for a three float value tuple. <code>Vector3d</code> can represent any three dimensional value, such as a
+ * vertex, a normal, etc. Utility methods are also included to aid in
+ * mathematical calculations.
+ *
+ * This class's only purpose is to give better accuracy in floating point operations during computations.
+ * This is made by copying the original Vector3f class from jme3 core and leaving only required methods and basic computation methods, so that
+ * the class is smaller and easier to maintain.
+ * Should any other methods be needed, they will be added.
+ *
+ * @author Mark Powell
+ * @author Joshua Slack
+ * @author Marcin Roguski (Kaelthas)
+ */
+public final class Vector3d implements Savable, Cloneable, Serializable {
+    private static final long    serialVersionUID = 3090477054277293078L;
+
+    private static final Logger  LOGGER           = Logger.getLogger(Vector3d.class.getName());
+
+    public final static Vector3d ZERO             = new Vector3d();
+    public final static Vector3d UNIT_XYZ         = new Vector3d(1, 1, 1);
+    public final static Vector3d UNIT_X           = new Vector3d(1, 0, 0);
+    public final static Vector3d UNIT_Y           = new Vector3d(0, 1, 0);
+    public final static Vector3d UNIT_Z           = new Vector3d(0, 0, 1);
+
+    /**
+     * the x value of the vector.
+     */
+    public double                x;
+
+    /**
+     * the y value of the vector.
+     */
+    public double                y;
+
+    /**
+     * the z value of the vector.
+     */
+    public double                z;
+
+    /**
+     * Constructor instantiates a new <code>Vector3d</code> with default
+     * values of (0,0,0).
+     *
+     */
+    public Vector3d() {
+    }
+
+    /**
+     * Constructor instantiates a new <code>Vector3d</code> with provides
+     * values.
+     *
+     * @param x
+     *            the x value of the vector.
+     * @param y
+     *            the y value of the vector.
+     * @param z
+     *            the z value of the vector.
+     */
+    public Vector3d(double x, double y, double z) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+    }
+
+    /**
+     * Constructor instantiates a new <code>Vector3d</code> that is a copy
+     * of the provided vector
+     * @param copy
+     *            The Vector3d to copy
+     */
+    public Vector3d(Vector3f vector3f) {
+        this(vector3f.x, vector3f.y, vector3f.z);
+    }
+
+    public Vector3f toVector3f() {
+        return new Vector3f((float) x, (float) y, (float) z);
+    }
+
+    /**
+     * <code>set</code> sets the x,y,z values of the vector based on passed
+     * parameters.
+     *
+     * @param x
+     *            the x value of the vector.
+     * @param y
+     *            the y value of the vector.
+     * @param z
+     *            the z value of the vector.
+     * @return this vector
+     */
+    public Vector3d set(double x, double y, double z) {
+        this.x = x;
+        this.y = y;
+        this.z = z;
+        return this;
+    }
+
+    /**
+     * <code>set</code> sets the x,y,z values of the vector by copying the
+     * supplied vector.
+     *
+     * @param vect
+     *            the vector to copy.
+     * @return this vector
+     */
+    public Vector3d set(Vector3d vect) {
+        return this.set(vect.x, vect.y, vect.z);
+    }
+
+    /**
+     *
+     * <code>add</code> adds a provided vector to this vector creating a
+     * resultant vector which is returned. If the provided vector is null, null
+     * is returned.
+     *
+     * @param vec
+     *            the vector to add to this.
+     * @return the resultant vector.
+     */
+    public Vector3d add(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        return new Vector3d(x + vec.x, y + vec.y, z + vec.z);
+    }
+
+    /**
+     *
+     * <code>add</code> adds the values of a provided vector storing the
+     * values in the supplied vector.
+     *
+     * @param vec
+     *            the vector to add to this
+     * @param result
+     *            the vector to store the result in
+     * @return result returns the supplied result vector.
+     */
+    public Vector3d add(Vector3d vec, Vector3d result) {
+        result.x = x + vec.x;
+        result.y = y + vec.y;
+        result.z = z + vec.z;
+        return result;
+    }
+
+    /**
+     * <code>addLocal</code> adds a provided vector to this vector internally,
+     * and returns a handle to this vector for easy chaining of calls. If the
+     * provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to add to this vector.
+     * @return this
+     */
+    public Vector3d addLocal(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x += vec.x;
+        y += vec.y;
+        z += vec.z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>add</code> adds the provided values to this vector, creating a
+     * new vector that is then returned.
+     *
+     * @param addX
+     *            the x value to add.
+     * @param addY
+     *            the y value to add.
+     * @param addZ
+     *            the z value to add.
+     * @return the result vector.
+     */
+    public Vector3d add(double addX, double addY, double addZ) {
+        return new Vector3d(x + addX, y + addY, z + addZ);
+    }
+
+    /**
+     * <code>addLocal</code> adds the provided values to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param addX
+     *            value to add to x
+     * @param addY
+     *            value to add to y
+     * @param addZ
+     *            value to add to z
+     * @return this
+     */
+    public Vector3d addLocal(double addX, double addY, double addZ) {
+        x += addX;
+        y += addY;
+        z += addZ;
+        return this;
+    }
+
+    /**
+     *
+     * <code>scaleAdd</code> multiplies this vector by a scalar then adds the
+     * given Vector3d.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @param add
+     *            the value to add
+     */
+    public Vector3d scaleAdd(double scalar, Vector3d add) {
+        x = x * scalar + add.x;
+        y = y * scalar + add.y;
+        z = z * scalar + add.z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>scaleAdd</code> multiplies the given vector by a scalar then adds
+     * the given vector.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @param mult
+     *            the value to multiply the scalar by
+     * @param add
+     *            the value to add
+     */
+    public Vector3d scaleAdd(double scalar, Vector3d mult, Vector3d add) {
+        x = mult.x * scalar + add.x;
+        y = mult.y * scalar + add.y;
+        z = mult.z * scalar + add.z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>dot</code> calculates the dot product of this vector with a
+     * provided vector. If the provided vector is null, 0 is returned.
+     *
+     * @param vec
+     *            the vector to dot with this vector.
+     * @return the resultant dot product of this vector and a given vector.
+     */
+    public double dot(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, 0 returned.");
+            return 0;
+        }
+        return x * vec.x + y * vec.y + z * vec.z;
+    }
+
+    /**
+     * <code>cross</code> calculates the cross product of this vector with a
+     * parameter vector v.
+     *
+     * @param v
+     *            the vector to take the cross product of with this.
+     * @return the cross product vector.
+     */
+    public Vector3d cross(Vector3d v) {
+        return this.cross(v, null);
+    }
+
+    /**
+     * <code>cross</code> calculates the cross product of this vector with a
+     * parameter vector v. The result is stored in <code>result</code>
+     *
+     * @param v
+     *            the vector to take the cross product of with this.
+     * @param result
+     *            the vector to store the cross product result.
+     * @return result, after recieving the cross product vector.
+     */
+    public Vector3d cross(Vector3d v, Vector3d result) {
+        return this.cross(v.x, v.y, v.z, result);
+    }
+
+    /**
+     * <code>cross</code> calculates the cross product of this vector with a
+     * parameter vector v. The result is stored in <code>result</code>
+     *
+     * @param otherX
+     *            x component of the vector to take the cross product of with this.
+     * @param otherY
+     *            y component of the vector to take the cross product of with this.
+     * @param otherZ
+     *            z component of the vector to take the cross product of with this.
+     * @param result
+     *            the vector to store the cross product result.
+     * @return result, after recieving the cross product vector.
+     */
+    public Vector3d cross(double otherX, double otherY, double otherZ, Vector3d result) {
+        if (result == null) {
+            result = new Vector3d();
+        }
+        double resX = y * otherZ - z * otherY;
+        double resY = z * otherX - x * otherZ;
+        double resZ = x * otherY - y * otherX;
+        result.set(resX, resY, resZ);
+        return result;
+    }
+
+    /**
+     * <code>crossLocal</code> calculates the cross product of this vector
+     * with a parameter vector v.
+     *
+     * @param v
+     *            the vector to take the cross product of with this.
+     * @return this.
+     */
+    public Vector3d crossLocal(Vector3d v) {
+        return this.crossLocal(v.x, v.y, v.z);
+    }
+
+    /**
+     * <code>crossLocal</code> calculates the cross product of this vector
+     * with a parameter vector v.
+     *
+     * @param otherX
+     *            x component of the vector to take the cross product of with this.
+     * @param otherY
+     *            y component of the vector to take the cross product of with this.
+     * @param otherZ
+     *            z component of the vector to take the cross product of with this.
+     * @return this.
+     */
+    public Vector3d crossLocal(double otherX, double otherY, double otherZ) {
+        double tempx = y * otherZ - z * otherY;
+        double tempy = z * otherX - x * otherZ;
+        z = x * otherY - y * otherX;
+        x = tempx;
+        y = tempy;
+        return this;
+    }
+
+    /**
+     * <code>length</code> calculates the magnitude of this vector.
+     *
+     * @return the length or magnitude of the vector.
+     */
+    public double length() {
+        return Math.sqrt(this.lengthSquared());
+    }
+
+    /**
+     * <code>lengthSquared</code> calculates the squared value of the
+     * magnitude of the vector.
+     *
+     * @return the magnitude squared of the vector.
+     */
+    public double lengthSquared() {
+        return x * x + y * y + z * z;
+    }
+
+    /**
+     * <code>distanceSquared</code> calculates the distance squared between
+     * this vector and vector v.
+     *
+     * @param v
+     *            the second vector to determine the distance squared.
+     * @return the distance squared between the two vectors.
+     */
+    public double distanceSquared(Vector3d v) {
+        double dx = x - v.x;
+        double dy = y - v.y;
+        double dz = z - v.z;
+        return dx * dx + dy * dy + dz * dz;
+    }
+
+    /**
+     * <code>distance</code> calculates the distance between this vector and
+     * vector v.
+     *
+     * @param v
+     *            the second vector to determine the distance.
+     * @return the distance between the two vectors.
+     */
+    public double distance(Vector3d v) {
+        return Math.sqrt(this.distanceSquared(v));
+    }
+
+    /**
+     *
+     * <code>mult</code> multiplies this vector by a scalar. The resultant
+     * vector is returned.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return the new vector.
+     */
+    public Vector3d mult(double scalar) {
+        return new Vector3d(x * scalar, y * scalar, z * scalar);
+    }
+
+    /**
+     *
+     * <code>mult</code> multiplies this vector by a scalar. The resultant
+     * vector is supplied as the second parameter and returned.
+     *
+     * @param scalar
+     *            the scalar to multiply this vector by.
+     * @param product
+     *            the product to store the result in.
+     * @return product
+     */
+    public Vector3d mult(double scalar, Vector3d product) {
+        if (null == product) {
+            product = new Vector3d();
+        }
+
+        product.x = x * scalar;
+        product.y = y * scalar;
+        product.z = z * scalar;
+        return product;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls.
+     *
+     * @param scalar
+     *            the value to multiply this vector by.
+     * @return this
+     */
+    public Vector3d multLocal(double scalar) {
+        x *= scalar;
+        y *= scalar;
+        z *= scalar;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @return this
+     */
+    public Vector3d multLocal(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x *= vec.x;
+        y *= vec.y;
+        z *= vec.z;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies this vector by 3 scalars
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param x
+     * @param y
+     * @param z
+     * @return this
+     */
+    public Vector3d multLocal(double x, double y, double z) {
+        this.x *= x;
+        this.y *= y;
+        this.z *= z;
+        return this;
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @return this
+     */
+    public Vector3d mult(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        return this.mult(vec, null);
+    }
+
+    /**
+     * <code>multLocal</code> multiplies a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to mult to this vector.
+     * @param store
+     *            result vector (null to create a new vector)
+     * @return this
+     */
+    public Vector3d mult(Vector3d vec, Vector3d store) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        if (store == null) {
+            store = new Vector3d();
+        }
+        return store.set(x * vec.x, y * vec.y, z * vec.z);
+    }
+
+    /**
+     * <code>divide</code> divides the values of this vector by a scalar and
+     * returns the result. The values of this vector remain untouched.
+     *
+     * @param scalar
+     *            the value to divide this vectors attributes by.
+     * @return the result <code>Vector</code>.
+     */
+    public Vector3d divide(double scalar) {
+        scalar = 1f / scalar;
+        return new Vector3d(x * scalar, y * scalar, z * scalar);
+    }
+
+    /**
+     * <code>divideLocal</code> divides this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls. Dividing
+     * by zero will result in an exception.
+     *
+     * @param scalar
+     *            the value to divides this vector by.
+     * @return this
+     */
+    public Vector3d divideLocal(double scalar) {
+        scalar = 1f / scalar;
+        x *= scalar;
+        y *= scalar;
+        z *= scalar;
+        return this;
+    }
+
+    /**
+     * <code>divide</code> divides the values of this vector by a scalar and
+     * returns the result. The values of this vector remain untouched.
+     *
+     * @param scalar
+     *            the value to divide this vectors attributes by.
+     * @return the result <code>Vector</code>.
+     */
+    public Vector3d divide(Vector3d scalar) {
+        return new Vector3d(x / scalar.x, y / scalar.y, z / scalar.z);
+    }
+
+    /**
+     * <code>divideLocal</code> divides this vector by a scalar internally,
+     * and returns a handle to this vector for easy chaining of calls. Dividing
+     * by zero will result in an exception.
+     *
+     * @param scalar
+     *            the value to divides this vector by.
+     * @return this
+     */
+    public Vector3d divideLocal(Vector3d scalar) {
+        x /= scalar.x;
+        y /= scalar.y;
+        z /= scalar.z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>negate</code> returns the negative of this vector. All values are
+     * negated and set to a new vector.
+     *
+     * @return the negated vector.
+     */
+    public Vector3d negate() {
+        return new Vector3d(-x, -y, -z);
+    }
+
+    /**
+     *
+     * <code>negateLocal</code> negates the internal values of this vector.
+     *
+     * @return this.
+     */
+    public Vector3d negateLocal() {
+        x = -x;
+        y = -y;
+        z = -z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>subtract</code> subtracts the values of a given vector from those
+     * of this vector creating a new vector object. If the provided vector is
+     * null, null is returned.
+     *
+     * @param vec
+     *            the vector to subtract from this vector.
+     * @return the result vector.
+     */
+    public Vector3d subtract(Vector3d vec) {
+        return new Vector3d(x - vec.x, y - vec.y, z - vec.z);
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts a provided vector to this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls. If the provided vector is null, null is returned.
+     *
+     * @param vec
+     *            the vector to subtract
+     * @return this
+     */
+    public Vector3d subtractLocal(Vector3d vec) {
+        if (null == vec) {
+            LOGGER.warning("Provided vector is null, null returned.");
+            return null;
+        }
+        x -= vec.x;
+        y -= vec.y;
+        z -= vec.z;
+        return this;
+    }
+
+    /**
+     *
+     * <code>subtract</code>
+     *
+     * @param vec
+     *            the vector to subtract from this
+     * @param result
+     *            the vector to store the result in
+     * @return result
+     */
+    public Vector3d subtract(Vector3d vec, Vector3d result) {
+        if (result == null) {
+            result = new Vector3d();
+        }
+        result.x = x - vec.x;
+        result.y = y - vec.y;
+        result.z = z - vec.z;
+        return result;
+    }
+
+    /**
+     *
+     * <code>subtract</code> subtracts the provided values from this vector,
+     * creating a new vector that is then returned.
+     *
+     * @param subtractX
+     *            the x value to subtract.
+     * @param subtractY
+     *            the y value to subtract.
+     * @param subtractZ
+     *            the z value to subtract.
+     * @return the result vector.
+     */
+    public Vector3d subtract(double subtractX, double subtractY, double subtractZ) {
+        return new Vector3d(x - subtractX, y - subtractY, z - subtractZ);
+    }
+
+    /**
+     * <code>subtractLocal</code> subtracts the provided values from this vector
+     * internally, and returns a handle to this vector for easy chaining of
+     * calls.
+     *
+     * @param subtractX
+     *            the x value to subtract.
+     * @param subtractY
+     *            the y value to subtract.
+     * @param subtractZ
+     *            the z value to subtract.
+     * @return this
+     */
+    public Vector3d subtractLocal(double subtractX, double subtractY, double subtractZ) {
+        x -= subtractX;
+        y -= subtractY;
+        z -= subtractZ;
+        return this;
+    }
+
+    /**
+     * <code>normalize</code> returns the unit vector of this vector.
+     *
+     * @return unit vector of this vector.
+     */
+    public Vector3d normalize() {
+        double length = x * x + y * y + z * z;
+        if (length != 1f && length != 0f) {
+            length = 1.0f / Math.sqrt(length);
+            return new Vector3d(x * length, y * length, z * length);
+        }
+        return this.clone();
+    }
+
+    /**
+     * <code>normalizeLocal</code> makes this vector into a unit vector of
+     * itself.
+     *
+     * @return this.
+     */
+    public Vector3d normalizeLocal() {
+        // NOTE: this implementation is more optimized
+        // than the old jme normalize as this method
+        // is commonly used.
+        double length = x * x + y * y + z * z;
+        if (length != 1f && length != 0f) {
+            length = 1.0f / Math.sqrt(length);
+            x *= length;
+            y *= length;
+            z *= length;
+        }
+        return this;
+    }
+
+    /**
+     * <code>angleBetween</code> returns (in radians) the angle between two vectors.
+     * It is assumed that both this vector and the given vector are unit vectors (iow, normalized).
+     * 
+     * @param otherVector
+     *            a unit vector to find the angle against
+     * @return the angle in radians.
+     */
+    public double angleBetween(Vector3d otherVector) {
+        double dot = this.dot(otherVector);
+        // the vectors are normalized, but if they are parallel then the dot product migh get a value like: 1.000000000000000002
+        // which is caused by floating point operations; in such case, the acos function will return NaN so we need to clamp this value
+        dot = FastMath.clamp((float) dot, -1, 1);
+        return Math.acos(dot);
+    }
+
+    @Override
+    public Vector3d clone() {
+        try {
+            return (Vector3d) super.clone();
+        } catch (CloneNotSupportedException e) {
+            throw new AssertionError(); // can not happen
+        }
+    }
+
+    /**
+     * are these two vectors the same? they are is they both have the same x,y,
+     * and z values.
+     *
+     * @param o
+     *            the object to compare for equality
+     * @return true if they are equal
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Vector3d)) {
+            return false;
+        }
+
+        if (this == o) {
+            return true;
+        }
+
+        Vector3d comp = (Vector3d) o;
+        if (Double.compare(x, comp.x) != 0) {
+            return false;
+        }
+        if (Double.compare(y, comp.y) != 0) {
+            return false;
+        }
+        if (Double.compare(z, comp.z) != 0) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * <code>hashCode</code> returns a unique code for this vector object based
+     * on it's values. If two vectors are logically equivalent, they will return
+     * the same hash code value.
+     * @return the hash code value of this vector.
+     */
+    @Override
+    public int hashCode() {
+        long hash = 37;
+        hash += 37 * hash + Double.doubleToLongBits(x);
+        hash += 37 * hash + Double.doubleToLongBits(y);
+        hash += 37 * hash + Double.doubleToLongBits(z);
+        return (int) hash;
+    }
+
+    /**
+     * <code>toString</code> returns the string representation of this vector.
+     * The format is:
+     *
+     * org.jme.math.Vector3d [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ]
+     *
+     * @return the string representation of this vector.
+     */
+    @Override
+    public String toString() {
+        return "(" + x + ", " + y + ", " + z + ")";
+    }
+
+    public void write(JmeExporter e) throws IOException {
+        OutputCapsule capsule = e.getCapsule(this);
+        capsule.write(x, "x", 0);
+        capsule.write(y, "y", 0);
+        capsule.write(z, "z", 0);
+    }
+
+    public void read(JmeImporter e) throws IOException {
+        InputCapsule capsule = e.getCapsule(this);
+        x = capsule.readDouble("x", 0);
+        y = capsule.readDouble("y", 0);
+        z = capsule.readDouble("z", 0);
+    }
+}