/*****************************************************************************/
/*                             ______________________                        */
/*                            / _ _ _ _ _ _ _ _ _ _ _)                       */
/*            ____  ____  _  / /__  __  _____  __                            */
/*           (_  _)( ___)( \/ /(  \/  )(  _  )(  )                           */
/*             )(   )__)  )  (  )    (  )(_)(  )(__                          */
/*            (__) (____)/ /\_)(_/\/\_)(_____)(____)                         */
/*            _ _ _ _ __/ /                                                  */
/*           (___________/                     ___  ___                      */
/*                                      \  )| |   ) _ _|\   )                */
/*                                 ---   \/ | |  / |___| \_/                 */
/*                                                       _/                  */
/*                                                                           */
/*   Copyright (C) The University of Texas at Austin                         */
/*                                                                           */
/*     Author:     Vinay Siddavanahalli <skvinay@cs.utexas.edu>   2004-2005  */
/*                                                                           */
/*     Principal Investigator: Chandrajit Bajaj <bajaj@ices.utexas.edu>      */
/*                                                                           */
/*         Professor of Computer Sciences,                                   */
/*         Computational and Applied Mathematics Chair in Visualization,     */
/*         Director, Computational Visualization Center (CVC),               */
/*         Institute of Computational Engineering and Sciences (ICES)        */
/*         The University of Texas at Austin,                                */
/*         201 East 24th Street, ACES 2.324A,                                */
/*         1 University Station, C0200                                       */
/*         Austin, TX 78712-0027                                             */
/*         http://www.cs.utexas.edu/~bajaj                                   */
/*                                                                           */
/*         http://www.ices.utexas.edu/CVC                                    */
/*  This software comes with a license. Using this code implies that you     */
/*  read, understood and agreed to all the terms and conditions in that      */
/*  license.                                                                 */
/*                                                                           */
/*  We request that you agree to acknowledge the use of the software that    */
/*  results in any published work, including scientific papers, films and    */
/*  videotapes by citing the reference listed below                          */
/*                                                                           */
/*    C. Bajaj, P. Djeu, V. Siddavanahalli, A. Thane,                        */
/*    Interactive Visual Exploration of Large Flexible Multi-component       */
/*    Molecular Complexes,                                                   */
/*    Proc. of the Annual IEEE Visualization Conference, October 2004,       */
/*    Austin, Texas, IEEE Computer Society Press, pp. 243-250.               */
/*                                                                           */
/*****************************************************************************/
// GaussianRenderer.cpp: implementation of the GaussianRenderer class.
//
//////////////////////////////////////////////////////////////////////

#include "GaussianRenderer.h"
#include "Texture.h"
#include "GlobalCGContext.h"
#include "cgGLVertexShader.h"
#include "cgGLFragmentShader.h"
#include "MeshSphereRenderer.h"

#include <stdlib.h>

static const int BatchSize = 10000;

static const int TRI_STATE_WORKS = 0;
static const int TRI_STATE_DOESNT_WORK= 1;
static const int TRI_STATE_UNKNOWN = 2;

using CCVImposterRenderer::GaussianRenderer;

bool GaussianRenderer::m_UseGLFallback = false;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
GaussianRenderer::GaussianRenderer()
{
	m_Initialized = false;
	m_NumGaussians = 0;
	m_Dirty = true;
	m_NumBufferObjectsAllocated = 0;
	m_BufferObjects = 0;
	m_Extensions = 0;
	m_CGRequirements = TRI_STATE_UNKNOWN;
	m_OpenGLRequirements = TRI_STATE_UNKNOWN;
	m_ProgramsLoaded = TRI_STATE_UNKNOWN;
	m_VertexProgram = 0;
	m_FragmentProgram = 0;
}

GaussianRenderer::~GaussianRenderer()
{
	deallocateBufferObjects();
	delete m_Extensions; m_Extensions = 0;
}

void GaussianRenderer::initRenderer( bool drawFunction )
{
        // without extensions we cannot operate at all
        if(!initExtensions()) {
                m_Initialized = false;
                fprintf(stderr, "ERROR: OpenGL extensions unavailable, update your graphics drivers.\n");
                return;
        }
        // if FP30 isn't present we will use a different branch of every render method
        if(!initCG()) {
                fprintf(stderr, "WARNING: FP30 not found, falling back on GL Rendering.\n");
                m_UseGLFallback = true;
        }
	m_Initialized = true;
}

bool GaussianRenderer::initCG()
{
	if( m_CGRequirements != TRI_STATE_UNKNOWN ) return true;
	
	if ( cgGLIsProfileSupported(CG_PROFILE_VP20) ) 
		m_VertexProfile = CG_PROFILE_VP20;
	else 
	{
		m_CGRequirements = TRI_STATE_DOESNT_WORK;
		return false;
	}
	if ( cgGLIsProfileSupported(CG_PROFILE_FP30) ) 
		m_FragmentProfile = CG_PROFILE_FP30;
	else 
	{
		m_CGRequirements = TRI_STATE_DOESNT_WORK;
		return false;
	}
	m_CGRequirements = TRI_STATE_WORKS;

	bool ret = loadProgram();
	if( ret )
		m_CGRequirements = TRI_STATE_WORKS;
	else
		m_CGRequirements = TRI_STATE_DOESNT_WORK;

	return true;
}

bool GaussianRenderer::loadProgram()
{
	if( m_FragmentProgram ) cgDestroyProgram(m_FragmentProgram);
	if( m_VertexProgram ) cgDestroyProgram(m_VertexProgram);

	CGcontext context = GlobalCGContext::getCGContext();

	// Create Vertex Program for Spheres
	//	m_VertexProgram = cgCreateProgramFromFile(context, CG_SOURCE,
	//		"cgGLVertexShader.cg", m_VertexProfile, 0, 0);
	m_VertexProgram = cgCreateProgram(context, CG_SOURCE,cgGLVertexShader,
		m_VertexProfile, 0, 0 );

	// Load Vertex Program
	cgGLLoadProgram(m_VertexProgram);

	// get parameter handles
	m_PositionParam = cgGetNamedParameter(m_VertexProgram, "IN.center");
	m_ColorParam = cgGetNamedParameter(m_VertexProgram, "IN.color");
	m_ModelViewITParam = cgGetNamedParameter(m_VertexProgram, "ModelViewIT");
	m_ModelViewInverseParam = cgGetNamedParameter(m_VertexProgram, "ModelViewInverse");
	m_ModelViewProjParam = cgGetNamedParameter(m_VertexProgram, "ModelViewProj");
	m_ProjParam = cgGetNamedParameter(m_VertexProgram, "Proj");

	if (!m_ModelViewITParam || !m_ModelViewProjParam || !m_ProjParam || !m_ColorParam || !m_PositionParam) 
		return false;

	// Create Fragment Program
	//	m_FragmentProgram = cgCreateProgramFromFile(context, CG_SOURCE,
	//		"cgGLFragmentShader.cg", m_FragmentProfile, 0, 0);
	m_FragmentProgram = cgCreateProgram(context, CG_SOURCE,cgGLFragmentShader,
		m_FragmentProfile, 0, 0 );

	// Load Fragment Program
	cgGLLoadProgram(m_FragmentProgram);

	// get parameter handles
	m_MainColorParam = cgGetNamedParameter(m_FragmentProgram, "maincolor");
	m_NormalMapParam = cgGetNamedParameter(m_FragmentProgram, "normalmap");
	m_DepthMapParam = cgGetNamedParameter(m_FragmentProgram, "depthmap");

	// prepare the textures
	m_TextureNormalMap = new Texture();
	//m_TextureNormalMap->loadPGM("alphamask.pgm");
	m_TextureNormalMap->calculateSphereOpacityAndNormalMap(512);

	m_TextureDepthMap = new Texture();
	m_TextureDepthMap->calculateDepthMap(512,512);

	cgGLSetTextureParameter(m_NormalMapParam, m_TextureNormalMap->getTextureID());
	cgGLSetTextureParameter(m_DepthMapParam, m_TextureDepthMap->getTextureID());
	return true;

}

bool GaussianRenderer::initExtensions()
{
	delete m_Extensions; m_Extensions = 0;
	m_Extensions = new MyExtensions();

	if( !m_Extensions->initExtensions(
		"GL_VERSION_1_2 "
		"GL_EXT_secondary_color "
		"GL_ARB_multitexture "
		"GL_ARB_vertex_buffer_object" ))
	{
		m_OpenGLRequirements = TRI_STATE_DOESNT_WORK;
		return false;
	}
	m_OpenGLRequirements = TRI_STATE_WORKS;

	return true;
}

void GaussianRenderer::deallocateBufferObjects()
{
	if( m_OpenGLRequirements == TRI_STATE_UNKNOWN ) 
	{
		if( !initExtensions() ) return;
	}
	if( m_OpenGLRequirements == TRI_STATE_DOESNT_WORK )
		return;

	// OK, buffers are present on this card.
	if (m_BufferObjects)
		m_Extensions->glDeleteBuffersARB(m_NumBufferObjectsAllocated, m_BufferObjects);
	m_NumBufferObjectsAllocated = 0;
	delete [] m_BufferObjects; m_BufferObjects = 0;
}

void GaussianRenderer::clearGaussians()
{
	m_TextureCoord.clearBuffer();
	m_Color.clearBuffer();
	m_Position.clearBuffer();

	m_NumGaussians = 0;
}

int GaussianRenderer::getNumGaussians() const
{
	return m_NumGaussians;
}

#define BUFFER_OFFSET(i) ((char *)NULL + (i))

void GaussianRenderer::prepareBuffers()
{
	if( m_OpenGLRequirements == TRI_STATE_UNKNOWN ) 
	{
		if( !initExtensions() ) return;
	}
	if( m_OpenGLRequirements == TRI_STATE_DOESNT_WORK )
		return;
        
	// OK, buffers are present on this card.

	unsigned int remaining = m_NumGaussians;
	unsigned int batch = BatchSize;
	unsigned int offset = 0;
	unsigned int currentBuffer = 0;
	allocateBufferObjects((m_NumGaussians/BatchSize+1)*2);
	while (remaining>0) {
		unsigned int thisRender = (remaining<batch?remaining:batch);

		m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferObjects[currentBuffer]);
		m_Extensions->glBufferDataARB(GL_ARRAY_BUFFER_ARB, thisRender*4*4*sizeof(float), m_Position.getBuffer()+offset, GL_STATIC_DRAW_ARB);

		m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferObjects[currentBuffer+1]);
		m_Extensions->glBufferDataARB(GL_ARRAY_BUFFER_ARB, thisRender*4*4*sizeof(float), m_Color.getBuffer()+offset, GL_STATIC_DRAW_ARB);

		// decrement remaining
		remaining-=thisRender;
		// increment offset
		offset+=thisRender*4*4;
		currentBuffer+=2;
	}
	m_Dirty = false;
}

void GaussianRenderer::renderOnce()
{
        if(m_UseGLFallback) {
                MeshSphereRenderer::DrawSphereBuffer(m_Position, m_Color);
        }
        else {
                // try rendering in batches
                unsigned int remaining = m_NumGaussians;
                unsigned int batch = BatchSize;
                unsigned int offset = 0;
                unsigned int currentBuffer = 0;
                while (remaining>0) 
                {
                        unsigned int thisRender = (remaining<batch?remaining:batch);
                        
                        m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferObjects[currentBuffer]);
                        glVertexPointer(4, GL_FLOAT, 0, BUFFER_OFFSET(0));
                        m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferObjects[currentBuffer+1]);
                        glColorPointer(4, GL_FLOAT, 0, BUFFER_OFFSET(0));
                        glDrawArrays(GL_QUADS, 0, thisRender*4);
                        //glDrawRangeElements( GL_QUADS, 0 , thisRender*4-1 , thisRender*4 , GLenum type , const GLvoid *indices ); 
                        
                        // decrement remaining
                        remaining-=thisRender;
                        // increment offset
                        offset+=thisRender*4*4*sizeof(GLfloat);
                        currentBuffer+=2;
                }
        }
}
void GaussianRenderer::renderBuffer()
{
        if(m_UseGLFallback) {
                if (!m_Initialized)
                        return;
                if (m_NumGaussians==0)
                        return;
                
                glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LIGHTING_BIT );
                
                glEnable(GL_CULL_FACE);
                glCullFace(GL_BACK);
                glEnable(GL_DEPTH_TEST);
                glEnable(GL_LIGHTING);
                glEnable(GL_ALPHA_TEST);
                glAlphaFunc(GL_GREATER, 0.5 );
                glEnable(GL_COLOR_SUM_ARB);
                
                m_Extensions->glSecondaryColor3fEXT(0.,0.,1.);
                
                bindMatrices();
                renderOnce();
                
                m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
                glDisable(GL_CULL_FACE);
                glDisable(GL_DEPTH_TEST);
                glDisable(GL_LIGHTING);
                glDisable(GL_BLEND);
                glDisable(GL_COLOR_SUM_ARB);
                
                glPopAttrib();
        }
        else {
                if( m_OpenGLRequirements == TRI_STATE_UNKNOWN ) 
                {
                        if( !initExtensions() ) return;
                }
                if( m_OpenGLRequirements == TRI_STATE_DOESNT_WORK )
                        return;
                
                // bail if not initialized
                // WRONG SKVINAY, use glusphere or something like that
                if (!m_Initialized)
                        return;
                if (m_NumGaussians==0)
                        return;
                
                if (m_Dirty) prepareBuffers();
                
                glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LIGHTING_BIT );
                
                bindProgramAndParams();
                
                glDisable(GL_CULL_FACE);
                glDisable(GL_LIGHTING);
                glEnable(GL_ALPHA_TEST);
                glAlphaFunc(GL_GREATER, 0.5 );
                glEnable(GL_COLOR_SUM_ARB);
                
                m_Extensions->glSecondaryColor3fEXT(0.,0.,1.);
                
                glEnableClientState(GL_VERTEX_ARRAY);
                glEnableClientState(GL_COLOR_ARRAY);
                
                
                bindMatrices();
                renderOnce();
                
                glDisableClientState(GL_VERTEX_ARRAY);
                glDisableClientState(GL_COLOR_ARRAY);
                
                unbindProgramAndParams();
                
                m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
                glDisable(GL_BLEND);
                glDisable(GL_COLOR_SUM_ARB);
                
                glPopAttrib();
        }
}

void GaussianRenderer::allocateBufferObjects(unsigned int num)
{
	if( m_OpenGLRequirements == TRI_STATE_UNKNOWN ) 
	{
		if( !initExtensions() ) return;
	}
	if( m_OpenGLRequirements == TRI_STATE_DOESNT_WORK )
		return;

	// OK, buffers are present on this card.
	deallocateBufferObjects();
	m_BufferObjects = new GLuint[num];
	m_NumBufferObjectsAllocated = num;
	m_Extensions->glGenBuffersARB(num, m_BufferObjects);
}

bool GaussianRenderer::bindProgramAndParams()
{
	// Set up for ball rendering
	cgGLEnableProfile(m_VertexProfile);
	cgGLBindProgram(m_VertexProgram);

	cgGLEnableProfile(m_FragmentProfile);
	cgGLBindProgram(m_FragmentProgram);

	cgGLEnableTextureParameter(m_NormalMapParam);
	cgGLEnableTextureParameter(m_DepthMapParam);

	cgGLSetParameter4f(m_MainColorParam, 1.0, 0.0, 0.0, 1.0); // red
	return true;
}

bool GaussianRenderer::unbindProgramAndParams()
{
	cgGLDisableTextureParameter(m_NormalMapParam);
	cgGLDisableTextureParameter(m_DepthMapParam);
	cgGLDisableProfile(m_VertexProfile);
	cgGLDisableProfile(m_FragmentProfile);
	return true;
}

void GaussianRenderer::bindMatrices()
{
	cgGLSetStateMatrixParameter(m_ModelViewITParam, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE);
	cgGLSetStateMatrixParameter(m_ModelViewProjParam, CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(m_ProjParam, CG_GL_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY);
	cgGLSetStateMatrixParameter(m_ModelViewInverseParam, CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE);
}
