Bone
describes a bone in the bone-weight skeletal animation
* system. A bone contains a name and an index, as well as relevant
* transformation data.
*
* @author Kirill Vainer
*/
public final class Bone implements Savable {
private String name;
private Bone parent;
private final ArrayList* Only copies the name and bind pose from the original. *
* WARNING: Local bind pose and world inverse bind pose transforms shallow * copied. Modifying that data on the original bone will cause it to * be recomputed on any cloned bones. *
* The rest of the data is NOT copied, as it will be
* generated automatically when the bone is animated.
*
* @param source The bone from which to copy the data.
*/
Bone(Bone source) {
this.name = source.name;
userControl = source.userControl;
initialPos = source.initialPos;
initialRot = source.initialRot;
initialScale = source.initialScale;
worldBindInversePos = source.worldBindInversePos;
worldBindInverseRot = source.worldBindInverseRot;
worldBindInverseScale = source.worldBindInverseScale;
// parent and children will be assigned manually..
}
/**
* Serialization only. Do not use.
*/
public Bone() {
}
/**
* Returns the name of the bone, set in the constructor.
*
* @return The name of the bone, set in the constructor.
*/
public String getName() {
return name;
}
/**
* Returns parent bone of this bone, or null if it is a root bone.
* @return The parent bone of this bone, or null if it is a root bone.
*/
public Bone getParent() {
return parent;
}
/**
* Returns all the children bones of this bone.
*
* @return All the children bones of this bone.
*/
public ArrayList
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the inverse world bind pose position.
*/
public Vector3f getWorldBindInversePosition() {
return worldBindInversePos;
}
/**
* Returns the inverse world bind pose rotation.
*
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the inverse world bind pose rotation.
*/
public Quaternion getWorldBindInverseRotation() {
return worldBindInverseRot;
}
/**
* Returns the inverse world bind pose scale.
*
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the inverse world bind pose scale.
*/
public Vector3f getWorldBindInverseScale() {
return worldBindInverseScale;
}
/**
* Returns the world bind pose position.
*
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the world bind pose position.
*/
public Vector3f getWorldBindPosition() {
return initialPos;
}
/**
* Returns the world bind pose rotation.
*
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the world bind pose rotation.
*/
public Quaternion getWorldBindRotation() {
return initialRot;
}
/**
* Returns the world bind pose scale.
*
* The bind pose transform of the bone is its "default"
* transform with no animation applied.
*
* @return the world bind pose scale.
*/
public Vector3f getWorldBindScale() {
return initialScale;
}
/**
* If enabled, user can control bone transform with setUserTransforms.
* Animation transforms are not applied to this bone when enabled.
*/
public void setUserControl(boolean enable) {
userControl = enable;
}
/**
* Add a new child to this bone. Shouldn't be used by user code.
* Can corrupt skeleton.
*
* @param bone The bone to add
*/
public void addChild(Bone bone) {
children.add(bone);
bone.parent = this;
}
/**
* Updates the world transforms for this bone, and, possibly the attach node
* if not null.
*
* The world transform of this bone is computed by combining the parent's
* world transform with this bones' local transform.
*/
public final void updateWorldVectors() {
if (parent != null) {
//rotation
parent.worldRot.mult(localRot, worldRot);
//scale
//For scale parent scale is not taken into account!
// worldScale.set(localScale);
parent.worldScale.mult(localScale, worldScale);
//translation
//scale and rotation of parent affect bone position
parent.worldRot.mult(localPos, worldPos);
worldPos.multLocal(parent.worldScale);
worldPos.addLocal(parent.worldPos);
} else {
worldRot.set(localRot);
worldPos.set(localPos);
worldScale.set(localScale);
}
if (attachNode != null) {
attachNode.setLocalTranslation(worldPos);
attachNode.setLocalRotation(worldRot);
attachNode.setLocalScale(worldScale);
}
}
/**
* Updates world transforms for this bone and it's children.
*/
final void update() {
this.updateWorldVectors();
for (int i = children.size() - 1; i >= 0; i--) {
children.get(i).update();
}
}
/**
* Saves the current bone state as its binding pose, including its children.
*/
void setBindingPose() {
initialPos.set(localPos);
initialRot.set(localRot);
initialScale.set(localScale);
if (worldBindInversePos == null) {
worldBindInversePos = new Vector3f();
worldBindInverseRot = new Quaternion();
worldBindInverseScale = new Vector3f();
}
// Save inverse derived position/scale/orientation, used for calculate offset transform later
worldBindInversePos.set(worldPos);
worldBindInversePos.negateLocal();
worldBindInverseRot.set(worldRot);
worldBindInverseRot.inverseLocal();
worldBindInverseScale.set(Vector3f.UNIT_XYZ);
worldBindInverseScale.divideLocal(worldScale);
for (Bone b : children) {
b.setBindingPose();
}
}
/**
* Reset the bone and it's children to bind pose.
*/
final void reset() {
if (!userControl) {
localPos.set(initialPos);
localRot.set(initialRot);
localScale.set(initialScale);
}
for (int i = children.size() - 1; i >= 0; i--) {
children.get(i).reset();
}
}
/**
* Stores the skinning transform in the specified Matrix4f.
* The skinning transform applies the animation of the bone to a vertex.
*
* This assumes that the world transforms for the entire bone hierarchy
* have already been computed, otherwise this method will return undefined
* results.
*
* @param outTransform
*/
void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) {
// Computing scale
Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3);
// Computing rotation
Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1);
// Computing translation
// Translation depend on rotation and scale
Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2);
// Populating the matrix
outTransform.loadIdentity();
outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4));
}
/**
* Sets user transform.
*/
public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
if (!userControl) {
throw new IllegalStateException("User control must be on bone to allow user transforms");
}
localPos.set(initialPos);
localRot.set(initialRot);
localScale.set(initialScale);
localPos.addLocal(translation);
localRot = localRot.mult(rotation);
localScale.multLocal(scale);
}
/**
* Must update all bones in skeleton for this to work.
* @param translation
* @param rotation
*/
public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
if (!userControl) {
throw new IllegalStateException("User control must be on bone to allow user transforms");
}
// TODO: add scale here ???
worldPos.set(translation);
worldRot.set(rotation);
//if there is an attached Node we need to set it's local transforms too.
if(attachNode != null){
attachNode.setLocalTranslation(translation);
attachNode.setLocalRotation(rotation);
}
}
/**
* Returns the local transform of this bone combined with the given position and rotation
* @param position a position
* @param rotation a rotation
*/
public Transform getCombinedTransform(Vector3f position, Quaternion rotation) {
rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position);
tmpTransform.setRotation(rotation).getRotation().multLocal(localRot);
return tmpTransform;
}
/**
* Returns the attachment node.
* Attach models and effects to this node to make
* them follow this bone's motions.
*/
Node getAttachmentsNode() {
if (attachNode == null) {
attachNode = new Node(name + "_attachnode");
attachNode.setUserData("AttachedBone", this);
}
return attachNode;
}
/**
* Used internally after model cloning.
* @param attachNode
*/
void setAttachmentsNode(Node attachNode) {
this.attachNode = attachNode;
}
/**
* Sets the local animation transform of this bone.
* Bone is assumed to be in bind pose when this is called.
*/
void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
if (userControl) {
return;
}
// localPos.addLocal(translation);
// localRot.multLocal(rotation);
//localRot = localRot.mult(rotation);
localPos.set(initialPos).addLocal(translation);
localRot.set(initialRot).multLocal(rotation);
if (scale != null) {
localScale.set(initialScale).multLocal(scale);
}
}
void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
if (userControl) {
return;
}
TempVars vars = TempVars.get();
// assert vars.lock();
Vector3f tmpV = vars.vect1;
Vector3f tmpV2 = vars.vect2;
Quaternion tmpQ = vars.quat1;
//location
tmpV.set(initialPos).addLocal(translation);
localPos.interpolate(tmpV, weight);
//rotation
tmpQ.set(initialRot).multLocal(rotation);
localRot.nlerp(tmpQ, weight);
//scale
if (scale != null) {
tmpV2.set(initialScale).multLocal(scale);
localScale.interpolate(tmpV2, weight);
}
vars.release();
}
/**
* Sets local bind transform for bone.
* Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them.
*/
public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
initialPos.set(translation);
initialRot.set(rotation);
//ogre.xml can have null scale values breaking this if the check is removed
if (scale != null) {
initialScale.set(scale);
}
localPos.set(translation);
localRot.set(rotation);
if (scale != null) {
localScale.set(scale);
}
}
private String toString(int depth) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < depth; i++) {
sb.append('-');
}
sb.append(name).append(" bone\n");
for (Bone child : children) {
sb.append(child.toString(depth + 1));
}
return sb.toString();
}
@Override
public String toString() {
return this.toString(0);
}
@Override
@SuppressWarnings("unchecked")
public void read(JmeImporter im) throws IOException {
InputCapsule input = im.getCapsule(this);
name = input.readString("name", null);
initialPos = (Vector3f) input.readSavable("initialPos", null);
initialRot = (Quaternion) input.readSavable("initialRot", null);
initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
attachNode = (Node) input.readSavable("attachNode", null);
localPos.set(initialPos);
localRot.set(initialRot);
ArrayList