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.scene; 33 34 import com.jme3.asset.Asset; 35 import com.jme3.asset.AssetKey; 36 import com.jme3.bounding.BoundingVolume; 37 import com.jme3.collision.Collidable; 38 import com.jme3.export.*; 39 import com.jme3.light.Light; 40 import com.jme3.light.LightList; 41 import com.jme3.material.Material; 42 import com.jme3.math.*; 43 import com.jme3.renderer.Camera; 44 import com.jme3.renderer.RenderManager; 45 import com.jme3.renderer.ViewPort; 46 import com.jme3.renderer.queue.RenderQueue; 47 import com.jme3.renderer.queue.RenderQueue.Bucket; 48 import com.jme3.renderer.queue.RenderQueue.ShadowMode; 49 import com.jme3.scene.control.Control; 50 import com.jme3.util.SafeArrayList; 51 import com.jme3.util.TempVars; 52 import java.io.IOException; 53 import java.util.*; 54 import java.util.logging.Logger; 55 56 /** 57 * <code>Spatial</code> defines the base class for scene graph nodes. It 58 * maintains a link to a parent, it's local transforms and the world's 59 * transforms. All other scene graph elements, such as {@link Node} and 60 * {@link Geometry} are subclasses of <code>Spatial</code>. 61 * 62 * @author Mark Powell 63 * @author Joshua Slack 64 * @version $Revision: 4075 $, $Data$ 65 */ 66 public abstract class Spatial implements Savable, Cloneable, Collidable, Asset { 67 68 private static final Logger logger = Logger.getLogger(Spatial.class.getName()); 69 70 /** 71 * Specifies how frustum culling should be handled by 72 * this spatial. 73 */ 74 public enum CullHint { 75 76 /** 77 * Do whatever our parent does. If no parent, default to {@link #Dynamic}. 78 */ 79 Inherit, 80 /** 81 * Do not draw if we are not at least partially within the view frustum 82 * of the camera. This is determined via the defined 83 * Camera planes whether or not this Spatial should be culled. 84 */ 85 Dynamic, 86 /** 87 * Always cull this from the view, throwing away this object 88 * and any children from rendering commands. 89 */ 90 Always, 91 /** 92 * Never cull this from view, always draw it. 93 * Note that we will still get culled if our parent is culled. 94 */ 95 Never; 96 } 97 98 /** 99 * Specifies if this spatial should be batched 100 */ 101 public enum BatchHint { 102 103 /** 104 * Do whatever our parent does. If no parent, default to {@link #Always}. 105 */ 106 Inherit, 107 /** 108 * This spatial will always be batched when attached to a BatchNode. 109 */ 110 Always, 111 /** 112 * This spatial will never be batched when attached to a BatchNode. 113 */ 114 Never; 115 } 116 /** 117 * Refresh flag types 118 */ 119 protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms 120 RF_BOUND = 0x02, 121 RF_LIGHTLIST = 0x04; // changes in light lists 122 protected CullHint cullHint = CullHint.Inherit; 123 protected BatchHint batchHint = BatchHint.Inherit; 124 /** 125 * Spatial's bounding volume relative to the world. 126 */ 127 protected BoundingVolume worldBound; 128 /** 129 * LightList 130 */ 131 protected LightList localLights; 132 protected transient LightList worldLights; 133 /** 134 * This spatial's name. 135 */ 136 protected String name; 137 // scale values 138 protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; 139 protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; 140 protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit; 141 public transient float queueDistance = Float.NEGATIVE_INFINITY; 142 protected Transform localTransform; 143 protected Transform worldTransform; 144 protected SafeArrayList<Control> controls = new SafeArrayList<Control>(Control.class); 145 protected HashMap<String, Savable> userData = null; 146 /** 147 * Used for smart asset caching 148 * 149 * @see AssetKey#useSmartCache() 150 */ 151 protected AssetKey key; 152 /** 153 * Spatial's parent, or null if it has none. 154 */ 155 protected transient Node parent; 156 /** 157 * Refresh flags. Indicate what data of the spatial need to be 158 * updated to reflect the correct state. 159 */ 160 protected transient int refreshFlags = 0; 161 162 /** 163 * Serialization only. Do not use. 164 */ 165 public Spatial() { 166 localTransform = new Transform(); 167 worldTransform = new Transform(); 168 169 localLights = new LightList(this); 170 worldLights = new LightList(this); 171 172 refreshFlags |= RF_BOUND; 173 } 174 175 /** 176 * Constructor instantiates a new <code>Spatial</code> object setting the 177 * rotation, translation and scale value to defaults. 178 * 179 * @param name 180 * the name of the scene element. This is required for 181 * identification and comparison purposes. 182 */ 183 public Spatial(String name) { 184 this(); 185 this.name = name; 186 } 187 188 public void setKey(AssetKey key) { 189 this.key = key; 190 } 191 192 public AssetKey getKey() { 193 return key; 194 } 195 196 /** 197 * Indicate that the transform of this spatial has changed and that 198 * a refresh is required. 199 */ 200 protected void setTransformRefresh() { 201 refreshFlags |= RF_TRANSFORM; 202 setBoundRefresh(); 203 } 204 205 protected void setLightListRefresh() { 206 refreshFlags |= RF_LIGHTLIST; 207 } 208 209 /** 210 * Indicate that the bounding of this spatial has changed and that 211 * a refresh is required. 212 */ 213 protected void setBoundRefresh() { 214 refreshFlags |= RF_BOUND; 215 216 // XXX: Replace with a recursive call? 217 Spatial p = parent; 218 while (p != null) { 219 if ((p.refreshFlags & RF_BOUND) != 0) { 220 return; 221 } 222 223 p.refreshFlags |= RF_BOUND; 224 p = p.parent; 225 } 226 } 227 228 /** 229 * <code>checkCulling</code> checks the spatial with the camera to see if it 230 * should be culled. 231 * <p> 232 * This method is called by the renderer. Usually it should not be called 233 * directly. 234 * 235 * @param cam The camera to check against. 236 * @return true if inside or intersecting camera frustum 237 * (should be rendered), false if outside. 238 */ 239 public boolean checkCulling(Camera cam) { 240 if (refreshFlags != 0) { 241 throw new IllegalStateException("Scene graph is not properly updated for rendering.\n" 242 + "State was changed after rootNode.updateGeometricState() call. \n" 243 + "Make sure you do not modify the scene from another thread!\n" 244 + "Problem spatial name: " + getName()); 245 } 246 247 CullHint cm = getCullHint(); 248 assert cm != CullHint.Inherit; 249 if (cm == Spatial.CullHint.Always) { 250 setLastFrustumIntersection(Camera.FrustumIntersect.Outside); 251 return false; 252 } else if (cm == Spatial.CullHint.Never) { 253 setLastFrustumIntersection(Camera.FrustumIntersect.Intersects); 254 return true; 255 } 256 257 // check to see if we can cull this node 258 frustrumIntersects = (parent != null ? parent.frustrumIntersects 259 : Camera.FrustumIntersect.Intersects); 260 261 if (frustrumIntersects == Camera.FrustumIntersect.Intersects) { 262 if (getQueueBucket() == Bucket.Gui) { 263 return cam.containsGui(getWorldBound()); 264 } else { 265 frustrumIntersects = cam.contains(getWorldBound()); 266 } 267 } 268 269 return frustrumIntersects != Camera.FrustumIntersect.Outside; 270 } 271 272 /** 273 * Sets the name of this spatial. 274 * 275 * @param name 276 * The spatial's new name. 277 */ 278 public void setName(String name) { 279 this.name = name; 280 } 281 282 /** 283 * Returns the name of this spatial. 284 * 285 * @return This spatial's name. 286 */ 287 public String getName() { 288 return name; 289 } 290 291 /** 292 * Returns the local {@link LightList}, which are the lights 293 * that were directly attached to this <code>Spatial</code> through the 294 * {@link #addLight(com.jme3.light.Light) } and 295 * {@link #removeLight(com.jme3.light.Light) } methods. 296 * 297 * @return The local light list 298 */ 299 public LightList getLocalLightList() { 300 return localLights; 301 } 302 303 /** 304 * Returns the world {@link LightList}, containing the lights 305 * combined from all this <code>Spatial's</code> parents up to and including 306 * this <code>Spatial</code>'s lights. 307 * 308 * @return The combined world light list 309 */ 310 public LightList getWorldLightList() { 311 return worldLights; 312 } 313 314 /** 315 * <code>getWorldRotation</code> retrieves the absolute rotation of the 316 * Spatial. 317 * 318 * @return the Spatial's world rotation quaternion. 319 */ 320 public Quaternion getWorldRotation() { 321 checkDoTransformUpdate(); 322 return worldTransform.getRotation(); 323 } 324 325 /** 326 * <code>getWorldTranslation</code> retrieves the absolute translation of 327 * the spatial. 328 * 329 * @return the Spatial's world tranlsation vector. 330 */ 331 public Vector3f getWorldTranslation() { 332 checkDoTransformUpdate(); 333 return worldTransform.getTranslation(); 334 } 335 336 /** 337 * <code>getWorldScale</code> retrieves the absolute scale factor of the 338 * spatial. 339 * 340 * @return the Spatial's world scale factor. 341 */ 342 public Vector3f getWorldScale() { 343 checkDoTransformUpdate(); 344 return worldTransform.getScale(); 345 } 346 347 /** 348 * <code>getWorldTransform</code> retrieves the world transformation 349 * of the spatial. 350 * 351 * @return the world transform. 352 */ 353 public Transform getWorldTransform() { 354 checkDoTransformUpdate(); 355 return worldTransform; 356 } 357 358 /** 359 * <code>rotateUpTo</code> is a utility function that alters the 360 * local rotation to point the Y axis in the direction given by newUp. 361 * 362 * @param newUp 363 * the up vector to use - assumed to be a unit vector. 364 */ 365 public void rotateUpTo(Vector3f newUp) { 366 TempVars vars = TempVars.get(); 367 368 Vector3f compVecA = vars.vect1; 369 Quaternion q = vars.quat1; 370 371 // First figure out the current up vector. 372 Vector3f upY = compVecA.set(Vector3f.UNIT_Y); 373 Quaternion rot = localTransform.getRotation(); 374 rot.multLocal(upY); 375 376 // get angle between vectors 377 float angle = upY.angleBetween(newUp); 378 379 // figure out rotation axis by taking cross product 380 Vector3f rotAxis = upY.crossLocal(newUp).normalizeLocal(); 381 382 // Build a rotation quat and apply current local rotation. 383 q.fromAngleNormalAxis(angle, rotAxis); 384 q.mult(rot, rot); 385 386 vars.release(); 387 388 setTransformRefresh(); 389 } 390 391 /** 392 * <code>lookAt</code> is a convenience method for auto-setting the local 393 * rotation based on a position and an up vector. It computes the rotation 394 * to transform the z-axis to point onto 'position' and the y-axis to 'up'. 395 * Unlike {@link Quaternion#lookAt(com.jme3.math.Vector3f, com.jme3.math.Vector3f) } 396 * this method takes a world position to look at and not a relative direction. 397 * 398 * @param position 399 * where to look at in terms of world coordinates 400 * @param upVector 401 * a vector indicating the (local) up direction. (typically {0, 402 * 1, 0} in jME.) 403 */ 404 public void lookAt(Vector3f position, Vector3f upVector) { 405 Vector3f worldTranslation = getWorldTranslation(); 406 407 TempVars vars = TempVars.get(); 408 409 Vector3f compVecA = vars.vect4; 410 vars.release(); 411 412 compVecA.set(position).subtractLocal(worldTranslation); 413 getLocalRotation().lookAt(compVecA, upVector); 414 415 setTransformRefresh(); 416 } 417 418 /** 419 * Should be overridden by Node and Geometry. 420 */ 421 protected void updateWorldBound() { 422 // the world bound of a leaf is the same as it's model bound 423 // for a node, the world bound is a combination of all it's children 424 // bounds 425 // -> handled by subclass 426 refreshFlags &= ~RF_BOUND; 427 } 428 429 protected void updateWorldLightList() { 430 if (parent == null) { 431 worldLights.update(localLights, null); 432 refreshFlags &= ~RF_LIGHTLIST; 433 } else { 434 if ((parent.refreshFlags & RF_LIGHTLIST) == 0) { 435 worldLights.update(localLights, parent.worldLights); 436 refreshFlags &= ~RF_LIGHTLIST; 437 } else { 438 assert false; 439 } 440 } 441 } 442 443 /** 444 * Should only be called from updateGeometricState(). 445 * In most cases should not be subclassed. 446 */ 447 protected void updateWorldTransforms() { 448 if (parent == null) { 449 worldTransform.set(localTransform); 450 refreshFlags &= ~RF_TRANSFORM; 451 } else { 452 // check if transform for parent is updated 453 assert ((parent.refreshFlags & RF_TRANSFORM) == 0); 454 worldTransform.set(localTransform); 455 worldTransform.combineWithParent(parent.worldTransform); 456 refreshFlags &= ~RF_TRANSFORM; 457 } 458 } 459 460 /** 461 * Computes the world transform of this Spatial in the most 462 * efficient manner possible. 463 */ 464 void checkDoTransformUpdate() { 465 if ((refreshFlags & RF_TRANSFORM) == 0) { 466 return; 467 } 468 469 if (parent == null) { 470 worldTransform.set(localTransform); 471 refreshFlags &= ~RF_TRANSFORM; 472 } else { 473 TempVars vars = TempVars.get(); 474 475 Spatial[] stack = vars.spatialStack; 476 Spatial rootNode = this; 477 int i = 0; 478 while (true) { 479 Spatial hisParent = rootNode.parent; 480 if (hisParent == null) { 481 rootNode.worldTransform.set(rootNode.localTransform); 482 rootNode.refreshFlags &= ~RF_TRANSFORM; 483 i--; 484 break; 485 } 486 487 stack[i] = rootNode; 488 489 if ((hisParent.refreshFlags & RF_TRANSFORM) == 0) { 490 break; 491 } 492 493 rootNode = hisParent; 494 i++; 495 } 496 497 vars.release(); 498 499 for (int j = i; j >= 0; j--) { 500 rootNode = stack[j]; 501 //rootNode.worldTransform.set(rootNode.localTransform); 502 //rootNode.worldTransform.combineWithParent(rootNode.parent.worldTransform); 503 //rootNode.refreshFlags &= ~RF_TRANSFORM; 504 rootNode.updateWorldTransforms(); 505 } 506 } 507 } 508 509 /** 510 * Computes this Spatial's world bounding volume in the most efficient 511 * manner possible. 512 */ 513 void checkDoBoundUpdate() { 514 if ((refreshFlags & RF_BOUND) == 0) { 515 return; 516 } 517 518 checkDoTransformUpdate(); 519 520 // Go to children recursively and update their bound 521 if (this instanceof Node) { 522 Node node = (Node) this; 523 int len = node.getQuantity(); 524 for (int i = 0; i < len; i++) { 525 Spatial child = node.getChild(i); 526 child.checkDoBoundUpdate(); 527 } 528 } 529 530 // All children's bounds have been updated. Update my own now. 531 updateWorldBound(); 532 } 533 534 private void runControlUpdate(float tpf) { 535 if (controls.isEmpty()) { 536 return; 537 } 538 539 for (Control c : controls.getArray()) { 540 c.update(tpf); 541 } 542 } 543 544 /** 545 * Called when the Spatial is about to be rendered, to notify 546 * controls attached to this Spatial using the Control.render() method. 547 * 548 * @param rm The RenderManager rendering the Spatial. 549 * @param vp The ViewPort to which the Spatial is being rendered to. 550 * 551 * @see Spatial#addControl(com.jme3.scene.control.Control) 552 * @see Spatial#getControl(java.lang.Class) 553 */ 554 public void runControlRender(RenderManager rm, ViewPort vp) { 555 if (controls.isEmpty()) { 556 return; 557 } 558 559 for (Control c : controls.getArray()) { 560 c.render(rm, vp); 561 } 562 } 563 564 /** 565 * Add a control to the list of controls. 566 * @param control The control to add. 567 * 568 * @see Spatial#removeControl(java.lang.Class) 569 */ 570 public void addControl(Control control) { 571 controls.add(control); 572 control.setSpatial(this); 573 } 574 575 /** 576 * Removes the first control that is an instance of the given class. 577 * 578 * @see Spatial#addControl(com.jme3.scene.control.Control) 579 */ 580 public void removeControl(Class<? extends Control> controlType) { 581 for (int i = 0; i < controls.size(); i++) { 582 if (controlType.isAssignableFrom(controls.get(i).getClass())) { 583 Control control = controls.remove(i); 584 control.setSpatial(null); 585 } 586 } 587 } 588 589 /** 590 * Removes the given control from this spatial's controls. 591 * 592 * @param control The control to remove 593 * @return True if the control was successfuly removed. False if 594 * the control is not assigned to this spatial. 595 * 596 * @see Spatial#addControl(com.jme3.scene.control.Control) 597 */ 598 public boolean removeControl(Control control) { 599 boolean result = controls.remove(control); 600 if (result) { 601 control.setSpatial(null); 602 } 603 604 return result; 605 } 606 607 /** 608 * Returns the first control that is an instance of the given class, 609 * or null if no such control exists. 610 * 611 * @param controlType The superclass of the control to look for. 612 * @return The first instance in the list of the controlType class, or null. 613 * 614 * @see Spatial#addControl(com.jme3.scene.control.Control) 615 */ 616 public <T extends Control> T getControl(Class<T> controlType) { 617 for (Control c : controls.getArray()) { 618 if (controlType.isAssignableFrom(c.getClass())) { 619 return (T) c; 620 } 621 } 622 return null; 623 } 624 625 /** 626 * Returns the control at the given index in the list. 627 * 628 * @param index The index of the control in the list to find. 629 * @return The control at the given index. 630 * 631 * @throws IndexOutOfBoundsException 632 * If the index is outside the range [0, getNumControls()-1] 633 * 634 * @see Spatial#addControl(com.jme3.scene.control.Control) 635 */ 636 public Control getControl(int index) { 637 return controls.get(index); 638 } 639 640 /** 641 * @return The number of controls attached to this Spatial. 642 * @see Spatial#addControl(com.jme3.scene.control.Control) 643 * @see Spatial#removeControl(java.lang.Class) 644 */ 645 public int getNumControls() { 646 return controls.size(); 647 } 648 649 /** 650 * <code>updateLogicalState</code> calls the <code>update()</code> method 651 * for all controls attached to this Spatial. 652 * 653 * @param tpf Time per frame. 654 * 655 * @see Spatial#addControl(com.jme3.scene.control.Control) 656 */ 657 public void updateLogicalState(float tpf) { 658 runControlUpdate(tpf); 659 } 660 661 /** 662 * <code>updateGeometricState</code> updates the lightlist, 663 * computes the world transforms, and computes the world bounds 664 * for this Spatial. 665 * Calling this when the Spatial is attached to a node 666 * will cause undefined results. User code should only call this 667 * method on Spatials having no parent. 668 * 669 * @see Spatial#getWorldLightList() 670 * @see Spatial#getWorldTransform() 671 * @see Spatial#getWorldBound() 672 */ 673 public void updateGeometricState() { 674 // assume that this Spatial is a leaf, a proper implementation 675 // for this method should be provided by Node. 676 677 // NOTE: Update world transforms first because 678 // bound transform depends on them. 679 if ((refreshFlags & RF_LIGHTLIST) != 0) { 680 updateWorldLightList(); 681 } 682 if ((refreshFlags & RF_TRANSFORM) != 0) { 683 updateWorldTransforms(); 684 } 685 if ((refreshFlags & RF_BOUND) != 0) { 686 updateWorldBound(); 687 } 688 689 assert refreshFlags == 0; 690 } 691 692 /** 693 * Convert a vector (in) from this spatials' local coordinate space to world 694 * coordinate space. 695 * 696 * @param in 697 * vector to read from 698 * @param store 699 * where to write the result (null to create a new vector, may be 700 * same as in) 701 * @return the result (store) 702 */ 703 public Vector3f localToWorld(final Vector3f in, Vector3f store) { 704 checkDoTransformUpdate(); 705 return worldTransform.transformVector(in, store); 706 } 707 708 /** 709 * Convert a vector (in) from world coordinate space to this spatials' local 710 * coordinate space. 711 * 712 * @param in 713 * vector to read from 714 * @param store 715 * where to write the result 716 * @return the result (store) 717 */ 718 public Vector3f worldToLocal(final Vector3f in, final Vector3f store) { 719 checkDoTransformUpdate(); 720 return worldTransform.transformInverseVector(in, store); 721 } 722 723 /** 724 * <code>getParent</code> retrieves this node's parent. If the parent is 725 * null this is the root node. 726 * 727 * @return the parent of this node. 728 */ 729 public Node getParent() { 730 return parent; 731 } 732 733 /** 734 * Called by {@link Node#attachChild(Spatial)} and 735 * {@link Node#detachChild(Spatial)} - don't call directly. 736 * <code>setParent</code> sets the parent of this node. 737 * 738 * @param parent 739 * the parent of this node. 740 */ 741 protected void setParent(Node parent) { 742 this.parent = parent; 743 } 744 745 /** 746 * <code>removeFromParent</code> removes this Spatial from it's parent. 747 * 748 * @return true if it has a parent and performed the remove. 749 */ 750 public boolean removeFromParent() { 751 if (parent != null) { 752 parent.detachChild(this); 753 return true; 754 } 755 return false; 756 } 757 758 /** 759 * determines if the provided Node is the parent, or parent's parent, etc. of this Spatial. 760 * 761 * @param ancestor 762 * the ancestor object to look for. 763 * @return true if the ancestor is found, false otherwise. 764 */ 765 public boolean hasAncestor(Node ancestor) { 766 if (parent == null) { 767 return false; 768 } else if (parent.equals(ancestor)) { 769 return true; 770 } else { 771 return parent.hasAncestor(ancestor); 772 } 773 } 774 775 /** 776 * <code>getLocalRotation</code> retrieves the local rotation of this 777 * node. 778 * 779 * @return the local rotation of this node. 780 */ 781 public Quaternion getLocalRotation() { 782 return localTransform.getRotation(); 783 } 784 785 /** 786 * <code>setLocalRotation</code> sets the local rotation of this node 787 * by using a {@link Matrix3f}. 788 * 789 * @param rotation 790 * the new local rotation. 791 */ 792 public void setLocalRotation(Matrix3f rotation) { 793 localTransform.getRotation().fromRotationMatrix(rotation); 794 setTransformRefresh(); 795 } 796 797 /** 798 * <code>setLocalRotation</code> sets the local rotation of this node. 799 * 800 * @param quaternion 801 * the new local rotation. 802 */ 803 public void setLocalRotation(Quaternion quaternion) { 804 localTransform.setRotation(quaternion); 805 setTransformRefresh(); 806 } 807 808 /** 809 * <code>getLocalScale</code> retrieves the local scale of this node. 810 * 811 * @return the local scale of this node. 812 */ 813 public Vector3f getLocalScale() { 814 return localTransform.getScale(); 815 } 816 817 /** 818 * <code>setLocalScale</code> sets the local scale of this node. 819 * 820 * @param localScale 821 * the new local scale, applied to x, y and z 822 */ 823 public void setLocalScale(float localScale) { 824 localTransform.setScale(localScale); 825 setTransformRefresh(); 826 } 827 828 /** 829 * <code>setLocalScale</code> sets the local scale of this node. 830 */ 831 public void setLocalScale(float x, float y, float z) { 832 localTransform.setScale(x, y, z); 833 setTransformRefresh(); 834 } 835 836 /** 837 * <code>setLocalScale</code> sets the local scale of this node. 838 * 839 * @param localScale 840 * the new local scale. 841 */ 842 public void setLocalScale(Vector3f localScale) { 843 localTransform.setScale(localScale); 844 setTransformRefresh(); 845 } 846 847 /** 848 * <code>getLocalTranslation</code> retrieves the local translation of 849 * this node. 850 * 851 * @return the local translation of this node. 852 */ 853 public Vector3f getLocalTranslation() { 854 return localTransform.getTranslation(); 855 } 856 857 /** 858 * <code>setLocalTranslation</code> sets the local translation of this 859 * spatial. 860 * 861 * @param localTranslation 862 * the local translation of this spatial. 863 */ 864 public void setLocalTranslation(Vector3f localTranslation) { 865 this.localTransform.setTranslation(localTranslation); 866 setTransformRefresh(); 867 } 868 869 /** 870 * <code>setLocalTranslation</code> sets the local translation of this 871 * spatial. 872 */ 873 public void setLocalTranslation(float x, float y, float z) { 874 this.localTransform.setTranslation(x, y, z); 875 setTransformRefresh(); 876 } 877 878 /** 879 * <code>setLocalTransform</code> sets the local transform of this 880 * spatial. 881 */ 882 public void setLocalTransform(Transform t) { 883 this.localTransform.set(t); 884 setTransformRefresh(); 885 } 886 887 /** 888 * <code>getLocalTransform</code> retrieves the local transform of 889 * this spatial. 890 * 891 * @return the local transform of this spatial. 892 */ 893 public Transform getLocalTransform() { 894 return localTransform; 895 } 896 897 /** 898 * Applies the given material to the Spatial, this will propagate the 899 * material down to the geometries in the scene graph. 900 * 901 * @param material The material to set. 902 */ 903 public void setMaterial(Material material) { 904 } 905 906 /** 907 * <code>addLight</code> adds the given light to the Spatial; causing 908 * all child Spatials to be effected by it. 909 * 910 * @param light The light to add. 911 */ 912 public void addLight(Light light) { 913 localLights.add(light); 914 setLightListRefresh(); 915 } 916 917 /** 918 * <code>removeLight</code> removes the given light from the Spatial. 919 * 920 * @param light The light to remove. 921 * @see Spatial#addLight(com.jme3.light.Light) 922 */ 923 public void removeLight(Light light) { 924 localLights.remove(light); 925 setLightListRefresh(); 926 } 927 928 /** 929 * Translates the spatial by the given translation vector. 930 * 931 * @return The spatial on which this method is called, e.g <code>this</code>. 932 */ 933 public Spatial move(float x, float y, float z) { 934 this.localTransform.getTranslation().addLocal(x, y, z); 935 setTransformRefresh(); 936 937 return this; 938 } 939 940 /** 941 * Translates the spatial by the given translation vector. 942 * 943 * @return The spatial on which this method is called, e.g <code>this</code>. 944 */ 945 public Spatial move(Vector3f offset) { 946 this.localTransform.getTranslation().addLocal(offset); 947 setTransformRefresh(); 948 949 return this; 950 } 951 952 /** 953 * Scales the spatial by the given value 954 * 955 * @return The spatial on which this method is called, e.g <code>this</code>. 956 */ 957 public Spatial scale(float s) { 958 return scale(s, s, s); 959 } 960 961 /** 962 * Scales the spatial by the given scale vector. 963 * 964 * @return The spatial on which this method is called, e.g <code>this</code>. 965 */ 966 public Spatial scale(float x, float y, float z) { 967 this.localTransform.getScale().multLocal(x, y, z); 968 setTransformRefresh(); 969 970 return this; 971 } 972 973 /** 974 * Rotates the spatial by the given rotation. 975 * 976 * @return The spatial on which this method is called, e.g <code>this</code>. 977 */ 978 public Spatial rotate(Quaternion rot) { 979 this.localTransform.getRotation().multLocal(rot); 980 setTransformRefresh(); 981 982 return this; 983 } 984 985 /** 986 * Rotates the spatial by the yaw, roll and pitch angles (in radians), 987 * in the local coordinate space. 988 * 989 * @return The spatial on which this method is called, e.g <code>this</code>. 990 */ 991 public Spatial rotate(float yaw, float roll, float pitch) { 992 TempVars vars = TempVars.get(); 993 Quaternion q = vars.quat1; 994 q.fromAngles(yaw, roll, pitch); 995 rotate(q); 996 vars.release(); 997 998 return this; 999 } 1000 1001 /** 1002 * Centers the spatial in the origin of the world bound. 1003 * @return The spatial on which this method is called, e.g <code>this</code>. 1004 */ 1005 public Spatial center() { 1006 Vector3f worldTrans = getWorldTranslation(); 1007 Vector3f worldCenter = getWorldBound().getCenter(); 1008 1009 Vector3f absTrans = worldTrans.subtract(worldCenter); 1010 setLocalTranslation(absTrans); 1011 1012 return this; 1013 } 1014 1015 /** 1016 * @see #setCullHint(CullHint) 1017 * @return the cull mode of this spatial, or if set to CullHint.Inherit, 1018 * the cullmode of it's parent. 1019 */ 1020 public CullHint getCullHint() { 1021 if (cullHint != CullHint.Inherit) { 1022 return cullHint; 1023 } else if (parent != null) { 1024 return parent.getCullHint(); 1025 } else { 1026 return CullHint.Dynamic; 1027 } 1028 } 1029 1030 public BatchHint getBatchHint() { 1031 if (batchHint != BatchHint.Inherit) { 1032 return batchHint; 1033 } else if (parent != null) { 1034 return parent.getBatchHint(); 1035 } else { 1036 return BatchHint.Always; 1037 } 1038 } 1039 1040 /** 1041 * Returns this spatial's renderqueue bucket. If the mode is set to inherit, 1042 * then the spatial gets its renderqueue bucket from its parent. 1043 * 1044 * @return The spatial's current renderqueue mode. 1045 */ 1046 public RenderQueue.Bucket getQueueBucket() { 1047 if (queueBucket != RenderQueue.Bucket.Inherit) { 1048 return queueBucket; 1049 } else if (parent != null) { 1050 return parent.getQueueBucket(); 1051 } else { 1052 return RenderQueue.Bucket.Opaque; 1053 } 1054 } 1055 1056 /** 1057 * @return The shadow mode of this spatial, if the local shadow 1058 * mode is set to inherit, then the parent's shadow mode is returned. 1059 * 1060 * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) 1061 * @see ShadowMode 1062 */ 1063 public RenderQueue.ShadowMode getShadowMode() { 1064 if (shadowMode != RenderQueue.ShadowMode.Inherit) { 1065 return shadowMode; 1066 } else if (parent != null) { 1067 return parent.getShadowMode(); 1068 } else { 1069 return ShadowMode.Off; 1070 } 1071 } 1072 1073 /** 1074 * Sets the level of detail to use when rendering this Spatial, 1075 * this call propagates to all geometries under this Spatial. 1076 * 1077 * @param lod The lod level to set. 1078 */ 1079 public void setLodLevel(int lod) { 1080 } 1081 1082 /** 1083 * <code>updateModelBound</code> recalculates the bounding object for this 1084 * Spatial. 1085 */ 1086 public abstract void updateModelBound(); 1087 1088 /** 1089 * <code>setModelBound</code> sets the bounding object for this Spatial. 1090 * 1091 * @param modelBound 1092 * the bounding object for this spatial. 1093 */ 1094 public abstract void setModelBound(BoundingVolume modelBound); 1095 1096 /** 1097 * @return The sum of all verticies under this Spatial. 1098 */ 1099 public abstract int getVertexCount(); 1100 1101 /** 1102 * @return The sum of all triangles under this Spatial. 1103 */ 1104 public abstract int getTriangleCount(); 1105 1106 /** 1107 * @return A clone of this Spatial, the scene graph in its entirety 1108 * is cloned and can be altered independently of the original scene graph. 1109 * 1110 * Note that meshes of geometries are not cloned explicitly, they 1111 * are shared if static, or specially cloned if animated. 1112 * 1113 * All controls will be cloned using the Control.cloneForSpatial method 1114 * on the clone. 1115 * 1116 * @see Mesh#cloneForAnim() 1117 */ 1118 public Spatial clone(boolean cloneMaterial) { 1119 try { 1120 Spatial clone = (Spatial) super.clone(); 1121 if (worldBound != null) { 1122 clone.worldBound = worldBound.clone(); 1123 } 1124 clone.worldLights = worldLights.clone(); 1125 clone.localLights = localLights.clone(); 1126 1127 // Set the new owner of the light lists 1128 clone.localLights.setOwner(clone); 1129 clone.worldLights.setOwner(clone); 1130 1131 // No need to force cloned to update. 1132 // This node already has the refresh flags 1133 // set below so it will have to update anyway. 1134 clone.worldTransform = worldTransform.clone(); 1135 clone.localTransform = localTransform.clone(); 1136 1137 if (clone instanceof Node) { 1138 Node node = (Node) this; 1139 Node nodeClone = (Node) clone; 1140 nodeClone.children = new SafeArrayList<Spatial>(Spatial.class); 1141 for (Spatial child : node.children) { 1142 Spatial childClone = child.clone(cloneMaterial); 1143 childClone.parent = nodeClone; 1144 nodeClone.children.add(childClone); 1145 } 1146 } 1147 1148 clone.parent = null; 1149 clone.setBoundRefresh(); 1150 clone.setTransformRefresh(); 1151 clone.setLightListRefresh(); 1152 1153 clone.controls = new SafeArrayList<Control>(Control.class); 1154 for (int i = 0; i < controls.size(); i++) { 1155 clone.controls.add(controls.get(i).cloneForSpatial(clone)); 1156 } 1157 1158 if (userData != null) { 1159 clone.userData = (HashMap<String, Savable>) userData.clone(); 1160 } 1161 1162 return clone; 1163 } catch (CloneNotSupportedException ex) { 1164 throw new AssertionError(); 1165 } 1166 } 1167 1168 /** 1169 * @return A clone of this Spatial, the scene graph in its entirety 1170 * is cloned and can be altered independently of the original scene graph. 1171 * 1172 * Note that meshes of geometries are not cloned explicitly, they 1173 * are shared if static, or specially cloned if animated. 1174 * 1175 * All controls will be cloned using the Control.cloneForSpatial method 1176 * on the clone. 1177 * 1178 * @see Mesh#cloneForAnim() 1179 */ 1180 @Override 1181 public Spatial clone() { 1182 return clone(true); 1183 } 1184 1185 /** 1186 * @return Similar to Spatial.clone() except will create a deep clone 1187 * of all geometry's meshes, normally this method shouldn't be used 1188 * instead use Spatial.clone() 1189 * 1190 * @see Spatial#clone() 1191 */ 1192 public abstract Spatial deepClone(); 1193 1194 public void setUserData(String key, Object data) { 1195 if (userData == null) { 1196 userData = new HashMap<String, Savable>(); 1197 } 1198 1199 if (data instanceof Savable) { 1200 userData.put(key, (Savable) data); 1201 } else { 1202 userData.put(key, new UserData(UserData.getObjectType(data), data)); 1203 } 1204 } 1205 1206 @SuppressWarnings("unchecked") 1207 public <T> T getUserData(String key) { 1208 if (userData == null) { 1209 return null; 1210 } 1211 1212 Savable s = userData.get(key); 1213 if (s instanceof UserData) { 1214 return (T) ((UserData) s).getValue(); 1215 } else { 1216 return (T) s; 1217 } 1218 } 1219 1220 public Collection<String> getUserDataKeys() { 1221 if (userData != null) { 1222 return userData.keySet(); 1223 } 1224 1225 return Collections.EMPTY_SET; 1226 } 1227 1228 /** 1229 * Note that we are <i>matching</i> the pattern, therefore the pattern 1230 * must match the entire pattern (i.e. it behaves as if it is sandwiched 1231 * between "^" and "$"). 1232 * You can set regex modes, like case insensitivity, by using the (?X) 1233 * or (?X:Y) constructs. 1234 * 1235 * @param spatialSubclass Subclass which this must implement. 1236 * Null causes all Spatials to qualify. 1237 * @param nameRegex Regular expression to match this name against. 1238 * Null causes all Names to qualify. 1239 * @return true if this implements the specified class and this's name 1240 * matches the specified pattern. 1241 * 1242 * @see java.util.regex.Pattern 1243 */ 1244 public boolean matches(Class<? extends Spatial> spatialSubclass, 1245 String nameRegex) { 1246 if (spatialSubclass != null && !spatialSubclass.isInstance(this)) { 1247 return false; 1248 } 1249 1250 if (nameRegex != null && (name == null || !name.matches(nameRegex))) { 1251 return false; 1252 } 1253 1254 return true; 1255 } 1256 1257 public void write(JmeExporter ex) throws IOException { 1258 OutputCapsule capsule = ex.getCapsule(this); 1259 capsule.write(name, "name", null); 1260 capsule.write(worldBound, "world_bound", null); 1261 capsule.write(cullHint, "cull_mode", CullHint.Inherit); 1262 capsule.write(batchHint, "batch_hint", BatchHint.Inherit); 1263 capsule.write(queueBucket, "queue", RenderQueue.Bucket.Inherit); 1264 capsule.write(shadowMode, "shadow_mode", ShadowMode.Inherit); 1265 capsule.write(localTransform, "transform", Transform.IDENTITY); 1266 capsule.write(localLights, "lights", null); 1267 1268 // Shallow clone the controls array to convert its type. 1269 capsule.writeSavableArrayList(new ArrayList(controls), "controlsList", null); 1270 capsule.writeStringSavableMap(userData, "user_data", null); 1271 } 1272 1273 public void read(JmeImporter im) throws IOException { 1274 InputCapsule ic = im.getCapsule(this); 1275 1276 name = ic.readString("name", null); 1277 worldBound = (BoundingVolume) ic.readSavable("world_bound", null); 1278 cullHint = ic.readEnum("cull_mode", CullHint.class, CullHint.Inherit); 1279 batchHint = ic.readEnum("batch_hint", BatchHint.class, BatchHint.Inherit); 1280 queueBucket = ic.readEnum("queue", RenderQueue.Bucket.class, 1281 RenderQueue.Bucket.Inherit); 1282 shadowMode = ic.readEnum("shadow_mode", ShadowMode.class, 1283 ShadowMode.Inherit); 1284 1285 localTransform = (Transform) ic.readSavable("transform", Transform.IDENTITY); 1286 1287 localLights = (LightList) ic.readSavable("lights", null); 1288 localLights.setOwner(this); 1289 1290 //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split 1291 //the AnimControl creates the SkeletonControl for old files and add it to the spatial. 1292 //The SkeletonControl must be the last in the stack so we add the list of all other control before it. 1293 //When backward compatibility won't be needed anymore this can be replaced by : 1294 //controls = ic.readSavableArrayList("controlsList", null)); 1295 controls.addAll(0, ic.readSavableArrayList("controlsList", null)); 1296 1297 userData = (HashMap<String, Savable>) ic.readStringSavableMap("user_data", null); 1298 } 1299 1300 /** 1301 * <code>getWorldBound</code> retrieves the world bound at this node 1302 * level. 1303 * 1304 * @return the world bound at this level. 1305 */ 1306 public BoundingVolume getWorldBound() { 1307 checkDoBoundUpdate(); 1308 return worldBound; 1309 } 1310 1311 /** 1312 * <code>setCullHint</code> sets how scene culling should work on this 1313 * spatial during drawing. NOTE: You must set this AFTER attaching to a 1314 * parent or it will be reset with the parent's cullMode value. 1315 * 1316 * @param hint 1317 * one of CullHint.Dynamic, CullHint.Always, CullHint.Inherit or 1318 * CullHint.Never 1319 */ 1320 public void setCullHint(CullHint hint) { 1321 cullHint = hint; 1322 } 1323 1324 /** 1325 * <code>setBatchHint</code> sets how batching should work on this 1326 * spatial. NOTE: You must set this AFTER attaching to a 1327 * parent or it will be reset with the parent's cullMode value. 1328 * 1329 * @param hint 1330 * one of BatchHint.Never, BatchHint.Always, BatchHint.Inherit 1331 */ 1332 public void setBatchHint(BatchHint hint) { 1333 batchHint = hint; 1334 } 1335 1336 /** 1337 * @return the cullmode set on this Spatial 1338 */ 1339 public CullHint getLocalCullHint() { 1340 return cullHint; 1341 } 1342 1343 /** 1344 * @return the batchHint set on this Spatial 1345 */ 1346 public BatchHint getLocalBatchHint() { 1347 return batchHint; 1348 } 1349 1350 /** 1351 * <code>setQueueBucket</code> determines at what phase of the 1352 * rendering process this Spatial will rendered. See the 1353 * {@link Bucket} enum for an explanation of the various 1354 * render queue buckets. 1355 * 1356 * @param queueBucket 1357 * The bucket to use for this Spatial. 1358 */ 1359 public void setQueueBucket(RenderQueue.Bucket queueBucket) { 1360 this.queueBucket = queueBucket; 1361 } 1362 1363 /** 1364 * Sets the shadow mode of the spatial 1365 * The shadow mode determines how the spatial should be shadowed, 1366 * when a shadowing technique is used. See the 1367 * documentation for the class {@link ShadowMode} for more information. 1368 * 1369 * @see ShadowMode 1370 * 1371 * @param shadowMode The local shadow mode to set. 1372 */ 1373 public void setShadowMode(RenderQueue.ShadowMode shadowMode) { 1374 this.shadowMode = shadowMode; 1375 } 1376 1377 /** 1378 * @return The locally set queue bucket mode 1379 * 1380 * @see Spatial#setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket) 1381 */ 1382 public RenderQueue.Bucket getLocalQueueBucket() { 1383 return queueBucket; 1384 } 1385 1386 /** 1387 * @return The locally set shadow mode 1388 * 1389 * @see Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) 1390 */ 1391 public RenderQueue.ShadowMode getLocalShadowMode() { 1392 return shadowMode; 1393 } 1394 1395 /** 1396 * Returns this spatial's last frustum intersection result. This int is set 1397 * when a check is made to determine if the bounds of the object fall inside 1398 * a camera's frustum. If a parent is found to fall outside the frustum, the 1399 * value for this spatial will not be updated. 1400 * 1401 * @return The spatial's last frustum intersection result. 1402 */ 1403 public Camera.FrustumIntersect getLastFrustumIntersection() { 1404 return frustrumIntersects; 1405 } 1406 1407 /** 1408 * Overrides the last intersection result. This is useful for operations 1409 * that want to start rendering at the middle of a scene tree and don't want 1410 * the parent of that node to influence culling. 1411 * 1412 * @param intersects 1413 * the new value 1414 */ 1415 public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) { 1416 frustrumIntersects = intersects; 1417 } 1418 1419 /** 1420 * Returns the Spatial's name followed by the class of the spatial <br> 1421 * Example: "MyNode (com.jme3.scene.Spatial) 1422 * 1423 * @return Spatial's name followed by the class of the Spatial 1424 */ 1425 @Override 1426 public String toString() { 1427 return name + " (" + this.getClass().getSimpleName() + ')'; 1428 } 1429 1430 /** 1431 * Creates a transform matrix that will convert from this spatials' 1432 * local coordinate space to the world coordinate space 1433 * based on the world transform. 1434 * 1435 * @param store Matrix where to store the result, if null, a new one 1436 * will be created and returned. 1437 * 1438 * @return store if not null, otherwise, a new matrix containing the result. 1439 * 1440 * @see Spatial#getWorldTransform() 1441 */ 1442 public Matrix4f getLocalToWorldMatrix(Matrix4f store) { 1443 if (store == null) { 1444 store = new Matrix4f(); 1445 } else { 1446 store.loadIdentity(); 1447 } 1448 // multiply with scale first, then rotate, finally translate (cf. 1449 // Eberly) 1450 store.scale(getWorldScale()); 1451 store.multLocal(getWorldRotation()); 1452 store.setTranslation(getWorldTranslation()); 1453 return store; 1454 } 1455 1456 /** 1457 * Visit each scene graph element ordered by DFS 1458 * @param visitor 1459 */ 1460 public abstract void depthFirstTraversal(SceneGraphVisitor visitor); 1461 1462 /** 1463 * Visit each scene graph element ordered by BFS 1464 * @param visitor 1465 */ 1466 public void breadthFirstTraversal(SceneGraphVisitor visitor) { 1467 Queue<Spatial> queue = new LinkedList<Spatial>(); 1468 queue.add(this); 1469 1470 while (!queue.isEmpty()) { 1471 Spatial s = queue.poll(); 1472 visitor.visit(s); 1473 s.breadthFirstTraversal(visitor, queue); 1474 } 1475 } 1476 1477 protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue); 1478 } 1479