/**
* 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.jsr231;
import java.nio.FloatBuffer;
import javax.media.opengl.GL;
import org.jagatoo.logging.ProfileTimer;
import org.openmali.vecmath2.Colorf;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Tuple3f;
import org.openmali.vecmath2.Vector3f;
import org.xith3d.render.CanvasPeer;
import org.xith3d.render.OpenGLCapabilities;
import org.xith3d.render.OpenGLStatesCache;
import org.xith3d.render.RenderOptions;
import org.xith3d.render.RenderPeer;
import org.xith3d.render.RenderPeer.RenderMode;
import org.xith3d.render.preprocessing.RenderAtom;
import org.xith3d.render.states.StateUnit;
import org.xith3d.render.states.units.LightingStateUnit;
import org.xith3d.render.states.units.StateUnitPeer;
import org.xith3d.scenegraph.AmbientLight;
import org.xith3d.scenegraph.DirectionalLight;
import org.xith3d.scenegraph.Light;
import org.xith3d.scenegraph.PointLight;
import org.xith3d.scenegraph.SpotLight;
import org.xith3d.scenegraph.Transform3D;
import org.xith3d.scenegraph.View;
import org.xith3d.scenegraph._SG_PrivilegedAccess;
import org.xith3d.utility.logging.X3DLog;
import com.sun.opengl.util.BufferUtil;
/**
* Handles lighting.
*
* @author David Yazel
* @author Lilian Chamontin [jsr231 port]
* @author Marvin Froehlich (aka Qudus)
*/
public class LightingStateUnitPeer implements StateUnitPeer
{
// buffers used to send lighting data to the GL
private final FloatBuffer floatBuffer = BufferUtil.newFloatBuffer( 4 );
private static final FloatBuffer blackColorBuffer = BufferUtil.newFloatBuffer( 4 );
static
{
blackColorBuffer.put( 0f );
blackColorBuffer.put( 0f );
blackColorBuffer.put( 0f );
blackColorBuffer.put( 1f );
blackColorBuffer.rewind();
}
private final Point3f tmpPoint = new Point3f();
private final Vector3f tmpVector = new Vector3f();
public LightingStateUnitPeer()
{
}
public static final int getGlLight( int l )
{
switch ( l )
{
case 0:
return GL.GL_LIGHT0;
case 1:
return GL.GL_LIGHT1;
case 2:
return GL.GL_LIGHT2;
case 3:
return GL.GL_LIGHT3;
case 4:
return GL.GL_LIGHT4;
case 5:
return GL.GL_LIGHT5;
case 6:
return GL.GL_LIGHT6;
case 7:
return GL.GL_LIGHT7;
default:
Error e = new Error( "Invalid Light" );
X3DLog.print( e );
throw e;
}
}
// TODO (yvg) add support for separate diffuse and specular lighting
private final boolean shadeLight( final GL gl, OpenGLStatesCache statesCache, final Light light, final Transform3D transform, final int lightIndex )
{
if ( ( light == null ) || !light.isEnabled() || ( lightIndex > 7 ) )
return ( false );
if ( light instanceof AmbientLight )
{
// TODO (yvg) Add support for multiple ambient lights (should
// accumulate ambient light colors and after clamp to 1,1,1)
final AmbientLight aLight = (AmbientLight)light;
final Colorf color = aLight.getColor();
floatBuffer.clear();
floatBuffer.put( color.getRed() );
floatBuffer.put( color.getGreen() );
floatBuffer.put( color.getBlue() );
floatBuffer.put( 1 );
floatBuffer.rewind();
gl.glLightModelfv( GL.GL_LIGHT_MODEL_AMBIENT, floatBuffer );
return ( false ); // don't incement light-index
}
else if ( light instanceof DirectionalLight )
{
final int glLight = getGlLight( lightIndex );
final DirectionalLight dLight = (DirectionalLight)light;
final Colorf color = dLight.getColor();
floatBuffer.clear();
floatBuffer.put( color.getRed() );
floatBuffer.put( color.getGreen() );
floatBuffer.put( color.getBlue() );
floatBuffer.put( 1.0f );
floatBuffer.rewind();
gl.glLightfv( glLight, GL.GL_DIFFUSE, floatBuffer );
gl.glLightfv( glLight, GL.GL_SPECULAR, floatBuffer );
// Set Ambient component of this light to 0
gl.glLightfv( glLight, GL.GL_AMBIENT, blackColorBuffer );
if ( transform == null )
tmpVector.set( dLight.getDirection() );
else
transform.getMatrix4f().transform( dLight.getDirection(), tmpVector );
floatBuffer.clear();
floatBuffer.put( -tmpVector.getX() );
floatBuffer.put( -tmpVector.getY() );
floatBuffer.put( -tmpVector.getZ() );
floatBuffer.put( 0.0f );
floatBuffer.rewind();
gl.glLightfv( glLight, GL.GL_POSITION, floatBuffer );
gl.glLightf( glLight, GL.GL_SPOT_EXPONENT, 0f );
gl.glLightf( glLight, GL.GL_SPOT_CUTOFF, 180f );
if ( !statesCache.enabled || !statesCache.lightEnabled[ lightIndex ] )
{
gl.glEnable( glLight );
statesCache.lightEnabled[ lightIndex ] = true;
}
}
else if ( light instanceof PointLight )
{
final int glLight = getGlLight( lightIndex );
final PointLight pLight = (PointLight)light;
final Colorf color = pLight.getColor();
floatBuffer.clear();
floatBuffer.put( color.getRed() );
floatBuffer.put( color.getGreen() );
floatBuffer.put( color.getBlue() );
floatBuffer.put( 1 );
floatBuffer.rewind();
gl.glLightfv( glLight, GL.GL_DIFFUSE, floatBuffer );
gl.glLightfv( glLight, GL.GL_SPECULAR, floatBuffer );
// Set Ambient component of this light to 0
gl.glLightfv( glLight, GL.GL_AMBIENT, blackColorBuffer );
if ( transform == null )
tmpPoint.set( pLight.getLocation() );
else
transform.transform( pLight.getLocation(), tmpPoint );
floatBuffer.clear();
floatBuffer.put( tmpPoint.getX() );
floatBuffer.put( tmpPoint.getY() );
floatBuffer.put( tmpPoint.getZ() + 0.00001f ); // Workaround to prevent halfVector randomness
floatBuffer.put( 1f );
floatBuffer.rewind();
gl.glLightfv( glLight, GL.GL_POSITION, floatBuffer );
final Tuple3f att = pLight.getAttenuation();
gl.glLightf( glLight, GL.GL_CONSTANT_ATTENUATION, att.getX() );
gl.glLightf( glLight, GL.GL_LINEAR_ATTENUATION, att.getY() );
gl.glLightf( glLight, GL.GL_QUADRATIC_ATTENUATION, att.getZ() );
if ( light instanceof SpotLight )
{
final SpotLight sLight = (SpotLight)pLight;
if ( transform == null )
tmpVector.set( sLight.getDirection() );
else
transform.getMatrix4f().transform( sLight.getDirection(), tmpVector );
floatBuffer.clear();
floatBuffer.put( tmpVector.getX() );
floatBuffer.put( tmpVector.getY() );
floatBuffer.put( tmpVector.getZ() );
floatBuffer.put( 1f );
floatBuffer.rewind();
gl.glLightfv( glLight, GL.GL_SPOT_DIRECTION, floatBuffer );
gl.glLightf( glLight, GL.GL_SPOT_EXPONENT, sLight.getConcentration() );
gl.glLightf( glLight, GL.GL_SPOT_CUTOFF, sLight.getSpreadAngleDeg() );
}
else
{
gl.glLightf( glLight, GL.GL_SPOT_EXPONENT, 0f );
gl.glLightf( glLight, GL.GL_SPOT_CUTOFF, 180f );
}
if ( !statesCache.enabled || !statesCache.lightEnabled[ lightIndex ] )
{
gl.glEnable( glLight );
statesCache.lightEnabled[ lightIndex ] = true;
}
}
return ( true );
}
private static final Transform3D getTrackedNodeTransform( final Light light )
{
if ( light instanceof PointLight )
{
final PointLight pLight = (PointLight)light;
if ( pLight.getTrackedNode() != null )
{
return ( pLight.getTrackedNode().getWorldTransform() );
}
}
else if ( light instanceof DirectionalLight )
{
final DirectionalLight dLight = (DirectionalLight)light;
if ( dLight.getTrackedNode() != null )
{
return ( dLight.getTrackedNode().getWorldTransform() );
}
}
return ( null );
}
public void apply( RenderAtom< ? > atom, StateUnit stateUnit, Object glObj, CanvasPeer canvasPeer, RenderPeer renderPeer, OpenGLCapabilities glCaps, View view, OpenGLStatesCache statesCache, RenderOptions options, long nanoTime, long nanoStep, RenderMode renderMode, long frameId )
{
ProfileTimer.startProfile( X3DLog.LOG_CHANNEL, "LightingStateUnitPeer::apply()" );
final GL gl = (GL)glObj;
LightingStateUnit ls = (LightingStateUnit)stateUnit;
// enable and set all the lights
int lightIndex = 0;
final boolean hasLights = ( ( ls.numLights() > 0 ) || ( view.getAttachedLight() != null ) );
if ( ( renderMode == RenderMode.NORMAL ) && hasLights )
{
gl.glPushMatrix();
gl.glLoadMatrixf( _SG_PrivilegedAccess.getFloatBuffer( view.getModelViewTransform( false ), true ) );
for ( int i = 0; i < ls.numLights(); i++ )
{
final Light light = ls.getLight( i );
if ( shadeLight( gl, statesCache, light, getTrackedNodeTransform( light ), lightIndex ) )
{
lightIndex++;
}
}
if ( shadeLight( gl, statesCache, view.getAttachedLight(), view.getTransform(), lightIndex ) )
{
lightIndex++;
}
}
// disable all the other lights
for ( int i = lightIndex; i <= 7; i++ )
{
if ( !statesCache.enabled || statesCache.lightEnabled[ i ] )
{
gl.glDisable( getGlLight( i ) );
statesCache.lightEnabled[ i ] = false;
}
}
if ( ( renderMode == RenderMode.NORMAL ) && hasLights )
{
gl.glPopMatrix();
}
ProfileTimer.endProfile();
}
}