• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
Spatial()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      */
Spatial(String name)183     public Spatial(String name) {
184         this();
185         this.name = name;
186     }
187 
setKey(AssetKey key)188     public void setKey(AssetKey key) {
189         this.key = key;
190     }
191 
getKey()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      */
setTransformRefresh()200     protected void setTransformRefresh() {
201         refreshFlags |= RF_TRANSFORM;
202         setBoundRefresh();
203     }
204 
setLightListRefresh()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      */
setBoundRefresh()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      */
checkCulling(Camera cam)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      */
setName(String name)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      */
getName()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      */
getLocalLightList()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      */
getWorldLightList()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      */
getWorldRotation()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      */
getWorldTranslation()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      */
getWorldScale()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      */
getWorldTransform()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      */
rotateUpTo(Vector3f newUp)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      */
lookAt(Vector3f position, Vector3f upVector)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      */
updateWorldBound()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 
updateWorldLightList()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      */
updateWorldTransforms()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      */
checkDoTransformUpdate()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      */
checkDoBoundUpdate()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 
runControlUpdate(float tpf)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      */
runControlRender(RenderManager rm, ViewPort vp)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      */
addControl(Control control)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      */
removeControl(Class<? extends Control> controlType)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      */
removeControl(Control control)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      */
getControl(Class<T> controlType)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      */
getControl(int index)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      */
getNumControls()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      */
updateLogicalState(float tpf)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      */
updateGeometricState()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      */
localToWorld(final Vector3f in, Vector3f store)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      */
worldToLocal(final Vector3f in, final Vector3f store)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      */
getParent()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      */
setParent(Node parent)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      */
removeFromParent()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      */
hasAncestor(Node ancestor)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      */
getLocalRotation()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      */
setLocalRotation(Matrix3f rotation)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      */
setLocalRotation(Quaternion quaternion)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      */
getLocalScale()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      */
setLocalScale(float localScale)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      */
setLocalScale(float x, float y, float z)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      */
setLocalScale(Vector3f localScale)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      */
getLocalTranslation()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      */
setLocalTranslation(Vector3f localTranslation)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      */
setLocalTranslation(float x, float y, float z)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      */
setLocalTransform(Transform t)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      */
getLocalTransform()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      */
setMaterial(Material material)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      */
addLight(Light light)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      */
removeLight(Light light)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      */
move(float x, float y, float z)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      */
move(Vector3f offset)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      */
scale(float s)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      */
scale(float x, float y, float z)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      */
rotate(Quaternion rot)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      */
rotate(float yaw, float roll, float pitch)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      */
center()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      */
getCullHint()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 
getBatchHint()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      */
getQueueBucket()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      */
getShadowMode()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      */
setLodLevel(int lod)1079     public void setLodLevel(int lod) {
1080     }
1081 
1082     /**
1083      * <code>updateModelBound</code> recalculates the bounding object for this
1084      * Spatial.
1085      */
updateModelBound()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      */
setModelBound(BoundingVolume modelBound)1094     public abstract void setModelBound(BoundingVolume modelBound);
1095 
1096     /**
1097      * @return The sum of all verticies under this Spatial.
1098      */
getVertexCount()1099     public abstract int getVertexCount();
1100 
1101     /**
1102      * @return The sum of all triangles under this Spatial.
1103      */
getTriangleCount()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      */
clone(boolean cloneMaterial)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
clone()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      */
deepClone()1192     public abstract Spatial deepClone();
1193 
setUserData(String key, Object data)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")
getUserData(String key)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 
getUserDataKeys()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      */
matches(Class<? extends Spatial> spatialSubclass, String nameRegex)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 
write(JmeExporter ex)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 
read(JmeImporter im)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      */
getWorldBound()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      */
setCullHint(CullHint hint)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      */
setBatchHint(BatchHint hint)1332     public void setBatchHint(BatchHint hint) {
1333         batchHint = hint;
1334     }
1335 
1336     /**
1337      * @return the cullmode set on this Spatial
1338      */
getLocalCullHint()1339     public CullHint getLocalCullHint() {
1340         return cullHint;
1341     }
1342 
1343     /**
1344      * @return the batchHint set on this Spatial
1345      */
getLocalBatchHint()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      */
setQueueBucket(RenderQueue.Bucket queueBucket)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      */
setShadowMode(RenderQueue.ShadowMode shadowMode)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      */
getLocalQueueBucket()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      */
getLocalShadowMode()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      */
getLastFrustumIntersection()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      */
setLastFrustumIntersection(Camera.FrustumIntersect intersects)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
toString()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      */
getLocalToWorldMatrix(Matrix4f store)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      */
depthFirstTraversal(SceneGraphVisitor visitor)1460     public abstract void depthFirstTraversal(SceneGraphVisitor visitor);
1461 
1462     /**
1463      * Visit each scene graph element ordered by BFS
1464      * @param visitor
1465      */
breadthFirstTraversal(SceneGraphVisitor visitor)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 
breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue)1477     protected abstract void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue);
1478 }
1479