Home | History | Annotate | Download | only in scene
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     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      */
     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