diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java index a786e8ad4..b858c9727 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java @@ -46,8 +46,8 @@ import com.jme3.asset.AssetManager; import com.jme3.asset.BlenderKey; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.scene.Node; import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.DnaBlockData; @@ -101,11 +101,6 @@ public class BlenderContext { private Map loadedFeaturesByName = new HashMap(); /** A stack that hold the parent structure of currently loaded feature. */ private Stack parentStack = new Stack(); - /** - * A map storing loaded ipos. The key is the ipo's owner old memory address - * and the value is the ipo. - */ - private Map loadedIpos = new HashMap(); /** A list of modifiers for the specified object. */ protected Map> modifiers = new HashMap>(); /** A list of constraints for the specified object. */ @@ -114,6 +109,8 @@ public class BlenderContext { private Map animData = new HashMap(); /** Loaded skeletons. */ private Map skeletons = new HashMap(); + /** A map between skeleton and node it modifies. */ + private Map nodesWithSkeletons = new HashMap(); /** A map of mesh contexts. */ protected Map meshContexts = new HashMap(); /** A map of bone contexts. */ @@ -345,25 +342,6 @@ public class BlenderContext { return null; } - /** - * This method returns the feature of a given name. If the feature is not - * yet loaded then null is returned. - * - * @param featureName - * the name of the feature - * @param loadedFeatureDataType - * the type of data we want to retreive it can be either filled - * structure or already converted feature - * @return loaded feature or null if it was not yet loaded - */ - public Object getLoadedFeature(String featureName, LoadedFeatureDataType loadedFeatureDataType) { - Object[] result = loadedFeaturesByName.get(featureName); - if (result != null) { - return result[loadedFeatureDataType.getIndex()]; - } - return null; - } - /** * This method clears the saved features stored in the features map. */ @@ -408,38 +386,6 @@ public class BlenderContext { } } - /** - * This method adds new ipo curve for the feature. - * - * @param ownerOMA - * the OMA of blender feature that owns the ipo - * @param ipo - * the ipo to be added - */ - public void addIpo(Long ownerOMA, Ipo ipo) { - loadedIpos.put(ownerOMA, ipo); - } - - /** - * This method removes the ipo curve from the feature. - * - * @param ownerOma - * the OMA of blender feature that owns the ipo - */ - public Ipo removeIpo(Long ownerOma) { - return loadedIpos.remove(ownerOma); - } - - /** - * This method returns the ipo curve of the feature. - * - * @param ownerOMA - * the OMA of blender feature that owns the ipo - */ - public Ipo getIpo(Long ownerOMA) { - return loadedIpos.get(ownerOMA); - } - /** * This method adds a new modifier to the list. * @@ -499,18 +445,6 @@ public class BlenderContext { objectConstraints.addAll(constraints); } - /** - * This method returns constraints for the object specified by its old - * memory address. If no modifiers are found - null is returned. - * - * @param objectOMA - * object's old memory address - * @return the list of object's modifiers or null - */ - public List getConstraints(Long objectOMA) { - return objectOMA == null ? null : constraints.get(objectOMA); - } - /** * @return all available constraints */ @@ -557,6 +491,30 @@ public class BlenderContext { this.skeletons.put(skeletonOMA, skeleton); } + /** + * The method stores a binding between the skeleton and the proper armature + * node. + * + * @param skeleton + * the skeleton + * @param node + * the armature node + */ + public void setNodeForSkeleton(Skeleton skeleton, Node node) { + nodesWithSkeletons.put(skeleton, node); + } + + /** + * This method returns the armature node that is defined for the skeleton. + * + * @param skeleton + * the skeleton + * @return the armature node that defines the skeleton in blender + */ + public Node getControlledNode(Skeleton skeleton) { + return nodesWithSkeletons.get(skeleton); + } + /** * This method returns the skeleton for the specified OMA of its owner. * @@ -635,6 +593,22 @@ public class BlenderContext { return null; } + /** + * Returns bone context for the given bone. + * + * @param bone + * the bone + * @return the bone's bone context + */ + public BoneContext getBoneContext(Bone bone) { + for (Entry entry : boneContexts.entrySet()) { + if (entry.getValue().getBone().equals(bone)) { + return entry.getValue(); + } + } + throw new IllegalStateException("Cannot find context for bone: " + bone); + } + /** * This metod returns the default material. * @@ -658,7 +632,6 @@ public class BlenderContext { loadedFeatures.clear(); loadedFeaturesByName.clear(); parentStack.clear(); - loadedIpos.clear(); modifiers.clear(); constraints.clear(); animData.clear(); @@ -672,7 +645,7 @@ public class BlenderContext { * This enum defines what loaded data type user wants to retreive. It can be * either filled structure or already converted data. * - * @author Marcin Roguski + * @author Marcin Roguski (Kaelthas) */ public static enum LoadedFeatureDataType { diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java index 0f45f001c..e98890cf1 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Map; import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -19,6 +20,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper; * @author Marcin Roguski (Kaelthas) */ public class BoneContext { + private BlenderContext blenderContext; /** The OMA of the bone's armature object. */ private Long armatureObjectOMA; /** The structure of the bone. */ @@ -30,7 +32,7 @@ public class BoneContext { /** The parent context. */ private BoneContext parent; /** The children of this context. */ - private List children = new ArrayList(); + private List children = new ArrayList(); /** Created bone (available after calling 'buildBone' method). */ private Bone bone; /** The bone's rest matrix. */ @@ -74,21 +76,22 @@ public class BoneContext { */ private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException { this.parent = parent; + this.blenderContext = blenderContext; this.boneStructure = boneStructure; this.armatureObjectOMA = armatureObjectOMA; boneName = boneStructure.getFieldValue("name").toString(); length = ((Number) boneStructure.getFieldValue("length")).floatValue(); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis()); - - //compute the bone's rest matrix + + // compute the bone's rest matrix restMatrix = armatureMatrix.clone(); inverseTotalTransformation = restMatrix.invert(); - if(parent != null) { + if (parent != null) { restMatrix = parent.inverseTotalTransformation.mult(restMatrix); } - - //create the children + + // create the children List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext); for (Structure child : childbase) { this.children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext)); @@ -97,7 +100,6 @@ public class BoneContext { blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); } - /** * This method builds the bone. It recursively builds the bone's children. * @@ -118,14 +120,12 @@ public class BoneContext { boneOMAs.put(bone, boneOMA); blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Vector3f poseLocation = restMatrix.toTranslationVector(); Quaternion rotation = restMatrix.toRotationQuat().normalizeLocal(); - Vector3f scale = objectHelper.getScale(restMatrix); - if(parent == null) { - Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal(); - scale.multLocal(objectHelper.getScale(objectToArmatureMatrix)); + Vector3f scale = restMatrix.toScaleVector(); + if (parent == null) { + Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal(); + scale.multLocal(objectToArmatureMatrix.toScaleVector()); rotationQuaternion.multLocal(poseLocation.addLocal(objectToArmatureMatrix.toTranslationVector())); rotation.multLocal(rotationQuaternion); } @@ -165,4 +165,11 @@ public class BoneContext { public Long getArmatureObjectOMA() { return armatureObjectOMA; } + + /** + * @return the skeleton the bone of this context belongs to + */ + public Skeleton getSkeleton() { + return blenderContext.getSkeleton(armatureObjectOMA); + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java index 790269262..a4c3b243d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java @@ -40,7 +40,10 @@ public class Ipo { private Track calculatedTrack; /** This variable indicates if the Y asxis is the UP axis or not. */ protected boolean fixUpAxis; - /** Depending on the blender version rotations are stored in degrees or radians so we need to know the version that is used. */ + /** + * Depending on the blender version rotations are stored in degrees or + * radians so we need to know the version that is used. + */ protected final int blenderVersion; /** @@ -158,7 +161,8 @@ public class Ipo { // calculating track data for (int frame = startFrame; frame <= stopFrame; ++frame) { int index = frame - startFrame; - times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames; + times[index] = index * timeBetweenFrames;// start + (frame - 1) + // * timeBetweenFrames; for (int j = 0; j < bezierCurves.length; ++j) { double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); switch (bezierCurves[j].getType()) { @@ -168,7 +172,7 @@ public class Ipo { break; case AC_LOC_Y: if (fixUpAxis) { - translation[2] = (float) -value; + translation[2] = value == 0.0f ? 0 : (float) -value; } else { translation[1] = (float) value; } @@ -185,7 +189,7 @@ public class Ipo { break; case OB_ROT_Y: if (fixUpAxis) { - objectRotation[2] = (float) -value * degreeToRadiansFactor; + objectRotation[2] = value == 0.0f ? 0 : (float) -value * degreeToRadiansFactor; } else { objectRotation[1] = (float) value * degreeToRadiansFactor; } @@ -199,11 +203,7 @@ public class Ipo { scale[0] = (float) value; break; case AC_SIZE_Y: - if (fixUpAxis) { - scale[2] = (float) value; - } else { - scale[1] = (float) value; - } + scale[fixUpAxis ? 2 : 1] = (float) value; break; case AC_SIZE_Z: scale[fixUpAxis ? 1 : 2] = (float) value; @@ -220,17 +220,13 @@ public class Ipo { break; case AC_QUAT_Y: if (fixUpAxis) { - quaternionRotation[2] = -(float) value; + quaternionRotation[2] = value == 0.0f ? 0 : -(float) value; } else { quaternionRotation[1] = (float) value; } break; case AC_QUAT_Z: - if (fixUpAxis) { - quaternionRotation[1] = (float) value; - } else { - quaternionRotation[2] = (float) value; - } + quaternionRotation[fixUpAxis ? 1 : 2] = (float) value; break; default: LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType()); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java index a207c4f22..6143f53d8 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java @@ -3,10 +3,6 @@ package com.jme3.scene.plugins.blender.constraints; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Track; import com.jme3.math.Transform; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext; @@ -14,13 +10,12 @@ import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.animations.ArmatureHelper; import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.ogre.AnimData; /** * Constraint applied on the bone. + * * @author Marcin Roguski (Kaelthas) */ /* package */class BoneConstraint extends Constraint { @@ -47,12 +42,14 @@ import com.jme3.scene.plugins.ogre.AnimData; } @Override - protected boolean validate() { + public boolean validate() { if (targetOMA != null) { Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); - // the second part of the if expression verifies if the found node (if any) is an armature node + // the second part of the if expression verifies if the found node + // (if any) is an armature node if (nodeTarget == null || nodeTarget.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { - // if the target is not an object node then it is an Armature, so make sure the bone is in the current skeleton + // if the target is not an object node then it is an Armature, + // so make sure the bone is in the current skeleton BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); @@ -62,120 +59,24 @@ import com.jme3.scene.plugins.ogre.AnimData; isNodeTarget = true; } } - return true; } @Override - public void performBakingOperation() { - Bone owner = blenderContext.getBoneContext(ownerOMA).getBone(); - + public void apply(int frame) { + BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); + Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); if (targetOMA != null) { if (isNodeTarget) { - Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if (animData != null) { - for (Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Transform targetTransform = constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext); - - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - Track targetTrack = constraintHelper.getTrack(target, animation); - - constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); - } - } + Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; + constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); } else { - BoneContext boneContext = blenderContext.getBoneByName(subtargetName); - Bone target = boneContext.getBone(); - this.targetOMA = boneContext.getBoneOma(); - - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if (animData != null) { - for (Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Transform targetTransform = constraintHelper.getBoneTransform(targetSpace, target); - - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - Track targetTrack = constraintHelper.getTrack(target, animData.skeleton, animation); - - constraintDefinition.bake(ownerTransform, targetTransform, boneTrack, targetTrack, this.ipo); - } - } + Transform targetTransform = constraintHelper.getTransform(targetOMA, subtargetName, targetSpace); + constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); } } else { - this.prepareTracksForApplyingConstraints(); - AnimData animData = blenderContext.getAnimData(ownerOMA); - if (animData != null) { - for (Animation animation : animData.anims) { - Transform ownerTransform = constraintHelper.getBoneTransform(ownerSpace, owner); - Track boneTrack = constraintHelper.getTrack(owner, animData.skeleton, animation); - - constraintDefinition.bake(ownerTransform, null, boneTrack, null, this.ipo); - } - } - } - } - - @Override - protected void prepareTracksForApplyingConstraints() { - Long[] bonesOMAs = new Long[] { ownerOMA, targetOMA }; - Space[] spaces = new Space[] { ownerSpace, targetSpace }; - - // creating animations for current objects if at least on of their parents have an animation - for (int i = 0; i < bonesOMAs.length; ++i) { - Long oma = bonesOMAs[i]; - if (this.hasAnimation(oma)) { - Bone currentBone = blenderContext.getBoneContext(oma).getBone(); - Bone parent = currentBone.getParent(); - boolean foundAnimation = false; - AnimData animData = null; - while (parent != null && !foundAnimation) { - BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); - foundAnimation = this.hasAnimation(boneContext.getBoneOma()); - animData = blenderContext.getAnimData(boneContext.getBoneOma()); - parent = parent.getParent(); - } - - if (foundAnimation) { - this.applyAnimData(blenderContext.getBoneContext(oma), spaces[i], animData); - } - } - } - - // creating animation for owner if it doesn't have one already and if the target has it - if (!this.hasAnimation(ownerOMA) && this.hasAnimation(targetOMA)) { - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - this.applyAnimData(blenderContext.getBoneContext(ownerOMA), ownerSpace, targetAnimData); - } - } - - /** - * The method determines if the bone has animations. - * - * @param animOwnerOMA - * OMA of the animation's owner - * @return true if the target has animations and false otherwise - */ - protected boolean hasAnimation(Long animOwnerOMA) { - AnimData animData = blenderContext.getAnimData(animOwnerOMA); - if (animData != null) { - if (!isNodeTarget) { - Bone bone = blenderContext.getBoneContext(animOwnerOMA).getBone(); - int boneIndex = animData.skeleton.getBoneIndex(bone); - for (Animation animation : animData.anims) { - for (Track track : animation.getTracks()) { - if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) { - return true; - } - } - } - } else { - return true; - } + constraintDefinition.bake(ownerTransform, null, this.ipo.calculateValue(frame)); } - return false; + constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java index 3a92e6b59..85d8ed7b6 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java @@ -1,17 +1,9 @@ package com.jme3.scene.plugins.blender.constraints; -import java.util.Arrays; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.animation.Animation; -import com.jme3.animation.BoneTrack; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition; @@ -19,7 +11,6 @@ import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefiniti import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.ogre.AnimData; /** * The implementation of a constraint. @@ -55,6 +46,8 @@ public abstract class Constraint { * the constraint's structure (bConstraint clss in blender 2.49). * @param ownerOMA * the old memory address of the constraint owner + * @param ownerType + * the type of the constraint owner * @param influenceIpo * the ipo curve of the influence factor * @param blenderContext @@ -69,101 +62,88 @@ public abstract class Constraint { Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); if (pData.isNotNull()) { Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, blenderContext); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, ownerOMA, blenderContext); Pointer pTar = (Pointer) data.getFieldValue("tar"); if (pTar != null && pTar.isNotNull()) { this.targetOMA = pTar.getOldMemoryAddress(); this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); Object subtargetValue = data.getFieldValue("subtarget"); - if (subtargetValue != null) {// not all constraint data have the subtarget field + if (subtargetValue != null) {// not all constraint data have the + // subtarget field subtargetName = subtargetValue.toString(); } } } else { // Null constraint has no data, so create it here - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, blenderContext); + constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, null, blenderContext); } this.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); this.ipo = influenceIpo; this.ownerOMA = ownerOMA; this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); + LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition }); } /** - * This method bakes the required sontraints into its owner. It checks if the constraint is invalid - * or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking - * order is kept. + * @return true if the constraint is implemented and false + * otherwise */ - public void bake() { - if (!this.validate()) { - LOGGER.warning("The constraint " + name + " is invalid and will not be applied."); - } else if (!baked) { - if (targetOMA != null) { - List targetConstraints = blenderContext.getConstraints(targetOMA); - if (targetConstraints != null && targetConstraints.size() > 0) { - LOGGER.log(Level.FINE, "Baking target constraints of constraint: {0}", name); - for (Constraint targetConstraint : targetConstraints) { - targetConstraint.bake(); - } - } - } - - LOGGER.log(Level.FINE, "Performing baking of constraint: {0}", name); - this.performBakingOperation(); - baked = true; - } + public boolean isImplemented() { + return constraintDefinition == null ? true : constraintDefinition.isImplemented(); } /** - * Performs validation before baking. Checks factors that can prevent constraint from baking that could not be - * checked during constraint loading. + * @return the name of the constraint type, similar to the constraint name + * used in Blender */ - protected abstract boolean validate(); + public String getConstraintTypeName() { + return constraintDefinition.getConstraintTypeName(); + } /** - * This method should be overwridden and perform the baking opertion. + * Performs validation before baking. Checks factors that can prevent + * constraint from baking that could not be checked during constraint + * loading. */ - protected abstract void performBakingOperation(); + public abstract boolean validate(); - /** - * This method prepares the tracks for both owner and parent. If either owner or parent have no track while its parent has - - * the tracks are created. The tracks will not modify the owner/target movement but will be there ready for applying constraints. - * For example if the owner is a spatial and has no animation but its parent is moving then the track is created for the owner - * that will have non modifying values for translation, rotation and scale and will have the same amount of frames as its parent has. - */ - protected abstract void prepareTracksForApplyingConstraints(); - - /** - * The method applies bone's current position to all of the traces of the - * given animations. - * - * @param boneContext - * the bone context - * @param space - * the bone's evaluation space - * @param referenceAnimData - * the object containing the animations - */ - protected void applyAnimData(BoneContext boneContext, Space space, AnimData referenceAnimData) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - Transform transform = constraintHelper.getBoneTransform(space, boneContext.getBone()); - - AnimData animData = blenderContext.getAnimData(boneContext.getBoneOma()); + public abstract void apply(int frame); - for (Animation animation : referenceAnimData.anims) { - BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0]; + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((ownerOMA == null) ? 0 : ownerOMA.hashCode()); + return result; + } - float[] times = parentTrack.getTimes(); - Vector3f[] translations = new Vector3f[times.length]; - Quaternion[] rotations = new Quaternion[times.length]; - Vector3f[] scales = new Vector3f[times.length]; - Arrays.fill(translations, transform.getTranslation()); - Arrays.fill(rotations, transform.getRotation()); - Arrays.fill(scales, transform.getScale()); - for (Animation anim : animData.anims) { - anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales)); + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + Constraint other = (Constraint) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (ownerOMA == null) { + if (other.ownerOMA != null) { + return false; } + } else if (!ownerOMA.equals(other.ownerOMA)) { + return false; } - blenderContext.setAnimData(boneContext.getBoneOma(), animData); + return true; } } \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java index 6ecef8cc1..ac1a26170 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java @@ -1,51 +1,65 @@ package com.jme3.scene.plugins.blender.constraints; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Logger; -import com.jme3.animation.Animation; import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; import com.jme3.animation.Skeleton; -import com.jme3.animation.SpatialTrack; -import com.jme3.animation.Track; +import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Transform; import com.jme3.math.Vector3f; +import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.animations.IpoHelper; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; /** * This class should be used for constraint calculations. + * * @author Marcin Roguski (Kaelthas) */ public class ConstraintHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); + private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); + + private static final Quaternion POS_POSE_SPACE_QUATERNION = new Quaternion(new float[] { FastMath.PI, 0, 0 }); + private static final Quaternion NEG_POSE_SPACE_QUATERNION = new Quaternion(new float[] { -FastMath.PI, 0, 0 }); + private static final Quaternion POS_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { FastMath.HALF_PI, 0, 0 }); + private static final Quaternion NEG_PARLOC_SPACE_QUATERNION = new Quaternion(new float[] { -FastMath.HALF_PI, 0, 0 }); + + private BlenderContext blenderContext; /** - * Helper constructor. It's main task is to generate the affection functions. These functions are common to all - * ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall - * consider refactoring. The constructor parses the given blender version and stores the result. Some - * functionalities may differ in different blender versions. + * Helper constructor. It's main task is to generate the affection + * functions. These functions are common to all ConstraintHelper instances. + * Unfortunately this constructor might grow large. If it becomes too large + * - I shall consider refactoring. The constructor parses the given blender + * version and stores the result. Some functionalities may differ in + * different blender versions. + * * @param blenderVersion * the version read from the blend file + * @param blenderContext + * the blender context * @param fixUpAxis * a variable that indicates if the Y asxis is the UP axis or not */ public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { super(blenderVersion, fixUpAxis); + this.blenderContext = blenderContext; } /** @@ -95,7 +109,8 @@ public class ConstraintHelper extends AbstractBlenderHelper { List constraintsList = new ArrayList(); Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); - // the name is read directly from structure because bone might not yet be loaded + // the name is read directly from structure because bone might + // not yet be loaded String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); for (Structure constraint : constraints) { @@ -130,7 +145,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { ipo = ipoHelper.fromValue(enforce); } - constraintsList.add(this.getConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); + constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); } blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); } @@ -155,7 +170,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { * @throws BlenderFileException * thrown when problems with blender file occured */ - private Constraint getConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { + private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); } else if ("Armature".equalsIgnoreCase(dataType)) { @@ -172,266 +187,243 @@ public class ConstraintHelper extends AbstractBlenderHelper { * the blender context */ public void bakeConstraints(BlenderContext blenderContext) { + List simulationRootNodes = new ArrayList(); for (Constraint constraint : blenderContext.getAllConstraints()) { - constraint.bake(); - } - } + boolean constraintUsed = false; + for (SimulationNode node : simulationRootNodes) { + if (node.contains(constraint)) { + constraintUsed = true; + break; + } + } - /** - * The method returns track for bone. - * - * @param bone - * the bone - * @param skeleton - * the bone's skeleton - * @param animation - * the bone's animation - * @return track for the given bone that was found among the given - * animations or null if none is found - */ - /* package */BoneTrack getTrack(Bone bone, Skeleton skeleton, Animation animation) { - int boneIndex = skeleton.getBoneIndex(bone); - for (Track track : animation.getTracks()) { - if (((BoneTrack) track).getTargetBoneIndex() == boneIndex) { - return (BoneTrack) track; + if (!constraintUsed) { + if (constraint instanceof BoneConstraint) { + BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA); + simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext)); + } else if (constraint instanceof SpatialConstraint) { + Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + while (spatial.getParent() != null) { + spatial = spatial.getParent(); + } + simulationRootNodes.add(new SimulationNode((Long) spatial.getUserData("oma"), blenderContext)); + } else { + throw new IllegalStateException("Unsupported constraint type: " + constraint); + } } } - return null; - } - /** - * The method returns track for spatial. - * - * @param bone - * the spatial - * @param animation - * the spatial's animation - * @return track for the given spatial that was found among the given - * animations or null if none is found - */ - /* package */SpatialTrack getTrack(Spatial spatial, Animation animation) { - Track[] tracks = animation.getTracks(); - if (tracks != null && tracks.length == 1) { - return (SpatialTrack) tracks[0]; + for (SimulationNode node : simulationRootNodes) { + node.simulate(); } - return null; } /** - * This method returns the transform read directly from the blender - * structure. This can be used to read transforms from one of the object - * types:
  • Spatial
  • Camera
  • Light + * The method retreives the transform from a feature in a given space. * + * @param oma + * the OMA of the feature (spatial or armature node) + * @param subtargetName + * the feature's subtarget (bone in a case of armature's node) * @param space - * the space where transform is evaluated - * @param spatialOMA - * the OMA of the object - * @param blenderContext - * the blender context - * @return the object's transform in a given space + * the space the transform is evaluated to + * @return thensform of a feature in a given space */ - @SuppressWarnings("unchecked") - /* package */Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE); - - DynamicArray locArray = ((DynamicArray) targetStructure.getFieldValue("loc")); - Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue()); - DynamicArray rotArray = ((DynamicArray) targetStructure.getFieldValue("rot")); - Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() }); - DynamicArray sizeArray = ((DynamicArray) targetStructure.getFieldValue("size")); - Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue()); - - if (blenderContext.getBlenderKey().isFixUpAxis()) { - float y = loc.y; - loc.y = loc.z; - loc.z = -y; - - y = rot.getY(); - float z = rot.getZ(); - rot.set(rot.getX(), z, -y, rot.getW()); - - y = size.y; - size.y = size.z; - size.z = y; - } + public Transform getTransform(Long oma, String subtargetName, Space space) { + Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); + boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null; + if (isArmature) { + BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName); + Bone bone = targetBoneContext.getBone(); + + switch (space) { + case CONSTRAINT_SPACE_WORLD: + Transform t = new Transform(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale()); + System.out.println("A: " + Arrays.toString(t.getRotation().toAngles(null))); + return t; + case CONSTRAINT_SPACE_LOCAL: + Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); + localTransform.setScale(bone.getLocalScale()); + return localTransform; + case CONSTRAINT_SPACE_POSE: + Node nodeWithAnimationControl = blenderContext.getControlledNode(targetBoneContext.getSkeleton()); + Matrix4f m = this.toMatrix(nodeWithAnimationControl.getWorldTransform()); + Matrix4f boneAgainstModifiedNodeMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale()); + Matrix4f boneWorldMatrix = m.multLocal(boneAgainstModifiedNodeMatrix); + + Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform()).invertLocal(); + Matrix4f r2 = armatureWorldMatrix.multLocal(boneWorldMatrix); + + Vector3f loc2 = r2.toTranslationVector(); + Quaternion rot2 = r2.toRotationQuat().normalizeLocal().multLocal(POS_POSE_SPACE_QUATERNION); + Vector3f scl2 = r2.toScaleVector(); + + return new Transform(loc2, rot2, scl2); + case CONSTRAINT_SPACE_PARLOCAL: + Matrix4f parentLocalMatrix = Matrix4f.IDENTITY; + if (bone.getParent() != null) { + Bone parent = bone.getParent(); + parentLocalMatrix = this.toMatrix(parent.getLocalPosition(), parent.getLocalRotation(), parent.getLocalScale()); + } else {// we need to clone it because otherwise we could + // spoil the IDENTITY matrix + parentLocalMatrix = parentLocalMatrix.clone(); + } + Matrix4f boneLocalMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale()); + Matrix4f result = parentLocalMatrix.multLocal(boneLocalMatrix); - Transform result = new Transform(loc, rot); - result.setScale(size); - return result; - case CONSTRAINT_SPACE_WORLD:// TODO: get it from the object structure ??? - Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); - if (feature instanceof Spatial) { + Vector3f loc = result.toTranslationVector(); + Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(NEG_PARLOC_SPACE_QUATERNION); + Vector3f scl = result.toScaleVector(); + return new Transform(loc, rot, scl); + default: + throw new IllegalStateException("Unknown space type: " + space); + } + } else { + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + return ((Spatial) feature).getLocalTransform(); + case CONSTRAINT_SPACE_WORLD: return ((Spatial) feature).getWorldTransform(); - } else if (feature instanceof Skeleton) { - LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null."); - return null; - } else { - throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light)."); - } - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + case CONSTRAINT_SPACE_PARLOCAL: + case CONSTRAINT_SPACE_POSE: + throw new IllegalStateException("Nodes can have only Local and World spaces applied!"); + default: + throw new IllegalStateException("Unknown space type: " + space); + } } } /** - * The method returns the transform for the given bone computed in the given + * Applies transform to a feature (bone or spatial). Computations transform + * the given transformation from the given space to the feature's local * space. * + * @param oma + * the OMA of the feature we apply transformation to + * @param subtargetName + * the name of the feature's subtarget (bone in case of armature) * @param space - * the computation space - * @param bone - * the bone we get the transform from - * @return the transform of the given bone - */ - /* package */Transform getBoneTransform(Space space, Bone bone) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - localTransform.setScale(bone.getLocalScale()); - return localTransform; - case CONSTRAINT_SPACE_WORLD: - Transform worldTransform = new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation()); - worldTransform.setScale(bone.getWorldBindScale()); - return worldTransform; - case CONSTRAINT_SPACE_POSE: - Transform poseTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - poseTransform.setScale(bone.getLocalScale()); - return poseTransform; - case CONSTRAINT_SPACE_PARLOCAL: - Transform parentLocalTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation()); - parentLocalTransform.setScale(bone.getLocalScale()); - return parentLocalTransform; - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * The method applies the transform for the given spatial, computed in the - * given space. - * - * @param spatial - * the spatial we apply the transform for - * @param space - * the computation space + * the space in which the given transform is to be applied * @param transform - * the transform being applied + * the transform we apply */ - /* package */void applyTransform(Spatial spatial, Space space, Transform transform) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - Transform ownerLocalTransform = spatial.getLocalTransform(); - ownerLocalTransform.getTranslation().addLocal(transform.getTranslation()); - ownerLocalTransform.getRotation().multLocal(transform.getRotation()); - ownerLocalTransform.getScale().multLocal(transform.getScale()); - break; - case CONSTRAINT_SPACE_WORLD: - Matrix4f m = this.getParentWorldTransformMatrix(spatial); - m.invertLocal(); - Matrix4f matrix = this.toMatrix(transform); - m.multLocal(matrix); - - float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20); - float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21); - float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22); - - transform.setTranslation(m.toTranslationVector()); - transform.setRotation(m.toRotationQuat()); - transform.setScale(scaleX, scaleY, scaleZ); - spatial.setLocalTransform(transform); - break; - case CONSTRAINT_SPACE_PARLOCAL: - case CONSTRAINT_SPACE_POSE: - throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object."); - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) { + Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); + boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null; + if (isArmature) { + Skeleton skeleton = blenderContext.getSkeleton(oma); + BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName); + Bone bone = targetBoneContext.getBone(); + + Node nodeControlledBySkeleton = blenderContext.getControlledNode(skeleton); + Transform nodeTransform = nodeControlledBySkeleton.getWorldTransform(); + Matrix4f invertedNodeMatrix = this.toMatrix(nodeTransform).invertLocal(); + + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); + break; + case CONSTRAINT_SPACE_WORLD: + System.out.println("B: " + Arrays.toString(transform.getRotation().toAngles(null))); + Matrix4f boneMatrix = this.toMatrix(transform); + Bone parent = bone.getParent(); + if (parent != null) { + Matrix4f invertedParentWorldMatrix = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale()).invertLocal(); + boneMatrix = invertedParentWorldMatrix.multLocal(boneMatrix); + } + + boneMatrix = invertedNodeMatrix.multLocal(boneMatrix); + bone.setBindTransforms(boneMatrix.toTranslationVector(), boneMatrix.toRotationQuat(), boneMatrix.toScaleVector()); + break; + case CONSTRAINT_SPACE_POSE: + Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform()); + Matrix4f bonePoseMatrix = this.toMatrix(transform); + Matrix4f boneWorldMatrix = armatureWorldMatrix.multLocal(bonePoseMatrix); + // now compute bone's local matrix + Matrix4f boneLocalMatrix = invertedNodeMatrix.multLocal(boneWorldMatrix); + + Vector3f loc2 = boneLocalMatrix.toTranslationVector(); + Quaternion rot2 = boneLocalMatrix.toRotationQuat().normalizeLocal().multLocal(new Quaternion(NEG_POSE_SPACE_QUATERNION)); + Vector3f scl2 = boneLocalMatrix.toScaleVector(); + bone.setBindTransforms(loc2, rot2, scl2); + break; + case CONSTRAINT_SPACE_PARLOCAL: + Matrix4f parentLocalMatrix = Matrix4f.IDENTITY; + if (bone.getParent() != null) { + parentLocalMatrix = this.toMatrix(bone.getParent().getLocalPosition(), bone.getParent().getLocalRotation(), bone.getParent().getLocalScale()); + parentLocalMatrix.invertLocal(); + } else {// we need to clone it because otherwise we could + // spoil the IDENTITY matrix + parentLocalMatrix = parentLocalMatrix.clone(); + } + Matrix4f m = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale()); + Matrix4f result = parentLocalMatrix.multLocal(m); + Vector3f loc = result.toTranslationVector(); + Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(POS_PARLOC_SPACE_QUATERNION); + Vector3f scl = result.toScaleVector(); + bone.setBindTransforms(loc, rot, scl); + break; + default: + throw new IllegalStateException("Invalid space type for target object: " + space.toString()); + } + } else if (feature instanceof Spatial) { + Spatial spatial = (Spatial) feature; + switch (space) { + case CONSTRAINT_SPACE_LOCAL: + spatial.getLocalTransform().set(transform); + break; + case CONSTRAINT_SPACE_WORLD: + if (spatial.getParent() == null) { + spatial.setLocalTransform(transform); + } else { + Transform parentWorldTransform = spatial.getParent().getWorldTransform(); + + Matrix4f parentMatrix = this.toMatrix(parentWorldTransform).invertLocal(); + Matrix4f m = this.toMatrix(transform); + m = m.multLocal(parentMatrix); + + transform.setTranslation(m.toTranslationVector()); + transform.setRotation(m.toRotationQuat()); + transform.setScale(m.toScaleVector()); + + spatial.setLocalTransform(transform); + } + break; + default: + throw new IllegalStateException("Invalid space type for spatial object: " + space.toString()); + } + } else { + throw new IllegalStateException("Constrained transformation can be applied only to Bone or Spatial feature!"); } } /** - * The method applies the transform for the given bone, computed in the - * given space. + * Converts given transform to the matrix. * - * @param bone - * the bone we apply the transform for - * @param space - * the computation space * @param transform - * the transform being applied - */ - /* package */void applyTransform(Bone bone, Space space, Transform transform) { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - break; - case CONSTRAINT_SPACE_WORLD: - Matrix4f m = this.getParentWorldTransformMatrix(bone); - // m.invertLocal(); - transform.setTranslation(m.mult(transform.getTranslation())); - transform.setRotation(m.mult(transform.getRotation(), null)); - transform.setScale(transform.getScale()); - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - // float x = FastMath.HALF_PI/2; - // float y = -FastMath.HALF_PI; - // float z = -FastMath.HALF_PI/2; - // bone.setBindTransforms(new Vector3f(0,0,0), new Quaternion().fromAngles(x, y, z), new Vector3f(1,1,1)); - break; - case CONSTRAINT_SPACE_PARLOCAL: - Vector3f parentLocalTranslation = bone.getLocalPosition().add(transform.getTranslation()); - Quaternion parentLocalRotation = bone.getLocalRotation().mult(transform.getRotation()); - bone.setBindTransforms(parentLocalTranslation, parentLocalRotation, transform.getScale()); - break; - case CONSTRAINT_SPACE_POSE: - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - break; - default: - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - } - - /** - * @return world transform matrix of the feature's parent or identity matrix - * if the feature has no parent - */ - private Matrix4f getParentWorldTransformMatrix(Spatial spatial) { - Matrix4f result = new Matrix4f(); - if (spatial.getParent() != null) { - Transform t = spatial.getParent().getWorldTransform(); - result.setTransform(t.getTranslation(), t.getScale(), t.getRotation().toRotationMatrix()); - } - return result; - } - - /** - * @return world transform matrix of the feature's parent or identity matrix - * if the feature has no parent + * the transform to be converted + * @return 4x4 matrix that represents the given transform */ - private Matrix4f getParentWorldTransformMatrix(Bone bone) { - Matrix4f result = new Matrix4f(); - Bone parent = bone.getParent(); - if (parent != null) { - result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix()); + private Matrix4f toMatrix(Transform transform) { + Matrix4f result = Matrix4f.IDENTITY; + if (transform != null) { + result = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale()); } return result; } /** - * Converts given transform to the matrix. + * Converts given transformation parameters into the matrix. * * @param transform * the transform to be converted - * @return 4x4 matri that represents the given transform + * @return 4x4 matrix that represents the given transformation parameters */ - private Matrix4f toMatrix(Transform transform) { - Matrix4f result = Matrix4f.IDENTITY; - if (transform != null) { - result = new Matrix4f(); - result.setTranslation(transform.getTranslation()); - result.setRotationQuaternion(transform.getRotation()); - result.setScale(transform.getScale()); - } + private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale) { + Matrix4f result = new Matrix4f(); + result.setTranslation(position); + result.setRotationQuaternion(rotation); + result.setScale(scale); return result; } @@ -447,7 +439,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { */ public static enum Space { - CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL, CONSTRAINT_SPACE_INVALID; + CONSTRAINT_SPACE_WORLD, CONSTRAINT_SPACE_LOCAL, CONSTRAINT_SPACE_POSE, CONSTRAINT_SPACE_PARLOCAL; /** * This method returns the enum instance when given the appropriate @@ -468,7 +460,7 @@ public class ConstraintHelper extends AbstractBlenderHelper { case 3: return CONSTRAINT_SPACE_PARLOCAL; default: - return CONSTRAINT_SPACE_INVALID; + throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!"); } } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java new file mode 100644 index 000000000..70f1c1d8c --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java @@ -0,0 +1,415 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.animation.AnimChannel; +import com.jme3.animation.AnimControl; +import com.jme3.animation.Animation; +import com.jme3.animation.Bone; +import com.jme3.animation.BoneTrack; +import com.jme3.animation.Skeleton; +import com.jme3.animation.SpatialTrack; +import com.jme3.animation.Track; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.blender.BlenderContext; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; +import com.jme3.scene.plugins.blender.animations.ArmatureHelper; +import com.jme3.scene.plugins.blender.animations.BoneContext; +import com.jme3.util.TempVars; + +/** + * A node that represents either spatial or bone in constraint simulation. The + * node is applied its translation, rotation and scale for each frame of its + * animation. Then the constraints are applied that will eventually alter it. + * After that the feature's transformation is stored in VirtualTrack which is + * converted to new bone or spatial track at the very end. + * + * @author Marcin Roguski (Kaelthas) + */ +public class SimulationNode { + private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); + + /** The name of the node (for debugging purposes). */ + private String name; + /** A list of children for the node (either bones or child spatials). */ + private List children = new ArrayList(); + /** A virtual track for each of the nodes. */ + private Map virtualTrack = new HashMap(); + /** A list of constraints that the current node has. */ + private List constraints; + /** A list of node's animations. */ + private List animations; + + /** The nodes spatial (if null then the boneContext should be set). */ + private Spatial spatial; + /** The skeleton of the bone (not null if the node simulated the bone). */ + private Skeleton skeleton; + /** Animation controller for the node's feature. */ + private AnimControl animControl; + + /** + * The star transform of a spatial. Needed to properly reset the spatial to + * its start position. + */ + private Transform spatialStartTransform; + /** Star transformations for bones. Needed to properly reset the bones. */ + private Map boneStartTransforms; + + /** + * Builds the nodes tree for the given feature. The feature (bone or + * spatial) is found by its OMA. The feature must be a root bone or a root + * spatial. + * + * @param featureOMA + * the OMA of either bone or spatial + * @param blenderContext + * the blender context + */ + public SimulationNode(Long featureOMA, BlenderContext blenderContext) { + this(featureOMA, blenderContext, true); + } + + /** + * Creates the node for the feature. + * + * @param featureOMA + * the OMA of either bone or spatial + * @param blenderContext + * the blender context + * @param rootNode + * indicates if the feature is a root bone or root spatial or not + */ + private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { + Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (spatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null) { + this.skeleton = blenderContext.getSkeleton(featureOMA); + + Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); + this.animControl = nodeWithAnimationControl.getControl(AnimControl.class); + + boneStartTransforms = new HashMap(); + for (int i = 0; i < skeleton.getBoneCount(); ++i) { + Bone bone = skeleton.getBone(i); + boneStartTransforms.put(bone, new Transform(bone.getWorldBindPosition(), bone.getWorldBindRotation(), bone.getWorldBindScale())); + } + } else { + if (rootNode && spatial.getParent() != null) { + throw new IllegalStateException("Given spatial must be a root node!"); + } + this.spatial = spatial; + this.spatialStartTransform = spatial.getLocalTransform().clone(); + } + + this.name = '>' + spatial.getName() + '<'; + + constraints = this.findConstraints(featureOMA, blenderContext); + if (constraints == null) { + constraints = new ArrayList(); + } + + // 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); + } + } + + // each bone of the skeleton has the same anim data applied + BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(1)); + Long boneOma = boneContext.getBoneOma(); + animations = blenderContext.getAnimData(boneOma) == null ? null : blenderContext.getAnimData(boneOma).anims; + } else { + animations = blenderContext.getAnimData(featureOMA) == null ? null : blenderContext.getAnimData(featureOMA).anims; + for (Spatial child : spatial.getChildren()) { + if (child instanceof Node) { + children.add(new SimulationNode((Long) child.getUserData("oma"), blenderContext, false)); + } + } + } + + LOGGER.info("Removing invalid constraints."); + List validConstraints = new ArrayList(constraints.size()); + for (Constraint constraint : this.constraints) { + if (constraint.validate()) { + validConstraints.add(constraint); + } else { + LOGGER.log(Level.WARNING, "Constraint {0} is invalid and will not be applied.", constraint.name); + } + } + this.constraints = validConstraints; + } + + /** + * Tells if the node already contains the given constraint (so that it is + * not applied twice). + * + * @param constraint + * the constraint to be checked + * @return 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; + } + + /** + * Resets the node's feature to its starting transformation. + */ + private void reset() { + if (spatial != null) { + spatial.setLocalTransform(spatialStartTransform); + for (SimulationNode child : children) { + child.reset(); + } + } else if (skeleton != null) { + for (Entry entry : boneStartTransforms.entrySet()) { + Transform t = entry.getValue(); + entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); + } + skeleton.reset(); + } + } + + /** + * Simulates the spatial node. + */ + private void simulateSpatial() { + if (constraints != null && constraints.size() > 0) { + boolean applyStaticConstraints = true; + if (animations != null) { + for (Animation animation : animations) { + float[] animationTimeBoundaries = computeAnimationTimeBoundaries(animation); + int maxFrame = (int) animationTimeBoundaries[0]; + float maxTime = animationTimeBoundaries[1]; + + VirtualTrack vTrack = new VirtualTrack(maxFrame, maxTime); + virtualTrack.put(animation.getName(), vTrack); + for (Track track : animation.getTracks()) { + for (int frame = 0; frame < maxFrame; ++frame) { + spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]); + spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]); + spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]); + + for (Constraint constraint : constraints) { + constraint.apply(frame); + vTrack.setTransform(frame, spatial.getLocalTransform()); + } + } + Track newTrack = vTrack.getAsSpatialTrack(); + if (newTrack != null) { + animation.removeTrack(track); + animation.addTrack(newTrack); + } + applyStaticConstraints = false; + } + } + } + + // if there are no animations then just constraint the static + // object's transformation + if (applyStaticConstraints) { + for (Constraint constraint : constraints) { + constraint.apply(0); + } + } + } + + for (SimulationNode child : children) { + child.simulate(); + } + } + + /** + * Simulates the bone node. + */ + private void simulateSkeleton() { + if (constraints != null && constraints.size() > 0) { + boolean applyStaticConstraints = true; + + if (animations != null) { + TempVars vars = TempVars.get(); + AnimChannel animChannel = animControl.createChannel(); + for (Animation animation : animations) { + float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); + int maxFrame = (int) animationTimeBoundaries[0]; + float maxTime = animationTimeBoundaries[1]; + + Map tracks = new HashMap(); + Map previousTransforms = new HashMap(); + for (int frame = 0; frame < maxFrame; ++frame) { + this.reset();// this MUST be done here, otherwise + // setting next frame of animation will + // lead to possible errors + // first set proper time for all bones in all the tracks + // ... + for (Track track : animation.getTracks()) { + float time = ((BoneTrack) track).getTimes()[frame]; + Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex(); + + track.setTime(time, 1, animControl, animChannel, vars); + skeleton.updateWorldVectors(); + + Transform previousTransform = previousTransforms.get(boneIndex); + if (previousTransform == null) { + Bone bone = skeleton.getBone(boneIndex); + previousTransform = new Transform(); + previousTransform.setTranslation(bone.getLocalPosition()); + previousTransform.setRotation(bone.getLocalRotation()); + previousTransform.setScale(bone.getLocalScale()); + previousTransforms.put(boneIndex, previousTransform); + } + } + + // ... and then apply constraints ... + for (Constraint constraint : constraints) { + constraint.apply(frame); + } + + // ... and fill in another frame in the result track + for (Track track : animation.getTracks()) { + Integer boneIndex = ((BoneTrack) track).getTargetBoneIndex(); + Bone bone = skeleton.getBone(boneIndex); + + // take the initial transform of a bone + Transform previousTransform = previousTransforms.get(boneIndex); + + VirtualTrack vTrack = tracks.get(boneIndex); + if (vTrack == null) { + vTrack = new VirtualTrack(maxFrame, maxTime); + tracks.put(boneIndex, vTrack); + } + + Vector3f bonePositionDifference = bone.getLocalPosition().subtract(previousTransform.getTranslation()); + Quaternion boneRotationDifference = bone.getLocalRotation().mult(previousTransform.getRotation().inverse()).normalizeLocal(); + Vector3f boneScaleDifference = bone.getLocalScale().divide(previousTransform.getScale()); + if (frame > 0) { + bonePositionDifference = vTrack.translations.get(frame - 1).add(bonePositionDifference); + boneRotationDifference = vTrack.rotations.get(frame - 1).mult(boneRotationDifference); + boneScaleDifference = vTrack.scales.get(frame - 1).mult(boneScaleDifference); + } + vTrack.setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); + + previousTransform.setTranslation(bone.getLocalPosition()); + previousTransform.setRotation(bone.getLocalRotation()); + previousTransform.setScale(bone.getLocalScale()); + } + } + + for (Entry trackEntry : tracks.entrySet()) { + Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey()); + if (newTrack != null) { + for (Track track : animation.getTracks()) { + if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) { + animation.removeTrack(track); + animation.addTrack(newTrack); + break; + } + } + } + applyStaticConstraints = false; + } + } + vars.release(); + animControl.clearChannels(); + this.reset(); + } + + // if there are no animations then just constraint the static + // object's transformation + if (applyStaticConstraints) { + for (Constraint constraint : constraints) { + constraint.apply(0); + } + } + } + } + + /** + * Simulates the node. + */ + public void simulate() { + this.reset(); + if (spatial != null) { + this.simulateSpatial(); + } else { + this.simulateSkeleton(); + } + this.reset(); + } + + /** + * Computes the maximum frame and time for the animation. Different tracks + * can have different lengths so here the maximum one is being found. + * + * @param animation + * the animation + * @return maximum frame and time of the animation + */ + private float[] computeAnimationTimeBoundaries(Animation animation) { + int maxFrame = Integer.MIN_VALUE; + float maxTime = Float.MIN_VALUE; + for (Track track : animation.getTracks()) { + if (track instanceof BoneTrack) { + maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length); + maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]); + } else if (track instanceof SpatialTrack) { + maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length); + maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]); + } else { + throw new IllegalStateException("Unsupported track type for simuation: " + track); + } + } + return new float[] { maxFrame, maxTime }; + } + + /** + * Finds constraints for the node's features. + * + * @param ownerOMA + * the feature's OMA + * @param blenderContext + * the blender context + * @return a list of feature's constraints or empty list if none were found + */ + private List findConstraints(Long ownerOMA, BlenderContext blenderContext) { + List result = new ArrayList(); + for (Constraint constraint : blenderContext.getAllConstraints()) { + if (constraint.ownerOMA.longValue() == ownerOMA.longValue()) { + if (constraint.isImplemented()) { + result.add(constraint); + } else { + LOGGER.log(Level.WARNING, "Constraint named: ''{0}'' of type ''{1}'' is not implemented and will NOT be applied!", new Object[] { constraint.name, constraint.getConstraintTypeName() }); + } + } + } + return result.size() > 0 ? result : null; + } + + @Override + public String toString() { + return name; + } +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java index 1317032b4..fb6e89b51 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java @@ -23,16 +23,13 @@ import com.jme3.scene.plugins.blender.file.Structure; } @Override - public void performBakingOperation() { - LOGGER.warning("Applying constraints to skeleton is not supported."); + public boolean validate() { + LOGGER.warning("Constraints for skeleton are not supported."); + return false; } @Override - protected boolean validate() { - return true; - } - - @Override - protected void prepareTracksForApplyingConstraints() { + public void apply(int frame) { + LOGGER.warning("Applying constraints to skeleton is not supported."); } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java index b8179ac93..f55e80f7d 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java @@ -1,204 +1,36 @@ package com.jme3.scene.plugins.blender.constraints; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.logging.Logger; - -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.SpatialTrack; -import com.jme3.animation.Track; -import com.jme3.math.Quaternion; import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; -import com.jme3.scene.plugins.blender.animations.ArmatureHelper; -import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.ogre.AnimData; /** - * Constraint applied on the spatial objects. - * This includes: nodes, cameras nodes and light nodes. + * Constraint applied on the spatial objects. This includes: nodes, cameras + * nodes and light nodes. + * * @author Marcin Roguski (Kaelthas) */ /* package */class SpatialConstraint extends Constraint { - private static final Logger LOGGER = Logger.getLogger(SpatialConstraint.class.getName()); - - /** The owner of the constraint. */ - private Spatial owner; - /** The target of the constraint. */ - private Object target; - public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { super(constraintStructure, ownerOMA, influenceIpo, blenderContext); } @Override - protected boolean validate() { + public boolean validate() { if (targetOMA != null) { return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null; } return true; } - - @Override - public void performBakingOperation() { - this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); - this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null; - this.prepareTracksForApplyingConstraints(); - - // apply static constraint - Transform ownerTransform = constraintHelper.getNodeObjectTransform(ownerSpace, ownerOMA, blenderContext); - Transform targetTransform = targetOMA != null ? constraintHelper.getNodeObjectTransform(targetSpace, targetOMA, blenderContext) : null; - constraintDefinition.bake(ownerTransform, targetTransform, null, null, this.ipo); - constraintHelper.applyTransform(owner, ownerSpace, ownerTransform); - - // apply dynamic constraint - AnimData animData = blenderContext.getAnimData(ownerOMA); - if (animData != null) { - for (Animation animation : animData.anims) { - SpatialTrack ownerTrack = constraintHelper.getTrack(owner, animation); - - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - SpatialTrack targetTrack = null; - if (targetAnimData != null) { - targetTrack = constraintHelper.getTrack((Spatial) target, targetAnimData.anims.get(0)); - } - - constraintDefinition.bake(ownerTransform, targetTransform, ownerTrack, targetTrack, this.ipo); - } - } - } @Override - protected void prepareTracksForApplyingConstraints() { - Long[] spatialsOMAs = new Long[] { ownerOMA, targetOMA }; - Space[] spaces = new Space[] { ownerSpace, targetSpace }; - - // creating animations for current objects if at least on of their parents have an animation - for (int i = 0; i < spatialsOMAs.length; ++i) { - Long oma = spatialsOMAs[i]; - if (oma != null && oma > 0L) { - AnimData animData = blenderContext.getAnimData(oma); - if (animData == null) { - Spatial currentSpatial = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE); - if (currentSpatial != null) { - if (currentSpatial.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) == Boolean.TRUE) {// look for it among bones - BoneContext currentBoneContext = blenderContext.getBoneByName(subtargetName); - Bone currentBone = currentBoneContext.getBone(); - Bone parent = currentBone.getParent(); - boolean foundAnimation = false; - while (parent != null && !foundAnimation) { - BoneContext boneContext = blenderContext.getBoneByName(parent.getName()); - foundAnimation = this.hasAnimation(boneContext.getBoneOma()); - animData = blenderContext.getAnimData(boneContext.getBoneOma()); - parent = parent.getParent(); - } - if (foundAnimation) { - this.applyAnimData(currentBoneContext, spaces[i], animData); - } - } else { - Spatial parent = currentSpatial.getParent(); - while (parent != null && animData == null) { - Structure parentStructure = (Structure) blenderContext.getLoadedFeature(parent.getName(), LoadedFeatureDataType.LOADED_STRUCTURE); - if (parentStructure == null) { - parent = null; - } else { - Long parentOma = parentStructure.getOldMemoryAddress(); - animData = blenderContext.getAnimData(parentOma); - parent = parent.getParent(); - } - } - - if (animData != null) {// create anim data for the current object - this.applyAnimData(currentSpatial, oma, spaces[i], animData.anims.get(0)); - } - } - } else { - LOGGER.warning("Couldn't find target object for constraint: " + name + ". Make sure that the target is on layer that is defined to be loaded in blender key!"); - } - } - } - } - - // creating animation for owner if it doesn't have one already and if the target has it - AnimData animData = blenderContext.getAnimData(ownerOMA); - if (animData == null) { - AnimData targetAnimData = blenderContext.getAnimData(targetOMA); - if (targetAnimData != null) { - this.applyAnimData(owner, ownerOMA, ownerSpace, targetAnimData.anims.get(0)); - } - } - } - - /** - * The method determines if the bone has animations. - * - * @param boneOMA - * OMA of the animation's owner - * @return true if the target has animations and false otherwise - */ - protected boolean hasAnimation(Long boneOMA) { - AnimData animData = blenderContext.getAnimData(boneOMA); - if (animData != null) { - Bone bone = blenderContext.getBoneContext(boneOMA).getBone(); - int boneIndex = animData.skeleton.getBoneIndex(bone); - for (Animation animation : animData.anims) { - for (Track track : animation.getTracks()) { - if (track instanceof BoneTrack && ((BoneTrack) track).getTargetBoneIndex() == boneIndex) { - return true; - } - } - } - } - return false; - } - - /** - * This method applies spatial transform on each frame of the given - * animations. - * - * @param spatial - * the spatial - * @param spatialOma - * the OMA of the given spatial - * @param space - * the space we compute the transform in - * @param referenceAnimation - * the object containing the animations - */ - private void applyAnimData(Spatial spatial, Long spatialOma, Space space, Animation referenceAnimation) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - Transform transform = constraintHelper.getNodeObjectTransform(space, spatialOma, blenderContext); - - SpatialTrack parentTrack = (SpatialTrack) referenceAnimation.getTracks()[0]; - - HashMap anims = new HashMap(1); - Animation animation = new Animation(spatial.getName(), referenceAnimation.getLength()); - anims.put(spatial.getName(), animation); - - float[] times = parentTrack.getTimes(); - Vector3f[] translations = new Vector3f[times.length]; - Quaternion[] rotations = new Quaternion[times.length]; - Vector3f[] scales = new Vector3f[times.length]; - Arrays.fill(translations, transform.getTranslation()); - Arrays.fill(rotations, transform.getRotation()); - Arrays.fill(scales, transform.getScale()); - animation.addTrack(new SpatialTrack(times, translations, rotations, scales)); - - AnimControl control = new AnimControl(null); - control.setAnimations(anims); - spatial.addControl(control); - - blenderContext.setAnimData(spatialOma, new AnimData(null, new ArrayList(anims.values()))); + public void apply(int frame) { + Transform ownerTransform = constraintHelper.getTransform(ownerOMA, null, ownerSpace); + Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; + constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame)); + constraintHelper.applyTransform(ownerOMA, subtargetName, ownerSpace, ownerTransform); } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java new file mode 100644 index 000000000..944a3fab8 --- /dev/null +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java @@ -0,0 +1,146 @@ +package com.jme3.scene.plugins.blender.constraints; + +import java.util.ArrayList; + +import com.jme3.animation.BoneTrack; +import com.jme3.animation.SpatialTrack; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; + +/** + * A virtual track that stores computed frames after constraints are applied. + * Not all the frames need to be inserted. If there are lacks then the class + * will fill the gaps. + * + * @author Marcin Roguski (Kaelthas) + */ +/* package */class VirtualTrack { + /** The last frame for the track. */ + public int maxFrame; + /** The max time for the track. */ + public float maxTime; + /** Translations of the track. */ + public ArrayList translations; + /** Rotations of the track. */ + public ArrayList rotations; + /** Scales of the track. */ + public ArrayList scales; + + /** + * Constructs the object storing the maximum frame and time. + * + * @param maxFrame + * the last frame for the track + * @param maxTime + * the max time for the track + */ + public VirtualTrack(int maxFrame, float maxTime) { + this.maxFrame = maxFrame; + this.maxTime = maxTime; + } + + /** + * Sets the transform for the given frame. + * + * @param frameIndex + * the frame for which the transform will be set + * @param transform + * the transformation to be set + */ + public void setTransform(int frameIndex, Transform transform) { + if (translations == null) { + translations = this.createList(Vector3f.ZERO, frameIndex); + } + this.append(translations, Vector3f.ZERO, frameIndex - translations.size()); + translations.add(transform.getTranslation().clone()); + + if (rotations == null) { + rotations = this.createList(Quaternion.IDENTITY, frameIndex); + } + this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size()); + rotations.add(transform.getRotation().clone()); + + if (scales == null) { + scales = this.createList(Vector3f.UNIT_XYZ, frameIndex); + } + this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size()); + scales.add(transform.getScale().clone()); + } + + /** + * Returns the track as a bone track. + * + * @param targetBoneIndex + * the bone index + * @return the bone track + */ + public BoneTrack getAsBoneTrack(int targetBoneIndex) { + if (translations == null && rotations == null && scales == null) { + return null; + } + return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); + } + + /** + * Returns the track as a spatial track. + * + * @return the spatial track + */ + public SpatialTrack getAsSpatialTrack() { + if (translations == null && rotations == null && scales == null) { + return null; + } + return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); + } + + /** + * The method creates times for the track based on the given maximum values. + * + * @return the times for the track + */ + private float[] createTimes() { + float[] times = new float[maxFrame]; + float dT = maxTime / (float) maxFrame; + float t = 0; + for (int i = 0; i < maxFrame; ++i) { + times[i] = t; + t += dT; + } + return times; + } + + /** + * Helper method that creates a list of a given size filled with given + * elements. + * + * @param element + * the element to be put into the list + * @param count + * the list size + * @return the list + */ + private ArrayList createList(T element, int count) { + ArrayList result = new ArrayList(count); + for (int i = 0; i < count; ++i) { + result.add(element); + } + return result; + } + + /** + * Appends the element to the given list. + * + * @param list + * the list where the element will be appended + * @param element + * the element to be appended + * @param count + * how many times the element will be appended + */ + private void append(ArrayList list, T element, int count) { + for (int i = 0; i < count; ++i) { + list.add(element); + } + } +} \ No newline at end of file diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java index 91868db4d..4ff0b15b5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java @@ -1,243 +1,49 @@ package com.jme3.scene.plugins.blender.constraints.definitions; -import java.io.IOException; - -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.SpatialTrack; -import com.jme3.animation.Track; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; import com.jme3.math.Transform; -import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.Ipo; +import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.util.TempVars; public abstract class ConstraintDefinition { - protected int flag; + protected int flag; + private Object owner; + private BlenderContext blenderContext; + private Long ownerOMA; - public ConstraintDefinition(Structure constraintData, BlenderContext blenderContext) { + public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { if (constraintData != null) {// Null constraint has no data Number flag = (Number) constraintData.getFieldValue("flag"); if (flag != null) { this.flag = flag.intValue(); } } - } - - public void bake(Transform ownerTransform, Transform targetTransform, Track ownerTrack, Track targetTrack, Ipo influenceIpo) { - TrackWrapper ownerWrapperTrack = ownerTrack != null ? new TrackWrapper(ownerTrack) : null; - TrackWrapper targetWrapperTrack = targetTrack != null ? new TrackWrapper(targetTrack) : null; - - // uruchamiamy bake dla transformat zalenie od tego, ktre argumenty s nullami, a ktre - nie - this.bake(ownerTransform, targetTransform, influenceIpo.calculateValue(0)); - if (ownerWrapperTrack != null) { - float[] ownerTimes = ownerWrapperTrack.getTimes(); - Vector3f[] translations = ownerWrapperTrack.getTranslations(); - Quaternion[] rotations = ownerWrapperTrack.getRotations(); - Vector3f[] scales = ownerWrapperTrack.getScales(); - - float[] targetTimes = targetWrapperTrack == null ? null : targetWrapperTrack.getTimes(); - Vector3f[] targetTranslations = targetWrapperTrack == null ? null : targetWrapperTrack.getTranslations(); - Quaternion[] targetRotations = targetWrapperTrack == null ? null : targetWrapperTrack.getRotations(); - Vector3f[] targetScales = targetWrapperTrack == null ? null : targetWrapperTrack.getScales(); - Vector3f translation = new Vector3f(), scale = new Vector3f(); - Quaternion rotation = new Quaternion(); - - Transform ownerTemp = new Transform(), targetTemp = new Transform(); - for (int i = 0; i < ownerTimes.length; ++i) { - float t = ownerTimes[i]; - ownerTemp.setTranslation(translations[i]); - ownerTemp.setRotation(rotations[i]); - ownerTemp.setScale(scales[i]); - if (targetWrapperTrack == null) { - this.bake(ownerTemp, targetTransform, influenceIpo.calculateValue(i)); - } else { - // getting the values that are the interpolation of the target track for the time 't' - this.interpolate(targetTranslations, targetTimes, t, translation); - this.interpolate(targetRotations, targetTimes, t, rotation); - this.interpolate(targetScales, targetTimes, t, scale); - - targetTemp.setTranslation(translation); - targetTemp.setRotation(rotation); - targetTemp.setScale(scale); - - this.bake(ownerTemp, targetTemp, influenceIpo.calculateValue(i)); - } - // need to clone here because each of the arrays will reference the same instance if they hold the same value in the compact array - translations[i] = ownerTemp.getTranslation().clone(); - rotations[i] = ownerTemp.getRotation().clone(); - scales[i] = ownerTemp.getScale().clone(); - } - ownerWrapperTrack.setKeyframes(ownerTimes, translations, rotations, scales); - } - } - - protected abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); - - private void interpolate(Vector3f[] targetVectors, float[] targetTimes, float currentTime, Vector3f result) { - int index = 0; - for (int i = 1; i < targetTimes.length; ++i) { - if (targetTimes[i] < currentTime) { - ++index; - } else { - break; - } - } - if (index >= targetTimes.length - 1) { - result.set(targetVectors[targetTimes.length - 1]); - } else { - float delta = targetTimes[index + 1] - targetTimes[index]; - if (delta == 0.0f) { - result.set(targetVectors[index + 1]); - } else { - float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); - FastMath.interpolateLinear(scale, targetVectors[index], targetVectors[index + 1], result); - } - } - } - - private void interpolate(Quaternion[] targetQuaternions, float[] targetTimes, float currentTime, Quaternion result) { - int index = 0; - for (int i = 1; i < targetTimes.length; ++i) { - if (targetTimes[i] < currentTime) { - ++index; - } else { - break; - } - } - if (index >= targetTimes.length - 1) { - result.set(targetQuaternions[targetTimes.length - 1]); - } else { - float delta = targetTimes[index + 1] - targetTimes[index]; - if (delta == 0.0f) { - result.set(targetQuaternions[index + 1]); - } else { - float scale = (currentTime - targetTimes[index]) / (targetTimes[index + 1] - targetTimes[index]); - result.slerp(targetQuaternions[index], targetQuaternions[index + 1], scale); - } - } + this.blenderContext = blenderContext; + this.ownerOMA = ownerOMA; } /** - * This class holds either the bone track or spatial track. Is made to improve - * code readability. + * This method is here because we have no guarantee that the owner is loaded + * when constraint is being created. So use it to get the owner when it is + * needed for computations. * - * @author Marcin Roguski (Kaelthas) + * @return the owner of the constraint or null if none is set */ - private static class TrackWrapper implements Track { - /** The spatial track. */ - private SpatialTrack spatialTrack; - /** The bone track. */ - private BoneTrack boneTrack; - - /** - * Constructs the object using the given track. The track must be of one of the types:
  • BoneTrack
  • SpatialTrack - * - * @param track - * the animation track - */ - public TrackWrapper(Track track) { - if (track instanceof SpatialTrack) { - this.spatialTrack = (SpatialTrack) track; - } else if (track instanceof BoneTrack) { - this.boneTrack = (BoneTrack) track; - } else { - throw new IllegalStateException("Unknown track type!"); + public Object getOwner() { + if (ownerOMA != null && owner == null) { + owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); + if (owner == null) { + throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName()); } } + return owner; + } - /** - * @return the array of rotations of this track - */ - public Quaternion[] getRotations() { - if (boneTrack != null) { - return boneTrack.getRotations(); - } - return spatialTrack.getRotations(); - } - - /** - * @return the array of scales for this track - */ - public Vector3f[] getScales() { - if (boneTrack != null) { - return boneTrack.getScales(); - } - return spatialTrack.getScales(); - } - - /** - * @return the arrays of time for this track - */ - public float[] getTimes() { - if (boneTrack != null) { - return boneTrack.getTimes(); - } - return spatialTrack.getTimes(); - } - - /** - * @return the array of translations of this track - */ - public Vector3f[] getTranslations() { - if (boneTrack != null) { - return boneTrack.getTranslations(); - } - return spatialTrack.getTranslations(); - } - - /** - * Set the translations, rotations and scales for this bone track - * - * @param times - * a float array with the time of each frame - * @param translations - * the translation of the bone for each frame - * @param rotations - * the rotation of the bone for each frame - * @param scales - * the scale of the bone for each frame - */ - public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { - if (boneTrack != null) { - boneTrack.setKeyframes(times, translations, rotations, scales); - } else { - spatialTrack.setKeyframes(times, translations, rotations, scales); - } - } - - public void write(JmeExporter ex) throws IOException { - // no need to implement this one (the TrackWrapper is used internally and never serialized) - } - - public void read(JmeImporter im) throws IOException { - // no need to implement this one (the TrackWrapper is used internally and never serialized) - } - - public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { - if (boneTrack != null) { - boneTrack.setTime(time, weight, control, channel, vars); - } else { - spatialTrack.setTime(time, weight, control, channel, vars); - } - } + public boolean isImplemented() { + return true; + } - public float getLength() { - return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength(); - } + public abstract String getConstraintTypeName(); - @Override - public TrackWrapper clone() { - if (boneTrack != null) { - return new TrackWrapper(boneTrack.clone()); - } - return new TrackWrapper(spatialTrack.clone()); - } - } + public abstract void bake(Transform ownerTransform, Transform targetTransform, float influence); } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java index 18231ed23..986e589c9 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java @@ -1,5 +1,6 @@ package com.jme3.scene.plugins.blender.constraints.definitions; +import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; @@ -7,6 +8,7 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Dist limit' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition { @@ -17,14 +19,19 @@ import com.jme3.scene.plugins.blender.file.Structure; protected int mode; protected float dist; - public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); mode = ((Number) constraintData.getFieldValue("mode")).intValue(); dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); } @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) { + // distance limit does not work on bones who have parent + return; + } + Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); float currentDistance = v.length(); @@ -56,4 +63,9 @@ import com.jme3.scene.plugins.blender.file.Structure; throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); } } + + @Override + public String getConstraintTypeName() { + return "Limit distance"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java index 839b80e6d..ad392d714 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java @@ -40,7 +40,7 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.file.Structure; public class ConstraintDefinitionFactory { - private static final Map> CONSTRAINT_CLASSES = new HashMap>(); + private static final Map> CONSTRAINT_CLASSES = new HashMap>(); static { CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); @@ -51,8 +51,8 @@ public class ConstraintDefinitionFactory { CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); } - - private static final Map UNSUPPORTED_CONSTRAINTS = new HashMap(); + + private static final Map UNSUPPORTED_CONSTRAINTS = new HashMap(); static { UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action"); UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of"); @@ -84,22 +84,23 @@ public class ConstraintDefinitionFactory { * This method creates the constraint instance. * * @param constraintStructure - * the constraint's structure (bConstraint clss in blender 2.49). If the value is null the NullConstraint is created. + * the constraint's structure (bConstraint clss in blender 2.49). + * If the value is null the NullConstraint is created. * @param blenderContext * the blender context * @throws BlenderFileException * this exception is thrown when the blender file is somehow * corrupted */ - public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, BlenderContext blenderContext) throws BlenderFileException { + public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException { if (constraintStructure == null) { - return new ConstraintDefinitionNull(null, blenderContext); + return new ConstraintDefinitionNull(null, ownerOMA, blenderContext); } String constraintClassName = constraintStructure.getType(); Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); if (constraintDefinitionClass != null) { try { - return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext); + return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext); } catch (IllegalArgumentException e) { throw new BlenderFileException(e.getLocalizedMessage(), e); } catch (SecurityException e) { @@ -113,7 +114,7 @@ public class ConstraintDefinitionFactory { } } else { String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); - if(constraintName != null) { + if (constraintName != null) { return new UnsupportedConstraintDefinition(constraintName); } else { throw new BlenderFileException("Unknown constraint type: " + constraintClassName); diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java index a9bbd89d9..a79d730f4 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java @@ -1,5 +1,6 @@ package com.jme3.scene.plugins.blender.constraints.definitions; +import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; @@ -7,27 +8,32 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Loc like' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionLocLike extends ConstraintDefinition { private static final int LOCLIKE_X = 0x01; private static final int LOCLIKE_Y = 0x02; private static final int LOCLIKE_Z = 0x04; - // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in blender + // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in + // blender private static final int LOCLIKE_X_INVERT = 0x10; private static final int LOCLIKE_Y_INVERT = 0x20; private static final int LOCLIKE_Z_INVERT = 0x40; private static final int LOCLIKE_OFFSET = 0x80; - public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); if (blenderContext.getBlenderKey().isFixUpAxis()) { // swapping Y and X limits flag in the bitwise flag int y = flag & LOCLIKE_Y; int invY = flag & LOCLIKE_Y_INVERT; int z = flag & LOCLIKE_Z; int invZ = flag & LOCLIKE_Z_INVERT; - flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the other flags to swap them + flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET;// clear the + // other flags + // to swap + // them flag |= y << 1; flag |= invY << 1; flag |= z >> 1; @@ -37,12 +43,18 @@ import com.jme3.scene.plugins.blender.file.Structure; @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) { + // cannot copy the location of a bone attached to its parent, + // Blender forbids that + return; + } Vector3f ownerLocation = ownerTransform.getTranslation(); Vector3f targetLocation = targetTransform.getTranslation(); Vector3f startLocation = ownerTransform.getTranslation().clone(); Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to + // the copied location offset = startLocation; } @@ -71,4 +83,9 @@ import com.jme3.scene.plugins.blender.file.Structure; ownerLocation.addLocal(startLocation); } } + + @Override + public String getConstraintTypeName() { + return "Copy location"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java index abd2cfa11..68d6a9cf6 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java @@ -1,5 +1,6 @@ package com.jme3.scene.plugins.blender.constraints.definitions; +import com.jme3.animation.Bone; import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.plugins.blender.BlenderContext; @@ -7,6 +8,7 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Loc limit' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition { @@ -19,8 +21,8 @@ import com.jme3.scene.plugins.blender.file.Structure; protected float[][] limits = new float[3][2]; - public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); if (blenderContext.getBlenderKey().isFixUpAxis()) { limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); @@ -34,7 +36,8 @@ import com.jme3.scene.plugins.blender.file.Structure; int ymax = flag & LIMIT_YMAX; int zmin = flag & LIMIT_ZMIN; int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap + // them flag |= ymin << 2; flag |= ymax << 2; flag |= zmin >> 2; @@ -51,6 +54,11 @@ import com.jme3.scene.plugins.blender.file.Structure; @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null) { + // location limit does not work on bones who have parent + return; + } + Vector3f translation = ownerTransform.getTranslation(); if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { @@ -72,4 +80,9 @@ import com.jme3.scene.plugins.blender.file.Structure; translation.z -= (translation.z - limits[2][1]) * influence; } } + + @Override + public String getConstraintTypeName() { + return "Limit location"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java index 7c558e3e9..71b536d0a 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java @@ -6,16 +6,22 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Null' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionNull extends ConstraintDefinition { - public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); } @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { // null constraint does nothing so no need to implement this one } + + @Override + public String getConstraintTypeName() { + return "Null"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java index c01624466..c476bf3c5 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java @@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Rot like' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionRotLike extends ConstraintDefinition { @@ -21,8 +22,8 @@ import com.jme3.scene.plugins.blender.file.Structure; private transient float[] ownerAngles = new float[3]; private transient float[] targetAngles = new float[3]; - public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); } @Override @@ -33,7 +34,8 @@ import com.jme3.scene.plugins.blender.file.Structure; Quaternion startRotation = ownerRotation.clone(); Quaternion offset = Quaternion.IDENTITY; - if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to the copied rotation + if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to + // the copied rotation offset = startRotation; } @@ -58,10 +60,14 @@ import com.jme3.scene.plugins.blender.file.Structure; ownerRotation.fromAngles(ownerAngles).multLocal(offset); if (influence < 1.0f) { - // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); // ownerLocation.addLocal(startLocation); // TODO } } + + @Override + public String getConstraintTypeName() { + return "Copy rotation"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java index 80e18cbc6..9e07f8f45 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java @@ -18,13 +18,13 @@ import com.jme3.scene.plugins.blender.file.Structure; private transient float[][] limits = new float[3][2]; private transient float[] angles = new float[3]; - public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null */) {// FIXME: !!!!!!!! + public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); + if (blenderContext.getBlenderKey().isFixUpAxis()) { limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); + limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); + limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); @@ -45,15 +45,38 @@ import com.jme3.scene.plugins.blender.file.Structure; // until blender 2.49 the rotations values were stored in degrees if (blenderContext.getBlenderVersion() <= 249) { - for (int i = 0; i < limits.length; ++i) { + for (int i = 0; i < 3; ++i) { limits[i][0] *= FastMath.DEG_TO_RAD; limits[i][1] *= FastMath.DEG_TO_RAD; } } + + // make sure that the limits are always in range [0, 2PI) + // TODO: left it here because it is essential to make sure all cases + // work poperly + // but will do it a little bit later ;) + /* + * for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int + * multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if + * (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor + + * 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make + * sure the lower limit is not greater than the upper one + * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0]; + * limits[i][0] = limits[i][1]; limits[i][1] = temp; } } + */ } @Override public void bake(Transform ownerTransform, Transform targetTransform, float influence) { + ownerTransform.getRotation().toAngles(angles); + // make sure that the rotations are always in range [0, 2PI) + // TODO: same comment as in constructor + /* + * for (int i = 0; i < 3; ++i) { int multFactor = + * (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) { + * angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i] + * -= FastMath.TWO_PI * multFactor; } } + */ if ((flag & LIMIT_XROT) != 0) { float difference = 0.0f; if (angles[0] < limits[0][0]) { @@ -81,5 +104,11 @@ import com.jme3.scene.plugins.blender.file.Structure; } angles[2] -= difference; } + ownerTransform.getRotation().fromAngles(angles); + } + + @Override + public String getConstraintTypeName() { + return "Limit rotation"; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java index 828b954d9..ebeb9e887 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java @@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Size like' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition { @@ -15,13 +16,14 @@ import com.jme3.scene.plugins.blender.file.Structure; private static final int SIZELIKE_Z = 0x04; private static final int LOCLIKE_OFFSET = 0x80; - public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); if (blenderContext.getBlenderKey().isFixUpAxis()) { // swapping Y and X limits flag in the bitwise flag int y = flag & SIZELIKE_Y; int z = flag & SIZELIKE_Z; - flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap them + flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap + // them flag |= y << 1; flag |= z >> 1; } @@ -33,7 +35,8 @@ import com.jme3.scene.plugins.blender.file.Structure; Vector3f targetScale = targetTransform.getScale(); Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the copied scale + if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the + // copied scale offset = ownerScale.clone(); } @@ -48,4 +51,9 @@ import com.jme3.scene.plugins.blender.file.Structure; } ownerScale.addLocal(offset); } + + @Override + public String getConstraintTypeName() { + return "Copy scale"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java index 9796fdbf2..ad6e23eab 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java @@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure; /** * This class represents 'Size limit' constraint type in blender. + * * @author Marcin Roguski (Kaelthas) */ /* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition { @@ -19,8 +20,8 @@ import com.jme3.scene.plugins.blender.file.Structure; protected transient float[][] limits = new float[3][2]; - public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) { - super(constraintData, blenderContext); + public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { + super(constraintData, ownerOMA, blenderContext); if (blenderContext.getBlenderKey().isFixUpAxis()) { limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); @@ -34,7 +35,8 @@ import com.jme3.scene.plugins.blender.file.Structure; int ymax = flag & LIMIT_YMAX; int zmin = flag & LIMIT_ZMIN; int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap them + flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap + // them flag |= ymin << 2; flag |= ymax << 2; flag |= zmin >> 2; @@ -72,4 +74,9 @@ import com.jme3.scene.plugins.blender.file.Structure; scale.z -= (scale.z - limits[2][1]) * influence; } } + + @Override + public String getConstraintTypeName() { + return "Limit scale"; + } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java index 5a1a12e14..e9ef8c844 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java @@ -1,28 +1,33 @@ package com.jme3.scene.plugins.blender.constraints.definitions; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.jme3.math.Transform; /** - * This class represents a constraint that is defined by blender but not supported by either importer - * ot jme. It only wirtes down a warning when baking is called. + * This class represents a constraint that is defined by blender but not + * supported by either importer ot jme. It only wirtes down a warning when + * baking is called. * * @author Marcin Roguski (Kaelthas) */ -/* package */ class UnsupportedConstraintDefinition extends ConstraintDefinition { - private static final Logger LOGGER = Logger.getLogger(UnsupportedConstraintDefinition.class.getName()); - - private String name; - - public UnsupportedConstraintDefinition(String name) { - super(null, null); - this.name = name; +/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition { + private String typeName; + + public UnsupportedConstraintDefinition(String typeName) { + super(null, null, null); + this.typeName = typeName; + } + + @Override + public void bake(Transform ownerTransform, Transform targetTransform, float influence) { } - + + @Override + public boolean isImplemented() { + return false; + } + @Override - protected void bake(Transform ownerTransform, Transform targetTransform, float influence) { - LOGGER.log(Level.WARNING, "'{0}' constraint NOT implemented!", name); + public String getConstraintTypeName() { + return typeName; } } diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java index 30b87d46d..8237a91bc 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java @@ -44,7 +44,8 @@ import com.jme3.util.BufferUtils; */ /* package */class ArmatureModifier extends Modifier { private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); - private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME limitation + private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; // JME + // limitation private Skeleton skeleton; private Structure objectStructure; @@ -71,7 +72,9 @@ import com.jme3.util.BufferUtils; */ public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert + // = + // DeformVERTices // if pDvert==null then there are not vertex groups and no need to load // skeleton (untill bone envelopes are supported) @@ -109,7 +112,8 @@ import com.jme3.util.BufferUtils; // read animations ArrayList animations = new ArrayList(); List actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); - if (actionHeaders != null) {// it may happen that the model has armature with no actions + if (actionHeaders != null) {// it may happen that the model has + // armature with no actions for (FileBlockHeader header : actionHeaders) { Structure actionStructure = header.getStructure(blenderContext); String actionName = actionStructure.getName(); @@ -202,7 +206,8 @@ import com.jme3.util.BufferUtils; if (bindPoseBuffer != null) { mesh.setBuffer(bindPoseBuffer); } - // change the usage type of vertex and normal buffers from Static to Stream + // change the usage type of vertex and normal buffers from + // Static to Stream mesh.getBuffer(Type.Position).setUsage(Usage.Stream); mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); } @@ -226,7 +231,9 @@ import com.jme3.util.BufferUtils; } node.addControl(control); node.addControl(new SkeletonControl(animData.skeleton)); - + + blenderContext.setNodeForSkeleton(skeleton, node); + return node; } @@ -282,23 +289,50 @@ import com.jme3.util.BufferUtils; * this exception is thrown when the blend file structure is * somehow invalid or corrupted */ - private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap, BlenderContext blenderContext) throws BlenderFileException { + private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map> vertexReferenceMap, Map groupToBoneIndexMap, BlenderContext blenderContext) + throws BlenderFileException { bonesGroups[0] = 0; - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices + Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert + // = + // DeformVERTices FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); if (pDvert.isNotNull()) {// assigning weights and bone indices boolean warnAboutTooManyVertexWeights = false; - List dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender) + List dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() + // == + // verticesAmount + // (one + // dvert + // per + // vertex + // in + // blender) int vertexIndex = 0; // use tree map to sort weights from the lowest to the highest ones TreeMap weightToIndexMap = new TreeMap(); for (Structure dvert : dverts) { - List vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here + List vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we + // fetch + // the + // referenced + // vertices + // here if (vertexIndices != null) { - int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total amount of weights assignet to the vertex (max. 4 in JME) + int totweight = ((Number) dvert.getFieldValue("totweight")).intValue();// total + // amount + // of + // weights + // assignet + // to + // the + // vertex + // (max. + // 4 + // in + // JME) Pointer pDW = (Pointer) dvert.getFieldValue("dw"); if (totweight > 0 && groupToBoneIndexMap != null) { weightToIndexMap.clear(); @@ -307,25 +341,32 @@ import com.jme3.util.BufferUtils; for (Structure deformWeight : dw) { Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); - // boneIndex == null: it here means that we came accross group that has no bone attached to, so simply ignore it - // if weight == 0 and weightIndex == 0 then ignore the weight (do not set weight = 0 as a first weight) + // boneIndex == null: it here means that we came + // accross group that has no bone attached to, so + // simply ignore it + // if weight == 0 and weightIndex == 0 then ignore + // the weight (do not set weight = 0 as a first + // weight) if (boneIndex != null && (weight > 0.0f || weightIndex > 0)) { if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { if (weight == 0.0f) { boneIndex = Integer.valueOf(0); } - // we apply the weight to all referenced vertices + // we apply the weight to all referenced + // vertices for (Integer index : vertexIndices) { weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); } weightToIndexMap.put(weight, weightIndex); bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); - } else if (weight > 0) {// if weight is zero the simply ignore it + } else if (weight > 0) {// if weight is zero the + // simply ignore it warnAboutTooManyVertexWeights = true; Entry lowestWeightAndIndex = weightToIndexMap.firstEntry(); if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { - // we apply the weight to all referenced vertices + // we apply the weight to all referenced + // vertices for (Integer index : vertexIndices) { weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue()); @@ -338,7 +379,8 @@ import com.jme3.util.BufferUtils; } } } else { - // 0.0 weight indicates, do not transform this vertex, but keep it in bind pose. + // 0.0 weight indicates, do not transform this vertex, + // but keep it in bind pose. for (Integer index : vertexIndices) { weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); @@ -354,7 +396,8 @@ import com.jme3.util.BufferUtils; } else { // always bind all vertices to 0-indexed bone // this bone makes the model look normally if vertices have no bone - // assigned and it is used in object animation, so if we come accross object + // assigned and it is used in object animation, so if we come + // accross object // animation we can use the 0-indexed bone for this for (List vertexIndexList : vertexReferenceMap.values()) { // we apply the weight to all referenced vertices diff --git a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java index 076b09212..58af0e94f 100644 --- a/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ b/engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java @@ -37,6 +37,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import com.jme3.asset.BlenderKey.FeaturesToLoad; +import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; import com.jme3.math.Transform; @@ -65,6 +66,7 @@ import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; /** * A class that is used in object calculations. + * * @author Marcin Roguski (Kaelthas) */ public class ObjectHelper extends AbstractBlenderHelper { @@ -83,8 +85,9 @@ public class ObjectHelper extends AbstractBlenderHelper { protected static final int OBJECT_TYPE_ARMATURE = 25; /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. + * This constructor parses the given blender version and stores the result. + * Some functionalities may differ in different blender versions. + * * @param blenderVersion * the version read from the blend file * @param fixUpAxis @@ -95,7 +98,9 @@ public class ObjectHelper extends AbstractBlenderHelper { } /** - * This method reads the given structure and createn an object that represents the data. + * This method reads the given structure and createn an object that + * represents the data. + * * @param objectStructure * the object's structure * @param blenderContext @@ -205,7 +210,8 @@ public class ObjectHelper extends AbstractBlenderHelper { } break; case OBJECT_TYPE_ARMATURE: - // need to create an empty node to properly create parent-children relationships between nodes + // need to create an empty node to properly create + // parent-children relationships between nodes Node armature = new Node(name); armature.setLocalTransform(t); armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE); @@ -223,9 +229,13 @@ public class ObjectHelper extends AbstractBlenderHelper { } if (result != null) { - result.updateModelBound();// I prefer do compute bounding box here than read it from the file + result.updateModelBound();// I prefer do compute bounding box here + // than read it from the file blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result); + // TODO: this data is only to help during loading, shall I remove it + // after all the loading is done ??? + result.setUserData("oma", objectStructure.getOldMemoryAddress()); // applying modifiers LOGGER.log(Level.FINE, "Reading and applying object's modifiers."); @@ -242,7 +252,8 @@ public class ObjectHelper extends AbstractBlenderHelper { // reading custom properties if (blenderContext.getBlenderKey().isLoadObjectProperties()) { Properties properties = this.loadProperties(objectStructure, blenderContext); - // the loaded property is a group property, so we need to get each value and set it to Spatial + // the loaded property is a group property, so we need to get + // each value and set it to Spatial if (result instanceof Spatial && properties != null && properties.getValue() != null) { this.applyProperties(result, properties); } @@ -250,9 +261,11 @@ public class ObjectHelper extends AbstractBlenderHelper { } return result; } - + /** - * This method calculates local transformation for the object. Parentage is taken under consideration. + * This method calculates local transformation for the object. Parentage is + * taken under consideration. + * * @param objectStructure * the object's structure * @return objects transformation relative to its parent @@ -277,16 +290,16 @@ public class ObjectHelper extends AbstractBlenderHelper { Vector3f translation = localMatrix.toTranslationVector(); Quaternion rotation = localMatrix.toRotationQuat(); - Vector3f scale = this.getScale(parentInv).multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); + Vector3f scale = parentInv.toScaleVector().multLocal(size.get(0).floatValue(), size.get(1).floatValue(), size.get(2).floatValue()); if (fixUpAxis) { float y = translation.y; translation.y = translation.z; - translation.z = -y; + translation.z = y == 0 ? 0 : -y; y = rotation.getY(); float z = rotation.getZ(); - rotation.set(rotation.getX(), z, -y, rotation.getW()); + rotation.set(rotation.getX(), z, y == 0 ? 0 : -y, rotation.getW()); y = scale.y; scale.y = scale.z; @@ -301,7 +314,9 @@ public class ObjectHelper extends AbstractBlenderHelper { /** * This method returns the matrix of a given name for the given structure. - * The matrix is NOT transformed if Y axis is up - the raw data is loaded from the blender file. + * The matrix is NOT transformed if Y axis is up - the raw data is loaded + * from the blender file. + * * @param structure * the structure with matrix data * @param matrixName @@ -315,6 +330,7 @@ public class ObjectHelper extends AbstractBlenderHelper { /** * This method returns the matrix of a given name for the given structure. * It takes up axis into consideration. + * * @param structure * the structure with matrix data * @param matrixName @@ -325,7 +341,11 @@ public class ObjectHelper extends AbstractBlenderHelper { public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { Matrix4f result = new Matrix4f(); DynamicArray obmat = (DynamicArray) structure.getFieldValue(matrixName); - int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the matrix must be square + int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));// the + // matrix + // must + // be + // square for (int i = 0; i < rowAndColumnSize; ++i) { for (int j = 0; j < rowAndColumnSize; ++j) { result.set(i, j, obmat.get(j, i).floatValue()); @@ -334,15 +354,15 @@ public class ObjectHelper extends AbstractBlenderHelper { if (applyFixUpAxis && fixUpAxis) { Vector3f translation = result.toTranslationVector(); Quaternion rotation = result.toRotationQuat(); - Vector3f scale = this.getScale(result); + Vector3f scale = result.toScaleVector(); float y = translation.y; translation.y = translation.z; - translation.z = -y; + translation.z = y == 0 ? 0 : -y; y = rotation.getY(); float z = rotation.getZ(); - rotation.set(rotation.getX(), z, -y, rotation.getW()); + rotation.set(rotation.getX(), z, y == 0 ? 0 : -y, rotation.getW()); y = scale.y; scale.y = scale.z; @@ -353,21 +373,17 @@ public class ObjectHelper extends AbstractBlenderHelper { result.setRotationQuaternion(rotation); result.setScale(scale); } - return result; - } - /** - * This method returns the scale from the given matrix. - * - * @param matrix - * the transformation matrix - * @return the scale from the given matrix - */ - public Vector3f getScale(Matrix4f matrix) { - float scaleX = (float) Math.sqrt(matrix.m00 * matrix.m00 + matrix.m10 * matrix.m10 + matrix.m20 * matrix.m20); - float scaleY = (float) Math.sqrt(matrix.m01 * matrix.m01 + matrix.m11 * matrix.m11 + matrix.m21 * matrix.m21); - float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22); - return new Vector3f(scaleX, scaleY, scaleZ); + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + float value = result.get(i, j); + if (Math.abs(value) <= FastMath.FLT_EPSILON) { + result.set(i, j, 0); + } + } + } + + return result; } @Override