/**
* OpenGL Screen Effects
* @version: 3-20-2005
* @author Joseph 'Pcjoe' Florencio
*/

#include "../hud.h"
#include "../cl_util.h"
#include "fxsys.h"
#include "r_studioint.h"

CFXScreenEffects g_ScreenEffects;

#define BLUR_TEX_W	256
#define BLUR_TEX_H	256

extern engine_studio_api_t IEngineStudio;
extern cvar_t *cl_radialblur_quality;
PFNGLMULTITEXCOORD2FARBPROC		glMultiTexCoord2fARB	= NULL;
PFNGLACTIVETEXTUREARBPROC		glActiveTextureARB		= NULL;

// Radial Blur Quality (blur amount, blur inc)
float gflRadBlurQuality [3][2] = { {3,0.05}, {5, 0.02}, {10, 0.01} };

// From ATI code, my function didnt seem to work correctly with all video cards
/////////////////////////////////////
// Determine if a particular extension is supported
//   extension -- Name of the extension to check
/////////////////////////////////////
bool mainExtensionSupported (const char *extension)
{
	static const GLubyte *extensions = NULL;
	const GLubyte *start;
	GLubyte *where, *terminator;

	where = (GLubyte *) strchr(extension, ' ');
	if (where || *extension == '\0')
	return false;

	if (!extensions)
	extensions = glGetString(GL_EXTENSIONS);

	start = extensions;
	for (;;) {
		where = (GLubyte *) strstr((const char *) start, extension);
		if (!where)
			break;
		terminator = where + strlen(extension);
		if (where == start || *(where - 1) == ' ') {
			if (*terminator == ' ' || *terminator == '\0') {
			return true;
			}
		}
		start = terminator;
	}

	return false;
}

// Constructor
CFXScreenEffects::CFXScreenEffects()
{
}

// Precache
void CFXScreenEffects::Precache( void )
{	// Get multitexturing functions
	if((IEngineStudio.IsHardware() == 1) && mainExtensionSupported( "GL_ARB_multitexture" ))
	{	glMultiTexCoord2fARB = 
			(PFNGLMULTITEXCOORD2FARBPROC)wglGetProcAddress("glMultiTexCoord2fARB");
		glActiveTextureARB = 
			(PFNGLACTIVETEXTUREARBPROC)wglGetProcAddress("glActiveTextureARB");
		getRadialBlur()->setErrorStatus(false);
	}
	else
		return;

	// Precache Radial Blur textures
	getRadialBlur()->Precache();
}

// Think
void CFXScreenEffects::Think( void )
{	getRadialBlur()->Think();
}


/*
* Screen Shake
*/
// Constructor
CFXScreenShake::CFXScreenShake()
{	setScreenShakeAmplitude(0);
}

// Get Shake Offset
vec3_t CFXScreenShake::getShakeOffset( void )
{	vec3_t vecShake;
	float flAmp = getScreenShakeAmplitude();
	if(flAmp>0)
	{	for(int i=0; i<3;i++)
			vecShake[i] = gEngfuncs.pfnRandomFloat(-flAmp,flAmp);
	}
	return vecShake;
}

// Can Use
bool CFXScreenShake::canUse( void )
{	if(getScreenShakeAmplitude() > 0)
		return true;
	return false;
}

/*
* Radial Blur
*/

// Constructor
CFXRadialBlur::CFXRadialBlur()
{	setRadialBlurStatus(false);
	setErrorStatus(true);
	setRadialBlurAlpha(1.0);
	for(int i = 0; i < FXSTEX_MAX; i++)
		setTexture(i,0);
}

// Precache
void CFXRadialBlur::Precache( void )
{	GLuint iTex=0;
	// Radial Blur Range
	gGLTextureManager.loadBMP("Data/rad_blur_range.bmp",false, iTex);
	setTexture(FXSTEX_RADBLURRANGE,iTex);

	// Radial Blur Range Inverted
	gGLTextureManager.loadBMP("Data/rad_blur_range.bmp",false, iTex)->invertTextureColors();
	setTexture(FXSTEX_RADBLURRANGE_INV,iTex);

	// Radial Blur Texture (smaller screen copy
	setTexture(FXSTEX_SCREENTEXBLUR,gGLTextureManager.createBlankTexture(BLUR_TEX_W, BLUR_TEX_H, GL_TEXTURE_2D));

	// Original Screen Before Effects
	setTexture(FXSTEX_SCREENSAVE,gGLTextureManager.createBlankTexture(ScreenWidth, ScreenHeight, GL_TEXTURE_RECTANGLE_NV));

	// Radial Blur Screen
	setTexture(FXSTEX_SCREENSAVEBLUR,gGLTextureManager.createBlankTexture(ScreenWidth, ScreenHeight, GL_TEXTURE_RECTANGLE_NV));
}

// Think
void CFXRadialBlur::Think( void )
{	// Enable Radial Blur
	if(!getErrorStatus() && getRadialBlurStatus() && cl_radialblur_quality->value > 0)
	{	// Set Quality
		int iQuality = cl_radialblur_quality->value;
		iQuality--;
		if(iQuality < 0)
			iQuality = 0;
		else if(iQuality > 2)
			iQuality = 2;

		// Do blur
		doRadialBlur(gflRadBlurQuality[iQuality][0], gflRadBlurQuality[iQuality][1]);
	}
}

// Do Radial Blur
void CFXRadialBlur::doRadialBlur( int iAmount, float flBlurInc )
{	// Save current screen as a texture
	glBindTexture(GL_TEXTURE_RECTANGLE_NV, getTexture(FXSTEX_SCREENSAVE));
	glCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_NV, 0, 0, 0, 0, 0, ScreenWidth, ScreenHeight );

	// Init vars
	float flTexOff = 0.0;			// Starting tex offset
	float flAlphaInc = 0.9f / float(iAmount);// Alpha Inc
	float flBlurAlpha = 0.5f;		// Starting Alpha

	// Setup Ortho Projection
	setOrthogonal();
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_TEXTURE_2D);

	// Store blur texture (small)
	{	glViewport(0,0,BLUR_TEX_W,BLUR_TEX_H);

		// Draw current screen in smaller viewport
		{	glEnable(GL_TEXTURE_RECTANGLE_NV);
			// Bind Tex
			glBindTexture(GL_TEXTURE_RECTANGLE_NV,getTexture(FXSTEX_SCREENSAVE));
			// Draw Quad
			drawQuad();
			glDisable(GL_TEXTURE_RECTANGLE_NV);
		}
		
		// Bind & save
		glBindTexture(GL_TEXTURE_2D, getTexture(FXSTEX_SCREENTEXBLUR));
		glCopyTexSubImage2D( GL_TEXTURE_2D, 0, 0, 0, 0, 0, BLUR_TEX_W, BLUR_TEX_H );

		// Restore viewport
		glViewport(0 , 0, ScreenWidth , ScreenHeight);

		// Render regular image (back where we started)
		{	glEnable(GL_TEXTURE_RECTANGLE_NV);
			// Bind Tex
			glBindTexture(GL_TEXTURE_RECTANGLE_NV,getTexture(FXSTEX_SCREENSAVE));
			// Draw Quad
			drawQuad();
			glDisable(GL_TEXTURE_RECTANGLE_NV);
		}
	}

	/*
	* Start Radial Blur Effect
	*/
	// Enable blending and set blur tex
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable(GL_BLEND);
	glBindTexture(GL_TEXTURE_2D,getTexture(FXSTEX_SCREENTEXBLUR));

	flAlphaInc = flBlurAlpha / float(iAmount);

	// Render radial blur
	glBegin(GL_QUADS);
		for (int iNum = 0; iNum < iAmount; iNum++)
		{	glColor4f(1.0f, 1.0f, 1.0f, flBlurAlpha);
			glTexCoord2f(0+flTexOff,1-flTexOff);
			glVertex3f(0,0,-1);

			glTexCoord2f(0+flTexOff,0+flTexOff);
			glVertex3f(0,1,-1);

			glTexCoord2f(1-flTexOff,0+flTexOff);
			glVertex3f(1,1,-1);

			glTexCoord2f(1-flTexOff,1-flTexOff);
			glVertex3f(1,0,-1);

			flTexOff += flBlurInc;
			flBlurAlpha -= flAlphaInc;
		}
	glEnd();													// Done Drawing Quads

	/*
	* End Radial Blur Effect
	*/

	// Save current screen blur as a texture
	glDisable(GL_BLEND);
	glEnable(GL_TEXTURE_RECTANGLE_NV);
	glBindTexture(GL_TEXTURE_RECTANGLE_NV, getTexture(FXSTEX_SCREENSAVEBLUR));
	glCopyTexSubImage2D( GL_TEXTURE_RECTANGLE_NV, 0, 0, 0, 0, 0, ScreenWidth, ScreenHeight );
	
	// Draw normal screen
	{	// Bind Tex
		glBindTexture(GL_TEXTURE_RECTANGLE_NV,getTexture(FXSTEX_SCREENSAVE));
		// Draw Quad
		drawQuad();
	}

	// Draw regular screen with mask
	drawQuadRadialMultitex(getTexture(FXSTEX_SCREENSAVE), getTexture(FXSTEX_RADBLURRANGE_INV), GL_ONE_MINUS_SRC_ALPHA);
	
	// Draw radial blur
	drawQuadRadialMultitex(getTexture(FXSTEX_SCREENSAVEBLUR), getTexture(FXSTEX_RADBLURRANGE), GL_ONE);

	// Restore Model View
	setModelView();

	// Disable
	glDisable(GL_TEXTURE_RECTANGLE_NV);
	glEnable(GL_DEPTH_TEST);
	glDisable(GL_BLEND);
}

// Set Orthogonal Projection
void CFXRadialBlur::setOrthogonal( void )
{	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	glOrtho(0, 1, 1, 0, 0.1, 100);
}

// Set ModelView Projection
void CFXRadialBlur::setModelView( void )
{	glMatrixMode(GL_PROJECTION);
	glPopMatrix();

	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

// Draw quad
void CFXRadialBlur::drawQuad( void )
{	glBegin(GL_QUADS);
		glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		glTexCoord2f(0,ScreenHeight);
		glVertex3f(0,0,-1);

		glTexCoord2f(0,0);
		glVertex3f(0,1,-1);

		glTexCoord2f(ScreenWidth,0);
		glVertex3f(1,1,-1);

		glTexCoord2f(ScreenWidth,ScreenHeight);
		glVertex3f(1,0,-1);
	glEnd();
}

// Draw Radial Blur Multitextured quad
void CFXRadialBlur::drawQuadRadialMultitex( GLuint iFirstTex, GLuint iSecondTex, GLenum dfactor)
{	glActiveTextureARB( GL_TEXTURE0_ARB );
	glEnable(GL_TEXTURE_RECTANGLE_NV);
	glBindTexture(GL_TEXTURE_RECTANGLE_NV,iFirstTex);
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

	glActiveTextureARB( GL_TEXTURE1_ARB );
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,iSecondTex);
	glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
	glActiveTextureARB( GL_TEXTURE0_ARB );
	
	// Set Blending Mode
	glBlendFunc(GL_SRC_ALPHA, dfactor);
	glEnable(GL_BLEND);

	// RENDER
	glBegin(GL_QUADS);											// Begin Drawing Quads
		glColor4f(1.0f, 1.0f, 1.0f, getRadialBlurAlpha());
		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, ScreenHeight);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 1);
		glVertex3f(0,0,-1);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, 0, 0);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 0, 0);
		glVertex3f(0,1,-1);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ScreenWidth, 0);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 0);
		glVertex3f(1,1,-1);

		glMultiTexCoord2fARB(GL_TEXTURE0_ARB, ScreenWidth, ScreenHeight);
		glMultiTexCoord2fARB(GL_TEXTURE1_ARB, 1, 1);
		glVertex3f(1,0,-1);
	glEnd();

	// Disable
	glActiveTextureARB( GL_TEXTURE1_ARB );
	glDisable(GL_TEXTURE_2D);
	glActiveTextureARB( GL_TEXTURE0_ARB );
	glDisable(GL_TEXTURE_RECTANGLE_NV);
}