pnTrianglesNode.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.
//
// File: pnTrianglesNode.cpp
//
// Dependency Graph Node: pnTriangles
//
// Description:
//
//      Hardware shader plugin to perform :
//      1) ATI PN triangle tessellation on geometry, if the extension
//          ATI_pn_triangles is supported in hardware.
//      2) A simple vertex program using EXT_vertex_program, if the
//          extension is supported.
//      3) A simple fragement program using AT_fragment_program, if the
//          extension is supported.
//
//      This is an excerpt from the PN triangle extension
//      specification:
//
//      "When PN Triangle generation is enabled, each triangle-based geometric 
//      primitive is replaced with a new curved surface using the primitive 
//      vertices as control points.  The intent of PN Triangles 
//      are to take a set of triangle-based geometry and algorithmically 
//      tessellate it into a more organic shape with a smoother silhouette. 
//      The new surface can either linearly or quadratically interpolate the 
//      normals across the patch.  The vertices can be either linearly or
//      cubically interpolated across the patch.  Linear interpolation
//      of the points would be useful for getting more sample points for 
//      lighting on the same geometric shape.  All other vertex information
//      (colors, texture coordinates, fog coordinates, and vertex weights) are 
//      interpolated linearly across the patch."
//
//      This plugin is ATI Radeon specific.
//
#include <maya/MIOStream.h>
#include <math.h>

#include "pnTrianglesNode.h"

#include <maya/MFnPlugin.h>
#include <maya/MString.h>

#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MGlobal.h>
#include <maya/M3dView.h>
#include <maya/MMaterial.h>
#include <maya/MDrawData.h>
#include <maya/MColor.h>
#include <maya/MDagPath.h>
#include <maya/MPoint.h>
#include <maya/MIOStream.h>

MTypeId     pnTriangles::id( 0x00105448 );

#define     pnDefaultMaxTessellation    0       // Default max tessellation

// Attributes
// 
MObject     pnTriangles::attrSubdivisions;  // Subdivision level      
MObject     pnTriangles::attrColored;       // Display colored
MObject     pnTriangles::attrTextured;      // Display textured
MObject     pnTriangles::attrWireframe;     // Display wireframe
MObject     pnTriangles::attrNormalMode;    // Normal evaluation mode
MObject     pnTriangles::attrPointMode;     // Point evaluation mode
MObject     pnTriangles::attrEnableVertexProgram; // Enable vertex program
MObject     pnTriangles::attrEnablePixelProgram; // Enable pixel program

pnTriangles::pnTriangles() 
:   fSubdivisions( 0 ),                                 // Default 0 level of subdivision
    fNumTextureCoords( 0 ),                             // Default not-textured
    fNumNormals( 1 ),                                   // Default shaded
    fNumColors( 0 ),                                    // Default not-colored
    fPointMode( kPointCubic ),                          // Default cubic
    fNormalMode( kNormalQuadratic ),                    // Default quadratic
    fMaxTessellationLevel( pnDefaultMaxTessellation ),  // 0 maximum default
    fWireframe( false ),                                // Default filled
    fInTexturedMode( false ),                           // Keep track of textured mode
    fTestVertexProgram( false ),
    fTestFragmentProgram( false ),
    fVertexShaderId( -1 ),
    fFragmentShaderId( -1 )
{
    fExtensionSupported[kPNTriangesEXT ] = false;
    fExtensionSupported[kVertexShaderEXT] = false;
    fExtensionSupported[kFragmentShaderEXT] = false;


    // Initialize required extension(s)
    //

    // Initialize PN-triangles
    fExtensionSupported[kPNTriangesEXT] = initialize_ATI_Extension("GL_ATI_pn_triangles");
    // Get the maximum tessellation level supported by the extension
    //
    if (fExtensionSupported[kPNTriangesEXT])
    {
        int val;
        glGetIntegerv( GL_MAX_PN_TRIANGLES_TESSELATION_LEVEL_ATI, &val );
        fMaxTessellationLevel = val;
    }

    fExtensionSupported[kVertexShaderEXT] = initialize_ATI_Extension("GL_EXT_vertex_shader");
    fExtensionSupported[kFragmentShaderEXT] = initialize_ATI_Extension("GL_ATI_fragment_shader");
}

pnTriangles::~pnTriangles() 
//
// Description:
//      Destructor 
{
    // Delete any vertex shader
    //
    if (fExtensionSupported[kVertexShaderEXT])
    {
        if (fVertexShaderId != -1)
            glDeleteVertexShaderEXT(fVertexShaderId);
    }
}

bool pnTriangles::getInternalValue( const MPlug &plug, MDataHandle &dh ) 
//
// Description:
//      Get internally cached values when attribute is queried
//
{
    if( plug == pnTriangles::attrSubdivisions ) 
    {
        dh.set( int(fSubdivisions) );
        return true;
    }
    else if ( plug == pnTriangles::attrColored)
    {
        dh.set( (fNumColors > 0) );
        return true;
    }
    else if ( plug == pnTriangles::attrWireframe)
    {
        dh.set( fWireframe > 0);
        return true;
    }
    else if ( plug == pnTriangles::attrTextured)
    {
        dh.set( (fNumTextureCoords > 0) );
        return true;
    }
    else if ( plug == pnTriangles::attrNormalMode )
    {
        if (fNormalMode == kNormalLinear)
            dh.set( 0 );
        else
            dh.set( 1 );
        return true;
    }
    else if ( plug == pnTriangles::attrPointMode )
    {
        if (fPointMode == kPointLinear)
            dh.set( 0 );
        else
            dh.set( 1 );
        return true;
    }
    else if ( plug == pnTriangles::attrEnableVertexProgram)
    {
        dh.set( fTestVertexProgram );
        return true;
    }
    else if ( plug == pnTriangles::attrEnablePixelProgram)
    {
        dh.set( fTestFragmentProgram );
        return true;
    }
    return false;
}

bool pnTriangles::setInternalValue( const MPlug &plug, const MDataHandle &dh ) 
//
// Description:
//      Set internally cached values when attribute changes
//
{
   if( plug == pnTriangles::attrSubdivisions ) 
    {
        fSubdivisions = dh.asInt();

        // Make sure we don't go above the maximum allowed by 
        // the extension
        if (fSubdivisions > fMaxTessellationLevel)
            fSubdivisions = fMaxTessellationLevel;
        return true;
    }
    else if ( plug == pnTriangles::attrColored)
    {
        if (dh.asBool() == true)
            fNumColors = 1;
        else
            fNumColors = 0;
        return true;
    }
    else if ( plug == pnTriangles::attrWireframe)
    {
        if (dh.asBool() == true)
            fWireframe = 1;
        else
            fWireframe = 0;
        return true;
    }
    else if ( plug == pnTriangles::attrTextured)
    {
        if (dh.asBool() == true)
            fNumTextureCoords = 1;
        else
            fNumTextureCoords = 0;
        return true;
    }
    else if ( plug == pnTriangles::attrNormalMode )
    {
        if (dh.asInt() == 0)
            fNormalMode = kNormalLinear;
        else
            fNormalMode = kNormalQuadratic;
        return true;
    }
    else if ( plug == pnTriangles::attrPointMode )
    {
        if (dh.asInt() == 0)
            fPointMode = kPointLinear;
        else
            fPointMode = kPointCubic;
        return true;
    }
    else if ( plug == pnTriangles::attrEnableVertexProgram )
    {
        if (dh.asBool() == true)
        {
            if (fExtensionSupported[kVertexShaderEXT])
                fTestVertexProgram = 1;
            else
                fTestVertexProgram = 0;
        }
        else
            fTestVertexProgram = 0;

        return true;
    }
    else if ( plug == pnTriangles::attrEnablePixelProgram )
    {
#ifdef _FRAGMENT_PROGRAM_READY_
        if (dh.asBool() == true)
            if (fExtensionSupported[kFragmentShaderEXT])
                fTestFragmentProgram = 1;
            else
                fTestFragmentProgram = 0;
        else
            fTestFragmentProgram = 0;
#else
        fTestFragmentProgram = 0;
#endif
        return true;
    }
    return false;
}

void* pnTriangles::creator()
//
//  Description:
//      Create new pnTriangles class
//
//  Return Value:
//      a new object of this type
//
{
    return new pnTriangles();
}

MStatus pnTriangles::initialize()
//
//  Description:
//      Initialize class
//  Return Values:
//      MS::kSuccess
//      MS::kFailure
//      
{
    MFnNumericAttribute nAttr;
    MStatus             stat;

    // Subivision attribute
    attrSubdivisions = nAttr.create( "subdivisions", "sd", MFnNumericData::kInt, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setMin(0);
    nAttr.setMax(10);
    nAttr.setDefault(1);
    nAttr.setKeyable(true);

    attrColored = nAttr.create( "colored", "cl", MFnNumericData::kBoolean, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setDefault( false );
    nAttr.setKeyable(true);

    attrTextured = nAttr.create( "textured", "tx", MFnNumericData::kBoolean, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setDefault( false );
    nAttr.setKeyable(true);

    attrWireframe = nAttr.create( "wireframe", "wf", MFnNumericData::kBoolean, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setDefault( false );
    nAttr.setKeyable(true);
    
    // Normal mode attribute
    attrNormalMode = nAttr.create( "quadraticNormals", "qn", MFnNumericData::kInt, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setMin(0); // Linear
    nAttr.setMax(1); // Quadratic
    nAttr.setDefault(1);
    nAttr.setKeyable(true);

    // Point mode attribute
    attrPointMode= nAttr.create( "cubicPoints", "cp", MFnNumericData::kInt, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setMin(0); // Linear
    nAttr.setMax(1); // Cubic
    nAttr.setDefault(1);
    nAttr.setKeyable(true);

    // Enable vertex program
    attrEnableVertexProgram = nAttr.create( "vertexProgram", "vp", MFnNumericData::kBoolean, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setDefault( false );
    nAttr.setKeyable(true);
        
    // Enable pixel program
    attrEnablePixelProgram = nAttr.create( "pixelProgram", "pp", MFnNumericData::kBoolean, 1 );
    nAttr.setStorable(true);
    nAttr.setInternal(true);
    nAttr.setDefault( false );
    nAttr.setKeyable(true);

    // Add the attributes we have created to the node
    //
    stat = addAttribute( attrSubdivisions );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrColored );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrWireframe );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrTextured );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrNormalMode );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrPointMode );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrEnableVertexProgram );
    if (!stat) { stat.perror("addAttribute"); return stat;}
    stat = addAttribute( attrEnablePixelProgram );
    if (!stat) { stat.perror("addAttribute"); return stat;}

    return MS::kSuccess;
}

MStatus pnTriangles::getFloat3(MObject attr, float value[3])
{
    MStatus status;
    // Get the attr to use
    //
    MPlug   plug(thisMObject(), attr);

    MObject object;

    status = plug.getValue(object);
    if (!status)
    {
        status.perror("pnTrianglesNode::bind plug.getValue.");
        return status;
    }


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

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

    return MS::kSuccess;
}

#define DEG_TO_RAD(_x_) (3.14159265358979323846264338327950F / 180.0F * (_x_))


void pnTriangles::bindVertexProgram(const MColor diffuse, 
                                    const MColor specular, 
                                    const MColor emission, 
                                    const MColor ambient)
//
// Description:
//      Sample vertex program which sets up a yellow diffuse
//      shader.
//                                  
{
    // See if extension is supported
    //
    if (!fExtensionSupported[kVertexShaderEXT])
        return;

    // Enable vertex shaders
    glEnable(GL_VERTEX_SHADER_EXT);

    // Generate a new vertex shader id, if none has been created.
    if (fVertexShaderId != -1)
    {
        glBindVertexShaderEXT (fVertexShaderId); 
        return;
    }

    fVertexShaderId = glGenVertexShadersEXT (1);

    //Initialize global parameter bindings
    GLuint Modelview = glBindParameterEXT (GL_MODELVIEW_MATRIX);
    GLuint Projection = glBindParameterEXT (GL_PROJECTION_MATRIX);
    // Q: does this read properly from vertex arrays or
    // do I need to define a variant pointer via glVariantPointerEXT ?
    //
    GLuint Vertex = glBindParameterEXT (GL_CURRENT_VERTEX_EXT);
    // GL_CURRENT_NORMAL comes from the glGetPointerTarget enums
    GLuint Normal = glBindParameterEXT (GL_CURRENT_NORMAL); 
    
    //GLuint lightPos = glBindLightParameterEXT( 0, GL_POSITION );

    // Bind a diffuse shader 
    glBindVertexShaderEXT (fVertexShaderId); 
    glBeginVertexShaderEXT ();
    {
        float direction[4] = { 0.57735f, 0.57735f, 0.57735f, 0.0f}; //direction vector (1,1,1) normalized
        GLuint lightDirection;
        GLuint diffMaterial;
        GLuint sceneAmbient;
        GLuint eyeVertex;
        GLuint clipVertex;
        GLuint eyeNormal;
        GLuint intensity;

        // generate local values
        eyeVertex = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
        clipVertex = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
        eyeNormal = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);
        intensity = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_EXT, GL_FULL_RANGE_EXT, 1);

        // generate constant values
        lightDirection = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
        diffMaterial = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
        sceneAmbient = glGenSymbolsEXT (GL_VECTOR_EXT, GL_LOCAL_CONSTANT_EXT, GL_FULL_RANGE_EXT, 1);
        glSetLocalConstantEXT (lightDirection, GL_FLOAT, direction);
        glSetLocalConstantEXT (diffMaterial, GL_FLOAT, (void *)&(diffuse.r));
        glSetLocalConstantEXT (sceneAmbient, GL_FLOAT, (void *)&(ambient.r));
        glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, eyeVertex, Modelview, Vertex);
        glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, GL_OUTPUT_VERTEX_EXT, Projection, eyeVertex);

        // assumes no scaling/shearing in modelview matrix
        glShaderOp2EXT (GL_OP_MULTIPLY_MATRIX_EXT, eyeNormal, Modelview, Normal);
        glShaderOp2EXT (GL_OP_DOT3_EXT, intensity, lightDirection, eyeNormal);
        glShaderOp2EXT (GL_OP_ADD_EXT, intensity, sceneAmbient, intensity);
        glShaderOp2EXT (GL_OP_MUL_EXT, GL_OUTPUT_COLOR0_EXT, diffMaterial, intensity);
    }
    glEndVertexShaderEXT ();

}

void pnTriangles::bindFragmentProgram()
//
// Description:
//
//  THIS IS JUST SAMPLE CODE, MORE NEEDS TO BE DONE TO ACTUALLY USE
//  INCORPORATE A FRAGMENT SHADER.
//
{
    // See if extension is supported
    //
    if (!fExtensionSupported[kFragmentShaderEXT])
        return;

    // Enable vertex shaders
    glEnable(GL_FRAGMENT_SHADER_ATI);

    // Generate a new fragment shader id, if none has been created.
    if (fFragmentShaderId != -1)
    {
        glBindFragmentShaderATI (fFragmentShaderId);
        return;
    }

    fFragmentShaderId = glGenFragmentShadersATI(1);

    glBindFragmentShaderATI (fFragmentShaderId);
    glBeginFragmentShaderATI();

    // Place sample fragment shader code here ....

    glEndFragmentShaderATI();
}

/* virtual */
MStatus pnTriangles::bind(const MDrawRequest& request, M3dView& view)
//
// Description:
//      This bind demonstrates the usage of internal material
//      and texture properties. This shader must be connected
//      to the "hardwareShader" attribute of a lambert derived
//      shader.
//
{
    // Setup the view
    view.beginGL();
    glPushAttrib( GL_ALL_ATTRIB_BITS );
    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    MColor diffuse(1.0F, 1.0F, 0.0F, 1.0F);
    MColor specular(1.0F, 1.0F, 1.0F, 1.0F);
    MColor emission(0.0F, 0.0F, 0.0F, 1.0F);
    MColor ambient(0.2F, 0.2F, 0.2F, 1.0F);

    // Get the diffuse and specular colors
    //
    float shininess;
    bool hasTransparency = false;

    MMaterial material = request.material();
    fInTexturedMode = material.materialIsTextured();

    // Setting this to true will get the default "green" material back
    // since it will try and evaluate this shader, which internally
    // Maya does not understand -> thus giving the "green" material back
    bool useInternalMaterialSetting = false;
    
    if (!useInternalMaterialSetting)
    {
        material.getEmission( emission );
        material.getSpecular( specular );
        shininess = 13.0;
    }
    material.getHasTransparency( hasTransparency ); 

    if (!fInTexturedMode)
    {
        if (!fTestVertexProgram && !useInternalMaterialSetting)
            material.getDiffuse( diffuse );
    }
    // In textured mode. Diffuse material is always white
    // for texture blends
    else
    {
        if (!useInternalMaterialSetting)
            diffuse.r = diffuse.g = diffuse.b = diffuse.a = 1.0;
    }

    // Use a vertex program to set up shading
    //
    if (fTestVertexProgram)
    {
        bindVertexProgram(diffuse, specular, emission, ambient);
    }
    else if (fTestFragmentProgram)
    {
        bindFragmentProgram();
    }

    // Don't use a vertex program to set up shading
    //
    else
    {
        // Set up the material state
        //
        glEnable(GL_COLOR_MATERIAL);
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT);
        glColor4fv(&ambient.r);

        if (fInTexturedMode)
        {
            glEnable( GL_TEXTURE_2D );
            MDrawData drawData = request.drawData();
            material.applyTexture( view, drawData );

            float scaleS, scaleT, translateS, translateT, rotate;

            material.getTextureTransformation(scaleS, scaleT, translateS,
                                              translateT, rotate);

            rotate = DEG_TO_RAD(-rotate);
            float c = cosf(rotate);
            float s = sinf(rotate);
            translateS += ((c+s)/2.0F);
            translateT += ((c-s)/2.0F);

            glMatrixMode(GL_TEXTURE);
            glPushMatrix();
            glLoadIdentity();

            if(scaleS != 1.0f || scaleT != 1.0f)
                glScalef(1.0f/scaleS, 1.0f/scaleT, 1.0f);
            if(translateS != 0.0f || translateT != 0.0f)
                glTranslatef(0.5f-translateS, 0.5f-translateT, 0.0f);
            else
                glTranslatef(0.5f, 0.5f, 0.0f);

            if(rotate != 0.0f)
                glRotatef(-rotate, 0.0f, 0.0f, 1.0f);

            glMatrixMode(GL_MODELVIEW);
        }

        if (!useInternalMaterialSetting)
        {
            glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
            glColor4fv(&diffuse.r);
            glColorMaterial(GL_FRONT_AND_BACK, GL_SPECULAR);
            glColor4fv(&specular.r);
            glColorMaterial(GL_FRONT_AND_BACK, GL_EMISSION);
            glColor4fv(&emission.r);
            glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, shininess);
        }
        else
        {
            const MDagPath dagPath =  request.multiPath();
            material.evaluateMaterial( view, dagPath );
            material.setMaterial(dagPath, hasTransparency);
        }
    }

    // Do PN triangles in hardware, or do nothing
    // if LOD = 0
    if (fExtensionSupported[kPNTriangesEXT] || (fSubdivisions == 0))
    {
        if (fSubdivisions != 0)
        {
            glEnable( GL_PN_TRIANGLES_ATI );

            // Set point mode
            //
            if (fPointMode == kPointLinear)
                glPNTrianglesiATI( GL_PN_TRIANGLES_POINT_MODE_ATI, 
                                   GL_PN_TRIANGLES_POINT_MODE_LINEAR_ATI);
            else
                glPNTrianglesiATI( GL_PN_TRIANGLES_POINT_MODE_ATI, 
                                   GL_PN_TRIANGLES_POINT_MODE_CUBIC_ATI);

            // Set normal mode
            //
            if (fNormalMode == kNormalLinear)
                glPNTrianglesiATI( GL_PN_TRIANGLES_NORMAL_MODE_ATI, 
                                   GL_PN_TRIANGLES_NORMAL_MODE_LINEAR_ATI);
            else
                glPNTrianglesiATI( GL_PN_TRIANGLES_NORMAL_MODE_ATI, 
                                   GL_PN_TRIANGLES_NORMAL_MODE_QUADRATIC_ATI);

            // Set tessellation level
            //
            glPNTrianglesiATI( GL_PN_TRIANGLES_TESSELATION_LEVEL_ATI,
                              fSubdivisions );
        }
    }

    view.endGL();

    return MS::kSuccess;
}

/* virtual */
MStatus pnTriangles::unbind(const MDrawRequest& request,
                            M3dView& view)
//
// Description:
//      
//
{
    view.beginGL();

    // Disable vertex shader
    if (fTestVertexProgram)
    {
        glDisable(GL_VERTEX_SHADER_EXT);
    }
    // Disable fragment shader
    else if (fTestFragmentProgram)
    {
        glDisable(GL_FRAGMENT_SHADER_ATI);
    }
    // Disable regular material state
    else
    {
        glDisable( GL_TEXTURE_2D );
        glDisable(GL_COLOR_MATERIAL);

        if (fInTexturedMode)
        {
            glMatrixMode(GL_TEXTURE);
            glPopMatrix();
            glMatrixMode(GL_MODELVIEW);
        }
    }

    // Disable PN triangles extension if needed
    //
    if (fExtensionSupported[kPNTriangesEXT] || (fSubdivisions == 0))
    {
        if (fSubdivisions != 0)
            glDisable( GL_PN_TRIANGLES_ATI );
    }
    
    glPopClientAttrib();
    glPopAttrib();

    view.endGL();

    return MS::kSuccess;
}

unsigned int pnTriangles:: computePNTriangles(const float * vertexArray,
                                      const float * normalArray,
                                      const float * texCoordArray,
                                      float * pnVertexArray,
                                      float * pnNormalArray,
                                      float * pnTexCoordArray,
                                      float * pnColorArray)
{
    unsigned int triangleCount = 0;
    // Software implementation left to the user...
    return triangleCount;
}

/* virtual */
MStatus pnTriangles::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;    // Should call parent class here !

    view.beginGL();

    if (fWireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

    bool needColors = (fNumColors && colorCount);
    bool needTexCoords = (fNumTextureCoords && texCoordCount);
    bool needNormals = (fNumNormals && normalCount);

    if (!fExtensionSupported[kPNTriangesEXT])
    {
        // Write code here to support PN-triangles in software...
    }

    glVertexPointer(3, GL_FLOAT, 0, vertexArray);
    glEnableClientState(GL_VERTEX_ARRAY);

    if (needNormals)
    {
        glNormalPointer(GL_FLOAT, 0, normalArrays[0]);
        glEnableClientState(GL_NORMAL_ARRAY);
    }
    if (needTexCoords)
    {
        glTexCoordPointer(2, GL_FLOAT, 0, texCoordArrays[0]);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    if (needColors)
    {
        // Override material color
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
        glColorPointer(4, GL_FLOAT, 0, colorArrays[0]);
        glEnableClientState(GL_COLOR_ARRAY);
    }

    glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray);

    if (fWireframe)
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

    view.endGL();


    return MS::kSuccess;
}

/* virtual */
int pnTriangles::normalsPerVertex()
{
    return fNumNormals;
}

/* virtual */
int pnTriangles::texCoordsPerVertex()
{
    if (fInTexturedMode)
        return fNumTextureCoords;
    else
        return 0;
}

/* virtual */
int pnTriangles::colorsPerVertex()
{
    return fNumColors;
}

// Main plugin interface
MStatus initializePlugin( MObject obj )
//
//  Description:
//      Initialize plugin
//
//  Arguments:
//      obj - a handle to the plug-in object (use MFnPlugin to access it)
//
{ 
    MStatus   status;

    const MString UserClassify( "shader/surface/utility" );
    
    MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");

    status = plugin.registerNode( "pnTriangles", pnTriangles::id, pnTriangles::creator,
                                  pnTriangles::initialize, MPxNode::kHwShaderNode, &UserClassify  );
    if (!status) {
        status.perror("registerNode");
        return status;
    }

    return status;
}

MStatus uninitializePlugin( MObject obj)
//
//  Description:
//      Deregister node
//
//  Arguments:
//      obj - a handle to the plug-in object (use MFnPlugin to access it)
//
{
    MStatus   status;
    MFnPlugin plugin( obj );

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

    return status;
}