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