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
33package com.jme3.scene;
34
35import com.jme3.bounding.BoundingVolume;
36import com.jme3.collision.Collidable;
37import com.jme3.collision.CollisionResults;
38import com.jme3.export.JmeExporter;
39import com.jme3.export.JmeImporter;
40import com.jme3.export.Savable;
41import com.jme3.material.Material;
42import com.jme3.util.SafeArrayList;
43import java.io.IOException;
44import java.util.ArrayList;
45import java.util.List;
46import java.util.Queue;
47import java.util.logging.Level;
48import 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 */
61public 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     */
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     */
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     */
96    public int getQuantity() {
97        return children.size();
98    }
99
100    @Override
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
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
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
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
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
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
472    public List<Spatial> getChildren() {
473        return children;
474    }
475
476    @Override
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
484    public void setLodLevel(int lod){
485        super.setLodLevel(lod);
486        for (Spatial child : children.getArray()) {
487            child.setLodLevel(lod);
488        }
489    }
490
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")
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     */
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     */
559    public <T extends Spatial>List<T> descendantMatches(String nameRegex) {
560        return descendantMatches(null, nameRegex);
561    }
562
563    @Override
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
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
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
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
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
622    public void updateModelBound() {
623        if(children != null) {
624            for (Spatial child : children.getArray()) {
625                child.updateModelBound();
626            }
627        }
628    }
629
630    @Override
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
639    protected void breadthFirstTraversal(SceneGraphVisitor visitor, Queue<Spatial> queue) {
640        queue.addAll(children);
641    }
642
643}
644