//
//  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 );
	}
}
