/***********************************************************************************/
/*																				   */
/*	  Copyright 2003 University of Texas at Austin                                 */
/*	  Authors: Dr C Bajaj bajaj@cs.utexas.edu,                                     */
/*             S K Vinay  skvinay@cs.utexas.edu                                    */
/*             Anthony Thane thanea@ices.utexas.edu                                */
/*																				   */
/*    This program is free software; you can redistribute it and/or modify         */
/*    it under the terms of the GNU General Public License as published by         */
/*    the Free Software Foundation; either version 2 of the License, or            */
/*    (at your option) any later version.                                          */
/*																				   */
/*    This program is distributed in the hope that it will be useful,              */
/*    but WITHOUT ANY WARRANTY; without even the implied warranty of               */
/*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                */
/*    GNU General Public License for more details.                                 */
/*																				   */
/*    You should have received a copy of the GNU General Public License			   */
/*    along with this program; if not, write to the Free Software                  */
/*    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA    */
/*                                                                                 */
/***********************************************************************************/

// BallRenderer.cpp: implementation of the BallRenderer class.
//
//////////////////////////////////////////////////////////////////////

#include "BallRenderer.h"
#include "PlainSphereRenderer.h"
#include "FunctionSphereRenderer.h"
#include "GlobalCGContext.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;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

using CCVImposterRenderer::BallRenderer;
using CCVImposterRenderer::PlainSphereRenderer;
using CCVImposterRenderer::FunctionSphereRenderer;

bool BallRenderer::m_UseGLFallback = false;

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

BallRenderer::BallRenderer()
{
	initDefaults();
}

void BallRenderer::initDefaults()
{
	m_Initialized = false;
        m_UseGLFallback = false;
	m_NumBalls = 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_PlainSphereRenderer = new PlainSphereRenderer();
	m_FunctionSphereRenderer = 0;
}

BallRenderer::~BallRenderer()
{
	deallocateBufferObjects();
	delete m_Extensions; m_Extensions = 0;
	delete m_PlainSphereRenderer; m_PlainSphereRenderer = 0;
	delete m_FunctionSphereRenderer; m_FunctionSphereRenderer = 0;
}

void BallRenderer::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 BallRenderer::initCG()
{
	if( m_CGRequirements != TRI_STATE_UNKNOWN ) return true;
	
	// drastic, change
	if( !m_PlainSphereRenderer ) 
	{
		m_CGRequirements = TRI_STATE_DOESNT_WORK;
		return false;
	}

	if( !m_PlainSphereRenderer->initCG())
	{
		m_CGRequirements = TRI_STATE_DOESNT_WORK;
		return false;
	}

	bool ret = m_PlainSphereRenderer->loadProgram();
	if( ret )
		m_CGRequirements = TRI_STATE_WORKS;
	else
		m_CGRequirements = TRI_STATE_DOESNT_WORK;
	
	return ret;
}

bool BallRenderer::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 BallRenderer::clearBalls()
{
	m_TextureCoord.clearBuffer();
	m_Color.clearBuffer();
	m_Position.clearBuffer();

	m_NumBalls = 0;
}

int BallRenderer::getNumBalls() const
{
	return m_NumBalls;
}

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

void BallRenderer::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_NumBalls;
	unsigned int batch = BatchSize;
	unsigned int offset = 0;
	unsigned int currentBuffer = 0;
	allocateBufferObjects((m_NumBalls/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 BallRenderer::renderOnce()
{
        // MESH RENDERING
        if(m_UseGLFallback) {
                MeshSphereRenderer::DrawSphereBuffer(m_Position, m_Color);
        }
        // CG RENDERING
        else {
                // try rendering in batches
                unsigned int remaining = m_NumBalls;
                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 BallRenderer::renderBuffer( bool useHardwareBuffer, bool useFunctionOnSurface, double* rotations, double* translations, int numberOfTransformations, bool randomizeColors, float avgradius )
{
        // MESH RENDERER
        if(m_UseGLFallback) {
                if (!m_Initialized)
                        return;
                if (m_NumBalls==0)
                        return;
                
                glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LIGHTING_BIT );
                
                // if ( useFunctionOnSurface )
                // {
                //         if( !m_FunctionSphereRenderer ) return;
                //         m_FunctionSphereRenderer->bindProgramAndParams();
                // }
                // else
                // {
                //         if( !m_PlainSphereRenderer ) return;
                //         m_PlainSphereRenderer->bindProgramAndParams();
                // }
                
                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.);
                
                srand(255); // needed to call rand to set colors for SS, CHAIN and PROTEINS
                ////// perform transformations if needed and repeat the rendering
                if( rotations && translations && (numberOfTransformations>0) )
                {
                        int i;
                        for( i=0; i<numberOfTransformations; i++ )
                        {
                                GLfloat trans[16];
                                trans[0]  = (GLfloat)rotations[i*9+0];
                                trans[1]  = (GLfloat)rotations[i*9+3];
                                trans[2]  = (GLfloat)rotations[i*9+6];
                                trans[3]  = (GLfloat)0;
                                trans[4]  = (GLfloat)rotations[i*9+1];
                                trans[5]  = (GLfloat)rotations[i*9+4];
                                trans[6]  = (GLfloat)rotations[i*9+7];			
                                trans[7]  = (GLfloat)0;
                                trans[8]  = (GLfloat)rotations[i*9+2];
                                trans[9]  = (GLfloat)rotations[i*9+5];
                                trans[10] = (GLfloat)rotations[i*9+8];
                                trans[11] = (GLfloat)0;
                                trans[12]  = (GLfloat)translations[i*3+0];
                                trans[13]  = (GLfloat)translations[i*3+1];
                                trans[14] = (GLfloat)translations[i*3+2];
                                trans[15] = (GLfloat)1;
                                
                                // perform the rotation needed
                                glMatrixMode(GL_MODELVIEW);
                                glPushMatrix();
                                glMultMatrixf(trans);
                                
                                if ( useFunctionOnSurface )
                                        m_FunctionSphereRenderer->bindMatrices();
                                else
                                        m_PlainSphereRenderer->bindMatrices();
                                
                                if( randomizeColors )
                                {
                                        float blue = (float)(rand() / ( RAND_MAX + 1.0f ));
                                        float red = (float)(rand() / ( RAND_MAX + 1.0f )); 
                                        float green = (float)(rand() / ( RAND_MAX + 1.0f )); 
                                        
                                        glColor4f( red, blue, green, avgradius );
                                }
                                
                                renderOnce();
                                
                                glPopMatrix();
                        }
                }
                else
                {
                        if ( useFunctionOnSurface )
                                m_FunctionSphereRenderer->bindMatrices();
                        else
                                m_PlainSphereRenderer->bindMatrices();
                        
                        renderOnce();
                }
                
                // if ( useFunctionOnSurface )
                // {
                //         if( !m_FunctionSphereRenderer ) return;
                //         m_FunctionSphereRenderer->unbindProgramAndParams();
                // }
                // else
                // {
                //         if( !m_PlainSphereRenderer ) return;
                //         m_PlainSphereRenderer->unbindProgramAndParams();
                // }
                
                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();
        }
        // CG RENDERER
        else {
                if( useHardwareBuffer )
                {
                        if( m_OpenGLRequirements == TRI_STATE_UNKNOWN ) 
                        {
                                if( !initExtensions() ) return;
                        }
                        if( m_OpenGLRequirements == TRI_STATE_DOESNT_WORK )
                                return;
                        
                        // OK, buffers are present on this card.
                }
                
                // bail if not initialized
                // WRONG SKVINAY, use glusphere or something like that
                if (!m_Initialized)
                        return;
                if (m_NumBalls==0)
                        return;
                
                if (m_Dirty) prepareBuffers();
                
                glPushAttrib(GL_ENABLE_BIT | GL_CURRENT_BIT | GL_LIGHTING_BIT );
                
                if ( useFunctionOnSurface )
                {
                        if( !m_FunctionSphereRenderer ) return;
                        m_FunctionSphereRenderer->bindProgramAndParams();
                }
                else
                {
                        if( !m_PlainSphereRenderer ) return;
                        m_PlainSphereRenderer->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);
                if( ! randomizeColors )
                        glEnableClientState(GL_COLOR_ARRAY);
                
                
                srand(255); // needed to call rand to set colors for SS, CHAIN and PROTEINS
                ////// perform transformations if needed and repeat the rendering
                if( rotations && translations && (numberOfTransformations>0) )
                {
                        int i;
                        for( i=0; i<numberOfTransformations; i++ )
                        {
                                GLfloat trans[16];
                                trans[0]  = (GLfloat)rotations[i*9+0];
                                trans[1]  = (GLfloat)rotations[i*9+3];
                                trans[2]  = (GLfloat)rotations[i*9+6];
                                trans[3]  = (GLfloat)0;
                                trans[4]  = (GLfloat)rotations[i*9+1];
                                trans[5]  = (GLfloat)rotations[i*9+4];
                                trans[6]  = (GLfloat)rotations[i*9+7];			
                                trans[7]  = (GLfloat)0;
                                trans[8]  = (GLfloat)rotations[i*9+2];
                                trans[9]  = (GLfloat)rotations[i*9+5];
                                trans[10] = (GLfloat)rotations[i*9+8];
                                trans[11] = (GLfloat)0;
                                trans[12]  = (GLfloat)translations[i*3+0];
                                trans[13]  = (GLfloat)translations[i*3+1];
                                trans[14] = (GLfloat)translations[i*3+2];
                                trans[15] = (GLfloat)1;
                                
                                // perform the rotation needed
                                glMatrixMode(GL_MODELVIEW);
                                glPushMatrix();
                                glMultMatrixf(trans);
                                
                                if ( useFunctionOnSurface )
                                        m_FunctionSphereRenderer->bindMatrices();
                                else
                                        m_PlainSphereRenderer->bindMatrices();
                                
                                if( randomizeColors )
                                {
                                        float blue = (float)(rand() / ( RAND_MAX + 1.0f ));
                                        float red = (float)(rand() / ( RAND_MAX + 1.0f )); 
                                        float green = (float)(rand() / ( RAND_MAX + 1.0f )); 
                                        
                                        glColor4f( red, blue, green, avgradius );
                                }
                                
                                renderOnce();
                                
                                glPopMatrix();
                                
                        }
                }
                else
                {
                        if ( useFunctionOnSurface )
                                m_FunctionSphereRenderer->bindMatrices();
                        else
                                m_PlainSphereRenderer->bindMatrices();
                        
                        renderOnce();
                }
                
                glDisableClientState(GL_VERTEX_ARRAY);
                if( ! randomizeColors )
                        glDisableClientState(GL_COLOR_ARRAY);
                
                if ( useFunctionOnSurface )
                {
                        if( !m_FunctionSphereRenderer ) return;
                        m_FunctionSphereRenderer->unbindProgramAndParams();
                }
                else
                {
                        if( !m_PlainSphereRenderer ) return;
                        m_PlainSphereRenderer->unbindProgramAndParams();
                }
                
                m_Extensions->glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
                glDisable(GL_BLEND);
                glDisable(GL_COLOR_SUM_ARB);
                
                glPopAttrib();
        }
}
void BallRenderer::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);
}
void BallRenderer::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;
}

bool BallRenderer::addFunction( unsigned char* data, double minx, double miny, double minz, double maxx, double maxy, double maxz, int width, int height, int depth)
{
        if(m_UseGLFallback) {
                delete m_FunctionSphereRenderer;
                m_FunctionSphereRenderer = new FunctionSphereRenderer();
                if( !m_FunctionSphereRenderer->initCG()) return false;
                
                if( !m_FunctionSphereRenderer->addFunction( data, minx, miny, minz, maxx, maxy, maxz, width, height, depth) )
                        return false;
                return m_FunctionSphereRenderer->fallbackLoadProgram();
        }
        else {
                delete m_FunctionSphereRenderer;
                m_FunctionSphereRenderer = new FunctionSphereRenderer();
                if( !m_FunctionSphereRenderer->initCG()) return false;
                
                if( !m_FunctionSphereRenderer->addFunction( data, minx, miny, minz, maxx, maxy, maxz, width, height, depth) )
                        return false;
                return m_FunctionSphereRenderer->loadProgram();
        }
        
}
