/**
* 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.render.preprocessing;
import org.openmali.spatial.bodies.Box;
import org.openmali.spatial.bodies.Classifier;
import org.openmali.spatial.bodies.Classifier.Classification;
import org.openmali.spatial.bodies.Frustum;
import org.openmali.spatial.bodies.Sphere;
import org.openmali.spatial.bounds.BoundsType;
import org.openmali.types.twodee.Sized2iRO;
import org.openmali.vecmath2.Point3f;
import org.xith3d.effects.EffectFactory;
import org.xith3d.effects.shadows.ShadowFactory;
import org.xith3d.picking.PickPool;
import org.xith3d.picking.PickRay;
import org.xith3d.picking.PickRequest;
import org.xith3d.render.Canvas3D;
import org.xith3d.render.OpenGLCapabilities;
import org.xith3d.render.RenderPass;
import org.xith3d.render.RenderPassConfig;
import org.xith3d.scenegraph.*;
import org.xith3d.utility.logging.X3DLog;
import java.util.BitSet;
import java.util.List;
/**
* The ViewCuller is in charge of traversing the scenegraph to
* cull any Shape3D with the View's Frustum.<br>
* <br>
* This class is based on AtomsCollector, which is originally coded by David Yazel
* and heavily modified by Marvin Froehlich.
*
* @author Marvin Froehlich (aka Qudus)
*/
public class FrustumCuller
{
private ShadowFactory shadowFactory;
private int unculledShapesCount = 0;
private final Point3f viewPosition2 = new Point3f();
/**
* Further traverses all enabled subnodes of the Switch.
*/
private final void cullSwitchAtoms( Switch sw, Classification parentClassify, boolean cullingSuppressed, View view, Point3f viewPosition, Frustum frustum, RenderBinProvider binProvider, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRay pickRay, boolean isShadowPass )
{
if ( sw instanceof LODSwitch )
{
( (LODSwitch)sw ).updateWhichChild( viewPosition );
}
final int childIdx = sw.getWhichChild();
switch ( childIdx )
{
case Switch.CHILD_MASK:
{
final BitSet bs = sw.getChildMask();
final int numChildren = sw.numChildren();
for ( int i = bs.nextSetBit( 0 ); i >= 0 && i < numChildren; i = bs.nextSetBit( i + 1 ) )
{
cullNodeAtoms( sw.getChild( i ), parentClassify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
break;
}
case Switch.CHILD_ALL:
{
final int numChildren = sw.numChildren();
for ( int i = 0; i < numChildren; i++ )
{
cullNodeAtoms( sw.getChild( i ), parentClassify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
break;
}
case Switch.CHILD_NONE:
{
break;
}
default:
{
final Node n = sw.getChild( childIdx );
cullNodeAtoms( n, parentClassify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
break;
}
}
}
/**
* Further traverses this group to find Shape3Ds.
* Checks for state changes and cares for the state-stack.
*/
private final void cullGroupAtoms( GroupNode group, Classification parentClassify, boolean cullingSuppressed, View view, Point3f viewPosition, Frustum frustum, RenderBinProvider binProvider, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRay pickRay, boolean isShadowPass )
{
final int numChildren = group.numChildren();
for ( int i = 0; i < numChildren; i++ )
{
cullNodeAtoms( group.getChild( i ), parentClassify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
}
private final void handleGroupShadow( GroupNode group, Classification classify, RenderBinProvider binProvider, long frameId, boolean isShadowPass )
{
final boolean b = ( shadowFactory.needsPerLightCulling() && isShadowPass ) || ( !shadowFactory.needsPerLightCulling() && !isShadowPass );
// submit the occluder if there is one. Set the virtual world transform for the occluder
if ( ( shadowFactory != null ) && b && ( group.isOccluder() ) )
{
final ShadowAtom shadowAtom = shadowFactory.getShadowAtom( group );
if ( shadowAtom != null )
{
shadowAtom.updateLightsAndFogs();
binProvider.addShadowAtom( shadowAtom, classify, frameId );
//unculledShapesCount++;
return;
}
}
}
/**
* Adds the ShapeAtom the the appropriate list
*
* @param shape
*/
private void addShapeAtom( Shape3D shape, Classification classify, Point3f viewPosition, RenderBinProvider binProvider, OpenGLCapabilities glCaps, long frameId, PickRay pickRay, boolean isShadowPass )
{
ShapeAtom atom = _SG_PrivilegedAccess.getAtom( shape );
if ( atom == null )
{
atom = new ShapeAtom( shape, glCaps );
_SG_PrivilegedAccess.setAtom( shape, atom );
}
if ( shape instanceof AbstractLODShape3D )
{
( (AbstractLODShape3D)shape ).updateLOD( viewPosition );
}
binProvider.addMainAtom( atom, classify, frameId );
if ( pickRay == null )
{
Appearance app = shape.getAppearance();
if ( ( app != null ) && ( !app.isStatic() || app.isStaticDirty() ) )
{
atom.updateStateUnits( app, glCaps );
}
if ( ( shadowFactory != null ) && shadowFactory.isEnabled() )
{
// submit the occluder if there is one. Set the virtual world transform for the occluder
if ( shape.isOccluder() && ( ( shadowFactory.needsPerLightCulling() && isShadowPass ) || ( !shadowFactory.needsPerLightCulling() && !isShadowPass ) ) )
{
final ShadowAtom shadowAtom = shadowFactory.getShadowAtom( shape );
if ( shadowAtom != null )
binProvider.addShadowAtom( shadowAtom, classify, frameId );
}
}
}
unculledShapesCount++;
}
/**
* Checks the Node's actual type and invokes the appropriate method to further traverse the scenegraph.
*/
@SuppressWarnings("unchecked")
public final void cullNodeAtoms( Node node, Classification parentClassify, boolean cullingSuppressed, View view, Point3f viewPosition, Frustum frustum, RenderBinProvider binProvider, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRay pickRay, boolean isShadowPass )
{
/*
if ( node.getName() == null )
X3DLog.debug( "FrustumCuller.cullNodeAtoms(): Checking node ", node.getClass().getName() );
else
X3DLog.debug( "FrustumCuller.cullNodeAtoms(): Checking node ", node.getClass().getName(), " \"", node.getName(), "\"" );
*/
if (node == null) {
// this should never happen. this is a bug that I am hiding for now. -MFORD
X3DLog.error("node is null. Node should never be null");
return;
}
// a non-renderable node or in pickmode a non-pickable node must not be processed
if ( ( ( pickRay == null ) && !node.isRenderable() ) || ( ( pickRay != null ) && !node.isPickable() ) )
{
return;
}
Classification classify;
if ( node instanceof BranchGroup )
{
classify = null;
}
else if ( pickRay != null )
{
// pick-ray intersection test replaces frustum culling here.
// This results in more accurate preselection for GLSelect picking.
if ( !node.isIgnoreBounds() )
{
if ( node.getWorldBounds().intersects( pickRay ) )
{
classify = null;
}
else
{
classify = Classification.OUTSIDE;
return;
}
}
else
{
classify = null;
}
}
else if ( !cullingSuppressed && !node.isIgnoreBounds() )
{
if ( parentClassify != Classification.INSIDE )
{
if ( node.getBoundsType() == BoundsType.SPHERE )
{
classify = Classifier.classifyFrustumSphere( frustum, (Sphere)node.getWorldBounds() );
}
else if ( node.getBoundsType() == BoundsType.AABB )
{
classify = Classifier.classifyFrustumBox( frustum, (Box)node.getWorldBounds() );
}
else
{
classify = null;
}
// Break traversal here. This is especially useful in case this is a Group.
if ( classify == Classification.OUTSIDE )
{
//System.out.println( "culled: " + node + ", " + node.getWorldBounds() );
return;
}
}
else
{
classify = parentClassify;
}
}
else
{
classify = null;
}
if ( node.isUpdatableNode() )
{
_SG_PrivilegedAccess.update( (UpdatableNode)node, view, frustum, nanoTime, nanoStep );
}
if ( ( node.getShowBounds() ) && ( !( node instanceof Shape3D ) ) )
{
binProvider.addMainAtom( new BoundsAtom( node ), classify, frameId );
}
if ( node instanceof GroupNode )
{
if ( pickRay == null )
{
handleGroupShadow( (GroupNode)node, classify, binProvider, frameId, isShadowPass );
}
if ( ( (GroupNode)node ).getTotalNumShapes() > 0 )
{
if ( node instanceof SpecialCullingNode )
{
( (SpecialCullingNode)node ).cullSpecialNode( node, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass, this );
}
else if ( node instanceof Switch )
{
cullSwitchAtoms( (Switch)node, classify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
else
{
cullGroupAtoms( (GroupNode)node, classify, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
}
}
else if ( node instanceof Shape3D )
{
final Shape3D shape = (Shape3D)node;
if ( shape.isVisible() )
{
addShapeAtom( shape, classify, viewPosition, binProvider, glCaps, frameId, pickRay, isShadowPass );
}
}
}
/**
* Collects all Atoms in this Group, if the give Node is a Group, otherwise only this Node's Atom.
*
* @param group the Group to collect Atoms from
* @param frustum
* @param cullingSuppressed
* @param viewPosition
* @param binProvider
* @param frameId
* @param pickRay
*/
private final void cullAtoms( GroupNode group, Frustum frustum, boolean cullingSuppressed, View view, Point3f viewPosition, RenderBinProvider binProvider, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRay pickRay, boolean isShadowPass )
{
cullNodeAtoms( group, null, cullingSuppressed, view, viewPosition, frustum, binProvider, glCaps, frameId, nanoTime, nanoStep, pickRay, isShadowPass );
}
/**
* Traverses the scenegraph and collects all RenderAtoms and spreads them over different RenderBins.
* This collection is later passed to the OpenGL renderer.
*
* @param renderPass
* @param initialize
* @param rootGroup
* @param canvas
* @param viewPosition
* @param glCaps
* @param frameId
* @param nanoTime
* @param nanoStep
*/
private final int cullAtoms_normal( RenderPass renderPass, boolean initialize, GroupNode rootGroup, Canvas3D canvas, Point3f viewPosition, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep )
{
unculledShapesCount = 0;
final View view = canvas.getView();
//if ( initialize )
//{
_SG_PrivilegedAccess.set( view, true, renderPass.getConfig() );
final Sized2iRO viewport;
if ( ( renderPass.getConfig() != null ) && ( renderPass.getConfig().getViewport() != null ) )
{
viewport = renderPass.getConfig().getViewport();
}
else
{
viewport = canvas;
}
Frustum frustum = view.getFrustum( viewport );
//}
renderPass.getRenderBinProvider().clearAllBins();
cullAtoms( rootGroup, frustum, !renderPass.isFrustumCullingEnabled(), view, viewPosition, renderPass.getRenderBinProvider(), glCaps, frameId, nanoTime, nanoStep, null, false );
try
{
//if ( initialize )
{
_SG_PrivilegedAccess.set( canvas.getView(), false, (RenderPassConfig)null );
}
if ( ( shadowFactory != null ) && shadowFactory.isEnabled() && shadowFactory.needsPerLightCulling() && ( renderPass.getShadowCasterLight() != null ) )
{
final RenderPass viewPass = shadowFactory.setupRenderPass( view, renderPass.getShadowCasterLight(), 0f, null, frameId, true );
if ( viewPass != null )
{
_SG_PrivilegedAccess.set( view, true, viewPass.getConfig() );
final Sized2iRO viewport2;
if ( shadowFactory.getLightViewport() != null )
{
viewport2 = shadowFactory.getLightViewport();
}
else if ( ( renderPass.getConfig() != null ) && ( renderPass.getConfig().getViewport() != null ) )
{
viewport2 = renderPass.getConfig().getViewport();
}
else
{
viewport2 = canvas;
}
frustum = view.getFrustum( viewport2 );
cullAtoms( rootGroup, frustum, !renderPass.isFrustumCullingEnabled(), canvas.getView(), viewPosition, renderPass.getRenderBinProvider(), glCaps, frameId, nanoTime, nanoStep, null, true );
_SG_PrivilegedAccess.set( view, false, (RenderPassConfig)null );
}
}
}
finally
{
renderPass.getRenderBinProvider().shrinkAllBins();
}
return ( unculledShapesCount );
}
/**
* Traverses the scenegraph and collects all RenderAtoms and spreads them over different RenderBins.
* This collection is later passed to the OpenGL renderer.
*
* @param canvas
* @param viewPosition
* @param frameId
* @param pickRequest
*/
private final int cullAtoms_picking( Canvas3D canvas, Point3f viewPosition, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRequest pickRequest )
{
unculledShapesCount = 0;
BranchGroup bg0 = null;
Frustum frustum = null;
PickRay pickRay = null;
int j = 0;
for ( int i = 0; i < pickRequest.getGroups().size(); i++ )
{
pickRequest.getRenderPasses().get( i ).getRenderBinProvider().clearAllBins();
}
for ( int i = 0; i < pickRequest.getGroups().size(); i++ )
{
final GroupNode group = (GroupNode)pickRequest.getGroups().get( i );
final RenderPass pass = pickRequest.getRenderPasses().get( i );
final BranchGroup bg = group.getRoot();
if ( ( i == 0 ) || ( bg != bg0 ) )
{
bg0 = bg;
if ( bg == null )
{
throw new NullPointerException( "You cannot cull on non-live Groups." );
}
final View view = canvas.getView();
_SG_PrivilegedAccess.set( view, true, pass.getConfig() );
j++;
final Sized2iRO viewport;
if ( pass.getConfig() != null )
{
if ( pass.getConfig().getViewport() != null )
viewport = pass.getConfig().getViewport();
else
viewport = canvas;
}
else
{
viewport = canvas;
}
frustum = view.getFrustum( viewport );
}
//if ( ( i == 0 ) || ( group.getLayeredNode() != layNode ) )
{
//layNode = group.getLayeredNode();
if ( pickRay == null )
{
pickRay = PickPool.allocatePickRay();
}
/*
final CameraMode cameraMode;
if ( layNode == null )
cameraMode = null;
else
cameraMode = layNode.getCameraMode();
*/
pickRay.recalculate( pass.getConfig(), canvas, pickRequest.getMouseX(), pickRequest.getMouseY() );
}
cullAtoms( group, frustum, !pass.isFrustumCullingEnabled(), canvas.getView(), viewPosition, pass.getRenderBinProvider(), glCaps, frameId, nanoTime, nanoStep, pickRay, false );
}
if ( pickRay != null )
{
PickPool.deallocatePickRay( pickRay );
}
while ( j-- > 0 )
{
_SG_PrivilegedAccess.set( canvas.getView(), false, (RenderPassConfig)null );
}
return ( unculledShapesCount );
}
private final void updateShadowFactory()
{
final EffectFactory effFact = EffectFactory.getInstance();
if ( effFact != null )
{
shadowFactory = effFact.getShadowFactory();
}
else
{
shadowFactory = null;
}
}
/**
* Traverses the scenegraph and collects all RenderAtoms and spreads them over different RenderBins.
* This collection is later passed to the OpenGL renderer.
*
* @param renderPass
* @param rootGroup
* @param canvas
* @param viewPosition
* @param frameId
* @param pickRequest
*/
public final int cullAtoms( RenderPass renderPass, GroupNode rootGroup, Canvas3D canvas, Point3f viewPosition, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRequest pickRequest )
{
updateShadowFactory();
if ( pickRequest == null )
return ( cullAtoms_normal( renderPass, true, rootGroup, canvas, viewPosition, glCaps, frameId, nanoTime, nanoStep ) );
return ( cullAtoms_picking( canvas, viewPosition, glCaps, frameId, nanoTime, nanoStep, pickRequest ) );
}
/**
* Traverses the scenegraph and collects all RenderAtoms and spreads them over different RenderBins.
* This collection is later passed to the OpenGL renderer.
*
* @param renderPasses
* @param groupsLists
* @param canvas
* @param frameId
* @param nanoTime
* @param nanoStep
* @param pickRequest
*/
public final int cullAtoms( List< RenderPass > renderPasses, List< ? extends List< GroupNode >> groupsLists, Canvas3D canvas, OpenGLCapabilities glCaps, long frameId, long nanoTime, long nanoStep, PickRequest pickRequest )
{
updateShadowFactory();
int unculledShapesCount = 0;
if ( pickRequest == null )
{
if ( groupsLists == null )
{
for ( int i = 0; i < renderPasses.size(); i++ )
{
final RenderPass pass = renderPasses.get( i );
// notify the RenderCallbacks, if any
pass.getRenderCallbackNotifier().notifyBeforeRenderPassIsProcessed( pass );
if ( pass.getConfig().getViewTransform() == null )
canvas.getView().getPosition( viewPosition2 );
else
pass.getConfig().getViewTransform().getTranslation( viewPosition2 );
if ( pass.getBranchGroup() != null )
unculledShapesCount += cullAtoms_normal( pass, ( i == 0 ), pass.getBranchGroup(), canvas, viewPosition2, glCaps, frameId, nanoTime, nanoStep );
// notify the RenderCallbacks, if any
pass.getRenderCallbackNotifier().notifyAfterRenderPassIsProcessed( pass );
}
}
else
{
for ( int i = 0; i < renderPasses.size(); i++ )
{
final RenderPass pass = renderPasses.get( i );
// notify the RenderCallbacks, if any
pass.getRenderCallbackNotifier().notifyBeforeRenderPassIsProcessed( pass );
if ( pass.getConfig().getViewTransform() == null )
canvas.getView().getPosition( viewPosition2 );
else
pass.getConfig().getViewTransform().getTranslation( viewPosition2 );
final List< GroupNode > groups = groupsLists.get( i );
for ( int j = 0; j < groups.size(); j++ )
{
unculledShapesCount += cullAtoms_normal( pass, ( i == 0 && j == 0 ), groups.get( j ), canvas, viewPosition2, glCaps, frameId, nanoTime, nanoStep );
}
// notify the RenderCallbacks, if any
pass.getRenderCallbackNotifier().notifyAfterRenderPassIsProcessed( pass );
}
}
}
else
{
return ( cullAtoms_picking( canvas, canvas.getView().getPosition(), glCaps, frameId, nanoTime, nanoStep, pickRequest ) );
}
return ( unculledShapesCount );
}
/**
* Creates a new renderer that also capable of collecting RenderAtoms.
*/
public FrustumCuller()
{
}
}