1 /* 2 * Copyright (c) 2009-2010 jMonkeyEngine 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 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. 15 * 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. 19 * 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. 31 */ 32 package com.jme3.animation; 33 34 import com.jme3.export.*; 35 import com.jme3.math.*; 36 import com.jme3.scene.Node; 37 import com.jme3.util.TempVars; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 41 /** 42 * <code>Bone</code> describes a bone in the bone-weight skeletal animation 43 * system. A bone contains a name and an index, as well as relevant 44 * transformation data. 45 * 46 * @author Kirill Vainer 47 */ 48 public final class Bone implements Savable { 49 50 private String name; 51 private Bone parent; 52 private final ArrayList<Bone> children = new ArrayList<Bone>(); 53 /** 54 * If enabled, user can control bone transform with setUserTransforms. 55 * Animation transforms are not applied to this bone when enabled. 56 */ 57 private boolean userControl = false; 58 /** 59 * The attachment node. 60 */ 61 private Node attachNode; 62 /** 63 * Initial transform is the local bind transform of this bone. 64 * PARENT SPACE -> BONE SPACE 65 */ 66 private Vector3f initialPos; 67 private Quaternion initialRot; 68 private Vector3f initialScale; 69 /** 70 * The inverse world bind transform. 71 * BONE SPACE -> MODEL SPACE 72 */ 73 private Vector3f worldBindInversePos; 74 private Quaternion worldBindInverseRot; 75 private Vector3f worldBindInverseScale; 76 /** 77 * The local animated transform combined with the local bind transform and parent world transform 78 */ 79 private Vector3f localPos = new Vector3f(); 80 private Quaternion localRot = new Quaternion(); 81 private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f); 82 /** 83 * MODEL SPACE -> BONE SPACE (in animated state) 84 */ 85 private Vector3f worldPos = new Vector3f(); 86 private Quaternion worldRot = new Quaternion(); 87 private Vector3f worldScale = new Vector3f(); 88 //used for getCombinedTransform 89 private Transform tmpTransform = new Transform(); 90 91 /** 92 * Creates a new bone with the given name. 93 * 94 * @param name Name to give to this bone 95 */ Bone(String name)96 public Bone(String name) { 97 if (name == null) 98 throw new IllegalArgumentException("Name cannot be null"); 99 100 this.name = name; 101 102 initialPos = new Vector3f(); 103 initialRot = new Quaternion(); 104 initialScale = new Vector3f(1, 1, 1); 105 106 worldBindInversePos = new Vector3f(); 107 worldBindInverseRot = new Quaternion(); 108 worldBindInverseScale = new Vector3f(); 109 } 110 111 /** 112 * Special-purpose copy constructor. 113 * <p> 114 * Only copies the name and bind pose from the original. 115 * <p> 116 * WARNING: Local bind pose and world inverse bind pose transforms shallow 117 * copied. Modifying that data on the original bone will cause it to 118 * be recomputed on any cloned bones. 119 * <p> 120 * The rest of the data is <em>NOT</em> copied, as it will be 121 * generated automatically when the bone is animated. 122 * 123 * @param source The bone from which to copy the data. 124 */ Bone(Bone source)125 Bone(Bone source) { 126 this.name = source.name; 127 128 userControl = source.userControl; 129 130 initialPos = source.initialPos; 131 initialRot = source.initialRot; 132 initialScale = source.initialScale; 133 134 worldBindInversePos = source.worldBindInversePos; 135 worldBindInverseRot = source.worldBindInverseRot; 136 worldBindInverseScale = source.worldBindInverseScale; 137 138 // parent and children will be assigned manually.. 139 } 140 141 /** 142 * Serialization only. Do not use. 143 */ Bone()144 public Bone() { 145 } 146 147 /** 148 * Returns the name of the bone, set in the constructor. 149 * 150 * @return The name of the bone, set in the constructor. 151 */ getName()152 public String getName() { 153 return name; 154 } 155 156 /** 157 * Returns parent bone of this bone, or null if it is a root bone. 158 * @return The parent bone of this bone, or null if it is a root bone. 159 */ getParent()160 public Bone getParent() { 161 return parent; 162 } 163 164 /** 165 * Returns all the children bones of this bone. 166 * 167 * @return All the children bones of this bone. 168 */ getChildren()169 public ArrayList<Bone> getChildren() { 170 return children; 171 } 172 173 /** 174 * Returns the local position of the bone, relative to the parent bone. 175 * 176 * @return The local position of the bone, relative to the parent bone. 177 */ getLocalPosition()178 public Vector3f getLocalPosition() { 179 return localPos; 180 } 181 182 /** 183 * Returns the local rotation of the bone, relative to the parent bone. 184 * 185 * @return The local rotation of the bone, relative to the parent bone. 186 */ getLocalRotation()187 public Quaternion getLocalRotation() { 188 return localRot; 189 } 190 191 /** 192 * Returns the local scale of the bone, relative to the parent bone. 193 * 194 * @return The local scale of the bone, relative to the parent bone. 195 */ getLocalScale()196 public Vector3f getLocalScale() { 197 return localScale; 198 } 199 200 /** 201 * Returns the position of the bone in model space. 202 * 203 * @return The position of the bone in model space. 204 */ getModelSpacePosition()205 public Vector3f getModelSpacePosition() { 206 return worldPos; 207 } 208 209 /** 210 * Returns the rotation of the bone in model space. 211 * 212 * @return The rotation of the bone in model space. 213 */ getModelSpaceRotation()214 public Quaternion getModelSpaceRotation() { 215 return worldRot; 216 } 217 218 /** 219 * Returns the scale of the bone in model space. 220 * 221 * @return The scale of the bone in model space. 222 */ getModelSpaceScale()223 public Vector3f getModelSpaceScale() { 224 return worldScale; 225 } 226 227 /** 228 * Returns the inverse world bind pose position. 229 * <p> 230 * The bind pose transform of the bone is its "default" 231 * transform with no animation applied. 232 * 233 * @return the inverse world bind pose position. 234 */ getWorldBindInversePosition()235 public Vector3f getWorldBindInversePosition() { 236 return worldBindInversePos; 237 } 238 239 /** 240 * Returns the inverse world bind pose rotation. 241 * <p> 242 * The bind pose transform of the bone is its "default" 243 * transform with no animation applied. 244 * 245 * @return the inverse world bind pose rotation. 246 */ getWorldBindInverseRotation()247 public Quaternion getWorldBindInverseRotation() { 248 return worldBindInverseRot; 249 } 250 251 /** 252 * Returns the inverse world bind pose scale. 253 * <p> 254 * The bind pose transform of the bone is its "default" 255 * transform with no animation applied. 256 * 257 * @return the inverse world bind pose scale. 258 */ getWorldBindInverseScale()259 public Vector3f getWorldBindInverseScale() { 260 return worldBindInverseScale; 261 } 262 263 /** 264 * Returns the world bind pose position. 265 * <p> 266 * The bind pose transform of the bone is its "default" 267 * transform with no animation applied. 268 * 269 * @return the world bind pose position. 270 */ getWorldBindPosition()271 public Vector3f getWorldBindPosition() { 272 return initialPos; 273 } 274 275 /** 276 * Returns the world bind pose rotation. 277 * <p> 278 * The bind pose transform of the bone is its "default" 279 * transform with no animation applied. 280 * 281 * @return the world bind pose rotation. 282 */ getWorldBindRotation()283 public Quaternion getWorldBindRotation() { 284 return initialRot; 285 } 286 287 /** 288 * Returns the world bind pose scale. 289 * <p> 290 * The bind pose transform of the bone is its "default" 291 * transform with no animation applied. 292 * 293 * @return the world bind pose scale. 294 */ getWorldBindScale()295 public Vector3f getWorldBindScale() { 296 return initialScale; 297 } 298 299 /** 300 * If enabled, user can control bone transform with setUserTransforms. 301 * Animation transforms are not applied to this bone when enabled. 302 */ setUserControl(boolean enable)303 public void setUserControl(boolean enable) { 304 userControl = enable; 305 } 306 307 /** 308 * Add a new child to this bone. Shouldn't be used by user code. 309 * Can corrupt skeleton. 310 * 311 * @param bone The bone to add 312 */ addChild(Bone bone)313 public void addChild(Bone bone) { 314 children.add(bone); 315 bone.parent = this; 316 } 317 318 /** 319 * Updates the world transforms for this bone, and, possibly the attach node 320 * if not null. 321 * <p> 322 * The world transform of this bone is computed by combining the parent's 323 * world transform with this bones' local transform. 324 */ updateWorldVectors()325 public final void updateWorldVectors() { 326 if (parent != null) { 327 //rotation 328 parent.worldRot.mult(localRot, worldRot); 329 330 //scale 331 //For scale parent scale is not taken into account! 332 // worldScale.set(localScale); 333 parent.worldScale.mult(localScale, worldScale); 334 335 //translation 336 //scale and rotation of parent affect bone position 337 parent.worldRot.mult(localPos, worldPos); 338 worldPos.multLocal(parent.worldScale); 339 worldPos.addLocal(parent.worldPos); 340 } else { 341 worldRot.set(localRot); 342 worldPos.set(localPos); 343 worldScale.set(localScale); 344 } 345 346 if (attachNode != null) { 347 attachNode.setLocalTranslation(worldPos); 348 attachNode.setLocalRotation(worldRot); 349 attachNode.setLocalScale(worldScale); 350 } 351 } 352 353 /** 354 * Updates world transforms for this bone and it's children. 355 */ update()356 final void update() { 357 this.updateWorldVectors(); 358 359 for (int i = children.size() - 1; i >= 0; i--) { 360 children.get(i).update(); 361 } 362 } 363 364 /** 365 * Saves the current bone state as its binding pose, including its children. 366 */ setBindingPose()367 void setBindingPose() { 368 initialPos.set(localPos); 369 initialRot.set(localRot); 370 initialScale.set(localScale); 371 372 if (worldBindInversePos == null) { 373 worldBindInversePos = new Vector3f(); 374 worldBindInverseRot = new Quaternion(); 375 worldBindInverseScale = new Vector3f(); 376 } 377 378 // Save inverse derived position/scale/orientation, used for calculate offset transform later 379 worldBindInversePos.set(worldPos); 380 worldBindInversePos.negateLocal(); 381 382 worldBindInverseRot.set(worldRot); 383 worldBindInverseRot.inverseLocal(); 384 385 worldBindInverseScale.set(Vector3f.UNIT_XYZ); 386 worldBindInverseScale.divideLocal(worldScale); 387 388 for (Bone b : children) { 389 b.setBindingPose(); 390 } 391 } 392 393 /** 394 * Reset the bone and it's children to bind pose. 395 */ reset()396 final void reset() { 397 if (!userControl) { 398 localPos.set(initialPos); 399 localRot.set(initialRot); 400 localScale.set(initialScale); 401 } 402 403 for (int i = children.size() - 1; i >= 0; i--) { 404 children.get(i).reset(); 405 } 406 } 407 408 /** 409 * Stores the skinning transform in the specified Matrix4f. 410 * The skinning transform applies the animation of the bone to a vertex. 411 * 412 * This assumes that the world transforms for the entire bone hierarchy 413 * have already been computed, otherwise this method will return undefined 414 * results. 415 * 416 * @param outTransform 417 */ getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4)418 void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) { 419 // Computing scale 420 Vector3f scale = worldScale.mult(worldBindInverseScale, tmp3); 421 422 // Computing rotation 423 Quaternion rotate = worldRot.mult(worldBindInverseRot, tmp1); 424 425 // Computing translation 426 // Translation depend on rotation and scale 427 Vector3f translate = worldPos.add(rotate.mult(scale.mult(worldBindInversePos, tmp2), tmp2), tmp2); 428 429 // Populating the matrix 430 outTransform.loadIdentity(); 431 outTransform.setTransform(translate, scale, rotate.toRotationMatrix(tmp4)); 432 } 433 434 /** 435 * Sets user transform. 436 */ setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale)437 public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { 438 if (!userControl) { 439 throw new IllegalStateException("User control must be on bone to allow user transforms"); 440 } 441 442 localPos.set(initialPos); 443 localRot.set(initialRot); 444 localScale.set(initialScale); 445 446 localPos.addLocal(translation); 447 localRot = localRot.mult(rotation); 448 localScale.multLocal(scale); 449 } 450 451 /** 452 * Must update all bones in skeleton for this to work. 453 * @param translation 454 * @param rotation 455 */ setUserTransformsWorld(Vector3f translation, Quaternion rotation)456 public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) { 457 if (!userControl) { 458 throw new IllegalStateException("User control must be on bone to allow user transforms"); 459 } 460 461 // TODO: add scale here ??? 462 worldPos.set(translation); 463 worldRot.set(rotation); 464 465 //if there is an attached Node we need to set it's local transforms too. 466 if(attachNode != null){ 467 attachNode.setLocalTranslation(translation); 468 attachNode.setLocalRotation(rotation); 469 } 470 } 471 472 /** 473 * Returns the local transform of this bone combined with the given position and rotation 474 * @param position a position 475 * @param rotation a rotation 476 */ getCombinedTransform(Vector3f position, Quaternion rotation)477 public Transform getCombinedTransform(Vector3f position, Quaternion rotation) { 478 rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position); 479 tmpTransform.setRotation(rotation).getRotation().multLocal(localRot); 480 return tmpTransform; 481 } 482 483 /** 484 * Returns the attachment node. 485 * Attach models and effects to this node to make 486 * them follow this bone's motions. 487 */ getAttachmentsNode()488 Node getAttachmentsNode() { 489 if (attachNode == null) { 490 attachNode = new Node(name + "_attachnode"); 491 attachNode.setUserData("AttachedBone", this); 492 } 493 return attachNode; 494 } 495 496 /** 497 * Used internally after model cloning. 498 * @param attachNode 499 */ setAttachmentsNode(Node attachNode)500 void setAttachmentsNode(Node attachNode) { 501 this.attachNode = attachNode; 502 } 503 504 /** 505 * Sets the local animation transform of this bone. 506 * Bone is assumed to be in bind pose when this is called. 507 */ setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale)508 void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { 509 if (userControl) { 510 return; 511 } 512 513 // localPos.addLocal(translation); 514 // localRot.multLocal(rotation); 515 //localRot = localRot.mult(rotation); 516 517 localPos.set(initialPos).addLocal(translation); 518 localRot.set(initialRot).multLocal(rotation); 519 520 if (scale != null) { 521 localScale.set(initialScale).multLocal(scale); 522 } 523 } 524 blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight)525 void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale, float weight) { 526 if (userControl) { 527 return; 528 } 529 530 TempVars vars = TempVars.get(); 531 // assert vars.lock(); 532 533 Vector3f tmpV = vars.vect1; 534 Vector3f tmpV2 = vars.vect2; 535 Quaternion tmpQ = vars.quat1; 536 537 //location 538 tmpV.set(initialPos).addLocal(translation); 539 localPos.interpolate(tmpV, weight); 540 541 //rotation 542 tmpQ.set(initialRot).multLocal(rotation); 543 localRot.nlerp(tmpQ, weight); 544 545 //scale 546 if (scale != null) { 547 tmpV2.set(initialScale).multLocal(scale); 548 localScale.interpolate(tmpV2, weight); 549 } 550 551 552 vars.release(); 553 } 554 555 /** 556 * Sets local bind transform for bone. 557 * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them. 558 */ setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale)559 public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { 560 initialPos.set(translation); 561 initialRot.set(rotation); 562 //ogre.xml can have null scale values breaking this if the check is removed 563 if (scale != null) { 564 initialScale.set(scale); 565 } 566 567 localPos.set(translation); 568 localRot.set(rotation); 569 if (scale != null) { 570 localScale.set(scale); 571 } 572 } 573 toString(int depth)574 private String toString(int depth) { 575 StringBuilder sb = new StringBuilder(); 576 for (int i = 0; i < depth; i++) { 577 sb.append('-'); 578 } 579 580 sb.append(name).append(" bone\n"); 581 for (Bone child : children) { 582 sb.append(child.toString(depth + 1)); 583 } 584 return sb.toString(); 585 } 586 587 @Override toString()588 public String toString() { 589 return this.toString(0); 590 } 591 592 @Override 593 @SuppressWarnings("unchecked") read(JmeImporter im)594 public void read(JmeImporter im) throws IOException { 595 InputCapsule input = im.getCapsule(this); 596 597 name = input.readString("name", null); 598 initialPos = (Vector3f) input.readSavable("initialPos", null); 599 initialRot = (Quaternion) input.readSavable("initialRot", null); 600 initialScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); 601 attachNode = (Node) input.readSavable("attachNode", null); 602 603 localPos.set(initialPos); 604 localRot.set(initialRot); 605 606 ArrayList<Bone> childList = input.readSavableArrayList("children", null); 607 for (int i = childList.size() - 1; i >= 0; i--) { 608 this.addChild(childList.get(i)); 609 } 610 611 // NOTE: Parent skeleton will call update() then setBindingPose() 612 // after Skeleton has been de-serialized. 613 // Therefore, worldBindInversePos and worldBindInverseRot 614 // will be reconstructed based on that information. 615 } 616 617 @Override write(JmeExporter ex)618 public void write(JmeExporter ex) throws IOException { 619 OutputCapsule output = ex.getCapsule(this); 620 621 output.write(name, "name", null); 622 output.write(attachNode, "attachNode", null); 623 output.write(initialPos, "initialPos", null); 624 output.write(initialRot, "initialRot", null); 625 output.write(initialScale, "initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); 626 output.writeSavableArrayList(children, "children", null); 627 } 628 } 629