Refactoring: large changes in constraints system (see the proper topic on the forum for further changes)

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10581 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Kae..pl 12 years ago
parent c4fc9b723f
commit 29dd973122
  1. 115
      engine/src/blender/com/jme3/scene/plugins/blender/BlenderContext.java
  2. 27
      engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
  3. 26
      engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
  4. 129
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java
  5. 128
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/Constraint.java
  6. 464
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java
  7. 415
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  8. 13
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java
  9. 186
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java
  10. 146
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java
  11. 242
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java
  12. 16
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java
  13. 15
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java
  14. 27
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java
  15. 19
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java
  16. 10
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java
  17. 14
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java
  18. 41
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java
  19. 16
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java
  20. 13
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java
  21. 31
      engine/src/blender/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java
  22. 75
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  23. 74
      engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@ -46,8 +46,8 @@ import com.jme3.asset.AssetManager;
import com.jme3.asset.BlenderKey; import com.jme3.asset.BlenderKey;
import com.jme3.material.Material; import com.jme3.material.Material;
import com.jme3.math.ColorRGBA; 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.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.constraints.Constraint; import com.jme3.scene.plugins.blender.constraints.Constraint;
import com.jme3.scene.plugins.blender.file.BlenderInputStream; import com.jme3.scene.plugins.blender.file.BlenderInputStream;
import com.jme3.scene.plugins.blender.file.DnaBlockData; import com.jme3.scene.plugins.blender.file.DnaBlockData;
@ -101,11 +101,6 @@ public class BlenderContext {
private Map<String, Object[]> loadedFeaturesByName = new HashMap<String, Object[]>(); private Map<String, Object[]> loadedFeaturesByName = new HashMap<String, Object[]>();
/** A stack that hold the parent structure of currently loaded feature. */ /** A stack that hold the parent structure of currently loaded feature. */
private Stack<Structure> parentStack = new Stack<Structure>(); private Stack<Structure> parentStack = new Stack<Structure>();
/**
* A map storing loaded ipos. The key is the ipo's owner old memory address
* and the value is the ipo.
*/
private Map<Long, Ipo> loadedIpos = new HashMap<Long, Ipo>();
/** A list of modifiers for the specified object. */ /** A list of modifiers for the specified object. */
protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>(); protected Map<Long, List<Modifier>> modifiers = new HashMap<Long, List<Modifier>>();
/** A list of constraints for the specified object. */ /** A list of constraints for the specified object. */
@ -114,6 +109,8 @@ public class BlenderContext {
private Map<Long, AnimData> animData = new HashMap<Long, AnimData>(); private Map<Long, AnimData> animData = new HashMap<Long, AnimData>();
/** Loaded skeletons. */ /** Loaded skeletons. */
private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>(); private Map<Long, Skeleton> skeletons = new HashMap<Long, Skeleton>();
/** A map between skeleton and node it modifies. */
private Map<Skeleton, Node> nodesWithSkeletons = new HashMap<Skeleton, Node>();
/** A map of mesh contexts. */ /** A map of mesh contexts. */
protected Map<Long, MeshContext> meshContexts = new HashMap<Long, MeshContext>(); protected Map<Long, MeshContext> meshContexts = new HashMap<Long, MeshContext>();
/** A map of bone contexts. */ /** A map of bone contexts. */
@ -345,25 +342,6 @@ public class BlenderContext {
return null; 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. * 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. * This method adds a new modifier to the list.
* *
@ -499,18 +445,6 @@ public class BlenderContext {
objectConstraints.addAll(constraints); objectConstraints.addAll(constraints);
} }
/**
* This method returns constraints for the object specified by its old
* memory address. If no modifiers are found - <b>null</b> is returned.
*
* @param objectOMA
* object's old memory address
* @return the list of object's modifiers or null
*/
public List<Constraint> getConstraints(Long objectOMA) {
return objectOMA == null ? null : constraints.get(objectOMA);
}
/** /**
* @return all available constraints * @return all available constraints
*/ */
@ -557,6 +491,30 @@ public class BlenderContext {
this.skeletons.put(skeletonOMA, skeleton); 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. * This method returns the skeleton for the specified OMA of its owner.
* *
@ -635,6 +593,22 @@ public class BlenderContext {
return null; 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<Long, BoneContext> 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. * This metod returns the default material.
* *
@ -658,7 +632,6 @@ public class BlenderContext {
loadedFeatures.clear(); loadedFeatures.clear();
loadedFeaturesByName.clear(); loadedFeaturesByName.clear();
parentStack.clear(); parentStack.clear();
loadedIpos.clear();
modifiers.clear(); modifiers.clear();
constraints.clear(); constraints.clear();
animData.clear(); animData.clear();
@ -672,7 +645,7 @@ public class BlenderContext {
* This enum defines what loaded data type user wants to retreive. It can be * This enum defines what loaded data type user wants to retreive. It can be
* either filled structure or already converted data. * either filled structure or already converted data.
* *
* @author Marcin Roguski * @author Marcin Roguski (Kaelthas)
*/ */
public static enum LoadedFeatureDataType { public static enum LoadedFeatureDataType {

@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
@ -19,6 +20,7 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class BoneContext { public class BoneContext {
private BlenderContext blenderContext;
/** The OMA of the bone's armature object. */ /** The OMA of the bone's armature object. */
private Long armatureObjectOMA; private Long armatureObjectOMA;
/** The structure of the bone. */ /** The structure of the bone. */
@ -30,7 +32,7 @@ public class BoneContext {
/** The parent context. */ /** The parent context. */
private BoneContext parent; private BoneContext parent;
/** The children of this context. */ /** The children of this context. */
private List<BoneContext> children = new ArrayList<BoneContext>(); private List<BoneContext> children = new ArrayList<BoneContext>();
/** Created bone (available after calling 'buildBone' method). */ /** Created bone (available after calling 'buildBone' method). */
private Bone bone; private Bone bone;
/** The bone's rest matrix. */ /** The bone's rest matrix. */
@ -74,6 +76,7 @@ public class BoneContext {
*/ */
private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException { private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException {
this.parent = parent; this.parent = parent;
this.blenderContext = blenderContext;
this.boneStructure = boneStructure; this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA; this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString(); boneName = boneStructure.getFieldValue("name").toString();
@ -81,14 +84,14 @@ public class BoneContext {
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis()); armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
//compute the bone's rest matrix // compute the bone's rest matrix
restMatrix = armatureMatrix.clone(); restMatrix = armatureMatrix.clone();
inverseTotalTransformation = restMatrix.invert(); inverseTotalTransformation = restMatrix.invert();
if(parent != null) { if (parent != null) {
restMatrix = parent.inverseTotalTransformation.mult(restMatrix); restMatrix = parent.inverseTotalTransformation.mult(restMatrix);
} }
//create the children // create the children
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext); List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
for (Structure child : childbase) { for (Structure child : childbase) {
this.children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext)); this.children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
@ -97,7 +100,6 @@ public class BoneContext {
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
} }
/** /**
* This method builds the bone. It recursively builds the bone's children. * This method builds the bone. It recursively builds the bone's children.
* *
@ -118,14 +120,12 @@ public class BoneContext {
boneOMAs.put(bone, boneOMA); boneOMAs.put(bone, boneOMA);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone); blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Vector3f poseLocation = restMatrix.toTranslationVector(); Vector3f poseLocation = restMatrix.toTranslationVector();
Quaternion rotation = restMatrix.toRotationQuat().normalizeLocal(); Quaternion rotation = restMatrix.toRotationQuat().normalizeLocal();
Vector3f scale = objectHelper.getScale(restMatrix); Vector3f scale = restMatrix.toScaleVector();
if(parent == null) { if (parent == null) {
Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal(); Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal();
scale.multLocal(objectHelper.getScale(objectToArmatureMatrix)); scale.multLocal(objectToArmatureMatrix.toScaleVector());
rotationQuaternion.multLocal(poseLocation.addLocal(objectToArmatureMatrix.toTranslationVector())); rotationQuaternion.multLocal(poseLocation.addLocal(objectToArmatureMatrix.toTranslationVector()));
rotation.multLocal(rotationQuaternion); rotation.multLocal(rotationQuaternion);
} }
@ -165,4 +165,11 @@ public class BoneContext {
public Long getArmatureObjectOMA() { public Long getArmatureObjectOMA() {
return armatureObjectOMA; return armatureObjectOMA;
} }
/**
* @return the skeleton the bone of this context belongs to
*/
public Skeleton getSkeleton() {
return blenderContext.getSkeleton(armatureObjectOMA);
}
} }

@ -40,7 +40,10 @@ public class Ipo {
private Track calculatedTrack; private Track calculatedTrack;
/** This variable indicates if the Y asxis is the UP axis or not. */ /** This variable indicates if the Y asxis is the UP axis or not. */
protected boolean fixUpAxis; 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; protected final int blenderVersion;
/** /**
@ -158,7 +161,8 @@ public class Ipo {
// calculating track data // calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) { for (int frame = startFrame; frame <= stopFrame; ++frame) {
int index = frame - startFrame; 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) { for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) { switch (bezierCurves[j].getType()) {
@ -168,7 +172,7 @@ public class Ipo {
break; break;
case AC_LOC_Y: case AC_LOC_Y:
if (fixUpAxis) { if (fixUpAxis) {
translation[2] = (float) -value; translation[2] = value == 0.0f ? 0 : (float) -value;
} else { } else {
translation[1] = (float) value; translation[1] = (float) value;
} }
@ -185,7 +189,7 @@ public class Ipo {
break; break;
case OB_ROT_Y: case OB_ROT_Y:
if (fixUpAxis) { if (fixUpAxis) {
objectRotation[2] = (float) -value * degreeToRadiansFactor; objectRotation[2] = value == 0.0f ? 0 : (float) -value * degreeToRadiansFactor;
} else { } else {
objectRotation[1] = (float) value * degreeToRadiansFactor; objectRotation[1] = (float) value * degreeToRadiansFactor;
} }
@ -199,11 +203,7 @@ public class Ipo {
scale[0] = (float) value; scale[0] = (float) value;
break; break;
case AC_SIZE_Y: case AC_SIZE_Y:
if (fixUpAxis) { scale[fixUpAxis ? 2 : 1] = (float) value;
scale[2] = (float) value;
} else {
scale[1] = (float) value;
}
break; break;
case AC_SIZE_Z: case AC_SIZE_Z:
scale[fixUpAxis ? 1 : 2] = (float) value; scale[fixUpAxis ? 1 : 2] = (float) value;
@ -220,17 +220,13 @@ public class Ipo {
break; break;
case AC_QUAT_Y: case AC_QUAT_Y:
if (fixUpAxis) { if (fixUpAxis) {
quaternionRotation[2] = -(float) value; quaternionRotation[2] = value == 0.0f ? 0 : -(float) value;
} else { } else {
quaternionRotation[1] = (float) value; quaternionRotation[1] = (float) value;
} }
break; break;
case AC_QUAT_Z: case AC_QUAT_Z:
if (fixUpAxis) { quaternionRotation[fixUpAxis ? 1 : 2] = (float) value;
quaternionRotation[1] = (float) value;
} else {
quaternionRotation[2] = (float) value;
}
break; break;
default: default:
LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType()); LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());

@ -3,10 +3,6 @@ package com.jme3.scene.plugins.blender.constraints;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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.math.Transform;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext; 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.ArmatureHelper;
import com.jme3.scene.plugins.blender.animations.BoneContext; import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo; 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.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.ogre.AnimData;
/** /**
* Constraint applied on the bone. * Constraint applied on the bone.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class BoneConstraint extends Constraint { /* package */class BoneConstraint extends Constraint {
@ -47,12 +42,14 @@ import com.jme3.scene.plugins.ogre.AnimData;
} }
@Override @Override
protected boolean validate() { public boolean validate() {
if (targetOMA != null) { if (targetOMA != null) {
Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); 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 (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); BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { 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); 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; isNodeTarget = true;
} }
} }
return true; return true;
} }
@Override @Override
public void performBakingOperation() { public void apply(int frame) {
Bone owner = blenderContext.getBoneContext(ownerOMA).getBone(); BoneContext boneContext = blenderContext.getBoneContext(ownerOMA);
Transform ownerTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace);
if (targetOMA != null) { if (targetOMA != null) {
if (isNodeTarget) { if (isNodeTarget) {
Spatial target = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE); Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
this.prepareTracksForApplyingConstraints(); constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
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);
}
}
} else { } else {
BoneContext boneContext = blenderContext.getBoneByName(subtargetName); Transform targetTransform = constraintHelper.getTransform(targetOMA, subtargetName, targetSpace);
Bone target = boneContext.getBone(); constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
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);
}
}
} }
} else { } else {
this.prepareTracksForApplyingConstraints(); constraintDefinition.bake(ownerTransform, null, this.ipo.calculateValue(frame));
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 <b>true</b> if the target has animations and <b>false</b> 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;
}
} }
return false; constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform);
} }
} }

@ -1,17 +1,9 @@
package com.jme3.scene.plugins.blender.constraints; package com.jme3.scene.plugins.blender.constraints;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; 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.BlenderContext;
import com.jme3.scene.plugins.blender.animations.BoneContext;
import com.jme3.scene.plugins.blender.animations.Ipo; import com.jme3.scene.plugins.blender.animations.Ipo;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition; 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.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.ogre.AnimData;
/** /**
* The implementation of a constraint. * The implementation of a constraint.
@ -55,6 +46,8 @@ public abstract class Constraint {
* the constraint's structure (bConstraint clss in blender 2.49). * the constraint's structure (bConstraint clss in blender 2.49).
* @param ownerOMA * @param ownerOMA
* the old memory address of the constraint owner * the old memory address of the constraint owner
* @param ownerType
* the type of the constraint owner
* @param influenceIpo * @param influenceIpo
* the ipo curve of the influence factor * the ipo curve of the influence factor
* @param blenderContext * @param blenderContext
@ -69,101 +62,88 @@ public abstract class Constraint {
Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); Pointer pData = (Pointer) constraintStructure.getFieldValue("data");
if (pData.isNotNull()) { if (pData.isNotNull()) {
Structure data = pData.fetchData(blenderContext.getInputStream()).get(0); 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"); Pointer pTar = (Pointer) data.getFieldValue("tar");
if (pTar != null && pTar.isNotNull()) { if (pTar != null && pTar.isNotNull()) {
this.targetOMA = pTar.getOldMemoryAddress(); this.targetOMA = pTar.getOldMemoryAddress();
this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); this.targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue());
Object subtargetValue = data.getFieldValue("subtarget"); 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(); subtargetName = subtargetValue.toString();
} }
} }
} else { } else {
// Null constraint has no data, so create it here // 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.ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue());
this.ipo = influenceIpo; this.ipo = influenceIpo;
this.ownerOMA = ownerOMA; this.ownerOMA = ownerOMA;
this.constraintHelper = blenderContext.getHelper(ConstraintHelper.class); 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 * @return <b>true</b> if the constraint is implemented and <b>false</b>
* or if it isn't yet baked. It also performs baking of its target constraints so that the proper baking * otherwise
* order is kept.
*/ */
public void bake() { public boolean isImplemented() {
if (!this.validate()) { return constraintDefinition == null ? true : constraintDefinition.isImplemented();
LOGGER.warning("The constraint " + name + " is invalid and will not be applied.");
} else if (!baked) {
if (targetOMA != null) {
List<Constraint> 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;
}
} }
/** /**
* Performs validation before baking. Checks factors that can prevent constraint from baking that could not be * @return the name of the constraint type, similar to the constraint name
* checked during constraint loading. * used in Blender
*/
protected abstract boolean validate();
/**
* This method should be overwridden and perform the baking opertion.
*/
protected abstract void performBakingOperation();
/**
* 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(); public String getConstraintTypeName() {
return constraintDefinition.getConstraintTypeName();
}
/** /**
* The method applies bone's current position to all of the traces of the * Performs validation before baking. Checks factors that can prevent
* given animations. * constraint from baking that could not be checked during constraint
* * loading.
* @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) { public abstract boolean validate();
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) { @Override
BoneTrack parentTrack = (BoneTrack) animation.getTracks()[0]; 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(); @Override
Vector3f[] translations = new Vector3f[times.length]; public boolean equals(Object obj) {
Quaternion[] rotations = new Quaternion[times.length]; if (this == obj) {
Vector3f[] scales = new Vector3f[times.length]; return true;
Arrays.fill(translations, transform.getTranslation()); }
Arrays.fill(rotations, transform.getRotation()); if (obj == null) {
Arrays.fill(scales, transform.getScale()); return false;
for (Animation anim : animData.anims) { }
anim.addTrack(new BoneTrack(animData.skeleton.getBoneIndex(boneContext.getBone()), times, translations, rotations, scales)); 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;
} }
} }

@ -1,51 +1,65 @@
package com.jme3.scene.plugins.blender.constraints; package com.jme3.scene.plugins.blender.constraints;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.jme3.animation.Animation;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton; import com.jme3.animation.Skeleton;
import com.jme3.animation.SpatialTrack; import com.jme3.math.FastMath;
import com.jme3.animation.Track;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial; import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper; import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; 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.Ipo;
import com.jme3.scene.plugins.blender.animations.IpoHelper; import com.jme3.scene.plugins.blender.animations.IpoHelper;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; 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.Pointer;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
/** /**
* This class should be used for constraint calculations. * This class should be used for constraint calculations.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ConstraintHelper extends AbstractBlenderHelper { 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 * Helper constructor. It's main task is to generate the affection
* ConstraintHelper instances. Unfortunately this constructor might grow large. If it becomes too large - I shall * functions. These functions are common to all ConstraintHelper instances.
* consider refactoring. The constructor parses the given blender version and stores the result. Some * Unfortunately this constructor might grow large. If it becomes too large
* functionalities may differ in different blender versions. * - 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 * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param blenderContext
* the blender context
* @param fixUpAxis * @param fixUpAxis
* a variable that indicates if the Y asxis is the UP axis or not * a variable that indicates if the Y asxis is the UP axis or not
*/ */
public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) { public ConstraintHelper(String blenderVersion, BlenderContext blenderContext, boolean fixUpAxis) {
super(blenderVersion, fixUpAxis); super(blenderVersion, fixUpAxis);
this.blenderContext = blenderContext;
} }
/** /**
@ -95,7 +109,8 @@ public class ConstraintHelper extends AbstractBlenderHelper {
List<Constraint> constraintsList = new ArrayList<Constraint>(); List<Constraint> constraintsList = new ArrayList<Constraint>();
Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); 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(); String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString();
List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext); List<Structure> constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(blenderContext);
for (Structure constraint : constraints) { for (Structure constraint : constraints) {
@ -130,7 +145,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
ipo = ipoHelper.fromValue(enforce); 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); blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList);
} }
@ -155,7 +170,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
* @throws BlenderFileException * @throws BlenderFileException
* thrown when problems with blender file occured * 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)) { if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) {
return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} else if ("Armature".equalsIgnoreCase(dataType)) { } else if ("Armature".equalsIgnoreCase(dataType)) {
@ -172,266 +187,243 @@ public class ConstraintHelper extends AbstractBlenderHelper {
* the blender context * the blender context
*/ */
public void bakeConstraints(BlenderContext blenderContext) { public void bakeConstraints(BlenderContext blenderContext) {
List<SimulationNode> simulationRootNodes = new ArrayList<SimulationNode>();
for (Constraint constraint : blenderContext.getAllConstraints()) { for (Constraint constraint : blenderContext.getAllConstraints()) {
constraint.bake(); boolean constraintUsed = false;
} for (SimulationNode node : simulationRootNodes) {
} if (node.contains(constraint)) {
constraintUsed = true;
break;
}
}
/** if (!constraintUsed) {
* The method returns track for bone. if (constraint instanceof BoneConstraint) {
* BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA);
* @param bone simulationRootNodes.add(new SimulationNode(boneContext.getArmatureObjectOMA(), blenderContext));
* the bone } else if (constraint instanceof SpatialConstraint) {
* @param skeleton Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
* the bone's skeleton while (spatial.getParent() != null) {
* @param animation spatial = spatial.getParent();
* the bone's animation }
* @return track for the given bone that was found among the given simulationRootNodes.add(new SimulationNode((Long) spatial.getUserData("oma"), blenderContext));
* animations or null if none is found } else {
*/ throw new IllegalStateException("Unsupported constraint type: " + constraint);
/* 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;
} }
} }
return null;
}
/** for (SimulationNode node : simulationRootNodes) {
* The method returns track for spatial. node.simulate();
*
* @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];
} }
return null;
} }
/** /**
* This method returns the transform read directly from the blender * The method retreives the transform from a feature in a given space.
* structure. This can be used to read transforms from one of the object
* types: <li>Spatial <li>Camera <li>Light
* *
* @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 * @param space
* the space where transform is evaluated * the space the transform is evaluated to
* @param spatialOMA * @return thensform of a feature in a given space
* the OMA of the object
* @param blenderContext
* the blender context
* @return the object's transform in a given space
*/ */
@SuppressWarnings("unchecked") public Transform getTransform(Long oma, String subtargetName, Space space) {
/* package */Transform getNodeObjectTransform(Space space, Long spatialOMA, BlenderContext blenderContext) { Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
switch (space) { boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null;
case CONSTRAINT_SPACE_LOCAL: if (isArmature) {
Structure targetStructure = (Structure) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_STRUCTURE); BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName);
Bone bone = targetBoneContext.getBone();
DynamicArray<Number> locArray = ((DynamicArray<Number>) targetStructure.getFieldValue("loc"));
Vector3f loc = new Vector3f(locArray.get(0).floatValue(), locArray.get(1).floatValue(), locArray.get(2).floatValue()); switch (space) {
DynamicArray<Number> rotArray = ((DynamicArray<Number>) targetStructure.getFieldValue("rot")); case CONSTRAINT_SPACE_WORLD:
Quaternion rot = new Quaternion(new float[] { rotArray.get(0).floatValue(), rotArray.get(1).floatValue(), rotArray.get(2).floatValue() }); Transform t = new Transform(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale());
DynamicArray<Number> sizeArray = ((DynamicArray<Number>) targetStructure.getFieldValue("size")); System.out.println("A: " + Arrays.toString(t.getRotation().toAngles(null)));
Vector3f size = new Vector3f(sizeArray.get(0).floatValue(), sizeArray.get(1).floatValue(), sizeArray.get(2).floatValue()); return t;
case CONSTRAINT_SPACE_LOCAL:
if (blenderContext.getBlenderKey().isFixUpAxis()) { Transform localTransform = new Transform(bone.getLocalPosition(), bone.getLocalRotation());
float y = loc.y; localTransform.setScale(bone.getLocalScale());
loc.y = loc.z; return localTransform;
loc.z = -y; case CONSTRAINT_SPACE_POSE:
Node nodeWithAnimationControl = blenderContext.getControlledNode(targetBoneContext.getSkeleton());
y = rot.getY(); Matrix4f m = this.toMatrix(nodeWithAnimationControl.getWorldTransform());
float z = rot.getZ(); Matrix4f boneAgainstModifiedNodeMatrix = this.toMatrix(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale());
rot.set(rot.getX(), z, -y, rot.getW()); Matrix4f boneWorldMatrix = m.multLocal(boneAgainstModifiedNodeMatrix);
y = size.y; Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform()).invertLocal();
size.y = size.z; Matrix4f r2 = armatureWorldMatrix.multLocal(boneWorldMatrix);
size.z = y;
} 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); Vector3f loc = result.toTranslationVector();
result.setScale(size); Quaternion rot = result.toRotationQuat().normalizeLocal().multLocal(NEG_PARLOC_SPACE_QUATERNION);
return result; Vector3f scl = result.toScaleVector();
case CONSTRAINT_SPACE_WORLD:// TODO: get it from the object structure ??? return new Transform(loc, rot, scl);
Object feature = blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE); default:
if (feature instanceof Spatial) { 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(); return ((Spatial) feature).getWorldTransform();
} else if (feature instanceof Skeleton) { case CONSTRAINT_SPACE_PARLOCAL:
LOGGER.warning("Trying to get transformation for skeleton. This is not supported. Returning null."); case CONSTRAINT_SPACE_POSE:
return null; throw new IllegalStateException("Nodes can have only Local and World spaces applied!");
} else { default:
throw new IllegalArgumentException("Given old memory address does not point to a valid object type (spatial, camera or light)."); throw new IllegalStateException("Unknown space type: " + space);
} }
default:
throw new IllegalStateException("Invalid space type for target object: " + space.toString());
} }
} }
/** /**
* 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. * 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 * @param space
* the computation space * the space in which the given transform is to be applied
* @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
* @param transform * @param transform
* the transform being applied * the transform we apply
*/ */
/* package */void applyTransform(Spatial spatial, Space space, Transform transform) { public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) {
switch (space) { Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedFeatureDataType.LOADED_FEATURE);
case CONSTRAINT_SPACE_LOCAL: boolean isArmature = feature.getUserData(ArmatureHelper.ARMETURE_NODE_MARKER) != null;
Transform ownerLocalTransform = spatial.getLocalTransform(); if (isArmature) {
ownerLocalTransform.getTranslation().addLocal(transform.getTranslation()); Skeleton skeleton = blenderContext.getSkeleton(oma);
ownerLocalTransform.getRotation().multLocal(transform.getRotation()); BoneContext targetBoneContext = blenderContext.getBoneByName(subtargetName);
ownerLocalTransform.getScale().multLocal(transform.getScale()); Bone bone = targetBoneContext.getBone();
break;
case CONSTRAINT_SPACE_WORLD: Node nodeControlledBySkeleton = blenderContext.getControlledNode(skeleton);
Matrix4f m = this.getParentWorldTransformMatrix(spatial); Transform nodeTransform = nodeControlledBySkeleton.getWorldTransform();
m.invertLocal(); Matrix4f invertedNodeMatrix = this.toMatrix(nodeTransform).invertLocal();
Matrix4f matrix = this.toMatrix(transform);
m.multLocal(matrix); switch (space) {
case CONSTRAINT_SPACE_LOCAL:
float scaleX = (float) Math.sqrt(m.m00 * m.m00 + m.m10 * m.m10 + m.m20 * m.m20); bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale());
float scaleY = (float) Math.sqrt(m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21); break;
float scaleZ = (float) Math.sqrt(m.m02 * m.m02 + m.m12 * m.m12 + m.m22 * m.m22); case CONSTRAINT_SPACE_WORLD:
System.out.println("B: " + Arrays.toString(transform.getRotation().toAngles(null)));
transform.setTranslation(m.toTranslationVector()); Matrix4f boneMatrix = this.toMatrix(transform);
transform.setRotation(m.toRotationQuat()); Bone parent = bone.getParent();
transform.setScale(scaleX, scaleY, scaleZ); if (parent != null) {
spatial.setLocalTransform(transform); Matrix4f invertedParentWorldMatrix = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale()).invertLocal();
break; boneMatrix = invertedParentWorldMatrix.multLocal(boneMatrix);
case CONSTRAINT_SPACE_PARLOCAL: }
case CONSTRAINT_SPACE_POSE:
throw new IllegalStateException("Invalid space type (" + space.toString() + ") for owner object."); boneMatrix = invertedNodeMatrix.multLocal(boneMatrix);
default: bone.setBindTransforms(boneMatrix.toTranslationVector(), boneMatrix.toRotationQuat(), boneMatrix.toScaleVector());
throw new IllegalStateException("Invalid space type for target object: " + space.toString()); 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 * Converts given transform to the matrix.
* given space.
* *
* @param bone
* the bone we apply the transform for
* @param space
* the computation space
* @param transform * @param transform
* the transform being applied * the transform to be converted
*/ * @return 4x4 matrix that represents the given transform
/* 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
*/ */
private Matrix4f getParentWorldTransformMatrix(Bone bone) { private Matrix4f toMatrix(Transform transform) {
Matrix4f result = new Matrix4f(); Matrix4f result = Matrix4f.IDENTITY;
Bone parent = bone.getParent(); if (transform != null) {
if (parent != null) { result = this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale());
result.setTransform(parent.getWorldBindPosition(), parent.getWorldBindScale(), parent.getWorldBindRotation().toRotationMatrix());
} }
return result; return result;
} }
/** /**
* Converts given transform to the matrix. * Converts given transformation parameters into the matrix.
* *
* @param transform * @param transform
* the transform to be converted * 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) { private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale) {
Matrix4f result = Matrix4f.IDENTITY; Matrix4f result = new Matrix4f();
if (transform != null) { result.setTranslation(position);
result = new Matrix4f(); result.setRotationQuaternion(rotation);
result.setTranslation(transform.getTranslation()); result.setScale(scale);
result.setRotationQuaternion(transform.getRotation());
result.setScale(transform.getScale());
}
return result; return result;
} }
@ -447,7 +439,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
*/ */
public static enum Space { 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 * This method returns the enum instance when given the appropriate
@ -468,7 +460,7 @@ public class ConstraintHelper extends AbstractBlenderHelper {
case 3: case 3:
return CONSTRAINT_SPACE_PARLOCAL; return CONSTRAINT_SPACE_PARLOCAL;
default: default:
return CONSTRAINT_SPACE_INVALID; throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!");
} }
} }
} }

@ -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<SimulationNode> children = new ArrayList<SimulationNode>();
/** A virtual track for each of the nodes. */
private Map<String, VirtualTrack> virtualTrack = new HashMap<String, VirtualTrack>();
/** A list of constraints that the current node has. */
private List<Constraint> constraints;
/** A list of node's animations. */
private List<Animation> 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<Bone, Transform> boneStartTransforms;
/**
* Builds the nodes tree for the given feature. The feature (bone or
* spatial) is found by its OMA. The feature must be a root bone or a root
* 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<Bone, Transform>();
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<Constraint>();
}
// add children nodes
if (skeleton != null) {
// bone with index 0 is a root bone and should not be considered
// here
for (int i = 1; i < skeleton.getBoneCount(); ++i) {
BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(i));
List<Constraint> boneConstraints = this.findConstraints(boneContext.getBoneOma(), blenderContext);
if (boneConstraints != null) {
constraints.addAll(boneConstraints);
}
}
// 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<Constraint> validConstraints = new ArrayList<Constraint>(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 <b>true</b> if the constraint already is stored in the node and
* <b>false</b> otherwise
*/
public boolean contains(Constraint constraint) {
boolean result = false;
if (constraints != null && constraints.size() > 0) {
for (Constraint c : constraints) {
if (c.equals(constraint)) {
return true;
}
}
}
return result;
}
/**
* 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<Bone, Transform> 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<Integer, VirtualTrack> tracks = new HashMap<Integer, VirtualTrack>();
Map<Integer, Transform> previousTransforms = new HashMap<Integer, Transform>();
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<Integer, VirtualTrack> 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<Constraint> findConstraints(Long ownerOMA, BlenderContext blenderContext) {
List<Constraint> result = new ArrayList<Constraint>();
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;
}
}

@ -23,16 +23,13 @@ import com.jme3.scene.plugins.blender.file.Structure;
} }
@Override @Override
public void performBakingOperation() { public boolean validate() {
LOGGER.warning("Applying constraints to skeleton is not supported."); LOGGER.warning("Constraints for skeleton are not supported.");
return false;
} }
@Override @Override
protected boolean validate() { public void apply(int frame) {
return true; LOGGER.warning("Applying constraints to skeleton is not supported.");
}
@Override
protected void prepareTracksForApplyingConstraints() {
} }
} }

@ -1,49 +1,25 @@
package com.jme3.scene.plugins.blender.constraints; 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.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;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType; 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.Ipo;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.exceptions.BlenderFileException; import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.ogre.AnimData;
/** /**
* Constraint applied on the spatial objects. * Constraint applied on the spatial objects. This includes: nodes, cameras
* This includes: nodes, cameras nodes and light nodes. * nodes and light nodes.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class SpatialConstraint extends Constraint { /* 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 { public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException {
super(constraintStructure, ownerOMA, influenceIpo, blenderContext); super(constraintStructure, ownerOMA, influenceIpo, blenderContext);
} }
@Override @Override
protected boolean validate() { public boolean validate() {
if (targetOMA != null) { if (targetOMA != null) {
return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null; return blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) != null;
} }
@ -51,154 +27,10 @@ import com.jme3.scene.plugins.ogre.AnimData;
} }
@Override @Override
public void performBakingOperation() { public void apply(int frame) {
this.owner = (Spatial) blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE); Transform ownerTransform = constraintHelper.getTransform(ownerOMA, null, ownerSpace);
this.target = targetOMA != null ? blenderContext.getLoadedFeature(targetOMA, LoadedFeatureDataType.LOADED_FEATURE) : null; Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null;
this.prepareTracksForApplyingConstraints(); constraintDefinition.bake(ownerTransform, targetTransform, this.ipo.calculateValue(frame));
constraintHelper.applyTransform(ownerOMA, subtargetName, ownerSpace, ownerTransform);
// 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 <b>true</b> if the target has animations and <b>false</b> 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<String, Animation> anims = new HashMap<String, Animation>(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<Animation>(anims.values())));
} }
} }

@ -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<Vector3f> translations;
/** Rotations of the track. */
public ArrayList<Quaternion> rotations;
/** Scales of the track. */
public ArrayList<Vector3f> 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 <T> ArrayList<T> createList(T element, int count) {
ArrayList<T> result = new ArrayList<T>(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 <T> void append(ArrayList<T> list, T element, int count) {
for (int i = 0; i < count; ++i) {
list.add(element);
}
}
}

@ -1,243 +1,49 @@
package com.jme3.scene.plugins.blender.constraints.definitions; 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.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext; 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.scene.plugins.blender.file.Structure;
import com.jme3.util.TempVars;
public abstract class ConstraintDefinition { 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 if (constraintData != null) {// Null constraint has no data
Number flag = (Number) constraintData.getFieldValue("flag"); Number flag = (Number) constraintData.getFieldValue("flag");
if (flag != null) { if (flag != null) {
this.flag = flag.intValue(); this.flag = flag.intValue();
} }
} }
} this.blenderContext = blenderContext;
this.ownerOMA = ownerOMA;
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 class holds either the bone track or spatial track. Is made to improve * This method is here because we have no guarantee that the owner is loaded
* code readability. * 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 { public Object getOwner() {
/** The spatial track. */ if (ownerOMA != null && owner == null) {
private SpatialTrack spatialTrack; owner = blenderContext.getLoadedFeature(ownerOMA, LoadedFeatureDataType.LOADED_FEATURE);
/** The bone track. */ if (owner == null) {
private BoneTrack boneTrack; throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName());
/**
* Constructs the object using the given track. The track must be of one of the types: <li>BoneTrack <li>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!");
} }
} }
return owner;
}
/** public boolean isImplemented() {
* @return the array of rotations of this track return true;
*/ }
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 float getLength() { public abstract String getConstraintTypeName();
return spatialTrack == null ? boneTrack.getLength() : spatialTrack.getLength();
}
@Override public abstract void bake(Transform ownerTransform, Transform targetTransform, float influence);
public TrackWrapper clone() {
if (boneTrack != null) {
return new TrackWrapper(boneTrack.clone());
}
return new TrackWrapper(spatialTrack.clone());
}
}
} }

@ -1,5 +1,6 @@
package com.jme3.scene.plugins.blender.constraints.definitions; package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext; 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. * This class represents 'Dist limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition {
@ -17,14 +19,19 @@ import com.jme3.scene.plugins.blender.file.Structure;
protected int mode; protected int mode;
protected float dist; protected float dist;
public ConstraintDefinitionDistLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
mode = ((Number) constraintData.getFieldValue("mode")).intValue(); mode = ((Number) constraintData.getFieldValue("mode")).intValue();
dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); dist = ((Number) constraintData.getFieldValue("dist")).floatValue();
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { 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()); Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation());
float currentDistance = v.length(); 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); throw new IllegalStateException("Unknown distance limit constraint mode: " + mode);
} }
} }
@Override
public String getConstraintTypeName() {
return "Limit distance";
}
} }

@ -40,7 +40,7 @@ import com.jme3.scene.plugins.blender.exceptions.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
public class ConstraintDefinitionFactory { public class ConstraintDefinitionFactory {
private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>(); private static final Map<String, Class<? extends ConstraintDefinition>> CONSTRAINT_CLASSES = new HashMap<String, Class<? extends ConstraintDefinition>>();
static { static {
CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class);
CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class);
@ -52,7 +52,7 @@ public class ConstraintDefinitionFactory {
CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class);
} }
private static final Map<String, String> UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>(); private static final Map<String, String> UNSUPPORTED_CONSTRAINTS = new HashMap<String, String>();
static { static {
UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action"); UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action");
UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of"); UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of");
@ -84,22 +84,23 @@ public class ConstraintDefinitionFactory {
* This method creates the constraint instance. * This method creates the constraint instance.
* *
* @param constraintStructure * @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 * @param blenderContext
* the blender context * the blender context
* @throws BlenderFileException * @throws BlenderFileException
* this exception is thrown when the blender file is somehow * this exception is thrown when the blender file is somehow
* corrupted * 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) { if (constraintStructure == null) {
return new ConstraintDefinitionNull(null, blenderContext); return new ConstraintDefinitionNull(null, ownerOMA, blenderContext);
} }
String constraintClassName = constraintStructure.getType(); String constraintClassName = constraintStructure.getType();
Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); Class<? extends ConstraintDefinition> constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName);
if (constraintDefinitionClass != null) { if (constraintDefinitionClass != null) {
try { try {
return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, blenderContext); return (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new BlenderFileException(e.getLocalizedMessage(), e); throw new BlenderFileException(e.getLocalizedMessage(), e);
} catch (SecurityException e) { } catch (SecurityException e) {
@ -113,7 +114,7 @@ public class ConstraintDefinitionFactory {
} }
} else { } else {
String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); String constraintName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName);
if(constraintName != null) { if (constraintName != null) {
return new UnsupportedConstraintDefinition(constraintName); return new UnsupportedConstraintDefinition(constraintName);
} else { } else {
throw new BlenderFileException("Unknown constraint type: " + constraintClassName); throw new BlenderFileException("Unknown constraint type: " + constraintClassName);

@ -1,5 +1,6 @@
package com.jme3.scene.plugins.blender.constraints.definitions; package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext; 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. * This class represents 'Loc like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition { /* package */class ConstraintDefinitionLocLike extends ConstraintDefinition {
private static final int LOCLIKE_X = 0x01; private static final int LOCLIKE_X = 0x01;
private static final int LOCLIKE_Y = 0x02; private static final int LOCLIKE_Y = 0x02;
private static final int LOCLIKE_Z = 0x04; 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_X_INVERT = 0x10;
private static final int LOCLIKE_Y_INVERT = 0x20; private static final int LOCLIKE_Y_INVERT = 0x20;
private static final int LOCLIKE_Z_INVERT = 0x40; private static final int LOCLIKE_Z_INVERT = 0x40;
private static final int LOCLIKE_OFFSET = 0x80; private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionLocLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
// swapping Y and X limits flag in the bitwise flag // swapping Y and X limits flag in the bitwise flag
int y = flag & LOCLIKE_Y; int y = flag & LOCLIKE_Y;
int invY = flag & LOCLIKE_Y_INVERT; int invY = flag & LOCLIKE_Y_INVERT;
int z = flag & LOCLIKE_Z; int z = flag & LOCLIKE_Z;
int invZ = flag & LOCLIKE_Z_INVERT; 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 |= y << 1;
flag |= invY << 1; flag |= invY << 1;
flag |= z >> 1; flag |= z >> 1;
@ -37,12 +43,18 @@ import com.jme3.scene.plugins.blender.file.Structure;
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { 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 ownerLocation = ownerTransform.getTranslation();
Vector3f targetLocation = targetTransform.getTranslation(); Vector3f targetLocation = targetTransform.getTranslation();
Vector3f startLocation = ownerTransform.getTranslation().clone(); Vector3f startLocation = ownerTransform.getTranslation().clone();
Vector3f offset = Vector3f.ZERO; 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; offset = startLocation;
} }
@ -71,4 +83,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
ownerLocation.addLocal(startLocation); ownerLocation.addLocal(startLocation);
} }
} }
@Override
public String getConstraintTypeName() {
return "Copy location";
}
} }

@ -1,5 +1,6 @@
package com.jme3.scene.plugins.blender.constraints.definitions; package com.jme3.scene.plugins.blender.constraints.definitions;
import com.jme3.animation.Bone;
import com.jme3.math.Transform; import com.jme3.math.Transform;
import com.jme3.math.Vector3f; import com.jme3.math.Vector3f;
import com.jme3.scene.plugins.blender.BlenderContext; 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. * This class represents 'Loc limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition { /* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition {
@ -19,8 +21,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
protected float[][] limits = new float[3][2]; protected float[][] limits = new float[3][2];
public ConstraintDefinitionLocLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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 ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN; int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX; 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 |= ymin << 2;
flag |= ymax << 2; flag |= ymax << 2;
flag |= zmin >> 2; flag |= zmin >> 2;
@ -51,6 +54,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { 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(); Vector3f translation = ownerTransform.getTranslation();
if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { 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; translation.z -= (translation.z - limits[2][1]) * influence;
} }
} }
@Override
public String getConstraintTypeName() {
return "Limit location";
}
} }

@ -6,16 +6,22 @@ import com.jme3.scene.plugins.blender.file.Structure;
/** /**
* This class represents 'Null' constraint type in blender. * This class represents 'Null' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionNull extends ConstraintDefinition { /* package */class ConstraintDefinitionNull extends ConstraintDefinition {
public ConstraintDefinitionNull(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
} }
@Override @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
// null constraint does nothing so no need to implement this one // null constraint does nothing so no need to implement this one
} }
@Override
public String getConstraintTypeName() {
return "Null";
}
} }

@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
/** /**
* This class represents 'Rot like' constraint type in blender. * This class represents 'Rot like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition { /* 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[] ownerAngles = new float[3];
private transient float[] targetAngles = new float[3]; private transient float[] targetAngles = new float[3];
public ConstraintDefinitionRotLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
} }
@Override @Override
@ -33,7 +34,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
Quaternion startRotation = ownerRotation.clone(); Quaternion startRotation = ownerRotation.clone();
Quaternion offset = Quaternion.IDENTITY; 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; offset = startRotation;
} }
@ -58,10 +60,14 @@ import com.jme3.scene.plugins.blender.file.Structure;
ownerRotation.fromAngles(ownerAngles).multLocal(offset); ownerRotation.fromAngles(ownerAngles).multLocal(offset);
if (influence < 1.0f) { if (influence < 1.0f) {
// startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence);
// ownerLocation.addLocal(startLocation); // ownerLocation.addLocal(startLocation);
// TODO // TODO
} }
} }
@Override
public String getConstraintTypeName() {
return "Copy rotation";
}
} }

@ -18,13 +18,13 @@ import com.jme3.scene.plugins.blender.file.Structure;
private transient float[][] limits = new float[3][2]; private transient float[][] limits = new float[3][2];
private transient float[] angles = new float[3]; private transient float[] angles = new float[3];
public ConstraintDefinitionRotLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()/* && owner.spatial != null */) {// FIXME: !!!!!!!! if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue();
limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue();
limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue();
limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue();
limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).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 // until blender 2.49 the rotations values were stored in degrees
if (blenderContext.getBlenderVersion() <= 249) { 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][0] *= FastMath.DEG_TO_RAD;
limits[i][1] *= 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 @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) { 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) { if ((flag & LIMIT_XROT) != 0) {
float difference = 0.0f; float difference = 0.0f;
if (angles[0] < limits[0][0]) { if (angles[0] < limits[0][0]) {
@ -81,5 +104,11 @@ import com.jme3.scene.plugins.blender.file.Structure;
} }
angles[2] -= difference; angles[2] -= difference;
} }
ownerTransform.getRotation().fromAngles(angles);
}
@Override
public String getConstraintTypeName() {
return "Limit rotation";
} }
} }

@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
/** /**
* This class represents 'Size like' constraint type in blender. * This class represents 'Size like' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition { /* 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 SIZELIKE_Z = 0x04;
private static final int LOCLIKE_OFFSET = 0x80; private static final int LOCLIKE_OFFSET = 0x80;
public ConstraintDefinitionSizeLike(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
// swapping Y and X limits flag in the bitwise flag // swapping Y and X limits flag in the bitwise flag
int y = flag & SIZELIKE_Y; int y = flag & SIZELIKE_Y;
int z = flag & SIZELIKE_Z; 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 |= y << 1;
flag |= z >> 1; flag |= z >> 1;
} }
@ -33,7 +35,8 @@ import com.jme3.scene.plugins.blender.file.Structure;
Vector3f targetScale = targetTransform.getScale(); Vector3f targetScale = targetTransform.getScale();
Vector3f offset = Vector3f.ZERO; 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(); offset = ownerScale.clone();
} }
@ -48,4 +51,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
} }
ownerScale.addLocal(offset); ownerScale.addLocal(offset);
} }
@Override
public String getConstraintTypeName() {
return "Copy scale";
}
} }

@ -7,6 +7,7 @@ import com.jme3.scene.plugins.blender.file.Structure;
/** /**
* This class represents 'Size limit' constraint type in blender. * This class represents 'Size limit' constraint type in blender.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition { /* 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]; protected transient float[][] limits = new float[3][2];
public ConstraintDefinitionSizeLimit(Structure constraintData, BlenderContext blenderContext) { public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, blenderContext); super(constraintData, ownerOMA, blenderContext);
if (blenderContext.getBlenderKey().isFixUpAxis()) { if (blenderContext.getBlenderKey().isFixUpAxis()) {
limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue();
limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).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 ymax = flag & LIMIT_YMAX;
int zmin = flag & LIMIT_ZMIN; int zmin = flag & LIMIT_ZMIN;
int zmax = flag & LIMIT_ZMAX; 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 |= ymin << 2;
flag |= ymax << 2; flag |= ymax << 2;
flag |= zmin >> 2; flag |= zmin >> 2;
@ -72,4 +74,9 @@ import com.jme3.scene.plugins.blender.file.Structure;
scale.z -= (scale.z - limits[2][1]) * influence; scale.z -= (scale.z - limits[2][1]) * influence;
} }
} }
@Override
public String getConstraintTypeName() {
return "Limit scale";
}
} }

@ -1,28 +1,33 @@
package com.jme3.scene.plugins.blender.constraints.definitions; package com.jme3.scene.plugins.blender.constraints.definitions;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.math.Transform; import com.jme3.math.Transform;
/** /**
* This class represents a constraint that is defined by blender but not supported by either importer * This class represents a constraint that is defined by blender but not
* ot jme. It only wirtes down a warning when baking is called. * supported by either importer ot jme. It only wirtes down a warning when
* baking is called.
* *
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
/* package */ class UnsupportedConstraintDefinition extends ConstraintDefinition { /* package */class UnsupportedConstraintDefinition extends ConstraintDefinition {
private static final Logger LOGGER = Logger.getLogger(UnsupportedConstraintDefinition.class.getName()); private String typeName;
public UnsupportedConstraintDefinition(String typeName) {
super(null, null, null);
this.typeName = typeName;
}
private String name; @Override
public void bake(Transform ownerTransform, Transform targetTransform, float influence) {
}
public UnsupportedConstraintDefinition(String name) { @Override
super(null, null); public boolean isImplemented() {
this.name = name; return false;
} }
@Override @Override
protected void bake(Transform ownerTransform, Transform targetTransform, float influence) { public String getConstraintTypeName() {
LOGGER.log(Level.WARNING, "'{0}' constraint NOT implemented!", name); return typeName;
} }
} }

@ -44,7 +44,8 @@ import com.jme3.util.BufferUtils;
*/ */
/* package */class ArmatureModifier extends Modifier { /* package */class ArmatureModifier extends Modifier {
private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); 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 Skeleton skeleton;
private Structure objectStructure; private Structure objectStructure;
@ -71,7 +72,9 @@ import com.jme3.util.BufferUtils;
*/ */
public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException {
Structure meshStructure = ((Pointer) objectStructure.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0); 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 // if pDvert==null then there are not vertex groups and no need to load
// skeleton (untill bone envelopes are supported) // skeleton (untill bone envelopes are supported)
@ -109,7 +112,8 @@ import com.jme3.util.BufferUtils;
// read animations // read animations
ArrayList<Animation> animations = new ArrayList<Animation>(); ArrayList<Animation> animations = new ArrayList<Animation>();
List<FileBlockHeader> actionHeaders = blenderContext.getFileBlocks(Integer.valueOf(FileBlockHeader.BLOCK_AC00)); List<FileBlockHeader> 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) { for (FileBlockHeader header : actionHeaders) {
Structure actionStructure = header.getStructure(blenderContext); Structure actionStructure = header.getStructure(blenderContext);
String actionName = actionStructure.getName(); String actionName = actionStructure.getName();
@ -202,7 +206,8 @@ import com.jme3.util.BufferUtils;
if (bindPoseBuffer != null) { if (bindPoseBuffer != null) {
mesh.setBuffer(bindPoseBuffer); 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.Position).setUsage(Usage.Stream);
mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); mesh.getBuffer(Type.Normal).setUsage(Usage.Stream);
} }
@ -227,6 +232,8 @@ import com.jme3.util.BufferUtils;
node.addControl(control); node.addControl(control);
node.addControl(new SkeletonControl(animData.skeleton)); node.addControl(new SkeletonControl(animData.skeleton));
blenderContext.setNodeForSkeleton(skeleton, node);
return node; return node;
} }
@ -282,23 +289,50 @@ import com.jme3.util.BufferUtils;
* this exception is thrown when the blend file structure is * this exception is thrown when the blend file structure is
* somehow invalid or corrupted * somehow invalid or corrupted
*/ */
private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext) throws BlenderFileException { private VertexBuffer[] getBoneWeightAndIndexBuffer(Structure meshStructure, int vertexListSize, int[] bonesGroups, Map<Integer, List<Integer>> vertexReferenceMap, Map<Integer, Integer> groupToBoneIndexMap, BlenderContext blenderContext)
throws BlenderFileException {
bonesGroups[0] = 0; 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); FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX); ByteBuffer indicesData = BufferUtils.createByteBuffer(vertexListSize * MAXIMUM_WEIGHTS_PER_VERTEX);
if (pDvert.isNotNull()) {// assigning weights and bone indices if (pDvert.isNotNull()) {// assigning weights and bone indices
boolean warnAboutTooManyVertexWeights = false; boolean warnAboutTooManyVertexWeights = false;
List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size() == verticesAmount (one dvert per vertex in blender) List<Structure> dverts = pDvert.fetchData(blenderContext.getInputStream());// dverts.size()
// ==
// verticesAmount
// (one
// dvert
// per
// vertex
// in
// blender)
int vertexIndex = 0; int vertexIndex = 0;
// use tree map to sort weights from the lowest to the highest ones // use tree map to sort weights from the lowest to the highest ones
TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>(); TreeMap<Float, Integer> weightToIndexMap = new TreeMap<Float, Integer>();
for (Structure dvert : dverts) { for (Structure dvert : dverts) {
List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we fetch the referenced vertices here List<Integer> vertexIndices = vertexReferenceMap.get(Integer.valueOf(vertexIndex));// we
// fetch
// the
// referenced
// vertices
// here
if (vertexIndices != null) { 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"); Pointer pDW = (Pointer) dvert.getFieldValue("dw");
if (totweight > 0 && groupToBoneIndexMap != null) { if (totweight > 0 && groupToBoneIndexMap != null) {
weightToIndexMap.clear(); weightToIndexMap.clear();
@ -307,25 +341,32 @@ import com.jme3.util.BufferUtils;
for (Structure deformWeight : dw) { for (Structure deformWeight : dw) {
Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue()); Integer boneIndex = groupToBoneIndexMap.get(((Number) deformWeight.getFieldValue("def_nr")).intValue());
float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); 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 // boneIndex == null: it here means that we came
// if weight == 0 and weightIndex == 0 then ignore the weight (do not set weight = 0 as a first weight) // 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 (boneIndex != null && (weight > 0.0f || weightIndex > 0)) {
if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) { if (weightIndex < MAXIMUM_WEIGHTS_PER_VERTEX) {
if (weight == 0.0f) { if (weight == 0.0f) {
boneIndex = Integer.valueOf(0); boneIndex = Integer.valueOf(0);
} }
// we apply the weight to all referenced vertices // we apply the weight to all referenced
// vertices
for (Integer index : vertexIndices) { for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight); weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, weight);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue()); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + weightIndex, boneIndex.byteValue());
} }
weightToIndexMap.put(weight, weightIndex); weightToIndexMap.put(weight, weightIndex);
bonesGroups[0] = Math.max(bonesGroups[0], weightIndex + 1); 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; warnAboutTooManyVertexWeights = true;
Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry(); Entry<Float, Integer> lowestWeightAndIndex = weightToIndexMap.firstEntry();
if (lowestWeightAndIndex != null && lowestWeightAndIndex.getKey() < weight) { 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) { for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight); weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), weight);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue()); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + lowestWeightAndIndex.getValue(), boneIndex.byteValue());
@ -338,7 +379,8 @@ import com.jme3.util.BufferUtils;
} }
} }
} else { } 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) { for (Integer index : vertexIndices) {
weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f); weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 0.0f);
indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0);
@ -354,7 +396,8 @@ import com.jme3.util.BufferUtils;
} else { } else {
// always bind all vertices to 0-indexed bone // always bind all vertices to 0-indexed bone
// this bone makes the model look normally if vertices have no 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 // animation we can use the 0-indexed bone for this
for (List<Integer> vertexIndexList : vertexReferenceMap.values()) { for (List<Integer> vertexIndexList : vertexReferenceMap.values()) {
// we apply the weight to all referenced vertices // we apply the weight to all referenced vertices

@ -37,6 +37,7 @@ import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import com.jme3.asset.BlenderKey.FeaturesToLoad; import com.jme3.asset.BlenderKey.FeaturesToLoad;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix4f; import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
import com.jme3.math.Transform; 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. * A class that is used in object calculations.
*
* @author Marcin Roguski (Kaelthas) * @author Marcin Roguski (Kaelthas)
*/ */
public class ObjectHelper extends AbstractBlenderHelper { public class ObjectHelper extends AbstractBlenderHelper {
@ -83,8 +85,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
protected static final int OBJECT_TYPE_ARMATURE = 25; protected static final int OBJECT_TYPE_ARMATURE = 25;
/** /**
* This constructor parses the given blender version and stores the result. Some functionalities may differ in * This constructor parses the given blender version and stores the result.
* different blender versions. * Some functionalities may differ in different blender versions.
*
* @param blenderVersion * @param blenderVersion
* the version read from the blend file * the version read from the blend file
* @param fixUpAxis * @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 * @param objectStructure
* the object's structure * the object's structure
* @param blenderContext * @param blenderContext
@ -205,7 +210,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
} }
break; break;
case OBJECT_TYPE_ARMATURE: 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); Node armature = new Node(name);
armature.setLocalTransform(t); armature.setLocalTransform(t);
armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE); armature.setUserData(ArmatureHelper.ARMETURE_NODE_MARKER, Boolean.TRUE);
@ -223,9 +229,13 @@ public class ObjectHelper extends AbstractBlenderHelper {
} }
if (result != null) { 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); 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 // applying modifiers
LOGGER.log(Level.FINE, "Reading and applying object's modifiers."); LOGGER.log(Level.FINE, "Reading and applying object's modifiers.");
@ -242,7 +252,8 @@ public class ObjectHelper extends AbstractBlenderHelper {
// reading custom properties // reading custom properties
if (blenderContext.getBlenderKey().isLoadObjectProperties()) { if (blenderContext.getBlenderKey().isLoadObjectProperties()) {
Properties properties = this.loadProperties(objectStructure, blenderContext); 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) { if (result instanceof Spatial && properties != null && properties.getValue() != null) {
this.applyProperties(result, properties); this.applyProperties(result, properties);
} }
@ -252,7 +263,9 @@ public class ObjectHelper extends AbstractBlenderHelper {
} }
/** /**
* 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 * @param objectStructure
* the object's structure * the object's structure
* @return objects transformation relative to its parent * @return objects transformation relative to its parent
@ -277,16 +290,16 @@ public class ObjectHelper extends AbstractBlenderHelper {
Vector3f translation = localMatrix.toTranslationVector(); Vector3f translation = localMatrix.toTranslationVector();
Quaternion rotation = localMatrix.toRotationQuat(); 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) { if (fixUpAxis) {
float y = translation.y; float y = translation.y;
translation.y = translation.z; translation.y = translation.z;
translation.z = -y; translation.z = y == 0 ? 0 : -y;
y = rotation.getY(); y = rotation.getY();
float z = rotation.getZ(); 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; y = scale.y;
scale.y = scale.z; 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. * 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 * @param structure
* the structure with matrix data * the structure with matrix data
* @param matrixName * @param matrixName
@ -315,6 +330,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
/** /**
* This method returns the matrix of a given name for the given structure. * This method returns the matrix of a given name for the given structure.
* It takes up axis into consideration. * It takes up axis into consideration.
*
* @param structure * @param structure
* the structure with matrix data * the structure with matrix data
* @param matrixName * @param matrixName
@ -325,7 +341,11 @@ public class ObjectHelper extends AbstractBlenderHelper {
public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) { public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
Matrix4f result = new Matrix4f(); Matrix4f result = new Matrix4f();
DynamicArray<Number> obmat = (DynamicArray<Number>) structure.getFieldValue(matrixName); DynamicArray<Number> obmat = (DynamicArray<Number>) 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 i = 0; i < rowAndColumnSize; ++i) {
for (int j = 0; j < rowAndColumnSize; ++j) { for (int j = 0; j < rowAndColumnSize; ++j) {
result.set(i, j, obmat.get(j, i).floatValue()); result.set(i, j, obmat.get(j, i).floatValue());
@ -334,15 +354,15 @@ public class ObjectHelper extends AbstractBlenderHelper {
if (applyFixUpAxis && fixUpAxis) { if (applyFixUpAxis && fixUpAxis) {
Vector3f translation = result.toTranslationVector(); Vector3f translation = result.toTranslationVector();
Quaternion rotation = result.toRotationQuat(); Quaternion rotation = result.toRotationQuat();
Vector3f scale = this.getScale(result); Vector3f scale = result.toScaleVector();
float y = translation.y; float y = translation.y;
translation.y = translation.z; translation.y = translation.z;
translation.z = -y; translation.z = y == 0 ? 0 : -y;
y = rotation.getY(); y = rotation.getY();
float z = rotation.getZ(); 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; y = scale.y;
scale.y = scale.z; scale.y = scale.z;
@ -353,21 +373,17 @@ public class ObjectHelper extends AbstractBlenderHelper {
result.setRotationQuaternion(rotation); result.setRotationQuaternion(rotation);
result.setScale(scale); result.setScale(scale);
} }
return result;
}
/** for (int i = 0; i < 4; ++i) {
* This method returns the scale from the given matrix. for (int j = 0; j < 4; ++j) {
* float value = result.get(i, j);
* @param matrix if (Math.abs(value) <= FastMath.FLT_EPSILON) {
* the transformation matrix result.set(i, j, 0);
* @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); return result;
float scaleZ = (float) Math.sqrt(matrix.m02 * matrix.m02 + matrix.m12 * matrix.m12 + matrix.m22 * matrix.m22);
return new Vector3f(scaleX, scaleY, scaleZ);
} }
@Override @Override

Loading…
Cancel
Save