www.pudn.com > tomohide_fur.03.13.02.zip > DynamicFur.cpp


//----------------------------------------------------------------------------- 
// File: DynamicFur.cpp 
// 
// Copyright (C) 2001-2002 Tomohide Kano. All rights reserved. 
//----------------------------------------------------------------------------- 
 
#pragma comment (lib, "d3dx8.lib") 
#define DECLARE_EXTENSION_SUBSTANCE 
#include  
#include  
#include  
#include "extensions.h" 
#include "FurMesh.h" 
#include "FurTexture.h" 
#include "NormalMap.h" 
#include "MeshFunc.h" 
#include "text.h" 
#include "timer.h" 
#include "trackball.h" 
 
 
//============================================================================= 
// constants 
//============================================================================= 
enum 
{ 
	MENU_NOP, 
	MENU_CHANGE_OBJECT, 
	MENU_TOGGLE_WIREFRAME, 
	MENU_INCREASE_FUR_LAYERS, 
	MENU_DECREASE_FUR_LAYERS, 
	MENU_INCREASE_FUR_LENGTH, 
	MENU_DECREASE_FUR_LENGTH, 
	MENU_TOGGLE_ANISOTROPIC_FILTER, 
	MENU_TOGGLE_BACKFACE_CULLING, 
	MENU_TOGGLE_ALPHA_TEST, 
	MENU_TOGGLE_LIGHT_ROTATION, 
	MENU_TOGGLE_DIFFUSE, 
	MENU_TOGGLE_SPECULAR, 
	MENU_TOGGLE_SIMULATION, 
	MENU_TOGGLE_GRAVITY, 
	MENU_SHOW_HIDE_NORMAL_MAP, 
	MENU_SHOW_HIDE_OFFSET_MAP, 
	MENU_SHOW_HIDE_INFO, 
	MENU_TOGGLE_FULLSCREEN, 
	MENU_EXIT 
}; 
 
#define FULLSCREEN_WIDTH  640 
#define FULLSCREEN_HEIGHT 480 
 
#define DEMO_MANUAL_MODE_TIMEOUT 5.0 
#define DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT 10.0 
 
 
//============================================================================= 
// static variables 
//============================================================================= 
 
// initialization flag 
static bool				s_Initialized = false; 
 
// miscellaneous states 
static int				s_NumLayers          = 20; 
static float			s_FurLength          = 0.7; 
static bool				s_Wireframe          = false; 
static bool				s_RotateLight        = false; 
static bool				s_UseAnisotropy      = true; 
static bool				s_CullBackface       = true; 
static bool				s_UseAlphaTest       = false; 
static bool				s_DrawDiffuse        = true; 
static bool				s_DrawSpecular       = true; 
static bool				s_UseSimulation      = true; 
static bool				s_UseGravity         = true; 
static bool				s_ShowNormalMap      = false; 
static bool				s_ShowOffsetMap      = false; 
static bool				s_FullScreenPossible = false; 
static bool				s_FullScreenMode     = false; 
static bool				s_ShowFPS            = false; 
static int				s_ShowInfo           = 1; 
 
// timer used to determine what mode the demo is in, 
// demo will go into automatic mode in 5 seconds if nothing is touched 
static float			s_IdleModeTimer = DEMO_MANUAL_MODE_TIMEOUT; 
 
// client area size 
static int				s_Width; 
static int				s_Height; 
 
// mouse tracking (modelview transformation) 
static bool				s_MouseState[3]	= { false, false, false }; 
static D3DXVECTOR2		s_Mouse; 
static D3DXVECTOR2		s_PrevMouse; 
static D3DXVECTOR3		s_Trans; 
static D3DXVECTOR3		s_TransDelta; 
static D3DXQUATERNION	s_Rot; 
static D3DXQUATERNION	s_RotDelta; 
 
// projection parameters 
static float			s_NearZ = 0.5; 
static float			s_FarZ  = 50.0; 
static float			s_FovY  = 30.0; 
 
// modelview matrix 
static D3DXMATRIX		s_ModelView; 
static D3DXMATRIX		s_ModelViewInv; 
 
// model meshes 
static FurMesh *		s_pMeshes      = NULL; 
static FurMesh *		s_pCurrentMesh = NULL; 
static int				s_CurrentMesh  = 0; 
 
// textures 
static FurTexture *		s_pFurTexture = NULL; 
static NormalMap *		s_pNormalMap  = NULL; 
static GLuint			s_texLighting = 0; 
 
// vertex/fragment shaders 
static GLuint			s_vsSkin = 0; 
static GLuint			s_vsFur  = 0; 
static GLuint			s_fsFur  = 0; 
 
// vertex shader invariants 
static GLuint			I_LIGHT_DIR    = 0; 
static GLuint			I_CAMERA_POS   = 0; 
static GLuint			I_FUR_LENGTH   = 0; 
static GLuint			I_OFFSET_SCALE = 0; 
 
 
//============================================================================= 
// function prototypes 
//============================================================================= 
void InitInterface(); 
bool SetupScreenMode(int width, int height, bool aAttemptFullScreen); 
void CleanUp(); 
bool Init(); 
bool InitShaders(); 
void MouseButton(int button, int state, int x, int y); 
void MouseMotion(int x, int y); 
void Menu(int item); 
void Keyboard(unsigned char key, int x, int y); 
void SpecialKey(int key, int x, int y); 
void Reshape(int w, int h); 
void Idle(); 
void Display(); 
void RenderFurMesh(); 
void ShowTexture(int x, int y, int w, int h); 
void ShowInfo(); 
 
 
 
//----------------------------------------------------------------------------- 
// main 
//----------------------------------------------------------------------------- 
int main(int argc, char *argv[]) 
{ 
	bool fullScreen = true; 
 
	atexit(CleanUp); 
 
	// initialize GLUT 
	glutInit(&argc, argv); 
	glutInitDisplayString("double rgb>=8 depth>=16"); 
 
	// check command line options 
	for (int i = 1; i < argc; i++) 
	{ 
		if (strcmp("-window", argv[i]) == 0) 
		{ 
			fullScreen = false; 
		} 
		if (strcmp("-showfps", argv[i]) == 0) 
		{ 
			s_ShowFPS = true; 
		} 
	} 
 
	s_FullScreenPossible = SetupScreenMode(FULLSCREEN_WIDTH, FULLSCREEN_HEIGHT, fullScreen); 
	s_FullScreenMode = s_FullScreenPossible; 
 
	InitInterface(); 
 
	return 0; 
} 
 
 
//----------------------------------------------------------------------------- 
// InitInterface 
//----------------------------------------------------------------------------- 
void InitInterface() 
{ 
	// register GLUT callback functions 
	glutKeyboardFunc(Keyboard); 
	glutSpecialFunc(SpecialKey); 
	glutReshapeFunc(Reshape); 
	glutMouseFunc(MouseButton); 
	glutMotionFunc(MouseMotion); 
	glutIdleFunc(Idle); 
	glutDisplayFunc(Display); 
 
	// initialize popup menu if not in full screen mode 
	if (!s_FullScreenMode) 
	{ 
		glutCreateMenu(Menu); 
		glutAddMenuEntry("Change Object [Enter]", MENU_CHANGE_OBJECT); 
		glutAddMenuEntry("Toggle Wireframe [W]", MENU_TOGGLE_WIREFRAME); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		glutAddMenuEntry("Increase Fur Layers [PageUp]", MENU_INCREASE_FUR_LAYERS); 
		glutAddMenuEntry("Decrease Fur Layers [PageDown]", MENU_DECREASE_FUR_LAYERS); 
		glutAddMenuEntry("Increase Fur Length [Home]", MENU_INCREASE_FUR_LENGTH); 
		glutAddMenuEntry("Decrease Fur Length [End]", MENU_DECREASE_FUR_LENGTH); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		glutAddMenuEntry("Toggle Anisotropic Filter [F]", MENU_TOGGLE_ANISOTROPIC_FILTER); 
		glutAddMenuEntry("Toggle Backface Culling [B]", MENU_TOGGLE_BACKFACE_CULLING); 
		glutAddMenuEntry("Toggle Alpha Test [A]", MENU_TOGGLE_ALPHA_TEST); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		glutAddMenuEntry("Toggle Light Rotation [L]", MENU_TOGGLE_LIGHT_ROTATION); 
		glutAddMenuEntry("Toggle Diffuse [D]", MENU_TOGGLE_DIFFUSE); 
		glutAddMenuEntry("Toggle Specular [S]", MENU_TOGGLE_SPECULAR); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		glutAddMenuEntry("Toggle Fur Simulation [Space]", MENU_TOGGLE_SIMULATION); 
		glutAddMenuEntry("Toggle Gravity [G]", MENU_TOGGLE_GRAVITY); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		glutAddMenuEntry("Show/Hide Normal Map [N]", MENU_SHOW_HIDE_NORMAL_MAP); 
		glutAddMenuEntry("Show/Hide Offset Map [O]", MENU_SHOW_HIDE_OFFSET_MAP); 
		glutAddMenuEntry("Show/Hide Info [F1]", MENU_SHOW_HIDE_INFO); 
		glutAddMenuEntry("-------------------------", MENU_NOP); 
		if (s_FullScreenPossible) 
			glutAddMenuEntry("Toggle Full Screen [Tab]", MENU_TOGGLE_FULLSCREEN); 
		glutAddMenuEntry("Exit [Esc]", MENU_EXIT); 
		glutAttachMenu(GLUT_RIGHT_BUTTON); 
	} 
 
	if (!Init()) 
	{ 
		exit(0); 
	} 
 
	glutMainLoop(); 
} 
 
 
//----------------------------------------------------------------------------- 
// SetupScreenMode 
//----------------------------------------------------------------------------- 
bool SetupScreenMode(int width, int height, bool aAttemptFullScreen) 
{ 
	char modeString[256]; 
 
	// attempt full screen mode 
	if (aAttemptFullScreen) 
	{ 
		// prefer as higher refresh rate as possible 
		sprintf(modeString, "width=%d height=%d bpp=32 hertz>=60", width, height); 
		glutGameModeString(modeString); 
		if (glutGameModeGet(GLUT_GAME_MODE_WIDTH) != -1) 
		{ 
			glutEnterGameMode(); // enter full screen mode 
			return true; 
		} 
	} 
 
	// no full screen mode, init window instead 
	glutInitWindowSize(width, height); 
	glutCreateWindow("Dynamic Fur Demo"); 
	return false;      
} 
 
 
//----------------------------------------------------------------------------- 
// CleanUp 
//----------------------------------------------------------------------------- 
void CleanUp() 
{ 
	if (!s_Initialized) 
	{ 
		MessageBox(NULL, "Failed to initialize the demo.", "Error", MB_ICONERROR); 
		return; 
	} 
 
	if (s_pMeshes)     delete [] s_pMeshes; 
	if (s_pFurTexture) delete s_pFurTexture; 
	if (s_pNormalMap)  delete s_pNormalMap; 
	if (s_texLighting) glDeleteTextures(1, &s_texLighting); 
	if (s_vsSkin)      glDeleteVertexShaderEXT(s_vsSkin); 
	if (s_vsFur)       glDeleteVertexShaderEXT(s_vsFur); 
	if (s_fsFur)       glDeleteFragmentShaderATI(s_fsFur); 
	textDestroy(); 
	timerDestroy(); 
} 
 
 
//----------------------------------------------------------------------------- 
// Init 
//----------------------------------------------------------------------------- 
bool Init() 
{ 
	// OpenGL extensions 
	if (!InitExtensions()) 
		return false; 
 
	// modelview transformation 
	s_Trans      = D3DXVECTOR3(0, 0, -17); 
	s_TransDelta = D3DXVECTOR3(0, 0, 0); 
	D3DXQuaternionRotationYawPitchRoll(&s_Rot, 0, -0.3*D3DX_PI, 0.2*D3DX_PI); 
	D3DXQuaternionIdentity(&s_RotDelta); 
 
	// model meshes 
	s_pMeshes = new FurMesh[NUM_MESHES]; 
	for (int i = 0; i < NUM_MESHES; i++) 
	{ 
		if (!s_pMeshes[i].Init(s_MeshParams[i])) 
			return false; 
	} 
	s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; 
 
	// fur texture 
	s_pFurTexture = new FurTexture(); 
	if (!s_pFurTexture->Init(timeGetTime(), 128, 20)) 
		return false; 
	s_pFurTexture->SetMaxAnisotropy(16); 
 
	// normal map 
	s_pNormalMap = new NormalMap(); 
	if (!s_pNormalMap->Init(64, 32)) 
		return false; 
 
	// lighting texture 
	glGenTextures(1, &s_texLighting); 
	glBindTexture(GL_TEXTURE_3D_EXT, s_texLighting); 
	BuildFurLightingTexture(); 
 
	// vertex/fragment shaders 
	if (!InitShaders()) 
		return false; 
 
	// text drawing 
	if (!textInit()) 
		return false; 
 
	// timer 
	if (!timerInit(1.0)) 
		return false; 
 
	s_Initialized = true; 
	return true; 
} 
 
 
//----------------------------------------------------------------------------- 
// InitShaders 
//----------------------------------------------------------------------------- 
bool InitShaders() 
{ 
	// invariants 
	GLuint I_MODELVIEWPROJ = glBindParameterEXT(GL_MVP_MATRIX_EXT); 
	GLuint I_MODELVIEW     = glBindParameterEXT(GL_MODELVIEW_MATRIX); 
	I_LIGHT_DIR    = glGenSymbolsEXT(GL_VECTOR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); 
	I_CAMERA_POS   = glGenSymbolsEXT(GL_VECTOR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); 
	I_FUR_LENGTH   = glGenSymbolsEXT(GL_SCALAR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); 
	I_OFFSET_SCALE = glGenSymbolsEXT(GL_SCALAR_EXT, GL_INVARIANT_EXT, GL_FULL_RANGE_EXT, 1); 
 
	// variants 
	GLuint V_POS    = glBindParameterEXT(GL_CURRENT_VERTEX_EXT); 
	GLuint V_TEX1   = glBindTextureUnitParameterEXT(GL_TEXTURE0_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_TEX2   = glBindTextureUnitParameterEXT(GL_TEXTURE1_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_SCALE  = glBindTextureUnitParameterEXT(GL_TEXTURE2_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_RADIUS = glBindTextureUnitParameterEXT(GL_TEXTURE3_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_DS     = glBindTextureUnitParameterEXT(GL_TEXTURE4_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_DT     = glBindTextureUnitParameterEXT(GL_TEXTURE5_ARB, GL_CURRENT_TEXTURE_COORDS); 
	GLuint V_NORMAL = glBindParameterEXT(GL_CURRENT_NORMAL); 
 
	////////////////////////////////////////////////////////////////////// 
	// vertex shader for skin 
	s_vsSkin = glGenVertexShadersEXT(1); 
	glBindVertexShaderEXT(s_vsSkin); 
	glBeginVertexShaderEXT(); 
	{ 
		// transform the vertex into homogeneous clip space 
		glShaderOp2EXT(GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, I_MODELVIEWPROJ, V_POS); 
 
		// calculate diffuse color (hemisphere lighting) 
		GLuint R_TEMP = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); 
		GLuint C_1 = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1); 
		GLuint C_2 = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1); 
		glSetLocalConstantEXT(C_1, GL_FLOAT, D3DXVECTOR4(0.09, 0.06, 0.03, 0.0)); 
		glSetLocalConstantEXT(C_2, GL_FLOAT, D3DXVECTOR4(0.09, 0.06, 0.03, 0.0)); 
		glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMP, I_LIGHT_DIR, V_NORMAL); 
		glShaderOp3EXT(GL_OP_MADD_EXT, GL_OUTPUT_COLOR0_EXT, R_TEMP, C_1, C_2); 
	} 
	glEndVertexShaderEXT(); 
 
	////////////////////////////////////////////////////////////////////// 
	// vertex shader for fur 
	s_vsFur = glGenVertexShadersEXT(1); 
	glBindVertexShaderEXT(s_vsFur); 
	glBeginVertexShaderEXT(); 
	{ 
		// local variables 
		GLuint R_TEMPS = glGenSymbolsEXT(GL_SCALAR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); 
		GLuint R_TEMPV = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); 
		GLuint R_POS   = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); 
		GLuint R_HALF  = glGenSymbolsEXT(GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1); 
 
		// add fur length to vertex position 
		glShaderOp3EXT(GL_OP_MADD_EXT, R_POS, V_NORMAL, I_FUR_LENGTH, V_POS); 
 
		// transform vertex into homogeneous clip space 
		glShaderOp2EXT(GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, I_MODELVIEWPROJ, R_POS); 
 
		// assign texture coord for normal map 
		glShaderOp1EXT(GL_OP_MOV_EXT, GL_OUTPUT_TEXTURE_COORD0_EXT, V_TEX1); 
 
		// calculate light vector 
		{ 
			// transform light vector into local texture space 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DS, I_LIGHT_DIR); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DT, I_LIGHT_DIR); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_NORMAL, I_LIGHT_DIR); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 2); 
 
			// normalize light vector and store it into TEXTURE_COORD1 
			// (texture space is not necessarily orthonormal) 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); 
			glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); 
			glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD1_EXT, R_TEMPV, R_TEMPS); 
		} 
 
		// calculate halfangle vector 
		{ 
			// calculate halfangle vector in model space 
			glShaderOp2EXT(GL_OP_SUB_EXT, R_TEMPV, I_CAMERA_POS, R_POS); 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); 
			glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); 
			glShaderOp3EXT(GL_OP_MADD_EXT, R_HALF, R_TEMPV, R_TEMPS, I_LIGHT_DIR); 
 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_HALF, R_HALF); 
			glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); 
			glShaderOp2EXT(GL_OP_MUL_EXT, R_HALF, R_HALF, R_TEMPS); 
 
			// transform halfangle vector into local texture space 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DS, R_HALF); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_DT, R_HALF); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, V_NORMAL, R_HALF); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 2); 
 
			// normalize halfangle vector and store it into TEXTURE_COORD2 
			glShaderOp2EXT(GL_OP_DOT3_EXT, R_TEMPS, R_TEMPV, R_TEMPV); 
			glShaderOp1EXT(GL_OP_RECIP_SQRT_EXT, R_TEMPS, R_TEMPS); 
			glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD2_EXT, R_TEMPV, R_TEMPS); 
		} 
 
		// assign texture coord for fur texture (unperturbed) 
		glShaderOp1EXT(GL_OP_MOV_EXT, GL_OUTPUT_TEXTURE_COORD3_EXT, V_TEX2); 
 
		// calculate offset scale factor and store it into TEXTURE_COORD4 
		{ 
			// R_TEMPV = radius / (radius + length) 
			glShaderOp2EXT(GL_OP_ADD_EXT, R_TEMPV, V_RADIUS, I_FUR_LENGTH); 
			glExtractComponentEXT(R_TEMPS, R_TEMPV, 0); 
			glShaderOp1EXT(GL_OP_RECIP_EXT, R_TEMPS, R_TEMPS); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 0); 
			glExtractComponentEXT(R_TEMPS, R_TEMPV, 1); 
			glShaderOp1EXT(GL_OP_RECIP_EXT, R_TEMPS, R_TEMPS); 
			glInsertComponentEXT(R_TEMPV, R_TEMPS, 1); 
			glShaderOp2EXT(GL_OP_MUL_EXT, R_TEMPV, R_TEMPV, V_RADIUS); 
 
			// TEX_COORD2 = R_TEMPV * V_SCALE * I_OFFSET_SCALE 
			glShaderOp2EXT(GL_OP_MUL_EXT, R_TEMPV, R_TEMPV, V_SCALE); 
			glShaderOp2EXT(GL_OP_MUL_EXT, GL_OUTPUT_TEXTURE_COORD4_EXT, R_TEMPV, I_OFFSET_SCALE); 
		} 
	} 
	glEndVertexShaderEXT(); 
 
	////////////////////////////////////////////////////////////////////// 
	// fragment shaders for fur 
	//   s_fsFur   : diffuse and specular 
	//   s_fsFur+1 : diffuse only 
	//   s_fsFur+2 : specular only 
	s_fsFur = glGenFragmentShadersATI(3); 
	for (int i = 0; i < 3; i++) 
	{ 
		glBindFragmentShaderATI(s_fsFur+i); 
		glBeginFragmentShaderATI(); 
 
		////////// first pass ////////// 
		// cubemap normalizer could be used for L and H instead of just passing tex coords, 
		// but it would consume more fill rate while quality would not be all that improved. 
		glSampleMapATI(GL_REG_0_ATI, GL_TEXTURE0_ARB, GL_SWIZZLE_STR_ATI);    // 0.5*N + 0.5 
		glPassTexCoordATI(GL_REG_1_ATI, GL_TEXTURE1_ARB, GL_SWIZZLE_STR_ATI); // L 
		glPassTexCoordATI(GL_REG_2_ATI, GL_TEXTURE2_ARB, GL_SWIZZLE_STR_ATI); // H 
		glPassTexCoordATI(GL_REG_3_ATI, GL_TEXTURE3_ARB, GL_SWIZZLE_STR_ATI); // tex_coord 
		glPassTexCoordATI(GL_REG_4_ATI, GL_TEXTURE4_ARB, GL_SWIZZLE_STR_ATI); // offset_scale 
 
		// REG_2 = texture coord for lighting texture 
		{ 
			// REG_2.y = N dot H 
			glColorFragmentOp2ATI(GL_DOT3_ATI,	GL_REG_2_ATI, GL_GREEN_BIT_ATI, GL_NONE, 
												GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, 
												GL_REG_2_ATI, GL_NONE, GL_NONE); 
			// REG_2.x = N dot L 
			glColorFragmentOp2ATI(GL_DOT3_ATI,	GL_REG_2_ATI, GL_RED_BIT_ATI, GL_NONE, 
												GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, 
												GL_REG_1_ATI, GL_NONE, GL_NONE); 
			// REG_2.z = compressed(L.z) 
			glSetFragmentShaderConstantATI(GL_CON_0_ATI, D3DXVECTOR4(0.5, 0.5, 0.5, 0.0)); 
			glColorFragmentOp3ATI(GL_MAD_ATI,	GL_REG_2_ATI, GL_BLUE_BIT_ATI, GL_NONE, 
												GL_REG_1_ATI, GL_BLUE, GL_NONE, 
												GL_CON_0_ATI, GL_NONE, GL_NONE, 
												GL_CON_0_ATI, GL_NONE, GL_NONE); 
		} 
 
		// REG_1 = texture coord for fur texture 
		{ 
			// REG_1 = (Nx*Nx + Ny*Ny + 1) 
			glColorFragmentOp3ATI(GL_DOT2_ADD_ATI,	GL_REG_1_ATI, GL_NONE, GL_NONE, 
												GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, 
												GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, 
												GL_ONE,       GL_NONE, GL_NONE); 
 
			// REG_1 = (Nx*Nx + Ny*Ny + 1) * (Nx, Ny, **) 
			// this gives fairly good approximation of the offset map, 
			// and is faster than sampling both the normal map and the offset map. 
			glColorFragmentOp2ATI(GL_MUL_ATI,	GL_REG_1_ATI, GL_NONE, GL_NONE, 
												GL_REG_0_ATI, GL_NONE, GL_BIAS_BIT_ATI | GL_2X_BIT_ATI, 
												GL_REG_1_ATI, GL_NONE, GL_NONE); 
 
			// REG_1 = tex_coord + offset_scale * REG_1 
			glColorFragmentOp3ATI(GL_MAD_ATI,	GL_REG_1_ATI, GL_RED_BIT_ATI | GL_GREEN_BIT_ATI, GL_NONE, 
												GL_REG_4_ATI, GL_NONE, GL_NONE, 
												GL_REG_1_ATI, GL_NONE, GL_NONE, 
												GL_REG_3_ATI, GL_NONE, GL_NONE); 
		} 
 
		////////// second pass ////////// 
		glSampleMapATI(GL_REG_1_ATI, GL_REG_2_ATI, GL_SWIZZLE_STR_ATI); // diffuse, specular 
		glSampleMapATI(GL_REG_2_ATI, GL_REG_1_ATI, GL_SWIZZLE_STR_ATI); // fur_color, fur_transparency 
 
		if (i == 0 || i == 1) 
		{ 
			// REG_0.rgb = diffuse * fur_color 
			glColorFragmentOp2ATI(GL_MUL_ATI,	GL_REG_0_ATI, GL_NONE, GL_NONE, 
												GL_REG_1_ATI, GL_NONE, GL_NONE, 
												GL_REG_2_ATI, GL_NONE, GL_NONE); 
		} 
		if (i == 0 || i == 2) 
		{ 
			// REG_0.a = specular * fur_color.r 
			glAlphaFragmentOp2ATI(GL_MUL_ATI,	GL_REG_0_ATI, GL_NONE, 
												GL_REG_1_ATI, GL_NONE, GL_NONE, 
												GL_REG_2_ATI, GL_RED,  GL_NONE); 
			if (i == 0) 
			{ 
				// REG_0.rgb = CON_1 * REG_0.a + REG_0.rgb 
				glSetFragmentShaderConstantATI(GL_CON_1_ATI, D3DXVECTOR4(0.5, 0.5, 0.4, 0.0)); 
				glColorFragmentOp3ATI(GL_MAD_ATI,	GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI, 
													GL_CON_1_ATI, GL_NONE, GL_NONE, 
													GL_REG_0_ATI, GL_ALPHA, GL_NONE, 
													GL_REG_0_ATI, GL_NONE, GL_NONE); 
			} 
			else 
			{ 
				// REG_0.rgb = CON_1 * REG_0.a 
				glSetFragmentShaderConstantATI(GL_CON_1_ATI, D3DXVECTOR4(0.6, 0.6, 0.5, 0.0)); 
				glColorFragmentOp2ATI(GL_MUL_ATI,	GL_REG_0_ATI, GL_NONE, GL_SATURATE_BIT_ATI, 
													GL_CON_1_ATI, GL_NONE, GL_NONE, 
													GL_REG_0_ATI, GL_ALPHA, GL_NONE); 
			} 
		} 
 
		// REG_0.a = fur_transparency 
		glAlphaFragmentOp1ATI(GL_MOV_ATI,	GL_REG_0_ATI, GL_SATURATE_BIT_ATI, 
											GL_REG_2_ATI, GL_NONE, GL_NONE); 
 
		glEndFragmentShaderATI(); 
	} 
 
	if (glGetError()) 
		return false; 
	return true; 
} 
 
 
//----------------------------------------------------------------------------- 
// Menu 
//----------------------------------------------------------------------------- 
void Menu(int item) 
{ 
	switch (item) 
	{ 
	case MENU_CHANGE_OBJECT: 
		s_CurrentMesh = (s_CurrentMesh + 1) % NUM_MESHES; 
		s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; 
		break; 
	case MENU_TOGGLE_WIREFRAME: 
		s_Wireframe = !s_Wireframe; 
		break; 
	case MENU_INCREASE_FUR_LAYERS: 
		s_NumLayers++; 
		break; 
	case MENU_DECREASE_FUR_LAYERS: 
		if (s_NumLayers > 0) s_NumLayers--; 
		break; 
	case MENU_INCREASE_FUR_LENGTH: 
		s_FurLength += 0.02; 
		if (s_FurLength > 1.0) s_FurLength = 1.0; 
		break; 
	case MENU_DECREASE_FUR_LENGTH: 
		s_FurLength -= 0.02; 
		if (s_FurLength < 0.2) s_FurLength = 0.2; 
		break; 
	case MENU_TOGGLE_ANISOTROPIC_FILTER: 
		s_UseAnisotropy = !s_UseAnisotropy; 
		s_pFurTexture->SetMaxAnisotropy(s_UseAnisotropy ? 16 : 1); 
		break; 
	case MENU_TOGGLE_BACKFACE_CULLING: 
		s_CullBackface = !s_CullBackface; 
		break; 
	case MENU_TOGGLE_ALPHA_TEST: 
		s_UseAlphaTest = !s_UseAlphaTest; 
		break; 
	case MENU_TOGGLE_LIGHT_ROTATION: 
		s_RotateLight = !s_RotateLight; 
		break; 
	case MENU_TOGGLE_DIFFUSE: 
		s_DrawDiffuse = !s_DrawDiffuse; 
		break; 
	case MENU_TOGGLE_SPECULAR: 
		s_DrawSpecular = !s_DrawSpecular; 
		break; 
	case MENU_TOGGLE_SIMULATION: 
		s_UseSimulation = !s_UseSimulation; 
		s_pNormalMap->ResetInertia(); 
		break; 
	case MENU_TOGGLE_GRAVITY: 
		s_UseGravity = !s_UseGravity; 
		break; 
	case MENU_SHOW_HIDE_NORMAL_MAP: 
		s_ShowNormalMap = !s_ShowNormalMap; 
		break; 
	case MENU_SHOW_HIDE_OFFSET_MAP: 
		s_ShowOffsetMap = !s_ShowOffsetMap; 
		break; 
	case MENU_SHOW_HIDE_INFO: 
		s_ShowInfo = (s_ShowInfo + 1) % (s_ShowFPS ? 3 : 2); 
		break; 
	case MENU_TOGGLE_FULLSCREEN: 
		if (s_FullScreenPossible) 
		{ 
			CleanUp(); 
			if (s_FullScreenMode) 
			{ 
				glutLeaveGameMode(); 
			} 
			else 
			{ 
				glutDestroyWindow(glutGetWindow()); 
			} 
			s_FullScreenMode = !s_FullScreenMode; 
			SetupScreenMode(FULLSCREEN_WIDTH, FULLSCREEN_HEIGHT, s_FullScreenMode); 
			InitInterface(); 
		} 
		break; 
	case MENU_EXIT: 
		exit(0); 
	} 
	glutPostRedisplay(); 
} 
 
 
//----------------------------------------------------------------------------- 
// Keyboard 
//----------------------------------------------------------------------------- 
void Keyboard(unsigned char key, int x, int y) 
{ 
	switch (key) 
	{ 
	case 27: 
		Menu(MENU_EXIT); 
		break; 
	case '\r': 
		Menu(MENU_CHANGE_OBJECT); 
		break; 
	case 'w': 
		Menu(MENU_TOGGLE_WIREFRAME); 
		break; 
	case 'f': 
		Menu(MENU_TOGGLE_ANISOTROPIC_FILTER); 
		break; 
	case 'b': 
		Menu(MENU_TOGGLE_BACKFACE_CULLING); 
		break; 
	case 'a': 
		Menu(MENU_TOGGLE_ALPHA_TEST); 
		break; 
	case 'l': 
		Menu(MENU_TOGGLE_LIGHT_ROTATION); 
		break; 
	case 'd': 
		Menu(MENU_TOGGLE_DIFFUSE); 
		break; 
	case 's': 
		Menu(MENU_TOGGLE_SPECULAR); 
		break; 
	case ' ': 
		Menu(MENU_TOGGLE_SIMULATION); 
		break; 
	case 'g': 
		Menu(MENU_TOGGLE_GRAVITY); 
		break; 
	case 'n': 
		Menu(MENU_SHOW_HIDE_NORMAL_MAP); 
		break; 
	case 'o': 
		Menu(MENU_SHOW_HIDE_OFFSET_MAP); 
		break; 
	case '\t': 
		Menu(MENU_TOGGLE_FULLSCREEN); 
		break; 
	} 
} 
 
 
//----------------------------------------------------------------------------- 
// SpecialKey 
//----------------------------------------------------------------------------- 
void SpecialKey(int key, int x, int y) 
{ 
	switch (key) 
	{ 
	case GLUT_KEY_PAGE_UP: 
		Menu(MENU_INCREASE_FUR_LAYERS); 
		break; 
	case GLUT_KEY_PAGE_DOWN: 
		Menu(MENU_DECREASE_FUR_LAYERS); 
		break; 
	case GLUT_KEY_HOME: 
		Menu(MENU_INCREASE_FUR_LENGTH); 
		break; 
	case GLUT_KEY_END: 
		Menu(MENU_DECREASE_FUR_LENGTH); 
		break; 
	case GLUT_KEY_F1: 
		Menu(MENU_SHOW_HIDE_INFO); 
		break; 
	} 
} 
 
 
//----------------------------------------------------------------------------- 
// Mouse 
//----------------------------------------------------------------------------- 
void MouseButton(int button, int state, int x, int y) 
{ 
	s_IdleModeTimer = 0.0; // reset demo mode timer 
 
	s_Mouse.x = x; 
	s_Mouse.y = y; 
	s_PrevMouse = s_Mouse; 
 
	if (button == GLUT_LEFT_BUTTON) 
	{ 
		s_MouseState[0] = (state == GLUT_DOWN); 
	} 
	else if (button == GLUT_RIGHT_BUTTON) 
	{ 
		s_MouseState[1] = (state == GLUT_DOWN); 
	} 
	else if (button == GLUT_MIDDLE_BUTTON) 
	{ 
		s_MouseState[2] = (state == GLUT_DOWN); 
	} 
} 
 
 
//----------------------------------------------------------------------------- 
// MouseMotion 
//----------------------------------------------------------------------------- 
void MouseMotion(int x, int y) 
{ 
	s_IdleModeTimer = 0.0; // reset demo mode timer 
 
	s_Mouse.x = x; 
	s_Mouse.y = y; 
} 
 
 
//----------------------------------------------------------------------------- 
// Reshape 
//----------------------------------------------------------------------------- 
void Reshape(int w, int h) 
{ 
	s_Width  = w; 
	s_Height = h; 
	glViewport(0, 0, w, h); 
	glMatrixMode(GL_PROJECTION); 
	glLoadIdentity(); 
	gluPerspective(s_FovY, (double)w/h, s_NearZ, s_FarZ); 
} 
 
 
//----------------------------------------------------------------------------- 
// Idle 
//----------------------------------------------------------------------------- 
void Idle() 
{ 
	// update timer 
	timerUpdate(); 
 
	// time step 
	float dt = min(0.1, timerGetDT()); 
 
	// filtered time step 
	static float dt2 = 0.025; 
	dt2 = 0.9 * dt2 + 0.1 * dt; 
 
	// update demo mode timer 
	s_IdleModeTimer += dt; 
 
	// demo mode, change meshes every DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT seconds 
	// after demo mode has been entered 
	{ 
		static prev_count = 0; 
		if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) 
		{ 
			int count = int((s_IdleModeTimer - DEMO_MANUAL_MODE_TIMEOUT) / DEMO_AUTOMATIC_MODE_OBJECT_SWITCH_TIMEOUT); 
			if (count != prev_count) 
			{ 
				s_CurrentMesh = (s_CurrentMesh + 1) % NUM_MESHES; 
				s_pCurrentMesh = &s_pMeshes[s_CurrentMesh]; 
				prev_count = count; 
			} 
		} 
		else 
		{ 
			prev_count = 0; 
		} 
	} 
 
	// update mouse tracking 
	{ 
		D3DXVECTOR3 tempVec(0, 0, 0); 
		D3DXQUATERNION tempQuat(0, 0, 0, 1); 
		short shift = GetAsyncKeyState(VK_SHIFT); 
		short ctrl  = GetAsyncKeyState(VK_CONTROL); 
 
		if (s_MouseState[2] || s_MouseState[0] && shift) // translate 
		{ 
			tempVec.x =  5 * (s_Mouse.x - s_PrevMouse.x) / s_Height; 
			tempVec.y = -5 * (s_Mouse.y - s_PrevMouse.y) / s_Height; 
		} 
		else if (s_MouseState[0] && ctrl) // zoom 
		{ 
			s_Trans.z += 10 * (s_Mouse.y - s_PrevMouse.y) / s_Height; 
			s_Trans.z = max(-s_FarZ, min(-s_NearZ, s_Trans.z)); 
		} 
		else if (s_MouseState[0]) // rotate 
		{ 
			D3DXVECTOR2 pt1(float(2*s_PrevMouse.x)/s_Width - 1, 1 - float(2*s_PrevMouse.y)/s_Height); 
			D3DXVECTOR2 pt2(float(2*s_Mouse.x)    /s_Width - 1, 1 - float(2*s_Mouse.y)    /s_Height); 
			trackball(&tempQuat, &pt1, &pt2); 
		} 
		s_PrevMouse = s_Mouse; 
 
		// demo mode, rotate object randomly when manual mode times out 
		if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) 
		{ 
			static D3DXVECTOR3 randomAxis(0, 1, 0); 
			static float randomSpeed = 0.0; 
 
			// change rotation randomly 
			if (rand() < 0.5 * dt * RAND_MAX) 
			{ 
				if (rand() < 0.5 * RAND_MAX) 
				{ 
					// reversal of motion 
					randomAxis *= -1; 
				} 
				else 
				{ 
					// change axis of rotation 
					randomAxis.x = -1.0 + 2.0 * rand() / RAND_MAX; 
					randomAxis.y = -1.0 + 2.0 * rand() / RAND_MAX; 
					randomAxis.z = -1.0 + 2.0 * rand() / RAND_MAX; 
					D3DXVec3Normalize(&randomAxis, &randomAxis); 
 
					// change rotation speed (rad/sec) 
					randomSpeed = (0.5 + 0.5 * rand() / RAND_MAX) * D3DX_PI; 
				} 
			} 
 
			D3DXQuaternionRotationAxis(&tempQuat, &randomAxis, randomSpeed * dt); 
		} 
 
		float damping; 
		if (s_IdleModeTimer > DEMO_MANUAL_MODE_TIMEOUT) 
		{ 
			// demo mode, smooth out abrupt motions 
			damping = min(1.0, dt/0.8); 
		} 
		else 
		{ 
			// mouse mode 
			damping = min(1.0, dt/0.125); 
		} 
 
		// update modelview traslation and rotation using damping factor 
		s_TransDelta = (1 - damping) * s_TransDelta + damping * tempVec; 
		s_Trans += s_TransDelta; 
		D3DXQuaternionSlerp(&s_RotDelta, &s_RotDelta, &tempQuat, damping); 
		D3DXQuaternionNormalize(&s_RotDelta, &s_RotDelta); 
		D3DXQuaternionNormalize(&s_Rot, &(s_Rot * s_RotDelta)); 
	} 
 
	// calculate modelview matrix 
	{ 
		D3DXMATRIX rotation, translation; 
		D3DXMatrixRotationQuaternion(&rotation, &s_Rot); 
		D3DXMatrixTranslation(&translation, s_Trans.x, s_Trans.y, s_Trans.z); 
		D3DXMatrixMultiply(&s_ModelView, &rotation, &translation); 
		glMatrixMode(GL_MODELVIEW); 
		glLoadMatrixf(s_ModelView); 
 
		// inverse of modelview matrix 
		D3DXMatrixInverse(&s_ModelViewInv, NULL, &s_ModelView); 
 
		// camera position 
		glSetInvariantEXT(I_CAMERA_POS, GL_FLOAT, &s_ModelViewInv._41); 
	} 
 
	// calculate light direction in model space 
	{ 
		static float angle1 = 0.5 * D3DX_PI; 
		static float angle2 = 0.2 * D3DX_PI; 
		if (s_RotateLight) 
		{ 
			angle1 += 1.00 * dt; 
			angle2 += 0.35 * dt; 
		} 
		D3DXVECTOR4 dir(sin(angle1), sin(angle2), cos(angle1), 0); 
		D3DXVec4Normalize(&dir, D3DXVec4Transform(&dir, &dir, &s_ModelViewInv)); 
		glSetInvariantEXT(I_LIGHT_DIR, GL_FLOAT, &dir); 
	} 
 
	// update normal map 
	if (!s_Wireframe && s_UseSimulation) 
	{ 
		// gravity in model space 
		D3DXVECTOR4 gravity(0, s_UseGravity ? -0.4 : -0.02, 0, 0); 
		D3DXVec4Transform(&gravity, &gravity, &s_ModelViewInv); 
 
		// translational velocity in model space 
		D3DXVECTOR4 velocity; 
		velocity.x = s_TransDelta.x / dt2; 
		velocity.y = s_TransDelta.y / dt2; 
		velocity.z = s_TransDelta.z / dt2; 
		velocity.w = 0; 
		D3DXVec4Transform(&velocity, &velocity, &s_ModelViewInv); 
 
		// angular velocity in model space 
		D3DXVECTOR4 omega; 
		D3DXQuaternionLn((D3DXQUATERNION*)&omega, &s_RotDelta); 
		D3DXVec4Transform(&omega, &(2*omega/dt2), &s_ModelViewInv); 
 
		s_pNormalMap->Update(s_pCurrentMesh, dt2, gravity, velocity, omega); 
	} 
 
	glutPostRedisplay(); 
} 
 
 
//----------------------------------------------------------------------------- 
// Display 
//----------------------------------------------------------------------------- 
void Display() 
{ 
	glClearColor(0.4, 0.4, 0.4, 1.0); 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 
 
	if (s_ShowNormalMap) 
	{ 
		glEnable(GL_TEXTURE_2D); 
		s_pNormalMap->Bind(); 
		// upper right of screen 
		ShowTexture(s_Width - 128, s_Height - 128, 128, 128); 
		glDisable(GL_TEXTURE_2D); 
	} 
 
	if (s_ShowOffsetMap) 
	{ 
		glEnable(GL_TEXTURE_2D); 
		s_pNormalMap->BindOffsetMap(); 
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 
		// lower left of screen 
		ShowTexture(s_Width - 128, 0, 128, 128); 
		glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 
		glDisable(GL_TEXTURE_2D); 
	} 
 
	RenderFurMesh(); 
 
	ShowInfo(); 
	glutSwapBuffers(); 
} 
 
 
//----------------------------------------------------------------------------- 
// RenderFurMesh 
//----------------------------------------------------------------------------- 
void RenderFurMesh() 
{ 
	if (s_Wireframe) 
	{ 
		glPushAttrib(GL_ENABLE_BIT); 
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); 
		glEnable(GL_LINE_SMOOTH); 
		glLineWidth(0.9); 
		glEnable(GL_BLEND); 
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
		glColor3f(0, 0, 0); 
		s_pCurrentMesh->Draw(); 
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); 
		glPopAttrib(); 
		return; 
	} 
 
	glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT); 
	glEnable(GL_DEPTH_TEST); 
 
	// draw skin 
	{ 
		glEnable(GL_CULL_FACE); 
		glEnable(GL_VERTEX_SHADER_EXT); 
		glBindVertexShaderEXT(s_vsSkin); 
		s_pCurrentMesh->Draw(); 
	} 
 
	// draw fur 
	if (!s_Wireframe && s_NumLayers > 0 && (s_DrawDiffuse || s_DrawSpecular)) 
	{ 
		s_CullBackface ? glEnable(GL_CULL_FACE) : glDisable(GL_CULL_FACE); 
 
		glEnable(GL_BLEND); 
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 
 
		if (s_UseAlphaTest) 
		{ 
			glEnable(GL_ALPHA_TEST); 
			glAlphaFunc(GL_GREATER, 0.1); 
		} 
 
		// vertex shader 
		glEnable(GL_VERTEX_SHADER_EXT); 
		glBindVertexShaderEXT(s_vsFur); 
 
		// fragment shader 
		glEnable(GL_FRAGMENT_SHADER_ATI); 
		glBindFragmentShaderATI(s_DrawDiffuse ? (s_DrawSpecular ? s_fsFur : s_fsFur+1) : s_fsFur+2); 
 
		// normal map 
		glActiveTextureARB(GL_TEXTURE0_ARB); 
		glEnable(GL_TEXTURE_2D); 
		s_pNormalMap->Bind(); 
 
		// lighting texture 
		glActiveTextureARB(GL_TEXTURE1_ARB); 
		glEnable(GL_TEXTURE_3D_EXT); 
		glBindTexture(GL_TEXTURE_3D_EXT, s_texLighting); 
 
		// fur texture 
		glActiveTextureARB(GL_TEXTURE2_ARB); 
		glEnable(GL_TEXTURE_2D); 
		glTexEnvf(GL_TEXTURE_FILTER_CONTROL_EXT, GL_TEXTURE_LOD_BIAS_EXT, 0.5); 
 
		// draw fur shells 
		for (int i = 0; i < s_NumLayers; i++) 
		{ 
			float layer = float(i+1) / s_NumLayers; 
			float length = s_FurLength * layer; 
			float scale = -s_FurLength * (1.0*layer*layer + 0.4*layer); 
			s_pFurTexture->Bind(layer); 
			glSetInvariantEXT(I_FUR_LENGTH, GL_FLOAT, &length); 
			glSetInvariantEXT(I_OFFSET_SCALE, GL_FLOAT, &scale); 
			s_pCurrentMesh->Draw(); 
		} 
 
		glActiveTextureARB(GL_TEXTURE0_ARB); 
	} 
 
	glPopAttrib(); 
} 
 
 
//----------------------------------------------------------------------------- 
// ShowTexture 
//----------------------------------------------------------------------------- 
void ShowTexture(int x, int y, int w, int h) 
{ 
	glPushAttrib(GL_VIEWPORT_BIT); 
	glViewport(x, y, w, h); 
 
	glMatrixMode(GL_PROJECTION); 
	glPushMatrix(); 
	glLoadIdentity(); 
	gluOrtho2D(0, 1, 0, 1); 
 
	glMatrixMode(GL_MODELVIEW); 
	glPushMatrix(); 
	glLoadIdentity(); 
 
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); 
 
	glBegin(GL_TRIANGLE_STRIP); 
	glTexCoord2f(0, 0); glVertex2f(0, 0); 
	glTexCoord2f(1, 0); glVertex2f(1, 0); 
	glTexCoord2f(0, 1); glVertex2f(0, 1); 
	glTexCoord2f(1, 1); glVertex2f(1, 1); 
	glEnd(); 
 
	glMatrixMode(GL_PROJECTION); 
	glPopMatrix(); 
	glMatrixMode(GL_MODELVIEW); 
	glPopMatrix(); 
	glPopAttrib(); 
} 
 
 
//----------------------------------------------------------------------------- 
// ShowInfo 
//----------------------------------------------------------------------------- 
void ShowInfo() 
{ 
	textBegin(s_Width, s_Height); 
 
	if (s_ShowInfo == 1) 
	{ 
		#define ONOFF(x) ((x) ? "ON" : "OFF") 
 
		textSetPosition(4, 4); 
 
		if (s_ShowFPS) 
		{ 
			textSetColor(1, 1, 0); 
			textPrintf("%.1f fps (%dx%d)\n", timerGetFPS(), s_Width, s_Height); 
			textLineFeed(5); 
		} 
		textSetColor(1, 1, 1); 
		textPrintf("Object: %s\n", s_pCurrentMesh->GetName()); 
		textPrintf("Fur Layers: %d\n", s_NumLayers); 
		textPrintf("Fur Length: %.2f\n", s_FurLength); 
		textLineFeed(5); 
		textPrintf("Anisotropic Filter: %s\n", ONOFF(s_UseAnisotropy)); 
		textPrintf("Backface Culling: %s\n", ONOFF(s_CullBackface)); 
		textPrintf("Alpha Test: %s\n", ONOFF(s_UseAlphaTest)); 
		textLineFeed(5); 
		textPrintf("Diffuse: %s\n", ONOFF(s_DrawDiffuse)); 
		textPrintf("Specular: %s\n", ONOFF(s_DrawSpecular)); 
		textLineFeed(5); 
		textPrintf("Simulation: %s\n", ONOFF(s_UseSimulation)); 
		textPrintf("Gravity: %s\n", ONOFF(s_UseGravity)); 
		textSetPosition(4, s_Height-83); 
		textPrint("Mouse Controls\n"); 
		textPrint("Left:       Rotate\n"); 
		textPrint("Shift+Left: Translate\n"); 
		textPrint("Crtl+Left:  Zoom\n"); 
		if (!s_FullScreenMode) 
		{ 
			textPrint("Right:      Menu"); 
		} 
	} 
	else if (s_ShowInfo == 2) 
	{ 
		textSetPosition(4, 4); 
		textSetColor(1, 1, 0); 
		textPrintf("%.1f fps\n", timerGetFPS()); 
	} 
 
	textSetPosition(s_Width-246, s_Height-35); 
	textSetColor(1, 1, 1); 
	textPrint("Copyright \251 2002 Tomohide Kano"); 
	textSetPosition(s_Width-470, s_Height-19); 
	textSetColor(1, 1, 1); 
	textPrint("Demo source code available at http://www.ati.com/developer"); 
 
	textEnd(); 
}