Home | History | Annotate | Download | only in scene
      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