//--------------------------------------------------------------------------------------
// Copyright (C) 2008 http://shift.delblanco.es
//--------------------------------------------------------------------------------------
// SHIFT Videogame is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by the
// Free Software Foundation, either version 3 of the License, or later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
//--------------------------------------------------------------------------------------
// Original version by Kevin Harris
//--------------------------------------------------------------------------------------
/// \file 3DParticle.cpp
/// Para manejar los sistemas de partículas de la escena.
/// Este módulo ofrece el tratamiento de los sistemas de partículas de la escena
//--------------------------------------------------------------------------------------
#include "dxstdafx.h"

#include <tchar.h>
#include <vector>
#include "3DParticle.h"

//--------------------------------------------------------------------------------------
// Espacio de nombres
//--------------------------------------------------------------------------------------
using namespace std;

//--------------------------------------------------------------------------------------
// Variables Globales
//--------------------------------------------------------------------------------------
vector<C3DParticles*>	g_aParticles;   // Vector de sistemas de partículas

//--------------------------------------------------------------------------------------
// Funciones auxiliares
//--------------------------------------------------------------------------------------
// Función para convertir un FLOAT en un DWORD
inline DWORD FtoDW (FLOAT f)
{
	return *((DWORD*)&f);
}

// Función para obtener un real aleatorio entre un mínimo y un máximo
inline float getRandom_f (float fMin, float fMax)
{
    return fMin + (fMax - fMin) * (float)rand() / RAND_MAX;
}

// Función para obtener un vector aleatorio con X, y , Z entre -1.0 y 1.0
D3DXVECTOR3 getRandom_v (void)
{
	D3DXVECTOR3 vVector;
    // Tomamos un valor aleatorio para Z entre -1.0 y 1.0
    vVector.z = getRandom_f( -1.0f, 1.0f );
    // Tomamos el radio del círculo
    float radius = (float)sqrt(1 - vVector.z * vVector.z);
	// Tomamos un punto aleatorio del circulo
    float t = getRandom_f( -D3DX_PI, D3DX_PI );
    // Tomamos los valores X e Y para nuestro Z
    vVector.x = (float)cosf(t) * radius;
    vVector.y = (float)sinf(t) * radius;
	return vVector;
}

//--------------------------------------------------------------------------------------
// Constructor
//--------------------------------------------------------------------------------------
C3DParticles::C3DParticles ()
{
    m_dwVBOffset       = 0;
    m_dwFlush          = 512;
    m_dwDiscard        = 2048;
    m_pActiveList      = NULL;
    m_pFreeList        = NULL;
	m_dwActiveCount    = 0;
	m_fCurrentTime     = 0.0f;
	m_fLastUpdate      = 0.0f;
    m_pVB              = NULL;
    m_chTexFile        = NULL;
    m_ptexParticle     = NULL;
    m_dwMaxParticles   = 1;
    m_dwNumToRelease   = 1;
    m_fReleaseInterval = 1.0f;
    m_fLifeCycle       = 1.0f;
    m_fSize            = 1.0f;
    m_clrColor         = D3DXCOLOR(1.0f,1.0f,1.0f,1.0f);
    m_vPosition        = D3DXVECTOR3(0.0f,0.0f,0.0f);
    m_vVelocity        = D3DXVECTOR3(0.0f,0.0f,0.0f);
    m_vGravity         = D3DXVECTOR3(0.0f,0.0f,0.0f);
    m_vWind            = D3DXVECTOR3(0.0f,0.0f,0.0f);
    m_bAirResistence   = true;
    m_fVelocityVar     = 1.0f;

	// Inicialmente no está actualizado
	m_bUpdate		   = false;
}

//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
C3DParticles::~C3DParticles ()
{
	Release();
}

//--------------------------------------------------------------------------------------
// SetTexture
//--------------------------------------------------------------------------------------
void C3DParticles::SetTexture (LPWSTR chTexFile)
{
    // Liberamos memoria anterior de la cadena
	SAFE_DELETE_ARRAY(m_chTexFile);

    // Asignamos la nueva cadena
    m_chTexFile = new WCHAR[lstrlen( chTexFile ) + 1];
    if( m_chTexFile != NULL )
		lstrcpyW( m_chTexFile, chTexFile );
}

//--------------------------------------------------------------------------------------
// RestartParticleSystem
//--------------------------------------------------------------------------------------
void C3DParticles::RestartParticleSystem ()
{
	Particle  *pParticle;
	Particle **ppParticle;

	ppParticle = &m_pActiveList;

	while( *ppParticle )
	{
		pParticle = *ppParticle;

		*ppParticle = pParticle->m_pNext;
		pParticle->m_pNext = m_pFreeList;
		m_pFreeList = pParticle;

		--m_dwActiveCount;
	}
}

//--------------------------------------------------------------------------------------
// Inicializa el sistema de partículas
//--------------------------------------------------------------------------------------
HRESULT C3DParticles::initialize()
{
    HRESULT hr;

    // Tomamos el dispositivo
    m_pDevice = DXUTGetD3DDevice();
    assert(m_pDevice != NULL);
    m_pDevice->AddRef();

    // Inicializamos el sistema de partículas
    if( FAILED( hr = restore() ) )
        return hr;

    // Tomamos el tamaño de punto
    D3DCAPS9 d3dCaps;
    m_pDevice->GetDeviceCaps( &d3dCaps );
    m_fMaxPointSize = d3dCaps.MaxPointSize;

    // Comprobamos si tenemos soporte HW enviando D3DFVF_PSIZE con el FVF.
    if( d3dCaps.FVFCaps & D3DFVFCAPS_PSIZE )
        m_bDeviceSupportsPSIZE = true;
    else
        m_bDeviceSupportsPSIZE = false;

    // Cargamos textura para las partículas
    if( FAILED( D3DXCreateTextureFromFile( m_pDevice, m_chTexFile, &m_ptexParticle ) ) )
        return E_FAIL;

	// Insertamos la nueva instancia de partículas en la lista global
	g_aParticles.push_back(this);

    return S_OK;
}

//--------------------------------------------------------------------------------------
// Actualiza el sistema de partículas
//--------------------------------------------------------------------------------------
HRESULT C3DParticles::update()
{
    Particle  *pParticle;
    Particle **ppParticle;
    D3DXVECTOR3 vOldPosition;

    // Tiempo desde la última actualización
	float fElpasedTime = DXUTGetElapsedTime();

	// Actualizamos el contador interno del sistema
    m_fCurrentTime += fElpasedTime;

	// Iteramos desde la cabeza de la lista enlazada
    ppParticle = &m_pActiveList;
    while (*ppParticle)
    {
        // Calculamos la nueva posición desde la cabeza
        pParticle = *ppParticle;
        float fTimePassed  = m_fCurrentTime - pParticle->m_fInitTime;
        // Si el tiempo de vida se ha sobrepasado, encolamos la partícula para eliminarla
        if (fTimePassed >= m_fLifeCycle)
        {
            *ppParticle = pParticle->m_pNext;
            pParticle->m_pNext = m_pFreeList;
            m_pFreeList = pParticle;
            --m_dwActiveCount;
        }
		// En otro caso actualizamos sus parámetros
        else
        {
            // Actualizamos velocidad respecto gravedad
            pParticle->m_vCurVel += m_vGravity * fElpasedTime;
            // Actualizamos velocidad respecto viento
            if( m_bAirResistence == true )
                pParticle->m_vCurVel += (m_vWind - pParticle->m_vCurVel) * fElpasedTime;
            // Actualizamos posición respecto velocidad
            vOldPosition = pParticle->m_vCurPos;
            pParticle->m_vCurPos += pParticle->m_vCurVel * fElpasedTime;
			// Pasamos a la siguiente partícula
            ppParticle = &pParticle->m_pNext;
        }
    }

	// Creamos nuevas partículas cada cierto tiempo de actualización
    if( m_fCurrentTime - m_fLastUpdate > m_fReleaseInterval )
    {
        // Reseteamos el tiempo de actualización
        m_fLastUpdate = m_fCurrentTime;
        // Emitimos las partículas
        for( DWORD i = 0; i < m_dwNumToRelease; ++i )
        {
            // Comprobamos si hay partículas para destruir, en ese caso las reutilizamos
            if( m_pFreeList )
            {
                pParticle = m_pFreeList;
                m_pFreeList = pParticle->m_pNext;
            }
			// En otro caso, si no hemos superado el límite, la tenemos que crear
            else if( m_dwActiveCount < m_dwMaxParticles )
            {
                if( NULL == ( pParticle = new Particle ) )
                    return E_OUTOFMEMORY;
            }
			// Ajustamos las propiedades de la nueva partícula
            if( m_dwActiveCount < m_dwMaxParticles )
            {
                pParticle->m_pNext = m_pActiveList;
                m_pActiveList = pParticle;
                pParticle->m_vCurVel = m_vVelocity;
                if( m_fVelocityVar != 0.0f )
                {
                    D3DXVECTOR3 vRandomVec = getRandom_v();
                    pParticle->m_vCurVel += vRandomVec * m_fVelocityVar;
                }
                pParticle->m_fInitTime  = m_fCurrentTime;
                pParticle->m_vCurPos    = m_vPosition / 2.0f;
                ++m_dwActiveCount;
            }
        }
    }

	// Está actualizado
	m_bUpdate = true;

    return S_OK;
}

//--------------------------------------------------------------------------------------
// Renderiza el sistema de partículas
//--------------------------------------------------------------------------------------
HRESULT C3DParticles::render()
{
    HRESULT hr;

	// Sólo si está actualizado
	if (!m_bUpdate) return S_OK;

	// Ponemos la textura
    m_pDevice->SetTexture( 0, GetTextureObject() );

	// Adecuamos los parámetros de renderizado adecuadamente
    m_pDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, TRUE );
    m_pDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  TRUE );
    m_pDevice->SetRenderState( D3DRS_POINTSIZE,     FtoDW(m_fSize) );
    m_pDevice->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(1.0f) );
    m_pDevice->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(0.0f) );
    m_pDevice->SetRenderState( D3DRS_POINTSCALE_B,  FtoDW(0.0f) );
    m_pDevice->SetRenderState( D3DRS_POINTSCALE_C,  FtoDW(1.0f) );

    Particle    *pParticle = m_pActiveList;
    PointVertex *pVertices;
    DWORD        dwNumParticlesToRender = 0;

    // Calculamos el offset del buffer
    m_dwVBOffset += m_dwFlush;

    // Controlamos desbordamientos
    if( m_dwVBOffset >= m_dwDiscard )
        m_dwVBOffset = 0;

	// Bloqueamos el buffer de vértices
    if( FAILED( hr = m_pVB->Lock( m_dwVBOffset * sizeof(PointVertex),
                                  m_dwFlush * sizeof(PointVertex),
                                  (void**) &pVertices, 
                                  m_dwVBOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD)))
        return hr;

    // Renderizamos cada partícula
    while( pParticle )
    {
        D3DXVECTOR3 vPos(pParticle->m_vCurPos);
        D3DXVECTOR3 vVel(pParticle->m_vCurVel);

        pVertices->posit = vPos;
        pVertices->color = m_clrColor;
        pVertices++;

        if( ++dwNumParticlesToRender == m_dwFlush )
        {
            m_pVB->Unlock();

			m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(PointVertex) );
			m_pDevice->SetFVF( PointVertex::FVF_Flags );

            if( FAILED(hr = m_pDevice->DrawPrimitive( D3DPT_POINTLIST, 
                m_dwVBOffset, dwNumParticlesToRender)))
                return hr;

            m_dwVBOffset += m_dwFlush;

            if( m_dwVBOffset >= m_dwDiscard )
                m_dwVBOffset = 0;

            if( FAILED( hr = m_pVB->Lock( 
                m_dwVBOffset * sizeof(PointVertex),
                m_dwFlush    * sizeof(PointVertex),
                (void**) &pVertices, 
                m_dwVBOffset ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD)))
                return hr;

            dwNumParticlesToRender = 0;
        }

        pParticle = pParticle->m_pNext;
    }

    // Debloqueamos el buffer
    m_pVB->Unlock();

    // Renderizamos partículas que quedan
    if( dwNumParticlesToRender )
    {
		m_pDevice->SetStreamSource( 0, m_pVB, 0, sizeof(PointVertex) );
		m_pDevice->SetFVF( PointVertex::FVF_Flags );

        if(FAILED(hr = m_pDevice->DrawPrimitive( D3DPT_POINTLIST, m_dwVBOffset, 
                                                  dwNumParticlesToRender )))
            return hr;
    }

	// Restauramos parámetros del dispositivo
    m_pDevice->SetRenderState( D3DRS_POINTSPRITEENABLE, FALSE );
    m_pDevice->SetRenderState( D3DRS_POINTSCALEENABLE,  FALSE );

	// Una vez renderizado, ya no está actualizado
	m_bUpdate = false;

    return S_OK;
}

//--------------------------------------------------------------------------------------
// Restaura el sistema de partículas
//--------------------------------------------------------------------------------------
HRESULT C3DParticles::restore()
{
    HRESULT hr;
	// Creamos un buffer de vértices para el sistema de partículas
    if( FAILED( hr = m_pDevice->CreateVertexBuffer( 
        m_dwDiscard * sizeof(PointVertex), 
        D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY | D3DUSAGE_POINTS, 
        PointVertex::FVF_Flags, // Our custom FVF
        D3DPOOL_DEFAULT, 
        &m_pVB, NULL )))
        return E_FAIL;
    return S_OK;
}

//--------------------------------------------------------------------------------------
// Libera el sistema de partículas
//--------------------------------------------------------------------------------------
HRESULT C3DParticles::free()
{
	// Liberamos el buffer de vértices del sistema de partículas
	SAFE_RELEASE(m_pVB);
    return S_OK;
}

//-----------------------------------------------------------------------------
// Destruye el sistema de partículas
//-----------------------------------------------------------------------------
void C3DParticles::Release()
{
	// Extraemos la instancia de partículas de la lista
	for (vector<C3DParticles*>::iterator itr = g_aParticles.begin(); itr != g_aParticles.end() ; itr++)
		if (*(itr) == this)
		{
			g_aParticles.erase(itr);
			break;
		}

	// Liberamos el sistema de partículas
    free();

	// Vamos eliminando la cabeza de la lista hasta el final
    while(m_pActiveList)
    {
        Particle* pParticle = m_pActiveList;
        m_pActiveList = pParticle->m_pNext;
        delete pParticle;
    }
    m_pActiveList = NULL;

	// Vamos eliminando la cabeza de la lista hasta el final
    while(m_pFreeList)
    {
        Particle *pParticle = m_pFreeList;
        m_pFreeList = pParticle->m_pNext;
        delete pParticle;
    }
    m_pFreeList = NULL;

	// Liberamos otros elementos y el dispositivo
	SAFE_DELETE_ARRAY(m_chTexFile);
	SAFE_RELEASE(m_ptexParticle);
    SAFE_RELEASE(m_pDevice);
}
