/**
* 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.io.IOException;
import org.openmali.spatial.bounds.BoundingBox;
import org.openmali.spatial.bounds.BoundingSphere;
import org.openmali.spatial.bounds.Bounds;
import org.xith3d.effects.EffectFactory;
import org.xith3d.effects.bumpmapping.BumpMappingFactory;
import org.xith3d.effects.shadows.ShadowFactory;
import org.xith3d.render.CanvasPeer;
import org.xith3d.render.OpenGLCapabilities;
import org.xith3d.render.preprocessing.ShapeAtom;
import org.xith3d.scenegraph.modifications.ScenegraphModificationsListener;
import org.xith3d.scenegraph.traversal.DetailedTraversalCallback;
import org.xith3d.scenegraph.utils.CopyListener;
/**
* Shape3D is a class for all scene graph nodes that have no children. Leaf
* nodes specify lights, geometry, sounds, etc.
*
* @author David Yazel
* @author Marvin Froehlich (aka Qudus)
* @author Amos Wenger (aka BlueSky)
*/
public class Shape3D extends Leaf
{
private ShapeAtom atom = null;
/**
* This is the geometry for the object.
*/
private Geometry geometry = null;
/**
* This is the appearance for the object.
*/
private Appearance appearance = null;
@SuppressWarnings("unchecked")
private Comparable customComparable = null;
private boolean visible = true;
/**
* Appearance change flag
*/
private long appChangeID = -1L;
private boolean bumpMappingEnabled = false;
private boolean isShadowReceiver = false;
/**
* {@inheritDoc}
*/
@Override
public void setModListener( ScenegraphModificationsListener modListener )
{
super.setModListener( modListener );
if ( appearance != null )
appearance.setModListener( this.getModListener() );
if ( geometry != null )
geometry.setModListener( this.getModListener() );
}
/**
* Sets this Shape3D is visible or invisible.<br />
*/
public void setVisible( boolean visible )
{
this.visible = visible;
}
/**
* Checks, whether this Shape3D is visible.<br />
* <br />
* If the Appearance or the Appearance's RenderingAttributes is null or the
* RenderingAttributes are set to invisible false is returned.
*/
public final boolean isVisible()
{
//return ( visible && !( ( appearance != null ) && ( appearance.getRenderingAttributes() != null ) && ( !appearance.getRenderingAttributes().getVisible() ) ) );
return ( visible );
}
/**
* {@inheritDoc}
*/
@Override
public final void setBounds( Bounds bounds )
{
if ( getGeometry() != null )
getGeometry().setBoundsDirty();
super.setBounds( bounds );
}
/**
* {@inheritDoc}
*/
@Override
protected void updateBoundsCheap( boolean onlyDirty, boolean childrenToo, boolean parentToo, boolean onlyWorld )
{
// if we already have the bounds then return
if ( ( isIgnoreBounds() ) || ( !boundsDirty && onlyDirty ) )
{
return;
}
if ( boundsAutoCompute && !onlyWorld )
{
final Geometry geom = this.getGeometry();
if ( geom != null )
{
if ( ( geom.isBoundsDirty() ) || ( !onlyDirty ) )
{
final Bounds b = geom.getCachedBounds();
final Bounds newBounds;
if ( ( b == null ) || ( b.getType() != untransformedBounds.getType() ) )
{
if ( bounds instanceof BoundingBox )
newBounds = new BoundingBox();
//else if (bounds instanceof BoundingPolytope)
// newBounds = new BoundingPolytope();
else
//if (bounds instanceof BoundingSphere)
newBounds = new BoundingSphere();
}
else
{
newBounds = b;
}
newBounds.compute( geom );
geom.setCachedBounds( newBounds );
}
untransformedBounds.set( geom.getCachedBounds() );
bounds.set( geom.getCachedBounds() );
}
}
super.updateBoundsCheap( onlyDirty, childrenToo, parentToo, onlyWorld );
}
/**
* {@inheritDoc}
*/
@Override
public void updateBounds( boolean onlyDirty )
{
updateBoundsCheap( onlyDirty, false, true, false );
}
/**
* If this is a TransformGroupm 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>
*/
@Override
public final void updateWorldTransform()
{
super.updateWorldTransform();
}
private int passId = 0;
final void setPassId( int passId )
{
this.passId = passId;
}
final int getPassId()
{
return ( passId );
}
/**
* Sets the geometry for this object.
*
* @param geometry The new geometry
*/
public void setGeometry( Geometry geometry )
{
final boolean hasChanged = ( this.geometry != geometry );
if ( ( geometry == null ) && ( this.geometry != null ) )
this.geometry.setModListener( null );
this.geometry = geometry;
if ( geometry != null )
{
geometry.setModListener( this.getModListener() );
}
if ( ( hasChanged ) || ( ( geometry != null ) && ( geometry.isBoundsDirty() ) ) )
{
setBoundsDirty();
updateBounds( true );
}
}
/**
* @return the Geometry for this object.
*/
public Geometry getGeometry()
{
return ( geometry );
}
/**
* Sets the appearance for this object.
*/
public final void setAppearance( Appearance appearance )
{
if ( appearance == this.appearance )
return;
//this.appChangeID = -1L;
if ( ( appearance == null ) && ( this.appearance != null ) )
this.appearance.setModListener( null );
this.appearance = appearance;
if ( appearance != null )
this.appearance.setChangedRecursive( true );
if ( appearance != null )
appearance.setModListener( this.getModListener() );
}
/**
* @return the appearance for this object.
*/
public final Appearance getAppearance()
{
return ( appearance );
}
/**
* Returns this shape's Appearance, if it exists. If it doesn't exist, it
* is created depending on the <b>forceExistance</b> parameter.
*
* @param forceExistance if true, a new Appearance is created and attached,
* if it doesn't already exist.
*
* @return the appearance for this object
*/
public Appearance getAppearance( boolean forceExistance )
{
if ( ( getAppearance() == null ) && ( forceExistance ) )
{
setAppearance( new Appearance() );
}
return ( getAppearance() );
}
/**
* Creates a new appearance for this Shape3D and returns it.
*
* @return A new appearance for this Shape3D
*/
public Appearance newAppearance()
{
setAppearance( new Appearance() );
return ( getAppearance() );
}
public final boolean verifyAppChange( OpenGLCapabilities glCaps )
{
if ( appearance != null )
{
final long change_id = appearance.verifyChange( this, glCaps );
if ( change_id != appChangeID )
{
appChangeID = change_id;
return ( true );
}
}
else if ( appChangeID != -1L )
{
appChangeID = -1L;
return ( true );
}
return ( false );
}
/**
* Sets the custom comparable object.<br />
* This object can be used for shape sorting, but isn't by default.
*
* @param customComparable
*/
@SuppressWarnings("unchecked")
public void setCustomComparable( Comparable customComparable )
{
this.customComparable = customComparable;
}
/**
* Returns the custom comparable object.<br />
* This object can be used for shape sorting, but isn't by default.
*
* @return the custom comparable object.
*/
@SuppressWarnings("unchecked")
public final Comparable getCustomComparable()
{
return ( customComparable );
}
public final void setBumpMappingEnabled( boolean enabled, Texture normalMapTex )
{
this.bumpMappingEnabled = enabled;
if ( enabled && ( ( getGeometry() == null ) || ( getAppearance() == null ) ) )
{
System.err.println( "Warning: BumpMapping will fail, if this Shape doesn't have a Geometry AND an Appearance." );
}
else if ( enabled && ( getGeometry() != null ) && ( getAppearance() != null ) )
{
BumpMappingFactory bumpFactory = EffectFactory.getInstance().getBumpMappingFactory();
if ( bumpFactory != null )
{
try
{
bumpFactory.prepareForBumpMapping( this, normalMapTex );
}
catch ( IOException e )
{
e.printStackTrace();
}
}
else
{
System.err.println( "ERROR: Cannot apply BumpMapping, while no BumpMappingFactory is registered at the EffectFactory!" );
}
}
}
public final void setBumpMappingEnabled( boolean enabled, String normalMapTex )
{
BumpMappingFactory bumpFactory = EffectFactory.getInstance().getBumpMappingFactory();
if ( bumpFactory != null )
{
setBumpMappingEnabled( enabled, BumpMappingFactory.loadNormalMap( normalMapTex ) );
}
else
{
System.err.println( "ERROR: Cannot apply BumpMapping, while no BumpMappingFactory is registered at the EffectFactory!" );
}
}
public final boolean isBumpMappingEnabled()
{
return ( bumpMappingEnabled );
}
public final void setIsShadowReceiver( boolean isSR )
{
final boolean changed = this.isShadowReceiver != isSR;
this.isShadowReceiver = isSR;
if ( changed )
{
final EffectFactory effFact = EffectFactory.getInstance();
if ( effFact != null )
{
final ShadowFactory shadowFact = effFact.getShadowFactory();
if ( shadowFact != null )
shadowFact.onShadowReceiverStateChanged( this, isShadowReceiver );
}
}
}
public final boolean isShadowReceiver()
{
return ( isShadowReceiver );
}
/**
* Sets this Shape3D's RenderAtom.
*
* Do not use on your own ! For internal use only !
*
* @param atom The new atom
*/
final void setAtom( ShapeAtom atom )
{
this.atom = atom;
}
/**
* Do not use on your own ! For internal use only !
*
* @return this Shape3D's current RenderAtom
*/
final ShapeAtom getAtom()
{
return ( atom );
}
protected void copy( Shape3D dest )
{
dest.setGeometry( this.getGeometry() );
dest.setAppearance( this.getAppearance() );
dest.setBoundsAutoCompute( false );
dest.setBounds( this.getBounds() );
dest.boundsDirty = true;
dest.updateBounds( false );
dest.setPickable( this.isPickable() );
dest.setRenderable( this.isRenderable() );
dest.setName( this.getName() );
}
/**
* @return a new instance of this class. This is invoked by the sharedCopy() method.
*
* @see #sharedCopy(CopyListener)
*/
protected Shape3D newInstance()
{
boolean gib = Node.globalIgnoreBounds;
Node.globalIgnoreBounds = this.isIgnoreBounds();
Shape3D newShape = new Shape3D();
Node.globalIgnoreBounds = gib;
return ( newShape );
}
/**
* {@inheritDoc}
*/
@Override
public Shape3D sharedCopy( CopyListener listener )
{
Shape3D newShape = newInstance();
copy( newShape );
if ( listener != null )
{
listener.onNodeCopied( this, newShape, true );
}
return ( newShape );
}
/**
* {@inheritDoc}
*/
@Override
public Shape3D sharedCopy()
{
return ( (Shape3D)super.sharedCopy() );
}
/**
* {@inheritDoc}
*/
@Override
public void absorbDetails( Node node )
{
( (Shape3D)node ).copy( this );
}
/**
* {@inheritDoc}
*/
@Override
public void freeOpenGLResources( CanvasPeer canvasPeer )
{
if ( getAppearance() != null )
getAppearance().freeOpenGLResources( canvasPeer );
if ( getGeometry() != null )
getGeometry().freeOpenGLResources( canvasPeer );
}
/**
* 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
*/
@Override
public boolean traverse( DetailedTraversalCallback callback )
{
return ( callback.traversalOperationCommon( this ) && callback.traversalOperation( this ) && callback.traversalOperationAfter( this ) && callback.traversalOperationCommonAfter( this ) );
}
/**
* Constructs a new Shape3D object with a null geometry component
* and a null appearance component.
*/
public Shape3D()
{
this( (Geometry)null, (Appearance)null );
}
/**
* Constructs a new Shape3D object with specified geometry component
* and a null appearance component.
*/
public Shape3D( Geometry geometry )
{
this( geometry, (Appearance)null );
}
/**
* Constructs a new Shape3D object with specified geometry and
* appearance components.
*/
public Shape3D( Geometry geometry, Appearance appearance )
{
super();
this.setGeometry( geometry );
this.setAppearance( appearance );
}
}