#include "SM_Engine3DPCH.h"
#include "SM_D3DMesh.h"
#include "SM_KeyFrameSequence.h"
#include "SM_KeyFrame.h"
#include "SM_Mesh.h"
#include "SM_Voxelizer.h"
#include "SM_D3DLight.h"
#include "SM_RenderAux.h"


D3DMesh::D3DMesh()
{  
  m_pKeyFrameSequence=0;
  m_pD3DGeometry=0;
  m_pParent=0;
  m_bVisible=true;
}

D3DMesh::~D3DMesh()
{
  Shutdown();
}

int  D3DMesh::Init()
{   
  return (0);
}

int  D3DMesh::Shutdown()
{
  
  return (0);
}

void DrawTriangle(float x1, float y1, float fRadius)
{
  FVF_PosRhwDiffuseTex1 pVertices[4];
  unsigned short        pIndices[6]={0,1,2,1,2,3};


  pVertices[0].x=x1  ;       pVertices[0].y=y1;         pVertices[0].oow=pVertices[0].z=1.0f; pVertices[0].u=pVertices[0].v=0.0f; pVertices[0].diffuse=0xFF000000;
  pVertices[1].x=x1+fRadius; pVertices[1].y=y1;         pVertices[1].oow=pVertices[1].z=1.0f; pVertices[1].u=pVertices[1].v=0.0f; pVertices[1].diffuse=0xFF000000;
  pVertices[2].x=x1;         pVertices[2].y=y1+fRadius; pVertices[2].oow=pVertices[2].z=1.0f; pVertices[2].u=pVertices[2].v=0.0f; pVertices[2].diffuse=0xFF000000;
  pVertices[3].x=x1+fRadius; pVertices[3].y=y1+fRadius; pVertices[3].oow=pVertices[3].z=1.0f; pVertices[3].u=pVertices[3].v=0.0f; pVertices[3].diffuse=0xFF000000;

  
  SM_D3d::SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
  SM_D3d::SetTextureStageState(0, D3DTSS_COLOROP   , D3DTOP_SELECTARG1);
  SM_D3d::SetTextureStageState(0, D3DTSS_COLOROP   , D3DTA_DIFFUSE);
  SM_D3d::SetTextureStageState(0, D3DTSS_ALPHAOP   , D3DTOP_SELECTARG1);
  SM_D3d::SetTextureStageState(0, D3DTSS_ALPHAARG1 , D3DTA_DIFFUSE);  
  SM_D3d::SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
  SM_D3d::SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS);
  SM_D3d::SetRenderState(D3DRS_ZWRITEENABLE, D3DZB_TRUE);
  
  SM_D3d::SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE );    
  SM_D3d::Device()->SetVertexShader(FVF_POSRHWDIFFUSETEX1);
  
  
  SM_D3d::Device()->DrawIndexedPrimitiveUP(
      D3DPT_TRIANGLELIST, 
      0,
      6,
      2,
      pIndices,
      D3DFMT_INDEX16,
      pVertices, sizeof(FVF_PosRhwDiffuseTex1)); 


  SM_D3d::SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
  SM_D3d::SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
  SM_D3d::SetRenderState(D3DRS_COLORWRITEENABLE, 0xF);          

}

void D3DMesh::GetKeyFrame(float fTime, Vector3D* pV3d, Quaternion* pQ)
{
  if (m_pKeyFrameSequence)
  {
    KeyFrame CurrentKeyFrame;    
    m_pKeyFrameSequence->GetKeyFrame(fTime, m_fKeyFrameSequenceStartTime, pV3d, pQ);    
  }
  else
  {     
    if (pV3d) *pV3d=m_Position;
    if (pQ) *pQ  =m_Quaternion;
  }
}


void D3DMesh::Render(RenderContext* pRenderContext, int iOutcode, float fTime, bool bDrawShadow, bool bShadowReceivers, bool bNotShadowReceivers, bool bBeams, D3DLight* pLight)
{
  unsigned i;   

  if (!m_bVisible)
  {
    return;
  }

  if (m_pKeyFrameSequence)
  {
    KeyFrame CurrentKeyFrame;    
    m_pKeyFrameSequence->GetKeyFrame(fTime, m_fKeyFrameSequenceStartTime, &CurrentKeyFrame);

    ToTransform(&m_WorldTransform, &CurrentKeyFrame.m_Position, &CurrentKeyFrame.m_Rotation);        
    m_Position=CurrentKeyFrame.m_Position;         
  }
  else
  { 
    ToTransform(&m_WorldTransform, &m_Position, &m_Quaternion);                
  }
  
  /*
  */

  if (m_pParent)
  {
    Matrix4X4 Temp;
  
    Temp=m_WorldTransform;
    Matrix4X4::Mult(&m_WorldTransform, &Temp, &m_pParent->m_WorldTransform);
  }


  m_Position=Vector3D(m_WorldTransform.m_41,
                      m_WorldTransform.m_42,
                      m_WorldTransform.m_43);
                          

  if (m_Sons.size())
  {
    unsigned uSons=m_Sons.size();

    for (i=0 ; i<uSons ; i++)
    {
      m_Sons[i]->Render(pRenderContext, -1, fTime, bDrawShadow, bShadowReceivers, bNotShadowReceivers, bBeams, pLight);
    }
  }

  // Discard rendering. @TODO: we can be smarter drawing the shadows
  if (!bDrawShadow && pRenderContext->OutcodeSphere(&m_Position, m_pD3DGeometry->m_fRadius)==-1)
  {
    return;
  }

  
  bool bShadowSetup = false;
  Vector3D v3dSrc, v3dLight;

  for (i=0 ; i<m_pD3DGeometry->m_uMeshElements ; i++)
  { 
    MeshElement* pCurrentME = &m_pD3DGeometry->m_pMeshElements[i];
    ShaderManager::Shader* pShader = ShaderManager::GetShader(pCurrentME->m_iShader);

    if (pShader && bBeams && pShader->m_bProjectBeams)
    {
      ShadowVolumeData* pSVol = pCurrentME->m_pShadowVolumeData;

      Vector3D v3dOffsets[6] = 
      { Vector3D( 0.0f,  0.0f, 0.0f),
        Vector3D( 0.0f, -1.0f, 0.0f),
        Vector3D( 1.0f,  0.0f, 0.0f),
        Vector3D(-1.0f,  0.0f, 0.0f),
        Vector3D( 0.0f,  0.0f, 1.0f),
        Vector3D( 0.0f,  0.0f, -1.0f)
      };

      static bool bInit = false;
      

      RenderPipeline::Flush();

      int iRender = ResourceManager::GetScratchRenderTexture(0);
      
      
        

      FVF_PosDiffuseTex1 pVertices[4];
      unsigned short        pIndices[6]={0,2,1,0,3,2};

      
      int iDepthID=-1;
      
      ResourceManager::SetRenderTarget(iRender, ResourceManager::GetScratchDepthTexture(0));
      pRenderContext->SyncRasterizer();
      SM_D3d::Device()->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER , 0x00000000, 1.0f, 0 );
                      

      
      ShaderManager::Shader* pBeamShader = ShaderManager::GetShader("BEAMFLARE");
      if (!pBeamShader) continue;

      pBeamShader->SetShaderState(0);
      SM_D3d::Device()->SetVertexShader(FVF_POSDIFFUSETEX1);  
      
      pRenderContext->SyncRasterizer();
      Vector3D v3dBase = m_Position;
      float fRadius    = m_pD3DGeometry->m_fRadius*1.0;
      Vector3D v3dRP   = pRenderContext->m_VRP*fRadius;
      Vector3D v3dUP   = pRenderContext->m_VUP*fRadius;

      Vector3D v1      =v3dBase-v3dRP-v3dUP;
      Vector3D v2      =v3dBase+v3dRP-v3dUP;
      Vector3D v3      =v3dBase+v3dRP+v3dUP;
      Vector3D v4      =v3dBase-v3dRP+v3dUP;


      pVertices[0].x=v1.x; pVertices[0].y=v1.y; pVertices[0].z=v1.z; pVertices[0].u=0.0f; pVertices[0].v=0; pVertices[0].diffuse=0xFFFFFFFF;
      pVertices[1].x=v2.x; pVertices[1].y=v2.y; pVertices[1].z=v2.z; pVertices[1].u=1.0f; pVertices[1].v=0; pVertices[1].diffuse=0x00FFFFFF;
      pVertices[2].x=v3.x; pVertices[2].y=v3.y; pVertices[2].z=v3.z; pVertices[2].u=1.0f; pVertices[2].v=1.0f; pVertices[2].diffuse=0xFFFFFFFF;
      pVertices[3].x=v4.x; pVertices[3].y=v4.y; pVertices[3].z=v4.z; pVertices[3].u=0.0f; pVertices[3].v=1.0f; pVertices[3].diffuse=0x00FFFFFF;

      SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&Matrix4X4::Identity );  

      SM_D3d::Device()->DrawIndexedPrimitiveUP(
        D3DPT_TRIANGLELIST, 
        0,
        4,
        2,
        pIndices,
        D3DFMT_INDEX16,
        pVertices, sizeof(FVF_PosDiffuseTex1));    

      // Draw mesh        
      pCurrentME->m_WorldTransform=m_WorldTransform;

      MeshElement me = *pCurrentME;
      me.m_pShadowVolumeData = 0;
      me.m_pShadowVolumeIndices = 0;
      me.m_iShader = ShaderManager::FindShader("BEAMOPAQUE");
      RenderPipeline::Render(&me);    
      RenderPipeline::Flush();
      

      
      ResourceManager::RestoreRenderTarget();
      pRenderContext->SyncRasterizer();

      int AuxTexture[2] =
      {
        ResourceManager::GetScratchRenderTexture(1),
        ResourceManager::GetScratchRenderTexture(0),
      };
      int iFinalResult = RenderAuxRadialBlur(0.0, 0.0, 0.01f, ResourceManager::GetScratchRenderTexture(0), AuxTexture, false, 12, 0xFF858585);

      
      // Draw light on fb
      SM_D3d::Device()->SetVertexShader(FVF_POSDIFFUSETEX1);
      pBeamShader = ShaderManager::GetShader("BEAMFLARE");
      if (!pBeamShader) continue;
      pBeamShader->SetShaderState(0);      

      SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&Matrix4X4::Identity );  

      SM_D3d::Device()->DrawIndexedPrimitiveUP(
        D3DPT_TRIANGLELIST, 
        0,
        4,
        2,
        pIndices,
        D3DFMT_INDEX16,
        pVertices, sizeof(FVF_PosDiffuseTex1));    


      SM_D3d::Device()->SetTexture(0, ResourceManager::GetTextureFromID(iFinalResult));    
      SM_D3d::Device()->SetVertexShader(FVF_POSRHWDIFFUSETEX1);
    
      FVF_PosRhwDiffuseTex1 pV[4];

      float fScaleU=1.0f;
      float fScaleV=1.0f;    


      pV[0].x=0               -0.5f; pV[0].y=0                -0.5f; pV[0].z=0.0f; pV[0].u=0.0f; pV[0].v=0; pV[0].oow=1.0f; pV[0].diffuse=0xFFFFFFFF;
      pV[1].x=(float)Coordinates::PhysicalWidth()-0.5f; pV[1].y=0                -0.5f; pV[1].z=0.0f; pV[1].u=fScaleU; pV[1].v=0; pV[1].oow=1.0f; pV[1].diffuse=0xFFFFFFFF;
      pV[2].x=(float)Coordinates::PhysicalWidth()-0.5f; pV[2].y=(float)Coordinates::PhysicalHeight()-0.5f; pV[2].z=0.0f; pV[2].u=fScaleU; pV[2].v=fScaleV; pV[2].oow=1.0f; pV[2].diffuse=0xFFFFFFFF;
      pV[3].x=0               -0.5f; pV[3].y=(float)Coordinates::PhysicalHeight()-0.5f; pV[3].z=0.0f; pV[3].u=0.0f; pV[3].v=fScaleV; pV[3].oow=1.0f; pV[3].diffuse=0xFFFFFFFF;
    

      SM_D3d::Device()->DrawIndexedPrimitiveUP(
        D3DPT_TRIANGLELIST, 
        0,
        4,
        2,
        pIndices,
        D3DFMT_INDEX16,
        pV, sizeof(FVF_PosRhwDiffuseTex1));                
    }
    else
      if (pShader && bDrawShadow && pShader->m_bProjectShadows)
    {              
      unsigned j;
      ShadowVolumeData* pSVol = pCurrentME->m_pShadowVolumeData;

      if (pSVol)
      {
        if (!bShadowSetup)
        {          
          // Get light to object space. @TODO: factor this out, it's constant for all        
          if (pLight->GetLight()->Type == D3DLIGHT_DIRECTIONAL)
          {
            v3dSrc.x = pLight->GetLight()->Direction.x;
            v3dSrc.y = pLight->GetLight()->Direction.y;
            v3dSrc.z = pLight->GetLight()->Direction.z;

            m_WorldTransform.InverseRotTransform(&v3dLight, &v3dSrc, 1);
          }
          else
          {
            v3dSrc.x = pLight->GetLight()->Position.x;
            v3dSrc.y = pLight->GetLight()->Position.y;
            v3dSrc.z = pLight->GetLight()->Position.z;

            Matrix4X4 Inverse;
            m_WorldTransform.InvertAs4X3(&Inverse);
            Inverse.Transform(&v3dLight, &v3dSrc, 1);
          }
          
          
          // Shader setup
          ShaderManager::Shader* pStencilShader = ShaderManager::GetShader("STENCIL");
          if (pStencilShader)
          {
            pStencilShader->SetShaderState(0);                    
          }
          else
          {
            //SM_Main::OutputError("Stencil shader missing. Check out shaders.txt");
          }
          

          // Additional setup
          SM_D3d::Device()->SetVertexShader(FVF_POSNORMAL);  
          SM_D3d::Device()->SetTransform(D3DTS_WORLD, (D3DMATRIX*)&m_WorldTransform );  

                  
          SM_D3d::SetRenderState(D3DRS_ZWRITEENABLE, FALSE);
          SM_D3d::SetRenderState(D3DRS_ZFUNC, D3DCMP_LESS);
          SM_D3d::SetRenderState(D3DRS_STENCILFUNC, D3DCMP_ALWAYS);
          
  
          bShadowSetup = true;

        }
                               
        // Extrude to generate shadow volume
        unsigned uVertices  = pSVol->m_uVertices;

        FVF_PosNormal* pSrc = pSVol->m_pVertices;
        FVF_PosNormal* pDst = new FVF_PosNormal[uVertices];

        if (pLight->GetLight()->Type == D3DLIGHT_DIRECTIONAL)
        {
          for (j = 0 ; j < uVertices ; j++)
          {             
            if (Vector3D::Dot(Vector3D(pSrc[j].nx, 
                                       pSrc[j].ny,
                                       pSrc[j].nz),
                              v3dLight) < 0.0f)
            {
              const float fExtrude = 100000.0f;
              pDst[j].x = pSrc[j].x + fExtrude * v3dLight.x;
              pDst[j].y = pSrc[j].y + fExtrude * v3dLight.y;
              pDst[j].z = pSrc[j].z + fExtrude * v3dLight.z;
            }   
            else
            {
              pDst[j].x = pSrc[j].x;
              pDst[j].y = pSrc[j].y;
              pDst[j].z = pSrc[j].z;

            }
          }
        }
        else
        {
          for (j = 0 ; j < uVertices ; j++)
          {        
            Vector3D v3dLightEye(pSrc[j].x-v3dLight.x,
                                 pSrc[j].y-v3dLight.y,
                                 pSrc[j].z-v3dLight.z);
          
            v3dLightEye.Normalize();
          
            if (Vector3D::Dot(Vector3D(pSrc[j].nx, 
                                       pSrc[j].ny,
                                       pSrc[j].nz),
                              v3dLightEye) < 0.0f)
            {
              const float fExtrude = 100000.0f;
              pDst[j].x = pSrc[j].x + fExtrude * v3dLightEye.x;
              pDst[j].y = pSrc[j].y + fExtrude * v3dLightEye.y;
              pDst[j].z = pSrc[j].z + fExtrude * v3dLightEye.z;
            }   
            else
            {
              pDst[j].x = pSrc[j].x;
              pDst[j].y = pSrc[j].y;
              pDst[j].z = pSrc[j].z;

            }
          }
        }

        SM_D3d::SetRenderState(D3DRS_STENCILPASS, D3DSTENCILOP_KEEP);
        SM_D3d::SetRenderState(D3DRS_STENCILFAIL, D3DSTENCILOP_KEEP);
        SM_D3d::SetRenderState(D3DRS_ZBIAS, 0);
                              
        // Backfaces
        SM_D3d::SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
        SM_D3d::SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_INCR);
    
        SM_D3d::Device()->DrawIndexedPrimitiveUP(
          D3DPT_TRIANGLELIST, 
          0,
          uVertices,
          pSVol->m_uFaces,
          pSVol->m_pIndices,
          D3DFMT_INDEX16,
          pDst, sizeof(FVF_PosNormal));    


        // Front faces
        SM_D3d::SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
        SM_D3d::SetRenderState(D3DRS_STENCILZFAIL, D3DSTENCILOP_DECR);
    

        SM_D3d::Device()->DrawIndexedPrimitiveUP(
          D3DPT_TRIANGLELIST, 
          0,
          uVertices,
          pSVol->m_uFaces,
          pSVol->m_pIndices,
          D3DFMT_INDEX16,
          pDst, sizeof(FVF_PosNormal));

      
        SM_D3d::SetRenderState(D3DRS_ZBIAS, 0);        
        
        delete[] pDst;
      }
    }
    else
    {
      if ( !pShader ||
           (bShadowReceivers && pShader->m_bReceiveShadows) ||
           (bNotShadowReceivers && !pShader->m_bReceiveShadows) )
      {

        pCurrentME->m_fDepth=
          (m_Position-pRenderContext->GetViewport()->m_v3dPosition-
           pRenderContext->m_VPN).Length();

        pCurrentME->m_WorldTransform=m_WorldTransform;
        RenderPipeline::Render(pCurrentME);    
      }                   
    }
  }
}




