Bugfix: fixed an issue with proper bone orientation in 3D space (as in blender bones have different local coordinates system than other features); this fix caused more models to be loaded properly and made the code more simple

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@10840 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
experimental
Kae..pl 11 years ago
parent c0f0f0ca9e
commit 32b79324a9
  1. 9
      engine/src/blender/com/jme3/scene/plugins/blender/animations/ArmatureHelper.java
  2. 100
      engine/src/blender/com/jme3/scene/plugins/blender/animations/BoneContext.java
  3. 38
      engine/src/blender/com/jme3/scene/plugins/blender/animations/Ipo.java
  4. 13
      engine/src/blender/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java
  5. 82
      engine/src/blender/com/jme3/scene/plugins/blender/objects/ObjectHelper.java

@ -41,7 +41,6 @@ import java.util.logging.Logger;
import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.curves.BezierCurve;
@ -76,21 +75,25 @@ public class ArmatureHelper extends AbstractBlenderHelper {
/**
* This method builds the object's bones structure.
*
* @param armatureObjectOMA
* the OMa of the armature node
* @param boneStructure
* the structure containing the bones' data
* @param parent
* the parent bone
* @param result
* the list where the newly created bone will be added
* @param spatialOMA
* the OMA of the spatial that will own the skeleton
* @param blenderContext
* the blender context
* @throws BlenderFileException
* an exception is thrown when there is problem with the blender
* file
*/
public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Matrix4f objectToArmatureTransformation, BlenderContext blenderContext) throws BlenderFileException {
public void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List<Bone> result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException {
BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext);
bc.buildBone(result, objectToArmatureTransformation, blenderContext);
bc.buildBone(result, spatialOMA, blenderContext);
}
/**

@ -8,7 +8,10 @@ import com.jme3.animation.Skeleton;
import com.jme3.math.Matrix4f;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.BlenderContext.LoadedFeatureDataType;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
@ -19,27 +22,36 @@ import com.jme3.scene.plugins.blender.objects.ObjectHelper;
* @author Marcin Roguski (Kaelthas)
*/
public class BoneContext {
private BlenderContext blenderContext;
// the flags of the bone
private static final int CONNECTED_TO_PARENT = 0x10;
/**
* The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us).
* So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results.
*/
private static final Matrix4f BONE_ARMATURE_TRANSFORMATION_MATRIX = new Matrix4f(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1);
private BlenderContext blenderContext;
/** The OMA of the bone's armature object. */
private Long armatureObjectOMA;
private Long armatureObjectOMA;
/** The structure of the bone. */
private Structure boneStructure;
private Structure boneStructure;
/** Bone's name. */
private String boneName;
/** The bone's armature matrix. */
private Matrix4f armatureMatrix;
private String boneName;
/** The bone's flag. */
private int flag;
/** The bone's matrix in world space. */
private Matrix4f globalBoneMatrix;
/** The bone's matrix in the model space. */
private Matrix4f boneMatrixInModelSpace;
/** The parent context. */
private BoneContext parent;
private BoneContext parent;
/** 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). */
private Bone bone;
/** The bone's rest matrix. */
private Matrix4f restMatrix;
/** Bone's total inverse transformation. */
private Matrix4f inverseTotalTransformation;
private Bone bone;
/** The length of the bone. */
private float length;
private float length;
/**
* Constructor. Creates the basic set of bone's data.
@ -79,21 +91,26 @@ public class BoneContext {
this.boneStructure = boneStructure;
this.armatureObjectOMA = armatureObjectOMA;
boneName = boneStructure.getFieldValue("name").toString();
flag = ((Number) boneStructure.getFieldValue("flag")).intValue();
length = ((Number) boneStructure.getFieldValue("length")).floatValue();
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
armatureMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
// compute the bone's rest matrix
restMatrix = armatureMatrix.clone();
inverseTotalTransformation = restMatrix.invert();
if (parent != null) {
restMatrix = parent.inverseTotalTransformation.mult(restMatrix);
}
// first get the bone matrix in its armature space
globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis());
// then make sure it is rotated in a proper way to fit the jme bone transformation conventions
globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX);
Spatial armature = (Spatial) blenderContext.getLoadedFeature(armatureObjectOMA, LoadedFeatureDataType.LOADED_FEATURE);
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform());
// and now compute the final bone matrix in world space
globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix);
// create the children
List<Structure> childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(blenderContext);
for (Structure child : childbase) {
this.children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext));
}
blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this);
@ -104,31 +121,36 @@ public class BoneContext {
*
* @param bones
* a list of bones where the newly created bone will be added
* @param objectToArmatureMatrix
* object to armature transformation matrix
* @param skeletonOwnerOma
* the spatial of the object that will own the skeleton
* @param blenderContext
* the blender context
* @return newly created bone
*/
public Bone buildBone(List<Bone> bones, Matrix4f objectToArmatureMatrix, BlenderContext blenderContext) {
public Bone buildBone(List<Bone> bones, Long skeletonOwnerOma, BlenderContext blenderContext) {
Long boneOMA = boneStructure.getOldMemoryAddress();
bone = new Bone(boneName);
bones.add(bone);
blenderContext.addLoadedFeatures(boneOMA, boneName, boneStructure, bone);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
Vector3f poseLocation = restMatrix.toTranslationVector();
Quaternion rotation = restMatrix.toRotationQuat().normalizeLocal();
Vector3f scale = restMatrix.toScaleVector();
if (parent == null) {
Quaternion rotationQuaternion = objectToArmatureMatrix.toRotationQuat().normalizeLocal();
scale.multLocal(objectToArmatureMatrix.toScaleVector());
rotationQuaternion.multLocal(poseLocation.addLocal(objectToArmatureMatrix.toTranslationVector()));
rotation.multLocal(rotationQuaternion);
Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedFeatureDataType.LOADED_STRUCTURE);
Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "imat", blenderContext.getBlenderKey().isFixUpAxis());
if (objectHelper.isParent(skeletonOwnerOma, armatureObjectOMA)) {
boneMatrixInModelSpace = globalBoneMatrix.mult(invertedObjectOwnerGlobalMatrix);
} else {
boneMatrixInModelSpace = invertedObjectOwnerGlobalMatrix.mult(globalBoneMatrix);
}
Matrix4f boneLocalMatrix = parent == null ? boneMatrixInModelSpace : parent.boneMatrixInModelSpace.invert().multLocal(boneMatrixInModelSpace);
Vector3f poseLocation = parent == null || !this.is(CONNECTED_TO_PARENT) ? boneLocalMatrix.toTranslationVector() : new Vector3f(0, parent.length, 0);
Quaternion rotation = boneLocalMatrix.toRotationQuat().normalizeLocal();
Vector3f scale = boneLocalMatrix.toScaleVector();
bone.setBindTransforms(poseLocation, rotation, scale);
for (BoneContext child : children) {
bone.addChild(child.buildBone(bones, objectToArmatureMatrix, blenderContext));
bone.addChild(child.buildBone(bones, skeletonOwnerOma, blenderContext));
}
return bone;
@ -168,4 +190,14 @@ public class BoneContext {
public Skeleton getSkeleton() {
return blenderContext.getSkeleton(armatureObjectOMA);
}
/**
* Tells if the bone is of specified property defined by its flag.
* @param flagMask
* the mask of the flag (constants defined in this class)
* @return <b>true</b> if the bone IS of specified proeprty and <b>false</b> otherwise
*/
private boolean is(int flagMask) {
return (flag & flagMask) != 0;
}
}

@ -1,5 +1,6 @@
package com.jme3.scene.plugins.blender.animations;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.jme3.animation.BoneTrack;
@ -148,6 +149,11 @@ public class Ipo {
if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees
degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here
}
int yIndex = 1, zIndex = 2;
if(spatialTrack && fixUpAxis) {
yIndex = 2;
zIndex = 1;
}
// calculating track data
for (int frame = startFrame; frame <= stopFrame; ++frame) {
@ -157,36 +163,30 @@ public class Ipo {
for (int j = 0; j < bezierCurves.length; ++j) {
double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE);
switch (bezierCurves[j].getType()) {
// LOCATION
// LOCATION
case AC_LOC_X:
translation[0] = (float) value;
break;
case AC_LOC_Y:
if (fixUpAxis) {
translation[2] = value == 0.0f ? 0 : (float) -value;
} else {
translation[1] = (float) value;
}
translation[yIndex] = (float) value;
break;
case AC_LOC_Z:
translation[fixUpAxis ? 1 : 2] = (float) value;
translation[zIndex] = (float) value;
break;
// ROTATION (used with object animation)
// the value here is in degrees divided by 10 (so in
// example: 9 = PI/2)
case OB_ROT_X:
objectRotation[0] = (float) value * degreeToRadiansFactor;
break;
case OB_ROT_Y:
if (fixUpAxis) {
objectRotation[2] = value == 0.0f ? 0 : (float) -value * degreeToRadiansFactor;
objectRotation[yIndex] = value == 0.0f ? 0 : (float) -value * degreeToRadiansFactor;
} else {
objectRotation[1] = (float) value * degreeToRadiansFactor;
objectRotation[yIndex] = (float) value * degreeToRadiansFactor;
}
break;
case OB_ROT_Z:
objectRotation[fixUpAxis ? 1 : 2] = (float) value * degreeToRadiansFactor;
objectRotation[zIndex] = (float) value * degreeToRadiansFactor;
break;
// SIZE
@ -200,9 +200,7 @@ public class Ipo {
scale[fixUpAxis ? 1 : 2] = (float) value;
break;
// QUATERNION ROTATION (used with bone animation), dunno
// why but here we shouldn't check the
// spatialTrack flag value
// QUATERNION ROTATION (used with bone animation)
case AC_QUAT_W:
quaternionRotation[3] = (float) value;
break;
@ -210,17 +208,13 @@ public class Ipo {
quaternionRotation[0] = (float) value;
break;
case AC_QUAT_Y:
if (fixUpAxis) {
quaternionRotation[2] = value == 0.0f ? 0 : -(float) value;
} else {
quaternionRotation[1] = (float) value;
}
quaternionRotation[fixUpAxis ? 1 : 2] = (float) value;
break;
case AC_QUAT_Z:
quaternionRotation[fixUpAxis ? 1 : 2] = (float) value;
quaternionRotation[fixUpAxis ? 2 : 1] = (float) value;
break;
default:
LOGGER.warning("Unknown ipo curve type: " + bezierCurves[j].getType());
LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType());
}
}
translations[index] = localQuaternionRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2]));

@ -17,7 +17,6 @@ import com.jme3.animation.Bone;
import com.jme3.animation.BoneTrack;
import com.jme3.animation.Skeleton;
import com.jme3.animation.SkeletonControl;
import com.jme3.math.Matrix4f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
@ -35,7 +34,6 @@ import com.jme3.scene.plugins.blender.file.FileBlockHeader;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;
import com.jme3.scene.plugins.blender.meshes.MeshContext;
import com.jme3.scene.plugins.blender.objects.ObjectHelper;
import com.jme3.util.BufferUtils;
/**
@ -85,17 +83,10 @@ import com.jme3.util.BufferUtils;
// load skeleton
Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData(blenderContext.getInputStream()).get(0);
ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class);
boolean fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis();
Matrix4f armatureObjectMatrix = objectHelper.getMatrix(armatureObject, "obmat", fixUpAxis);
Matrix4f inverseMeshObjectMatrix = objectHelper.getMatrix(objectStructure, "imat", fixUpAxis);
Matrix4f objectToArmatureTransformation = armatureObjectMatrix.multLocal(inverseMeshObjectMatrix);
List<Structure> bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(blenderContext);
List<Bone> bonesList = new ArrayList<Bone>();
for (int i = 0; i < bonebase.size(); ++i) {
armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectToArmatureTransformation, blenderContext);
armatureHelper.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext);
}
bonesList.add(0, new Bone(""));
Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]);
@ -105,7 +96,7 @@ import com.jme3.util.BufferUtils;
this.meshStructure = meshStructure;
// read mesh indexes
this.meshOMA = meshStructure.getOldMemoryAddress();
meshOMA = meshStructure.getOldMemoryAddress();
// read animations
ArrayList<Animation> animations = new ArrayList<Animation>();

@ -44,6 +44,7 @@ import com.jme3.math.Transform;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
@ -67,9 +68,9 @@ import com.jme3.scene.plugins.blender.modifiers.ModifierHelper;
* @author Marcin Roguski (Kaelthas)
*/
public class ObjectHelper extends AbstractBlenderHelper {
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName());
public static final String OMA_MARKER = "oma";
public static final String OMA_MARKER = "oma";
/**
* This constructor parses the given blender version and stores the result.
@ -98,28 +99,28 @@ public class ObjectHelper extends AbstractBlenderHelper {
*/
public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException {
LOGGER.fine("Loading blender object.");
int type = ((Number) objectStructure.getFieldValue("type")).intValue();
ObjectType objectType = ObjectType.valueOf(type);
LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType);
if(objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) {
if (objectType == ObjectType.LAMP && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.LIGHTS)) {
LOGGER.fine("Lamps are not included in loading.");
return null;
}
if(objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) {
if (objectType == ObjectType.CAMERA && !blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.CAMERAS)) {
LOGGER.fine("Cameras are not included in loading.");
return null;
}
if(!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) {
if (!blenderContext.getBlenderKey().shouldLoad(FeaturesToLoad.OBJECTS)) {
LOGGER.fine("Objects are not included in loading.");
return null;
}
}
int lay = ((Number) objectStructure.getFieldValue("lay")).intValue();
if((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) {
if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) {
LOGGER.fine("The layer this object is located in is not included in loading.");
return null;
}
LOGGER.fine("Checking if the object has not been already loaded.");
Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedFeatureDataType.LOADED_FEATURE);
if (loadedResult != null) {
@ -201,29 +202,29 @@ public class ObjectHelper extends AbstractBlenderHelper {
if (result != null) {
blenderContext.addLoadedFeatures(objectStructure.getOldMemoryAddress(), name, objectStructure, result);
result.setLocalTransform(t);
result.setCullHint(visible ? CullHint.Always : CullHint.Inherit);
if (parent instanceof Node) {
((Node) parent).attachChild(result);
}
LOGGER.fine("Reading and applying object's modifiers.");
ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class);
Collection<Modifier> modifiers = modifierHelper.readModifiers(objectStructure, blenderContext);
for (Modifier modifier : modifiers) {
modifier.apply(result, blenderContext);
}
// I prefer do compute bounding box here than read it from the file
result.updateModelBound();
LOGGER.fine("Applying markers (those will be removed before the final result is released).");
blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress());
if(objectType == ObjectType.ARMATURE) {
if (objectType == ObjectType.ARMATURE) {
blenderContext.addMarker(ArmatureHelper.ARMATURE_NODE_MARKER, result, Boolean.TRUE);
}
LOGGER.fine("Loading constraints connected with this object.");
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class);
constraintHelper.loadConstraints(objectStructure, blenderContext);
@ -241,6 +242,31 @@ public class ObjectHelper extends AbstractBlenderHelper {
return result;
}
/**
* Checks if the first given OMA points to a parent of the second one.
* The parent need not to be the direct one. This method should be called when we are sure
* that both of the features are alred loaded because it does not check it.
* The OMA's should point to a spatials, otherwise the function will throw ClassCastException.
* @param supposedParentOMA
* the OMA of the node that we suppose might be a parent of the second one
* @param spatialOMA
* the OMA of the scene's node
* @return <b>true</b> if the first given OMA points to a parent of the second one and <b>false</b> otherwise
*/
public boolean isParent(Long supposedParentOMA, Long spatialOMA) {
Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedFeatureDataType.LOADED_FEATURE);
Spatial parent = spatial.getParent();
while (parent != null) {
if (parent.equals(supposedParent)) {
return true;
}
parent = parent.getParent();
}
return false;
}
/**
* This method calculates local transformation for the object. Parentage is
* taken under consideration.
@ -320,7 +346,7 @@ public class ObjectHelper extends AbstractBlenderHelper {
public Matrix4f getMatrix(Structure structure, String matrixName, boolean applyFixUpAxis) {
Matrix4f result = new Matrix4f();
DynamicArray<Number> obmat = (DynamicArray<Number>) structure.getFieldValue(matrixName);
//the matrix must be square
// the matrix must be square
int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize()));
for (int i = 0; i < rowAndColumnSize; ++i) {
for (int j = 0; j < rowAndColumnSize; ++j) {
@ -361,29 +387,19 @@ public class ObjectHelper extends AbstractBlenderHelper {
return result;
}
private static enum ObjectType {
EMPTY(0),
MESH(1),
CURVE(2),
SURF(3),
TEXT(4),
METABALL(5),
LAMP(10),
CAMERA(11),
WAVE(21),
LATTICE(22),
ARMATURE(25);
EMPTY(0), MESH(1), CURVE(2), SURF(3), TEXT(4), METABALL(5), LAMP(10), CAMERA(11), WAVE(21), LATTICE(22), ARMATURE(25);
private int blenderTypeValue;
private ObjectType(int blenderTypeValue) {
this.blenderTypeValue = blenderTypeValue;
}
public static ObjectType valueOf(int blenderTypeValue) throws BlenderFileException {
for(ObjectType type : ObjectType.values()) {
if(type.blenderTypeValue == blenderTypeValue) {
for (ObjectType type : ObjectType.values()) {
if (type.blenderTypeValue == blenderTypeValue) {
return type;
}
}

Loading…
Cancel
Save