//////////////////////////////////////////////////////////////////////////////
//
// Asura_PS2_Shadows.cpp
//
// Copyright (c) 2003-2004 Rebellion. All rights reserved.
// Some portions based on SCEE sample.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

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

// Stdlib platform-specific.
#include <libvu0.h>
#include <libgraph.h>

// Engine independent
#include "Animation/Asura_Animation.h"
#include "Core/Asura_LOS.h"
#include "Debugging/Asura_Logfile.h"

// Engine Platform headers
#include "Core/Asura_PlatformBasics.h"
#include "Core/Asura_PS2_DMA.h"
#include "Rendering/Asura_PlatformRenderStates.h"
#include "Rendering/Asura_PS2_2D_Graphics.h"
#include "Rendering/Asura_PS2_DeferredRendering.h"
#include "Rendering/Asura_PS2_GS.h"
#include "Rendering/Asura_PS2_ShadowBuffer.h"

// G L O B A L S /////////////////////////////////////////////////////////////

//static u_int g_uBfrTBP;
static float g_fShadowSTScale;
static int   g_iShadowSTScaleFixed;

DEBUGVAR float dbg_fMaxHeightForShadowToCast   = 3.0f;
DEBUGVAR float dbg_fStartHeightOfShadowFalloff = 2.75f;

DEBUGVAR float dbg_fShadowFullAlpha = ASURA_PS2_SHADOW_ALPHA;

DEBUGVAR float dbg_fMinLightLevelForShadows = 0.1f;
DEBUGVAR float dbg_fMaxLightLevelForShadows = 1.5f;

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static void ClearAndSetupRenderToBfr( u_int uFLUSH );

static void DownsampleShadowTextureAndCopyToAlpha( u_int uVifFlush );

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

//
// Asura_PS2_Shadows.
//

//u_int Asura_PS2_Shadows::s_uBfrTBP = 0;

float Asura_PS2_Shadows::s_fShadowBackfaceBias = ASURA_PS2_SHADOW_CASTING_BACKFACE_BIAS;	// overide define to 0.0f to stop shadows on verticals
float Asura_PS2_Shadows::s_fShadowBackfaceRate = ASURA_PS2_SHADOW_CASTING_BACKFACE_RATE;   // should never need to be changed

float Asura_PS2_Shadows::s_fShadowDistanceFalloffEnd   = ASURA_PS2_FADE_SHADOW_DIST_END;	// over-rideable by project
float Asura_PS2_Shadows::s_fShadowDistanceFalloffStart = ASURA_PS2_FADE_SHADOW_DIST_START;


void Asura_PS2_Shadows::Proj_GetShadowYAndHeightForPos( const Asura_Vector_3& xPos, float* pfShadowY, float* pfHeight )
{
	const Asura_Vector_3 xDown( 0.0f, 1.0f, 0.0f );

	Asura_LOS_InputData xInputData( xPos, xDown, ASURA_GUID_UNREGISTERED, dbg_fMaxHeightForShadowToCast );

    // Small fudge to try and ensure we don't start the LOS test below the
    // ground.
    xInputData.m_xOrigin.y -= 0.1f;

	const Asura_LOS_OutputData* pxOutputData = Asura_LOS::TestAgainstEnvironment( &xInputData );
    ASURA_LOS_TRACK_CALL( ASURA_LOS_CALLTYPE_ENV );

    float fGroundY = xInputData.m_xOrigin.y + pxOutputData->m_fDistanceToIntersection;
    *pfHeight = fGroundY - xPos.y;
    *pfShadowY = fGroundY;
}

#if 0 // RCD : shouldn't be needed any more because height calculations will take place in the renderer
void Asura_PS2_Shadows::Proj_ModifyShadowAlphaForHeight( float fHeight, float* pfAlpha )
{
    // Account for mesh being under the ground, etc.
    fHeight = Asura_Maths::Abs( fHeight );

    float fAlphaMultiplier = 1.0f - ((fHeight - dbg_fStartHeightOfShadowFalloff) / (dbg_fMaxHeightForShadowToCast - dbg_fStartHeightOfShadowFalloff));
    fAlphaMultiplier = Asura_Maths::Min( fAlphaMultiplier, 1.0f );
    fAlphaMultiplier = Asura_Maths::Max( fAlphaMultiplier, 0.0f );
    
    *pfAlpha *= fAlphaMultiplier;
}
#endif

void Asura_PS2_Shadows::Proj_GetShadowYAndHeightForAnim( const Asura_Animation* pxAnim, const Asura_Vector_3& xAnimPos, float* pfShadowY, float* pfHeight )
{
    *pfShadowY = pxAnim->GetGroundYPosition( &xAnimPos, pfHeight, dbg_fMaxHeightForShadowToCast );
}

#if 0 // no longer want to modify alpha based on height, so call the "get y and height" function instead
void Asura_PS2_Shadows::Proj_GetShadowYAndModifyAlphaForAnim( const Asura_Animation* pxAnim, float* pfShadowY, float* pfAlpha )
{
    float fHeight;
    Proj_GetShadowYAndHeightForAnim( pxAnim, pfShadowY, &fHeight );
    Proj_ModifyShadowAlphaForHeight( fHeight, pfAlpha );
}
#endif

#if 0 // Not needed because :
// buffer size and location are now defined by the shadow buffer in use
// scale is always 1.0 because we don't allow non-power-of-two buffers
// fixed scale is also constant, because it is just 3.12 bit version of above.
void Asura_PS2_Shadows::Proj_SetBfrParamsForBS( float fBSRadius, float fMinDistFromCam )
{
    Asura_Assert( Asura_PS2_GS::GetZBfrBitDepth() == 24, "" );
//OLD    s_uBfrTBP = ASURA_PS2_ZBFR_BASE_PTR << 5;
//	s_uBfrTBP = Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer->m_uBufferBasePointer << 5;

    // TODO - Scale for size and distance from camera.
//	u_int uChosenBfrDim = Asura_PS2_ShadowBuffer::s_pxCurrentShadowBuffer->m_uBufferWidth;
//    s_uBfrDim = uChosenBfrDim;
	// not used now because we want to use the actual shadow buffer data at all times

	// scale value (would only be different if we had non-power-of-two width, which is not allowed
    g_fShadowSTScale = 1.0f;

    // And the 1:3:12 fixed point version...
    g_iShadowSTScaleFixed = 4096;
}
#endif

// belongs to general shadow system because it has no direct dependency on the state of a single shadow-buffer
u_int Asura_PS2_Shadows::Proj_GetRenderToBfrPRIMSetting( void )
{
    return SCE_GS_SET_PRIM( SCE_GS_PRIM_TRISTRIP,           // PRIM
                            0,                              // IIP
                            false,                          // TME
                            false,                          // FGE
                            false,                          // ABE
							dbg_bShadowUseAAForRenderToBfr, // AA1
                            0,                              // FST
                            1,                              // CTXT
                            0 );                            // FIX
}

u_long Asura_PS2_Shadows::Proj_GetRenderToBfrRGBAQ( void )
{
    return SCE_GS_SET_RGBAQ( 0, 0, 0, 128, 0x3F800000 );
}

#if 0 // needs replacing with a function to render each active shadow buffer one after another
// and thus needs to be in the shadow buffer code, not in here
void Asura_PS2_Shadows::Proj_ShowShadowBfr( u_int uX, u_int uY, u_int uW, u_int uH, bool bAlpha, bool bNeedFLUSH )
{
    if( bNeedFLUSH )
    {
        (reinterpret_cast<Asura_PS2_DmaTag*>(Asura_PS2_DeferredRendering::GetVifBuffer(1)))->Set( 0,
                                                                                                  Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                                                                  Asura_PS2_DmaTag::ID_CNT,
                                                                                                  false,
                                                                                                  0,
                                                                                                  false, 
                                                                                                  SCE_VIF1_SET_NOP(false),
                                                                                                  SCE_VIF1_SET_FLUSH(false) );
    }

    // Renderstate.
    {
        Asura_PlatformRenderStates::CreateDefaultRenderStatePacket( Asura_PlatformRenderStates::CONTEXT_1 );

        Asura_PlatformRenderStates::SetPixelTest( false, false );
        Asura_PlatformRenderStates::SetZBufferWriteEnabled( true );
        Asura_PlatformRenderStates::SetTextureClamp( true );

        if( bAlpha )
        {
            // See comment by TEX0 setup below.
            Asura_PlatformRenderStates::SetAlphaBlend( SCE_GS_ALPHA_CS, SCE_GS_ALPHA_ZERO, SCE_GS_ALPHA_AS, SCE_GS_ALPHA_ZERO, 0 );
        }

        Asura_PlatformRenderStates::ApplyRenderStates();
    }

    const u_int uNumRegs  = 7;
    const u_int uQWC      = 1 + uNumRegs;
    const u_int uTotalQWs = 1 + uQWC;

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

    // DMA tag.
    (reinterpret_cast<Asura_PS2_DmaTag*>(pxOut++))->Set( uQWC,
                                                         Asura_PS2_DmaTag::PCE_NOCHANGE,
                                                         Asura_PS2_DmaTag::ID_CNT,
                                                         false,
                                                         0,
                                                         false,
                                                         SCE_VIF1_SET_NOP(false),
                                                         SCE_VIF1_SET_DIRECT(uQWC, false) );

    // GIF tag.
    {
        const u_int uPRIM = SCE_GS_SET_PRIM( SCE_GS_PRIM_SPRITE, // PRIM
                                             0,                  // IIP
                                             true,               // TME
                                             false,              // FGE
                                             bAlpha,             // ABE
                                             false,              // AA1
                                             1,                  // FST
                                             0,                  // CTXT
                                             0 );                // FIX

        (reinterpret_cast<Asura_PS2_GifTag*>(pxOut++))->Set( 1,                 // NLOOP
                                                             true,              // EOP
                                                             true,              // PRE
                                                             uPRIM,             // PRIM
                                                             SCE_GIF_PACKED,    // FLG
                                                             uNumRegs,          // NREG
                                                             SCE_GIF_PACKED_AD, // REG0
                                                             SCE_GIF_PACKED_AD, // REG1
                                                             SCE_GIF_PACKED_AD, // REG2
                                                             SCE_GS_UV,         // REG3
                                                             SCE_GS_XYZ2,       // REG4
                                                             SCE_GS_UV,         // REG5
                                                             SCE_GS_XYZ2 );     // REG6
    }

    // TEX1, TEX0.
    {
        Proj_GetTEX1andTEX0( &((pxOut + 0)->u64s[0]), &((pxOut + 1)->u64s[0]) );

        if( bAlpha )
        {
            // Change TFX to HIGHLIGHT2. That way, Rs=Rt*Rf+Af (similarly for
            // G and B) and As=At. If we then set Rf=Gf=Bf=0 and Af=128, we
            // get Rs=Gs=Bs=128 and As=At, meaning we get Rd=Gd=Bd=At when
            // using the alpha blend setup up above.
            (reinterpret_cast<sceGsTex0&>((pxOut + 1)->u64s[0])).TFX = SCE_GS_HIGHLIGHT2;
        }

        (pxOut++)->u64s[1] = SCE_GS_TEX1_1;
        (pxOut++)->u64s[1] = SCE_GS_TEX0_1;
    }

    // RGBA. See comment by TEX0 setup above.
    pxOut->u64s[0] = ( bAlpha ? SCE_GS_SET_RGBAQ(0, 0, 0, 128, 0x3F800000) : SCE_GS_SET_RGBAQ(128, 128, 128, 128, 0x3F800000) );
    pxOut->u64s[1] = SCE_GS_RGBAQ;
    ++pxOut;

    // Transform into primitive coordinate system.
    uX += ASURA_PS2_SCREEN_CENTRE_X - (Asura_PS2_GS::GetScreenWidth()  >> 1);
    uY += ASURA_PS2_SCREEN_CENTRE_Y - (Asura_PS2_GS::GetScreenHeight() >> 1);

    // TL.
    pxOut->u32s[0] = 0 << 4;
    pxOut->u32s[1] = 0 << 4;
    ++pxOut;
    pxOut->u32s[0] = uX << 4;
    pxOut->u32s[1] = uY << 4;
    pxOut->u64s[1] = (1ul << Asura_PS2_GS::GetZBfrBitDepth()) - 1;
    ++pxOut;

    // BR.
    pxOut->u32s[0] = s_uBfrDim << 4;
    pxOut->u32s[1] = s_uBfrDim << 4;
    ++pxOut;
    pxOut->u32s[0] = (uX + uW) << 4;
    pxOut->u32s[1] = (uY + uH) << 4;
    pxOut->u64s[1] = (1ul << Asura_PS2_GS::GetZBfrBitDepth()) - 1;
    ++pxOut;

    Asura_Assert( static_cast<u_int>(pxOut - pxBase) == uTotalQWs, "" );
}
#endif

void Asura_PS2_Shadows::Proj_GetShadowFalloffControls( Asura_PS2_QW *pxOut )
{
	pxOut->f32s[0] = s_fShadowBackfaceBias;
	pxOut->f32s[1] = -s_fShadowBackfaceRate;

	pxOut->f32s[2] = s_fShadowDistanceFalloffEnd;
	pxOut->f32s[3] = 1.0f / (s_fShadowDistanceFalloffStart - s_fShadowDistanceFalloffEnd);
}

float Asura_PS2_Shadows::CalcAlphaForMinDistFromCam( float fMinDistFromCam )
{
    float fAlpha = (s_fShadowDistanceFalloffEnd - fMinDistFromCam) / (s_fShadowDistanceFalloffEnd - s_fShadowDistanceFalloffStart);
    
    // Double alpha if using AA since that will force alpha into the range
    // [0,128], whereas the old method produced a [0,255] range.
    if( dbg_bShadowUseAAForRenderToBfr )
    {
        fAlpha *= 2.0f;
    }

    fAlpha = Asura_Maths::Min( fAlpha, 2.0f );
    fAlpha = Asura_Maths::Max( fAlpha, 0.0f );

    return fAlpha;
}

float Asura_PS2_Shadows::CalcAlphaForMinDistFromCam( const Asura_Vector_3& xBSCentre, float fBSRadius, u_int uCamVisFlags, float* pfMinDistFromCam )
{
    float fMinDistFromCam = Asura_Maths::Sqrt( Asura_Camera::FindAdjustedMinDistSqdFromCam(xBSCentre, uCamVisFlags) );
    fMinDistFromCam = Asura_Maths::Max( 0.0f, fMinDistFromCam - fBSRadius );

    if( pfMinDistFromCam ) { *pfMinDistFromCam = fMinDistFromCam; }

    return CalcAlphaForMinDistFromCam( fMinDistFromCam );
}

float Asura_PS2_Shadows::CalculateShadowLightLevelFromSH( const Asura_SphericalHarmonics_Order2_RGB& xSH, float fAlpha )
{
#ifdef ASURA_PS2_ADJUST_SHADOWS_FOR_STATIC_LIGHTING

    float fLightLevel;

    // At the moment, shadows are just flat quads on the ground below the
    // skin so let's calculate an RGB value based on an upward pointing
    // normal, ie. (0, -1, 0). Of course, this greatly simplifies the SH
    // calculation.
    {
        float fR = xSH.m_afRedCoefficients[0]   - xSH.m_afRedCoefficients[2];
        float fG = xSH.m_afGreenCoefficients[0] - xSH.m_afGreenCoefficients[2];
        float fB = xSH.m_afBlueCoefficients[0]  - xSH.m_afBlueCoefficients[2];

        float fMax = Asura_Maths::Max( fR, Asura_Maths::Max( fG, fB ) );
        float fSum = fR + fG + fB;

        // Weighted average. Just using the maximum tended to end up with
        // alpha values that were often too high but a plain average didn't
        // work so well with areas brightly lit by a source of near primary
        // colour.
        fLightLevel = fMax * 0.5f + (fSum - fMax) * 0.25f;
    }

    // Normal brightness is at 0.5 and above that is overbrightening so we
    // want to double the value produced.
    fLightLevel *= 2.0f;

    // Clamp.
    fLightLevel = Asura_Maths::Min( fLightLevel, dbg_fMaxLightLevelForShadows );
    fLightLevel = Asura_Maths::Max( fLightLevel, dbg_fMinLightLevelForShadows );

    // Modulate by global shadow level.
    fLightLevel *= dbg_fShadowFullAlpha;
    
    // Also modulate by the caster's alpha, so that if that's fading out then
    // the shadow does the same.
    fLightLevel *= fAlpha;

    return fLightLevel;

#else

    // Modulate by the caster's alpha, so that if that's fading out then the
    // shadow does the same.
    return( fAlpha * dbg_fShadowFullAlpha );

#endif
}

#if 0 
// RCD : plan to remove these functions soon because we have no scale and wouldn't need it anyway in new code
void Asura_PS2_Shadows::Proj_GetShadowQuadSTScales( int* piScales )
{
    *(piScales + 0) = 4096;
    *(piScales + 1) = 4096;
}

void Asura_PS2_Shadows::Proj_GetShadowQuadSTScales( float* pfScales )
{
    *(pfScales + 0) = 1.0f;
    *(pfScales + 1) = 1.0f;
}
#endif

// F U N C T I O N   I M P L E M E N T A T I O N S ///////////////////////////


// Function to find how wide a bounding sphere will be when projected
// Not as easy as it seems - Cant just project radius
// Unless you're using parallel projection
// Returns correct lightScreenZ value
static float FindProjectionPlane( sceVu0FVECTOR xBSCentre, float fBSRadius, sceVu0FVECTOR xLightPos, float fMaxProjSize )
{
  // Angle between direction vector from light to sphere - Theta
  // sinTheta = radius/separation
  // Distance from light to tangent point
  // tangentLength = separation * cosTheta
  // Distance to centre of projection
  // projectionDist = tangentLength * cosTheta
  // Projected size NB. Projects to double size
  // projectionSize = 2.0f * radius * cosTheta
  // Ratio of render sizes
  // projectionRatio = maxSize / projectionSize
  // Distance to projection plane
  // toReturn = projectionDist * projectionRatio
  // = tangentLength * cosTheta * maxSize / projectionSize
  // = separation * cosTheta * cosTheta * maxSize / 2.0f * radius * cosTheta
  // = separation * cosTheta * maxSize / (2.0f * radius)
  float separation, sinTheta, cosTheta;
  sceVu0FVECTOR toSphere;
  // First get vector to center of sphere
  sceVu0SubVector(toSphere, xBSCentre, xLightPos);
  separation = Asura_Maths::Sqrt( sceVu0InnerProduct( toSphere, toSphere ) );
  // Consider a sphere at lightPos with tangentLength as radius
  sinTheta = fBSRadius/separation;
  cosTheta = Asura_Maths::Sqrt( ( 1.0f - ( sinTheta * sinTheta ) ) );
  // Calculate projection distance
  return separation * cosTheta * fMaxProjSize / (2.0f * fBSRadius);
}

