hwUnlitShader.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.
//
// ==========================================================================
//+

// DESCRIPTION: This is a simple shader, that only
//              supports decal texturing using standard OpenGL commands.
//
// How to use this shader:
//
//  1. Compile it and put it in your plug-in path.
//  2. Window->Setting/Preferences->Plug-in Manager.
//     Make sure that the hwUnlitShader.mll plug-in is there
//     and click the "loaded" checkbox next to it to load it.
//  3. Create a new scene. 
//  4. Create a poly object (say, a polygon cube).
//  5. Create a new hwUnlitShader. You can use the hypershade, or
//     right-click on the cube and select Material->Create new Material->hwUnlitShader.
//  6. Open the attribute editor and select the hwUnlitShader you just created.
//  7. The color attribute can be mapped on a file texture, just like any
//     standard maya shader. Try it, press '6' to go in textured mode and see the texture.
//  8. The transparency attribute can be set to a value other than 0.0 to
//     display transparent color. If the color attribute is textured and
//     transparency is set, the average of the transparency is modulated
//     by the texture.
//
// COMPATIBILITY NOTE: Before Maya 4.5, a problem prevented Maya from
// properly using MPxHwShaderNode::hasTransparency(). The work-around is
// to create transparency attributes that mimics standard Lambert shaders,
// and to set the transparency value to a color different than black.
// This forces Maya to properly order geometry from farthest to closest.
// This shader illustrates both the workaround (the transparency attributes
// illustrated in initialize() below) and the recommended way (overloading 
// hasTransparency() to return true). If you do not need to support
// earlier versions of Maya, or do not need transparency, then there is
// no need to create the transparency attributes.
//
//

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


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

#include <maya/MString.h>
#include <maya/MPlug.h>
#include <maya/MDataBlock.h>
#include <maya/MDataHandle.h>
#include <maya/MArrayDataHandle.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnLightDataAttribute.h>
#include <maya/MFloatVector.h>
#include <maya/MFnPlugin.h>
#include <maya/MFnStringData.h>
#include <maya/MGlobal.h>

#include <maya/MPoint.h>
#include <maya/MMatrix.h>
#include <maya/MVector.h>
#include <maya/MEulerRotation.h>
#include <maya/MDagPath.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MSceneMessage.h>

#if defined(OSMac_MachO_)
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif

#include "hwUnlitShader.h"
#include "ShadingConnection.h"


// Plug-in ID and Attributes. 
// This ID needs to be unique to prevent clashes.
//
MTypeId  hwUnlitShader::id( 0x00105440 );

MObject  hwUnlitShader::color;
MObject  hwUnlitShader::colorR;
MObject  hwUnlitShader::colorG;
MObject  hwUnlitShader::colorB;

MObject  hwUnlitShader::transparency;
MObject  hwUnlitShader::transparencyR;
MObject  hwUnlitShader::transparencyG;
MObject  hwUnlitShader::transparencyB;


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



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

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

hwUnlitShader::hwUnlitShader()
{
    m_pTextureCache = MTextureCache::instance();

    attachSceneCallbacks();
}

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

void hwUnlitShader::releaseEverything()
{
    // Clean the texture cache, through refcounting.
    m_pTextureCache->release();

    if(!MTextureCache::getReferenceCount())
    {
        m_pTextureCache = 0;
    }

}

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

void hwUnlitShader::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;
}


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

MStatus initializePlugin( MObject obj )
{ 
    MStatus   status;
    
    const MString UserClassify( "shader/surface/utility" );

    MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.0.1", "Any");
    status = plugin.registerNode( "hwUnlitShader", hwUnlitShader::id, 
                                  hwUnlitShader::creator, hwUnlitShader::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( hwUnlitShader::id );
    if (!status) {
        status.perror("deregisterNode");
        return status;
    }

    return MS::kSuccess;
}

MStatus hwUnlitShader::initialize()
{
    MFnNumericAttribute nAttr; 
    MStatus status;
    MFnTypedAttribute sAttr; // For string attributes

    // Create COLOR 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); // ugly pink-salmon color. You can't miss it.
    nAttr.setUsedAsColor(true);

    // Create TRANSPARENCY input attributes
    transparencyR = nAttr.create( "transparencyR", "itr",MFnNumericData::kFloat);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.0f);

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

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

    transparency = nAttr.create( "transparency", "it", transparencyR, transparencyG, transparencyB);
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.0001f, 0.0001f, 0.0001f); // very light gray.
    nAttr.setUsedAsColor(true);

    // Add the attributes here
    addAttribute(color);
    addAttribute(transparency);


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

    return MS::kSuccess;
}


// DESCRIPTION:
// This function gets called by Maya to evaluate the shader.
//
// Get color from the input block.
// Compute the color/alpha of our bump for a given UV coordinate.
// Put the result into the output plug.

MStatus hwUnlitShader::compute(
const MPlug&      plug,
      MDataBlock& block ) 
{ 
    bool k = false;
    k |= (plug==outColor);
    k |= (plug==outColorR);
    k |= (plug==outColorG);
    k |= (plug==outColorB);
    if( !k ) return MS::kUnknownParameter;

    // Always return black for now.
    MFloatVector resultColor(0.0,0.0,0.0);

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

    return MS::kSuccess;
}

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

    MObject object;

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


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

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

    return MS::kSuccess;
}

MStatus hwUnlitShader::getString(MObject attr, MString &str)
{
    MPlug   plug(thisMObject(), attr);
    MStatus status = plug.getValue( str );
    return MS::kSuccess;
}


void hwUnlitShader::updateTransparencyFlags(MString objectPath)
{
    // Update the transparency flags and values.
    // Check if the transparency channel is mapped on a texture, or if
    // it is constant. Textured transparency is not supported in this example,
    // because it would involve multiplying alpha values of two texture maps...
    MString transparencyName = "";
    ShadingConnection transparencyConnection(thisMObject(), objectPath, "transparency");
    if (transparencyConnection.type() == ShadingConnection::CONSTANT_COLOR)
    {
        // transparency = average of r,g,b transparency channels.
        MColor tc = transparencyConnection.constantColor();
        fConstantTransparency = (tc.r + tc.g + tc.b) / 3.0f;
    }
    else
        fConstantTransparency = 0.0f;   // will result in alpha=1.
}


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

    // white, opaque.
    float bgColor[4] = {1,1,1,1};

    // Get path of current object in draw request
    currentObjectPath = request.multiPath();
    MString currentPathName( currentObjectPath.partialPathName() );

    updateTransparencyFlags(currentPathName);

    // Get decal texture name
    MString decalName = "";
    ShadingConnection colorConnection(thisMObject(), currentPathName, "color");

    // 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 (decalName == "")
            getFloat3(color, bgColor);
    }
    else
    {
        decalName = "";
        getFloat3(color, bgColor);
    }
    
    assert(glGetError() == GL_NO_ERROR);

    view.beginGL();

    glPushAttrib( GL_ALL_ATTRIB_BITS );
    glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

    // Set the standard OpenGL blending mode.
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    
    // Change the constant alpha value. 
    float alpha = 1.0f - fConstantTransparency;

    // Set a color (with alpha). This color will be used directly if
    // the shader is not textured. Otherwise, the texture will get modulated
    // by the alpha.
    glColor4f(bgColor[0], bgColor[1], bgColor[2], alpha);


    // If the shader is textured...
    if (decalName.length() != 0)
    {
        // Enable 2D texturing.
        glEnable(GL_TEXTURE_2D);

        assert(glGetError() == GL_NO_ERROR);

        // Bind the 2D texture through the texture cache. The cache will keep
        // the texture around, so that it will only be loaded in video
        // memory once. In this example, the third parameter (mipmapping) is
        // false, so no mipmaps are generated. Note that mipmaps only work if
        // the texture has even dimensions.

        if(m_pTextureCache)
            m_pTextureCache->bind(colorConnection.texture(), MTexture::RGBA, false);    
        
        // Set minification and magnification filtering to linear interpolation.
        // For better quality, you could enable mipmapping while binding and
        // use GL_MIPMAP_LINEAR_MIPMAP in for minification filtering.
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
    }

    // Disable lighting.
    glDisable(GL_LIGHTING);

    view.endGL();

    return MS::kSuccess;
}


/* virtual */
MStatus hwUnlitShader::unbind(const MDrawRequest& request,
               M3dView& view)
{
    view.beginGL(); 
    glPopClientAttrib();
    glPopAttrib();

    view.endGL();

    return MS::kSuccess;
}

/* virtual */
MStatus hwUnlitShader::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)
{
    view.beginGL();

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

    if (normalCount > 0)
    {
        // Technically, we don't need the normals for this example. But
        // most of the 3rd party plug-ins will probably want the normal,
        // which is why the following lines were kept.
        glNormalPointer(GL_FLOAT, 0, normalArrays[0]);
        glEnableClientState(GL_NORMAL_ARRAY);
    }

    if (texCoordCount > 0)
    {
        glTexCoordPointer(2, GL_FLOAT, 0, texCoordArrays[0]);
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    }

    glDrawElements(prim, indexCount, GL_UNSIGNED_INT, indexArray);

    view.endGL();

    return MS::kSuccess;
}

/* virtual */
int     hwUnlitShader::normalsPerVertex()
{
    // Want only normals
    return 1;
}

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

/* virtual */
bool    hwUnlitShader::hasTransparency()
{
    // Performance note: if we knew that the texture  
    // is always opaque, we could return false here
    // to avoid the computation cost associated with
    // ordering objects from farthest to closest.
    return true;
}