diff options
Diffstat (limited to 'engine/src/core/com/jme3/animation/Bone.java')
-rw-r--r-- | engine/src/core/com/jme3/animation/Bone.java | 628 |
1 files changed, 628 insertions, 0 deletions
diff --git a/engine/src/core/com/jme3/animation/Bone.java b/engine/src/core/com/jme3/animation/Bone.java new file mode 100644 index 0000000..e7e9e2b --- /dev/null +++ b/engine/src/core/com/jme3/animation/Bone.java @@ -0,0 +1,628 @@ +/* + * Copyright (c) 2009-2010 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; + +/** + * <code>Bone</code> 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<Bone> children = new ArrayList<Bone>(); + /** + * 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; + /** + * Initial transform is the local bind transform of this bone. + * PARENT SPACE -> BONE SPACE + */ + private Vector3f initialPos; + private Quaternion initialRot; + private Vector3f initialScale; + /** + * The inverse world bind transform. + * BONE SPACE -> MODEL SPACE + */ + private Vector3f worldBindInversePos; + private Quaternion worldBindInverseRot; + private Vector3f worldBindInverseScale; + /** + * The local animated transform combined with the local bind transform and parent world transform + */ + private Vector3f localPos = new Vector3f(); + private Quaternion localRot = new Quaternion(); + private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f); + /** + * MODEL SPACE -> BONE SPACE (in animated state) + */ + private Vector3f worldPos = new Vector3f(); + private Quaternion worldRot = new Quaternion(); + private Vector3f worldScale = new Vector3f(); + //used for getCombinedTransform + private Transform tmpTransform = new Transform(); + + /** + * 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; + + initialPos = new Vector3f(); + initialRot = new Quaternion(); + initialScale = new Vector3f(1, 1, 1); + + worldBindInversePos = new Vector3f(); + worldBindInverseRot = new Quaternion(); + worldBindInverseScale = new Vector3f(); + } + + /** + * Special-purpose copy constructor. + * <p> + * Only copies the name and bind pose from the original. + * <p> + * 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. + * <p> + * The rest of the data is <em>NOT</em> 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<Bone> 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 worldPos; + } + + /** + * Returns the rotation of the bone in model space. + * + * @return The rotation of the bone in model space. + */ + public Quaternion getModelSpaceRotation() { + return worldRot; + } + + /** + * Returns the scale of the bone in model space. + * + * @return The scale of the bone in model space. + */ + public Vector3f getModelSpaceScale() { + return worldScale; + } + + /** + * Returns the inverse world bind pose position. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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. + * <p> + * 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<Bone> 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(initialPos, "initialPos", null); + output.write(initialRot, "initialRot", null); + output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); + output.writeSavableArrayList(children, "children", null); + } +} |