//
//  CLASS
//    ExTexture	-  illustrate use of textures
//
//  LESSON
//    Use Texture2D and TextureAttributes to apply a texture image
//    to a shape.
//
//  AUTHOR
//    Michael J. Bailey / San Diego Supercomputer Center
//

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.lang.*;
import java.net.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.image.*;

public class ExTexture
	extends Example
{
	//  nodes that can be updated via a menu:

	private Shape3D sh = null;			// overall scene shape
	private TransformGroup geomGroup = null;	// group of geometry
	private GeometryArray geom = null;		// scene geometry
	private Appearance app = null;			// geometry appearance
	private Appearance dummyApp = null;		// temporary appearance
	private Material mat = null;			// geometry surface color
	private PolygonAttributes polyatt = null;	// polygon attributes
	private TextureAttributes texatt = null;	// texture attributes
	private TextureAttributes dummyAtt = null;	// temporary texture attributes
	private Transform3D tt = null;			// texture transform
	private Texture2D tex = null;			// current texture
	private TransformGroup tg = null;		// object transform
	private Transform3D tgt = null;			// object transform


	//  Build the scene:

	public Group buildScene()
	{
		// Turn on the headlight
		setHeadlightEnable( true );

		// Build the scene root
		Group scene = new Group();


		// Create application bounds
		BoundingSphere worldBounds = new BoundingSphere(
			new Point3d( 0.0, 0.0, 0.0 ),  // Center
			1000.0 );                      // Extent


		mat = new Material();
		mat.setAmbientColor( 0.6f, 0.6f, 0.6f );
		mat.setDiffuseColor( 1.0f, 0.0f, 0.0f );
		mat.setSpecularColor( 0.0f, 0.0f, 0.f );

		tt = new Transform3D();
		tt.setIdentity();
		tt.setScale( 1. );

		polyatt = new PolygonAttributes();
		polyatt.setCullFace( PolygonAttributes.CULL_NONE );
		polyatt.setPolygonMode( PolygonAttributes.POLYGON_FILL );

		dummyAtt = new TextureAttributes();

		texatt = new TextureAttributes();
		texatt.setCapability( TextureAttributes.ALLOW_MODE_WRITE );
		texatt.setCapability( TextureAttributes.ALLOW_BLEND_COLOR_WRITE );
		texatt.setCapability( TextureAttributes.ALLOW_TRANSFORM_WRITE );
		texatt.setTextureMode( ((Integer)modes[currentMode].value).intValue() );
		texatt.setPerspectiveCorrectionMode( TextureAttributes.NICEST );
		texatt.setTextureTransform( tt );
		texatt.setTextureBlendColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.5f ) );

		dummyApp = new Appearance();

		app = new Appearance();
		app.setCapability( Appearance.ALLOW_TEXTURE_WRITE );
		app.setCapability( Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE );
		app.setMaterial( mat );
		app.setPolygonAttributes( polyatt );
		app.setTextureAttributes( texatt );
		app.setTexture( tex );

		sh = new Shape3D();
		sh.setAppearance( app );
		sh.setCapability( Shape3D.ALLOW_GEOMETRY_WRITE );
		sh.setCapability( Shape3D.ALLOW_APPEARANCE_WRITE );
		buildGeometry();
		sh.setGeometry( geom );

		tgt = new Transform3D();
		tgt.rotX( 0.0 * Math.PI / 6. );
		tg = new TransformGroup( tgt );
		tg.addChild( sh );

		scene.addChild( tg );

		return scene;
	}


	//
	//  Main (if invoked as an application)
	//
	public static void main( String[] args )
	{
		ExTexture ex = new ExTexture();
		ex.initialize( args );
		ex.buildUniverse();
		ex.showFrame();
	}

	//  Image menu choices
	private int currentTexture = 0;
	private int currentMode = 0;
	private int currentBoundary = 0;
	private int currentFilter = 0;
	private CheckboxMenuItem[] textureMenu;
	private CheckboxMenuItem[] modeMenu;
	private CheckboxMenuItem[] boundaryMenu;
	private CheckboxMenuItem[] filterMenu;
	private CheckboxMenuItem[] xformMenu;

	private NameValue[] images =
	{
		new NameValue( "Earth",  	"earth.jpg" ),
	};

	private NameValue[] modes =
	{
		new NameValue( "BLEND",  	new Integer( TextureAttributes.BLEND ) ),
		new NameValue( "DECAL",  	new Integer( TextureAttributes.DECAL ) ),
		new NameValue( "MODULATE",  	new Integer( TextureAttributes.MODULATE ) ),
		new NameValue( "REPLACE",  	new Integer( TextureAttributes.REPLACE ) ),
	};

	private NameValue[] boundaries =
	{
		new NameValue( "CLAMP",  	new Integer( Texture.CLAMP ) ),
		new NameValue( "WRAP",  	new Integer( Texture.WRAP ) ),
	};

	private NameValue[] filters =
	{
		new NameValue( "POINT",  	new Integer( Texture.BASE_LEVEL_POINT ) ),
		new NameValue( "LINEAR",  	new Integer( Texture.BASE_LEVEL_LINEAR ) ),
	};

	private Texture2D[]	textureComponents;
	private TextureLoader[]	texLoader;


	//
	//  Initialize the GUI (application and applet)
	//
	public void initialize( String[] args )
	{
		// Initialize the window, menubar, etc.
		super.initialize( args );
		exampleFrame.setTitle( "Java 3D Texture Mapping Example" );

		// Add a menu to select among texture options

		Menu mt = new Menu( "Texture" );
		textureMenu = new CheckboxMenuItem[ images.length ];
		for( int i = 0; i < images.length; i++ )
		{
			textureMenu[i] = new CheckboxMenuItem( images[i].name );
			textureMenu[i].addItemListener( this );
			textureMenu[i].setState( false );
			mt.add( textureMenu[i] );
		}
		exampleMenuBar.add( mt );

		// Add a menu to select the texture mode:

		Menu mm = new Menu( "Texture Mode" );
		modeMenu = new CheckboxMenuItem[ modes.length ];
		for( int i = 0; i < modes.length; i++ )
		{
			modeMenu[i] = new CheckboxMenuItem( modes[i].name );
			modeMenu[i].addItemListener( this );
			modeMenu[i].setState( false );
			mm.add( modeMenu[i] );
		}
		exampleMenuBar.add( mm );

		// Add a menu to select the texture boundary:

		Menu mb = new Menu( "Texture Boundary" );
		boundaryMenu = new CheckboxMenuItem[ boundaries.length ];
		for( int i = 0; i < boundaries.length; i++ )
		{
			boundaryMenu[i] = new CheckboxMenuItem( boundaries[i].name );
			boundaryMenu[i].addItemListener( this );
			boundaryMenu[i].setState( false );
			mb.add( boundaryMenu[i] );
		}
		exampleMenuBar.add( mb );

		// Add a menu to select the filter mode:

		Menu mf = new Menu( "Filter Mode" );
		filterMenu = new CheckboxMenuItem[ filters.length ];
		for( int i = 0; i < filters.length; i++ )
		{
			filterMenu[i] = new CheckboxMenuItem( filters[i].name );
			filterMenu[i].addItemListener( this );
			filterMenu[i].setState( false );
			mf.add( filterMenu[i] );
		}
		exampleMenuBar.add( mf );

		// Add a menu to select the texture transformation:

		Menu mx = new Menu( "Transformation" );
		xformMenu = new CheckboxMenuItem[ 3 ];

		xformMenu[0] = new CheckboxMenuItem( "Identity" );
		xformMenu[0].addItemListener( this );
		xformMenu[0].setState( true );
		mx.add( xformMenu[0] );

		xformMenu[1] = new CheckboxMenuItem( "Rotate 45" );
		xformMenu[1].addItemListener( this );
		xformMenu[1].setState( false );
		mx.add( xformMenu[1] );

		xformMenu[2] = new CheckboxMenuItem( "Scale 2" );
		xformMenu[2].addItemListener( this );
		xformMenu[2].setState( false );
		mx.add( xformMenu[2] );

		exampleMenuBar.add( mx );


		// set the current values:

		currentTexture = 0;
		textureMenu[currentTexture].setState( true );

		currentMode = 3;
		modeMenu[currentMode].setState( true );

		currentBoundary = 0;
		boundaryMenu[currentBoundary].setState( true );

		currentFilter = 0;
		filterMenu[currentFilter].setState( true );


		// Preload the texture images
		//   Use the texture loading utility to read in the texture
		//   files and process them into an ImageComponent2D
		//   for use in the Background node.

		if( debug )
			System.err.println( "Loading textures..." );
		textureComponents = new Texture2D[images.length];
		texLoader = new TextureLoader[images.length];
		String value = null;
		for( int i = 0; i < images.length; i++ )
		{
			value = (String)images[i].value;
			if( debug )
				System.err.println( "Loading texture '" + value + "'" );
			texLoader[i] = new TextureLoader( value, this);
			ImageComponent2D ic = texLoader[i].getImage();
			if( ic == null )
			{
				System.err.println( "Cannot load texture '" + value + "'" );
				textureComponents[i] = null;
			}
			else
			{
				Texture2D t = (Texture2D) texLoader[i].getTexture();
				t.setBoundaryModeS( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryModeT( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.0f ) );
				t.setMagFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMinFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMipMapMode( Texture.BASE_LEVEL );
				t.setCapability( Texture.ALLOW_BOUNDARY_COLOR_READ );
				t.setCapability( Texture.ALLOW_BOUNDARY_MODE_READ );
				t.setCapability( Texture.ALLOW_ENABLE_READ );
				t.setCapability( Texture.ALLOW_FILTER_READ );
				t.setCapability( Texture.ALLOW_IMAGE_READ );
				t.setCapability( Texture.ALLOW_MIPMAP_MODE_READ );
				t.setCapability( Texture.ALLOW_ENABLE_WRITE );
				t.setEnable( true );
				textureComponents[i] = t;
			}
		}

		tex = textureComponents[ currentTexture ];

	}


	//
	//  Handle checkboxes
	//
	public void itemStateChanged( ItemEvent event )
	{
		Object src = event.getSource();

		// Check if it is an image choice
		for( int i = 0; i < textureMenu.length; i++ )
		{
			if( src == textureMenu[i] )
			{
				// Update the checkboxes
				textureMenu[currentTexture].setState( false );
				currentTexture = i;
				textureMenu[currentTexture].setState( true );

				// Set the texture
				sh.setAppearance( dummyApp );
				tex = textureComponents[currentTexture];
				app.setTexture( tex );
				sh.setAppearance( app );
				return;
			}
		}


		// Check if it is a mode choice
		for( int i = 0; i < modeMenu.length; i++ )
		{
			if( src == modeMenu[i] )
			{
				// Update the checkboxes
				modeMenu[currentMode].setState( false );
				currentMode = i;
				modeMenu[currentMode].setState( true );

				// Set the mode

				app.setTextureAttributes( dummyAtt );
				texatt.setTextureMode( ((Integer)modes[currentMode].value).intValue() );
				app.setTextureAttributes( texatt );
				return;
			}
		}

		// Check if it is a boundary choice
		for( int i = 0; i < boundaryMenu.length; i++ )
		{
			if( src == boundaryMenu[i] )
			{
				// Update the checkboxes
				boundaryMenu[currentBoundary].setState( false );
				currentBoundary = i;
				boundaryMenu[currentBoundary].setState( true );

				// Set the boundary
				String value = (String)images[currentTexture].value;
				if( debug )
					System.err.println( "Re-loading texture '" + value + "'" );
				TextureLoader tl = new TextureLoader( value, this );
				Texture2D t = (Texture2D) tl.getTexture();
				t.setBoundaryModeS( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryModeT( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.0f ) );
				t.setMagFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMinFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMipMapMode( Texture.BASE_LEVEL );
				t.setCapability( Texture.ALLOW_ENABLE_WRITE );
				t.setEnable( true );
				app.setTexture( t );
				tex = textureComponents[currentTexture] = t;
				if( debug )
					System.err.println( "Ready." );
				return;
			}
		}

		// Check if it is a filter choice
		for( int i = 0; i < filterMenu.length; i++ )
		{
			if( src == filterMenu[i] )
			{
				// Update the checkboxes
				filterMenu[currentFilter].setState( false );
				currentFilter = i;
				filterMenu[currentFilter].setState( true );

				// Set the filter
				String value = (String)images[currentTexture].value;
				if( debug )
					System.err.println( "Re-loading texture '" + value + "'" );
				TextureLoader tl = new TextureLoader( value, this );
				Texture2D t = (Texture2D) tl.getTexture();
				t.setBoundaryModeS( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryModeT( ((Integer)boundaries[currentBoundary].value).intValue() );
				t.setBoundaryColor( new Color4f( 0.0f, 1.0f, 0.0f, 0.0f ) );
				t.setMagFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMinFilter( ((Integer)filters[currentFilter].value).intValue() );
				t.setMipMapMode( Texture.BASE_LEVEL );
				t.setCapability( Texture.ALLOW_ENABLE_WRITE );
				t.setEnable( true );
				app.setTexture( t );
				tex = textureComponents[currentTexture] = t;
				if( debug )
					System.err.println( "Ready." );
				return;
			}
		}

		// Check if it is a texture transform choice
		for( int i = 0; i < 3; i++ )
		{
			if( src == xformMenu[0] )
			{
				tt = new Transform3D( );
				tt.setIdentity();
				texatt.setTextureTransform( tt );	
				xformMenu[0].setState( true );
				xformMenu[1].setState( false );
				xformMenu[2].setState( false );
				if( debug )
					System.err.println( "Ready." );
				return;
			}
			else if( src == xformMenu[1] )
			{
				tt = new Transform3D( );
				tt.setIdentity();
				tt.rotZ( Math.PI/4. );
				texatt.setTextureTransform( tt );	
				xformMenu[0].setState( false );
				xformMenu[1].setState( true );
				xformMenu[2].setState( false );
				if( debug )
					System.err.println( "Ready." );
				return;
			}
			else if( src == xformMenu[2] )
			{
				tt = new Transform3D( );
				tt.setIdentity();
				tt.setScale( 2. );
				texatt.setTextureTransform( tt );	
				xformMenu[0].setState( false );
				xformMenu[1].setState( false );
				xformMenu[2].setState( true );
				if( debug )
					System.err.println( "Ready." );
				return;
			}
		}

		// Handle all other checkboxes
		super.itemStateChanged( event );
	}


	public void buildGeometry()
	{
		QuadArray cube = new QuadArray(
					24,
					GeometryArray.COORDINATES |
					GeometryArray.NORMALS     |
					GeometryArray.TEXTURE_COORDINATE_2 );
		cube.setCapability( GeometryArray.ALLOW_COORDINATE_WRITE );
		cube.setCapability( GeometryArray.ALLOW_TEXCOORD_WRITE );

		VertexList vl = new VertexList( cube );

		float MAX =  1.0f;
		float MIN = -1.0f;

		vl.xyzijkst( -1.0f, -1.0f,  1.0f,	 0.0f,  0.0f,  1.0f,	MIN, MIN );
		vl.xyzijkst(  1.0f, -1.0f,  1.0f,	 0.0f,  0.0f,  1.0f,	MAX, MIN );
		vl.xyzijkst(  1.0f,  1.0f,  1.0f,	 0.0f,  0.0f,  1.0f,	MAX, MAX );
		vl.xyzijkst( -1.0f,  1.0f,  1.0f,	 0.0f,  0.0f,  1.0f,	MIN, MAX );
 
		vl.xyzijkst( -1.0f, -1.0f, -1.0f,	 0.0f,  0.0f, -1.0f,	MIN, MIN );
		vl.xyzijkst(  1.0f, -1.0f, -1.0f,	 0.0f,  0.0f, -1.0f,	MAX, MIN );
		vl.xyzijkst(  1.0f,  1.0f, -1.0f,	 0.0f,  0.0f, -1.0f,	MAX, MAX );
		vl.xyzijkst( -1.0f,  1.0f, -1.0f,	 0.0f,  0.0f, -1.0f,	MIN, MAX );

		vl.xyzijkst(  1.0f, -1.0f, -1.0f,	 1.0f,  0.0f,  0.0f,	MIN, MIN );
		vl.xyzijkst(  1.0f,  1.0f, -1.0f,	 1.0f,  0.0f,  0.0f,	MAX, MIN );
		vl.xyzijkst(  1.0f,  1.0f,  1.0f,	 1.0f,  0.0f,  0.0f,	MAX, MAX );
		vl.xyzijkst(  1.0f, -1.0f,  1.0f,	 1.0f,  0.0f,  0.0f,	MIN, MAX );

		vl.xyzijkst( -1.0f, -1.0f, -1.0f,	-1.0f,  0.0f,  0.0f,	MIN, MIN );
		vl.xyzijkst( -1.0f,  1.0f, -1.0f,	-1.0f,  0.0f,  0.0f,	MAX, MIN );
		vl.xyzijkst( -1.0f,  1.0f,  1.0f,	-1.0f,  0.0f,  0.0f,	MAX, MAX );
		vl.xyzijkst( -1.0f, -1.0f,  1.0f,	-1.0f,  0.0f,  0.0f,	MIN, MAX );

		vl.xyzijkst( -1.0f,  1.0f, -1.0f,	 0.0f,  1.0f,  0.0f,	MIN, MIN );
		vl.xyzijkst(  1.0f,  1.0f, -1.0f,	 0.0f,  1.0f,  0.0f,	MAX, MIN );
		vl.xyzijkst(  1.0f,  1.0f,  1.0f,	 0.0f,  1.0f,  0.0f,	MAX, MAX );
		vl.xyzijkst( -1.0f,  1.0f,  1.0f,	 0.0f,  1.0f,  0.0f,	MIN, MAX );

		vl.xyzijkst( -1.0f, -1.0f, -1.0f,	 0.0f, -1.0f,  0.0f,	MIN, MIN );
		vl.xyzijkst(  1.0f, -1.0f, -1.0f,	 0.0f, -1.0f,  0.0f,	MAX, MIN );
		vl.xyzijkst(  1.0f, -1.0f,  1.0f,	 0.0f, -1.0f,  0.0f,	MAX, MAX );
		vl.xyzijkst( -1.0f, -1.0f,  1.0f,	 0.0f, -1.0f,  0.0f,	MIN, MAX );

		geom = cube;
	}


	public class
	VertexList
	{
		private int index = 0;
		private GeometryArray ga = null;

		public VertexList( GeometryArray _ga )
		{
			index = 0;
			ga = _ga;
		}

		public void xyzst( float x, float y, float z, float s, float t )
		{
			ga.setCoordinate( index, new Point3f( x, y, z ) );
			ga.setTextureCoordinate( index, new Point2f( s, t ) );
			index++;
		}

		public void xyzijkst(	float x, float y, float z,
					float i, float j, float k,
					float s, float t )
		{
			ga.setCoordinate( index, new Point3f( x, y, z ) );
			ga.setNormal( index, new Vector3f( i, j, k ) );
			ga.setTextureCoordinate( index, new Point2f( s, t ) );
			index++;
		}
	}

}
