/* Autor: Leandro Bernsmuller
Usa as funcoes do Direct 3D - retained mode.
Gerencia cenas 3D: cria objetos, executa tranformacoes, desenho,...

As medidas da viewport dependem da distancia entre a camera e o plano
de projecao (definido na macro DIST_CAM). Medidas da viewport:
   X esquerdo = - DIST_CAM / 2;
   X direito  = + DIST_CAM / 2;
   Y superior = + DIST_CAM * 3 / 8;  (mantem proporcao da janela: 3/4 e /2)
   Y inferior = - DIST_CAM * 3 / 8;
Testado numa janela de 600x450

 (0,0,0) eh o meio do cenario
       |     (posicao relativa a janela)
   ----+---
       |                                      */



// para poder usar COM (I*)
#define INITGUID



#include <windows.h>
#include <windowsx.h>
#include <d3drmwin.h>
#include "..\princip\defs.h"
#include "..\extra\ddutil.h"
#include "..\extra\ddmm.h"
#include "cenario.h"



// algumas funcoes e as variaveis usadas pela classe e DX,
// nao podem ser C++.
static HRESULT WINAPI SelecionaDriver (
                        LPGUID lpGuid, LPSTR lpDeviceDescription,
                        LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc,
                        LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext );
static DWORD ConvBPP_DDBD ( int nBpp );

// servem para fazer comunicacao entre variaveis da classe e funcoes C
int nNUMBPP;
GUID astDRV_GUID [ MAX_DRIVERS ];             // GUIDs dos drivers para D3D
char aszNOME_DRV [ MAX_DRIVERS ] [ MAX_STR ]; // nomes
int  nNUM_DRVS;                               // numero de drivers disponiveis



/* construtor - inicia valores
**********************************************************/
CCenario::CCenario ()
{
   m_lpD3DRM = NULL;
   m_lpDDClipper = NULL;
   m_lpFrCamera = NULL;
   m_lpDisp = NULL;
   m_lpJanVis = NULL;
   m_pstTex = NULL;
   m_nNumBPP = 0;
   m_nNumDrvs = 0;
   m_nDrvSel = -1;
   m_bFuncionando = FALSE;
   m_nAltura = 0;
   m_nLargura = 0;
   m_nIniX = 0;
   m_nIniY = 0;
   m_pstSelecionados = NULL;
   
   // inicia arrays

   for ( int ncI = 0; ncI < NUM_MODELOS; ncI ++ )
      m_lpModelos [ ncI ] = NULL;

   for ( int ncG = 0; ncG < NUM_GRUPOS + 1; ncG ++ ) {
      m_lpLuzAmbiente [ ncG ] = NULL;
      m_lpFrGrupos [ ncG ] = NULL;
   }

   for ( ncG = 0; ncG < NUM_GRUPOS; ncG ++ )
      for ( ncI = 0; ncI < MAX_OBJETOS; ncI ++ )
         m_lpFrObjetos [ ncG ] [ ncI ] = NULL;

   for ( int ncT = 0; ncT < MAX_OBJETOS; ncT ++ )
      m_astTemporarios [ ncT ].bOcupado = FALSE;
}



/* destrutor - termina
**********************************************************/
CCenario::~CCenario ()
{
   mDestroi ();
}


  
/* Inicia D3D e configura cena basica com
frame da camera e demais frames de grupo e universo.
nLargura, nAltura - dimensoes da janela de saida do D3D
hWnd - janela queserah usada como saida
**********************************************************/
BOOL CCenario::mCria ( int nIniX, int nIniY, int nLargura, int nAltura, HWND hWnd )
{
   m_nLargura = nLargura;
   m_nAltura = nAltura;
   m_nIniX = nIniX;
   m_nIniY = nIniY;

   // destroi anterior
   mDestroi ();

   // define BPP
   HDC
      hHdc = ::GetDC ( hWnd );
   nNUMBPP = m_nNumBPP = ::GetDeviceCaps ( hHdc, BITSPIXEL );
   ::ReleaseDC ( hWnd, hHdc );
    
   // tenta definir drivers disponiveis para D3D e seleciona um
   if ( ! mDefineDrivers ( hWnd ) )
      return FALSE;

   // tenta criar objeto D3DRM
   if ( ::Direct3DRMCreate ( &m_lpD3DRM ) != D3DRM_OK )
      return FALSE;

   // tenta criar o frame principal: universo
   if ( m_lpD3DRM->CreateFrame ( NULL, &m_lpFrGrupos [ NUM_GRUPOS ] ) != D3DRM_OK )
      return FALSE;

   // tenta criar demais frames de grupos - e inclui no frame universo
   for ( DWORD dwcG = 0; dwcG < NUM_GRUPOS; dwcG ++ )
      if ( m_lpD3DRM->CreateFrame ( m_lpFrGrupos [ NUM_GRUPOS ], &m_lpFrGrupos [ dwcG ] ) != D3DRM_OK )
         return FALSE;

   // define a cor de fundo do frame principal
   if ( m_lpFrGrupos [ NUM_GRUPOS ]->SetSceneBackgroundRGB ( D3DVAL ( CORFUNDO_R ), D3DVAL ( CORFUNDO_G ), D3DVAL ( CORFUNDO_B ) ) != D3DRM_OK )
       return FALSE;

   // tenta criar o frame da camera
   if ( m_lpD3DRM->CreateFrame ( m_lpFrGrupos [ 0 ], &m_lpFrCamera ) != D3DRM_OK )
      return FALSE;

   // posicao da camera - relativo ao universo
   if ( m_lpFrCamera->SetPosition ( m_lpFrGrupos [ NUM_GRUPOS ], D3DVAL ( 0 ), D3DVAL ( 0 ), D3DVAL ( - DIST_CAM ) ) != D3DRM_OK )
      return FALSE;
   
   // orientacao da camera - relativo ao universo
   if ( m_lpFrCamera->SetOrientation ( m_lpFrGrupos [ NUM_GRUPOS ], D3DVAL ( 0 ), D3DVAL ( 0 ), D3DVAL ( 1 ), D3DVAL ( 0 ), D3DVAL ( 1 ), D3DVAL ( 0 ) ) != D3DRM_OK )
      return FALSE;

   // tenta criar objeto de recorte para janela
   if ( ::DirectDrawCreateClipper ( 0, &m_lpDDClipper, NULL ) != DD_OK )
      return FALSE;

   // tenta associar objeto de recorte a janela
   if ( m_lpDDClipper->SetHWnd ( 0, hWnd ) != DD_OK )
      return FALSE;

   // cria o dispositivo (habilita) D3DRM para usar o driver selecionado
   if ( ! mCriaDispVis ( m_nIniX, m_nIniY, m_nLargura, m_nAltura, FALSE ) )
      return FALSE;

   m_bFuncionando = TRUE;
   
   // criou tudo
   return TRUE;
}


  
/* Destroi objeto, desaloca memoria, termina
**********************************************************/
void CCenario::mDestroi ()
{
   if ( m_lpD3DRM != NULL ) {
      m_lpD3DRM->Release ();
      m_lpD3DRM = NULL;
   }
   if ( m_lpJanVis != NULL ) {  // a ordem de desalocacoes importa!
      m_lpJanVis->Release ();
      m_lpJanVis = NULL;
   }
   if ( m_lpDDClipper != NULL ) {
      m_lpDDClipper->Release ();
      m_lpDDClipper = NULL;
   }
   if ( m_lpFrCamera != NULL ) {
      m_lpFrCamera->Release ();
      m_lpFrCamera = NULL;
   }
   if ( m_lpDisp != NULL ) {
      m_lpDisp->Release ();
      m_lpDisp = NULL;
   }
   if ( m_pstTex != NULL ) {
      m_pstTex->Release ();
      m_pstTex = NULL;
   }   
   if ( m_pstSelecionados != NULL ) {
      m_pstSelecionados->Release();
      m_pstSelecionados = NULL;
   }

   for ( int ncI = 0; ncI < NUM_MODELOS; ncI ++ )
      if ( m_lpModelos [ ncI ] != NULL ) {
         m_lpModelos [ ncI ]->Release ();
         m_lpModelos [ ncI ] = NULL;
      }

   for ( int ncG = 0; ncG < NUM_GRUPOS; ncG ++ )
      for ( ncI = 0; ncI < MAX_OBJETOS; ncI ++ )
         if ( m_lpFrObjetos [ ncG ] [ ncI ] != NULL ) {
            m_lpFrObjetos [ ncG ] [ ncI ]->Release ();
            m_lpFrObjetos [ ncG ] [ ncI ] = NULL;
         }

   for ( ncG = 0; ncG < NUM_GRUPOS + 1; ncG ++ ) {
      if ( m_lpLuzAmbiente [ ncG ] != NULL ) {
         m_lpLuzAmbiente [ ncG ]->Release ();
         m_lpLuzAmbiente [ ncG ] = NULL;
      }
      if ( m_lpFrGrupos [ ncG ] != NULL ) {
         m_lpFrGrupos [ ncG ]->Release ();
         m_lpFrGrupos [ ncG ] = NULL;
      }
   }

   for ( int ncT = 0; ncT < MAX_OBJETOS; ncT ++ )
      m_astTemporarios [ ncT ].fTempo = -1.0;

   m_bFuncionando = FALSE;
}
   


/* Procura por um frame livre (NULL).
dwNumElems - numero de elementos, comprimento do array de frames
lpFrame aponta o array de frames onde procurar
return -1 se erro (nao achou) ou posicao vaga
**********************************************************/
int CCenario::mProcuraFrameLivre ( DWORD dwNumElems, LPDIRECT3DRMFRAME lpFrame )
{
   // procura um elemento vago (NULL) no array de frames
   int
      nVago = -1;   // nao achou um elemento -> -1

   for ( int ncI = 0; ncI < (int) dwNumElems; ncI ++ )
      // procura por algum frame vazio
      if ( &lpFrame [ ncI ] == NULL ) {
         nVago = ncI;
         break;
      }

   return nVago;
}



/* Define drivers disponiveis para D3D
return TRUE - achou e selecionou algum / FALSE - nenhum disponivel
**********************************************************/
BOOL CCenario::mDefineDrivers ( HWND hWnd )
{
   // cria DD
   LPDIRECTDRAW
      lpDD;
   HRESULT
      hRVal = DirectDrawCreate ( NULL, &lpDD, NULL );
   if ( hRVal != DD_OK )
      return FALSE;

   // solicita enumeracao de drivers para D3D
   LPDIRECT3D
      lpD3D;
   hRVal = lpDD->QueryInterface ( IID_IDirect3D, (void**) &lpD3D );
   if ( hRVal != DD_OK ) {
      lpDD->Release();
      return FALSE;
   }

   // define drivers dispiniveis
   m_nDrvSel = -1;
   nNUM_DRVS = m_nNumDrvs = 0;
   hRVal = lpD3D->EnumDevices ( SelecionaDriver, &m_nDrvSel );
   if ( hRVal != DD_OK )
      return FALSE;

   // atualiza variaveis da classe com as variaveis globais
   m_nNumDrvs = nNUM_DRVS;
   memcpy ( (void*) m_astDrvGUID, (void*) astDRV_GUID, MAX_DRIVERS * sizeof ( GUID ) );
   memcpy ( (void*) m_aszNomeDrv, (void*) aszNOME_DRV, MAX_DRIVERS * MAX_STR );

   // verifica se encontrou pelo menos um driver
   if ( m_nNumDrvs == 0 )
      return FALSE;

   // termina objetos usados para deteccao
   lpD3D->Release();
   lpDD->Release();

   return TRUE;
}



/* Cria e inicia dispositivo e viewport do D3DRM.
Forca redesenho da viewport sem executar programacao do DX.
nIniX, nIniY, nResX, nResY - coordenadas/dimensoes relativos a tela
bSoJanVis - TRUE - recria apenas viewport / FALSE -
 recria, tambem, o dispositivo (default TRUE).
return TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mCriaDispVis ( int nIniX, int nIniY, int nResX, int nResY,
                              BOOL bSoJanVis )
{
   // verifica erro no tamanho
   if ( ! nResX || ! nResY)
      return FALSE;

   if ( ! bSoJanVis ) {
      // desaloca, se jah alocado
      if ( m_lpDisp != NULL ) {
         m_lpDisp->Release ();
         m_lpDisp = NULL;
         return FALSE;
      }

      //  tenta criar dispositivo do D3DRM para esta janela, usando o driver selecionado
      if ( m_lpD3DRM->CreateDeviceFromClipper ( m_lpDDClipper,
                                                &m_astDrvGUID [ m_nDrvSel ],
                                                nResX, nResY,
                                                &m_lpDisp ) != D3DRM_OK )
         return FALSE;
   }

   //nResX = m_lpDisp->GetWidth ();
   //nResY = m_lpDisp->GetHeight ();

   // desaloca viewport, se jah foi alocada alguma vez
   //if ( m_lpJanVis != NULL ) {
   //   m_lpJanVis->Release ();
   //   m_lpJanVis = NULL;
   //}
   
   // cria viewport usando o frame da camera
   if ( m_lpD3DRM->CreateViewport ( m_lpDisp, m_lpFrCamera, nIniX, nIniY,
                                    nResX, nResY, &m_lpJanVis ) != D3DRM_OK ) {
      // desaloca  
      if ( m_lpDisp != NULL ) {
         m_lpDisp->Release ();
         m_lpDisp = NULL;
         return FALSE;
      }
   }

   // define grande profundidade para viewport
   if ( m_lpJanVis->SetBack ( D3DVAL ( PROFUNDIDADE ) ) != D3DRM_OK ) {
      // desaloca
      if ( m_lpDisp != NULL ) {
         m_lpDisp->Release ();
         m_lpDisp = NULL;
      }
      // desaloca
      if ( m_lpJanVis != NULL ) {
         m_lpJanVis->Release ();
         m_lpJanVis = NULL;
      }
      return FALSE;
   }

   // Define escala uniforme para X e Y - senao objetos se deformam
   // conforme janela.
   m_lpJanVis->SetUniformScaling ( TRUE );

   if ( ! bSoJanVis )
      // configura a qualidade da cena a ser gerada para o dispositivo criado
      if ( ! mConfiguraQualidadeCena () ) {
         // desaloca
         if ( m_lpDisp != NULL ) {
            m_lpDisp->Release ();
            m_lpDisp = NULL;
         }
         // desaloca
         if ( m_lpJanVis != NULL ) {
            m_lpJanVis->Release ();
            m_lpJanVis = NULL;
         }
         return FALSE;
      }

   // atualiza variaveis membro
   m_nIniX = nIniX;
   m_nIniY = nIniY;
   m_nAltura = nResY;
   m_nLargura = nResX;

   return TRUE;
}



/* Configura o dispositivo para gerar cenas com uma certa qualidade
return TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mConfiguraQualidadeCena ()
{
   // configura: iluminacao, preenchimento, sombreamento, se diferente
   if ( m_lpDisp->SetQuality ( CONFIGURACAO_IMAGEM ) != D3DRM_OK )
      return FALSE;

   // sem dithering (nao eh muito necessario para Ramp)
   if ( m_lpDisp->GetDither () != FALSE )
      if ( m_lpDisp->SetDither ( FALSE ) != D3DRM_OK )
         return FALSE;

   // qualidade das texturas
   if ( m_lpDisp->GetTextureQuality () != D3DRMTEXTURE_LINEAR )
      if ( m_lpDisp->SetTextureQuality ( D3DRMTEXTURE_LINEAR ) != D3DRM_OK )
         return FALSE;

   // configura sombreamento conforme numero de bits por pixel
   switch ( m_nNumBPP ) {

      case 1:
         if ( m_lpDisp->SetShades ( NIVEIS_SOMBREAMENTO_1 ) )
            return FALSE;
         if ( m_lpD3DRM->SetDefaultTextureShades ( 64 ) )
            return FALSE;
         break;
       
      case 8:
      case 15:
      case 16:
         if ( m_lpDisp->SetShades ( NIVEIS_SOMBREAMENTO_16 ) )
            return FALSE;
         if ( m_lpD3DRM->SetDefaultTextureColors ( 64 ) )
            return FALSE;
         if ( m_lpD3DRM->SetDefaultTextureShades ( 64 ) )
            return FALSE;
         break;

      case 24:
      case 32:
         if ( m_lpDisp->SetShades ( NIVEIS_SOMBREAMENTO_32 ) )
            return FALSE;
         if ( m_lpD3DRM->SetDefaultTextureColors ( 64 ) )
            return FALSE;
         if ( m_lpD3DRM->SetDefaultTextureShades ( 256 ) )
            return FALSE;
         break;
   }
   
   return TRUE;
}



/* Deve ou nao ser usado Z buffer num certo frame de grupo.
dwGrupo - qual grupo (o ultimo eh o principal)
bZBuffer - TRUE - usar ZBuffer / FALSE - nao usa
return TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mDefineUsarZBuffer ( DWORD dwGrupo, BOOL bZBuffer )
{
   // configura ZBuffer (usar ou nao)
   if ( bZBuffer ) {
      if ( m_lpFrGrupos [ dwGrupo ]->SetZbufferMode ( D3DRMZBUFFER_ENABLE/*D3DRMZBUFFER_FROMPARENT*/ ) != D3DRM_OK )
          return FALSE;
   }
   else
      if ( m_lpFrGrupos [ dwGrupo ]->SetZbufferMode ( D3DRMZBUFFER_DISABLE ) != D3DRM_OK )
          return FALSE;

   return TRUE;
}



/* Cria uma fonte de luz num dos frames de grupo.
dwTipo - tipo da fonte de luz: 0 - ambiente, 1 - direcional
dwFramePai - em qual grupo incluir, serah o frame iluminado
dwFrame - em qual frame incluir a fonte de luz:
          se for ambiente indica a posicao no array de fontes de luz
             ambiente;
          se for direcional indica a posicao no array de objetos
             (a direcao da fonte serah a direcao desse frame de objetos)
fX, fY, fZ - direcao da fonte de luz se for fonte direcional (senao nada)
fR, fG, fB - cor da fonte
return TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mCriaLuz ( DWORD dwTipo, DWORD dwFramePai, DWORD dwFrame,
                          float fX, float fY, float fZ,
                          float fR, float fG, float fB )
{
   switch ( dwTipo ) {

      case 0:      // ambiente
         {
            // tenta criar fonte de luz (no array de fontes ambiente)
            if ( FAILED ( m_lpD3DRM->CreateLightRGB ( D3DRMLIGHT_AMBIENT,
                                                      D3DVAL ( fR ), D3DVAL ( fG ), D3DVAL ( fB ),
                                                      &m_lpLuzAmbiente [ dwFrame ] ) ) )
               return FALSE;

            // adiciona luz ambiente no frame de grupo
            if ( FAILED ( m_lpFrGrupos [ dwFramePai ]->AddLight ( m_lpLuzAmbiente [ dwFrame ] ) ) )
               return FALSE;
         }
         break;

      case 1:      // direcional
         {
            // procura um elemento vago (NULL) no array de objetos

            int
               nVago = -1;   // nao achou um elemento -> -1

            for ( int ncI = 0; ncI < MAX_OBJETOS; ncI ++ )
               // procura por algum frame vazio
               if (  m_lpFrObjetos [ dwFrame ] [ ncI ] == NULL ) {
                  nVago = ncI;
                  break;
               }
            
            if ( nVago == -1 )
               return FALSE;

            // cria frame
            if ( FAILED ( m_lpD3DRM->CreateFrame ( m_lpFrGrupos [ dwFramePai ], &m_lpFrObjetos [ dwFrame ] [ nVago ] ) ) )
               return FALSE;

            // direciona frame
            if ( FAILED ( m_lpFrObjetos [ dwFrame ] [ nVago ]->SetOrientation ( m_lpFrGrupos [ dwFramePai ],
                                                                                fX, fY, fZ,  fX, fZ, fY ) ) )
               return FALSE;

            // cria fonte de luz
            LPDIRECT3DRMLIGHT
               lpFonte = NULL;
            if ( FAILED ( m_lpD3DRM->CreateLightRGB ( D3DRMLIGHT_DIRECTIONAL, fR, fG, fB, &lpFonte ) ) )
               return FALSE;

            // adiciona fonte de luz no seu frame
            if ( FAILED ( m_lpFrObjetos [ dwFrame ] [ nVago ]->AddLight ( lpFonte ) ) )
               return FALSE;

            // desaloca informacoes sobre aquela fonte
            lpFonte->Release ();
         }
         break;
   }

   return TRUE;
}



/* Inclui um modelo para objetos,
tornando disponivel para um objeto.
dwNdxModelo - indice onde alocar o modelo
szArq - arquivo que possui a malha, formato X
retorna TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mIncluiModelo ( DWORD dwNdxModelo, char* szArq )
{
   // tenta criar objeto que faz a malha
   if ( FAILED ( m_lpD3DRM->CreateMeshBuilder ( &m_lpModelos [ dwNdxModelo ] ) ) )
      return FALSE;

   // carrega arquivo com a malha de poligonos
   if ( m_lpModelos [ dwNdxModelo ]->Load ( szArq, NULL, D3DRMLOAD_FROMFILE, NULL, NULL ) != D3DRM_OK ) {
      char
         szMsg [ 1000 ];
      sprintf ( szMsg, STR_ERRO_ARQUIVO_NAO_ENCONTRADO, szArq );
      MSGSTR ( szMsg );
      return FALSE;
   }

   return TRUE;
}



/* Define qual bmp usar como fundo do cenario.
szNome - arquivo com bmp
dwNumCores - usar quantas cores para exibir imagem
dwXini e dwYini - posicao do inicio da texture conforme
                  origem do viewport.
retorna TRUE - certo / FALSE - erro
**********************************************************/
BOOL CCenario::mConfiguraImagemFundo ( char* szNome, DWORD dwNumCores,
                                       DWORD dwXini, DWORD dwYini )
{
   // carrega o bitmap do fundo da cena
   if ( m_lpD3DRM->LoadTexture ( szNome, &m_pstTex ) != D3DRM_OK ) {
      char
         szMsg [ 1000 ];
      sprintf ( szMsg, STR_ERRO_ARQUIVO_NAO_ENCONTRADO, szNome );
      MSGSTR ( szMsg );
      return FALSE;
   }

   // configura textura
   if ( FAILED ( m_pstTex->SetColors ( dwNumCores ) ) )
      return FALSE;
   if ( FAILED ( m_pstTex->SetDecalScale ( TRUE ) ) )
      return FALSE;
   if ( FAILED ( m_pstTex->SetDecalOrigin ( dwXini, dwYini ) ) )
      return FALSE;

   // coloca no universo
   if ( FAILED ( m_lpFrGrupos [ NUM_GRUPOS ]->SetSceneBackgroundImage ( m_pstTex ) ) )
      return FALSE;

   return TRUE;
}



/* Retorna os limites da janela de visualizacao (viewport),
em coordenadas do SRU.
**********************************************************/
void CCenario::mRetLimiteJanVis ( float& fSup, float& fInf,
                                  float& fDir, float& fEsq ) 
{
   fEsq = (float) - DIST_CAM / 2;
   fDir = (float) + DIST_CAM / 2;
   float
      fPropYX = (float) m_nAltura / m_nLargura;
   fSup = + DIST_CAM * fPropYX / 2;
   fInf = - DIST_CAM * fPropYX / 2;
}



/* Adiciona um objeto (Frame) na cena: cria o seu frame ligado
ao frame do grupo e adiciona malha (forma do objeto).
dwGrupo - em qual grupo criar 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 - deslocamento do frame do objeto no universo
fRX, fRY, fRZ - vetor usado como eixo para rotacao do objeto
fAng - angulo de rotacao (especifica uma rotacao que anima o objeto)
return: identificador do objeto no grupo, ou -1 se erro.
**********************************************************/
int CCenario::mCriaObjeto ( DWORD dwGrupo, DWORD dwModelo,
                             float fX, float fY, float fZ,
                             float fDX, float fDY, float fDZ, 
                             float fRX, float fRY, float fRZ,
                             float fAng )
{
   // se foi passado um modelo invalido
   if ( dwModelo >= NUM_MODELOS )
      return -1;

   // procura um elemento vago (NULL) no array de frames
   int
      nVago = -1;   // nao achou um elemento -> -1

   for ( int ncI = 0; ncI < MAX_OBJETOS; ncI ++ )
      // procura por algum frame vazio
      if ( m_lpFrObjetos [ dwGrupo ] [ ncI ] == NULL ) {
         nVago = ncI;
         break;
      }

   // verifica se todos os frames estavam ocupados
   if ( nVago == -1 )
      return -1;

   // cria frame para objeto
   if ( FAILED ( m_lpD3DRM->CreateFrame ( m_lpFrGrupos [ dwGrupo ], &m_lpFrObjetos [ dwGrupo ] [ nVago ] ) ) )
      return -1;

   // adiciona malha do modelo ao frame
   if ( FAILED ( m_lpFrObjetos [ dwGrupo ] [ nVago ]->AddVisual ( (LPDIRECT3DRMVISUAL) m_lpModelos [ dwModelo ] ) ) )
      return -1;

   // configura posicao frame
   if ( FAILED ( m_lpFrObjetos [ dwGrupo ] [ nVago ]->SetPosition ( m_lpFrGrupos [ dwGrupo ], D3DVAL ( fX ), D3DVAL ( fY ), D3DVAL ( fZ ) ) ) )
      return -1;

   // configura direcao de deslocamento do frame
   if ( FAILED ( m_lpFrObjetos [ dwGrupo ] [ nVago ]->SetVelocity ( m_lpFrGrupos [ dwGrupo ], D3DVAL ( fDX ), D3DVAL ( fDY ), D3DVAL ( fDZ ), TRUE ) ) )
     return -1;

   // configura rotacao
   if ( FAILED ( m_lpFrObjetos [ dwGrupo ] [ nVago ]->SetRotation ( m_lpFrGrupos [ dwGrupo ], D3DVAL ( fRX ), D3DVAL ( fRY ), D3DVAL ( fRZ ), D3DVAL ( fAng ) ) ) )
     return -1;
   
   return nVago;
}



/* Destroi objeto, remove da cena
dwGrupo - a qual grupo o objeto pertence
dwNdxFrame - identificador do objeto a ser destruido
**********************************************************/
void CCenario::mDestroiObjeto ( DWORD dwGrupo, DWORD dwNdxFrame )
{
   if ( dwNdxFrame == (DWORD) -1 )
      return;
   
   m_lpFrGrupos [ dwGrupo ]->DeleteChild ( m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ] );
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->Release ();
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ] = NULL;
}

 
 
/* Altera a direcao de deslocamento de um objeto
**********************************************************/
void CCenario::mMudaDirecaoDeslocamentoObjeto ( DWORD dwGrupo, DWORD dwNdxFrame, float fDX, float fDY, float fDZ )
{
   if ( m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ] != NULL )
      m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->SetVelocity ( m_lpFrGrupos [ dwGrupo ], D3DVAL ( fDX ), D3DVAL ( fDY ), D3DVAL ( fDZ ), FALSE );
}



/* Rotaciona um Frame. Usado para executar uma unica rotacao
especifica sobre o objeto, nao uma animacao.
dwGrupo - a qual grupo pertence o objeto
dwNdxFrame - o frame do objeto (identificador do objeto)
fRX, fRY, fRZ - vetor entorno do qual se dara' a rotacao
fAng - angulo de rotacao em radianos
Apos a rotacao os parametros de rotacao do Frame sao
restaurados para os valores antes da chamada dessa funcao.
Com isso, se um Frame jah estava girando, ele continuara girando.
**********************************************************/
void CCenario::mRotacionaObjeto ( DWORD dwGrupo, DWORD dwNdxFrame, float fRX, float fRY, float fRZ, float fAng )
{
   // parametros de rotacao do Frame atualmente
   D3DVECTOR
      stVetor;
   D3DVALUE
      fAngulo;
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->GetRotation ( m_lpFrGrupos [ dwGrupo ], &stVetor, &fAngulo );
   
   // altera Frame
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->SetRotation ( m_lpFrGrupos [ dwGrupo ], D3DVAL ( fRX ), D3DVAL ( fRY ), D3DVAL ( fRZ ), D3DVAL ( fAng ) );
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->Move ( D3DVAL ( 1.0 ) );
   
   // restaura valores originais
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->SetRotation ( m_lpFrGrupos [ dwGrupo ], stVetor.x, stVetor.y, stVetor.z, fAngulo );
}

 
 
/* Retorna a direcao de deslocamento de um Frame
dwGrupo - a qual grupo pertence o objeto
dwNdxFrame - o frame do objeto (identificador do objeto)
retorna fRX, fRY, fRZ - vetor direcao
**********************************************************/
void CCenario::mPegaDirecaoDeslocamentoObjeto ( DWORD dwGrupo, DWORD dwNdxFrame, float& fDX, float& fDY, float& fDZ )
{
   D3DVECTOR
      stVel;
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->GetVelocity ( m_lpFrGrupos [ dwGrupo ], &stVel, FALSE );

   fDX = stVel.x;
   fDY = stVel.y;
   fDZ = stVel.z;
}

 
 
/* Altera a posicao de um objeto
dwGrupo - a qual grupo pertence o objeto
dwNdxFrame - o frame do objeto (identificador do objeto no grupo)
**********************************************************/
void CCenario::mMudaPosicaoObjeto ( DWORD dwGrupo, DWORD dwNdxFrame, float fX, float fY, float fZ )
{
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->SetPosition ( m_lpFrGrupos [ dwGrupo ],
                      (D3DVALUE) fX, (D3DVALUE) fY, (D3DVALUE) fZ );
}

 
 
/* Retorna a posicao de um objeto
dwGrupo - a qual grupo pertence o objeto
dwNdxFrame - o frame do objeto (identificador do objeto no grupo)
retorna fRX, fRY, fRZ - vetor posicao
**********************************************************/
void CCenario::mPegaPosicaoObjeto ( DWORD dwGrupo, DWORD dwNdxFrame, float& fX, float& fY, float& fZ )
{
   D3DVECTOR
      stPos;
   m_lpFrObjetos [ dwGrupo ] [ dwNdxFrame ]->GetPosition ( m_lpFrGrupos [ dwGrupo ], &stPos );

   fX = stPos.x;
   fY = stPos.y;
   fZ = stPos.z;
}

 
 
/* Define a cor da fonte de luz
dwGrupo - a qual grupo pertence a fonte de luz
dwNdx - qual luz
fR, fG, fB - cor
**********************************************************/
void CCenario::mMudaIntencidadeLuz ( DWORD dwGrupo, DWORD dwNdx, float fR, float fG, float fB )
{
   LPDIRECT3DRMLIGHTARRAY
      lplpLights;
   
   m_lpFrObjetos [dwGrupo ] [ dwNdx ]->GetLights( &lplpLights );

   if ( lplpLights->GetSize () != 0 ) {

      LPDIRECT3DRMLIGHT
         lplpD3DRMLight;
   
      lplpLights->GetElement ( 0, &lplpD3DRMLight );

      lplpD3DRMLight->SetColor ( D3DRGB ( fR, fG, fB ) );
   }
}



/* Adiciona um objeto temporario (Frame) na cena: cria o seu frame ligado
ao frame do grupo e adiciona malha (forma do objeto).
Esse objeto serah exibido um certo numero de execucoes da cena, apohs
isso eh automaticamente removido.
Util para criar detritos, e pequenos corpos que nao requerem maior
controle.
fTempo - tempo de vida do objeto (conforme ciclos de renderizacao)
dwGrupo - em qual grupo criar 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 - deslocamento do frame do objeto no universo
fRX, fRY, fRZ - vetor usado como eixo para rotacao do objeto
fAng - angulo de rotacao (especifica uma rotacao que anima o objeto)
return: identifica o objeto criado no grupo, ou -1 se erro.
**********************************************************/
void CCenario::mCriaObjetoTemporario ( float fTempo,
                             DWORD dwGrupo, DWORD dwModelo,
                             float fX, float fY, float fZ,
                             float fDX, float fDY, float fDZ, 
                             float fRX, float fRY, float fRZ,
                             float fAng )
{
   // procura uma vaga no array de controle de objetos temporarios
   // para alocar
   int
      nVago = -1;
   for ( int ncT = 0; ncT < MAX_OBJETOS; ncT ++ )
      // verifica se achou vaga
      if ( ! m_astTemporarios [ ncT ].bOcupado ) {
         nVago = ncT;
         break;
      }
   // se nao achou espaco livre, nao cria objeto
   if ( nVago == -1 )
      return;

   // tenta criar o objeto
   DWORD
      dwObjeto;
   if ( ( dwObjeto = mCriaObjeto ( dwGrupo, dwModelo,
                                   fX, fY, fZ,
                                   fDX, fDY, fDZ, 
                                   fRX, fRY, fRZ, fAng ) ) == -1 )
      // falhou
      return;

   // usa o espaco de controle para objeto temporario
   m_astTemporarios [ nVago ].bOcupado = TRUE;
   m_astTemporarios [ nVago ].fTempo = fTempo;
   m_astTemporarios [ nVago ].dwGrupo = dwGrupo;
   m_astTemporarios [ nVago ].dwObjeto = dwObjeto;
}



/* Retorna o numero de objetos (frames ocupados) de um grupo
**********************************************************/
DWORD CCenario::mRetNumeroObjetos ( DWORD dwGrupo )
{
   int
      nTotal = 0;   // contador

   // varre grupo
   for ( int ncI = 0; ncI < MAX_OBJETOS; ncI ++ )

      // procura por algum frame vazio
      if ( m_lpFrObjetos [ dwGrupo ] [ ncI ] == NULL )
         nTotal ++;

   return nTotal;
}



/* Seleciona objetos que estao sobre as coordenadas
que foram definidas.
dwX, dwY - coordenadas da viewport usadas para selecionar objetos
retorna - frame do primeiro objeto selecionado ou NULL
**********************************************************/
LPDIRECT3DRMFRAME CCenario::mSelecionaObjetos ( DWORD dwX, DWORD dwY )
{
   // deseleciona objetos
   if ( m_pstSelecionados != NULL ) {
      m_pstSelecionados->Release();
      m_pstSelecionados = NULL;
   }
   
   // seleciona novos objetos   
   m_lpJanVis->Pick ( dwX, dwY, &m_pstSelecionados );
   
   // Verifica se criou array de objetos selecionados
   // e se selecionou um, pelo menos. Caso afirmativo
   // define um ponteiro para esse frame.
   LPDIRECT3DRMFRAMEARRAY
      pastFrSelecionados;          // conjunto de frames selecionados
   LPDIRECT3DRMFRAME
      pPrimeiroFrameSelecionado = NULL; // o primeiro objeto encontrado
   LPDIRECT3DRMVISUAL     
      pVisu;                       // parte visivel do frame selecionado
   if ( m_pstSelecionados && m_pstSelecionados->GetSize () ) {

      // primeiro objeto selecionado
      m_pstSelecionados->GetPick ( 0, &pVisu, &pastFrSelecionados, 0 );
      pastFrSelecionados->GetElement ( pastFrSelecionados->GetSize () - 1, &pPrimeiroFrameSelecionado );
   }

   return pPrimeiroFrameSelecionado;
}



/* Gera a cena: atualiza objetos e desenha
bMover - se TRUE - move objetos / FALSE - apenas desenha na janela
**********************************************************/
void CCenario::mProcessaCena ( BOOL bMover )
{
   if ( bMover ) {

      // atualiza objetos da cena
      if ( m_lpFrGrupos [ NUM_GRUPOS ]->Move ( D3DVAL ( 1.0 ) ) != D3DRM_OK )
         return;

      //for ( int ncG = 0; ncG < NUM_GRUPOS; ncG ++ )
         //if ( m_lpFrGrupos [ ncG ]->Move ( D3DVAL ( 1.0 ) ) != D3DRM_OK )
           // return;
   }

   // limpa viewport e prepara para desenhar
   if ( m_lpJanVis->Clear () != D3DRM_OK )
     return;

   // desenha cena no viewport
   if ( m_lpJanVis->Render ( m_lpFrGrupos [ NUM_GRUPOS ] ) != D3DRM_OK )
      return;

   // atualiza controle de objetos temporarios
   for ( int ncT = 0; ncT < MAX_OBJETOS; ncT ++ )

      // se tem informacoes sobre um objeto
      if ( m_astTemporarios [ ncT ].bOcupado ) {

         m_astTemporarios [ ncT ].fTempo -= 1.0;

         // se deve remover objeto
         if ( m_astTemporarios [ ncT ].fTempo < 0 ) {

            mDestroiObjeto ( m_astTemporarios [ ncT ].dwGrupo,
                             m_astTemporarios [ ncT ].dwObjeto );
            m_astTemporarios [ ncT ].bOcupado = FALSE;
         }
      }

   //for ( int ncG = 0; ncG < NUM_GRUPOS; ncG ++ )
     // if ( m_lpJanVis->Render ( m_lpFrGrupos [ ncG ] ) != D3DRM_OK )
         //return;

   // atualiza janela de visualizacao
   if ( m_lpDisp->Update () != D3DRM_OK )
      return;
}



///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
//
// FUNCOES EXTRA EM C, USADAS PELA CLASSE EM CONJUNTO COM DX
//
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////



/* Converte representacao de BBP para um formato do DD.
(viva a MS!!!)
**********************************************************/
static DWORD ConvBPP_DDBD ( int nBpp )
{
   switch ( nBpp ) {
      case 1:
         return DDBD_1;
      case 2:
         return DDBD_2;
      case 4:
         return DDBD_4;
      case 8:
         return DDBD_8;
      case 16:
         return DDBD_16;
      case 24:
         return DDBD_24;
      case 32:
         return DDBD_32;
      default:
         return 0;
   }
}



/* DX chama essa funcao para cada driver encontrado.
Faz a selecao do driver a ser usado conforme criterios
programados nessa funcao:
1 - encontrar algum driver que funcione (se nenhum selecionado);
2 - encontrar um driver de hardware (se nenhum assim selecionado);
3 - encontrar um driver com luz mono - mantendo criterio 2.
// REVISAR:
Luz RGB tem melhor qualidade, mas menor desempenho se driver para
software. Se driver para hardware, o driver de iluminacao RGB tem maior
desempenho. Talvez seja melhor selecionar RGB se driver de hardware, mas
tem que ver problemas das cores.
lpContext - terah o numero do driver selecionado para iniciar o jogo
**********************************************************/
static HRESULT WINAPI SelecionaDriver (
                        LPGUID lpGuid, LPSTR lpDeviceDescription,
                        LPSTR lpDeviceName, LPD3DDEVICEDESC lpHWDesc,
                        LPD3DDEVICEDESC lpHELDesc, LPVOID lpContext )
{
   static BOOL
      bHardware = FALSE;    // TRUE - driver inicial usa hardware
   static BOOL
      mono = FALSE;         // driver inicial usa luz monocromatica
   LPD3DDEVICEDESC
      lpDesc;
   int
      *lpStartDriver = (int*) lpContext;

   // define de onde tirar as informacoes: descritor com hardware ou soft
   lpDesc = lpHWDesc->dcmColorModel ? lpHWDesc : lpHELDesc;

   // verifica se numero de BBP eh suportado pelo driver
   if ( ! ( lpDesc->dwDeviceRenderBitDepth & ConvBPP_DDBD ( nNUMBPP ) ) )
      // nao suporta
      return D3DENUMRET_OK;
   
   // define informacoes sobre o driver
   memcpy ( &astDRV_GUID [ nNUM_DRVS ], lpGuid, sizeof ( GUID ) );
   lstrcpy ( &aszNOME_DRV [ nNUM_DRVS ] [ 0 ], lpDeviceName );
   static BOOL
      bHardDrv = FALSE,     // TRUE - driver com hardware / FALSE - nao
      bMonoDrv = FALSE;     // TRUE - driver com luz mono / FALSE - luz RGB

   // verifica se deve selecionar este driver ou usar algum jah selecionado
   // primeiro encontrado, nenhum selecionado -> seleciona
   if ( *lpStartDriver == -1 ) {
        
      // atualiza informacoes sobre o driver selecionado
      *lpStartDriver = nNUM_DRVS;
      if ( lpDesc == lpHWDesc )
         bHardDrv = TRUE;
      else
         bHardDrv = FALSE;
      if ( lpDesc->dcmColorModel & D3DCOLOR_MONO )
         bMonoDrv = TRUE;
      else
         bMonoDrv = FALSE;

   } else
      // drive encontrado eh de hardware, e o selecionado nao -> seleciona
      if ( lpDesc == lpHWDesc && ! bHardDrv) {
        
         // atualiza informacoes sobre o driver selecionado
         *lpStartDriver = nNUM_DRVS;
         if ( lpDesc == lpHWDesc )
            bHardDrv = TRUE;
         else
            bHardDrv = FALSE;
         if ( lpDesc->dcmColorModel & D3DCOLOR_MONO )
            bMonoDrv = TRUE;
         else
            bMonoDrv = FALSE;

      } else
         // driver encontrado eh o mesmo que o selecionado, mas tem luz
         // mono e o selecionado nao -> seleciona
         if ( ( ( lpDesc == lpHWDesc && bHardDrv ) ||      // mesmo driver
                ( lpDesc == lpHELDesc && ! bHardDrv ) ) &&
                ( lpDesc->dcmColorModel == D3DCOLOR_MONO ) ) {  // luz mono
            
            *lpStartDriver = nNUM_DRVS;
            if ( lpDesc == lpHWDesc )
               bHardDrv = TRUE;
            else
               bHardDrv = FALSE;
            if ( lpDesc->dcmColorModel & D3DCOLOR_MONO )
               bMonoDrv = TRUE;
            else
               bMonoDrv = FALSE;
         }
    
   // atualiza contagem
   nNUM_DRVS ++;
   // termina busca
   if ( nNUM_DRVS == MAX_DRIVERS )
      return ( D3DENUMRET_CANCEL );
    
   // continua procura
   return ( D3DENUMRET_OK );
}
