Lighting the environment
ExHenge.java
//
// CLASS
// ExHenge - create a stone-henge like (vaguely) mysterious temple thing
//
// DESCRIPTION
// This example illustrates the use of a few of Java 3D's lighting
// types to create atmospheric lighting to make a structure look
// like it is glowing. In particular, we build a central emissive
// dome, unaffected by any lighting. Surrounding that dome are a
// series of arches that are lit by a one or more of a point
// light in the center, directional lights at front-left and
// back-right, and two ambient lights. Each of these lights can be
// turned on and off via menu items.
//
// SEE ALSO
// Arch
// ExAmbientLight
// ExDirectionalLight
// ExPointLight
//
// AUTHOR
// David R. Nadeau / San Diego Supercomputer Center
//
//
import java.awt.*;
import java.awt.event.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.image.*;
public class ExHenge
extends Example
{
//--------------------------------------------------------------
// SCENE CONTENT
//--------------------------------------------------------------
//
// Nodes (updated via menu)
//
private AmbientLight ambient = null;
private AmbientLight brightAmbient = null;
private DirectionalLight redDirectional = null;
private DirectionalLight yellowDirectional = null;
private PointLight orangePoint = null;
//
// Build scene
//
public Group buildScene( )
{
// Turn off the example headlight
setHeadlightEnable( false );
// Default to walk navigation
setNavigationType( Walk );
//
// Preload the texture images
//
if ( debug ) System.err.println( " textures..." );
Texture groundTex = null;
Texture spurTex = null;
Texture domeTex = null;
TextureLoader texLoader = null;
ImageComponent image = null;
texLoader = new TextureLoader( "mud01.jpg", this );
image = texLoader.getImage( );
if ( image == null )
System.err.println( "Cannot load mud01.jpg texture" );
else
{
groundTex = texLoader.getTexture( );
groundTex.setBoundaryModeS( Texture.WRAP );
groundTex.setBoundaryModeT( Texture.WRAP );
groundTex.setMinFilter( Texture.NICEST );
groundTex.setMagFilter( Texture.NICEST );
groundTex.setMipMapMode( Texture.BASE_LEVEL );
groundTex.setEnable( true );
}
texLoader = new TextureLoader( "stonebrk2.jpg", this );
image = texLoader.getImage( );
if ( image == null )
System.err.println( "Cannot load stonebrk2.jpg texture" );
else
{
spurTex = texLoader.getTexture( );
spurTex.setBoundaryModeS( Texture.WRAP );
spurTex.setBoundaryModeT( Texture.WRAP );
spurTex.setMinFilter( Texture.NICEST );
spurTex.setMagFilter( Texture.NICEST );
spurTex.setMipMapMode( Texture.BASE_LEVEL );
spurTex.setEnable( true );
}
texLoader = new TextureLoader( "fire.jpg", this );
image = texLoader.getImage( );
if ( image == null )
System.err.println( "Cannot load fire.jpg texture" );
else
{
domeTex = texLoader.getTexture( );
domeTex.setBoundaryModeS( Texture.WRAP );
domeTex.setBoundaryModeT( Texture.WRAP );
domeTex.setMinFilter( Texture.NICEST );
domeTex.setMagFilter( Texture.NICEST );
domeTex.setMipMapMode( Texture.BASE_LEVEL );
domeTex.setEnable( true );
}
//
// Build some shapes we'll need
//
if ( debug ) System.err.println( " flying buttresses..." );
// Build three types of spurs (flying buttresses)
Appearance spurApp = new Appearance( );
Material spurMat = new Material( );
spurMat.setAmbientColor( 0.6f, 0.6f, 0.6f );
spurMat.setDiffuseColor( 1.0f, 1.0f, 1.0f );
spurMat.setSpecularColor( 0.0f, 0.0f, 0.0f );
spurApp.setMaterial( spurMat );
Transform3D tr = new Transform3D( );
tr.setIdentity( );
tr.setScale( new Vector3d( 1.0, 4.0, 1.0 ) );
TextureAttributes spurTexAtt = new TextureAttributes( );
spurTexAtt.setTextureMode( TextureAttributes.MODULATE );
spurTexAtt.setPerspectiveCorrectionMode(
TextureAttributes.NICEST );
spurTexAtt.setTextureTransform( tr );
spurApp.setTextureAttributes( spurTexAtt );
if ( spurTex != null )
spurApp.setTexture( spurTex );
Arch spur1 = new Arch(
0.0, // start Phi
1.571, // end Phi
9, // nPhi
-0.0982, // start Theta
0.0982, // end Theta (11.25 degrees)
2, // nTheta
2.5, // start radius
1.0, // end radius
0.05, // start phi thickness
0.025, // end phi thickness
spurApp ); // appearance
Arch spur2 = new Arch(
0.0, // start Phi
1.571, // end Phi
9, // nPhi
-0.0982, // start Theta
0.0982, // end Theta (11.25 degrees)
2, // nTheta
1.5, // start radius
2.0, // end radius
0.05, // start phi thickness
0.025, // end phi thickness
spurApp ); // appearance
Arch spur3 = new Arch(
0.0, // start Phi
1.571, // end Phi
9, // nPhi
-0.0982, // start Theta
0.0982, // end Theta (11.25 degrees)
2, // nTheta
1.5, // start radius
1.0, // end radius
0.05, // start phi thickness
0.025, // end phi thickness
spurApp ); // appearance
Arch spur4 = new Arch(
0.0, // start Phi
1.178, // end Phi
9, // nPhi
-0.0982, // start Theta
0.0982, // end Theta (11.25 degrees)
2, // nTheta
4.0, // start radius
4.0, // end radius
0.05, // start phi thickness
0.025, // end phi thickness
spurApp ); // appearance
// Put each spur into a shared group so we can instance
// the spurs multiple times
SharedGroup spur1Group = new SharedGroup( );
spur1Group.addChild( spur1 );
spur1Group.compile( );
SharedGroup spur2Group = new SharedGroup( );
spur2Group.addChild( spur2 );
spur2Group.compile( );
SharedGroup spur3Group = new SharedGroup( );
spur3Group.addChild( spur3 );
spur3Group.compile( );
SharedGroup spur4Group = new SharedGroup( );
spur4Group.addChild( spur4 );
spur4Group.compile( );
// Build a central dome
if ( debug ) System.err.println( " central dome..." );
Appearance domeApp = new Appearance( );
// No material needed - we want the dome to glow,
// so use a REPLACE mode texture only
TextureAttributes domeTexAtt = new TextureAttributes( );
domeTexAtt.setTextureMode( TextureAttributes.REPLACE );
domeTexAtt.setPerspectiveCorrectionMode(
TextureAttributes.NICEST );
domeApp.setTextureAttributes( domeTexAtt );
if ( domeTex != null )
domeApp.setTexture( domeTex );
Arch dome = new Arch(
0.0, // start Phi
1.571, // end Phi
5, // nPhi
0.0, // start Theta
2.0*Math.PI, // end Theta (360 degrees)
17, // nTheta
1.0, // start radius
1.0, // end radius
0.0, // start phi thickness
0.0, // end phi thickness
domeApp ); // appearance
// Build the ground. Use a trick to get better lighting
// effects by using an elevation grid. The idea is this:
// for interactive graphics systems, such as those
// controlled by Java3D, lighting effects are computed only
// at triangle vertexes. Imagine a big rectangular ground
// underneath a PointLight (added below). If the
// PointLight is above the center of the square, in the real
// world we'd expect a bright spot below it, fading to
// darkness at the edges of the square. Not so in
// interactive graphics. Since lighting is only computed
// at vertexes, and the square's vertexes are each
// equidistant from a centered PointLight, all four square
// coordinates get the same brightness. That brightness
// is interpolated across the square, giving a *constant*
// brightness for the entire square! There is no bright
// spot under the PointLight. So, here's the trick: use
// more triangles. Pretty simple. Split the ground under
// the PointLight into a grid of smaller squares. Each
// smaller square is shaded using light brightness computed
// at the square's vertexes. Squares directly under the
// PointLight get brighter lighting at their vertexes, and
// thus they are bright. This gives the desired bright
// spot under the PointLight. The more squares we use
// (a denser grid), the more accurate the bright spot and
// the smoother the lighting gradation from bright directly
// under the PointLight, to dark at the distant edges. Of
// course, with more squares, we also get more polygons to
// draw and a performance slow-down. So there is a
// tradeoff between lighting quality and drawing speed.
// For this example, we'll use a coarse mesh of triangles
// created using an ElevationGrid shape.
if ( debug ) System.err.println( " ground..." );
Appearance groundApp = new Appearance( );
Material groundMat = new Material( );
groundMat.setAmbientColor( 0.3f, 0.3f, 0.3f );
groundMat.setDiffuseColor( 0.7f, 0.7f, 0.7f );
groundMat.setSpecularColor( 0.0f, 0.0f, 0.0f );
groundApp.setMaterial( groundMat );
tr = new Transform3D( );
tr.setScale( new Vector3d( 8.0, 8.0, 1.0 ) );
TextureAttributes groundTexAtt = new TextureAttributes( );
groundTexAtt.setTextureMode( TextureAttributes.MODULATE );
groundTexAtt.setPerspectiveCorrectionMode(
TextureAttributes.NICEST );
groundTexAtt.setTextureTransform( tr );
groundApp.setTextureAttributes( groundTexAtt );
if ( groundTex != null )
groundApp.setTexture( groundTex );
ElevationGrid ground = new ElevationGrid(
11, // X dimension
11, // Z dimension
2.0f, // X spacing
2.0f, // Z spacing
// Automatically use zero heights
groundApp ); // Appearance
//
// Build the scene using the shapes above. Place everything
// withing a TransformGroup.
//
// Build the scene root
TransformGroup scene = new TransformGroup( );
tr = new Transform3D( );
tr.setTranslation( new Vector3f( 0.0f, -1.6f, 0.0f ) );
scene.setTransform( tr );
// Create influencing bounds
BoundingSphere worldBounds = new BoundingSphere(
new Point3d( 0.0, 0.0, 0.0 ), // Center
1000.0 ); // Extent
// General Ambient light
ambient = new AmbientLight( );
ambient.setEnable( ambientOnOff );
ambient.setColor( new Color3f( 0.3f, 0.3f, 0.3f ) );
ambient.setCapability( AmbientLight.ALLOW_STATE_WRITE );
ambient.setInfluencingBounds( worldBounds );
scene.addChild( ambient );
// Bright Ambient light
brightAmbient = new AmbientLight( );
brightAmbient.setEnable( brightAmbientOnOff );
brightAmbient.setColor( new Color3f( 1.0f, 1.0f, 1.0f ) );
brightAmbient.setCapability( AmbientLight.ALLOW_STATE_WRITE );
brightAmbient.setInfluencingBounds( worldBounds );
scene.addChild( brightAmbient );
// Red directional light
redDirectional = new DirectionalLight( );
redDirectional.setEnable( redDirectionalOnOff );
redDirectional.setColor( new Color3f( 1.0f, 0.0f, 0.0f ) );
redDirectional.setDirection( new Vector3f( 1.0f, -0.5f, -0.5f ) );
redDirectional.setCapability( AmbientLight.ALLOW_STATE_WRITE );
redDirectional.setInfluencingBounds( worldBounds );
scene.addChild( redDirectional );
// Yellow directional light
yellowDirectional = new DirectionalLight( );
yellowDirectional.setEnable( yellowDirectionalOnOff );
yellowDirectional.setColor( new Color3f( 1.0f, 0.8f, 0.0f ) );
yellowDirectional.setDirection( new Vector3f( -1.0f, 0.5f, 1.0f ) );
yellowDirectional.setCapability( AmbientLight.ALLOW_STATE_WRITE );
yellowDirectional.setInfluencingBounds( worldBounds );
scene.addChild( yellowDirectional );
// Orange point light
orangePoint = new PointLight( );
orangePoint.setEnable( orangePointOnOff );
orangePoint.setColor( new Color3f( 1.0f, 0.5f, 0.0f ) );
orangePoint.setPosition( new Point3f( 0.0f, 0.5f, 0.0f ) );
orangePoint.setCapability( AmbientLight.ALLOW_STATE_WRITE );
orangePoint.setInfluencingBounds( worldBounds );
scene.addChild( orangePoint );
// Ground
scene.addChild( ground );
// Dome
scene.addChild( dome );
// Spur 1's
Group g = buildRing( spur1Group );
scene.addChild( g );
// Spur 2's
TransformGroup tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( 0.3927 );
tg.setTransform( tr );
g = buildRing( spur2Group );
tg.addChild( g );
scene.addChild( tg );
// Spur 3's
g = buildRing( spur3Group );
scene.addChild( g );
// Spur 4's
tg = new TransformGroup( );
tg.setTransform( tr );
g = buildRing( spur4Group );
tg.addChild( g );
scene.addChild( tg );
return scene;
}
//
// Build a ring of shapes, each shape contained in a given
// shared group
//
public Group buildRing( SharedGroup sg )
{
Group g = new Group( );
g.addChild( new Link( sg ) ); // 0 degrees
TransformGroup tg = new TransformGroup( );
Transform3D tr = new Transform3D( );
tr.rotY( 0.785 ); // 45 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( -0.785 ); // -45 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( 1.571 ); // 90 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( -1.571 ); // -90 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( 2.356 ); // 135 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( -2.356 ); // -135 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
tg = new TransformGroup( );
tr = new Transform3D( );
tr.rotY( Math.PI ); // 180 degrees
tg.setTransform( tr );
tg.addChild( new Link( sg ) );
g.addChild( tg );
return g;
}
//--------------------------------------------------------------
// USER INTERFACE
//--------------------------------------------------------------
//
// Main
//
public static void main( String[] args )
{
ExHenge ex = new ExHenge( );
ex.initialize( args );
ex.buildUniverse( );
ex.showFrame( );
}
// On/off choices
private boolean ambientOnOff = true;
private boolean brightAmbientOnOff = false;
private boolean redDirectionalOnOff = false;
private boolean yellowDirectionalOnOff = false;
private boolean orangePointOnOff = true;
private CheckboxMenuItem ambientOnOffMenu;
private CheckboxMenuItem brightAmbientOnOffMenu;
private CheckboxMenuItem redDirectionalOnOffMenu;
private CheckboxMenuItem yellowDirectionalOnOffMenu;
private CheckboxMenuItem orangePointOnOffMenu;
//
// Initialize the GUI (application and applet)
//
public void initialize( String[] args )
{
// Initialize the window, menubar, etc.
super.initialize( args );
exampleFrame.setTitle( "Java 3D ExHenge Example" );
//
// Add a menubar menu to change parameters
// Dim ambient light
// Bright ambient light
// Red directional light
// Yellow directional light
// Orange point light
//
Menu m = new Menu( "Lights" );
ambientOnOffMenu = new CheckboxMenuItem(
"Dim ambient light", ambientOnOff );
ambientOnOffMenu.addItemListener( this );
m.add( ambientOnOffMenu );
brightAmbientOnOffMenu = new CheckboxMenuItem(
"Bright ambient light", brightAmbientOnOff );
brightAmbientOnOffMenu.addItemListener( this );
m.add( brightAmbientOnOffMenu );
redDirectionalOnOffMenu = new CheckboxMenuItem(
"Red directional light", redDirectionalOnOff );
redDirectionalOnOffMenu.addItemListener( this );
m.add( redDirectionalOnOffMenu );
yellowDirectionalOnOffMenu = new CheckboxMenuItem(
"Yellow directional light", yellowDirectionalOnOff );
yellowDirectionalOnOffMenu.addItemListener( this );
m.add( yellowDirectionalOnOffMenu );
orangePointOnOffMenu = new CheckboxMenuItem(
"Orange point light", orangePointOnOff );
orangePointOnOffMenu.addItemListener( this );
m.add( orangePointOnOffMenu );
exampleMenuBar.add( m );
}
//
// Handle checkboxes
//
public void itemStateChanged( ItemEvent event )
{
Object src = event.getSource( );
if ( src == ambientOnOffMenu )
{
ambientOnOff = ambientOnOffMenu.getState( );
ambient.setEnable( ambientOnOff );
return;
}
if ( src == brightAmbientOnOffMenu )
{
brightAmbientOnOff = brightAmbientOnOffMenu.getState( );
brightAmbient.setEnable( brightAmbientOnOff );
return;
}
if ( src == redDirectionalOnOffMenu )
{
redDirectionalOnOff = redDirectionalOnOffMenu.getState( );
redDirectional.setEnable( redDirectionalOnOff );
return;
}
if ( src == yellowDirectionalOnOffMenu )
{
yellowDirectionalOnOff = yellowDirectionalOnOffMenu.getState( );
yellowDirectional.setEnable( yellowDirectionalOnOff );
return;
}
if ( src == orangePointOnOffMenu )
{
orangePointOnOff = orangePointOnOffMenu.getState( );
orangePoint.setEnable( orangePointOnOff );
return;
}
// Handle all other checkboxes
super.itemStateChanged( event );
}
}