| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- /*
- * 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;
- /**
- * <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();
-
- /**
- * 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;
- 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 (currentWeightSum == 1f) {
- currentWeightSum = -1;
- } else if (currentWeightSum != -1f) {
- // Apply the weight to the local transform
- if (currentWeightSum == 0) {
- localRot.set(initialRot);
- localPos.set(initialPos);
- localScale.set(initialScale);
- } else {
- float invWeightSum = 1f - currentWeightSum;
- localRot.nlerp(initialRot, invWeightSum);
- localPos.interpolateLocal(initialPos, invWeightSum);
- localScale.interpolateLocal(initialScale, invWeightSum);
- }
-
- // Future invocations of transform blend will start over.
- currentWeightSum = -1;
- }
-
- 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);
- }
- }
- /**
- * Blends the given animation transform onto the bone's local transform.
- * <p>
- * Subsequent calls of this method stack up, with the final transformation
- * of the bone computed at {@link #updateWorldVectors() } which resets
- * the stack.
- * <p>
- * E.g. a single transform blend with weight = 0.5 followed by an
- * updateWorldVectors() 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 updateWorldVectors().
- */
- 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(initialPos).addLocal(translation);
- localRot.set(initialRot).multLocal(rotation);
- if (scale != null) {
- localScale.set(initialScale).multLocal(scale);
- }
- // Set the weight. It will be applied in updateWorldVectors().
- 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(initialPos).addLocal(translation);
- localPos.interpolateLocal(tmpV, weight);
- tmpQ.set(initialRot).multLocal(rotation);
- localRot.nlerp(tmpQ, weight);
- if (scale != null) {
- tmpV2.set(initialScale).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) {
- 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);
- }
- }
|