2 * Copyright (c) 2009-2010 jMonkeyEngine
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17 * may be used to endorse or promote products derived from this software
18 * without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 package com.jme3.animation;
34 import com.jme3.export.JmeExporter;
35 import com.jme3.export.JmeImporter;
36 import com.jme3.export.InputCapsule;
37 import com.jme3.export.OutputCapsule;
38 import com.jme3.export.Savable;
39 import com.jme3.math.Matrix3f;
40 import com.jme3.math.Matrix4f;
41 import com.jme3.math.Quaternion;
42 import com.jme3.math.Transform;
43 import com.jme3.math.Vector3f;
44 import com.jme3.scene.Node;
45 import com.jme3.util.TempVars;
46 import java.io.IOException;
47 import java.util.ArrayList;
50 * <code>Bone</code> describes a bone in the bone-weight skeletal animation
51 * system. A bone contains a name and an index, as well as relevant
52 * transformation data.
54 * @author Kirill Vainer
56 public final class Bone implements Savable {
60 private final ArrayList<Bone> children = new ArrayList<Bone>();
62 * If enabled, user can control bone transform with setUserTransforms.
63 * Animation transforms are not applied to this bone when enabled.
65 private boolean userControl = false;
66 private boolean useModelSpaceVectors = false;
68 * The attachment node.
70 private Node attachNode;
72 * Initial transform is the local bind transform of this bone.
73 * PARENT SPACE -> BONE SPACE
75 private Vector3f initialPos;
76 private Quaternion initialRot;
77 private Vector3f initialScale;
79 * The inverse world bind transform.
80 * BONE SPACE -> MODEL SPACE
82 private Vector3f worldBindInversePos;
83 private Quaternion worldBindInverseRot;
84 private Vector3f worldBindInverseScale;
86 * The local animated transform combined with the local bind transform and parent world transform
88 private Vector3f localPos = new Vector3f();
89 private Quaternion localRot = new Quaternion();
90 private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f);
92 * MODEL SPACE -> BONE SPACE (in animated state)
94 private Vector3f worldPos = new Vector3f();
95 private Quaternion worldRot = new Quaternion();
96 private Vector3f worldScale = new Vector3f();
97 //used for getCombinedTransform
98 private Transform tmpTransform = new Transform();
101 * Creates a new bone with the given name.
103 * @param name Name to give to this bone
105 public Bone(String name) {
107 throw new IllegalArgumentException("Name cannot be null");
111 initialPos = new Vector3f();
112 initialRot = new Quaternion();
113 initialScale = new Vector3f(1, 1, 1);
115 worldBindInversePos = new Vector3f();
116 worldBindInverseRot = new Quaternion();
117 worldBindInverseScale = new Vector3f();
121 * Special-purpose copy constructor.
123 * Only copies the name and bind pose from the original.
125 * WARNING: Local bind pose and world inverse bind pose transforms shallow
126 * copied. Modifying that data on the original bone will cause it to
127 * be recomputed on any cloned bones.
129 * The rest of the data is <em>NOT</em> copied, as it will be
130 * generated automatically when the bone is animated.
132 * @param source The bone from which to copy the data.
135 this.name = source.name;
137 userControl = source.userControl;
139 initialPos = source.initialPos;
140 initialRot = source.initialRot;
141 initialScale = source.initialScale;
143 worldBindInversePos = source.worldBindInversePos;
144 worldBindInverseRot = source.worldBindInverseRot;
145 worldBindInverseScale = source.worldBindInverseScale;
147 // parent and children will be assigned manually..
151 * Serialization only. Do not use.
157 * Returns the name of the bone, set in the constructor.
159 * @return The name of the bone, set in the constructor.
161 public String getName() {
166 * Returns parent bone of this bone, or null if it is a root bone.
167 * @return The parent bone of this bone, or null if it is a root bone.
169 public Bone getParent() {
174 * Returns all the children bones of this bone.
176 * @return All the children bones of this bone.
178 public ArrayList<Bone> getChildren() {
183 * Returns the local position of the bone, relative to the parent bone.
185 * @return The local position of the bone, relative to the parent bone.
187 public Vector3f getLocalPosition() {
192 * Returns the local rotation of the bone, relative to the parent bone.
194 * @return The local rotation of the bone, relative to the parent bone.
196 public Quaternion getLocalRotation() {
201 * Returns the local scale of the bone, relative to the parent bone.
203 * @return The local scale of the bone, relative to the parent bone.
205 public Vector3f getLocalScale() {
210 * Returns the position of the bone in model space.
212 * @return The position of the bone in model space.
214 public Vector3f getModelSpacePosition() {
219 * Returns the rotation of the bone in model space.
221 * @return The rotation of the bone in model space.
223 public Quaternion getModelSpaceRotation() {
228 * Returns the scale of the bone in model space.
230 * @return The scale of the bone in model space.
232 public Vector3f getModelSpaceScale() {
237 * Returns the inverse world bind pose position.
239 * The bind pose transform of the bone is its "default"
240 * transform with no animation applied.
242 * @return the inverse world bind pose position.
244 public Vector3f getWorldBindInversePosition() {
245 return worldBindInversePos;
249 * Returns the inverse world bind pose rotation.
251 * The bind pose transform of the bone is its "default"
252 * transform with no animation applied.
254 * @return the inverse world bind pose rotation.
256 public Quaternion getWorldBindInverseRotation() {
257 return worldBindInverseRot;
261 * Returns the inverse world bind pose scale.
263 * The bind pose transform of the bone is its "default"
264 * transform with no animation applied.
266 * @return the inverse world bind pose scale.
268 public Vector3f getWorldBindInverseScale() {
269 return worldBindInverseScale;
273 * Returns the world bind pose position.
275 * The bind pose transform of the bone is its "default"
276 * transform with no animation applied.
278 * @return the world bind pose position.
280 public Vector3f getWorldBindPosition() {
285 * Returns the world bind pose rotation.
287 * The bind pose transform of the bone is its "default"
288 * transform with no animation applied.
290 * @return the world bind pose rotation.
292 public Quaternion getWorldBindRotation() {
297 * Returns the world bind pose scale.
299 * The bind pose transform of the bone is its "default"
300 * transform with no animation applied.
302 * @return the world bind pose scale.
304 public Vector3f getWorldBindScale() {
309 * If enabled, user can control bone transform with setUserTransforms.
310 * Animation transforms are not applied to this bone when enabled.
312 public void setUserControl(boolean enable) {
313 userControl = enable;
317 * Add a new child to this bone. Shouldn't be used by user code.
318 * Can corrupt skeleton.
320 * @param bone The bone to add
322 public void addChild(Bone bone) {
328 * Updates the world transforms for this bone, and, possibly the attach node
331 * The world transform of this bone is computed by combining the parent's
332 * world transform with this bones' local transform.
334 public final void updateWorldVectors() {
335 if (true || !useModelSpaceVectors) {
336 if (parent != null) {
338 parent.worldRot.mult(localRot, worldRot);
341 //For scale parent scale is not taken into account!
342 // worldScale.set(localScale);
343 parent.worldScale.mult(localScale, worldScale);
346 //scale and rotation of parent affect bone position
347 parent.worldRot.mult(localPos, worldPos);
348 worldPos.multLocal(parent.worldScale);
349 worldPos.addLocal(parent.worldPos);
351 worldRot.set(localRot);
352 worldPos.set(localPos);
353 worldScale.set(localScale);
356 if (attachNode != null) {
357 attachNode.setLocalTranslation(worldPos);
358 attachNode.setLocalRotation(worldRot);
359 attachNode.setLocalScale(worldScale);
364 * Updates world transforms for this bone and it's children.
366 final void update() {
367 this.updateWorldVectors();
369 for (int i = children.size() - 1; i >= 0; i--) {
370 children.get(i).update();
375 * Saves the current bone state as its binding pose, including its children.
377 void setBindingPose() {
378 initialPos.set(localPos);
379 initialRot.set(localRot);
380 initialScale.set(localScale);
382 if (worldBindInversePos == null) {
383 worldBindInversePos = new Vector3f();
384 worldBindInverseRot = new Quaternion();
385 worldBindInverseScale = new Vector3f();
388 // Save inverse derived position/scale/orientation, used for calculate offset transform later
389 worldBindInversePos.set(worldPos);
390 worldBindInversePos.negateLocal();
392 worldBindInverseRot.set(worldRot);
393 worldBindInverseRot.inverseLocal();
395 worldBindInverseScale.set(Vector3f.UNIT_XYZ);
396 worldBindInverseScale.divideLocal(worldScale);
398 for (Bone b : children) {
404 * Reset the bone and it's children to bind pose.
408 localPos.set(initialPos);
409 localRot.set(initialRot);
410 localScale.set(initialScale);
413 for (int i = children.size() - 1; i >= 0; i--) {
414 children.get(i).reset();
419 * Stores the skinning transform in the specified Matrix4f.
420 * The skinning transform applies the animation of the bone to a vertex.
422 * This assumes that the world transforms for the entire bone hierarchy
423 * have already been computed, otherwise this method will return undefined
426 * @param outTransform
428 void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) {
430 Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3);
432 // Computing rotation
433 Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1);
435 // Computing translation
436 // Translation depend on rotation and scale
437 Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2);
439 // Populating the matrix
440 outTransform.loadIdentity();
441 outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4));
445 * Sets user transform.
447 public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
449 throw new IllegalStateException("User control must be on bone to allow user transforms");
452 localPos.set(initialPos);
453 localRot.set(initialRot);
454 localScale.set(initialScale);
456 localPos.addLocal(translation);
457 localRot = localRot.mult(rotation);
458 localScale.multLocal(scale);
462 * Must update all bones in skeleton for this to work.
466 public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) {
468 throw new IllegalStateException("User control must be on bone to allow user transforms");
471 // TODO: add scale here ???
472 worldPos.set(translation);
473 worldRot.set(rotation);
477 * Returns the local transform of this bone combined with the given position and rotation
478 * @param position a position
479 * @param rotation a rotation
481 public Transform getCombinedTransform(Vector3f position, Quaternion rotation) {
482 rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position);
483 tmpTransform.setRotation(rotation).getRotation().multLocal(localRot);
488 * Returns the attachment node.
489 * Attach models and effects to this node to make
490 * them follow this bone's motions.
492 public Node getAttachmentsNode() {
493 if (attachNode == null) {
494 attachNode = new Node(name + "_attachnode");
495 attachNode.setUserData("AttachedBone", this);
501 * Used internally after model cloning.
504 void setAttachmentsNode(Node attachNode) {
505 this.attachNode = attachNode;
509 * Sets the local animation transform of this bone.
510 * Bone is assumed to be in bind pose when this is called.
512 void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
517 // localPos.addLocal(translation);
518 // localRot.multLocal(rotation);
519 //localRot = localRot.mult(rotation);
521 localPos.set(initialPos).addLocal(translation);
522 localRot.set(initialRot).multLocal(rotation);
525 localScale.set(initialScale).multLocal(scale);
529 void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) {
534 TempVars vars = TempVars.get();
535 // assert vars.lock();
537 Vector3f tmpV = vars.vect1;
538 Vector3f tmpV2 = vars.vect2;
539 Quaternion tmpQ = vars.quat1;
542 tmpV.set(initialPos).addLocal(translation);
543 localPos.interpolate(tmpV, weight);
546 tmpQ.set(initialRot).multLocal(rotation);
547 localRot.nlerp(tmpQ, weight);
551 tmpV2.set(initialScale).multLocal(scale);
552 localScale.interpolate(tmpV2, weight);
560 * Sets local bind transform for bone.
561 * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them.
563 public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) {
564 initialPos.set(translation);
565 initialRot.set(rotation);
566 //ogre.xml can have null scale values breaking this if the check is removed
568 initialScale.set(scale);
571 localPos.set(translation);
572 localRot.set(rotation);
574 localScale.set(scale);
578 private String toString(int depth) {
579 StringBuilder sb = new StringBuilder();
580 for (int i = 0; i < depth; i++) {
584 sb.append(name).append(" bone\n");
585 for (Bone child : children) {
586 sb.append(child.toString(depth + 1));
588 return sb.toString();
592 public String toString() {
593 return this.toString(0);
596 public boolean isUseModelSpaceVectors() {
597 return useModelSpaceVectors;
600 public void setUseModelSpaceVectors(boolean useModelSpaceVectors) {
601 this.useModelSpaceVectors = useModelSpaceVectors;
605 @SuppressWarnings("unchecked")
606 public void read(JmeImporter im) throws IOException {
607 InputCapsule input = im.getCapsule(this);
609 name = input.readString("name", null);
610 initialPos = (Vector3f) input.readSavable("initialPos", null);
611 initialRot = (Quaternion) input.readSavable("initialRot", null);
612 initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
613 attachNode = (Node) input.readSavable("attachNode", null);
615 localPos.set(initialPos);
616 localRot.set(initialRot);
618 ArrayList<Bone> childList = input.readSavableArrayList("children", null);
619 for (int i = childList.size() - 1; i >= 0; i--) {
620 this.addChild(childList.get(i));
623 // NOTE: Parent skeleton will call update() then setBindingPose()
624 // after Skeleton has been de-serialized.
625 // Therefore, worldBindInversePos and worldBindInverseRot
626 // will be reconstructed based on that information.
630 public void write(JmeExporter ex) throws IOException {
631 OutputCapsule output = ex.getCapsule(this);
633 output.write(name, "name", null);
634 output.write(attachNode, "attachNode", null);
635 output.write(initialPos, "initialPos", null);
636 output.write(initialRot, "initialRot", null);
637 output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f));
638 output.writeSavableArrayList(children, "children", null);