Bugfix: improvements to Inverse Kinematics constraint.

experimental
jmekaelthas 10 years ago
parent 9cd1a514a6
commit 9efa32a250
  1. 2
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java
  2. 158
      jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java

@ -295,7 +295,7 @@ public class SimulationNode {
// track contains differences between the frame position and bind positions of bones/spatials // track contains differences between the frame position and bind positions of bones/spatials
Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation()); Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation());
Quaternion boneRotationDifference = bone.getLocalRotation().mult(startTransform.getRotation().inverse()).normalizeLocal(); Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal();
Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale()); Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale());
trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference));

@ -1,33 +1,42 @@
package com.jme3.scene.plugins.blender.constraints.definitions; package com.jme3.scene.plugins.blender.constraints.definitions;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import com.jme3.animation.Bone; import com.jme3.animation.Bone;
import com.jme3.math.FastMath;
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.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.BoneContext;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper;
import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space;
import com.jme3.scene.plugins.blender.file.Structure; import com.jme3.scene.plugins.blender.file.Structure;
/**
* The Inverse Kinematics constraint.
*
* @author Wesley Shillingford (wezrule)
* @author Marcin Roguski (Kaelthas)
*/
public class ConstraintDefinitionIK extends ConstraintDefinition { public class ConstraintDefinitionIK extends ConstraintDefinition {
private static final float MIN_DISTANCE = 0.0001f;
private static final int FLAG_POSITION = 0x20; private static final int FLAG_POSITION = 0x20;
/** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */
private int bonesAffected; private int bonesAffected;
private float chainLength; /** The total length of the bone chain. Useful for optimisation of computations speed in some cases. */
private BoneContext[] bones; private float chainLength;
private boolean needToCompute = true; /** Tells if there is anything to compute at all. */
private boolean needToCompute = true;
/** The amount of iterations of the algorithm. */
private int iterations;
public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) {
super(constraintData, ownerOMA, blenderContext); super(constraintData, ownerOMA, blenderContext);
bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue(); bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue();
iterations = ((Number) constraintData.getFieldValue("iterations")).intValue();
if ((flag & FLAG_POSITION) == 0) { if ((flag & FLAG_POSITION) == 0) {
needToCompute = false; needToCompute = false;
@ -37,55 +46,76 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
alteredOmas = new HashSet<Long>(); alteredOmas = new HashSet<Long>();
} }
} }
@Override @Override
public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) {
if (needToCompute && influence != 0) { if(influence == 0 || !needToCompute) {
ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); return ;//no need to do anything
BoneContext[] boneContexts = this.getBones(); }
float b = chainLength; Quaternion q = new Quaternion();
Quaternion boneWorldRotation = new Quaternion(); Vector3f t = targetTransform.getTranslation();
List<BoneContext> bones = this.loadBones();
for (int i = 0; i < boneContexts.length; ++i) { float distanceFromTarget = Float.MAX_VALUE;
Bone bone = boneContexts[i].getBone();
int iterations = this.iterations;
bone.updateModelTransforms(); if (bones.size() == 1) {
Transform boneWorldTransform = constraintHelper.getTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD); iterations = 1;// if only one bone is in the chain then only one iteration that will properly rotate it will be needed
} else {
Vector3f head = boneWorldTransform.getTranslation(); // if the target cannot be rached by the bones' chain then the chain will become straight and point towards the target
Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(boneContexts[i].getLength()))); // in this case only one iteration will be needed, computed from the root to top bone
BoneContext rootBone = bones.get(bones.size() - 1);
Vector3f vectorA = tail.subtract(head); Transform rootBoneTransform = constraintHelper.getTransform(rootBone.getArmatureObjectOMA(), rootBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
float a = vectorA.length(); if (t.distance(rootBoneTransform.getTranslation()) >= chainLength) {
vectorA.normalizeLocal(); Collections.reverse(bones);
Vector3f vectorC = targetTransform.getTranslation().subtract(head); for (BoneContext boneContext : bones) {
float c = vectorC.length(); Bone bone = boneContext.getBone();
vectorC.normalizeLocal(); Transform boneTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD);
b -= a; Vector3f e = boneTransform.getTranslation().add(boneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(boneContext.getLength()));// effector
float theta = 0; Vector3f j = boneTransform.getTranslation(); // current join position
if (c >= a + b) { Vector3f currentDir = e.subtractLocal(j).normalizeLocal();
theta = vectorA.angleBetween(vectorC); Vector3f target = t.subtract(j).normalizeLocal();
} else if (c <= FastMath.abs(a - b) && i < boneContexts.length - 1) { float angle = currentDir.angleBetween(target);
theta = vectorA.angleBetween(vectorC) - FastMath.HALF_PI; if (angle != 0) {
} else { Vector3f cross = currentDir.crossLocal(target).normalizeLocal();
theta = vectorA.angleBetween(vectorC) - FastMath.acos(-(b * b - a * a - c * c) / (2 * a * c)); q.fromAngleAxis(angle, cross);
}
boneTransform.getRotation().set(q.multLocal(boneTransform.getRotation()));
theta *= influence; constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneTransform);
}
if (theta != 0) {
Vector3f vectorR = vectorA.cross(vectorC);
boneWorldRotation.fromAngleAxis(theta, vectorR);
boneWorldTransform.getRotation().multLocal(boneWorldRotation);
constraintHelper.applyTransform(boneContexts[i].getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform);
} }
bone.updateModelTransforms(); iterations = 0;
alteredOmas.add(boneContexts[i].getBoneOma()); }
}
BoneContext topBone = bones.get(0);
for (int i = 0; i < iterations && distanceFromTarget > MIN_DISTANCE; ++i) {
for (BoneContext boneContext : bones) {
Bone bone = boneContext.getBone();
Transform topBoneTransform = constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
Transform boneWorldTransform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD);
Vector3f e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(topBone.getLength()));// effector
Vector3f j = boneWorldTransform.getTranslation(); // current join position
Vector3f currentDir = e.subtractLocal(j).normalizeLocal();
Vector3f target = t.subtract(j).normalizeLocal();
float angle = currentDir.angleBetween(target);
if (angle != 0) {
Vector3f cross = currentDir.crossLocal(target).normalizeLocal();
q.fromAngleAxis(angle, cross);
boneWorldTransform.getRotation().set(q.multLocal(boneWorldTransform.getRotation()));
constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), bone.getName(), Space.CONSTRAINT_SPACE_WORLD, boneWorldTransform);
}
} }
Transform topBoneTransform = constraintHelper.getTransform(topBone.getArmatureObjectOMA(), topBone.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD);
Vector3f e = topBoneTransform.getTranslation().addLocal(topBoneTransform.getRotation().mult(Vector3f.UNIT_Y).multLocal(topBone.getLength()));// effector
distanceFromTarget = e.distance(t);
} }
} }
@ -97,20 +127,18 @@ public class ConstraintDefinitionIK extends ConstraintDefinition {
/** /**
* @return the bone contexts of all bones that will be used in this constraint computations * @return the bone contexts of all bones that will be used in this constraint computations
*/ */
private BoneContext[] getBones() { private List<BoneContext> loadBones() {
if (bones == null) { List<BoneContext> bones = new ArrayList<BoneContext>();
List<BoneContext> bones = new ArrayList<BoneContext>(); Bone bone = (Bone) this.getOwner();
Bone bone = (Bone) this.getOwner(); while (bone != null) {
while (bone != null) { BoneContext boneContext = blenderContext.getBoneContext(bone);
BoneContext boneContext = blenderContext.getBoneContext(bone); chainLength += boneContext.getLength();
bones.add(0, boneContext); bones.add(boneContext);
chainLength += boneContext.getLength(); alteredOmas.add(boneContext.getBoneOma());
if (bonesAffected != 0 && bones.size() >= bonesAffected) { if (bonesAffected != 0 && bones.size() >= bonesAffected) {
break; break;
}
bone = bone.getParent();
} }
this.bones = bones.toArray(new BoneContext[bones.size()]); bone = bone.getParent();
} }
return bones; return bones;
} }

Loading…
Cancel
Save