- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Examples

# hwDecalBumpShader_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 can simultaneously display both decal
    //              (base color) and bump textures.
    //
    //  This shader builds on the foundation demonstrated in the
    //  hwUnlitShader.
    //
    //  Additionally, this sample demonstrates how to:
    //      - Use vendor-specific extensions, namely vertex programs,
    //        texture shaders and register combiners, to achieve
    //        effects that are impossible in standard OpenGL.
    //      - Convert height field bump format (used by Maya) into
    //        a normal map format, for real-time rendering.
    //
    //  Many parameters are easily customizable:
    //      - The MNormalMapConverter::convertToNormalMap_InPlace()
    //        bumpScale parameter is currently constant. You can change
    //        it to a different value to increase or decrease the
    //        bumpiness.
    //
    //  PS: Thanks go to DAR from nVidia, for his help in making this
    //      shader more robust. ;-)
    //

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

    MTypeId hwDecalBumpShader_NV20::id( 0x00105441 );
    /*static*/ const unsigned int hwDecalBumpShader_NV20::lookup_texture_size(256);

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

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

    MObject  hwDecalBumpShader_NV20::bump;
    MObject  hwDecalBumpShader_NV20::bumpR;
    MObject  hwDecalBumpShader_NV20::bumpG;
    MObject  hwDecalBumpShader_NV20::bumpB;

    MObject  hwDecalBumpShader_NV20::camera;
    MObject  hwDecalBumpShader_NV20::cameraX;
    MObject  hwDecalBumpShader_NV20::cameraY;
    MObject  hwDecalBumpShader_NV20::cameraZ;

    MObject  hwDecalBumpShader_NV20::light;
    MObject  hwDecalBumpShader_NV20::lightX;
    MObject  hwDecalBumpShader_NV20::lightY;
    MObject  hwDecalBumpShader_NV20::lightZ;

    MObject  hwDecalBumpShader_NV20::uCoord;
    MObject  hwDecalBumpShader_NV20::vCoord;
    MObject  hwDecalBumpShader_NV20::uvCoord;

    MObject  hwDecalBumpShader_NV20::uBias;
    MObject  hwDecalBumpShader_NV20::vBias;

    MObject  hwDecalBumpShader_NV20::uvFilterSize;
    MObject  hwDecalBumpShader_NV20::uvFilterSizeX;
    MObject  hwDecalBumpShader_NV20::uvFilterSizeY;

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

    void hwDecalBumpShader_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 Decal Bump effect.
    //
    // 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)

    // VERTEX REGISTERS:
    // 0 - coord
    // 1 - normal
    // 2 - texcoord0
    // 3 - texcoord1
    // 4 - texcoord2 (binorm)

    // REGISTERS:
    // 4 = eye space vertex coordinate
    // 5 = eye space tangent vector
    // 6 = eye space binormal vector
    // 7 = eye space normal vector          (VERIFIED)
    // 8 = normalized eye space view vector (VERIFIED) (goes in the direction from vertex position to camera position)
    // 9 = eye space half-angle vector
    //
    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];"

            // Multiply the vertex coords by the modelview matrix,
            // to get eye-space coordinates.
            "DP4   R4.x, c[4], v[0];"
            "DP4   R4.y, c[5], v[0];"
            "DP4   R4.z, c[6], v[0];"
            "DP4   R4.w, c[7], v[0];"

            // Multiply the normals by the modelview matrix,
            // resulting in eye-space normals.
            "DP3   R7.x, c[4], v[1];"
            "DP3   R7.y, c[5], v[1];"
            "DP3   R7.z, c[6], v[1];"
            // Re-normalize, in case the model-view matrix's is not a simple rotation.
            "DP3   R7.w, R7, R7;"
            "RSQ   R7.w, R7.w;"
            "MUL   R7.xyz, R7, R7.w;"

            // Multiply the binormals by the modelview matrix,
            // resulting in eye-space binormals.
            "DP3   R6.x, c[4], v[4];"
            "DP3   R6.y, c[5], v[4];"
            "DP3   R6.z, c[6], v[4];"
            // Re-normalize the binormals.
            "DP3   R6.w, R6, R6;"
            "RSQ   R6.w, R6.w;"
            "MUL   R6.xyz, R6, R6.w;"

            // Build tangent: tangent = binormal x normal.
            "MUL   R5, R6.zxyw, R7.yzxw;"
            "MAD   R5, R6.yzxw, R7.zxyw, -R5;"
            // put the sign in the tangent.
            "MUL   R5.xyz, R5, v[4].w;"
            // Re-normalize the tangent.
            "DP3   R5.w, R5, R5;"
            "RSQ   R5.w, R5.w;"
            "MUL   R5.xyz, R5, R5.w;"

            // Calculate eye space view vector.
            // (In essence, a normalization of the inverse
            //  of the vertex coordinate in eye-space)
            "DP3   R8.w, R4, R4;"
            "RSQ   R8.w, R8.w;"
            "MUL   R8.xyz, R4, -R8.w;"

            // Calculate (and normalize) tangent space half-angle vector.
            "ADD   R9, R8, c[11];"
            "DP3   R9.w, R9, R9;"
            "RSQ   R9.w, R9.w;"
            "MUL   R9.xyz, R9, R9.w;"

            // Transform half-angle vector into tangent space.
            "DP3   o[TEX3].x, R5, R9;"
            "DP3   o[TEX3].y, R6, R9;"
            "DP3   o[TEX3].z, R7, R9;"

            // Transform light direction vector into tangent space.
            "DP3   o[TEX2].x, R5, c[11];"
            "DP3   o[TEX2].y, R6, c[11];"
            "DP3   o[TEX2].z, R7, c[11];"

    #ifndef DEBUGGING_VERTEX_PROGRAM
            // Put diffuse lighting into color.
            "DP3 o[COL0], R7, c[11];"
    #else
            // VISUALIZE NORMAL VECTOR IN EYE SPACE
            "MOV o[COL0], R7;"
    #endif
            // Copy texcoords.
            "MOV o[TEX0], v[2];"
            "MOV o[TEX1], v[3];"

            "END";

    // Load the vertexProgram and fill in the necessary constants used in the vertex program.
    //
    void hwDecalBumpShader_NV20::loadVertexProgramGL()
    {
        GLenum error = glGetError();
        assert(!error);

        // If the vertex program hasn't been created yet, do it now.
        // Note that Maya shares textures, display lists and vertex programs
        // between all viewports, so this only need to be done once.
        if (vertex_program_id == 0)
        {
            glGenProgramsNV(1, &vertex_program_id);
            error = glGetError();
            assert(!error);

            // Attempt to load the program.
            unsigned int length = strlen(vertexProgramString);
            glLoadProgramNV(GL_VERTEX_PROGRAM_NV, vertex_program_id, length, (const GLubyte *) vertexProgramString);
            error = glGetError();
            assert(!error);

            if (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);
                }
            }
        }

        // 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...
    }

    // Illumination (diffuse and specular) lookup table
    // (This function assumes that lookup_image and lookup_texture have been deallocated.)
    //
    void hwDecalBumpShader_NV20::make_lookup_texture()
    {
        // Re-calculate the look-up texture, if the shininess value/scale has changed.
        //
        float   shininessScale = 1.0f;
        MPlug   plug(thisMObject(), shininess);

        // Get the shininess scaling factor
        //
        MStatus status = plug.getValue(shininessScale);
        if (!status)
        {
            status.perror("hwDecalBumpShader_NV20::bind plug.getValue.");
            return;
        }

        if ( shininessScale < 0.01f )
            shininessScale = 0.01f;
        if ( shininessScale > 1.0f )
            shininessScale = 1.0f;

        // shininess factor is between 20 and 180
        //
        float shininessValue = 1.0f;
        shininessValue = 200.0f * (1.0f - 0.8f * shininessScale);

        // Only recompute the lookup texture if the values have changed since the last bind.
        if (shininessValue == currentShininessValue && shininessScale == currentShininessScale)
            return;

        currentShininessValue = shininessValue;
        currentShininessScale = shininessScale;

        unsigned int imgsize = lookup_texture_size;
        float imgsizeM1 = (float) (imgsize - 1);

        // Allocate the lookup_image and lookup_texture.
        if (lookup_table == NULL)
            lookup_table = new unsigned char[imgsize*imgsize*2];
        if (lookup_texture == NULL)
            lookup_texture = new tex_object_2D;

        // Fill it up.
        unsigned char * ip = lookup_table;
        for(int j=0; j < imgsize; j++)
        {
            unsigned char a = (unsigned char) (shininessScale * (255.0 * pow((j/imgsizeM1), shininessValue)));
            for(int i=0; i < imgsize; i++)
            {
                *ip++ = (unsigned char) ((255.0 - 64) * (i/imgsizeM1)) + 64;
                *ip++ = a;
            }
        }

        fLookupTextureReprocessed = true;

        return;
    }

    void hwDecalBumpShader_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 hwDecalBumpShader_NV20::init_ext(const char * ext)
    {
        if(!glh_init_extension(ext))
            { cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
    }

    hwDecalBumpShader_NV20::hwDecalBumpShader_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();

        vertex_program_id = 0;      // handle for the Vertex Program

        // The vertex program will get loaded during the first refresh.
        // We cannot do it here since it's not guaranteed that the GL
        // context will be current at construction or destruction time
        // (although it generally is).
    }

    hwDecalBumpShader_NV20::~hwDecalBumpShader_NV20()
    {
        detachSceneCallbacks();
    }

    void hwDecalBumpShader_NV20::releaseEverything()
    {
        release_lookup_texture();

        if (vertex_program_id > 0)
        {
            glFinish();

            // Unbind any program.
            glBindProgramNV(GL_VERTEX_PROGRAM_NV, 0);

            // Delete the program used by this shader.
            glDeleteProgramsNV(1, &vertex_program_id);

            // For sanity.
            vertex_program_id = 0;
        }

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

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

    void hwDecalBumpShader_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( "hwDecalBumpShader_NV20", hwDecalBumpShader_NV20::id,
                                      hwDecalBumpShader_NV20::creator, hwDecalBumpShader_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( hwDecalBumpShader_NV20::id );
        if (!status) {
            status.perror("deregisterNode");
            return status;
        }

        return MS::kSuccess;
    }

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

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

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

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

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

        bump = nAttr.create( "bump", "c2", bumpR, bumpG, bumpB);
        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);

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

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

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

        light = nAttr.create( "light", "lgt", lightX, lightY, lightZ);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setDefault(0.0f, 1.0f, 1.0f);

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

     // create output attributes here
        // outColor is the only output attribute and it is inherited
        // so we do not need to create or add it.
        //

     // Add the attributes here

        addAttribute(color);

        addAttribute(bump);

        addAttribute(uvCoord);

        addAttribute(uBias);
        addAttribute(vBias);

        addAttribute(uvFilterSize);

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

        attributeAffects (colorR, outColor);
        attributeAffects (colorG, outColor);
        attributeAffects (colorB, outColor);
        attributeAffects (color,  outColor);
        attributeAffects (bumpR, outColor);
        attributeAffects (bumpG, outColor);
        attributeAffects (bumpB, outColor);
        attributeAffects (bump,  outColor);
        attributeAffects (uCoord,  outColor);
        attributeAffects (vCoord,  outColor);
        attributeAffects (uvCoord, outColor);
        attributeAffects (uBias,   outColor);
        attributeAffects (vBias,   outColor);

        attributeAffects (lightX,   outColor);
        attributeAffects (lightY,   outColor);
        attributeAffects (lightZ,   outColor);
        attributeAffects (light,    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 shader.
    // See "Writing a shading node plug-in" in the documentation
    // for more information.
    //
    //
    MStatus hwDecalBumpShader_NV20::compute(
    const MPlug&      plug,
          MDataBlock& block )
    {
        bool k;
        k = (plug == outColor)  ||
            (plug == outColorR) ||
            (plug == outColorG) ||
            (plug == outColorB);
        if( !k )
            return MS::kUnknownParameter;

        // set output color attribute
        MDataHandle outColorHandle = block.outputValue( outColor );
        MFloatVector& outColor = outColorHandle.asFloatVector();
        outColor.x = 1.0;
        outColor.y = 0.5;
        outColor.z = 0.5;
        outColorHandle.setClean();
        return MS::kSuccess;
    }

    // To get 3 float values from the node attribute
    //
    MStatus hwDecalBumpShader_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("hwDecalBumpShader_NV20::getFloat3 plug.getValue.");
            return status;
        }

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

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

        return status;
    }

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

        return status;
    }

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

        // Get the diffuse color
        //
        float diffuse_color[4];
        status = getFloat3(color, diffuse_color);
        diffuse_color[3] = 1.0;
        if (!status)
            return status;

        // Get the light color
        //
        float light_color[4];
        light_color[3] = 1.0f;
        status = getFloat3(lightColor, light_color);
        if (!status)
            return status;

        // Get the light direction (for directionalLight)
        //
        status = getFloat3(light, &lightRotation[0]);
        if (!status)
            return status;

        // Get the bumpScale value
        //
        float   bumpScaleValue = 2.0f;

        // Get the bumpMap type
        //
        bool    isHeightFieldMap = true;

        // Direction of the directional light
        //
        // Convert the light direction (which is assumed in originally be in world space, in euler coordinates)
        // into an eye space vector.
        //
        double  scale = M_PI/180.0;     // Internal rotations are in radian and not in degrees
        MEulerRotation  lightRot( lightRotation[0] * scale, lightRotation[1] * scale, lightRotation[2] * scale );
        MVector light_v = MVector(0, 0, -1).rotateBy( lightRot );   // WS light vector

        MDagPath camDag;
        view.getCamera(camDag);

        light_v = light_v * camDag.inclusiveMatrixInverse();
        lightRotation[0] = (float) light_v[0];
        lightRotation[1] = (float) light_v[1];
        lightRotation[2] = (float) light_v[2];

        // Get the camera position
        //
        status = getFloat3(camera, &cameraPos[0]);
        if (!status)
            return status;

        // Get the decal and bump map file names
        //
        MString decalName = "";
        MString bumpName  = "";

        ShadingConnection colorConnection(thisMObject(), request.multiPath().partialPathName(), "color");
        ShadingConnection bumpConnection (thisMObject(), request.multiPath().partialPathName(), "bump");

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

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

        // Fail safe quit
        //
        if (bumpName.length() == 0 ||
            decalName.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);
            glColor4fv(diffuse_color);
            view.endGL();
            return MS::kSuccess;
        }

        view.beginGL();

        glPushAttrib( GL_ALL_ATTRIB_BITS );
        glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

        /* Starts Here... */
        glEnable(GL_TEXTURE_SHADER_NV);

        // stage 0 -- decal map
        glActiveTextureARB( GL_TEXTURE0_ARB );
        if(m_pTextureCache)
            m_pTextureCache->bind(colorConnection.texture(), MTexture::RGBA, false);
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);

        // stage 1 -- bumpped normal map
        glActiveTextureARB( GL_TEXTURE1_ARB );
        // We need to be able to pass the bumpScaleValue
        // to the texture cache and rebuild the bump or normal map
        if( isHeightFieldMap ) {
            // convert the HeightField to the NormalMap
            if(m_pTextureCache)
                m_pTextureCache->bind(bumpConnection.texture(), MTexture::NMAP, false);
        }
        else {
            if(m_pTextureCache)
                m_pTextureCache->bind(bumpConnection.texture(), MTexture::RGBA, false);
        }
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_TEXTURE_2D);

        // stage 2 -- dot product (diffuse component)
        glActiveTextureARB( GL_TEXTURE2_ARB );
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_NV);
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE1_ARB);

        // stage 3 -- dot product (specular component)
        glActiveTextureARB( GL_TEXTURE3_ARB );
        bind_lookup_table();    // 2D texture to get the diffuse and specular illumination
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_SHADER_OPERATION_NV, GL_DOT_PRODUCT_TEXTURE_2D_NV);
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_RGBA_UNSIGNED_DOT_PRODUCT_MAPPING_NV, GL_EXPAND_NORMAL_NV);
        glTexEnvi(GL_TEXTURE_SHADER_NV, GL_PREVIOUS_TEXTURE_INPUT_NV, GL_TEXTURE1_ARB);

        // With light color and intensity
        //
        glCombinerParameterfvNV(GL_CONSTANT_COLOR0_NV, diffuse_color);
        glCombinerParameterfvNV(GL_CONSTANT_COLOR1_NV, light_color);

        // The register combiner will do the multiplication between
        // the illumination and the decal color
        //
        glEnable(GL_REGISTER_COMBINERS_NV);

    #ifndef DEBUGGING_VERTEX_PROGRAM
        glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 2);
    #else
        // For testing, only use one general register combiner.
        glCombinerParameteriNV(GL_NUM_GENERAL_COMBINERS_NV, 1);
    #endif

        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 surface decal color
        //
        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_TEXTURE3_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_TEXTURE3_ARB, GL_UNSIGNED_IDENTITY_NV, GL_ALPHA);

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

        // Combiner stage 1, modulate the surface color by the light color
        //
        glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);
        glCombinerInputNV(GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_CONSTANT_COLOR1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB);

        glCombinerOutputNV(GL_COMBINER1_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE1_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
        //
        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 hwDecalBumpShader_NV20::unbind(const MDrawRequest& request,
                   M3dView& view)
    {
        view.beginGL();

        glDisable(GL_REGISTER_COMBINERS_NV);
        glDisable(GL_TEXTURE_SHADER_NV);

        glActiveTextureARB( GL_TEXTURE0_ARB );

        glPopClientAttrib();
        glPopAttrib();

        view.endGL();

        return MS::kSuccess;
    }

    // Compute array of binormals. Return the pointer to the array if succesful,
    // NULL otherwise.
    float* hwDecalBumpShader_NV20::computeBinormals(int indexCount,
                                               const unsigned int * indexArray,
                                               int vertexCount,
                                               const float* vertexArray,
                                               const float* normalArray,
                                               const float* texCoordArray)
    {
        // Allocate the array of binormals.
        float *biNormalArray = new float[3*vertexCount];

        // Allocate space for a triangle of vertices, textures, normals, binormals.
        // This triangle will be used to compute the binormal vector.
        //
        const float *   v[3];   // Vertex       (x,y,z)
        const float *   t[3];   // Texture      (s,t)
        const float *   n[3];   // Normal       (x,y,z)
        float *         b[3];   // BiNormal     (x,y,z)

        for (int i = 0; i < indexCount; i += 3)
        {
            int i0 = indexArray[i+0];
            int i1 = indexArray[i+1];
            int i2 = indexArray[i+2];

            // Get the xyz coords of the corners of the triangle.
            //
            v[0] = vertexArray + 3 * i0;
            v[1] = vertexArray + 3 * i1;
            v[2] = vertexArray + 3 * i2;

            // Get the st coords of the corners of the triangle.
            //
            t[0] = texCoordArray + 2 * i0;
            t[1] = texCoordArray + 2 * i1;
            t[2] = texCoordArray + 2 * i2;

            // Get the normals at the corners of the triangle.
            //
            n[0] = normalArray + 3 * i0;
            n[1] = normalArray + 3 * i1;
            n[2] = normalArray + 3 * i2;

            // Get pointers to the binormal vectors.
            //
            b[0] = biNormalArray + 3 * i0;
            b[1] = biNormalArray + 3 * i1;
            b[2] = biNormalArray + 3 * i2;

            // *********************************************
            // ******** compute the binormal vector ********
            // *********************************************
            vec3f   plane[3];
            vec3f   c0, c1, c2;
            vec3f   s0, s1;

            // Calculate plane equations for the planes defined
            // by the (x, s, t), (y, s, t), and (z, s, t) coords.
            int idx;
            for (idx = 0; idx < 3; ++idx)
            {
                // Set up the three corners
                c0.set_value(v[0][idx], t[0][0], t[0][1]);
                c1.set_value(v[1][idx], t[1][0], t[1][1]);
                c2.set_value(v[2][idx], t[2][0], t[2][1]);

                // Calculate two sides
                s0 = c0 - c2;
                s1 = c1 - c2;

                // Calculate the normal of the plane
                plane[idx] = s1.cross(s0);
            }

            // Now solve for the texture gradients dsdx, dsty, dsdt, ...
            vec3f ds, dt, dn;

            for (idx = 0; idx < 3; ++idx)
            {
                ds[idx] = -plane[idx][1]/plane[idx][0];
                dt[idx] = -plane[idx][2]/plane[idx][0];
            }

            dt.normalize();
            dn = ds.cross(dt);

            // Make sure that our computed normal vector points in the
            // same direction as the input normal vector.
            vec3f normal(n[0]);
            if (normal.dot(dn) < 0)
            {
                // They pointed in different directions, negate
                ds.negate();
                dt.negate();
                dn.negate();
            }

            // Compute the biNormal vector and store them in the biNormal array
            for (idx = 0; idx < 3; ++idx)
            {
                vec3f vn = n[idx];
                vec3f vb = vn.cross( dt );
                vb.normalize();

                for( int j = 0; j < 3; ++j )
                    b[idx][j] = vb[j];
            }
        }

        return biNormalArray;
    }

    /* virtual */
    MStatus hwDecalBumpShader_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();

        // Bind the vertex program. Note that this will automatically
        // create and compile the vertex program, the first time
        // this function gets called.
        //
        loadVertexProgramGL();

        // Bind and enable the vertex program
        //
        glBindProgramNV(GL_VERTEX_PROGRAM_NV, vertex_program_id);
        glEnable(GL_VERTEX_PROGRAM_NV);

        // VERTEX REGISTERS (Attributes):
        // 0 - coord
        // 1 - normal
        // 2 - texcoord0 (decal 2D texture)
        // 3 - texcoord1 (bump 2D texture)
        // 4 - texcoord2 (binormal vector (in object space))
        glVertexAttribPointerNV( 0, 3, GL_FLOAT, 0, vertexArray );
        glVertexAttribPointerNV( 1, 3, GL_FLOAT, 0, normalArrays[0] );
        glVertexAttribPointerNV( 2, 2, GL_FLOAT, 0, texCoordArrays[0] );
        glVertexAttribPointerNV( 3, 2, GL_FLOAT, 0, texCoordArrays[0] );
        glVertexAttribPointerNV( 4, 3, GL_FLOAT, 0, normalArrays[2] );

        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY1_NV );
        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY3_NV );
        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY4_NV );

        glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indexArray);

        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY1_NV );
        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );
        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY3_NV );
        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY4_NV );

        glDisable(GL_VERTEX_PROGRAM_NV);

        glClientActiveTextureARB(GL_TEXTURE0_ARB);

        view.endGL();

        return MS::kSuccess;
    }

    /* virtual */
    int     hwDecalBumpShader_NV20::normalsPerVertex()
    {
        return 3;
    }

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

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

        if (lookup_texture)
        {
            delete lookup_texture;
            lookup_table = NULL;
        }
    }