//-
// ==========================================================================
// Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All 
// rights reserved.
//
// The coded instructions, statements, computer programs, and/or related 
// material (collectively the "Data") in these files contain unpublished 
// information proprietary to Autodesk, Inc. ("Autodesk") and/or its 
// licensors, which is protected by U.S. and Canadian federal copyright 
// law and by international treaties.
//
// The Data is provided for use exclusively by You. You have the right 
// to use, modify, and incorporate this Data into other products for 
// purposes authorized by the Autodesk software license agreement, 
// without fee.
//
// The copyright notices in the Software and this entire statement, 
// including the above license grant, this restriction and the 
// following disclaimer, must be included in all copies of the 
// Software, in whole or in part, and all derivative works of 
// the Software, unless such copies or derivative works are solely 
// in the form of machine-executable object code generated by a 
// source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. 
// AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED 
// WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF 
// NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR 
// PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR 
// TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS 
// BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL, 
// DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK 
// AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY 
// OR PROBABILITY OF SUCH DAMAGES.
//
// ==========================================================================
//+

///////////////////////////////////////////////////////////////////
//
// NOTE: PLEASE READ THE README.TXT FILE FOR INSTRUCTIONS ON
// COMPILING AND USAGE REQUIREMENTS.
//
// DESCRIPTION: NV20-specific (Geforce3) sample shader.
//				This shader produces reflection and refraction effects.
//
//  This shader builds on the foundation demonstrated in the hwUnlitShader.
//
//
///////////////////////////////////////////////////////////////////

#ifdef WIN32
#pragma warning( disable : 4786 )		// Disable stupid STL warnings.
#endif

#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDagPath.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFnStringData.h>
#include <maya/MFnPlugin.h>
#include <maya/MGlobal.h>
#include <maya/MSceneMessage.h>

#include <maya/MPoint.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MQuaternion.h>
#include <maya/MEulerRotation.h>

#include <GL/gl.h>
#include <GL/glext.h>

// Include NVIDIA's helper libraries.  These libraries have
// copyright info in them so we cannot release them but we
// can use them to verify that the API works correctly.
//
#define GLH_EXT_SINGLE_FILE
#include "glh_extensions.h"
#undef GL_NV_vertex_array_range
#include "glh_genext.h"
#include "glh_obs.h"
using namespace glh;

#include "hwRefractReflectShader_NV20.h"
#include "ShadingConnection.h"

MTypeId hwRefractReflectShader_NV20::id( 0x00105445 );


void hwRefractReflectShader_NV20::postConstructor( )
{
	setMPSafe(false);
}

// Static attribute instances.
//
MObject  hwRefractReflectShader_NV20::color;
MObject  hwRefractReflectShader_NV20::colorR;
MObject  hwRefractReflectShader_NV20::colorG;
MObject  hwRefractReflectShader_NV20::colorB;

MObject  hwRefractReflectShader_NV20::refractionIndex;
MObject  hwRefractReflectShader_NV20::reflectivity;



// The Vertex Program for the reflection&refraction shading effect.
//
// CONSTANTS:
//  0- 3	4x4 ModelView-Projection composite matrix
//  4- 7	4x4 ModelView	inverseTranspose matrix
//  8- 11	4x4 ModelView	matrix
//  12-15	4x4 Texture		matrix
//
// 58	refraction index [rIdx, rIdx*rIdx, 0.0, 0.0]
// 59	camera position in eye space [0.0, 0.0, 0.0, 1.0]  
//		(camera could be offseted if necessary, should work but untested)
//
// 64	misc constants [0.0, 1.0, 2.0, 3.0]


// VERTEX REGISTERS (mapped so that standard gl calls work):
// 0 - coord
// 2 - normal
//
// RESULTS:
// texcoord0	(Refraction coords in eye-space)
// texcoord1	(Reflection coords in eye-space)
//
char vertexProgramString[] = 

		"!!VP1.0 # Refraction and Reflection\n"

		// Multiply the vertex coords by the GL_MODELVIEW_PROJECTION
		// composite matrix, to get clip space coordinates.
		//
		"DP4	o[HPOS].x, c[0], v[OPOS];"
		"DP4	o[HPOS].y, c[1], v[OPOS];"
		"DP4	o[HPOS].z, c[2], v[OPOS];"
		"DP4	o[HPOS].w, c[3], v[OPOS];"

		// =====================================================
		// The rest of the computations are done in the eyeSpace
		// =====================================================

        // Transform, vertex position to eye space, with the MODELVIEW matrix
		//
		"DP4	R9.x, c[8],  v[OPOS];"
		"DP4	R9.y, c[9],  v[OPOS];"
		"DP4	R9.z, c[10], v[OPOS];"
		"DP4	R9.w, c[11], v[OPOS];"		// R9 = eye space Position of this vertex

		// Using the inverseTranspose of the MODELVIEW matrix,
        // transform the vertex normal to eye space and normalize it
		//
		"DP3	R0.x, c[4], v[NRML];"
		"DP3	R0.y, c[5], v[NRML];"
		"DP3	R0.z, c[6], v[NRML];"
        "DP3	R11.w, R0, R0;"
		"RSQ	R11.w, R11.w;"
		"MUL	R11, R0, R11.w;"			// R11 = normalized normal vector in the eyeSpace

		// Compute the 'vertex->eye' vector and normalize it
		//
		"ADD	R0, -R9, c[59];"			// c[59] = eye position in eye space (0,0,0,1)
        "DP3	R8.w, R0, R0;"
		"RSQ	R8.w, R8.w;"
		"MUL	R8, R0, R8.w;"				// R8 = the eye/incident vector (I)

        // Calculate REFRACTION: Renderman style
		// float	eta;	// the refraction index value
		//
        // float	IdotN = I.N;
        // float	k = 1 - eta*eta*(1 - IdotN*IdotN);
        // return	k < 0 ? (0,0,0) : eta*I - (eta*IdotN + sqrt(k))*N;
		//
		"DP3	R0.x, R11, -R8;"				// R0 = N.I
        //
		"MAD	R1.x, -R0.x, R0.x, c[64].y;"	// R1.x = (1 - IdotN * IdotN)		== SQR( sin(Ti) )
		"MUL	R1.x, R1.x, c[58].y;"			// R1.x = R1.x * eta*eta
        "ADD    R1.x, c[64].y, -R1.x;"			// R1.x = (1 - (R1.x * eta * eta) )	== 1 - SQR( eta * sin(Ti) )
        //
        "RSQ    R2.x, R1.x;"					// R2.x = 1 / SQRT(R1.x)
        "RCP    R2.x, R2.x;"					// R2.x = cos(Tr) = SQRT(R1.x)	<=== OK
        "MAD    R2.x, c[58].x, R0.x, R2.x;"		// R2.x = eta*(IdotN) + cos(Tr)
        "MUL    R2, R11, R2.x;"					// R2 = N * R2.x
        "MAD    R2, c[58].x, -R8, R2;"			// R2 is the refracted ray direction
		//
        // Transform refracted ray by cubemap transform (texture matrix)
		//
        "DP3    o[TEX0].x, c[12], R2;"
        "DP3    o[TEX0].y, c[13], R2;"
        "DP3    o[TEX0].z, c[14], R2;"

        // Calculate REFLECTION in cubeMap space
        //
        "MUL    R0, R11, c[64].z;"		// R0   = 2*N
        "DP3    R3.w, R11, R8;"			// R3.w = N.dot.I
        "MAD    R3, R3.w, R0, -R8;"		// R3   = 2*N*(N.dot.I) - I
		//
        // Transform reflected ray by cubemap transform (texture matrix)
		//
        "DP3    o[TEX1].x, c[12], R3;"
        "DP3    o[TEX1].y, c[13], R3;"
        "DP3    o[TEX1].z, c[14], R3;"

        "END";




void initVertexProgram(const char vertexProgramCode[], GLuint* pVertexProgramId)
{
	// Allocate and initialize the vertex program.
	glGenProgramsNV(1, pVertexProgramId);
	GLenum error = glGetError();
	assert(error == GL_NO_ERROR);

	// Load the program.
	unsigned int length = strlen(vertexProgramCode);
	glLoadProgramNV(GL_VERTEX_PROGRAM_NV, *pVertexProgramId, length, 
		(const GLubyte *) vertexProgramCode);
	error = glGetError();

	// If an error occured, find the location in the vertex program
	// code and assert.
	if (error != GL_NO_ERROR)
	{
		// If an error occured, it's most likely due to a syntax or 
		// logic error in the vertex program. The error position
		// below will contain the index in the vertex program
		// string that is faulty. See the NV_vertex_program
		// extension specification for more details.
		if (error == GL_INVALID_OPERATION)
		{
			int error_position = -2;

			glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &error_position);

			// Most likely a bug in the vertex program code...
			assert(0);
		}
	}
}

// Load the vertexProgram and fill in the necessary constants used in the vertex program.
//
void hwRefractReflectShader_NV20::loadVertexProgramGL( M3dView& view )
{
	view.beginGL();
	{
		// Don't load/initialize the vertex program more than once.
		//
		if (vertex_program_id == 0)	
			initVertexProgram(vertexProgramString, &vertex_program_id);

		// Set up the constant values.
		//
		// CONSTANTS:
		//  0- 3	4x4 ModelView-Projection composite matrix
		//  4- 7	4x4 ModelView	inverseTranspose matrix
		//  8- 11	4x4 ModelView	matrix
		//  12-15	4x4 Texture		matrix
		//
		glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0,  GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
		glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4,  GL_MODELVIEW,               GL_INVERSE_TRANSPOSE_NV);
		glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 8,  GL_MODELVIEW,               GL_IDENTITY_NV);
		glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 12, GL_TEXTURE,                 GL_IDENTITY_NV);
		
		float rIdx = fRefractionIndex;
		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 58, rIdx, rIdx*rIdx, 0.0, 0.0);	// refraction index
		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 59, 0.0, 0.0, 0.0, 1.0);			// eye position
		glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 64, 0.0, 1.0, 2.0, 3.0);			// misc constants
	}
	view.endGL();
}

// Load the file textures for the cube maps.
//
MStatus	hwRefractReflectShader_NV20::loadTextures(const MDrawRequest& request, M3dView& view)
{
	// Get the cube map file names
	//
	MStringArray	decalNames;
	MString			decalName;

	// Find the cubemap textures by tracing through the connection from the color atttribute
	//
	ShadingConnection	colorConnection(thisMObject(), request.multiPath().partialPathName(), "color");

	// If the color attribute is ultimately connected to a environment, 
	// find its filenames, otherwise use the default color texture.
	//
	bool gotAllEnvironmentMaps = TRUE;
	if (colorConnection.type() == ShadingConnection::TEXTURE &&
		colorConnection.texture().hasFn(MFn::kEnvCube))
	{
		// Get the filenames of the texture.
		MFnDependencyNode textureNode(colorConnection.texture());
		MString attributeName;
		MString envNames[6] = { "top", "bottom", "left", "right", "front", "back" };

		// Scan for connected file textures to the environment map node
		//
		for (int i=0; i<6; i++)
		{
			ShadingConnection conn(colorConnection.texture(), request.multiPath().partialPathName(), 
							envNames[i]);

			if (conn.type() == ShadingConnection::TEXTURE &&
				conn.texture().hasFn(MFn::kFileTexture))
			{
				MFnDependencyNode envNode(conn.texture());
				MPlug filenamePlug( conn.texture(), envNode.attribute(MString("fileTextureName")) );

				filenamePlug.getValue(decalName);

				if (decalName.length() == 0)	decalName = "internalDefaultTexture";

				// Append next environment map name
				decalNames.append( decalName );
			}

			// If any of the environment maps are not mapped put in a fake texture
			else
			{
				decalName = "internalDefaultTexture";
				decalNames.append( decalName );
			}
		}
	}
	else
	{
		// Put in a fake texture for each side
		decalName = "internalDefaultTexture";
		for (int i=0; i<6; i++)
		{
			decalNames.append( decalName );
		}
	}

	// Reload cube maps if the name of the textures
	// for any of the cube maps changes
	//
	bool reload = FALSE;
	for (int i=0; i<6; i++)
	{
		if (currentTextureNames[i] != decalNames[i])
		{
			reload = TRUE;
			break;
		}
	}

	view.beginGL();
	{
		if ( reload )
		{
			MString	ypTexName(decalNames[0]);	// y+ == top
			MString	ynTexName(decalNames[1]);	// y- == bottom
			MString	xpTexName(decalNames[2]);	// x+ == left
			MString	xnTexName(decalNames[3]);	// x- == right
			MString	zpTexName(decalNames[4]);	// z+ == front
			MString	znTexName(decalNames[5]);	// z- == back

			MStatus stat;
			if (! (stat = theImage_XP.readFromFile(xpTexName)) )	return MS::kFailure;
			if (! (stat = theImage_XN.readFromFile(xnTexName)) )	return MS::kFailure;
			if (! (stat = theImage_YP.readFromFile(ypTexName)) )	return MS::kFailure;
			if (! (stat = theImage_YN.readFromFile(ynTexName)) )	return MS::kFailure;
			if (! (stat = theImage_ZP.readFromFile(zpTexName)) )	return MS::kFailure;
			if (! (stat = theImage_ZN.readFromFile(znTexName)) )	return MS::kFailure;

			// Only create texture names the first time
			if (fTextureName == -1) 	glGenTextures(1, &fTextureName);

			glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, fTextureName );
			glEnable( GL_TEXTURE_CUBE_MAP_ARB );

			// The cubeMap textures have to have the same size
			//
			unsigned int width, height;
			stat = theImage_XP.getSize( width, height );

			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X_ARB,
				0, GL_RGBA8, width,	height, 0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_XP.pixels() );
			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X_ARB,
				0, GL_RGBA8, width,	height,	0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_XN.pixels() );
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y_ARB,
				0, GL_RGBA8, width,	height,	0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_YP.pixels() );
			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_ARB,
				0, GL_RGBA8, width,	height,	0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_YN.pixels() );
			glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z_ARB,
				0, GL_RGBA8, width,	height,	0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_ZP.pixels() );
			glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_ARB,
				0, GL_RGBA8, width,	height,	0, GL_RGBA,	GL_UNSIGNED_BYTE, theImage_ZN.pixels() );

			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

			for (i=0; i<6; i++)		currentTextureNames[i] = decalNames[i];
		}

		// stage 0 -- cubeMap texture for the refraction
		//
		glActiveTextureARB( GL_TEXTURE0_ARB );
		glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, fTextureName );
		glEnable( GL_TEXTURE_CUBE_MAP_ARB );

		// stage 1 -- cubeMap texture for the reflection
		//
		glActiveTextureARB( GL_TEXTURE1_ARB );
		glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, fTextureName );
		glEnable( GL_TEXTURE_CUBE_MAP_ARB );
	}
	view.endGL();

	return MS::kSuccess;
}


// Initialize the register combiners setting
//
void	hwRefractReflectShader_NV20::initCombiners(const MDrawRequest& request, M3dView& view)
{
	view.beginGL();
	{
		// Use only the 1st stage of the register combiner stages
		//
		glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);

		{
			float refractivity[4], reflectivity[4];
			refractivity[0] = refractivity[1] = refractivity[2] = refractivity[3] = 1.0f - fReflectivity;
			reflectivity[0] = reflectivity[1] = reflectivity[2] = reflectivity[3] = fReflectivity;

			glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, refractivity);
			glCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, reflectivity);
		}
		
		// combiner 0
		// a*b+c*d
		// a is from the refractive color
		// c is from the refrlective texture
		glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		
		// output:
		// (stage, portion, abOutput, cdOutput, sumOutput, scale, bias, abDotProduct, cdDotProduct, muxSum)
		glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
		
		// final combiner
		// output: Frgb = A*B + (1-A)*C + D
		// (variable, input, mapping, componentUsage);
		// Just pass through the D variable
		//
		glFinalCombinerInputNV(GL_VARIABLE_A_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glFinalCombinerInputNV(GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glFinalCombinerInputNV(GL_VARIABLE_C_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
		glFinalCombinerInputNV(GL_VARIABLE_D_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
	}
	view.endGL();
}


// Load the textures, update the necessary variable values, initialize register combiners,
// save and load the matrices with the proper values
//
MStatus	hwRefractReflectShader_NV20::preDraw(const MDrawRequest& request, M3dView& view)
{
	MStatus stat = loadTextures( request, view);

	if( MS::kSuccess != stat )		return stat;

	// get the reflectivity value
	//
	MPlug	tPlug(thisMObject(), reflectivity);
	if( tPlug.getValue( fReflectivity ) )
	{
		if( fReflectivity < 0.01f )	fReflectivity = 0.01f;
		if( fReflectivity > 1.0f )	fReflectivity = 1.0f;
	}
	else	fReflectivity = 0.5f;
	
	// get the refraction index value
	//
	MPlug	rPlug(thisMObject(), refractionIndex);
	if( rPlug.getValue( fRefractionIndex ) )
	{
		if ( fRefractionIndex < 1.0f )	fRefractionIndex = 1.0f;
		if ( fRefractionIndex > 2.0f )	fRefractionIndex = 2.0f;
	}
	else	fRefractionIndex = 1.0f;
	
	initCombiners( request, view );
	
	// Compute the camera rotation angle and axis
	//
	MDagPath	cameraPath;
	MStatus		status = view.getCamera( cameraPath );
	MMatrix		mmatrix = cameraPath.inclusiveMatrix( &status );
	MTransformationMatrix tmatrix( mmatrix );
	
	MQuaternion camRotation = tmatrix.rotation();
	MVector		camAxis;
	double		camTheta;
	camRotation.getAxisAngle(  camAxis, camTheta );
	
	// Convert to degrees from radians
	camTheta *= 57.295779513082320876798154814105;	// == (180 / M_PI)
	
	view.beginGL();
		glMatrixMode( GL_TEXTURE );
		glPushMatrix();
		glLoadIdentity();
		glScalef(1.0, -1.0, 1.0);
		glRotated( camTheta, camAxis[0], camAxis[1], camAxis[2]);
		glMatrixMode( GL_MODELVIEW );
	view.endGL();

	return stat;
}


/* virtual */
MStatus	hwRefractReflectShader_NV20::geometry( const MDrawRequest& request,
	M3dView&		view,
	int				prim,
	unsigned int	writable,
	int				indexCount,
	const unsigned int * indexArray,
	int				vertexCount,
	const int *		vertexIDs,
	const float *	vertexArray,
	int				normalCount,
	const float **	normalArrays,
	int				colorCount,
	const float **	colorArrays,
	int				texCoordCount,
	const float **	texCoordArrays)
{
	// We assume triangles here.
	//
	if( prim != GL_TRIANGLES )		return	MS::kSuccess;

	// Save the current states of the openGL attributes
	//
	view.beginGL();
		glPushAttrib( GL_ALL_ATTRIB_BITS );
		glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
	view.endGL();

	MStatus preDrawStatus = preDraw( request, view );

	if( MS::kSuccess == preDrawStatus )
	{	
		loadVertexProgramGL( view );
		
		view.beginGL();
		{
			glEnable(GL_REGISTER_COMBINERS_NV);
			//
			// Load, bind and enable the vertex program
			//
			glBindProgramNV(GL_VERTEX_PROGRAM_NV, vertex_program_id);
			glEnable(GL_VERTEX_PROGRAM_NV);
			{				
				// VERTEX REGISTERS (Attributes):
				// 0 - coord
				// 2 - normal
				//
				glVertexAttribPointerNV( 0, 3, GL_FLOAT, 0, vertexArray );
				glVertexAttribPointerNV( 2, 3, GL_FLOAT, 0, normalArrays[0] );
				
				glEnableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
				glEnableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
				
				glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indexArray);
				
				glDisableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
				glDisableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
			}
			glDisable(GL_VERTEX_PROGRAM_NV);
			//
			glDisable(GL_REGISTER_COMBINERS_NV);
		}
		view.endGL();

		postDraw( request, view );
	}

	// Restore the openGL attributes
	//
	view.beginGL();
		glPopClientAttrib();
		glPopAttrib();
	view.endGL();

	return preDrawStatus;
}


// Retore the openGL matrices and the openGL texture objects states
//
MStatus	hwRefractReflectShader_NV20::postDraw(
	const MDrawRequest& request,
	M3dView& view )
{
	view.beginGL();
	{
		glMatrixMode( GL_TEXTURE );
		glPopMatrix();
		glMatrixMode( GL_MODELVIEW );
		
		glActiveTextureARB( GL_TEXTURE1_ARB );
		glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, 0 );
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
		
		glActiveTextureARB( GL_TEXTURE0_ARB );
		glBindTexture( GL_TEXTURE_CUBE_MAP_ARB, 0 );
		glDisable(GL_TEXTURE_CUBE_MAP_ARB);
	}
	view.endGL();

	return MS::kSuccess;
}


/* virtual */
int		hwRefractReflectShader_NV20::normalsPerVertex()
{
	return 1;
}

/* virtual */
int		hwRefractReflectShader_NV20::texCoordsPerVertex()
{
	return 1;
}
/////////

// Initialize the necessary OpenGL extensions
//
void hwRefractReflectShader_NV20::init_ext(const char * ext)
{
	if(!glh_init_extension(ext))
	{ cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
}


// The constructor
//
hwRefractReflectShader_NV20::hwRefractReflectShader_NV20()
{
	// Get an reference to the singleton texture cache.
	m_pTextureCache = MTextureCache::instance();

	init_ext("GL_ARB_multitexture");
	init_ext("GL_NV_register_combiners");
	init_ext("GL_NV_vertex_program");

	// Initialize the cubeMap texture names
	//
	fTextureName = -1;
	currentTextureNames[0] = "";
	currentTextureNames[1] = "";
	currentTextureNames[2] = "";
	currentTextureNames[3] = "";
	currentTextureNames[4] = "";
	currentTextureNames[5] = "";

	// Initialize callbacks.
	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
	attachSceneCallbacks();

	vertex_program_id = 0;		// handle for the Vertex Program
}

hwRefractReflectShader_NV20::~hwRefractReflectShader_NV20()
{
	detachSceneCallbacks();
}

void releaseVertexProgram(GLuint* pVertexProgramId)
{
	// If the vertex program id is set...
	if (*pVertexProgramId > 0)
	{
		// Unbind any vertex program...
		glBindProgramNV(GL_VERTEX_PROGRAM_NV, 0);

		glDeleteProgramsNV(1, pVertexProgramId);

		// For sanity, set the id to 0.
		*pVertexProgramId = 0;
	}
}


void hwRefractReflectShader_NV20::releaseEverything()
{
	if (fTextureName != -1)	glDeleteTextures(1, &fTextureName);

	releaseVertexProgram(&vertex_program_id);

	// Release the texture cache through refcounting.
	m_pTextureCache->release();
	if(!MTextureCache::getReferenceCount())
	{
		m_pTextureCache = 0;
	}
}

void hwRefractReflectShader_NV20::attachSceneCallbacks()
{
	fBeforeNewCB  = MSceneMessage::addCallback(MSceneMessage::kBeforeNew,  releaseCallback, this);
	fBeforeOpenCB = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, releaseCallback, this);
	fBeforeRemoveReferenceCB = MSceneMessage::addCallback(MSceneMessage::kBeforeRemoveReference, 
														  releaseCallback, this);
	fMayaExitingCB = MSceneMessage::addCallback(MSceneMessage::kMayaExiting, releaseCallback, this);
}

/*static*/
void hwRefractReflectShader_NV20::releaseCallback(void* clientData)
{
	hwRefractReflectShader_NV20 *pThis = (hwRefractReflectShader_NV20*) clientData;
	pThis->releaseEverything();
}

void hwRefractReflectShader_NV20::detachSceneCallbacks()
{
	if (fBeforeNewCB)				MMessage::removeCallback(fBeforeNewCB);
	if (fBeforeOpenCB)				MMessage::removeCallback(fBeforeOpenCB);
	if (fBeforeRemoveReferenceCB)	MMessage::removeCallback(fBeforeRemoveReferenceCB);
	if (fMayaExitingCB)				MMessage::removeCallback(fMayaExitingCB);

	fBeforeNewCB = 0;
	fBeforeOpenCB = 0;
	fBeforeRemoveReferenceCB = 0;
	fMayaExitingCB = 0;
}

MStatus initializePlugin( MObject obj )
{ 
	MStatus   status;
	
	const MString UserClassify( "shader/surface/utility" );

	MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.0", "Any");
	status = plugin.registerNode( "hwRefractReflectShader_NV20", hwRefractReflectShader_NV20::id, 
			                      hwRefractReflectShader_NV20::creator, hwRefractReflectShader_NV20::initialize,
								  MPxNode::kHwShaderNode, &UserClassify );
	if (!status) {
		status.perror("registerNode");
		return status;
	}

	return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
	MStatus		status;
	MFnPlugin	plugin( obj );

	status = plugin.deregisterNode( hwRefractReflectShader_NV20::id );
	if (!status) {
		status.perror("deregisterNode");
		return status;
	}

	return MS::kSuccess;
}


void * hwRefractReflectShader_NV20::creator()
{
    return new hwRefractReflectShader_NV20();
}

// Initialize the plug-in. Called once when the plug-in is loaded.
// This mostly involve creating attributes.
MStatus hwRefractReflectShader_NV20::initialize()
{
    MFnNumericAttribute nAttr; 
	MStatus status;
	MFnTypedAttribute sAttr; // For string attributes

    // Create input attributes

    colorR = nAttr.create( "colorR", "cr",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    colorG = nAttr.create( "colorG", "cg",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    colorB = nAttr.create( "colorB", "cb",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    color = nAttr.create( "color", "c", colorR, colorG, colorB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 0.5f, 0.5f);
    nAttr.setUsedAsColor(true);

    refractionIndex = nAttr.create( "refractionIndex", "ri", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(1.0f);
    nAttr.setMax(2.0f);
    nAttr.setDefault(1.1f);

    reflectivity = nAttr.create( "reflectivity", "rfl", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.5f);

 // Add the attributes here

    addAttribute(color);
    addAttribute(refractionIndex);
    addAttribute(reflectivity);

    attributeAffects (colorR, outColor);
    attributeAffects (colorG, outColor);
    attributeAffects (colorB, outColor);
    attributeAffects (color,  outColor);

    attributeAffects (refractionIndex,   outColor);
    attributeAffects (reflectivity,   outColor);

    return MS::kSuccess;
}


// This function gets called by Maya to evaluate the texture.
// See "Writing a shading node plug-in" in the documentation
// for more information.
//
// CAVEAT: This part of the HW shader plug-in is meant to allow
// seamless transition from HW to SW rendering.
// Unfortunately, as of 4.0.1 it's somewhat flaky.
// Meanwhile, it is recommended to build two shading networks
// in parallel (one for SW, one for HW) and use MEL scripts
// to switch from one to the other.
//
MStatus hwRefractReflectShader_NV20::compute(
const MPlug&      plug,
      MDataBlock& block ) 
{ 
	// Get color and lightModel from the input block.
	// Get UV coordinates from the input block.
	
	bool k = false;
    k |= (plug==outColor);
    k |= (plug==outColorR);
    k |= (plug==outColorG);
    k |= (plug==outColorB);
    if( !k ) return MS::kUnknownParameter;

    MFloatVector resultColor(0.0,0.0,0.0);

    // set ouput color attribute
    MDataHandle outColorHandle = block.outputValue( outColor );
    MFloatVector& outColor = outColorHandle.asFloatVector();
    outColor = resultColor;
    outColorHandle.setClean();

    return MS::kSuccess;
}
