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 
33 package com.jme3.scene;
34 
35 import com.jme3.bounding.BoundingVolume;
36 import com.jme3.collision.Collidable;
37 import com.jme3.collision.CollisionResults;
38 import com.jme3.export.JmeExporter;
39 import com.jme3.export.JmeImporter;
40 import com.jme3.export.Savable;
41 import com.jme3.material.Material;
42 import com.jme3.util.SafeArrayList;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Queue;
47 import java.util.logging.Level;
48 import java.util.logging.Logger;
49 
50 
51 /**
52  * <code>Node</code> defines an internal node of a scene graph. The internal
53  * node maintains a collection of children and handles merging said children
54  * into a single bound to allow for very fast culling of multiple nodes. Node
55  * allows for any number of children to be attached.
56  *
57  * @author Mark Powell
58  * @author Gregg Patton
59  * @author Joshua Slack
60  */
61 public class Node extends Spatial implements Savable {
62 
63     private static final Logger logger = Logger.getLogger(Node.class.getName());
64 
65 
66     /**
67      * This node's children.
68      */
69     protected SafeArrayList<Spatial> children = new SafeArrayList<Spatial>(Spatial.class);
70 
71     /**
72      * Serialization only. Do not use.
73      */
Node()74     public Node() {
75     }
76 
77     /**
78      * Constructor instantiates a new <code>Node</code> with a default empty
79      * list for containing children.
80      *
81      * @param name
82      *            the name of the scene element. This is required for
83      *            identification and comparision purposes.
84      */
Node(String name)85     public Node(String name) {
86         super(name);
87     }
88 
89     /**
90      *
91      * <code>getQuantity</code> returns the number of children this node
92      * maintains.
93      *
94      * @return the number of children this node maintains.
95      */
getQuantity()96     public int getQuantity() {
97         return children.size();
98     }
99 
100     @Override
setTransformRefresh()101     protected void setTransformRefresh(){
102         super.setTransformRefresh();
103         for (Spatial child : children.getArray()){
104             if ((child.refreshFlags & RF_TRANSFORM) != 0)
105                 continue;
106 
107             child.setTransformRefresh();
108         }
109     }
110 
111     @Override
setLightListRefresh()112     protected void setLightListRefresh(){
113         super.setLightListRefresh();
114         for (Spatial child : children.getArray()){
115             if ((child.refreshFlags & RF_LIGHTLIST) != 0)
116                 continue;
117 
118             child.setLightListRefresh();
119         }
120     }
121 
122     @Override
updateWorldBound()123     protected void updateWorldBound(){
124         super.updateWorldBound();
125 
126         // for a node, the world bound is a combination of all it's children
127         // bounds
128         BoundingVolume resultBound = null;
129         for (Spatial child : children.getArray()) {
130             // child bound is assumed to be updated
131             assert (child.refreshFlags & RF_BOUND) == 0;
132             if (resultBound != null) {
133                 // merge current world bound with child world bound
134                 resultBound.mergeLocal(child.getWorldBound());
135             } else {
136                 // set world bound to first non-null child world bound
137                 if (child.getWorldBound() != null) {
138                     resultBound = child.getWorldBound().clone(this.worldBound);
139                 }
140             }
141         }
142         this.worldBound = resultBound;
143     }
144 
145     @Override
updateLogicalState(float tpf)146     public void updateLogicalState(float tpf){
147         super.updateLogicalState(tpf);
148 
149         if (children.isEmpty()) {
150             return;
151         }
152 
153         for (Spatial child : children.getArray()) {
154             child.updateLogicalState(tpf);
155         }
156     }
157 
158     @Override
updateGeometricState()159     public void updateGeometricState(){
160         if ((refreshFlags & RF_LIGHTLIST) != 0){
161             updateWorldLightList();
162         }
163 
164         if ((refreshFlags & RF_TRANSFORM) != 0){
165             // combine with parent transforms- same for all spatial
166             // subclasses.
167             updateWorldTransforms();
168         }
169 
170         if (!children.isEmpty()) {
171             // the important part- make sure child geometric state is refreshed
172             // first before updating own world bound. This saves
173             // a round-trip later on.
174             // NOTE 9/19/09
175             // Although it does save a round trip,
176             for (Spatial child : children.getArray()) {
177                 child.updateGeometricState();
178             }
179         }
180 
181         if ((refreshFlags & RF_BOUND) != 0){
182             updateWorldBound();
183         }
184 
185         assert refreshFlags == 0;
186     }
187 
188     /**
189      * <code>getTriangleCount</code> returns the number of triangles contained
190      * in all sub-branches of this node that contain geometry.
191      *
192      * @return the triangle count of this branch.
193      */
194     @Override
getTriangleCount()195     public int getTriangleCount() {
196         int count = 0;
197         if(children != null) {
198             for(int i = 0; i < children.size(); i++) {
199                 count += children.get(i).getTriangleCount();
200             }
201         }
202 
203         return count;
204     }
205 
206     /**
207      * <code>getVertexCount</code> returns the number of vertices contained
208      * in all sub-branches of this node that contain geometry.
209      *
210      * @return the vertex count of this branch.
211      */
212     @Override
getVertexCount()213     public int getVertexCount() {
214         int count = 0;
215         if(children != null) {
216             for(int i = 0; i < children.size(); i++) {
217                count += children.get(i).getVertexCount();
218             }
219         }
220 
221         return count;
222     }
223 
224     /**
225      * <code>attachChild</code> attaches a child to this node. This node
226      * becomes the child's parent. The current number of children maintained is
227      * returned.
228      * <br>
229      * If the child already had a parent it is detached from that former parent.
230      *
231      * @param child
232      *            the child to attach to this node.
233      * @return the number of children maintained by this node.
234      * @throws NullPointerException If child is null.
235      */
attachChild(Spatial child)236     public int attachChild(Spatial child) {
237         if (child == null)
238             throw new NullPointerException();
239 
240         if (child.getParent() != this && child != this) {
241             if (child.getParent() != null) {
242                 child.getParent().detachChild(child);
243             }
244             child.setParent(this);
245             children.add(child);
246 
247             // XXX: Not entirely correct? Forces bound update up the
248             // tree stemming from the attached child. Also forces
249             // transform update down the tree-
250             child.setTransformRefresh();
251             child.setLightListRefresh();
252             if (logger.isLoggable(Level.INFO)) {
253                 logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
254                         new Object[]{child.getName(), getName()});
255             }
256         }
257 
258         return children.size();
259     }
260 
261     /**
262      *
263      * <code>attachChildAt</code> attaches a child to this node at an index. This node
264      * becomes the child's parent. The current number of children maintained is
265      * returned.
266      * <br>
267      * If the child already had a parent it is detached from that former parent.
268      *
269      * @param child
270      *            the child to attach to this node.
271      * @return the number of children maintained by this node.
272      * @throws NullPointerException if child is null.
273      */
attachChildAt(Spatial child, int index)274     public int attachChildAt(Spatial child, int index) {
275         if (child == null)
276             throw new NullPointerException();
277 
278         if (child.getParent() != this && child != this) {
279             if (child.getParent() != null) {
280                 child.getParent().detachChild(child);
281             }
282             child.setParent(this);
283             children.add(index, child);
284             child.setTransformRefresh();
285             child.setLightListRefresh();
286             if (logger.isLoggable(Level.INFO)) {
287                 logger.log(Level.INFO,"Child ({0}) attached to this node ({1})",
288                         new Object[]{child.getName(), getName()});
289             }
290         }
291 
292         return children.size();
293     }
294 
295     /**
296      * <code>detachChild</code> removes a given child from the node's list.
297      * This child will no longer be maintained.
298      *
299      * @param child
300      *            the child to remove.
301      * @return the index the child was at. -1 if the child was not in the list.
302      */
detachChild(Spatial child)303     public int detachChild(Spatial child) {
304         if (child == null)
305             throw new NullPointerException();
306 
307         if (child.getParent() == this) {
308             int index = children.indexOf(child);
309             if (index != -1) {
310                 detachChildAt(index);
311             }
312             return index;
313         }
314 
315         return -1;
316     }
317 
318     /**
319      * <code>detachChild</code> removes a given child from the node's list.
320      * This child will no longe be maintained. Only the first child with a
321      * matching name is removed.
322      *
323      * @param childName
324      *            the child to remove.
325      * @return the index the child was at. -1 if the child was not in the list.
326      */
detachChildNamed(String childName)327     public int detachChildNamed(String childName) {
328         if (childName == null)
329             throw new NullPointerException();
330 
331         for (int x = 0, max = children.size(); x < max; x++) {
332             Spatial child =  children.get(x);
333             if (childName.equals(child.getName())) {
334                 detachChildAt( x );
335                 return x;
336             }
337         }
338         return -1;
339     }
340 
341     /**
342      *
343      * <code>detachChildAt</code> removes a child at a given index. That child
344      * is returned for saving purposes.
345      *
346      * @param index
347      *            the index of the child to be removed.
348      * @return the child at the supplied index.
349      */
detachChildAt(int index)350     public Spatial detachChildAt(int index) {
351         Spatial child =  children.remove(index);
352         if ( child != null ) {
353             child.setParent( null );
354             logger.log(Level.INFO, "{0}: Child removed.", this.toString());
355 
356             // since a child with a bound was detached;
357             // our own bound will probably change.
358             setBoundRefresh();
359 
360             // our world transform no longer influences the child.
361             // XXX: Not neccessary? Since child will have transform updated
362             // when attached anyway.
363             child.setTransformRefresh();
364             // lights are also inherited from parent
365             child.setLightListRefresh();
366         }
367         return child;
368     }
369 
370     /**
371      *
372      * <code>detachAllChildren</code> removes all children attached to this
373      * node.
374      */
detachAllChildren()375     public void detachAllChildren() {
376         for ( int i = children.size() - 1; i >= 0; i-- ) {
377             detachChildAt(i);
378         }
379         logger.log(Level.INFO, "{0}: All children removed.", this.toString());
380     }
381 
382     /**
383      * <code>getChildIndex</code> returns the index of the given spatial
384      * in this node's list of children.
385      * @param sp
386      *          The spatial to look up
387      * @return
388      *          The index of the spatial in the node's children, or -1
389      *          if the spatial is not attached to this node
390      */
getChildIndex(Spatial sp)391     public int getChildIndex(Spatial sp) {
392         return children.indexOf(sp);
393     }
394 
395     /**
396      * More efficient than e.g detaching and attaching as no updates are needed.
397      *
398      * @param index1 The index of the first child to swap
399      * @param index2 The index of the second child to swap
400      */
swapChildren(int index1, int index2)401     public void swapChildren(int index1, int index2) {
402         Spatial c2 =  children.get(index2);
403         Spatial c1 =  children.remove(index1);
404         children.add(index1, c2);
405         children.remove(index2);
406         children.add(index2, c1);
407     }
408 
409     /**
410      *
411      * <code>getChild</code> returns a child at a given index.
412      *
413      * @param i
414      *            the index to retrieve the child from.
415      * @return the child at a specified index.
416      */
getChild(int i)417     public Spatial getChild(int i) {
418         return children.get(i);
419     }
420 
421     /**
422      * <code>getChild</code> returns the first child found with exactly the
423      * given name (case sensitive.)
424      *
425      * @param name
426      *            the name of the child to retrieve. If null, we'll return null.
427      * @return the child if found, or null.
428      */
getChild(String name)429     public Spatial getChild(String name) {
430         if (name == null)
431             return null;
432 
433         for (Spatial child : children.getArray()) {
434             if (name.equals(child.getName())) {
435                 return child;
436             } else if(child instanceof Node) {
437                 Spatial out = ((Node)child).getChild(name);
438                 if(out != null) {
439                     return out;
440                 }
441             }
442         }
443         return null;
444     }
445 
446     /**
447      * determines if the provided Spatial is contained in the children list of
448      * this node.
449      *
450      * @param spat
451      *            the child object to look for.
452      * @return true if the object is contained, false otherwise.
453      */
hasChild(Spatial spat)454     public boolean hasChild(Spatial spat) {
455         if (children.contains(spat))
456             return true;
457 
458         for (Spatial child : children.getArray()) {
459             if (child instanceof Node && ((Node) child).hasChild(spat))
460                 return true;
461         }
462 
463         return false;
464     }
465 
466     /**
467      * Returns all children to this node. Note that modifying that given
468      * list is not allowed.
469      *
470      * @return a list containing all children to this node
471      */
getChildren()472     public List<Spatial> getChildren() {
473         return children;
474     }
475 
476     @Override
setMaterial(Material mat)477     public void setMaterial(Material mat){
478         for (int i = 0; i < children.size(); i++){
479             children.get(i).setMaterial(mat);
480         }
481     }
482 
483     @Override
setLodLevel(int lod)484     public void setLodLevel(int lod){
485         super.setLodLevel(lod);
486         for (Spatial child : children.getArray()) {
487             child.setLodLevel(lod);
488         }
489     }
490 
collideWith(Collidable other, CollisionResults results)491     public int collideWith(Collidable other, CollisionResults results){
492         int total = 0;
493         for (Spatial child : children.getArray()){
494             total += child.collideWith(other, results);
495         }
496         return total;
497     }
498 
499 
500      /**
501      * Returns flat list of Spatials implementing the specified class AND
502      * with name matching the specified pattern.
503      * </P> <P>
504      * Note that we are <i>matching</i> the pattern, therefore the pattern
505      * must match the entire pattern (i.e. it behaves as if it is sandwiched
506      * between "^" and "$").
507      * You can set regex modes, like case insensitivity, by using the (?X)
508      * or (?X:Y) constructs.
509      * </P> <P>
510      * By design, it is always safe to code loops like:<CODE><PRE>
511      *     for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
512      * </PRE></CODE>
513      * </P> <P>
514      * "Descendants" does not include self, per the definition of the word.
515      * To test for descendants AND self, you must do a
516      * <code>node.matches(aClass, aRegex)</code> +
517      * <code>node.descendantMatches(aClass, aRegex)</code>.
518      * <P>
519      *
520      * @param spatialSubclass Subclass which matching Spatials must implement.
521      *                        Null causes all Spatials to qualify.
522      * @param nameRegex  Regular expression to match Spatial name against.
523      *                        Null causes all Names to qualify.
524      * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials).
525      *
526      * @see java.util.regex.Pattern
527      * @see Spatial#matches(java.lang.Class, java.lang.String)
528      */
529     @SuppressWarnings("unchecked")
descendantMatches( Class<T> spatialSubclass, String nameRegex)530     public <T extends Spatial>List<T> descendantMatches(
531             Class<T> spatialSubclass, String nameRegex) {
532         List<T> newList = new ArrayList<T>();
533         if (getQuantity() < 1) return newList;
534         for (Spatial child : getChildren()) {
535             if (child.matches(spatialSubclass, nameRegex))
536                 newList.add((T)child);
537             if (child instanceof Node)
538                 newList.addAll(((Node) child).descendantMatches(
539                         spatialSubclass, nameRegex));
540         }
541         return newList;
542     }
543 
544     /**
545      * Convenience wrapper.
546      *
547      * @see #descendantMatches(java.lang.Class, java.lang.String)
548      */
descendantMatches( Class<T> spatialSubclass)549     public <T extends Spatial>List<T> descendantMatches(
550             Class<T> spatialSubclass) {
551         return descendantMatches(spatialSubclass, null);
552     }
553 
554     /**
555      * Convenience wrapper.
556      *
557      * @see #descendantMatches(java.lang.Class, java.lang.String)
558      */
descendantMatches(String nameRegex)559     public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
560         return descendantMatches(null, nameRegex);
561     }
562 
563     @Override
clone(boolean cloneMaterials)564     public Node clone(boolean cloneMaterials){
565         Node nodeClone = (Node) super.clone(cloneMaterials);
566 //        nodeClone.children = new ArrayList<Spatial>();
567 //        for (Spatial child : children){
568 //            Spatial childClone = child.clone();
569 //            childClone.parent = nodeClone;
570 //            nodeClone.children.add(childClone);
571 //        }
572         return nodeClone;
573     }
574 
575     @Override
deepClone()576     public Spatial deepClone(){
577         Node nodeClone = (Node) super.clone();
578         nodeClone.children = new SafeArrayList<Spatial>(Spatial.class);
579         for (Spatial child : children){
580             Spatial childClone = child.deepClone();
581             childClone.parent = nodeClone;
582             nodeClone.children.add(childClone);
583         }
584         return nodeClone;
585     }
586 
587     @Override
write(JmeExporter e)588     public void write(JmeExporter e) throws IOException {
589         super.write(e);
590         e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null);
591     }
592 
593     @Override
read(JmeImporter e)594     public void read(JmeImporter e) throws IOException {
595         // XXX: Load children before loading itself!!
596         // This prevents empty children list if controls query
597         // it in Control.setSpatial().
598 
599         children = new SafeArrayList( Spatial.class,
600                                       e.getCapsule(this).readSavableArrayList("children", null) );
601 
602         // go through children and set parent to this node
603         if (children != null) {
604             for (Spatial child : children.getArray()) {
605                 child.parent = this;
606             }
607         }
608 
609         super.read(e);
610     }
611 
612     @Override
setModelBound(BoundingVolume modelBound)613     public void setModelBound(BoundingVolume modelBound) {
614         if(children != null) {
615             for (Spatial child : children.getArray()) {
616                 child.setModelBound(modelBound != null ? modelBound.clone(null) : null);
617             }
618         }
619     }
620 
621     @Override
updateModelBound()622     public void updateModelBound() {
623         if(children != null) {
624             for (Spatial child : children.getArray()) {
625                 child.updateModelBound();
626             }
627         }
628     }
629 
630     @Override
depthFirstTraversal(SceneGraphVisitor visitor)631     public void depthFirstTraversal(SceneGraphVisitor visitor) {
632         for (Spatial child : children.getArray()) {
633             child.depthFirstTraversal(visitor);
634         }
635         visitor.visit(this);
636     }
637 
638     @Override
breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue)639     protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
640         queue.addAll(children);
641     }
642 
643 }
644