com.jme3.animation.Bone Maven / Gradle / Ivy
Show all versions of jme3-core Show documentation
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.animation;
import com.jme3.export.*;
import com.jme3.math.*;
import com.jme3.scene.Node;
import com.jme3.util.TempVars;
import java.io.IOException;
import java.util.ArrayList;
/**
* 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.
*
* A bone has 3 sets of transforms :
* 1. The bind transforms, that are the transforms of the bone when the skeleton
* is in its rest pose (also called bind pose or T pose in the literature).
* The bind transforms are expressed in Local space meaning relatively to the
* parent bone.
*
* 2. The Local transforms, that are the transforms of the bone once animation
* or user transforms has been applied to the bind pose. The local transforms are
* expressed in Local space meaning relatively to the parent bone.
*
* 3. The Model transforms, that are the transforms of the bone relatives to the
* rootBone of the skeleton. Those transforms are what is needed to apply skinning
* to the mesh the skeleton controls.
* Note that there can be several rootBones in a skeleton. The one considered for
* these transforms is the one that is an ancestor of this bone.
*
* @author Kirill Vainer
* @author Rémy Bouquet
*/
public final class Bone implements Savable {
// Version #2: Changed naming of transforms as they were misleading
public static final int SAVABLE_VERSION = 2;
private String name;
private Bone parent;
private final ArrayList children = new ArrayList();
/**
* If enabled, user can control bone transform with setUserTransforms.
* Animation transforms are not applied to this bone when enabled.
*/
private boolean userControl = false;
/**
* The attachment node.
*/
private Node attachNode;
/**
* Bind transform is the local bind transform of this bone. (local space)
*/
private Vector3f bindPos;
private Quaternion bindRot;
private Vector3f bindScale;
/**
* The inverse bind transforms of this bone expressed in model space
*/
private Vector3f modelBindInversePos;
private Quaternion modelBindInverseRot;
private Vector3f modelBindInverseScale;
/**
* The local animated or user transform combined with the local bind transform
*/
private Vector3f localPos = new Vector3f();
private Quaternion localRot = new Quaternion();
private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f);
/**
* The model transforms of this bone
*/
private Vector3f modelPos = new Vector3f();
private Quaternion modelRot = new Quaternion();
private Vector3f modelScale = new Vector3f();
// Used for getCombinedTransform
private Transform tmpTransform;
/**
* Used to handle blending from one animation to another.
* See {@link #blendAnimTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f, float)}
* on how this variable is used.
*/
private transient float currentWeightSum = -1;
/**
* Creates a new bone with the given name.
*
* @param name Name to give to this bone
*/
public Bone(String name) {
if (name == null)
throw new IllegalArgumentException("Name cannot be null");
this.name = name;
bindPos = new Vector3f();
bindRot = new Quaternion();
bindScale = new Vector3f(1, 1, 1);
modelBindInversePos = new Vector3f();
modelBindInverseRot = new Quaternion();
modelBindInverseScale = new Vector3f();
}
/**
* Special-purpose copy constructor.
*
* Only copies the name, user control state and bind pose transforms from the original.
*
* 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;
bindPos = source.bindPos.clone();
bindRot = source.bindRot.clone();
bindScale = source.bindScale.clone();
modelBindInversePos = source.modelBindInversePos.clone();
modelBindInverseRot = source.modelBindInverseRot.clone();
modelBindInverseScale = source.modelBindInverseScale.clone();
// 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 getChildren() {
return children;
}
/**
* Returns the local position of the bone, relative to the parent bone.
*
* @return The local position of the bone, relative to the parent bone.
*/
public Vector3f getLocalPosition() {
return localPos;
}
/**
* Returns the local rotation of the bone, relative to the parent bone.
*
* @return The local rotation of the bone, relative to the parent bone.
*/
public Quaternion getLocalRotation() {
return localRot;
}
/**
* Returns the local scale of the bone, relative to the parent bone.
*
* @return The local scale of the bone, relative to the parent bone.
*/
public Vector3f getLocalScale() {
return localScale;
}
/**
* Returns the position of the bone in model space.
*
* @return The position of the bone in model space.
*/
public Vector3f getModelSpacePosition() {
return modelPos;
}
/**
* Returns the rotation of the bone in model space.
*
* @return The rotation of the bone in model space.
*/
public Quaternion getModelSpaceRotation() {
return modelRot;
}
/**
* Returns the scale of the bone in model space.
*
* @return The scale of the bone in model space.
*/
public Vector3f getModelSpaceScale() {
return modelScale;
}
/**
* @deprecated use {@link #getModelBindInversePosition()}
*/
@Deprecated
public Vector3f getWorldBindInversePosition() {
return modelBindInversePos;
}
/**
* Returns the inverse Bind position of this bone expressed in model space.
*
* The inverse bind pose transform of the bone in model space is its "default"
* transform with no animation applied.
*
* @return the inverse bind position of this bone expressed in model space.
*/
public Vector3f getModelBindInversePosition() {
return modelBindInversePos;
}
/**
* @deprecated use {@link #getModelBindInverseRotation()}
*/
@Deprecated
public Quaternion getWorldBindInverseRotation() {
return modelBindInverseRot;
}
/**
* Returns the inverse bind rotation of this bone expressed in model space.
*
* The inverse bind pose transform of the bone in model space is its "default"
* transform with no animation applied.
*
* @return the inverse bind rotation of this bone expressed in model space.
*/
public Quaternion getModelBindInverseRotation() {
return modelBindInverseRot;
}
/**
* @deprecated use {@link #getModelBindInverseScale()}
*/
@Deprecated
public Vector3f getWorldBindInverseScale() {
return modelBindInverseScale;
}
/**
* Returns the inverse world bind pose scale.
*
* The inverse bind pose transform of the bone in model space is its "default"
* transform with no animation applied.
*
* @return the inverse world bind pose scale.
*/
public Vector3f getModelBindInverseScale() {
return modelBindInverseScale;
}
public Transform getModelBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(modelBindInversePos);
t.setRotation(modelBindInverseRot);
if (modelBindInverseScale != null) {
t.setScale(modelBindInverseScale);
}
return t;
}
public Transform getBindInverseTransform() {
Transform t = new Transform();
t.setTranslation(bindPos);
t.setRotation(bindRot);
if (bindScale != null) {
t.setScale(bindScale);
}
return t.invert();
}
/**
* @deprecated use {@link #getBindPosition()}
*/
@Deprecated
public Vector3f getWorldBindPosition() {
return bindPos;
}
/**
* Returns the bind position expressed in local space (relative to the parent bone).
*
* The bind pose transform of the bone in local space is its "default"
* transform with no animation applied.
*
* @return the bind position in local space.
*/
public Vector3f getBindPosition() {
return bindPos;
}
/**
* @deprecated use {@link #getBindRotation() }
*/
@Deprecated
public Quaternion getWorldBindRotation() {
return bindRot;
}
/**
* Returns the bind rotation expressed in local space (relative to the parent bone).
*
* The bind pose transform of the bone in local space is its "default"
* transform with no animation applied.
*
* @return the bind rotation in local space.
*/
public Quaternion getBindRotation() {
return bindRot;
}
/**
* @deprecated use {@link #getBindScale() }
*/
@Deprecated
public Vector3f getWorldBindScale() {
return bindScale;
}
/**
* Returns the bind scale expressed in local space (relative to the parent bone).
*
* The bind pose transform of the bone in local space is its "default"
* transform with no animation applied.
*
* @return the bind scale in local space.
*/
public Vector3f getBindScale() {
return bindScale;
}
/**
* 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;
}
/**
*
* @deprecated use {@link #updateModelTransforms() }
*/
@Deprecated
public final void updateWorldVectors(){
updateModelTransforms();
}
/**
* Updates the model transforms for this bone, and, possibly the attach node
* if not null.
*
* The model transform of this bone is computed by combining the parent's
* model transform with this bones' local transform.
*/
public final void updateModelTransforms() {
if (currentWeightSum == 1f) {
currentWeightSum = -1;
} else if (currentWeightSum != -1f) {
// Apply the weight to the local transform
if (currentWeightSum == 0) {
localRot.set(bindRot);
localPos.set(bindPos);
localScale.set(bindScale);
} else {
float invWeightSum = 1f - currentWeightSum;
localRot.nlerp(bindRot, invWeightSum);
localPos.interpolateLocal(bindPos, invWeightSum);
localScale.interpolateLocal(bindScale, invWeightSum);
}
// Future invocations of transform blend will start over.
currentWeightSum = -1;
}
if (parent != null) {
//rotation
parent.modelRot.mult(localRot, modelRot);
//scale
//For scale parent scale is not taken into account!
// worldScale.set(localScale);
parent.modelScale.mult(localScale, modelScale);
//translation
//scale and rotation of parent affect bone position
parent.modelRot.mult(localPos, modelPos);
modelPos.multLocal(parent.modelScale);
modelPos.addLocal(parent.modelPos);
} else {
modelRot.set(localRot);
modelPos.set(localPos);
modelScale.set(localScale);
}
if (attachNode != null) {
attachNode.setLocalTranslation(modelPos);
attachNode.setLocalRotation(modelRot);
attachNode.setLocalScale(modelScale);
}
}
/**
* Updates world transforms for this bone and it's children.
*/
public final void update() {
this.updateModelTransforms();
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() {
bindPos.set(localPos);
bindRot.set(localRot);
bindScale.set(localScale);
if (modelBindInversePos == null) {
modelBindInversePos = new Vector3f();
modelBindInverseRot = new Quaternion();
modelBindInverseScale = new Vector3f();
}
// Save inverse derived position/scale/orientation, used for calculate offset transform later
modelBindInversePos.set(modelPos);
modelBindInversePos.negateLocal();
modelBindInverseRot.set(modelRot);
modelBindInverseRot.inverseLocal();
modelBindInverseScale.set(Vector3f.UNIT_XYZ);
modelBindInverseScale.divideLocal(modelScale);
for (Bone b : children) {
b.setBindingPose();
}
}
/**
* Reset the bone and it's children to bind pose.
*/
final void reset() {
if (!userControl) {
localPos.set(bindPos);
localRot.set(bindRot);
localScale.set(bindScale);
}
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 = modelScale.mult(modelBindInverseScale, tmp3);
// Computing rotation
Quaternion rotate = modelRot.mult(modelBindInverseRot, tmp1);
// Computing translation
// Translation depend on rotation and scale
Vector3f translate = modelPos.add(rotate.mult(scale.mult(modelBindInversePos, tmp2), tmp2), tmp2);
// Populating the matrix
outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4));
}
/**
*
* Sets the transforms of this bone in local space (relative to the parent bone)
*
* @param translation the translation in local space
* @param rotation the rotation in local space
* @param scale the scale in local space
*/
public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
if (!userControl) {
throw new IllegalStateException("You must call setUserControl(true) in order to setUserTransform to work");
}
localPos.set(bindPos);
localRot.set(bindRot);
localScale.set(bindScale);
localPos.addLocal(translation);
localRot.multLocal(rotation);
localScale.multLocal(scale);
}
/**
*
* @param translation -
* @param rotation -
* @deprecated use {@link #setUserTransformsInModelSpace(com.jme3.math.Vector3f, com.jme3.math.Quaternion) }
*/
@Deprecated
public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
}
/**
* Sets the transforms of this bone in model space (relative to the root bone)
*
* Must update all bones in skeleton for this to work.
* @param translation translation in model space
* @param rotation rotation in model space
*/
public void setUserTransformsInModelSpace(Vector3f translation, Quaternion rotation) {
if (!userControl) {
throw new IllegalStateException("You must call setUserControl(true) in order to setUserTransformsInModelSpace to work");
}
// TODO: add scale here ???
modelPos.set(translation);
modelRot.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) {
if(tmpTransform == null){
tmpTransform = new Transform();
}
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(bindPos).addLocal(translation);
localRot.set(bindRot).multLocal(rotation);
if (scale != null) {
localScale.set(bindScale).multLocal(scale);
}
}
/**
* Blends the given animation transform onto the bone's local transform.
*
* Subsequent calls of this method stack up, with the final transformation
* of the bone computed at {@link #updateModelTransforms() } which resets
* the stack.
*
* E.g. a single transform blend with weight = 0.5 followed by an
* updateModelTransforms() call will result in final transform = transform * 0.5.
* Two transform blends with weight = 0.5 each will result in the two
* transforms blended together (nlerp) with blend = 0.5.
*
* @param translation The translation to blend in
* @param rotation The rotation to blend in
* @param scale The scale to blend in
* @param weight The weight of the transform to apply. Set to 1.0 to prevent
* any other transform from being applied until updateModelTransforms().
*/
void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
if (userControl) {
return;
}
if (weight == 0) {
// Do not apply this transform at all.
return;
}
if (currentWeightSum == 1){
return; // More than 2 transforms are being blended
} else if (currentWeightSum == -1 || currentWeightSum == 0) {
// Set the transform fully
localPos.set(bindPos).addLocal(translation);
localRot.set(bindRot).multLocal(rotation);
if (scale != null) {
localScale.set(bindScale).multLocal(scale);
}
// Set the weight. It will be applied in updateModelTransforms().
currentWeightSum = weight;
} else {
// The weight is already set.
// Blend in the new transform.
TempVars vars = TempVars.get();
Vector3f tmpV = vars.vect1;
Vector3f tmpV2 = vars.vect2;
Quaternion tmpQ = vars.quat1;
tmpV.set(bindPos).addLocal(translation);
localPos.interpolateLocal(tmpV, weight);
tmpQ.set(bindRot).multLocal(rotation);
localRot.nlerp(tmpQ, weight);
if (scale != null) {
tmpV2.set(bindScale).multLocal(scale);
localScale.interpolateLocal(tmpV2, weight);
}
// Ensures no new weights will be blended in the future.
currentWeightSum = 1;
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) {
bindPos.set(translation);
bindRot.set(rotation);
//ogre.xml can have null scale values breaking this if the check is removed
if (scale != null) {
bindScale.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);
int ver = input.getSavableVersion(Bone.class);
if(ver < 2){
bindPos = (Vector3f) input.readSavable("initialPos", null);
bindRot = (Quaternion) input.readSavable("initialRot", null);
bindScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
}else{
bindPos = (Vector3f) input.readSavable("bindPos", null);
bindRot = (Quaternion) input.readSavable("bindRot", null);
bindScale = (Vector3f) input.readSavable("bindScale", new Vector3f(1.0f, 1.0f, 1.0f));
}
attachNode = (Node) input.readSavable("attachNode", null);
localPos.set(bindPos);
localRot.set(bindRot);
localScale.set(bindScale);
ArrayList childList = input.readSavableArrayList("children", null);
for (int i = childList.size() - 1; i >= 0; i--) {
this.addChild(childList.get(i));
}
// NOTE: Parent skeleton will call update() then setBindingPose()
// after Skeleton has been de-serialized.
// Therefore, worldBindInversePos and worldBindInverseRot
// will be reconstructed based on that information.
}
@Override
public void write(JmeExporter ex) throws IOException {
OutputCapsule output = ex.getCapsule(this);
output.write(name, "name", null);
output.write(attachNode, "attachNode", null);
output.write(bindPos, "bindPos", null);
output.write(bindRot, "bindRot", null);
output.write(bindScale, "bindScale", new Vector3f(1.0f, 1.0f, 1.0f));
output.writeSavableArrayList(children, "children", null);
}
public void setLocalRotation(Quaternion rot){
if (!userControl) {
throw new IllegalStateException("User control must be on bone to allow user transforms");
}
this.localRot = rot;
}
public boolean hasUserControl(){
return userControl;
}
}