//////////////////////////////////////////////////////////////////////////////
//
// Asura_PSP_Skybox.cpp
//
// Copyright (c) 2005 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header
#include "SpecialFX/Asura_PSP_Skybox.h"

// Asura headers
#include "Core/Asura_TextureManagement.h"
#include "Rendering/Asura_PSP_Graphics.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/Asura_Skybox.h"

// PSP headers
#include "Rendering/Asura_PSP_TextureManagement.h"

// KJFL - 2004/04/06 16:06:06 - using the same code from the original PC version,
// which uses static arrays of cube vertices and face indices to construct the skybox.
// It would save a little memory and a small amount of loading time to precaculate this
// and write it down longhand in the final vertex array.
// KOR - (a negligible amount though...)
// The larger problem is triangles being culled out do to going outside the guard band.
// Therefore we have to subdivide. As a quick fix for the demo on 01/10/2004, render
// out quad by quad. The render should be optimised when we have time; it needs to cull
// faces outside the frustum, to avoid unnecessary texture caching, and the subdivided
// mesh should be tri-stripped. We can use the usual structure to do one strip per face
// -------
// |/|/|/|
// -------
// |\|\|\|
// -------
// |/|/|/|
// -------

#define ASURA_PSP_SKYBOX_SUBDIVISIONS (4)
#define ASURA_PSP_SKYBOX_SUBDIVISIONS_SQ (ASURA_PSP_SKYBOX_SUBDIVISIONS * ASURA_PSP_SKYBOX_SUBDIVISIONS)

static float g_afXCoords[] = {-1.0f,-1.0f,+1.0f,+1.0f,-1.0f,-1.0f,+1.0f,+1.0f};
static float g_afYCoords[] = {+1.0f,+1.0f,+1.0f,+1.0f,-1.0f,-1.0f,-1.0f,-1.0f};
static float g_afZCoords[] = {-1.0f,+1.0f,+1.0f,-1.0f,-1.0f,+1.0f,+1.0f,-1.0f};

static u_char g_aucFaceVertexIDs[6][4] =
{
	{1,0,3,2},
	{0,4,7,3},
	{2,6,5,1},
	{3,7,6,2},
	{1,5,4,0},
	{4,5,6,7}
};

// simplistic vertex we'll be using to draw the skybox
class Asura_PSP_Skybox_Vertex
{
	public:		
		float			m_fU;
		float			m_fV;
		Asura_Vector_3	m_axPosition;
};

// actual vertex data that will get sent to the GPU
// ASURA_PSP_SKYBOX_SUBDIVISIONS vertically, ASURA_PSP_SKYBOX_SUBDIVISIONS horizontally, 4 verts per quad, 6 faces
static Asura_PSP_Skybox_Vertex  g_axSkyboxVertices[ASURA_PSP_SKYBOX_SUBDIVISIONS_SQ * 4 * 6];
static int						g_aiSkyboxTextureIndex[6] = { ASURA_TEXTURE_NONE, ASURA_TEXTURE_NONE, ASURA_TEXTURE_NONE, ASURA_TEXTURE_NONE, ASURA_TEXTURE_NONE, ASURA_TEXTURE_NONE };
static u_int					g_uColour;

void Asura_PSP_Skybox::Initialise() 
{}
void Asura_PSP_Skybox::Reset() 
{
	for( u_int u = 0; u < 6; ++u ) 
	{ 
		g_aiSkyboxTextureIndex[u] = ASURA_TEXTURE_NONE; 
	}
}
void Asura_PSP_Skybox::Shutdown() 
{}

void Asura_PSP_Skybox::DoRenderSetup()
{
	// cache the texture indices
	for (int iFace = 0; iFace < 6; ++iFace)
	{
		// (MH - 23.11.2005) only set texture index if texture is set
		if( !Asura_Skybox::GetTexture(iFace).IsBlank() )
		{
			g_aiSkyboxTextureIndex[iFace] = Asura_Texture_Management::AddTexture(Asura_Skybox::GetTexture(iFace));
		}
	}

	// construct the skybox vertices, using the orientation around the Y axis
	float fAngle;
	Asura_Skybox::GetOrientationAroundYAxis(fAngle);
	const float fSine = Asura_Maths::Sine(fAngle);
	const float fCosine = Asura_Maths::Cosine(fAngle);

	for (int iFace = 0; iFace < 6; ++iFace)
	{
		// Work out co-ordinates of entire face
		Asura_Vector_3 axPositions[4];
		for(int iVertex = 0; iVertex < 4; ++iVertex)
		{
			// note this will make a skybox a couple of metres across - we're not bothered about
			// the scale since the camera will always be in the centre of the skybox, and z-writes
			// will be turned off, so any size of environment will 'fit' in the skybox.
			const float fX = g_afXCoords[g_aucFaceVertexIDs[iFace][iVertex]];
			const float fZ = g_afZCoords[g_aucFaceVertexIDs[iFace][iVertex]];
			axPositions[iVertex].x = fX * fCosine + fZ * fSine;
			axPositions[iVertex].y = g_afYCoords[g_aucFaceVertexIDs[iFace][iVertex]];
			axPositions[iVertex].z = fZ * fCosine - fX * fSine;
		}

		Asura_Vector_3 xDeltaX = axPositions[1] - axPositions[2];
		Asura_Vector_3 xDeltaY = axPositions[3] - axPositions[2];

		bool bIsHalved = false;
		switch(iFace)
		{
			case Asura_Skybox::ASURA_SKYBOX_BK:
			case Asura_Skybox::ASURA_SKYBOX_FR:
				{
					bIsHalved = Asura_Skybox::GetBackTextureIsFrontUpsideDown();
				}
				break;
			case Asura_Skybox::ASURA_SKYBOX_RT:
			case Asura_Skybox::ASURA_SKYBOX_LF:
				{
					bIsHalved = Asura_Skybox::GetRightTextureIsLeftUpsideDown();
				}
				break;
		}
		
		for(u_int uX = 0; uX < ASURA_PSP_SKYBOX_SUBDIVISIONS; ++uX)
		{
			const float fLoU = static_cast<float>(uX) / ASURA_PSP_SKYBOX_SUBDIVISIONS;
			const float fHiU = static_cast<float>(uX + 1) / ASURA_PSP_SKYBOX_SUBDIVISIONS;
			const int uYSteps = bIsHalved ? ASURA_PSP_SKYBOX_SUBDIVISIONS / 2 : ASURA_PSP_SKYBOX_SUBDIVISIONS;
			for(u_int uY = 0; uY < uYSteps; ++uY)
			{
				u_int uBaseIndex = 4 * (uX + (ASURA_PSP_SKYBOX_SUBDIVISIONS * uY) + (iFace * ASURA_PSP_SKYBOX_SUBDIVISIONS_SQ));
				float fLoV = static_cast<float>(uY) / ASURA_PSP_SKYBOX_SUBDIVISIONS;
				float fHiV = static_cast<float>(uY + 1) / ASURA_PSP_SKYBOX_SUBDIVISIONS;
	
			
				g_axSkyboxVertices[0 + uBaseIndex].m_axPosition = axPositions[2] + xDeltaX * fHiU + xDeltaY * fHiV;
				g_axSkyboxVertices[1 + uBaseIndex].m_axPosition = axPositions[2] + xDeltaX * fHiU + xDeltaY * fLoV;
				g_axSkyboxVertices[2 + uBaseIndex].m_axPosition = axPositions[2] + xDeltaX * fLoU + xDeltaY * fLoV;
				g_axSkyboxVertices[3 + uBaseIndex].m_axPosition = axPositions[2] + xDeltaX * fLoU + xDeltaY * fHiV;

				if ((iFace == Asura_Skybox::ASURA_SKYBOX_BK && Asura_Skybox::GetBackTextureIsFrontUpsideDown()) ||
					(iFace == Asura_Skybox::ASURA_SKYBOX_RT && Asura_Skybox::GetRightTextureIsLeftUpsideDown()))
				{
					fLoV = 1.0f - fLoV;
					fHiV = 1.0f - fHiV;
				}


				g_axSkyboxVertices[0 + uBaseIndex].m_fU = fHiU;	g_axSkyboxVertices[0 + uBaseIndex].m_fV = fHiV;
				g_axSkyboxVertices[1 + uBaseIndex].m_fU = fHiU;	g_axSkyboxVertices[1 + uBaseIndex].m_fV = fLoV;
				g_axSkyboxVertices[2 + uBaseIndex].m_fU = fLoU;	g_axSkyboxVertices[2 + uBaseIndex].m_fV = fLoV;
				g_axSkyboxVertices[3 + uBaseIndex].m_fU = fLoU;	g_axSkyboxVertices[3 + uBaseIndex].m_fV = fHiV;
	
			}
		}
	}
	g_uColour = (static_cast<u_int>(Asura_Maths::ClampToRange(Asura_Skybox::GetR(), 0.f, 255.f)))
			  + (static_cast<u_int>(Asura_Maths::ClampToRange(Asura_Skybox::GetG(), 0.f, 255.f)) << 8)
			  + (static_cast<u_int>(Asura_Maths::ClampToRange(Asura_Skybox::GetB(), 0.f, 255.f)) << 16)
			  + 0xff000000;
}

void Asura_PSP_Skybox::Render()
{
	// No lighting
	Asura_PSP_Graphics::SetUseLighting(false);
	Asura_PSP_Graphics::SetUseColourDoubling(false);
	Asura_PSP_Graphics::SetMaterialType(ASURA_PSP_GRAPHICS_MATERIALTYPE_SKYBOX, g_uColour);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);

	// push the current world matrix onto the stack so we can mess around with it
	GUM_MatrixMode(SCEGU_MATRIX_VIEW);
	GUM_PushMatrix();

	// get the world matrix...
	Asura_PSP_Matrix4 xMatrix;
	GUM_StoreMatrix(&xMatrix);

	// zero out the translation part, so we're always in the 'centre' of world space
	// this means the skybox will always be drawn around the camera, so there is no 
	// relative movement between camera and skybox.
	xMatrix.w.x = 0.0f;
	xMatrix.w.y = 0.0f;
	xMatrix.w.z = 0.0f;

	// and give that back the GPU
	GUM_LoadMatrix(&xMatrix);

	// AJE - 12/06/2007 - ensure no UV texture offsets are applied prior to rendering skybox geometry
	GU_TexOffset(0.0f, 0.0f);

	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	// no z-buffer writing for skybox.
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

	// JG - Now that we are inverting the top bit of the skybox the winding comes all the way around depending on the camera matrix
	// So we can't use this test in skyboxes. Anyway the viewport will cull the part of the skybox behind us
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

	// Centralised state override
	Asura_PSP_Graphics::DoStateOverride();

	for (u_int uFace=0; uFace<6; uFace++)
	{
		// KJFL - 2004/04/06 09:42:26 - Possible optimisation: face culling could go here
		// JG - See comment above

		// set the texture and draw a face
        // (MH - 23.11.2005) skip if texture not set.
		int iTextureIndex = g_aiSkyboxTextureIndex[uFace];
		if (iTextureIndex < 0)
		{
			continue;
		}

		Asura_PSP_TextureManagement::RequestTextureIndex( iTextureIndex );

		// RJT NOTE: Moved here as RequestTextureIndex(...) could override it (ideally shouldn't duplicate calls though)
		// KJFL - 2004/04/06 09:35:18 - clamp texturing so that the filtering doesn't wrap round
		// to the other side of a texture - this would create seams in the skybox.
		GU_TexWrap(SCEGU_CLAMP, SCEGU_CLAMP);

		for(u_int u = 0; u < ASURA_PSP_SKYBOX_SUBDIVISIONS_SQ; ++u)
		{
			GUM_DrawArray
			(
				SCEGU_PRIM_TRIANGLE_FAN,
				SCEGU_TEXTURE_FLOAT|SCEGU_VERTEX_FLOAT,
				4,
				0,
				&g_axSkyboxVertices[4 * (u + uFace * ASURA_PSP_SKYBOX_SUBDIVISIONS_SQ)]
			);
		}
	}

	// turn texture wrapping back on
	GU_TexWrap(SCEGU_REPEAT, SCEGU_REPEAT);

	// get back the original world matrix
	GUM_PopMatrix();
}
