/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the 'Xith3D Project Group' nor the names of its * contributors may be used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.scenegraph; import java.util.Stack; import org.jagatoo.logging.ProfileTimer; import org.openmali.spatial.SpatialNode; import org.openmali.spatial.bounds.BoundingBox; import org.openmali.spatial.bounds.BoundingPolytope; import org.openmali.spatial.bounds.BoundingSphere; import org.openmali.spatial.bounds.Bounds; import org.openmali.spatial.bounds.BoundsType; import org.xith3d.effects.EffectFactory; import org.xith3d.effects.shadows.ShadowFactory; import org.xith3d.render.Canvas3D; import org.xith3d.render.CanvasPeer; import org.xith3d.render.Clipper; import org.xith3d.render.ClipperInfo; import org.xith3d.render.ScissorRect; import org.xith3d.render.preprocessing.OrderedState; import org.xith3d.scenegraph.modifications.ScenegraphModificationsListener; import org.xith3d.scenegraph.traversal.DetailedTraversalCallback; import org.xith3d.scenegraph.traversal.TraversalCallback; import org.xith3d.scenegraph.utils.CopyListener; import org.xith3d.utility.logging.X3DLog; /** * Node is the base class for all node objects in a scene graph. * * @author Scott Shaver * @author David Yazel * @author Marvin Froehlich (aka Qudus) */ public abstract class Node extends SceneGraphObject implements SpatialNode { private final boolean is_billboard = ( this instanceof Billboard ); private final boolean is_updatableNode = ( this instanceof UpdatableNode ); private static boolean defaultPickable = true; private transient boolean pickable = defaultPickable; private transient boolean renderable = true; /** * The parent of this node. */ private transient GroupNode parent = null; protected transient boolean boundsAutoCompute = true; private transient boolean ignoreBounds = false; /** * Transform group for this node. The universe maintains this transform when * the graph is parsed. We are putting a pointer here so that we do not need * to replicate a transform3d all over the place */ protected TransformGroup transformGroup = null; private ScenegraphModificationsListener modListener = null; /** * The following two pointers help us maintain information regarding the * ordered state of this node. The ordered child pointers travel back up * through parents linked from ordered group to ordered group. Each child of * an ordered group has a unique OrderedState. */ private Node orderedChild = null; private OrderedState orderedState = null; /** * Defines an occluder for this portion this sub-scene on down. If this node * is within view and an occluder is set then it will be passed to the * renderer for shadow rendering. */ private boolean isOccluder = false; private Object shadowAttachment = null; private final InheritedNodeAttributes inheritedNodeAttribs = new InheritedNodeAttributes(); /** * The bounds of this object. */ protected Bounds bounds; protected Bounds untransformedBounds; private BoundsType boundsType = null; private static BoundsTypeHint boundsTypeHint = BoundsTypeHint.SPHERE; protected BoundsTypeHint instanceBoundsTypeHint; private Bounds worldBounds; private boolean showBounds = false; protected boolean boundsDirty = false; protected static boolean globalIgnoreBounds = false; private static Stack< Boolean > globalIgnoreBoundsStack = new Stack< Boolean >(); static { globalIgnoreBoundsStack.push( globalIgnoreBounds ); } private Object treeCell = null; public void setTreeCell( Object treeCell ) { this.treeCell = treeCell; } public Object getTreeCell() { return ( treeCell ); } public static void setBoundsTypeHint( BoundsTypeHint bth ) { if ( bth == null ) throw new IllegalArgumentException( "BoundsTypeHint must not be null" ); boundsTypeHint = bth; } public static BoundsTypeHint getBoundsTypeHint() { return ( boundsTypeHint ); } public final boolean isBillboard() { return ( is_billboard ); } public final boolean isUpdatableNode() { return ( is_updatableNode ); } /** * Is a Node object pickable when created? */ public static boolean getDefaultPickable() { return ( defaultPickable ); } /** * Sets whether this Node is attended by picking algorithms. * * @param value if false, this Node is ignored by picking algorithms */ public void setPickable( boolean value ) { pickable = value; if ( modListener != null ) modListener.onNodePropertyChanged( this, "Pickable" ); } /** * @return whether this Node is attended by picking algorithms */ public final boolean isPickable() { return ( pickable ); } /** * Sets whether this Node is attended by picking algorithms, and all * its children * * @param pickable if false, this Node and all its children, recursively, * are ignored by picking algorithms */ public final static void setPickableRecursive( Node node, boolean pickable ) { node.setPickable( pickable ); if ( node instanceof GroupNode ) { final GroupNode group = (GroupNode)node; final int numChildren = group.numChildren(); for ( int i = 0; i < numChildren; i++ ) { Node child = group.getChild( i ); setPickableRecursive( child, pickable ); } } } /** * Sets whether this Node is attended by picking algorithms, and all * its children. * * @param pickable if false, this Node and all its children, recursively * are ignored by picking algorithms */ public final void setPickableRecursive( boolean pickable ) { setPickableRecursive( this, pickable ); } /** * Sets whether this Node is excluded from rendering or not. * * @param value if false, this Node is excluded from rendering */ public final void setRenderable( boolean value ) { renderable = value; if ( modListener != null ) modListener.onNodePropertyChanged( this, "Renderable" ); } /** * @return false, if this Node is excluded from rendering */ public final boolean isRenderable() { return ( renderable ); } public final InheritedNodeAttributes getInheritedNodeAttributes() { return ( inheritedNodeAttribs ); } public void setModListener( ScenegraphModificationsListener modListener ) { this.modListener = modListener; } public final ScenegraphModificationsListener getModListener() { return ( modListener ); } public void setIgnoreBounds( boolean ignoreBounds ) { if ( ignoreBounds == this.ignoreBounds ) return; this.ignoreBounds = ignoreBounds; //setBoundsDirty(); } public final boolean isIgnoreBounds() { return ( ignoreBounds ); } public static void setGlobalIgnoreBounds( boolean val ) { globalIgnoreBounds = val; } public static void pushGlobalIgnoreBounds( boolean val ) { globalIgnoreBoundsStack.push( val ); globalIgnoreBounds = val; } public static boolean popGlobalIgnoreBounds() { if ( globalIgnoreBoundsStack.size() > 1 ) // leave at least one element on the Stack globalIgnoreBoundsStack.pop(); /* else globalIgnoreBoundsStack.peek(); */ globalIgnoreBounds = globalIgnoreBoundsStack.peek(); return ( globalIgnoreBounds ); } protected void mergeInheritedNodes( InheritedNodeAttributes in ) { if ( in != this.getInheritedNodeAttributes() ) this.getInheritedNodeAttributes().merge( in ); } protected void unmergeInheritedNodes( InheritedNodeAttributes in ) { if ( in != this.getInheritedNodeAttributes() ) this.getInheritedNodeAttributes().unmerge( in ); } protected void unmergeInheritedLight( Light light ) { this.getInheritedNodeAttributes().removeLight( light ); } protected void unmergeInheritedFog( Fog fog ) { this.getInheritedNodeAttributes().removeFog( fog ); } protected void mergeInheritedScissorRect( ScissorRect scissorRect ) { this.getInheritedNodeAttributes().setScissorRect( scissorRect ); } /** * * @param clipper * @param clipperInfo */ protected void mergeInheritedClipper( Clipper clipper, ClipperInfo clipperInfo ) { this.getInheritedNodeAttributes().setClipper( clipperInfo ); } public final void setBoundsAutoCompute( boolean autocompute ) { this.boundsAutoCompute = autocompute; } public final boolean getBoundsAutoCompute() { return ( boundsAutoCompute ); } public final void getWorldTransform( Transform3D transform3D ) { if ( transformGroup != null ) { transform3D.set( transformGroup.getWorldTransform() ); } else { transform3D.set( Transform3D.IDENTITY ); } } public Transform3D getWorldTransform() { if ( transformGroup == null ) return ( Transform3D.IDENTITY ); return ( transformGroup.getInlinedWorldTransform() ); } public final boolean getShowBounds() { return ( showBounds ); } public void setShowBounds( boolean show ) { showBounds = show; } /** * Sets the bounds for this object. */ public void setBounds( Bounds bounds ) { /* if (this.bounds != null) { if (bounds != null) this.bounds.set( bounds ); else this.bounds = null; } else if (bounds != null) { if (bounds instanceof BoundingBox) this.bounds = new BoundingBox( bounds ); else this.bounds = new BoundingSphere( bounds ); } */ this.bounds = bounds; if ( bounds != null ) { if ( untransformedBounds == null ) { if ( bounds instanceof BoundingBox ) untransformedBounds = new BoundingBox( bounds ); else if ( bounds instanceof BoundingPolytope ) untransformedBounds = new BoundingPolytope( bounds ); else untransformedBounds = new BoundingSphere( bounds ); } else { if ( bounds instanceof BoundingBox ) { if ( untransformedBounds instanceof BoundingBox ) untransformedBounds.set( bounds ); else untransformedBounds = new BoundingBox( bounds ); } else if ( bounds instanceof BoundingPolytope ) { if ( untransformedBounds instanceof BoundingPolytope ) untransformedBounds.set( bounds ); else untransformedBounds = new BoundingPolytope( bounds ); } else //if (bounds instanceof BoundingSphere) { //if (untransformedBounds instanceof BoundingSphere) untransformedBounds.set( bounds ); //else // untransformedBounds = new BoundingSphere( bounds ); } } if ( worldBounds == null ) { if ( bounds instanceof BoundingBox ) worldBounds = new BoundingBox( bounds ); else if ( bounds instanceof BoundingPolytope ) worldBounds = new BoundingPolytope( bounds ); else worldBounds = new BoundingSphere( bounds ); } else { if ( bounds instanceof BoundingBox ) { if ( worldBounds instanceof BoundingBox ) worldBounds.set( bounds ); else worldBounds = new BoundingBox( bounds ); } else if ( bounds instanceof BoundingPolytope ) { if ( worldBounds instanceof BoundingPolytope ) worldBounds.set( bounds ); else worldBounds = new BoundingPolytope( bounds ); } else //if (bounds instanceof BoundingSphere) { //if (worldBounds instanceof BoundingSphere) worldBounds.set( bounds ); //else // worldBounds = new BoundingSphere( bounds ); } } } if ( bounds == null ) boundsType = null; else if ( bounds instanceof BoundingBox ) boundsType = BoundsType.AABB; else if ( bounds instanceof BoundingPolytope ) boundsType = BoundsType.POLYTOPE; else boundsType = BoundsType.SPHERE; this.setBoundsDirtyUpward(); //if ( setBoundsTriggersUpdate ) { updateBounds( true ); } } public final BoundsType getBoundsType() { return ( boundsType ); } public final Bounds getWorldBounds() { return ( worldBounds ); } /** * @return the bounds for this object. */ public final Bounds getBounds() { return ( bounds ); } /** * @return the parent of this Node or returns null if there is no parent. */ public final GroupNode getParent() { return ( parent ); } /** * @return the root BranchGroup of this Node. */ public final BranchGroup getRoot() { if ( getParent() != null ) { return ( getParent().getRoot() ); } else if ( this instanceof BranchGroup ) { return ( (BranchGroup)this ); } else { return ( null ); } } public final Node getOrderedChild() { return ( orderedChild ); } public final void setOrderedChild( Node orderedChild ) { this.orderedChild = orderedChild; } public final OrderedState getOrderedState() { if ( orderedState != null ) { return ( orderedState ); } else if ( orderedChild != null ) { return orderedChild.getOrderedState(); } else { return ( null ); } } protected final void setTransformGroup( TransformGroup tg ) { transformGroup = tg; } /** * Returns the TransformGroup, which defines the Transform of this Node. So * it does <b>not</b> return itself, if it is a TransformGroup. * * @return the TransformGroup which defines this nodes Transform, null if no * TransformGroup is its parent */ public final TransformGroup getTransformGroup() { return ( transformGroup ); } protected void setBoundsDirty() { setBoundsDirtyUpward(); } protected void setBoundsDirtyUpward() { boundsDirty = true; if ( parent != null ) { parent.setBoundsDirtyUpward(); } } /** * Detaches the Node from its parent and sets the node to not live. */ public final void detach() { if ( SceneGraph.CHECK_FOR_ILLEGAL_MODIFICATION ) SceneGraph.checkForIllegalModification( this ); GroupNode parent = getParent(); if ( parent != null ) parent.removeChild( this ); } /** * Sets the parent for this Node object. If the parent is live then this * node and any children it has are made live. If the parent is null then * this node and any children it has are made not live. * * @throws IllegalSceneGraphOperation if the Node already has a parent */ protected void setParent( GroupNode parent ) { // Checks for violation of the scenegraph's directed acyclic graph // constraint (setting the parent of a node when it already has one) if ( ( this.parent != null ) && ( parent != null ) ) { throw new IllegalSceneGraphOperation( "Illegal attempt to set the parent of this Node (name: '" + this.getName() + "', " + this + ") as it already has a parent (existing parent name: '" + this.parent.getName() + "', " + this.parent + ") and this would violate the directed acyclic graph constraint of the scenegraph." ); } ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "Node::setParent()" ); this.parent = parent; if ( ( parent == null ) || !parent.isLive() ) { this.setLive( false ); } else if ( parent.isLive() ) { this.setLive( true ); } // update the transformations and bounds this.updateTransformGroup(); this.updateWorldTransform(); this.updateOrderedChild(); if ( parent != null ) { if ( !parent.boundsDirty ) parent.expandBounds( this, true ); else parent.updateBoundsCheap( true, false, true, false ); } ProfileTimer.endProfile(); } public void printBounds( boolean childrenToo ) { printBounds( childrenToo, 0 ); } private void printBounds( boolean childrenToo, int indent ) { for ( int i = 0; i < indent; i++ ) { System.out.print( " " ); } System.out.println( getName() + " " + this.getWorldBounds() ); if ( isLive() ) System.out.println( " (live)" ); if ( childrenToo ) { if ( this instanceof GroupNode ) { final GroupNode group = (GroupNode)this; final int numChildren = group.numChildren(); Bounds childrenBounds[] = new Bounds[ numChildren ]; for ( int i = 0; i < numChildren; i++ ) { Node n = group.getChild( i ); n.printBounds( childrenToo, indent + 1 ); childrenBounds[ i ] = n.getBounds(); } bounds.set( childrenBounds ); } } } /** * When autocompute is turned off we need to take the bounds and assign * them to all the children. * * @param b the Bounds to assign recursively to all children */ private final void setAllWorldBounds( Bounds b ) { worldBounds.set( b ); } /** * Update bounds sets the bounds and virutal world bounds of all nodes * recursively. * * @param onlyDirty Only update those nodes which have been marked as dirty. * @param childrenToo if false, the children bounds are not updated * @param parentToo if false, the parent Node will not be updated * @param onlyWorld if true, only the vworld bounds are updated */ protected void updateBoundsCheap( boolean onlyDirty, boolean childrenToo, boolean parentToo, boolean onlyWorld ) { // if we already have the bounds then return if ( ( isIgnoreBounds() ) || ( !boundsDirty && onlyDirty ) ) { return; } worldBounds.set( untransformedBounds ); worldBounds.transform( getWorldTransform().getMatrix4f() ); if ( !boundsAutoCompute ) { if ( this instanceof GroupNode ) { final GroupNode group = (GroupNode)this; final int numChildren = group.numChildren(); for ( int i = 0; i < numChildren; i++ ) { group.getChild( i ).setAllWorldBounds( worldBounds ); } } } boundsDirty = false; final GroupNode parent = getParent(); if ( ( parentToo ) && ( parent != null ) ) { parent.boundsDirty = true; parent.updateBoundsCheap( onlyDirty, false, parentToo, onlyWorld ); } } /** * Update bounds sets the bounds and virutal world bounds of all nodes * recursively. * * @param onlyDirty Only update those nodes which have been marked as dirty. */ public void updateBounds( boolean onlyDirty ) { updateBoundsCheap( onlyDirty, true, true, false ); } public final void setIsOccluder( boolean isOccluder ) { final boolean changed = this.isOccluder != isOccluder; this.isOccluder = isOccluder; if ( changed ) { final EffectFactory effFact = EffectFactory.getInstance(); if ( effFact != null ) { final ShadowFactory shadowFact = effFact.getShadowFactory(); if ( shadowFact != null ) shadowFact.onOccluderStateChanged( this, isOccluder ); } } } public final boolean isOccluder() { return ( isOccluder ); } public final void setShadowAttachment( Object shadowAttachment ) { this.shadowAttachment = shadowAttachment; } public final Object getShadowAttachment() { return ( shadowAttachment ); } /** * If this is a TansformGroup, then it will multiply the transform against * the parent transform and store it into the world-transform matrix. * * <b>Never use this method on your own! It's just for internal use.</b> */ public void updateWorldTransform() { ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "Node::updateWorldTransform" ); if ( this instanceof TransformGroup ) { final TransformGroup tg = (TransformGroup)this; final Transform3D t = tg.getWorldTransform(); if ( getTransformGroup() == null ) { t.set( tg.getTransform() ); } else { //getTransformGroup().getWorldTransform().transform( tg.getTransform(), t ); t.set( tg.getTransform() ); getTransformGroup().getWorldTransform().transform( t ); } } if ( this instanceof GroupNode ) { final GroupNode g = (GroupNode)this; final int n = g.numChildren(); for ( int i = 0; i < n; i++ ) { g.getChild( i ).updateWorldTransform(); } } ProfileTimer.endProfile(); } /** * <b>Never use this method on your own! It's just for internal use.</b> */ public final void updateTransformGroup() { ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "Node::updateTransformGroup" ); if (parent == null) { setTransformGroup( null ); } else if (parent instanceof TransformGroup) { setTransformGroup( (TransformGroup)parent ); } else { setTransformGroup( parent.getTransformGroup() ); } /* * if (transformGroup==null) System.out.println("node "+getName()+" has * no tg"); else System.out.println("node "+getName()+" has tg * "+transformGroup.getName()); */ if ( this instanceof GroupNode ) { final GroupNode g = (GroupNode)this; final int n = g.numChildren(); for ( int i = 0; i < n; i++ ) { g.getChild( i ).updateTransformGroup(); } } ProfileTimer.endProfile(); } /** * Steps down from the nodes. */ protected final void updateOrderedChild() { ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "Node::updateOrderedChild" ); if ( parent == null ) { setOrderedChild( null ); } else if ( parent instanceof OrderedGroup ) { // this is an ordered child, so we need to // build a new ordered state down one depth OrderedState os = parent.getOrderedState(); if ( os == null ) { os = new OrderedState(); } else { os = (OrderedState)os.clone(); } // drop down a depth of one os.addDepth( ( (OrderedGroup)parent ).allocateOrderedId() ); // Set newly created ordered state as the ordered state for this node orderedState = os; setOrderedChild( this ); } else { // otherwise just take the ordered child of the parent setOrderedChild( parent.getOrderedChild() ); } if ( this instanceof GroupNode ) { // if this is a group, step through the children final GroupNode group = (GroupNode)this; final int numChildren = group.numChildren(); for ( int i = 0; i < numChildren; i++ ) { group.getChild( i ).updateOrderedChild(); } } ProfileTimer.endProfile(); } /** * Creates a new instance of the Node. */ public Node cloneNode( boolean forceDuplicate ) { try { Node node = (Node)getClass().newInstance(); node.duplicateNode( this, forceDuplicate ); return ( node ); } catch ( Exception ex ) { RuntimeException runtimeException = new RuntimeException(); runtimeException.initCause( ex ); throw runtimeException; } } /** * Copies all the node information from the originalNode into the current * node. This method is called from the cloneNode method which is called * from the cloneTree method. This method is empty - it doesn't do anything * * @param originalNode * @param forceDuplicate */ public void duplicateNode( Node originalNode, boolean forceDuplicate ) { } /** * Creates a shared copy of this Node. A shared copy is one where the * geometry and appearance is shared, but everything else is copied. This * is a replacement for shared groups because of performance considerations. * If you are loading the same model many times then this can save on memory * and load times. The only allowable within the subtree are groups and * shapes. This also copies a shapes bounds and turns autocomute off so that * it is fast to insert the model into the scene. * * @param listener * * @return a shared copy of this Node * * @see #absorbDetails(Node) */ public Node sharedCopy( CopyListener listener ) { throw new UnsupportedOperationException( "sharedCopy is not yet implemented for " + this.getClass().getName() ); } /** * Creates a shared copy of this Node. A shared copy is one where the * geometry and appearance is shared, but everything else is copied. This * is a replacement for shared groups because of performance considerations. * If you are loading the same model many times then this can save on memory * and load times. The only allowable within the subtree are groups and * shapes. This also copies a shapes bounds and turns autocomute off so that * it is fast to insert the model into the scene. * * @return a shared copy of this Node * * @see #absorbDetails(Node) */ public Node sharedCopy() { return ( sharedCopy( (CopyListener)null ) ); } /** * Turns the receiver into a shared copy of the node parameter. This is * precisely the inverse operation of sharedCopy; i.e., shared copy creates * a new node, but absorbDetails turns the current node into a copy exactly * equivalent to what would be returned, if you created a new copy via the * sharedCopy() method. * * @param node the node to copy. * * @see #sharedCopy() */ public void absorbDetails( Node node ) { throw new UnsupportedOperationException( "absorbDetails is not yet implemented for " + this.getClass().getName() ); } /** * Each Node object will be pickable by default following this static flag. * * @param value pickable by default? */ public static void setDefaultPickable( boolean value ) { defaultPickable = value; } /** * This method frees OpenGL resources (names) for all Nodes in the traversal * of this Node(-Group). * * @param canvasPeer */ public abstract void freeOpenGLResources( CanvasPeer canvasPeer ); /** * This method frees OpenGL resources (names) for all Nodes in the traversal * of this Node(-Group). * * @param canvas */ public final void freeOpenGLResources( Canvas3D canvas ) { if ( canvas.getPeer() == null ) throw new Error( "The given Canvas3D is not linked to a CanvasPeer." ); freeOpenGLResources( canvas.getPeer() ); } protected final String getIndentString( int indent ) { char[] spaces = new char[ indent * 2 ]; for ( int i = 0; i < spaces.length; i++ ) { spaces[ i ] = ' '; } return ( new String( spaces ) ); } protected abstract void dump( int indent ); /** * Traverses the scenegraph from this node on. If this Node is a Group it * will recusively run through each child. * * @param callback the listener is notified of any traversed Node on the way * @return if false, the whole traversal will stop */ public boolean traverse( TraversalCallback callback ) { return ( callback.traversalOperation( this ) ); } /** * Traverses the scenegraph from this node on. If this Node is a Group it * will recusively run through each child. * * @param callback the listener is notified of any traversed Node on the way * @return if false, the whole traversal will stop */ public abstract boolean traverse( DetailedTraversalCallback callback ); /** * Constructs a new Node object. */ protected Node( boolean initializeBounds ) { super(); if ( initializeBounds ) { instanceBoundsTypeHint = boundsTypeHint; ignoreBounds = globalIgnoreBounds; if ( !globalIgnoreBounds ) { switch ( instanceBoundsTypeHint ) { case AABB: bounds = new BoundingBox(); untransformedBounds = new BoundingBox(); worldBounds = new BoundingBox(); boundsType = BoundsType.AABB; break; case POLYTOPE: throw new Error( "BoundingPolytopes are not yet supported" ); case NONE: bounds = null; untransformedBounds = null; worldBounds = null; boundsType = BoundsType.POLYTOPE; break; case SPHERE: default: bounds = new BoundingSphere(); untransformedBounds = new BoundingSphere(); worldBounds = new BoundingSphere(); boundsType = BoundsType.SPHERE; } } } else { instanceBoundsTypeHint = null; ignoreBounds = true; bounds = null; untransformedBounds = null; worldBounds = null; boundsType = null; } } /** * Constructs a new Node object. */ public Node() { this( true ); } }