Introducing texture mapping
ExTexture.java

	

//
//  CLASS
//    ExTexture	-  illustrate use of textures
//
//  LESSON
//    Use Texture2D and TextureAttributes to apply a texture image
//    to a shape.
//
//  AUTHOR
//    David R. Nadeau / 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
{
	//--------------------------------------------------------------
	//  SCENE CONTENT
	//--------------------------------------------------------------

	//
	//  Nodes (updated via menu)
	//
	private Shape3D shape = null;			// overall scene shape
	private Appearance app = null;			// geometry appearance
	private Appearance dummyApp = null;		// temporary appearance
	private TextureAttributes texatt = null;// texture attributes
	private TextureAttributes dummyAtt = null;// temporary texture attributes
	private Texture2D tex = null;			// current texture

	//
	//  Build scene
	//
	public Group buildScene( )
	{
		// Get the current menu choices for appearance attributes
		int textureMode = ((Integer)modes[currentMode].value).intValue( );
		Color3f color = (Color3f)colors[currentColor].value;
		Color3f blendColor = (Color3f)colors[currentBlendColor].value;

		// Turn on the example headlight
		setHeadlightEnable( true );

		// Default to examine navigation
		setNavigationType( Examine );

		// Disable scene graph compilation for this example
		setCompilable( false );

		// Create the scene group
		Group scene = new Group( );


	// BEGIN EXAMPLE TOPIC
		// Set up a basic material
		Material mat = new Material( );
		mat.setAmbientColor( 0.2f, 0.2f, 0.2f );
		mat.setDiffuseColor( 1.0f, 1.0f, 1.0f );
		mat.setSpecularColor( 0.0f, 0.0f, 0.0f );
		mat.setLightingEnable( true );

		// Set up the texturing attributes with an initial
		// texture mode, texture transform, and blend color
		texatt = new TextureAttributes( );
		texatt.setPerspectiveCorrectionMode( TextureAttributes.NICEST );
		texatt.setTextureMode( textureMode );
		texatt.setTextureTransform( new Transform3D( ) );  // Identity
		texatt.setTextureBlendColor( blendColor.x, blendColor.y, blendColor.z, 0.5f );

		// Enable changing these while the node component is live
		texatt.setCapability( TextureAttributes.ALLOW_MODE_WRITE );
		texatt.setCapability( TextureAttributes.ALLOW_BLEND_COLOR_WRITE );
		texatt.setCapability( TextureAttributes.ALLOW_TRANSFORM_WRITE );

		// Create an appearance using these attributes
		app = new Appearance( );
		app.setMaterial( mat );
		app.setTextureAttributes( texatt );
		app.setTexture( tex );

		// And enable changing these while the node component is live
		app.setCapability( Appearance.ALLOW_TEXTURE_WRITE );
		app.setCapability( Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE );

		// Build a shape and enable changing its appearance
		shape = new Shape3D( buildGeometry( ), app );
		shape.setCapability( Shape3D.ALLOW_APPEARANCE_WRITE );
	// END EXAMPLE TOPIC

		// Create some dummy appearance and tex attribute node components
		// In response to menu choices, we quickly switch the shape to
		// use one of these, then diddle with the main appearance or
		// tex attribute, then switch the shape back.  This effectively
		// makes the appearance or tex attributes we want to change
		// become un-live during a change.  We have to do this approach
		// because some texture features have no capability bits to set
		// to allow changes while live.
		dummyApp = new Appearance();
		dummyAtt = new TextureAttributes( );

		scene.addChild( shape );

		return scene;
	}





	//--------------------------------------------------------------
	//  USER INTERFACE
	//--------------------------------------------------------------

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


	//  Texture enable/disable
	private boolean textureOnOff = true;
	private CheckboxMenuItem textureOnOffMenu = null;

	//  Texture image choices
	private NameValue[] images =
	{
		new NameValue( "Red bricks",  	"brick.jpg" ),
		new NameValue( "Stone bricks", 	"stonebrk2.jpg" ),
		new NameValue( "Marble",  		"granite07rev.jpg" ),
		new NameValue( "Mud",  			"mud01.jpg" ),
		new NameValue( "Wood",  		"flooring.jpg" ),
		new NameValue( "Earth",  		"earthmap.jpg" ),
	};
	private CheckboxMenu imageMenu = null;
	private int currentImage = 0;


	//  Texture boundary mode choices
	private NameValue[] boundaries =
	{
		new NameValue( "CLAMP",  	new Integer( Texture.CLAMP ) ),
		new NameValue( "WRAP",  	new Integer( Texture.WRAP ) ),
	};
	private CheckboxMenu boundaryMenu = null;
	private int currentBoundary = 0;

	//  Texture boundary color choices
	private NameValue[] colors = {
		new NameValue( "White",       White ),
		new NameValue( "Gray",        Gray ),
		new NameValue( "Dark Gray",   DarkGray ),
		new NameValue( "Black",       Black ),
		new NameValue( "Red",         Red ),
		new NameValue( "Dark Red",    DarkRed ),
		new NameValue( "Green",       Green ),
		new NameValue( "Dark Green",  DarkGreen ),
		new NameValue( "Blue",        Blue ),
		new NameValue( "Dark Blue",   DarkBlue ),
	};
	private CheckboxMenu colorMenu = null;
	private int currentColor = 6;

	//  Texture filter choices
	private NameValue[] filters =
	{
		new NameValue( "POINT",  	new Integer( Texture.BASE_LEVEL_POINT ) ),
		new NameValue( "LINEAR",  	new Integer( Texture.BASE_LEVEL_LINEAR ) ),
	};
	private CheckboxMenu filterMenu = null;
	private int currentFilter = 0;


	// Texture attributes mode choices
	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 CheckboxMenu modeMenu = null;
	private int currentMode = 2;

	//  Texture attributes blend color choices
	private CheckboxMenu blendColorMenu = null;
	private int currentBlendColor = 6;

	private NameValue[] xforms =
	{
		new NameValue( "Identity", 	new Integer( 0 ) ),
		new NameValue( "Scale by 2", 	new Integer( 1 ) ),
		new NameValue( "Scale by 4", 	new Integer( 2 ) ),
		new NameValue( "Rotate by 45 degrees", 	new Integer( 3 ) ),
		new NameValue( "Translate by 0.25", 	new Integer( 4 ) ),
	};
	private CheckboxMenu xformMenu = null;
	private int currentXform = 0;



	private Texture2D[]	textureComponents;


	//
	//  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 menubar menu to change texture parameters
		//    Image -->
		//    Boundary mode -->
		//    Boundary color -->
		//    Filter mode -->
		//

		Menu m = new Menu( "Texture" );

		textureOnOffMenu = new CheckboxMenuItem(
			"Texturing enabled", textureOnOff );
		textureOnOffMenu.addItemListener( this );
		m.add( textureOnOffMenu );

		imageMenu = new CheckboxMenu( "Image", images,
			currentImage, this );
		m.add( imageMenu );

		boundaryMenu = new CheckboxMenu( "Boundary mode", boundaries,
			currentBoundary, this );
		m.add( boundaryMenu );

		colorMenu = new CheckboxMenu( "Boundary color", colors,
			currentColor, this );
		m.add( colorMenu );

		filterMenu = new CheckboxMenu( "Filter mode", filters,
			currentFilter, this );
		m.add( filterMenu );

		exampleMenuBar.add( m );


		//
		//  Add a menubar menu to change texture attributes parameters
		//    Mode -->
		//    Blend color -->
		//

		m = new Menu( "TextureAttributes" );

		modeMenu = new CheckboxMenu( "Mode", modes,
			currentMode, this );
		m.add( modeMenu );

		blendColorMenu = new CheckboxMenu( "Blend color", colors,
			currentBlendColor, this );
		m.add( blendColorMenu );

		xformMenu = new CheckboxMenu( "Transform", xforms,
			currentXform, this );
		m.add( xformMenu );

		exampleMenuBar.add( m );



		// 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];

		String value = null;
		for ( int i = 0; i < images.length; i++ )
		{
			value = (String)images[i].value;
			textureComponents[i] = loadTexture( value );
		}

		tex = textureComponents[ currentImage ];

	}


	//
	//  Handle checkboxes and menu choices
	//
	public void checkboxChanged( CheckboxMenu menu, int check )
	{
		if ( menu == imageMenu )
		{
			// Change the texture image
			currentImage = check;
			Texture tex = textureComponents[currentImage];
			int mode = ((Integer)boundaries[currentBoundary].value).intValue();
			Color3f color = (Color3f)colors[currentColor].value;
			int filter = ((Integer)filters[currentFilter].value).intValue( );

			shape.setAppearance( dummyApp );
			tex.setEnable( textureOnOff );
			tex.setBoundaryModeS( mode );
			tex.setBoundaryModeT( mode );
			tex.setBoundaryColor( color.x, color.y, color.z, 0.0f );
			tex.setMagFilter( filter );
			tex.setMinFilter( filter );
			app.setTexture( tex );
			shape.setAppearance( app );

			return;
		}

		if ( menu == boundaryMenu )
		{
			// Change the texture boundary mode
			currentBoundary = check;
			Texture tex = textureComponents[currentImage];
			int mode = ((Integer)boundaries[currentBoundary].value).intValue();

			shape.setAppearance( dummyApp );
			tex.setBoundaryModeS( mode );
			tex.setBoundaryModeT( mode );
			app.setTexture( tex );
			shape.setAppearance( app );

			return;
		}

		if ( menu == colorMenu )
		{
			// Change the boundary color
			currentColor = check;
			Color3f color = (Color3f)colors[currentColor].value;
			Texture tex = textureComponents[currentImage];

			shape.setAppearance( dummyApp );
			tex.setBoundaryColor( color.x, color.y, color.z, 0.0f );
			app.setTexture( tex );
			shape.setAppearance( app );

			return;
		}

		if ( menu == filterMenu )
		{
			// Change the filter mode
			currentFilter = check;
			int filter = ((Integer)filters[currentFilter].value).intValue( );
			Texture tex = textureComponents[currentImage];

			shape.setAppearance( dummyApp );
			tex.setMagFilter( filter );
			tex.setMinFilter( filter );
			app.setTexture( tex );
			shape.setAppearance( app );

			return;
		}

		if ( menu == modeMenu )
		{
			// Change the texture mode
			currentMode = check;
			int mode = ((Integer)modes[currentMode].value).intValue( );

			app.setTextureAttributes( dummyAtt );
			texatt.setTextureMode( mode );
			app.setTextureAttributes( texatt );

			return;
		}

		if ( menu == blendColorMenu )
		{
			// Change the boundary color
			currentBlendColor = check;
			Color3f color = (Color3f)colors[currentBlendColor].value;

			app.setTextureAttributes( dummyAtt );
			texatt.setTextureBlendColor( color.x, color.y, color.z, 0.5f );
			app.setTextureAttributes( texatt );

			return;
		}

		if ( menu == xformMenu )
		{
			// Change the texture transform
			currentXform = check;
			Transform3D tt = new Transform3D( );
			switch ( currentXform )
			{
			default:
			case 0:
				// Identity
				texatt.setTextureTransform( tt );	
				return;

			case 1:
				// Scale by 2
				tt.setScale( 2.0 );
				texatt.setTextureTransform( tt );	
				return;

			case 2:
				// Scale by 4
				tt.setScale( 4.0 );
				texatt.setTextureTransform( tt );	
				return;

			case 3:
				// Z rotate by 45 degrees
				tt.rotZ( Math.PI/4.0 );
				texatt.setTextureTransform( tt );	
				return;

			case 4:
				// Translate by 0.25
				tt.set( new Vector3f( 0.25f, 0.0f, 0.0f ) );
				texatt.setTextureTransform( tt );	
				return;
			}
		}

		// Handle all other checkboxes
		super.checkboxChanged( menu, check );
	}


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

		// Check if it is the texture on/off choice
		if ( src == textureOnOffMenu )
		{
			textureOnOff = textureOnOffMenu.getState( );
			Texture tex = textureComponents[currentImage];
			tex.setEnable( textureOnOff );
		}

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





	//--------------------------------------------------------------
	//  UTILITY METHODS
	//--------------------------------------------------------------

	//
	//  Handle loading a texture and setting up its attributes
	//
	private Texture2D loadTexture( String filename )
	{
		// Load the texture image file
		if ( debug )
			System.err.println( "Loading texture '" + filename + "'" );
		TextureLoader texLoader = new TextureLoader( filename, this);

		// If the image is NULL, something went wrong
		ImageComponent2D ic = texLoader.getImage( );
		if ( ic == null )
		{
			System.err.println( "Cannot load texture '" + filename + "'" );
			return null;
		}

		// Configure a Texture2D with the image
		Texture2D t = (Texture2D)texLoader.getTexture( );

		int mode = ((Integer)boundaries[currentBoundary].value).intValue();
		t.setBoundaryModeS( mode );
		t.setBoundaryModeT( mode );

		Color3f color = (Color3f)colors[currentColor].value;
		t.setBoundaryColor( color.x, color.y, color.z, 0.0f );

		int filter = ((Integer)filters[currentFilter].value).intValue( );
		t.setMagFilter( filter );
		t.setMinFilter( filter );

		t.setMipMapMode( Texture.BASE_LEVEL );

		// Turn it on and allow future changes
		t.setEnable( true );
		t.setCapability( Texture.ALLOW_ENABLE_WRITE );

		return t;
	}

	//
	//  Build a cube using a QuadArray
	//
	public QuadArray 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 =  0.0f;

		//           Coordinate             Normal               Texture
		//             X      Y      Z       I      J      K      S    T

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

		// Back
		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,  MIN, MIN );
		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,  MAX, MAX );

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

		// Left
		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,  MIN, MAX );
		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,  MAX, MIN );

		// Top
		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,  MAX, MAX );
		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,  MIN, MIN );

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

		return cube;
	}

	//
	//  Use a helper class to manage the coordinate lists
	//
	private class VertexList
	{
		private int index = 0;
		private GeometryArray ga = null;

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

		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++;
		}
	}
}