//
// Asura_PSP_Water.cpp
//
// Created by JG - 02/11/2005 14:31:19
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core\Asura.h"

// This file's header
#include "Rendering\Asura_PSP_Water.h"

// Sony libraries
#include "kernel.h"

// Asura independent renderer
#include "Rendering\Asura_Render.h"

// PSP headers
#include "Rendering/Asura_PSP_GU.h"
#include "Rendering/Asura_PSP_Stencil_Enums.h"
#include "Rendering/Asura_PSP_StencilBufferManager.h"

typedef struct 
{
	float			u1, v1; 
	float			x1, y1, z1; 
	float			u2, v2; 
	float			x2, y2, z2; 
	float			u3, v3; 
	float			x3, y3, z3; 

} MORPH; 

typedef struct 
{
	float			u, v; 
	unsigned int	color;
	float			x, y, z; 

} VERTEX; 

typedef struct
{
	float			x, y; 

} NOISE; 

const unsigned int cuRows = 10; 
const unsigned int cuColumns = 10; 
const unsigned int cuHorizontal = 480; 
const unsigned int cuVertical = 272; 
const float		   cfDetailRatio = 50.0f; 

MORPH axMesh[cuRows][2*cuColumns];
NOISE axNoise[cuRows][cuColumns];

float fMorphing = 1.0f; 

DEBUGVAR unsigned int dbg_uWidthWater = 256;
unsigned int uHeightWater = dbg_uWidthWater / 2;
unsigned int uSizeWater = dbg_uWidthWater*uHeightWater*4;

DEBUGVAR unsigned int dbg_uNoiseRatioH = 12;
DEBUGVAR unsigned int dbg_uNoiseRatioV = 4;

DEBUGVAR bool dbg_bShift = false; 
DEBUGVAR unsigned int dbg_uShoreColour = 0x00ffffff; 
DEBUGVAR unsigned int dbg_uAlpha = 255; 

const unsigned int uNumberFrames = 20; 
unsigned int uAnimation[uNumberFrames][2] = {
	{1, 1}, {1, 1}, {1, 1}, {2, 2}, {2, 2}, {2, 2}, {1, 1}, {1, 1}, {0, 0}, {0, 0},
	{0, 0}, {0, 0}, {0, 0}, {-1, -1}, {-1, -1}, {-1, -1}, {0, 0}, {0, 0}, {0, 0}, {1, 1}};
unsigned int uDirection[uNumberFrames][2] = {
	{1, 1}, {-1, 0}, {1, -1}, {0, -1}, {1, -1}, {1, 0}, {-1, 1}, {-1, -1}, {0, 1}, {1, -1},
	{-1, 0}, {1, 1}, {0, 1}, {1, -1}, {0, 1}, {-1, -1}, {1, 1}, {0, 1}, {1, -1}, {0, -1}};

unsigned int* uOutBufferReadWater = NULL; 
unsigned int* uOutBufferWriteWater = NULL; 
unsigned int* uFilterBufferReadWater = NULL; 
unsigned int* uFilterBufferWriteWater = NULL; 

// Static members
u_char Asura_PSP_Water::s_ucWaterOpacity = 0xFF;
u_char Asura_PSP_Water::s_ucWaterReflectivity = 0x40;

void Asura_PSP_Water::Initialise( void )
{
}

void Asura_PSP_Water::Render( const char* acDisplayList, const unsigned int uCurrentRenderingBuffer )
{
	// Initialise the mesh
	static bool bInitialised = false; 
	if (!bInitialised)
	{
		// Mesh
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 0; j < cuColumns; j++)
			{
				float v1 = (i / (float) cuRows);
				float u1 = (j / (float) (cuColumns-1)); 

				axMesh[i][2*j].u1 = u1; 
				axMesh[i][2*j].v1 = v1; 
				axMesh[i][2*j].x1 = u1 * dbg_uWidthWater; 
				axMesh[i][2*j].y1 = v1 * uHeightWater; 
				axMesh[i][2*j].z1 = 10.0f; 

				axMesh[i][2*j].u2 = axMesh[i][2*j].u1;
				axMesh[i][2*j].v2 = axMesh[i][2*j].v1;
				axMesh[i][2*j].x2 = axMesh[i][2*j].x1;
				axMesh[i][2*j].y2 = axMesh[i][2*j].y1;
				axMesh[i][2*j].z2 = axMesh[i][2*j].z1;

				axMesh[i][2*j].u3 = axMesh[i][2*j].u1;
				axMesh[i][2*j].v3 = axMesh[i][2*j].v1;
				axMesh[i][2*j].x3 = axMesh[i][2*j].x1;
				axMesh[i][2*j].y3 = axMesh[i][2*j].y1;
				axMesh[i][2*j].z3 = axMesh[i][2*j].z1;

				float v2 = ((i+1) / (float) cuRows);
				float u2 = (j / (float) (cuColumns-1)); 

				axMesh[i][2*j+1].u1 = u2; 
				axMesh[i][2*j+1].v1 = v2;
				axMesh[i][2*j+1].x1 = u2 * dbg_uWidthWater; 
				axMesh[i][2*j+1].y1 = v2 * uHeightWater;  
				axMesh[i][2*j+1].z1 = 10.0f; 

				axMesh[i][2*j+1].u2 = axMesh[i][2*j+1].u1;
				axMesh[i][2*j+1].v2 = axMesh[i][2*j+1].v1;
				axMesh[i][2*j+1].x2 = axMesh[i][2*j+1].x1;
				axMesh[i][2*j+1].y2 = axMesh[i][2*j+1].y1;
				axMesh[i][2*j+1].z2 = axMesh[i][2*j+1].z1;

				axMesh[i][2*j+1].u3 = axMesh[i][2*j+1].u1;
				axMesh[i][2*j+1].v3 = axMesh[i][2*j+1].v1;
				axMesh[i][2*j+1].x3 = axMesh[i][2*j+1].x1;
				axMesh[i][2*j+1].y3 = axMesh[i][2*j+1].y1;
				axMesh[i][2*j+1].z3 = axMesh[i][2*j+1].z1;

			}

		}
		
		// Noise
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 0; j < cuColumns; j++)
			{
				float fNoiseStrengh = i / (float)(cuRows - 1);  
				axNoise[i][j].x = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioH) / dbg_uWidthWater); 
				axNoise[i][j].y = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioV) / uHeightWater);
			}
		}

		// Update mesh (first morphing mesh)
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 1; j < cuColumns-1; j++)
			{

				axMesh[i][2*j].u1 += axNoise[i][j].x; 
				axMesh[i][2*j].v1 += axNoise[i][j].y; 

				if (i < cuRows-1) 
				{
					axMesh[i][2*j+1].u1 += axNoise[i+1][j].x; 
					axMesh[i][2*j+1].v1 += axNoise[i+1][j].y; 
				}
			}
		}
		
		// Noise
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 0; j < cuColumns; j++)
			{
				float fNoiseStrengh = i / (float)(cuRows - 1);  
				axNoise[i][j].x = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioH) / dbg_uWidthWater); 
				axNoise[i][j].y = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioV) / uHeightWater);
			}
		}

		// Update mesh (second morphing mesh)
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 1; j < cuColumns-1; j++)
			{

				axMesh[i][2*j].u2 += axNoise[i][j].x; 
				axMesh[i][2*j].v2 += axNoise[i][j].y; 

				if (i < cuRows-1) 
				{
					axMesh[i][2*j+1].u2 += axNoise[i+1][j].x; 
					axMesh[i][2*j+1].v2 += axNoise[i+1][j].y; 
				}
			}
		}

		// Noise
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 0; j < cuColumns; j++)
			{
				float fNoiseStrengh = i / (float)(cuRows - 1);  
				axNoise[i][j].x = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioH) / dbg_uWidthWater); 
				axNoise[i][j].y = fNoiseStrengh * ((((rand() / (float) (RAND_MAX)) * 2.0f - 1.0f) * dbg_uNoiseRatioV) / uHeightWater);
			}
		}

		// Update mesh (third morphing mesh)
		for (int i = 0; i < cuRows; i++)
		{
			for (int j = 1; j < cuColumns-1; j++)
			{

				axMesh[i][2*j].u3 += axNoise[i][j].x; 
				axMesh[i][2*j].v3 += axNoise[i][j].y; 

				if (i < cuRows-1) 
				{
					axMesh[i][2*j+1].u3 += axNoise[i+1][j].x; 
					axMesh[i][2*j+1].v3 += axNoise[i+1][j].y; 
				}
			}
		}

		for (int w = 0; w < 8; w++)
		{
			GU_MorphWeight(w, 0.0f); 
		}

		// Flush the data write cache
		sceKernelDcacheWritebackAll();

		bInitialised = true; 

	}

	static float fInc = 0.05f; 
	fMorphing += fInc;
	if (fMorphing > 3.0f)
	{
		fMorphing = 0.0f;
	}
	else
	if (fMorphing < 0.0f)
	{
		fMorphing = 0.0f;
	}


	if (fMorphing < 1.0f)
	{
		GU_MorphWeight(0, 1.0f - fMorphing); 
		GU_MorphWeight(1, fMorphing); 
		GU_MorphWeight(2, 0.0f); 
	}
	else
	if (fMorphing < 2.0f)
	{
		GU_MorphWeight(0, 0.0f); 
		GU_MorphWeight(1, 2.0f - fMorphing); 
		GU_MorphWeight(2, fMorphing - 1.0f); 
	}
	else
	{
		GU_MorphWeight(0, fMorphing - 2.0f); 
		GU_MorphWeight(1, 0.0f); 
		GU_MorphWeight(2, 3.0f - fMorphing); 
	}


	// Get a couple of pointers to the back buffer, one to read from it, another to write on it
	unsigned int* pReadBackBufferWater = (unsigned int*)((unsigned int)sceGeEdramGetAddr() + uCurrentRenderingBuffer);
	unsigned int* pWriteBackBufferWater = (unsigned int*)(uCurrentRenderingBuffer);
	
	// Pointer to intermediate buffers

	// Read and write access to down copy of the back buffer
	uOutBufferWriteWater	= (unsigned int*)((unsigned int)SCEGU_VRAM_BP32_2 + SCEGU_VRAM_BUFSIZE32); 
	uOutBufferReadWater	= (unsigned int*)((unsigned int)sceGeEdramGetAddr() + (unsigned int)SCEGU_VRAM_BP32_2 + SCEGU_VRAM_BUFSIZE32); 	

	// Read and write access to the filtered buffer
	uFilterBufferWriteWater	= (unsigned int*)((unsigned int)SCEGU_VRAM_BP32_2 + SCEGU_VRAM_BUFSIZE32 + uSizeWater); 
	uFilterBufferReadWater	= (unsigned int*)((unsigned int)sceGeEdramGetAddr() + (unsigned int)SCEGU_VRAM_BP32_2 + SCEGU_VRAM_BUFSIZE32 + uSizeWater); 	

	// Draw the current frame buffer into our intermediate buffer
	unsigned int uCurrentWidthWater = dbg_uWidthWater; 
	unsigned int uCurrentHeightWater = dbg_uWidthWater / 2; 

	GU_DrawBuffer(SCEGU_PF8888, (void*)uOutBufferWriteWater, uCurrentWidthWater);
	GU_Offset(0, 0);
	GU_Viewport(dbg_uWidthWater / 2, uHeightWater / 2, dbg_uWidthWater, uHeightWater);

	// Clear the destination buffer
	GU_ClearColor(dbg_uShoreColour);
	Asura_PSP_StencilBufferManager::SetStencilBufferClearBits(dbg_uAlpha);
	GU_Clear(SCEGU_CLEAR_COLOR | SCEGU_CLEAR_STENCIL); 

	// Texture, no depth test and no blending
	GU_Enable(SCEGU_TEXTURE); 
	GU_Disable(SCEGU_DEPTH_TEST); 
	GU_Disable(SCEGU_BLEND);
	Asura_PSP_StencilBufferManager::SetStencilEnabled(false);
	GU_Disable(SCEGU_ALPHA_TEST);
	
	// Mask this pass to draw only the water
	//GU_Enable(SCEGU_ALPHA_TEST); 
	//GU_AlphaFunc(SCEGU_EQUAL, dbg_cWaterReflectivity, 0xff); 

	// Copy our back buffer down into the intermediate buffer
	GU_TexImage(0,512,512,512,((char*)pReadBackBufferWater));

	GU_TexMode(SCEGU_PF8888, 0, SCEGU_SINGLE_CLUT, SCEGU_TEXBUF_NORMAL);
	GU_TexFilter(SCEGU_LINEAR, SCEGU_LINEAR);
	GU_TexWrap(SCEGU_CLAMP, SCEGU_CLAMP); 
	GU_TexFlush();

	GU_SpriteMode(480, 272, uCurrentWidthWater, uCurrentHeightWater);

	if (dbg_bShift)
	{
		static unsigned int uFrame = 0; 
		GU_DrawSprite( uDirection[uFrame/10][0] * uAnimation[uFrame%10][0], uDirection[uFrame/10][1] * uAnimation[uFrame%10][1], 0, 0, 0, SCEGU_NOFLIP, SCEGU_NOROTATE ); 
		uFrame = (uFrame + 1) % (10 * uNumberFrames); 	
	}
	else
	{
		GU_DrawSprite( 0, 0, 0, 0, 0, SCEGU_NOFLIP, SCEGU_NOROTATE ); 
	}

	// Copy our intermediate buffer onto the filter buffer
	GU_DrawBuffer(SCEGU_PF8888, (void*)uFilterBufferWriteWater, uCurrentWidthWater);
	GU_Offset(0, 0);
	GU_Viewport(uCurrentWidthWater / 2, uCurrentHeightWater / 2, uCurrentWidthWater, uCurrentHeightWater);

	GU_TexImage(0,uCurrentWidthWater,uCurrentHeightWater,uCurrentWidthWater,uOutBufferReadWater);
	GU_TexMode(SCEGU_PF8888, 0, SCEGU_SINGLE_CLUT, SCEGU_TEXBUF_NORMAL);
	GU_TexFilter(SCEGU_LINEAR, SCEGU_LINEAR);
	GU_TexFlush();

	//GU_Disable(SCEGU_ALPHA_TEST); 

	// Test: just a copy
	//GU_SpriteMode( uCurrentWidthWater, uCurrentHeightWater, uCurrentWidthWater, uCurrentHeightWater);
	//GU_DrawSprite( 0, 0, 0, 0, 0, SCEGU_NOFLIP, SCEGU_NOROTATE ); 

	// Orthographic projection
	GUM_MatrixMode(SCEGU_MATRIX_WORLD); 
	GUM_PushMatrix(); 
	GUM_LoadIdentity(); 
	GUM_MatrixMode(SCEGU_MATRIX_VIEW); 
	GUM_PushMatrix(); 
	GUM_LoadIdentity(); 
	GUM_MatrixMode(SCEGU_MATRIX_PROJECTION); 
	GUM_PushMatrix(); 
	GUM_LoadIdentity(); 
	GUM_Ortho(0, dbg_uWidthWater, uHeightWater, 0, -10.0f, 10.0f); 
	GUM_UpdateMatrix();

	// Draw the deformation mesh 
	for (int i = 0; i < cuRows; i++)
	{
		GU_DrawArray( SCEGU_PRIM_TRIANGLE_STRIP, SCEGU_VERTEX_3 | SCEGU_TEXTURE_FLOAT | SCEGU_VERTEX_FLOAT, 2*cuColumns, NULL, axMesh[i]); 
	}


	// Back to previous projection
	GUM_MatrixMode(SCEGU_MATRIX_WORLD); 
	GUM_PopMatrix(); 
	GUM_MatrixMode(SCEGU_MATRIX_VIEW); 
	GUM_PopMatrix(); 
	GUM_MatrixMode(SCEGU_MATRIX_PROJECTION); 
	GUM_PopMatrix(); 
	GUM_UpdateMatrix();

	// Draw the full screen pass onto the back buffer
	GU_DrawBuffer(SCEGU_PF8888, pWriteBackBufferWater, SCEGU_VRAM_WIDTH);
	GU_Offset(SCEGU_SCR_OFFSETX, SCEGU_SCR_OFFSETY);
	GU_Viewport(2048, 2048, SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT);
	GU_Scissor(0, 0, SCEGU_SCR_WIDTH, SCEGU_SCR_HEIGHT);

	// Mask this pass to draw only on top of water
	Asura_PSP_StencilBufferManager::SetStencilOperation(ASURA_PSP_STENCIL_OPERATION_KEEP, ASURA_PSP_STENCIL_OPERATION_KEEP, ASURA_PSP_STENCIL_OPERATION_KEEP);
	Asura_PSP_StencilBufferManager::SetStencilFunction(ASURA_PSP_STENCIL_FUNCTION_EQUAL, ASURA_PSP_STENCIL_WATER, 0xFF);
	Asura_PSP_StencilBufferManager::SetStencilEnabled(true);

	// Test: draw the buffer without filtering
	//GU_TexImage(0,uCurrentWidthWater,uCurrentHeightWater,uCurrentWidthWater,uOutBufferReadWater);
	
	GU_TexImage(0,uCurrentWidthWater,uCurrentHeightWater,uCurrentWidthWater,uFilterBufferReadWater);

	GU_TexMode(SCEGU_PF8888, 0, SCEGU_SINGLE_CLUT, SCEGU_TEXBUF_NORMAL);
	GU_TexFilter(SCEGU_LINEAR, SCEGU_LINEAR);
	GU_TexFlush();
	
	// Blend the water effect back into the frame buffer
	//GU_Disable(SCEGU_BLEND);

	GU_Enable(SCEGU_BLEND); 
	GU_BlendFunc(SCEGU_ADD, SCEGU_SRC_ALPHA, SCEGU_ONE_MINUS_SRC_ALPHA, 0, 0); 
	//GU_BlendFunc(SCEGU_ADD, SCEGU_SRC_ALPHA, SCEGU_FIX_VALUE, 0, 0xffffffff); 

	GU_Disable(SCEGU_COLOR_TEST); 
	GU_Enable(SCEGU_TEXTURE); 
	GU_Disable(SCEGU_DEPTH_TEST); 
	GU_Disable(SCEGU_ALPHA_TEST); 

	GU_SpriteMode( uCurrentWidthWater, uCurrentHeightWater, 480, 272 );
	GU_DrawSprite( 0, 0, 0, 0, 0, SCEGU_NOFLIP, SCEGU_NOROTATE ); 
	
	// Go back to previous texture filter 
	GU_TexFilter(SCEGU_LINEAR_MIPMAP_LINEAR, SCEGU_LINEAR);
	
	// Mask everything render from now as non glowing surfaces
	Asura_PSP_StencilBufferManager::SetStencilOperation(ASURA_PSP_STENCIL_OPERATION_KEEP, ASURA_PSP_STENCIL_OPERATION_KEEP, ASURA_PSP_STENCIL_OPERATION_REPLACE);
	Asura_PSP_StencilBufferManager::SetStencilFunction(ASURA_PSP_STENCIL_FUNCTION_ALWAYS, ASURA_PSP_STENCIL_NONE, 0xFF);
	
	GU_AlphaFunc(SCEGU_GREATER, 0x04, 0xff);

	Asura_Render::CurrentStates.ResetAll(); 

	//GU_Finish();

}
