From c9eadcc762584137a2201256410fffed96c628b4 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 21 Dec 2014 17:54:39 +0100 Subject: [PATCH] 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 :) --- .../blender/constraints/ConstraintHelper.java | 37 +- .../blender/constraints/SimulationNode.java | 366 ++------ .../definitions/ConstraintDefinitionIK.java | 55 +- .../plugins/blender/math/DQuaternion.java | 453 +++++++++ .../plugins/blender/math/DTransform.java | 170 ++++ .../scene/plugins/blender/math/Vector3d.java | 867 ++++++++++++++++++ 6 files changed, 1633 insertions(+), 315 deletions(-) create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java create mode 100644 jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index 1624e36cb..6d3058ed2 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/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 simulationRootNodes = new ArrayList(); + Set owners = new HashSet(); 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 simulationRootNodes = new ArrayList(owners.size()); + for(Long ownerOMA : owners) { + simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext)); + } for (SimulationNode node : simulationRootNodes) { node.simulate(); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java index 458deee46..031676b94 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ b/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 children = new ArrayList(); - /** A list of constraints that the current node has. */ - private List constraints; /** A list of node's animations. */ private List animations; @@ -68,7 +64,7 @@ public class SimulationNode { private Transform spatialStartTransform; /** Star transformations for bones. Needed to properly reset the bones. */ private Map 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(); - } - // 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 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 validConstraints = new ArrayList(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 true if the constraint already is stored in the node and - * false 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 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 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 alteredOmas = new HashSet(); - - if (animations != null) { - TempVars vars = TempVars.get(); - AnimChannel animChannel = animControl.createChannel(); - - List bonesWithConstraints = this.collectBonesWithConstraints(skeleton); - for (Animation animation : animations) { - float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); - int maxFrame = (int) animationTimeBoundaries[0]; - float maxTime = animationTimeBoundaries[1]; - - Map tracks = new HashMap(); - 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 alteredOmas = new HashSet(); + + if (animations != null) { + TempVars vars = TempVars.get(); + AnimChannel animChannel = animControl.createChannel(); + + // List bonesWithConstraints = this.collectBonesWithConstraints(skeleton); + for (Animation animation : animations) { + float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); + int maxFrame = (int) animationTimeBoundaries[0]; + float maxTime = animationTimeBoundaries[1]; + + Map tracks = new HashMap(); + 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 applied = new HashSet(); + 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 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 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 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 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 alteredOmas, int frame) { + private void applyConstraints(Bone bone, Set alteredOmas, Set applied, int frame) { BoneContext boneContext = blenderContext.getBoneContext(bone); - List 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 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 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 collectBonesWithConstraints(Skeleton skeleton) { - Map> bonesWithConstraints = new HashMap>(); - 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 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 bonesToRemove = new ArrayList(bonesWithConstraints.size()); - for (Entry> entry : bonesWithConstraints.entrySet()) { - List validConstraints = new ArrayList(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 bonesConstrainedWithoutTarget = new ArrayList(); - Set remainedOMAS = new HashSet(); - // later move all bones with not dependant constraints to the front - bonesToRemove.clear(); - for (Entry> 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 bonesConstrainedWithTarget = new ArrayList(); - do { - bonesToRemove.clear(); - for (Entry> 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 result = new ArrayList(); - 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 bones) { - Map> branches = new HashMap>(); - - for (BoneContext bone : bones) { - BoneContext root = bone.getRoot(); - List list = branches.get(root); - if (list == null) { - list = new ArrayList(); - branches.put(root, list); - } - list.add(bone); - } - - // sort the bones in each branch from root to leaf - bones.clear(); - for (Entry> entry : branches.entrySet()) { - Collections.sort(entry.getValue(), new Comparator() { - @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 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; diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index dc63ae42b..1d6139e04 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/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 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); } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java new file mode 100644 index 000000000..359abf057 --- /dev/null +++ b/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; + +/** + * DQuaternion 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. + * + * DQuaternion 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 DQuaternion object + * initializing all values to zero, except w which is initialized to 1. + * + */ + public DQuaternion() { + } + + /** + * Constructor instantiates a new DQuaternion 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 DQuaternion 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 DQuaternion object to be equal to the + * passed DQuaternion 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; + } + + /** + * fromAngleAxis 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; + } + + /** + * fromAngleNormalAxis 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; + } + + /** + * add 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); + } + + /** + * add 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; + } + + /** + * subtract 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); + } + + /** + * subtract 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; + } + + /** + * mult 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); + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * mult 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; + } + + /** + * + * toString creates the string representation of this DQuaternion. The values of the quaternion are displaced (x, + * y, z, w), in the following manner:
+ * (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 + ")"; + } + + /** + * equals 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; + } + + /** + * + * hashCode 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 + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java new file mode 100644 index 000000000..ed31a4c98 --- /dev/null +++ b/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
+ *
+ * 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(); + } + } +} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java new file mode 100644 index 000000000..f2a3c8075 --- /dev/null +++ b/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 + */ + +/** + * Vector3d defines a Vector for a three float value tuple. Vector3d 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 Vector3d with default + * values of (0,0,0). + * + */ + public Vector3d() { + } + + /** + * Constructor instantiates a new Vector3d 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 Vector3d 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); + } + + /** + * set 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; + } + + /** + * set 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); + } + + /** + * + * add 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); + } + + /** + * + * add 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; + } + + /** + * addLocal 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; + } + + /** + * + * add 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); + } + + /** + * addLocal 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * scaleAdd 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; + } + + /** + * + * dot 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; + } + + /** + * cross 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); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @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); + } + + /** + * cross calculates the cross product of this vector with a + * parameter vector v. The result is stored in result + * + * @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; + } + + /** + * crossLocal 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); + } + + /** + * crossLocal 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; + } + + /** + * length calculates the magnitude of this vector. + * + * @return the length or magnitude of the vector. + */ + public double length() { + return Math.sqrt(this.lengthSquared()); + } + + /** + * lengthSquared 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; + } + + /** + * distanceSquared 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; + } + + /** + * distance 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)); + } + + /** + * + * mult 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); + } + + /** + * + * mult 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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; + } + + /** + * multLocal 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); + } + + /** + * multLocal 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); + } + + /** + * divide 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 Vector. + */ + public Vector3d divide(double scalar) { + scalar = 1f / scalar; + return new Vector3d(x * scalar, y * scalar, z * scalar); + } + + /** + * divideLocal 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; + } + + /** + * divide 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 Vector. + */ + public Vector3d divide(Vector3d scalar) { + return new Vector3d(x / scalar.x, y / scalar.y, z / scalar.z); + } + + /** + * divideLocal 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; + } + + /** + * + * negate 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); + } + + /** + * + * negateLocal negates the internal values of this vector. + * + * @return this. + */ + public Vector3d negateLocal() { + x = -x; + y = -y; + z = -z; + return this; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * + * subtract + * + * @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; + } + + /** + * + * subtract 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); + } + + /** + * subtractLocal 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; + } + + /** + * normalize 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(); + } + + /** + * normalizeLocal 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; + } + + /** + * angleBetween 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; + } + + /** + * hashCode 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; + } + + /** + * toString 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); + } +}