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