ToonMaterial/ToonMaterial.cpp


//**************************************************************************/

// Copyright (c) 2008 Autodesk, Inc.

// All rights reserved.

//

// Use of this software is subject to the terms of the Autodesk license

// agreement provided at the time of installation or download, or which

// otherwise accompanies this software in either electronic or hard copy form.

//

//**************************************************************************/

// DESCRIPTION:

// CREATED: October 2008

//**************************************************************************/



#include "ToonMaterial.h"

#include <QtCore/QDir>



// NOTE : This plugin does not have any exception handling.  This was done

// for brevity reasons.



// Plugin registration macros

MB_PLUGIN( "ToonMaterial", "Toon material", "Autodesk", "http://www.mudbox3d.com", 0 );



IMPLEMENT_CLASS( ToonMaterial, Material, "Toon Material" );



// forward declarations

static float diffuseRamp(float x);

static float specularRamp(float x);

static float edgeRamp(float x);

static void loadRamp(GLuint texobj, int size, float (*func)(float x));



ToonMaterial::ToonMaterial( void ) :

            m_aKd(this, "Kd"),

            m_aKs(this, "Ks"),

            m_aShininess(this, "Shiny")

{

    SetName( "Toon Material" );



    m_CGContext = cgCreateContext();

    cgGLSetDebugMode( CG_FALSE );

    

    cgGLSetManageTextureParameters(m_CGContext, CG_TRUE);

    cgSetParameterSettingMode(m_CGContext, CG_DEFERRED_PARAMETER_SETTING);



    /* Compile and load the vertex program. */

    m_VertexProfile = cgGLGetLatestProfile(CG_GL_VERTEX);

    cgGLSetOptimalOptions(m_VertexProfile);



    // Get the path to where the plug-in was loaded from

    QDir pluginDir( Kernel()->PluginDirectory("ToonMaterial") );

    QFileInfo vertexPath( pluginDir, QString("toon_vertex.cg") );

    QFileInfo fragmentPath( pluginDir, QString("toon_fragment.cg") );



    QByteArray qbaVertexPath = QFile::encodeName(vertexPath.filePath());

    m_VertexProgram =

        cgCreateProgramFromFile(

          m_CGContext,              /* Cg runtime context */

          CG_SOURCE,                /* Program in human-readable form */

          qbaVertexPath.constData(),  /* Name of file containing program */

          m_VertexProfile,        /* Profile: OpenGL ARB vertex program */

          "main",      /* Entry function name */

          NULL);                    /* No extra commyPiler options */



    cgGLLoadProgram(m_VertexProgram);



    m_ModelViewProjParam = cgGetNamedParameter(m_VertexProgram, "modelViewProj");

    m_LightPositionParam = cgGetNamedParameter(m_VertexProgram, "lightPosition");

    m_EyePositionParam = cgGetNamedParameter(m_VertexProgram, "eyePosition");

    m_ShininessParam = cgGetNamedParameter(m_VertexProgram, "shininess");



    m_FragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);

    cgGLSetOptimalOptions(m_FragmentProfile);

    

    /* Compile and load the fragment program. */

    QByteArray qbaFragmentPath = QFile::encodeName(fragmentPath.filePath());

    m_FragmentProgram =

        cgCreateProgramFromFile(

          m_CGContext,                /* Cg runtime context */

          CG_SOURCE,                  /* Program in human-readable form */

          qbaFragmentPath.constData(),  /* Name of file containing program */

          m_FragmentProfile,        /* Profile: OpenGL ARB vertex program */

          "main",      /* Entry function name */

          NULL);                      /* No extra compiler options */



    cgGLLoadProgram(m_FragmentProgram);



    m_KdParam = cgGetNamedParameter(m_FragmentProgram, "Kd");

    m_KsParam = cgGetNamedParameter(m_FragmentProgram, "Ks");



    m_DiffuseRampParam = cgGetNamedParameter(m_FragmentProgram, "diffuseRamp");

    m_SpecularRampParam = cgGetNamedParameter(m_FragmentProgram, "specularRamp");

    m_EdgeRampParam = cgGetNamedParameter(m_FragmentProgram, "edgeRamp");



    glGenTextures(1, &m_iDiffuseRamp); // m_iDiffuseRamp = 1;

    loadRamp(m_iDiffuseRamp, 255, diffuseRamp);

    cgGLSetTextureParameter(m_DiffuseRampParam, m_iDiffuseRamp);



    glGenTextures(1, &m_iSpecularRamp); // m_iSpecularRamp = 2;

    loadRamp(m_iSpecularRamp, 255, specularRamp);

    cgGLSetTextureParameter(m_SpecularRampParam, m_iSpecularRamp);



    glGenTextures(1, &m_iEdgeRamp); // m_iEdgeRamp = 3;

    loadRamp(m_iEdgeRamp, 255, edgeRamp);

    cgGLSetTextureParameter(m_EdgeRampParam, m_iEdgeRamp);



    m_aKd = Color(0.8f, 0.6f, 0.2f, 1.0f);

    m_aKs = Color(0.3f, 0.3f, 4.0f, 0.0f);



    // Set min & max of shininess slider.

    m_aShininess.SetMax(25);

    m_aShininess.SetMin(1);



    m_aShininess = 5.2f;

};



ToonMaterial::~ToonMaterial( void )

{

    // This destroys all programs as well

    cgDestroyContext(m_CGContext);



    // Clean up

    glDeleteTextures(1, &m_iDiffuseRamp);

    glDeleteTextures(1, &m_iSpecularRamp);

    glDeleteTextures(1, &m_iEdgeRamp);

};



bool ToonMaterial::Activate( const Mesh *pMesh, const AxisAlignedBoundingBox &cUVArea, const Color &cColor )

{

    if (!pMesh)

        return false;



    // Enable the profiles

    cgGLEnableProfile(m_VertexProfile);

    cgGLEnableProfile(m_FragmentProfile);



    // Bind the programs

    cgGLBindProgram(m_VertexProgram);

    cgGLBindProgram(m_FragmentProgram);



    // Ordinarily you'd want to only set these values if they change.  For

    // this sample we'll just set them here.



    cgSetParameter4fv(m_KdParam, m_aKd.Value());

    cgSetParameter4fv(m_KsParam, m_aKs.Value());

    cgSetParameter1f(m_ShininessParam, m_aShininess.Value());



    // Set the light on the shader.

    // Mudbox supports both point & directional lights.  If

    // the first light is directional we'll push it out from

    // the origin.  The shader assumes point light

    if (Kernel()->Scene()->LightCount())

    {

        Light *pLight = Kernel()->Scene()->Light(0);

        Vector v(0,0,1);

        if (pLight->Type() == Light::LIGHT_POINT)

        {

            v = pLight->Transformation()->Position();

        }

        else if (pLight->Type() == Light::LIGHT_DIRECTIONAL)

        {

            if (pLight->IsLockedToCamera())

                v = pLight->LockedToCameraMatrix().Transform(v);

            else

                v = pLight->Transformation()->WorldToLocalMatrix().Invert().Transform(v);



            v.Normalize();

            // Scoot this away from the origin to give it the appearance of a

            // point light.

            v*=100;

        }

        cgSetParameter3fv(m_LightPositionParam, v);

    }



    cgSetParameter3fv(m_EyePositionParam, Kernel()->Scene()->ActiveCamera()->Position());



    Matrix mModelViewProj = pMesh->Geometry()->Transformation()->LocalToWorldMatrix()*Kernel()->Scene()->ActiveCamera()->Matrix(true);

    cgSetParameterValuefc(m_ModelViewProjParam, 16, mModelViewProj);



    cgUpdateProgramParameters(m_VertexProgram);



    return true;

};



void ToonMaterial::Deactivate( void )

{

    cgGLDisableProfile(m_VertexProfile);

    cgGLDisableProfile(m_FragmentProfile);

};



void ToonMaterial::OnNodeEvent( const Attribute &cAttribute, NodeEventType cType )

{

    bool bRedraw = false;



    if(cType == etValueChanged)

    {

        if (cAttribute == m_aKs ||

            cAttribute == m_aKd ||

            cAttribute == m_aShininess)

            bRedraw = true;

    }



    Material::OnNodeEvent( cAttribute, cType );



    if (bRedraw)

        Kernel()->Redraw();

};



/* Callback function for loadRamp */

float diffuseRamp(float x)

{

    if (x > 0.5) {

        return x*x*(3-2*x);

    } else {

        return 0.5f;

    }

}



/* Callback function for loadRamp */

float specularRamp(float x)

{

    if (x > 0.2f) {

        return x;

    } else {

        return 0.0f;

    }

}



/* Callback function for loadRamp */

float edgeRamp(float x)

{

    if (x < 0.2f) {

        return 1.0f;

    } else {

        return 0.85f;

    }

}



/* Create a 1D texture ramp by evaluating func over the range [0,1]. */

void loadRamp(GLuint texobj, int size, float (*func)(float x))

{

    int bytesForRamp = size*sizeof(float);

    float *ramp = (float *) malloc(bytesForRamp);

    float *slot = ramp;

    float dx = 1.0f / (float) size;

    float x;

    int i;



    for (i=0, x=0.0, slot=ramp; i<size; i++, x += dx, slot++) {

        float v = func(x);



        *slot = v;

    }



#ifndef GL_CLAMP_TO_EDGE

#define GL_CLAMP_TO_EDGE                  0x812F  /* Added by OpenGL 1.2 */

#endif



    glBindTexture(GL_TEXTURE_1D, texobj);

    glTexImage1D(GL_TEXTURE_1D, 0, GL_INTENSITY16, size, 0, GL_LUMINANCE, GL_FLOAT, ramp);

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

}