/* Autor: Leandro Bernsmuller
Faz consistencia fisica entre objetos e a cena: detecta colisao, envia dados
para agente.
Classe derivada de CCenario. */



class CCenarioFisico;
#include <windows.h>
#include <windowsx.h>
#include <d3drmwin.h>
#include "..\princip\defs.h"
#include "..\extra\ddutil.h"
#include "..\extra\ddmm.h"
#include "..\ia\dados\dados.h"
#include "..\ia\acoes\acao.h"
#include "..\ia\agente.h"
#include "cenario.h"
#include "cenfis.h"



/* construtor - inicia valores
**********************************************************/
CCenarioFisico::CCenarioFisico ()
{
   mDestroi ();
}


  
/* Cria objeto: inicia D3D e configura detecsao de colisao.
nLargura, nAltura - dimensoes da janela de saida do D3D
fPrSup, fPrInf, fPrEsq, fPrDir - proporcoes para definir os
limites da borda da viewport na detecsao de colisao:
            
            fPrSup    |    maximo: 0.0
                      |
                      V
                 *--------*
    fPrEsq    -> |        | <- fPrDir  maximo: 0.0
 maximo: 0.0     *--------*
                      A
                      |
                      | fPrInf  maximo: 0.0

    maximo quer dizer mais proximo da margem, o maior possivel.

hWnd - janela que serah usada como saida.
retorna TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenarioFisico::mCria ( int nIniX, int nIniY, int nLargura, int nAltura, HWND hWnd,
                             float fPrSup, float fPrInf, float fPrEsq, float fPrDir )
{
   mDestroi ();

   m_fLimBordaSup = fPrSup;
   m_fLimBordaInf = fPrInf;
   m_fLimBordaEsq = fPrEsq;
   m_fLimBordaDir = fPrDir;

   return CCenario::mCria ( nIniX, nIniY, nLargura, nAltura, hWnd );
}

 
 
/* Destroi objeto: reinicia variaveis
**********************************************************/
void CCenarioFisico::mDestroi ()
{
   CCenario::mDestroi ();

   // inicia array
   for ( int dwcG = 0; dwcG < NUM_GRUPOS; dwcG ++ )
      for ( int ncI = 0; ncI < MAX_OBJETOS; ncI ++ )
         m_paoAgentes [ dwcG ] [ ncI ] = NULL;

   m_fLimBordaSup = 0;
   m_fLimBordaEsq = 0;
   m_fLimBordaDir = 0;
   m_fLimBordaInf = 0;
}



/* Adiciona um objeto na cena: cria o seu frame ligado
ao frame do universo e adiciona malha (forma do objeto).
poAgente - agente responsavel pelo objeto
dwGrupo - a qual grupo pertence o objeto
dwModelo - modelo (forma) do objeto (ver IDs em dpc/consts.h).
fX, fY, fZ - posicao do frame do objeto no universo
fDX, fDY, fDZ - orientacao do frame do objeto no universo (Ex.: 0,0,0)
return: numero que identifica o objeto criado, ou -1 se erro.
**********************************************************/
int CCenarioFisico::mCriaObjeto ( CAgente* poAgente,
                                  DWORD dwGrupo, DWORD dwModelo,
                                  float fX, float fY, float fZ,
                                  float fDX, float fDY, float fDZ, 
                                  float fRX, float fRY, float fRZ,
                                  float fAng )
{
   // tenta incluir objeto no cenario
   int
      nNdxObjeto = CCenario::mCriaObjeto ( dwGrupo, dwModelo, fX, fY, fZ, fDX, fDY, fDZ, fRX, fRY, fRZ, fAng );

   // verifica se obteve sucesso
   if ( nNdxObjeto != -1 )
      m_paoAgentes [ dwGrupo ] [ nNdxObjeto ] = poAgente;

   // define a caixa que circunscreve o objeto (no SRO)
   // A caixa eh definida a partir do modelo do objeto, mas
   // eh atribuida individualmente para cada objeto afim de
   // garantir flexibilidade.
   if ( nNdxObjeto != -1 )
      m_lpModelos [ dwModelo ]->GetBox ( &m_astCaixas [ dwGrupo ] [ nNdxObjeto ] );
   
   return nNdxObjeto;
}



/* Destroi objeto, remove da cena
dwGrupo - a qual grupo o objeto pertence
dwNdxObjeto - identificador do objeto a ser destruido
**********************************************************/
void CCenarioFisico::mDestroiObjeto ( DWORD dwGrupo, DWORD dwNdxObjeto )
{
    m_paoAgentes [ dwGrupo ] [ dwNdxObjeto ] = NULL;

    CCenario::mDestroiObjeto ( dwGrupo, dwNdxObjeto );
}



/* Detecta a colisao entre um objeto e as bordas da viewport.

dwGrupo - objetos de qual grupo devem ser testados

Se colidiu envia mensagem para agente responsavel pelo
objeto informando: indice do objeto (no primeiro parametro) e
em qual limite da viewport bateu (segundo parametro).
Conforme codificacao:
  bit  codigo          significado
   0      1     colidiu com borda superior
   1      2     colidiu com borda inferior
   2      4     colidiu com borda esquerda
   3      8     colidiu com borda direita 

Como decodificar: valor & 2^bit.

**********************************************************/
void CCenarioFisico::mDetectaColisaoComBordas ( DWORD dwGrupo )
{
   // define limites da janela de visualizacao (0, 0 no centro)
   float
      fSup,
      fInf,
      fDir,
      fEsq;
   mRetLimiteJanVis ( fSup, fInf, fDir, fEsq );

   // cada cada objeto do cenario
   for ( int ncO = 0; ncO < MAX_OBJETOS; ncO ++ )

      // verifica se deve testar colisao
      if ( m_lpFrObjetos [ dwGrupo ] [ ncO ] != NULL &&   // existe objeto
           m_paoAgentes  [ dwGrupo ] [ ncO ] != NULL ) {  // existe agente responsavel

         // posicao do objeto
         D3DVECTOR
            stPos;
         m_lpFrObjetos [ dwGrupo ] [ ncO ]->GetPosition ( m_lpFrGrupos [ dwGrupo ], &stPos );
         
         // verifica se saiu dos limites
         // O limite eh a posicao da borda menos um valor
         // proporcional a borda.
         DWORD
            dwCod = 0;
         // superior
         if ( stPos.y > ( fSup - fSup * m_fLimBordaSup ) )
            dwCod |= 1;
         // inferior
         if ( stPos.y < ( fInf - fInf * m_fLimBordaInf ) )
            dwCod |= 2;
         // esquerda
         if ( stPos.x < ( fEsq - fEsq * m_fLimBordaEsq ) )
            dwCod |= 4;
         // direita
         if ( stPos.x > ( fDir - fDir * m_fLimBordaDir ) )
            dwCod |= 8;

         // se detectou colisao envia mensagem
         if ( dwCod != 0 )
            m_paoAgentes [ dwGrupo ] [ ncO ]->mTrataEvento ( NDX_EVENTO_COLISAO_BORDA, dwCod, 0 );
      }
}



/* Detecta colisao entre os objetos (corpos fisicos) de um mesmo grupo.
dwGrupo - objetos de qual grupo devem ser testados
bTestaZ - TRUE - deve testar colisao considerando Z dos objetos,
          FALSE - ignora (soh testa X e Y)
Se algum objeto colidiu envia mensagem para agentes
responsaveis pelos objetos, com o formato:
parametro 1 - indice do objeto que colidiu
parametro 2 - ponteiro para o agente em que colidiu
**********************************************************/
void CCenarioFisico::mDetectaColisaoEntreObjetos ( DWORD dwGrupo, BOOL bTestaZ )
{
   // testa interseccoes entre caixas dos objetos
   // para cada objeto do cenario
   for ( int ncO = 0; ncO < MAX_OBJETOS; ncO ++ )

      // verifica se deve testar
      if ( m_lpFrObjetos [ dwGrupo ] [ ncO ] != NULL &&   // existe objeto
           m_paoAgentes  [ dwGrupo ] [ ncO ] != NULL ) {  // existe agente responsavel

         // define a posicao do objeto 1
         D3DVECTOR
            stPos1;
         m_lpFrObjetos [ dwGrupo ] [ ncO ]->GetPosition ( m_lpFrGrupos [ dwGrupo ], &stPos1 );

         // caixa movida para posicao do objeto
         float
            fMinX1 = m_astCaixas [ dwGrupo ] [ ncO ].min.x + stPos1.x,
            fMinY1 = m_astCaixas [ dwGrupo ] [ ncO ].min.y + stPos1.y,
            fMinZ1 = m_astCaixas [ dwGrupo ] [ ncO ].min.z + stPos1.z,
            fMaxX1 = m_astCaixas [ dwGrupo ] [ ncO ].max.x + stPos1.x,
            fMaxY1 = m_astCaixas [ dwGrupo ] [ ncO ].max.y + stPos1.y,
            fMaxZ1 = m_astCaixas [ dwGrupo ] [ ncO ].max.z + stPos1.z;

         // testa com outro objeto
         for ( int ncO2 = ncO + 1; ncO2 < MAX_OBJETOS; ncO2 ++ )

            // verifica se deve testar
            if ( m_lpFrObjetos [ dwGrupo ] [ ncO2 ] != NULL &&  // existe objeto
                 m_paoAgentes  [ dwGrupo ] [ ncO2 ] != NULL ) { // existe agente responsavel

               // define a posicao do objeto 2
               D3DVECTOR
                  stPos2;
               m_lpFrObjetos [ dwGrupo ] [ ncO2 ]->GetPosition ( m_lpFrGrupos [ dwGrupo ], &stPos2 );

               // caixa movida para posicao do objeto
               float
                  fMinX2 = m_astCaixas [ dwGrupo ] [ ncO2 ].min.x + stPos2.x,
                  fMinY2 = m_astCaixas [ dwGrupo ] [ ncO2 ].min.y + stPos2.y,
                  fMinZ2 = m_astCaixas [ dwGrupo ] [ ncO2 ].min.z + stPos2.z,
                  fMaxX2 = m_astCaixas [ dwGrupo ] [ ncO2 ].max.x + stPos2.x,
                  fMaxY2 = m_astCaixas [ dwGrupo ] [ ncO2 ].max.y + stPos2.y,
                  fMaxZ2 = m_astCaixas [ dwGrupo ] [ ncO2 ].max.z + stPos2.z;

               // testa interseccao em X
               if ( ( fMinX1 >= fMinX2 &&
                      fMinX1 <= fMaxX2 ) ||
                    ( fMaxX1 >= fMinX2 &&
                      fMaxX1 <= fMaxX2 ) )

                  // testa interseccao em Y
                  if ( ( fMinY1 >= fMinY2 &&
                         fMinY1 <= fMaxY2 ) ||
                       ( fMaxY1 >= fMinY2 &&
                         fMaxY1 <= fMaxY2 ) )

                     // testa interseccao em Z, se for o caso
                     if ( ! bTestaZ ||
                          ( ( fMinZ1 >= fMinZ2 &&
                              fMinZ1 <= fMaxZ2 ) ||
                            ( fMaxZ1 >= fMinZ2 &&
                              fMaxZ1 <= fMaxZ2 ) ) ) {

                        // colisao: envia mensagem para agentes
                        if ( m_paoAgentes [ dwGrupo ] [ ncO ] != NULL )
                           m_paoAgentes [ dwGrupo ] [ ncO ]->mTrataEvento ( NDX_EVENTO_COLISAO_OUTRO, ncO, (PTRVAL) m_paoAgentes [ dwGrupo ] [ ncO2 ] );

                        // se outro agente ainda estah alocado
                        if ( m_paoAgentes [ dwGrupo ] [ ncO2 ] != NULL )
                           m_paoAgentes [ dwGrupo ] [ ncO2 ]->mTrataEvento ( NDX_EVENTO_COLISAO_OUTRO, ncO2, (PTRVAL) m_paoAgentes [ dwGrupo ] [ ncO ] );
                     }
            }
      }
}



/* Altera a direcao de deslocamento do objeto no cenario e
gira em 180 graus objeto se a nova direcao for para o lado
oposto a direcao anterior.
(direita/esquerda)
**********************************************************/
void CCenarioFisico::mMudaDirecaoDeslocamentoObjeto ( DWORD dwGrupo, DWORD dwNdxObjeto, float fDX, float fDY, float fDZ )
{
   // gira o objeto 90 graus se trocou a direcao de deslocamento
   // direcao anterior
   float
      fDAntX,
      fDAntY,
      fDAntZ;
   mPegaDirecaoDeslocamentoObjeto ( dwGrupo, dwNdxObjeto, fDAntX, fDAntY, fDAntZ );

   // verifica se deve girar objeto
   if ( ( fDAntX >= 0 && fDX < 0 ) || // antes para direita, agora esquerda
        ( fDAntX < 0 && fDX >= 0 ) )  // antes para esquerda, agora direita
      mRotacionaObjeto ( dwGrupo, dwNdxObjeto, 0.0, 1.0, 0.0, 3.14159265359 );

   CCenario::mMudaDirecaoDeslocamentoObjeto ( dwGrupo, dwNdxObjeto, fDX, fDY, fDZ );
}



/* Retorna um ponteiro para o agente responsavel
pelo primeiro objeto 3D que esta sob as coordenadas
fornecidas para selecao de objetos.
dwX, dwY - coordenadas da viewport que devem
           ser usadas na selecao
retorna - NULL - se nao selecionou objeto / ponteiro para agente.
**********************************************************/
CAgente* CCenarioFisico::mSelecionaObjetos ( DWORD dwX, DWORD dwY )
{
   // o primeiro objeto (frame que tem objeto)
   LPDIRECT3DRMFRAME
      pPrimeiroFrameSelecionado = CCenario::mSelecionaObjetos ( dwX, dwY );

   // se selecionou algum objeto
   if ( pPrimeiroFrameSelecionado )

      // procura objeto selecionado nos grupos de objetos
      // (procura pelo que tem mesmo frame)

      // varre todos grupos e todos os objetos de cada grupo
      for ( int ncG = 0; ncG < NUM_GRUPOS; ncG ++ )
         for ( int ncI = 0; ncI < MAX_OBJETOS; ncI ++ )

            // verifica se tem o mesmo frame
            if ( pPrimeiroFrameSelecionado == m_lpFrObjetos [ ncG ] [ ncI ] )
               return m_paoAgentes [ ncG ] [ ncI ];

   // nao encontrou
   return NULL;
}
