
//**************************************************************************/
// 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") );

    m_VertexProgram =
        cgCreateProgramFromFile(
          m_CGContext,              /* Cg runtime context */
          CG_SOURCE,                /* Program in human-readable form */
          vertexPath.filePath().toAscii().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. */
    m_FragmentProgram =
        cgCreateProgramFromFile(
          m_CGContext,                /* Cg runtime context */
          CG_SOURCE,                  /* Program in human-readable form */
          fragmentPath.filePath().toAscii().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);
}
