aboutsummaryrefslogtreecommitdiff
path: root/engine/src/core/com/jme3/animation/Bone.java
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/core/com/jme3/animation/Bone.java')
-rw-r--r--engine/src/core/com/jme3/animation/Bone.java628
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);
+ }
+}