/**
* 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.atmosphere;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import org.jagatoo.opengl.enums.BlendFunction;
import org.jagatoo.opengl.enums.BlendMode;
import org.jagatoo.opengl.enums.DrawMode;
import org.jagatoo.opengl.enums.FaceCullMode;
import org.openmali.FastMath;
import org.openmali.vecmath2.AxisAngle3f;
import org.openmali.vecmath2.Colorf;
import org.openmali.vecmath2.Matrix3f;
import org.openmali.vecmath2.Point3f;
import org.openmali.vecmath2.Quaternion4f;
import org.openmali.vecmath2.Tuple3f;
import org.openmali.vecmath2.Vector3f;
import org.openmali.vecmath2.Vector4f;
import org.xith3d.loaders.shaders.impl.glsl.GLSLShaderLoader;
import org.xith3d.loop.UpdatingThread.TimingMode;
import org.xith3d.scenegraph.Appearance;
import org.xith3d.scenegraph.GLSLFragmentShader;
import org.xith3d.scenegraph.GLSLContext;
import org.xith3d.scenegraph.GLSLParameters;
import org.xith3d.scenegraph.GLSLShaderProgram;
import org.xith3d.scenegraph.GLSLVertexShader;
import org.xith3d.scenegraph.PointLight;
import org.xith3d.scenegraph.Transform3D;
import org.xith3d.scenegraph.TransparencyAttributes;
import org.xith3d.scenegraph.primitives.Sphere;
/**
* The AtmosphereFactory is a visual effect for spheres.
* It computes in real-time the atmosphere of a planet see from space.<br>
* <br>
* All computations are done by the GPU using shaders.<br>
* <br>
* This code is a Xith adaptation of Sean O'Neil code given here: http://sponeil.net/
* (Code under BSD license)<br>
* <br>
*
* @author Yoann Meste (aka Mancer)
*/
public class GLSLAtmosphereFactory extends AtmosphereFactory
{
private static GLSLShaderProgram groundFromSpace = null;
private static GLSLShaderProgram skyFromSpace = null;
private static ArrayList<GLSLParameters> parametersGround = new ArrayList<GLSLParameters>();
private static ArrayList<GLSLParameters> parametersSky = new ArrayList<GLSLParameters>();
private int nbSamples = 2;
private float kr = 0.0025f;
private float km = 0.0015f;
private float eSun = 15f;
private float g = -0.95f;
private float innerRadius;
private float outerRadius;
private float rayleighScaleDepth = 0.25f;
//private float mieScaleDepth = 0.1f;
/*
public void setNbSamples( int nbSamples )
{
this.nbSamples = nbSamples;
}
public int getNbSamples()
{
return ( nbSamples );
}
public void setKr( float kr )
{
this.kr = kr;
}
public float getKr()
{
return ( kr );
}
public void setKm( float km )
{
this.km = km;
}
public float getKm()
{
return ( km );
}
public void setESun( float sun )
{
eSun = sun;
}
public float getESun()
{
return ( eSun );
}
public void setG( float g )
{
this.g = g;
}
public float getG()
{
return ( g );
}
public void setRayleighScaleDepth( float rayleighScaleDepth )
{
this.rayleighScaleDepth = rayleighScaleDepth;
}
public float getRayleighScaleDepth()
{
return ( rayleighScaleDepth );
}
public void setMieScaleDepth( float mieScaleDepth )
{
this.mieScaleDepth = mieScaleDepth;
}
public float getMieScaleDepth()
{
return ( mieScaleDepth );
}
*/
private final float getScale()
{
return ( 1f / ( outerRadius - innerRadius ) );
}
private final float getKm4Pi()
{
return ( km * 4f * FastMath.PI );
}
private final float getKr4Pi()
{
return ( kr * 4f * FastMath.PI );
}
private void updateShaderWavelength( Tuple3f wavelength3 )
{
if ( wavelength3 == null )
wavelength3 = getWavelength3();
Vector4f wavelength4 = Vector4f.fromPool();
wavelength4.set( FastMath.pow( wavelength3.getX(), 4f ), FastMath.pow( wavelength3.getY(), 4f ), FastMath.pow( wavelength3.getZ(), 4f ), 0f );
Tuple3f invWaveLength = Tuple3f.fromPool();
invWaveLength.set( 1f / wavelength4.getX(), 1f / wavelength4.getY(), 1f / wavelength4.getZ() );
for ( int i = 0; i < parametersGround.size(); i++ )
{
parametersGround.get( i ).setUniformVar( "v3InvWavelength", invWaveLength );
parametersSky.get( i ).setUniformVar( "v3InvWavelength", invWaveLength );
}
Tuple3f.toPool( invWaveLength );
Vector4f.toPool( wavelength4 );
}
/**
* {@inheritDoc}
*/
@Override
public void setWavelength3( Tuple3f wavelength3 )
{
super.setWavelength3( wavelength3 );
updateShaderWavelength( wavelength3 );
}
public final void transformVector( Quaternion4f q, Tuple3f v, Vector3f out )
{
Matrix3f m = Matrix3f.fromPool();
m.set( q );
m.mul( v, out );
Matrix3f.toPool( m );
}
private void setShaderCameraPos( Point3f cameraPos, Vector3f translation, AxisAngle3f angle )
{
Vector3f cam = new Vector3f();
cam.set( cameraPos.getX(), cameraPos.getY(), cameraPos.getZ() );
cam.sub( translation );
Quaternion4f rotate = Quaternion4f.fromPool();
if ( angle.getAngle() != 0f )
{
rotate.set( angle );
}
else
{
rotate.setFromAxisAngle( 0f, 1f, 0f, 0f );
}
rotate.invert();
Vector3f computedCam = Vector3f.fromPool();
transformVector( rotate, cam, computedCam );
float camHeight = computedCam.length();
Quaternion4f.toPool( rotate );
for ( int i = 0; i < parametersGround.size(); i++ )
{
GLSLParameters paramsGround = parametersGround.get( i );
GLSLParameters paramsSky = parametersSky.get( i );
paramsGround.setUniformVar( "v3CameraPos", computedCam );
paramsGround.setUniformVar( "fCameraHeight", camHeight );
paramsGround.setUniformVar( "fCameraHeight2", camHeight * camHeight );
paramsSky.setUniformVar( "v3CameraPos", computedCam );
paramsSky.setUniformVar( "fCameraHeight", camHeight );
paramsSky.setUniformVar( "fCameraHeight2", camHeight * camHeight );
}
Vector3f.toPool( computedCam );
}
private void setShaderLightPos( Tuple3f lightPos, Vector3f translation, AxisAngle3f angle )
{
Quaternion4f rotate = Quaternion4f.fromPool();
if ( angle.getAngle() != 0f )
{
rotate.set( angle );
}
else
{
rotate.setFromAxisAngle( 0f, 1f, 0f, 0f );
}
rotate.invert();
lightPos.sub( translation );
Vector3f computedLight = Vector3f.fromPool();
transformVector( rotate, lightPos, computedLight );
computedLight.normalize();
for ( int i = 0; i < parametersGround.size(); i++ )
{
parametersGround.get( i ).setUniformVar( "v3LightPos", computedLight );
parametersSky.get( i ).setUniformVar( "v3LightPos", computedLight );
}
Vector3f.toPool( computedLight );
Quaternion4f.toPool( rotate );
}
private void initShaderParameters( GLSLParameters paramsGround, GLSLParameters paramsSky )
{
paramsGround.setUniformVar( "fInnerRadius", innerRadius );
paramsGround.setUniformVar( "fInnerRadius2", innerRadius * innerRadius );
paramsGround.setUniformVar( "fOuterRadius", outerRadius );
paramsGround.setUniformVar( "fOuterRadius2", outerRadius * outerRadius );
paramsGround.setUniformVar( "fKrESun", kr * eSun );
paramsGround.setUniformVar( "fKmESun", km * eSun );
paramsGround.setUniformVar( "fKr4PI", getKr4Pi() );
paramsGround.setUniformVar( "fKm4PI", getKm4Pi() );
paramsGround.setUniformVar( "fScale", getScale() );
paramsGround.setUniformVar( "fScaleDepth", rayleighScaleDepth );
paramsGround.setUniformVar( "fScaleOverScaleDepth", ( 1f / ( outerRadius - innerRadius ) ) / rayleighScaleDepth );
paramsGround.setUniformVar( "g", g );
paramsGround.setUniformVar( "g2", g * g );
paramsGround.setUniformVar( "nSamples", nbSamples );
paramsGround.setUniformVar( "fSamples", (float)nbSamples );
paramsGround.setUniformVar( "s2Tex2", 0 );
paramsSky.setUniformVar( "fInnerRadius", innerRadius );
paramsSky.setUniformVar( "fInnerRadius2", innerRadius * innerRadius );
paramsSky.setUniformVar( "fOuterRadius", outerRadius );
paramsSky.setUniformVar( "fOuterRadius2", outerRadius * outerRadius );
paramsSky.setUniformVar( "fKrESun", kr * eSun );
paramsSky.setUniformVar( "fKmESun", km * eSun );
paramsSky.setUniformVar( "fKr4PI", getKr4Pi() );
paramsSky.setUniformVar( "fKm4PI", getKm4Pi() );
paramsSky.setUniformVar( "fScale", getScale() );
paramsSky.setUniformVar( "fScaleDepth", rayleighScaleDepth );
paramsSky.setUniformVar( "fScaleOverScaleDepth", ( 1f / ( outerRadius - innerRadius ) ) / rayleighScaleDepth );
paramsSky.setUniformVar( "g", g );
paramsSky.setUniformVar( "g2", g * g );
paramsSky.setUniformVar( "nSamples", nbSamples );
paramsSky.setUniformVar( "fSamples", (float)nbSamples );
}
private static URL getResource( String resName ) throws IOException
{
URL url = GLSLAtmosphereFactory.class.getClassLoader().getResource( resName );
if ( url == null )
{
throw new IOException( "Could not find resource \"" + resName + "\"." );
}
return ( url );
}
private void loadShaderPrograms() throws IOException
{
if ( groundFromSpace == null )
{
GLSLVertexShader vertShader = GLSLShaderLoader.getInstance().loadVertexShader( getResource( "resources/org/xith3d/shaders/atmosphere/ground_from_space.glslvert" ) );
groundFromSpace = new GLSLShaderProgram();
groundFromSpace.addShader( vertShader );
GLSLFragmentShader fragShader = GLSLShaderLoader.getInstance().loadFragmentShader( getResource( "resources/org/xith3d/shaders/atmosphere/ground_from_space.glslfrag" ) );
groundFromSpace.addShader( fragShader );
}
if ( skyFromSpace == null )
{
GLSLVertexShader vertShader = GLSLShaderLoader.getInstance().loadVertexShader( getResource( "resources/org/xith3d/shaders/atmosphere/sky_from_space.glslvert" ) );
skyFromSpace = new GLSLShaderProgram();
skyFromSpace.addShader( vertShader );
GLSLFragmentShader fragShader = GLSLShaderLoader.getInstance().loadFragmentShader( getResource( "resources/org/xith3d/shaders/atmosphere/sky_from_space.glslfrag" ) );
skyFromSpace.addShader( fragShader );
}
}
/**
* {@inheritDoc}
*/
@Override
protected void prepareAtmosphere( Sphere sphere, float atmosphereRadius, PointLight light )
{
try
{
loadShaderPrograms();
}
catch ( IOException e )
{
throw new Error( e );
}
innerRadius = sphere.getRadius();
outerRadius = atmosphereRadius;
// Applying the shader to the first shape
Appearance appGround = sphere.getAppearance( true );
GLSLContext prgGround = new GLSLContext( groundFromSpace );
parametersGround.add( prgGround.getUniformParameters() );
appGround.setShaderProgramContext( prgGround );
// the atmospheric sphere is added
Sphere atmoSphere = new Sphere( outerRadius, 100, 100, Colorf.BLUE );
GLSLContext prgSky = new GLSLContext( skyFromSpace );
parametersSky.add( prgSky.getUniformParameters() );
Appearance appSky = atmoSphere.getAppearance( true );
TransparencyAttributes attributes = new TransparencyAttributes();
attributes.setMode( BlendMode.BLENDED );
attributes.setSrcBlendFunction( BlendFunction.ONE );
attributes.setDstBlendFunction( BlendFunction.ONE );
appSky.setTransparencyAttributes( attributes );
appSky.getPolygonAttributes( true ).setFaceCullMode( FaceCullMode.BACK );
appSky.getPolygonAttributes( true ).setDrawMode( DrawMode.FILL );
appSky.setShaderProgramContext( prgSky );
initShaderParameters( prgGround.getUniformParameters(), prgSky.getUniformParameters() );
sphere.getParent().addChild( atmoSphere );
updateShaderWavelength( null );
}
public void update( long gameTime, long frameTime, TimingMode timingMode )
{
// We do not update the shader if the shape is attached in the scenegraph
if ( getSphere().getRoot() == null )
{
return;
}
// using quaternions to avoid grimbal lock !
Transform3D world = getSphere().getWorldTransform();
Quaternion4f quat = Quaternion4f.fromPool();
AxisAngle3f angle = AxisAngle3f.fromPool();
world.get( quat );
angle.set( quat );
Vector3f translation = world.getTranslation();
Quaternion4f.toPool( quat );
Point3f cameraPos = getSphere().getRoot().getSceneGraph().getView().getPosition();
setShaderCameraPos( cameraPos, translation, angle );
Point3f lightPos = Point3f.fromPool();
getLightPos( lightPos );
setShaderLightPos( lightPos, translation, angle );
Point3f.toPool( lightPos );
AxisAngle3f.toPool( angle );
}
public GLSLAtmosphereFactory()
{
}
}