From 34cdd21488b42951b69613109903b3555c5979dc Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Sun, 26 Oct 2014 11:49:34 +0100 Subject: [PATCH] Feature: improved IK algorithm by taking axis locks for bones into consideration. Also rotation limits, stretch factor and stiffness are also loaded from the blend file to be used later. --- .../blender/animations/BoneContext.java | 124 +++++++++++++++++- .../definitions/ConstraintDefinitionIK.java | 22 +++- 2 files changed, 144 insertions(+), 2 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java index dbad86cb8..f21b36f00 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java @@ -13,6 +13,8 @@ import com.jme3.scene.plugins.blender.BlenderContext; import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; import com.jme3.scene.plugins.blender.file.BlenderFileException; +import com.jme3.scene.plugins.blender.file.DynamicArray; +import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.objects.ObjectHelper; @@ -33,6 +35,13 @@ public class BoneContext { */ public 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 static final int IKFLAG_LOCK_X = 0x01; + private static final int IKFLAG_LOCK_Y = 0x02; + private static final int IKFLAG_LOCK_Z = 0x04; + private static final int IKFLAG_LIMIT_X = 0x08; + private static final int IKFLAG_LIMIT_Y = 0x10; + private static final int IKFLAG_LIMIT_Z = 0x20; + private BlenderContext blenderContext; /** The OMA of the bone's armature object. */ private Long armatureObjectOMA; @@ -58,6 +67,21 @@ public class BoneContext { private float length; /** The bone's deform envelope. */ private BoneEnvelope boneEnvelope; + + // The below data is used only for IK constraint computations. + + /** The bone's stretch value. */ + private float ikStretch; + /** Bone's rotation minimum values. */ + private Vector3f limitMin; + /** Bone's rotation maximum values. */ + private Vector3f limitMax; + /** The bone's stiffness values (how much it rotates during IK computations. */ + private Vector3f stiffness; + /** Values that indicate if any axis' rotation should be limited by some angle. */ + private boolean[] limits; + /** Values that indicate if any axis' rotation should be disabled during IK computations. */ + private boolean[] locks; /** * Constructor. Creates the basic set of bone's data. @@ -91,6 +115,7 @@ public class BoneContext { * an exception is thrown when problem with blender data reading * occurs */ + @SuppressWarnings("unchecked") private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException { this.parent = parent; this.blenderContext = blenderContext; @@ -108,7 +133,8 @@ public class BoneContext { globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); } - Spatial armature = (Spatial) objectHelper.toObject(blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext), blenderContext); + Structure armatureStructure = blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext); + Spatial armature = (Spatial) objectHelper.toObject(armatureStructure, blenderContext); ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform(), new Matrix4f()); @@ -120,6 +146,32 @@ public class BoneContext { boneEnvelope = new BoneEnvelope(boneStructure, armatureWorldMatrix, blenderContext.getBlenderKey().isFixUpAxis()); } + // load bone's pose channel data + Pointer pPose = (Pointer) armatureStructure.getFieldValue("pose"); + if (pPose != null && pPose.isNotNull()) { + List poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase(); + for (Structure poseChannel : poseChannels) { + Long boneOMA = ((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress(); + if (boneOMA.equals(this.boneStructure.getOldMemoryAddress())) { + ikStretch = ((Number) poseChannel.getFieldValue("ikstretch")).floatValue(); + DynamicArray limitMin = (DynamicArray) poseChannel.getFieldValue("limitmin"); + this.limitMin = new Vector3f(limitMin.get(0).floatValue(), limitMin.get(1).floatValue(), limitMin.get(2).floatValue()); + + DynamicArray limitMax = (DynamicArray) poseChannel.getFieldValue("limitmax"); + this.limitMax = new Vector3f(limitMax.get(0).floatValue(), limitMax.get(1).floatValue(), limitMax.get(2).floatValue()); + + DynamicArray stiffness = (DynamicArray) poseChannel.getFieldValue("stiffness"); + this.stiffness = new Vector3f(stiffness.get(0).floatValue(), stiffness.get(1).floatValue(), stiffness.get(2).floatValue()); + + int ikFlag = ((Number) poseChannel.getFieldValue("ikflag")).intValue(); + locks = new boolean[] { (ikFlag & IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LOCK_Z) != 0 }; + // limits are enabled when locks are disabled, so we ween to take that into account here + limits = new boolean[] { (ikFlag & IKFLAG_LIMIT_X & ~IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LIMIT_Y & ~IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LIMIT_Z & ~IKFLAG_LOCK_Z) != 0 }; + break;// we have found what we need, no need to search further + } + } + } + // create the children List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(); for (Structure child : childbase) { @@ -234,6 +286,76 @@ public class BoneContext { return boneEnvelope; } + /** + * @return bone's stretch factor + */ + public float getIkStretch() { + return ikStretch; + } + + /** + * @return indicates if the X rotation should be limited + */ + public boolean isLimitX() { + return limits != null ? limits[0] : false; + } + + /** + * @return indicates if the Y rotation should be limited + */ + public boolean isLimitY() { + return limits != null ? limits[1] : false; + } + + /** + * @return indicates if the Z rotation should be limited + */ + public boolean isLimitZ() { + return limits != null ? limits[2] : false; + } + + /** + * @return indicates if the X rotation should be disabled + */ + public boolean isLockX() { + return locks != null ? locks[0] : false; + } + + /** + * @return indicates if the Y rotation should be disabled + */ + public boolean isLockY() { + return locks != null ? locks[1] : false; + } + + /** + * @return indicates if the Z rotation should be disabled + */ + public boolean isLockZ() { + return locks != null ? locks[2] : false; + } + + /** + * @return the minimum values in rotation limitation (if limitation is enabled for specific axis). + */ + public Vector3f getLimitMin() { + return limitMin; + } + + /** + * @return the maximum values in rotation limitation (if limitation is enabled for specific axis). + */ + public Vector3f getLimitMax() { + return limitMax; + } + + /** + * @return the stiffness of the bone + */ + public Vector3f getStiffness() { + return stiffness; + } + /** * Tells if the bone is of specified property defined by its flag. * @param flagMask diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java index d3ebc4d18..e848bea16 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java @@ -81,6 +81,15 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { if (angle != 0) { Vector3f cross = currentDir.crossLocal(target).normalizeLocal(); q.fromAngleAxis(angle, cross); + if(boneContext.isLockX()) { + q.set(0, q.getY(), q.getZ(), q.getW()); + } + if(boneContext.isLockY()) { + q.set(q.getX(), 0, q.getZ(), q.getW()); + } + if(boneContext.isLockZ()) { + q.set(q.getX(), q.getY(), 0, q.getW()); + } boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation())); constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform); @@ -97,7 +106,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { Bone bone = boneContext.getBone(); Transform topBoneTransform = constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); Transform boneWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD); - + Vector3f e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(topBone.getLength()));// effector Vector3f j = boneWorldTransform.getTranslation(); // current join position @@ -108,6 +117,16 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { Vector3f cross = currentDir.crossLocal(target).normalizeLocal(); q.fromAngleAxis(angle, cross); + if(boneContext.isLockX()) { + q.set(0, q.getY(), q.getZ(), q.getW()); + } + if(boneContext.isLockY()) { + q.set(q.getX(), 0, q.getZ(), q.getW()); + } + if(boneContext.isLockZ()) { + q.set(q.getX(), q.getY(), 0, q.getW()); + } + boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation())); constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform); } @@ -130,6 +149,7 @@ public class ConstraintDefinitionIK extends ConstraintDefinition { private List loadBones() { List bones = new ArrayList(); Bone bone = (Bone) this.getOwner(); + chainLength = 0; while (bone != null) { BoneContext boneContext = blenderContext.getBoneContext(bone); chainLength += boneContext.getLength();