hwToonShader_NV20.cpp

//-
// ==========================================================================
// 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 is meant to allow cartoon-like effects.
//              It allows the user to specify a base decal texture,
//              and a lighting look-up texture.
//
//  This shader builds on the foundation demonstrated in the 
//  hwUnlitShader.
//
//


// Uncomment the #ifdef below if you want to debug the vertex program by
// output a COL0 value that corresponds to an intermediate calculation.
// The only sane way we could find to debug that thing.
//#define DEBUGGING_VERTEX_PROGRAM 1

#ifdef WIN32
#pragma warning( disable : 4786 )       // Disable STL warnings.
#endif

#include <maya/MIOStream.h>
#include <math.h>

#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDagPath.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFnLight.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/MEulerRotation.h>
#include <maya/MFnNonAmbientLight.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.
//
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glext.h>

#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 "hwToonShader_NV20.h"
#include "ShadingConnection.h"

MTypeId hwToonShader_NV20::id( 0x00105443 );
/*static*/ const unsigned int hwToonShader_NV20::lookup_texture_size(256);


void hwToonShader_NV20::postConstructor( )
{
    setMPSafe(false);
}

// Static attribute instances.
//
MObject  hwToonShader_NV20::color;
MObject  hwToonShader_NV20::colorR;
MObject  hwToonShader_NV20::colorG;
MObject  hwToonShader_NV20::colorB;

MObject  hwToonShader_NV20::lightModel;
MObject  hwToonShader_NV20::lightModelR;
MObject  hwToonShader_NV20::lightModelG;
MObject  hwToonShader_NV20::lightModelB;

MObject  hwToonShader_NV20::camera;
MObject  hwToonShader_NV20::cameraX;
MObject  hwToonShader_NV20::cameraY;
MObject  hwToonShader_NV20::cameraZ;

MObject  hwToonShader_NV20::uCoord;
MObject  hwToonShader_NV20::vCoord;
MObject  hwToonShader_NV20::uvCoord;

MObject  hwToonShader_NV20::uBias;
MObject  hwToonShader_NV20::vBias;

MObject  hwToonShader_NV20::uvFilterSize;
MObject  hwToonShader_NV20::uvFilterSizeX;
MObject  hwToonShader_NV20::uvFilterSizeY;

MObject  hwToonShader_NV20::shininess;
MObject  hwToonShader_NV20::lightColor;
MObject  hwToonShader_NV20::lightColorR;
MObject  hwToonShader_NV20::lightColorG;
MObject  hwToonShader_NV20::lightColorB;

void hwToonShader_NV20::printGlError( const char *call )
{
    GLenum error;

    while( (error = glGetError()) != GL_NO_ERROR ) {
        assert(0);
        cerr << call << ":" << error << " is " << (const char *)gluErrorString( error ) << "\n";
    }
}


// The Vertex Program for the textured-light-model shading effect.
// Meant to be used as a cartoon shader.
//
// CONSTANTS:
//  0- 3  4x4 ModelView-Projection composite matrix
//  4- 7  4x4 ModelView matrix
//  8-10  light amb/diff/spec
// 11     light dir vector (from surface to light, in object space)
// 12     camera position in object space (possibly not normalized)

// VERTEX REGISTERS (mapped so that standard gl calls work):
// 0 - coord
// 2 - normal
// 3 - primary color
// 8 - texcoord0 (lighting look-up table)
// 9 - texcoord1 (decal, optional)

// REGISTERS:
//
// R0 - normalized view (surface-to-camera) direction in object space.
// R1 - normalized surface normal in object space.
// R2 - normalized light (surface-to-light) direction.
//
//
char vertexProgramString[] = 
    "!!VP1.0\n"

        // Multiply the vertex coords by the modelview-projection composite matrix,
        // to get clip space coordinates.
        "DP4   o[HPOS].x, c[0], v[0];"
        "DP4   o[HPOS].y, c[1], v[0];"
        "DP4   o[HPOS].z, c[2], v[0];"
        "DP4   o[HPOS].w, c[3], v[0];"

        // Normalize the N (normal), in case the modelview matrix is not a simple rotation.
        "MOV   R1, v[2];"
        "DP3   R1.w, R1, R1;"           
        "RSQ   R1.w, R1.w;"
        "MUL   R1.xyz, R1, R1.w;"

        // Normalize the L (light) direction.
        "MOV   R2, c[11];"
        "DP3   R2.w, R2, R2;"           
        "RSQ   R2.w, R2.w;"
        "MUL   R2.xyz, R2, R2.w;"

        // The vertex position, normal and light positions are expressed in object space at
        // this point. We need to find the view direction in object space too.
        "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
        "DP3   R0.w, R0, R0;"           // normalize the view direction.
        "RSQ   R0.w, R0.w;"
        "MUL   R0.xyz, R0, R0.w;"
        
        // Find the texture coordinates to fetch from the toon-param texture.
        // u = N dot L.
        // v = N dot V.
        "DP3 o[TEX0].x, R1, R2;"
        "DP3 o[TEX0].y, R1, R0;"

        // Move the texture coordinates from tex 1 if appropriate.
        "MOV o[TEX1], v[9];"

#ifndef DEBUGGING_VERTEX_PROGRAM
        // Put diffuse lighting into color.
        "MUL o[COL0], v[3], c[9];"
#else
        // VISUALIZE VECTOR
        "MOV o[COL0], R2;"  // normalized light
#endif      

        "END";


char vertexProgramStringPoint[] = 
    "!!VP1.0\n"

        // Multiply the vertex coords by the modelview-projection composite matrix,
        // to get clip space coordinates.
        "DP4   o[HPOS].x, c[0], v[0];"
        "DP4   o[HPOS].y, c[1], v[0];"
        "DP4   o[HPOS].z, c[2], v[0];"
        "DP4   o[HPOS].w, c[3], v[0];"

        // Normalize the N (normal), in case the modelview matrix is not a simple rotation.
        "MOV   R1, v[2];"
        "DP3   R1.w, R1, R1;"           
        "RSQ   R1.w, R1.w;"
        "MUL   R1.xyz, R1, R1.w;"

        // Compute normalize the L (light) direction.
        "ADD   R2, c[11], -v[0];"
        "DP3   R2.w, R2, R2;"           
        "RSQ   R2.w, R2.w;"
        "MUL   R2.xyz, R2, R2.w;"

        // The vertex position, normal and light positions are expressed in object space at
        // this point. We need to find the view direction in object space too.
        "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
        "DP3   R0.w, R0, R0;"           // normalize the view direction.
        "RSQ   R0.w, R0.w;"
        "MUL   R0.xyz, R0, R0.w;"
        
        // Find the texture coordinates to fetch from the toon-param texture.
        // u = N dot L.
        // v = N dot V.
        "DP3 o[TEX0].x, R1, R2;"
        "DP3 o[TEX0].y, R1, R0;"

        // Move the texture coordinates from tex 1 if appropriate.
        "MOV o[TEX1], v[9];"

#ifndef DEBUGGING_VERTEX_PROGRAM
        // Put diffuse lighting into color.
        "MUL o[COL0], v[3], c[9];"
//      "MOV o[COL0], c[9];"
#else
        // VISUALIZE VECTOR
        "MOV o[COL0], R2;"  // normalized light
#endif      

        "END";


char vertexProgramStringPointDecay[] = 
    "!!VP1.0\n"

        // Multiply the vertex coords by the modelview-projection composite matrix,
        // to get clip space coordinates.
        "DP4   o[HPOS].x, c[0], v[0];"
        "DP4   o[HPOS].y, c[1], v[0];"
        "DP4   o[HPOS].z, c[2], v[0];"
        "DP4   o[HPOS].w, c[3], v[0];"

        // Normalize the N (normal), in case the modelview matrix is not a simple rotation.
        "MOV   R1, v[2];"
        "DP3   R1.w, R1, R1;"           
        "RSQ   R1.w, R1.w;"
        "MUL   R1.xyz, R1, R1.w;"

        // Compute normalize the L (light) direction.
        "ADD   R2, c[11], -v[0];"
        "DP3   R2.w, R2, R2;"           
        "RSQ   R2.w, R2.w;"
        "MUL   R2.xyz, R2, R2.w;"

        // The vertex position, normal and light positions are expressed in object space at
        // this point. We need to find the view direction in object space too.
        "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
        "DP3   R0.w, R0, R0;"           // normalize the view direction.
        "RSQ   R0.w, R0.w;"
        "MUL   R0.xyz, R0, R0.w;"
        
        // Find the texture coordinates to fetch from the toon-param texture.
        // u = N dot L.
        // v = N dot V.
        "DP3 o[TEX0].x, R1, R2;"
        "DP3 o[TEX0].y, R1, R0;"

        // Move the texture coordinates from tex 1 if appropriate.
        "MOV o[TEX1], v[9];"

#ifndef DEBUGGING_VERTEX_PROGRAM
        // Multiply diffuse lighting into material color.
        // Take into account decay
        "MOV R4, c[9];"
        "MOV R5, c[9];"
        "MUL R5.xyz, R4, R2.w;"
        "MUL o[COL0], v[3], R5;"
#else
        // VISUALIZE VECTOR
        "MOV o[COL0], R2;"  // normalized light
#endif      

        "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 hwToonShader_NV20::loadVertexProgramGL()
{
    GLenum error = glGetError();
    assert(!error);

    // If the vertex programs haven't been loaded yet,
    // do it now. (Note that they are shared between all contexts.)
    if (!fVertexProgramsLoaded)
    {
        initVertexProgram(vertexProgramString, &fVertexProgramDirectional);
        initVertexProgram(vertexProgramStringPointDecay, &fVertexProgramPointDecay);
        initVertexProgram(vertexProgramStringPoint, &fVertexProgramPointNoDecay);
        fVertexProgramsLoaded = true;
    }

    // Set up the constant values.
    //
    // CONSTANTS:
    //  0- 3  4x4 ModelView-Projection composite matrix
    //  4- 7  4x4 ModelView  matrix
    //  8-10  light amb/diff/spec
    // 11     light dir vector (from surface to light)
    //
    glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
    glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 4, GL_MODELVIEW, GL_IDENTITY_NV);
    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 8, 1, 1, 1, 1);
    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1, 1, 1, 1);
    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 10, 1, 1, 1, 1);
    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightRotation[0], -lightRotation[1], -lightRotation[2], 0);   // light dir...
}

void hwToonShader_NV20::bind_lookup_table()
{
//  make_lookup_texture();

    lookup_texture->bind();

    if (fLookupTextureReprocessed)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE_ALPHA, lookup_texture_size, lookup_texture_size, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, lookup_table); 
        fLookupTextureReprocessed = false;
    }

    lookup_texture->parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    lookup_texture->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    lookup_texture->parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    lookup_texture->parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}

// Initialize the necessary OpenGL extensions
//
void hwToonShader_NV20::init_ext(const char * ext)
{
    if(!glh_init_extension(ext))
    { cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
}

hwToonShader_NV20::hwToonShader_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");

    isDirectionalLight = true;  // light's rotation is connected to the lightRotation attr

    // Set the shininess and shininess scale to absurd values, so that the
    // look-up table automatically get recomputed during the first update.
    currentShininessValue = -1.0;
    currentShininessScale = -1.0;
    lookup_texture = NULL;
    lookup_table   = NULL;
    fLookupTextureReprocessed = false;

    // Initialize callbacks.
    fBeforeNewCB = 0;
    fBeforeOpenCB = 0;
    fBeforeRemoveReferenceCB = 0;
    fMayaExitingCB = 0;
    attachSceneCallbacks();

    // Initialize the vertex program ids...
    fVertexProgramsLoaded = false;
    fVertexProgramDirectional = 0;
    fVertexProgramPointDecay = 0;
    fVertexProgramPointNoDecay = 0;
    // All vertex programs will get allocated and loaded
    // during the first refresh.
}

hwToonShader_NV20::~hwToonShader_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 hwToonShader_NV20::releaseEverything()
{
    release_lookup_texture();

    // Release all loaded vertex programs.
    if (fVertexProgramsLoaded)
    {
        releaseVertexProgram(&fVertexProgramDirectional);
        releaseVertexProgram(&fVertexProgramPointDecay);
        releaseVertexProgram(&fVertexProgramPointNoDecay);
        fVertexProgramsLoaded = false;
    }

    // Release the texture cache through refcounting.
    m_pTextureCache->release();
    if(!MTextureCache::getReferenceCount())
    {
        m_pTextureCache = 0;
    }
}

void hwToonShader_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 hwToonShader_NV20::releaseCallback(void* clientData)
{
    hwToonShader_NV20 *pThis = (hwToonShader_NV20*) clientData;
    pThis->releaseEverything();
}

void hwToonShader_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.5", "Any");
    status = plugin.registerNode( "hwToonShader_NV20", hwToonShader_NV20::id, 
                                  hwToonShader_NV20::creator, hwToonShader_NV20::initialize,
                                  MPxNode::kHwShaderNode, &UserClassify );
    if (!status) {
        status.perror("registerNode");
        return status;
    }

    return MS::kSuccess;
}

MStatus uninitializePlugin( MObject obj )
{
    MStatus   status;
    
    MFnPlugin plugin( obj );

    plugin.deregisterNode( hwToonShader_NV20::id );
    if (!status) {
        status.perror("deregisterNode");
        return status;
    }

    return MS::kSuccess;
}


void * hwToonShader_NV20::creator()
{
    return new hwToonShader_NV20();
}

// Initialize the plug-in. Called once when the plug-in is loaded.
// This mostly involve creating attributes.
MStatus hwToonShader_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);

    lightModelR = nAttr.create( "lightModelR", "c2r",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightModelG = nAttr.create( "lightModelG", "c2g",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightModelB = nAttr.create( "lightModelB", "c2b",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightModel = nAttr.create( "lightModel", "c2", lightModelR, lightModelG, lightModelB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 1.0f, 1.0f);
    nAttr.setUsedAsColor(true);

    uCoord = nAttr.create( "uCoord", "u", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    vCoord = nAttr.create( "vCoord", "v", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);
 
    uvCoord = nAttr.create( "uvCoord","uv", uCoord, vCoord);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f, 0.5f );
    nAttr.setHidden(true);

    uBias = nAttr.create( "uBias", "bu", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f);

    vBias = nAttr.create( "vBias", "bv", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.5f);

    uvFilterSizeX = nAttr.create( "uvFilterSizeX", "fsx", MFnNumericData::kFloat);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    uvFilterSizeY = nAttr.create( "uvFilterSizeY", "fsy", MFnNumericData::kFloat);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    uvFilterSize = nAttr.create("uvFilterSize","fs",uvFilterSizeX,uvFilterSizeY);
    nAttr.setStorable(false);
    nAttr.setReadable(true);
    nAttr.setWritable(true);
    nAttr.setHidden(true);

    cameraX = nAttr.create( "cameraX", "camx",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f);

    cameraY = nAttr.create( "cameraY", "camy",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f);

    cameraZ = nAttr.create( "cameraZ", "camz",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    camera = nAttr.create( "camera", "cam", cameraX, cameraY, cameraZ);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0f, 0.0f, 1.0f);

    shininess = nAttr.create( "shininess", "sn", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setMin(0.0f);
    nAttr.setMax(1.0f);
    nAttr.setDefault(0.5f);

    lightColorR = nAttr.create( "lightColorR", "lcr", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColorG = nAttr.create( "lightColorG", "lcg", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColorB = nAttr.create( "lightColorB", "lcb", MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

    lightColor = nAttr.create( "lightColor", "lc", lightColorR, lightColorG, lightColorB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f, 1.0f, 1.0f);
    nAttr.setUsedAsColor(true);


 // Add the attributes here

    addAttribute(color);
    addAttribute(lightModel);

    addAttribute(uvCoord);

    addAttribute(uBias);
    addAttribute(vBias);

    addAttribute(uvFilterSize);

    addAttribute(camera);
    addAttribute(shininess);
    addAttribute(lightColor);

    attributeAffects (colorR, outColor);
    attributeAffects (colorG, outColor);
    attributeAffects (colorB, outColor);
    attributeAffects (color,  outColor);
    attributeAffects (lightModelR, outColor);
    attributeAffects (lightModelG, outColor);
    attributeAffects (lightModelB, outColor);
    attributeAffects (lightModel,  outColor);
    attributeAffects (uCoord,  outColor);
    attributeAffects (vCoord,  outColor);
    attributeAffects (uvCoord, outColor);
    attributeAffects (uBias,   outColor);
    attributeAffects (vBias,   outColor);

    attributeAffects (cameraX,  outColor);
    attributeAffects (cameraY,  outColor);
    attributeAffects (cameraZ,  outColor);
    attributeAffects (camera,   outColor);

    attributeAffects (shininess,   outColor);
    attributeAffects (lightColorR, outColor);
    attributeAffects (lightColorG, outColor);
    attributeAffects (lightColorB, outColor);
    attributeAffects (lightColor,  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.
//
MStatus hwToonShader_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);

    float&  u = block.inputValue( uCoord ).asFloat();
    float&  v = block.inputValue( vCoord ).asFloat();
    float& bu = block.inputValue( uBias ).asFloat();
    float& bv = block.inputValue( vBias ).asFloat();

    if ( bu <= 0.0 ) bu = 0.001f;
    if ( bv <= 0.0 ) bv = 0.001f;

    MFloatVector& surfaceColor  = block.inputValue( color ).asFloatVector();
    MFloatVector& surfaceColor2 = block.inputValue( lightModel ).asFloatVector();

    // normalize the UV coords
    u = u - int(u);
    v = v - int(v);

    resultColor = surfaceColor;

    // set ouput color attribute
    MDataHandle outColorHandle = block.outputValue( outColor );
    MFloatVector& outColor = outColorHandle.asFloatVector();
    outColor = resultColor;
    outColorHandle.setClean();

    return MS::kSuccess;
}


// To get 3 float values from the node attribute
//
MStatus hwToonShader_NV20::getFloat3(MObject attr, float value[3])
{
    MStatus status = MS::kSuccess;

    // Get the attr to use
    //
    MPlug   plug(thisMObject(), attr);

    MObject object;
    status = plug.getValue(object);
    if (!status)
    {
        status.perror("hwToonShader_NV20::getFloat3 plug.getValue.");
        return status;
    }

    MFnNumericData data(object, &status);
    if (!status)
    {
        status.perror("hwToonShader_NV20::getFloat3 construct data.");
        return status;
    }

    status = data.getData(value[0], value[1], value[2]);
    if (!status)
    {
        status.perror("hwToonShader_NV20::getFloat3 get values.");
        return status;
    }

    return status;
}

// To get a string value from the node attribute
//
MStatus hwToonShader_NV20::getString(MObject attr, MString &str)
{
    MPlug   plug(thisMObject(), attr);
    MStatus status = plug.getValue( str );

    return status;
}

/* virtual */
MStatus hwToonShader_NV20::bind(const MDrawRequest& request, M3dView& view)
{
    MStatus status;

    // Get the decal and lightModel map file names
    //
    MString decalName = "";
    MString lightModelName  = "";

    ShadingConnection colorConnection(thisMObject(), request.multiPath().partialPathName(), "color");
    ShadingConnection lightModelConnection (thisMObject(), request.multiPath().partialPathName(), "lightModel");

    // If the lightModel attribute is ultimately connected to a file texture, find its filename.
    // otherwise use the default lightModel texture.
    if (lightModelConnection.type() == ShadingConnection::TEXTURE &&
        lightModelConnection.texture().hasFn(MFn::kFileTexture))
    {
        // Get the filename of the texture.
        MFnDependencyNode textureNode(lightModelConnection.texture());
        MPlug filenamePlug( lightModelConnection.texture(), textureNode.attribute(MString("fileTextureName")) );
        filenamePlug.getValue(lightModelName);
    }


    // Fail safe quit
    //
    if (lightModelName.length() == 0 )
    {
        view.beginGL();
        glPushAttrib( GL_ALL_ATTRIB_BITS );     // This might be too conservative
        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
        glEnable(GL_COLOR_MATERIAL);
        glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
        view.endGL();
        return MS::kSuccess;
    }

    view.beginGL();

    glPushAttrib( GL_ALL_ATTRIB_BITS );
    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    /* Starts Here... */
    
    // stage 0 -- lighting model texture
    glActiveTextureARB( GL_TEXTURE0_ARB );
    glEnable(GL_TEXTURE_2D);
    if(m_pTextureCache)
        m_pTextureCache->bind(lightModelConnection.texture(), MTexture::RGBA, false);
    
    // With light color and intensity
    //
    if (colorConnection.type() != ShadingConnection::TEXTURE)
    {
        MColor color = colorConnection.constantColor();
        glColor4f(color.r, color.g, color.b, color.a);
    }
    
    // The register combiner will do the multiplication between
    // the fetched light model result and the base (vertex or decal-textured) color
    //
    glEnable(GL_REGISTER_COMBINERS_NV);

    glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);

    
    float constColor0[4];
    constColor0[0] = constColor0[1] = constColor0[2] = constColor0[3] = 1.0;
    glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, constColor0);
    

#ifndef DEBUGGING_VERTEX_PROGRAM
    // Combiner stage 0 does the illumination modulation on the vertex color
    //
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_PRIMARY_COLOR_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);

    glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, 
                       GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);

#else
    // Simplified register combiners to help debugging vertex program.
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_PRIMARY_COLOR_NV, 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_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);
    glCombinerInputNV(GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);

    glCombinerOutputNV(GL_COMBINER0_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, 
                       GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE);
#endif // DEBUGGING_VERTEX_PROGRAM

    // The final Combiner just pass through. May want to add fog later.
    //
    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_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);

    view.endGL();

    return MS::kSuccess;
}


/* virtual */
MStatus hwToonShader_NV20::unbind(const MDrawRequest& request,
               M3dView& view)
{
    view.beginGL();
    
    glDisable(GL_REGISTER_COMBINERS_NV);

    glActiveTextureARB( GL_TEXTURE0_ARB );
    glDisable(GL_TEXTURE_2D);

    glPopClientAttrib();
    glPopAttrib();

    view.endGL();

    return MS::kSuccess;
}

/* virtual */
MStatus hwToonShader_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;        

    view.beginGL();

    // Find out if we have a directional light before
    // loading the vertex program since we use a different
    // vertex program depending on whether the light is a directional
    // one or not
    //
    isDirectionalLight = true; // Assume is directional
    isNonAmbientLight = false;

    boolean useDefaultLight = false;

    unsigned int numLights;
    MDagPath lightPath;
    view.getLightCount( numLights );
    if (numLights)
    {
        M3dView::LightingMode mode;
        view.getLightingMode(mode);
        if (mode == M3dView::kLightDefault)
        {
            useDefaultLight = true;
            isDirectionalLight = true;
        }
        else
        {
            view.getLightPath( 0, lightPath );
            MObject lightObj = lightPath.node();

            isDirectionalLight = lightObj.hasFn( MFn::kDirectionalLight );
            isNonAmbientLight = lightObj.hasFn( MFn::kNonAmbientLight );
            if (isNonAmbientLight)
            {
                MFnNonAmbientLight mNonAmbientLight(lightObj);
                if (mNonAmbientLight.decayRate() == 0)
                    isNonAmbientLight = false;
            }
        }
    }

    loadVertexProgramGL();

    // Bind and enable the appropriate vertex program,
    // depending on light type.
    //
    if (isDirectionalLight)
        glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramDirectional);
    else if (isNonAmbientLight)
        glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointDecay);
    else
        glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointNoDecay);

    // Assert if an error occurs after binding the vertex programs.
    GLenum error = glGetError();
    assert(error == GL_NO_ERROR);

    // Enable the vertex program.
    glEnable(GL_VERTEX_PROGRAM_NV);

    // Get object's inverse matrix (ie: from world to object space.)
    MDagPath objPath = request.multiPath();
    MMatrix objMatrix = objPath.inclusiveMatrixInverse();

    // Get the light direction in object space.
    // This code assumes that there is a directional light in the scene,
    // and that it is the first light in DAG order.
    //
    if (numLights)
    {
        // Handle default lighting mode
        if (useDefaultLight )
        {
            // Provide the direction to the vertex program (constant 11)
            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1);
            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1,1,1,1);
        }
        else
        {
            view.getLightPath( 0, lightPath );
            MMatrix matrix = lightPath.inclusiveMatrix();

            isDirectionalLight = lightPath.node().hasFn( MFn::kDirectionalLight );

            // Get rotation of a directional light in object space
            if (isDirectionalLight)
            {
                // Get rotation in world space
                MVector lightDir(0,0,1); // origin
                lightDir *= matrix;

                // Transform into object space
                lightDir *= objMatrix;

                // Provide the direction to the vertex program (constant 11)
                glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, lightDir.x, lightDir.y, lightDir.z, 1);
            }
            // Get the position of a non-directional light in object space
            else
            {
                MPoint lightPos(0,0,0); // origin
                lightPos *= matrix;

                // Transform into object space
                lightPos *= objMatrix;

                // Provide the position to the vertex program (constant 11)
                glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, lightPos.x, lightPos.y, lightPos.z, 1);
            }

            MFnLight mLight(lightPath.node());

            // Set the light's color.
            MColor lightColor = mLight.color();
            float intensity = mLight.intensity();
            lightColor.r *= intensity;
            lightColor.g *= intensity;
            lightColor.b *= intensity;

            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, lightColor.r, 
                                   lightColor.g, lightColor.b, 1);
        }
    }
    else
    {
        // Set some default values
        glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1);
        glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0, 1.0, 1.0, 1);
    }

    // Find the camera position in geometry's object space
    float cameraPos[4] = {0.0f, 0.0f, 0.0f, 0.0f};
    {
        MDagPath camDag;
        view.getCamera(camDag);

        MPoint cameraInObject(0,0,0); 
        
        MMatrix cameraToWorldMatrix = camDag.inclusiveMatrix();

        cameraInObject *= cameraToWorldMatrix; // to world
        cameraInObject *= objMatrix;

        glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 12, cameraInObject.x, cameraInObject.y, cameraInObject.z, 1);
    }

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

    glClientActiveTextureARB(GL_TEXTURE0_ARB);

    view.endGL();

    return MS::kSuccess;
}

/* virtual */
int     hwToonShader_NV20::normalsPerVertex()
{
    return 1;
}

/* virtual */
int     hwToonShader_NV20::texCoordsPerVertex()
{
    return 1;
}

// Release the lookup texture/image.
void hwToonShader_NV20::release_lookup_texture()
{
    if (lookup_table)
    {
        delete lookup_table;
        lookup_table = NULL;
    }
    
    if (lookup_texture)
    {
        delete lookup_texture;
        lookup_table = NULL;
    }
}