//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_BillboardPlant.h
//
// Copyright (c) 2002 Rebellion. All rights reserved.
//
// Author: Andrew Weinkove - 11/12/02
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////
#include "Core/Asura.h"

#ifdef ASURA_USE_BILLBOARD_PLANTS

#include "SpecialFX/Asura_BillboardPlant_Batch.h"
// Independent includes.
#include "Camera/Asura_Camera.h"
#include "Core/Asura_TextureManagement.h"
#include "SpecialFX/Asura_BillboardPlant_TemplateStore.h"
// Platform includes.
#include "Core/Asura_PS2_DMA.h"
#include "Rendering/Asura_PS2_VU1.h"
#include "Rendering/Asura_PS2_GS.h"
#include "Rendering/Asura_PlatformTextureManagement.h"
#include "Rendering/Asura_PlatformRenderStates.h"
#include "Rendering/Asura_PS2_GlassDefines.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_RenderingOptions.h"
#include "Rendering/Asura_PlatformRendering.h"

extern u_int VU1_GlassRender_BeginBatch[] __attribute__(( section(".vudata") ));

DEBUGVAR bool dbg_bDisablePlantRendering = false;
DEBUGVAR int dbg_iBillboardPlantAlphaLimit = 10; // Seems to look better with very low alpha limit.
DEBUGVAR bool dbg_bBillboardPlantAlphaTest = true;
DEBUGVAR bool dbg_bBillboardPlantTextureClamp = true;
DEBUGVAR Asura_Vector_3 dbg_axBillboardPlantLightDirections[4] = 
{
	Asura_Vector_3(0.0f, 1.0f, 0.0f),
	Asura_Vector_3(1.0f, 0.0f, 0.0f),
	Asura_Vector_3(-0.7071f, 0.0f, 0.7071f),
	Asura_Vector_3(-0.7071f, 0.0f, -0.7071f),
};
void Asura_BillboardPlant_Batch::Platform_Render(unsigned uCamVisFlags)
{
	if (dbg_bDisablePlantRendering ||
		!Asura_PS2_RenderingOptions::GetOption(Asura_PS2_RenderingOptions::OPTION_DRAW_GLASS))
	{
		return;
	}
	Asura_BillboardPlant_Template *pxTemplate = Asura_BillboardPlant_TemplateStore::GetTemplate(m_iPlantIndex);
	Asura_Assert(pxTemplate, "");
	if (!pxTemplate)
	{
		return;
	}
	// UVs are all the same - set them up here.
	float afU[4];
	float afV[4];
	for (int i=0;i<4;i++)
	{
		afU[i] = (i % 2) ? pxTemplate->m_fBottomRightU : pxTemplate->m_fTopLeftU;
		afV[i] = (i < 2) ? pxTemplate->m_fTopLeftV : pxTemplate->m_fBottomRightV;
	}
	Asura_Vector_3 xNorm(0.0f, -1.0f, 0.0f);
	u_int uPlantColour = 0xffffffff;
	// Load the Glass Renderer
	Asura_PS2_VU1::LoadRenderer( Asura_PS2_VU1::RENDERER_GLASS );

	const bool bIsTextured = (pxTemplate->m_iTextureIndex != ASURA_TEXTURE_NONE);
	if ( bIsTextured )
	{
	    // The glass is textured, so setup the buffer layout
		Asura_PS2_TextureManagement::SetBufferLayout( ASURA_PS2_TEXBFRLAYOUT_2_LARGE );

		// Upload the texture
		bool bTextureUploaded = Asura_PS2_TextureManagement::UploadTexture( pxTemplate->m_iTextureIndex );
		
		// And setup the texture environment
        Asura_PS2_TextureManagement::SetupTextureEnvironment( bTextureUploaded,
                                                              ASURA_PS2_TEXREGFLAG_ALL,
                                                              pxTemplate->m_iTextureIndex ,
                                                              Asura_PS2_TextureManagement::BFR_AUTO,
                                                              SCE_GS_LINEAR,
                                                              SCE_GS_LINEAR_MIPMAP_NEAREST );
	}

	// Setup the render state
    Asura_PlatformRenderStates::CreateDefaultRenderStatePacket(Asura_PlatformRenderStates::CONTEXT_1);
	Asura_PlatformRenderStates::SetTextureClamp( dbg_bBillboardPlantTextureClamp );
	Asura_PlatformRenderStates::SetPixelTest(dbg_bBillboardPlantAlphaTest, true, dbg_iBillboardPlantAlphaLimit);
    Asura_PlatformRenderStates::ApplyRenderStates();

    // How many batches of plants are we going to up load ?
	const u_int uNumberOfPlantTris = (m_uNumberOfPlants * 2);
	const u_int uMaxTrisPerBatch = ASURA_PS2_GLASS_MAX_TRIS_PER_BATCH;
    const u_int uBatchCount = (uNumberOfPlantTris + (uMaxTrisPerBatch - 1)) / uMaxTrisPerBatch;

    u_int uNumActiveCams = 0;
    for( int iCam = 0; iCam < Asura_Camera::GetNumberOfCameras(); ++iCam )
    {
        if( uCamVisFlags & (1 << iCam) ) { ++uNumActiveCams; }
    }

    const u_int uSetupQWs = 1                                                 // GIF tag.
                      + uNumActiveCams * ASURA_PS2_GLASS_NUM_QWS_PER_CAM; // Cam data.

    const u_int uPerBatchOverhead = 1  // UNPACK at start and then ITOP, MSCAL and either NOP or FLUSH at the end.
                            + 1; // Drawing GIF tag

    const u_int uTotalDmaQWs = uSetupQWs
                             + 1                                                          // MSCAL for setup microprogram and input buffer setup.
                             + 1 + ASURA_PS2_GLASS_NUM_QWS_LIGHT_DATA                     // Lighting data (and its UNPACK.)
                             + uBatchCount * uPerBatchOverhead
                             + uNumberOfPlantTris * ASURA_PS2_GLASS_NUM_QWS_PER_TRI; // plant tri data.

    const u_int uTotalQWsOfVifBuffer = 1 + uTotalDmaQWs;


    Asura_PS2_QW* const pxBase = Asura_PS2_DeferredRendering::GetVifBuffer( uTotalQWsOfVifBuffer );
    Asura_PS2_QW* pxOut = pxBase;

    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uTotalDmaQWs,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_STCYCL(1, 1, false),
                                                         SCE_VIF1_SET_UNPACK(0, uSetupQWs, CMD_V4_32, 0) );

    (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( 2,                   // NLOOP
                                                         false,               // EOP
                                                         false,               // PRE
                                                         0,                   // PRIM
                                                         SCE_GIF_PACKED,      // FLG
                                                         1,                   // NREG
                                                         SCE_GIF_PACKED_AD ); // REG0


    for( int iCam = 0; iCam < Asura_Camera::GetNumberOfCameras(); ++iCam )
    {
        if( !(uCamVisFlags & (1 << iCam)) ) { continue; }

        Asura_Camera::SetCameraIndex( iCam );

        sceVu0CopyMatrix( &pxOut->Vu0FVec, Asura_PS2_VU1::GetWorldToScreenMat() );
        pxOut += 4;
		// Since we're not clipping, we can send the guardband version of the
		// world->clip matrix. It's possible larger shards will occasionally
		// need clipping for absolute correctness; using the larger clipping
		// bounds here will make that a bit less obvious.
		sceVu0CopyMatrix( &pxOut->Vu0FVec, Asura_PS2_VU1::GetWorldToClipMatGuardband() );
		pxOut += 4;
		// And we can just skip writing the clip->screen matrix.
		pxOut += 4;

        pxOut = Asura_PS2_GS::GetViewportSetupGifCmds( iCam, Asura_PS2_GS::CONTEXT_1, pxOut );

        const Asura_Vector_3* pxCamPos = Asura_Camera::GetPosition();
        pxOut->f32s[0] = pxCamPos->x;
        pxOut->f32s[1] = pxCamPos->y;
        pxOut->f32s[2] = pxCamPos->z;
        ++pxOut;

        (pxOut++)->u128 = 0;
        (pxOut++)->u128 = 0;
    }

    // Unpack for light data. Just stick it in input buffer 0.
    for( u_int u = 0; u < 3; ++u ) { pxOut->u32s[u] = SCE_VIF1_SET_NOP( false ); }
    (pxOut++)->u32s[3] = SCE_VIF1_SET_UNPACK( ASURA_PS2_GLASS_ADDR_INPUT_BUFFER_0, ASURA_PS2_GLASS_NUM_QWS_LIGHT_DATA, Asura_PS2_VU1::VIFPK_V4_32, false );

    // Leave space for the light data to be added at the end.
    Asura_PS2_QW* const pxLightData = pxOut;
    pxOut += ASURA_PS2_GLASS_NUM_QWS_LIGHT_DATA;

    // Set the camera quantity, start the setup microcode and setup the
    // input buffer layout.
    pxOut->u32s[0] = SCE_VIF1_SET_ITOP( uNumActiveCams, false );
    pxOut->u32s[1] = SCE_VIF1_SET_MSCAL( 0, false );
    pxOut->u32s[2] = SCE_VIF1_SET_BASE( ASURA_PS2_GLASS_ADDR_INPUT_BUFFER_0, false );
    pxOut->u32s[3] = SCE_VIF1_SET_OFFSET( ASURA_PS2_GLASS_SIZE_INPUT_BUFFER, false );
    ++pxOut;

    // Make the batches equal size
    u_int uPlantTrisPerBatch = uNumberOfPlantTris / uBatchCount;

    // Integer division rounds down so it's possible we won't get all the
    // plant tris uploaded in the expect number of batches. Solution: increase
    // the tris-per-batch count by 1.
    if( uPlantTrisPerBatch < uMaxTrisPerBatch )
    {
        ++uPlantTrisPerBatch;
    }

    // Keep track of how many plant tris remain.
    u_int uPlantTrisRemaining = uNumberOfPlantTris;

    Asura_PS2_GifTag xDrawGIFTag;

    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_TRISTRIP, // PRIM
                                             1,                    // IIP
                                             bIsTextured,			// TME
                                             false,                // FGE
                                             true,                 // ABE
                                             false,                // AA1
                                             0,                    // FST
                                             0,                    // CTXT
                                             0 );                  // FIX

        xDrawGIFTag.Set( 0,              // NLOOP (setup per batch below)
                         true,           // EOP
                         true,           // PRE
                         uPRIM,          // PRIM
                         SCE_GIF_PACKED, // FLG
                         9,              // NREG
                         SCE_GS_ST,      // REG0
                         SCE_GS_RGBAQ,   // REG1
                         SCE_GS_XYZ2,    // REG2
                         SCE_GS_ST,      // REG3
                         SCE_GS_RGBAQ,   // REG4
                         SCE_GS_XYZ2,    // REG5
                         SCE_GS_ST,      // REG6
                         SCE_GS_RGBAQ,   // REG7
                         SCE_GS_XYZ2 );  // REG8
    }

	u_int uCounter = 0;
    while( uPlantTrisRemaining )
    {
        // Work out how many plant tris we are sending in this batch.
        u_int uPlantTrisInThisBatch = ( uPlantTrisRemaining < uMaxTrisPerBatch ? uPlantTrisRemaining : uPlantTrisPerBatch );
        uPlantTrisRemaining -= uPlantTrisInThisBatch;

        const u_int uUnpackQWs = 1 + (ASURA_PS2_GLASS_NUM_QWS_PER_TRI * uPlantTrisInThisBatch);
        pxOut->u32s[0] = SCE_VIF1_SET_UNPACKR( 0, uUnpackQWs, CMD_V4_32, false ); 

        xDrawGIFTag.uNLOOP = uPlantTrisInThisBatch;
        
        {
            const u_int* puSrc  = reinterpret_cast<const u_int*>( &xDrawGIFTag );
            for( u_int u = 1; u < 5; ++u ) { pxOut->u32s[u] = *puSrc++; }
            ++pxOut;
        }

        // Add each plant tri.
		Asura_Vector_3 axVertices[4];
		FillVertices(axVertices, uCounter / 2);
        for( u_int u = uPlantTrisInThisBatch; u--; )
        {
			if (!(uCounter % 2))
			{
				FillVertices(axVertices, uCounter / 2);
			}
            // Calculate a normal for this shard.
            pxOut->f32s[1] = xNorm.x;
            pxOut->f32s[2] = xNorm.y;
            pxOut->f32s[3] = xNorm.z;
            ++pxOut;
            pxOut->f32s[0] = 0.0f; // Normal's w is specular scale.
            
            const float fColourRange = 0.5f / 255.0f;
            pxOut->f32s[1] = ((uPlantColour >> 16) & 0xFF) * fColourRange; // R
            pxOut->f32s[2] = ((uPlantColour >>  8) & 0xFF) * fColourRange; // G
            pxOut->f32s[3] = ((uPlantColour >>  0) & 0xFF) * fColourRange; // B
            ++pxOut;
            pxOut->f32s[0] = (((uPlantColour >> 24) & 0xFF) + 1) * fColourRange; // A
            
            for( u_int uVtx = 0; uVtx < 3; ++uVtx )
            {
				u_int uIndex = uVtx;
				if (uCounter % 2 &&
					uVtx == 0)
				{
					uIndex = 3;
				}
                pxOut->f32s[1] = axVertices[uIndex].x;
                pxOut->f32s[2] = axVertices[uIndex].y;
                pxOut->f32s[3] = axVertices[uIndex].z;
                ++pxOut;
                pxOut->u32s[0] = ( 2 == uIndex ? 0 : 0x8000 );
                pxOut->f32s[1] = afU[uIndex];
                pxOut->f32s[2] = afV[uIndex];
                pxOut->f32s[3] = 1.0f;
                ++pxOut;
            }

			uCounter++;
        }
    
	    pxOut->u32s[1] = SCE_VIF1_SET_ITOP( 3 | ASURA_PS2_GLASS_FLAG_NO_CLIPPING_REQD, false );
	    pxOut->u32s[2] = SCE_VIF1_SET_MSCAL( reinterpret_cast<u_int>(VU1_GlassRender_BeginBatch) >> 3, false );
		// Flush after the final batch to make sure it's finished rendering.
		pxOut->u32s[3] = ( uPlantTrisRemaining ? SCE_VIF1_SET_NOP(false) : SCE_VIF1_SET_FLUSH(false) );
		++pxOut;
    }

    // Assert here rather than after filling in the light data since the ptrs
    // will be the same and this will keep us in scope.
    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWsOfVifBuffer, "" );

    // Finally fill in the light data.
	// (AW - 10.02.2005) - REALLY HACKY!
	{
		Asura_PS2_QW* const pxOut = pxLightData;
    
		// Now loop over the lights and fill in the required data.
		for( u_int u = 0; u < 4; ++u )
		{
			pxOut[0].f32s[u] = dbg_axBillboardPlantLightDirections[u].x;
			pxOut[1].f32s[u] = dbg_axBillboardPlantLightDirections[u].y;
			pxOut[2].f32s[u] = dbg_axBillboardPlantLightDirections[u].z;

			pxOut[3 + u].f32s[0] = m_xLighting.m_fR;
			pxOut[3 + u].f32s[1] = m_xLighting.m_fG;
			pxOut[3 + u].f32s[2] = m_xLighting.m_fB;
			pxOut[3 + u].f32s[3] = 0.0f;
		}
	}

    Asura_PS2_Render::UpdateTriStatsFromVisFlags( uNumberOfPlantTris, 1, uCamVisFlags );
}

#endif // #ifdef ASURA_USE_BILLBOARD_PLANTS
