/** * 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.effects.shadows; import org.jagatoo.opengl.enums.TextureBoundaryMode; import org.jagatoo.opengl.enums.TextureFilter; import org.jagatoo.opengl.enums.TextureFormat; import org.openmali.FastMath; import org.openmali.spatial.bodies.Body; import org.openmali.types.twodee.Dim2i; import org.openmali.types.twodee.Sized2iRO; import org.openmali.vecmath2.Colorf; import org.openmali.vecmath2.Point3f; import org.openmali.vecmath2.Vector3f; import org.openmali.vecmath2.util.TupleUtils; import org.xith3d.effects.EffectFactory; import org.xith3d.loaders.texture.TextureCreator; import org.xith3d.render.BaseRenderPassConfig; import org.xith3d.render.RenderPass; import org.xith3d.render.RenderPassConfig; import org.xith3d.render.TextureRenderTarget; import org.xith3d.render.preprocessing.RenderBin; import org.xith3d.render.preprocessing.RenderBinProvider; import org.xith3d.render.preprocessing.RenderBinType; import org.xith3d.render.preprocessing.ShadowAtom; import org.xith3d.scenegraph.DirectionalLight; import org.xith3d.scenegraph.Group; import org.xith3d.scenegraph.GroupNode; import org.xith3d.scenegraph.Light; import org.xith3d.scenegraph.Node; import org.xith3d.scenegraph.Shape3D; import org.xith3d.scenegraph.SpotLight; import org.xith3d.scenegraph.Texture2D; import org.xith3d.scenegraph.TextureAttributes; import org.xith3d.scenegraph.Transform3D; import org.xith3d.scenegraph.View; import org.xith3d.scenegraph._SG_PrivilegedAccess; import org.xith3d.scenegraph.View.ProjectionPolicy; /** * This {@link ShadowFactory} is a base for any factory * realizing shadow-mapping. * * @author Marvin Froehlich (aka Qudus) */ public abstract class ShadowMappingFactory extends ShadowFactory { public static final ShadowFactoryIdentifier SHADOW_FACTORY_ID = new ShadowFactoryIdentifier(); private static final float DIRECTIONAL_LIGHT_SOURCE_DISTANCE = 1000f; /** * {@inheritDoc} */ @Override public ShadowFactoryIdentifier getShadowFactoryId() { return ( SHADOW_FACTORY_ID ); } public static class HackedRenderBinProvider extends RenderBinProvider { public void setOpaqueBin( RenderBin opaqueBin ) { this.opaqueBin = opaqueBin; } public HackedRenderBinProvider() { super( null, new RenderBin( RenderBinType.MAIN_TRANSPARENT, "Shadow Transparent RenderBin", 0 ), new RenderBin( RenderBinType.MAIN_OPAQUE, "Shadow Opaque RenderBin", 0 ) ); } } private static final Transform3D createScaleAndBias() { Transform3D scaleAndBias = new Transform3D(); scaleAndBias.getMatrix4f().set( 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f ); return ( scaleAndBias ); } private final Point3f lightPos = new Point3f(); private final Vector3f lightDir = new Vector3f(); private final Transform3D lightProj = new Transform3D(); private final Transform3D lightModelView = new Transform3D(); private final Transform3D scaleAndBias = createScaleAndBias(); private final Dim2i lightViewport = new Dim2i(); private TextureRenderTarget depthTarget = null; private HackedRenderBinProvider renderBinProvider = null; private RenderPass generationPass = null; private Texture2D shadowMap = null; private TextureAttributes shadowMapAttribs = null; public TextureRenderTarget getDepthRenderTarget() { if ( depthTarget != null ) return ( depthTarget ); final ShadowMappingFactory shadowFactory = (ShadowMappingFactory)EffectFactory.getInstance().getShadowFactory(); Colorf back = new Colorf( 0f, 0f, 0f, 1.0f ); depthTarget = new TextureRenderTarget( new Group(), shadowFactory.getShadowMap(), back ); depthTarget.setBackgroundRenderingEnabled( false ); return ( depthTarget ); } public HackedRenderBinProvider getRenderBinProvider() { if ( renderBinProvider != null ) return ( renderBinProvider ); renderBinProvider = new HackedRenderBinProvider(); return ( renderBinProvider ); } public RenderPass getGenerationPass() { if ( generationPass != null ) return ( generationPass ); generationPass = new RenderPass( new BaseRenderPassConfig() ) { @Override protected RenderBinProvider createRenderBinProvider() { return ( ShadowMappingFactory.this.getRenderBinProvider() ); } }; generationPass.setRenderTarget( getDepthRenderTarget() ); generationPass.getConfig().setViewTransform( new Transform3D() ); return ( generationPass ); } /* private static final SpotLight findLight( RenderBin bin, String shadowLightName ) { for ( int a = 0; a < bin.size(); a++ ) { final InheritedNodes info = bin.getAtoms().get( a ).getNode().getInheritedNodes(); for ( int i = 0; i < info.getLightsCount(); i++ ) { final Light l = info.getLight( i ); if ( l instanceof SpotLight ) { if ( shadowLightName == null ) { return ( (SpotLight)l ); } else if ( shadowLightName.equals( l.getName() ) ) { return ( (SpotLight)l ); } } } } return ( null ); } */ private final void findNearAndFarPlanes( Point3f lightPos, RenderBin shadowBin, RenderPassConfig passConfig ) { float near = +Float.MAX_VALUE; float far = -Float.MAX_VALUE; for ( int i = 0; i < shadowBin.size(); i++ ) { final Body bounds = (Body)shadowBin.getAtom( i ).getNode().getWorldBounds(); final float distCenter = lightPos.distance( bounds.getCenterX(), bounds.getCenterY(), bounds.getCenterZ() ); final float radius = bounds.getMaxCenterDistance(); final float tmpNear = distCenter - radius; final float tmpFar = distCenter + radius; if ( tmpNear < near ) near = tmpNear; if ( tmpFar > far ) far = tmpFar; } // FIXME: I have no idea, why this is necessary! near -= ( far - near ) * 0.1f; passConfig.setFrontClipDistance( near ); passConfig.setBackClipDistance( far ); } protected float calculateScreenScale() { // TODO: This value needs to be calculateed somehow. Or does it need to be set by the user??? return ( 10.0f ); } /** * * @param fovy * @param aspect * @param near * @param far * @param lightTransform * @param viewTransform * @param textureTransform */ protected void calculateTextureMatrix( float fovy, float aspect, float near, float far, Transform3D lightTransform, Transform3D viewTransform, Transform3D textureTransform ) { // Extract light's projection matrix. if ( fovy < 0f ) // DirectionalLight { final float screenScale = calculateScreenScale(); lightProj.ortho( -screenScale, screenScale, -screenScale / aspect, screenScale / aspect, near, far ); } else { lightProj.perspective( fovy * 2f, aspect, near, far ); } // Extract light's model-view-matrix. lightModelView.set( lightTransform ); lightModelView.invert(); // Compose the Texture-Matrix... textureTransform.setIdentity(); textureTransform.mul( scaleAndBias ); textureTransform.mul( lightProj ); textureTransform.mul( lightModelView ); } /** * {@inheritDoc} */ @Override public RenderPass setupRenderPass( View view, Light light, float viewportAspect, RenderBin shadowBin, long frameId, boolean justForCulling ) { if ( !justForCulling && ( shadowBin.size() == 0 ) ) { return ( null ); } final RenderPass generationPass = getGenerationPass(); final RenderPassConfig passConfig = generationPass.getConfig(); if ( light instanceof DirectionalLight ) { ( (DirectionalLight)light ).getComputedDirection( lightDir ); // Set light-position to a galaxy far far away along the negated direction lightPos.set( lightDir ); lightPos.negate(); TupleUtils.normalizeVector( lightPos ).mul( DIRECTIONAL_LIGHT_SOURCE_DISTANCE ); passConfig.setProjectionPolicy( ProjectionPolicy.PARALLEL_PROJECTION ); passConfig.setScreenScale( calculateScreenScale() ); } else //if ( light instanceof SpotLight ) { ( (SpotLight)light ).getComputedDirection( lightDir ); ( (SpotLight)light ).getComputedLocation( lightPos ); passConfig.setProjectionPolicy( ProjectionPolicy.PERSPECTIVE_PROJECTION ); } /* * Setup the Texture-Matrix. */ final float near; final float far; if ( justForCulling ) { if ( light instanceof DirectionalLight ) { near = 0.01f; far = DIRECTIONAL_LIGHT_SOURCE_DISTANCE; } else { near = 0.01f; far = 2000f; } passConfig.setFrontClipDistance( near ); passConfig.setBackClipDistance( far ); } else { // Set near and far clip planes for depth-texture-rendering. findNearAndFarPlanes( lightPos, shadowBin, passConfig ); near = passConfig.getFrontClipDistance(); far = passConfig.getBackClipDistance(); } passConfig.getViewTransform().lookAlong( lightPos, lightDir ); float fovy = -1f; if ( light instanceof SpotLight ) { fovy = ( (SpotLight)light ).getSpreadAngle() * 2f; passConfig.setFieldOfView( fovy ); } if ( !justForCulling ) { calculateTextureMatrix( fovy, viewportAspect, near, far, passConfig.getViewTransform(), view.getTransform(), getShadowMapAttributes().getTextureTransform() ); getRenderBinProvider().setOpaqueBin( shadowBin ); } return ( generationPass ); } public Texture2D getShadowMap() { if ( shadowMap != null ) return ( shadowMap ); final int texSize = FastMath.pow( 2, getShadowQuality() ); shadowMap = TextureCreator.createTexture( TextureFormat.DEPTH, texSize, texSize ); shadowMap.setName( "ShadowMap" ); shadowMap.setBoundaryModeS( TextureBoundaryMode.CLAMP_TO_EDGE ); shadowMap.setBoundaryModeT( TextureBoundaryMode.CLAMP_TO_EDGE ); shadowMap.setFilter( TextureFilter.TRILINEAR ); return ( shadowMap ); } public TextureAttributes getShadowMapAttributes() { if ( shadowMapAttribs != null ) return ( shadowMapAttribs ); shadowMapAttribs = new TextureAttributes(); shadowMapAttribs.setTextureTransform( new Transform3D() ); //shadowMapAttribs.setCompareMode( org.jagatoo.opengl.enums.TextureCompareMode.COMPARE_R_TO_TEXTURE ); shadowMapAttribs.setCompareFunction( org.jagatoo.opengl.enums.CompareFunction.LOWER_OR_EQUAL ); return ( shadowMapAttribs ); } /** * {@inheritDoc} */ @Override public ShadowAtom getShadowAtom( Node node ) { final ShadowAtom shadowAtom; if ( node instanceof Shape3D ) shadowAtom = _SG_PrivilegedAccess.getAtom( (Shape3D)node ); else shadowAtom = null; /* if ( shadowAtom != null ) { } */ return ( shadowAtom ); } /** * {@inheritDoc} */ @Override public void verifyLight( Light light ) { if ( ( light != null ) && ( !( light instanceof SpotLight ) && !( light instanceof DirectionalLight ) ) ) { throw new IllegalArgumentException( "This shadowFactory accepts SpotLights and DirectionLights only." ); } } /** * {@inheritDoc} */ @Override public boolean needsPerLightCulling() { return ( true ); } /** * {@inheritDoc} */ @Override public void setShadowQuality( int quality ) { super.setShadowQuality( quality ); final int texSize = FastMath.pow( 2, getShadowQuality() ); lightViewport.set( texSize, texSize ); } /** * {@inheritDoc} */ @Override public Sized2iRO getLightViewport() { return ( lightViewport ); } /** * {@inheritDoc} */ @Override public void onOccluderStateChanged( Node node, boolean isOccluder ) { if ( node instanceof GroupNode ) { final GroupNode group = (GroupNode)node; final int n = group.numChildren(); for ( int i = 0; i < n; i++ ) group.getChild( i ).setIsOccluder( isOccluder ); } } /** * {@inheritDoc} */ @Override public void setEnabled( boolean enabled ) { super.setEnabled( enabled ); if ( shadowMap != null ) shadowMap.setEnabled( enabled ); } public ShadowMappingFactory() { setShadowQuality( getShadowQuality() ); } }