Gli esempi di integrazione tra directx 8.1 ed MFC forniti nell?SDK per Directx 8.1 non sono per niente chiari, né ci permettono di utilizzare Class Wizard per mappare nuovi messaggi, aggiungere funzioni, ecc?, con quest?ottimo tutorial faremo tutto ciò che abbiamo dettp in maniera veloce ed intuitiva. È? comunque necessario per leggere agevolmente il codice, avere conoscenze di MFC e dei criteri di funzionamento di un?applicazione Direct3d:
Il nostro obbiettivo è quello di creare una semplice applicazione SDI (Single Document Interface), in cui mostrare un oggetto tridimensionale in formato *.X e di farlo ruotare sull?asse y, il risultato finale dovrebbe essere il seguente:
Creare il progetto ed aggiungerne le librerie Create un nuovo poogetto con Visual Studio 6.0, quindi selezionate MFC AppWizard (exe) e intitolate il progetto col nome "prova_GPI", successivamente cliccate sul tasto ok.
Selezionate il Radio Button specificando Single Document e siate sicuri che l'opzione Document/View architecture support sia selezionata. A questo punto cliccate sul bottone Finish.
Ora bisogna inserire le librerie delle
DirectX necessarie per la compilazione del progetto. Prima di far questo andate sul menu
Build e poi su
Set Active Configuration... e selezionate l'opzione
Win32 Release.
Successivamente selezionate
Project dal menue poi
Settings (richiamabile anche pigiando ALT+F7), selezionate quindi la cartella Link, nel campo
Object/library modules aggiungete i tre file lib: d3d8.lib D3dx8.lib Winmm.lib.
Invece nella cartella C/C++ a sinistra di Link, scegliete la categoria
Precompiled Headers e selezionate il radio button
Automatic use of precompiled headersATTENZIONE: ovviamente dovranno essere ben impostate le directory in cui il compilatore andrà a cercare i files relativi agli headers ed alle librerie!!!Implementazione del manager 3d e della classe mesh personalizzataDefiniamo la classe base Graphics che ci permetterà di effettuare l?integrazione tra Directx ed MFC, quindi dal menu cliccate su
Insert e poi su
New Class, si aprirà una finestra, ora da
Class Type scegliete il campo
Generic Class, infine nel campo
Name inserite il nome
CManager3D; ora nel file header della classe CManager3D digitiamo il codice seguente sostituendo la classe scritta in maniera automatica dal compilatore:
#include <d3d8.h> //dichiarazione oggetti directx
#include <D3dx8mesh.h> //gestione delle mesh x
#include "Mesh.h" //header per la gestione della classe contenente mesh *.x
class CManager3D
{
public:
CManager3D();
virtual ~CManager3D();
public:
CMesh OBJ; //oggetto mesh personalizzato, verrà spiegato in seguito
float time;
bool create(HWND hwnd, int width,int height, bool windowed);
bool init (void);
bool resize(int width, int height);
bool update(void);
bool render(void);
IDirect3DDevice8* getDevice() {return _device;}
protected:
IDirect3D8* _d3d8; //oggetti per la gestione della device 3d
IDirect3DDevice8* _device;
};
Quindi passiamo all?implementazione del file cpp:
Quindi passiamo all?implementazione del file cpp:
CManager3D:: CManager3D () //costruttore
{
_d3d8 = NULL;
_device = NULL;
}
CManager3D::~ CManager3D () //distruttore
{
OBJ.SvuotaClasse();
if (_device) {
_device->Release();
_device = NULL; }
if (_d3d8) {
_d3d8->Release();
_d3d8 = NULL; }
}
bool CManager3D::init(void)
{
D3DXMATRIX m;
D3DXMatrixIdentity( &m );
_device->SetTransform(D3DTS_WORLD, &m); //impostazioni della telecamera
D3DXVECTOR3 eye(0.0f, 0.0f, -5.0f);
D3DXVECTOR3 at(0.0f, 0.0f, 1.0f);
D3DXVECTOR3 look(0.0f, 1.0f, 0.0f);
D3DXMatrixLookAtLH(&m, &eye, &at, &look);
_device->SetTransform(D3DTS_VIEW, &m);
OBJ.CaricaFile("navicella3.x"); //file della nostra navicella, ma al posto di
//questa potete utilizzare anche gli oggetti X
//forniti con l?SDK delle direct 8.1
//ricordate di avere nella stessa path dell?eseguibile
//il file *.x e la/le texture!
_device->SetRenderState (D3DRS_LIGHTING,FALSE );
return true;
}
bool CManager3D::resize(int width, int height) //resize della scena al cambiamento delle
{ //delle dimensioni della finestra
D3DXMATRIX m;
float aspect = (float)width / (float)height;
float fov = 3.14f / 2.0f;
D3DXMatrixPerspectiveFovLH(
&m,
fov,
aspect,
1.0f,
100.0f);
if( _device )
_device->SetTransform(D3DTS_PROJECTION, &m);
return true;
}
bool CManager3D::update(void) //rotazione della mesh X al variare del tempo
{
if (OBJ.ang_y < 2.0f * D3DX_PI) //notate la logica che c?è dietro, in questo modo evitiamo OBJ.ang_y += 3.0f * time; //l?overflow della variabile reale
else
OBJ.ang_y = OBJ.ang_y - (2.0f * D3DX_PI) + (3.0f * time);
return true;
}
bool CManager3D::render(void) //renderizzazione della scena
{
if( _device )
{
_device->Clear(
0,
0,
D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, //pulizia dello z-buffer e disegno bianco dello sfondo
RGB(255,255,255) , 1.0f, 0);
_device->BeginScene();
OBJ.RenderizzaTutteLeMesh();
_device->EndScene();
_device->Present(NULL, NULL, NULL, NULL);
}
return true;
}
bool CManager3D::create(HWND hwnd, int width,int height, bool windowed)
{
_d3d8 = Direct3DCreate8(D3D_SDK_VERSION);
D3DDISPLAYMODE displayMode;
_d3d8->GetAdapterDisplayMode (D3DADAPTER_DEFAULT, &displayMode);
D3DPRESENT_PARAMETERS presentParameters;
memset(&presentParameters,0,sizeof(D3DPRESENT_PARAMETERS));
presentParameters.Windowed = windowed; //rendering in finestra
presentParameters.BackBufferCount = 1;
presentParameters.SwapEffect = D3DSWAPEFFECT_DISCARD;
presentParameters.BackBufferFormat = displayMode.Format;
presentParameters.BackBufferWidth = width; //larghezza della finestra
presentParameters.BackBufferHeight = height; //altezza della finestra
presentParameters.hDeviceWindow = hwnd; //handle della finestra di visualizzazione
presentParameters.EnableAutoDepthStencil = TRUE;
presentParameters.AutoDepthStencilFormat = D3DFMT_D16; //16 bit
if (FAILED(_d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd,D3DCREATE_HARDWARE_VERTEXPROCESSING,
&presentParameters, &_device)))
_d3d8->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
hwnd,D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&presentParameters, &_device);
return true;
}
Create una nuova classe generica e chiamatela CMesh, questa farà da contenitore per gli oggetti *.X, eccovi la dichiarazione della classe:
#include <D3dx8math.h>
class CMesh
{
public:
CMesh();
virtual ~CMesh();
D3DXVECTOR3 posizione, scaling;
LPD3DXMESH mesh_geom; //conterrà le mesh caricate da file x
LPDIRECT3DTEXTURE8 *mesh_texture; //conterrà le texture caricate da file x
LPD3DXBUFFER buf_materiali, //conterrà i materiali
buf_adiacenze; //conterrà gli indici con le adiacenze
DWORD num_materiali;
D3DXMATERIAL *materiali;
D3DMATERIAL8 *mesh_materiali;
char nome_file[512];
float ang_y;
short TypeRender; //serve per settare il tipo di renderizzazione
BOOL CaricaFile (const char *buf);
BOOL RenderizzaTutteLeMesh(void);
BOOL SvuotaClasse (void);
};
//funzioni di utilità per le conversioni delle stringhe di testo e ricerca path
//si trovano tra le utility delle directx
HRESULT DXUtil_FindMediaFile(TCHAR* strPath, TCHAR* strFilename);
const TCHAR* DXUtil_GetDXSDKMediaPath();
VOID DXUtil_ConvertGenericStringToAnsi(CHAR* strDestination, const TCHAR* tstrSource, int cchDestChar = -1);
VOID DXUtil_ConvertAnsiStringToGeneric(TCHAR* tstrDestination, const CHAR* strSource, int cchDestChar = -1);
Passiamo all'implementazione della classe, ci sono degli spunti per migliorare il lavoro:
#include "Mesh.h"
#include "Manager3d.h"
extern CManager3D *manager3d;
CMesh::CMesh()
{
}
CMesh::~CMesh()
{
SvuotaClasse();
}
BOOL CMesh::CaricaFile(const char *buf) //caricamento della mesh, passiamo quindi il nome del file *.X
{
mesh_geom = NULL; //Annullamento puntatori
mesh_texture = NULL;
materiali = NULL;
mesh_materiali = NULL;
posizione.x = posizione.y = posizione.z = 0.0f;
ang_y = 0.0f;
TypeRender = D3DFILL_SOLID; //La mesh verrà renderizzata con la texture?
DWORD i;
strcpy(nome_file,buf);
TCHAR strPath[MAX_PATH];
CHAR strPathANSI[MAX_PATH];
// Cerca la path del file, e la converte in ANSI
DXUtil_FindMediaFile( strPath, nome_file );
DXUtil_ConvertGenericStringToAnsi( strPathANSI, strPath );
D3DXLoadMeshFromX( strPathANSI, D3DXMESH_SYSTEMMEM, //studiatevi le flag D3DXMESH per la gestione dei
//vertex buffer, infatti le mesh andranno nella
//memoria della scheda video?
manager3d->getDevice(), &buf_adiacenze,
&buf_materiali, &num_materiali,
&mesh_geom );
materiali = (D3DXMATERIAL*)buf_materiali->GetBufferPointer();
mesh_materiali = new D3DMATERIAL8[num_materiali];
mesh_texture = new LPDIRECT3DTEXTURE8[num_materiali];
for (i = 0; i < num_materiali; i++)
{
mesh_materiali = materiali.MatD3D;
mesh_materiali.Ambient = mesh_materiali.Diffuse;
TCHAR strPath[512];
DXUtil_FindMediaFile( strPath, materiali.pTextureFilename );
mesh_texture = NULL;
// Crea le texture
if( materiali.pTextureFilename ) // attenzione ai materiali con stessi nomi di texture?
{
TCHAR strTexture[MAX_PATH];
TCHAR strTextureTemp[MAX_PATH];
DXUtil_ConvertAnsiStringToGeneric( strTextureTemp, materiali.pTextureFilename );
DXUtil_FindMediaFile( strTexture, strTextureTemp );
D3DXCreateTextureFromFile( manager3d->getDevice(), strTexture,
&mesh_texture);
}//fine creazione texture
}//fine for
buf_materiali->Release();
return TRUE;
}
BOOL CMesh::SvuotaClasse(void) //svuotamento delle strutture che risiedono in memoria
{
if( mesh_materiali )
delete [] mesh_materiali;
if( mesh_texture )
{
for( DWORD i = 0; i < num_materiali; i++ )
{
if( mesh_texture )
mesh_texture->Release();
}
delete[] mesh_texture;
}
if( mesh_geom )
mesh_geom->Release();
return TRUE;
}
BOOL CMesh::RenderizzaTutteLeMesh(void) //l?ho chiamata così perchè un file *.x potrebbe contenere più mesh?
{
DWORD i;
//viene effettuata la traslazione e la rotazione sull?asse Y, ma potreste effettuare
//rotazioni anche sugli altri assi?
D3DXMATRIX wld, rot, Mat;
D3DXMatrixTranslation(&wld, posizione.x, posizione.y, posizione.z);
D3DXMatrixRotationY(&rot, ang_y);
D3DXMatrixMultiply(&wld, &rot, &wld);
manager3d->getDevice()->SetTransform(D3DTS_WORLD,&wld);
D3DXMatrixIdentity(&Mat);
manager3d->getDevice()->SetTransform(D3DTS_TEXTURE0,&Mat);
for (i = 0; i < num_materiali; i++)
{
manager3d->getDevice()->SetMaterial( &mesh_materiali);
manager3d->getDevice()->SetTexture( 0, mesh_texture);
//sarebbe più veloce ed indicato usare uno switch case?
if (TypeRender == D3DFILL_SOLID)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_SOLID);
else if (TypeRender == D3DFILL_WIREFRAME)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_WIREFRAME);
else if (TypeRender == D3DFILL_POINT)
manager3d->getDevice()->SetRenderState(D3DRS_FILLMODE , D3DFILL_POINT);
mesh_geom->DrawSubset(i); //disegno della mesh
}
return TRUE;
}
HRESULT DXUtil_FindMediaFile( TCHAR* strPath, TCHAR* strFilename )
{
HANDLE file;
if( NULL==strFilename || NULL==strPath )
return E_INVALIDARG;
// Check if the file exists in the current directory
_tcscpy( strPath, strFilename );
file = CreateFile( strPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL );
if( INVALID_HANDLE_VALUE != file )
{
CloseHandle( file );
return S_OK;
}
// Check if the file exists in the current directory
_stprintf( strPath, _T("%s%s"), DXUtil_GetDXSDKMediaPath(), strFilename );
file = CreateFile( strPath, GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, 0, NULL );
if( INVALID_HANDLE_VALUE != file )
{
CloseHandle( file );
return S_OK;
}
// On failure, just return the file as the path
_tcscpy( strPath, strFilename );
return E_FAIL;
}
const TCHAR* DXUtil_GetDXSDKMediaPath()
{
static TCHAR strNull[2] = _T("");
static TCHAR strPath[MAX_PATH];
DWORD dwType;
DWORD dwSize = MAX_PATH;
HKEY hKey;
// Open the appropriate registry key
LONG lResult = RegOpenKeyEx( HKEY_LOCAL_MACHINE,
_T("Software\\Microsoft\\DirectX"),
0, KEY_READ, &hKey );
if( ERROR_SUCCESS != lResult )
return strNull;
lResult = RegQueryValueEx( hKey, _T("DX8SDK Samples Path"), NULL,
&dwType, (BYTE*)strPath, &dwSize );
RegCloseKey( hKey );
if( ERROR_SUCCESS != lResult )
return strNull;
_tcscat( strPath, _T("\\Media\\") );
return strPath;
}
//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertGenericStringToAnsi()
// Desc: This is a UNICODE conversion utility to convert a TCHAR string into a
// CHAR string. cchDestChar defaults -1 which means it
// assumes strDest is large enough to store strSource
//-----------------------------------------------------------------------------
VOID DXUtil_ConvertGenericStringToAnsi( CHAR* strDestination, const TCHAR* tstrSource,
int cchDestChar )
{
if( strDestination==NULL || tstrSource==NULL || cchDestChar == 0 )
return;
#ifdef _UNICODE
DXUtil_ConvertWideStringToAnsi( strDestination, tstrSource, cchDestChar );
#else
if( cchDestChar == -1 )
{
strcpy( strDestination, tstrSource );
}
else
{
strncpy( strDestination, tstrSource, cchDestChar );
strDestination[cchDestChar-1] = '\0';
}
#endif
}
//-----------------------------------------------------------------------------
// Name: DXUtil_ConvertAnsiStringToGeneric()
// Desc: This is a UNICODE conversion utility to convert a CHAR string into a
// TCHAR string. cchDestChar defaults -1 which means it
// assumes strDest is large enough to store strSource
//-----------------------------------------------------------------------------
VOID DXUtil_ConvertAnsiStringToGeneric( TCHAR* tstrDestination, const CHAR* strSource,
int cchDestChar )
{
if( tstrDestination==NULL || strSource==NULL || cchDestChar == 0 )
return;
#ifdef _UNICODE
DXUtil_ConvertAnsiStringToWide( tstrDestination, strSource, cchDestChar );
#else
if( cchDestChar == -1 )
{
strcpy( tstrDestination, strSource );
}
else
{
strncpy( tstrDestination, strSource, cchDestChar );
tstrDestination[cchDestChar-1] = '\0';
}
#endif
}
Integrazione della classe CManager3D con la visualizzazioneAggiungete alla classe CDirect3DMFCView l'oggetto
CManager3D *manager3d; nella sezione pubblica, però ricordatevi di scrivere
#include "Manager3D.h" prima della dichiarazione della classe
CProva_GPIView.
A questo punto lanciate
Class Wizard e mappatevi i seguenti eventi e funzioni virtuali di
CProva_GPIView:
OnInitialUpdate
WM_ERASEBKGND
WM_SIZE Dopo averne creato in maniera automatica le funzioni passiamo alla loro sostituzione
void CProva_GPIView::OnInitialUpdate()
{
CView::OnInitialUpdate();
CRect rect;
GetClientRect(&rect); //vengono prese le dimensioni della finestra target di rendering
manager3d = new CManager3D; //viene allocata la memoria per il manager3d
if( !manager3d->create( GetSafeHwnd(),rect.right, rect.bottom, true) )
{
MessageBox("create() - Failed", "CView"); //abilitazione effettiva della device 3d
return;
}
if( !manager3d->init() ) //inizializzazione della telecamera, della mesh X e dei render state
{
MessageBox("init() - Failed", "CView");
return;
}
if( !manager3d->resize(rect.right, rect.bottom) )
{
MessageBox("resize() - Failed", "CView");
return;
}
}
BOOL CProva_GPIView::OnEraseBkgnd(CDC* pDC)
{
return FALSE;
}
void CProva_GPIView::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
if (manager3d)
manager3d->resize(cx, cy); //cambiamento delle dimensioni della finestra target di rendering
}
Aggiungete nella costruttore della classe CProva_GPIView:
manager3d = NULL;e nel distruttore:
if (manager3d)
delete manager3d;Eccovi invece il codice di disegno della finestra, andate a sostituirne il codice:
void CProva_GPIView::OnDraw(CDC* pDC)
{
CProva_GPIDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
static float lastTime = (float)timeGetTime();
float currentTime = (float)timeGetTime();
float deltaTime = (currentTime - lastTime) * 0.001f; //viene calcolato il tempo che intercorre
manager3d->time = deltaTime;
//tra un frame e l?altro
if (manager3d)
{
manager3d->update(); //viene aggiornata la rotazione della mesh
manager3d->render(); //disegno della scena
}
lastTime = currentTime;
}
Includete quindi sopra i seguenti headers:
#include "mmsystem.h" //funzioni per la gestione del timer di sistema
#include "Manager3D.h" //manager 3d creato da noi
Ora andremo a modificare la classe CProva_GPIApp responsabile dell'apertura della finestra di visualizzazione:
Apriamo come al solito Class Wizard, selezionamo all'interno di class name CProva_GPIApp ed editiamoci la funzione:
OnIdle Quindi sostituiamone il codice:
BOOL CProva_GPIApp::OnIdle(LONG lCount)
{
CWinApp::OnIdle(lCount);
AfxGetMainWnd()->Invalidate(false);
return TRUE;
}
In Questo modo verrà invalidata l'area della finestra di visualizzazione ad ogni loop dell'applicazione (ciclo idle), cioè verrà aggiornato il disegno della finestra.
Compilate il tutto e non dovreste riscontrare problemi. Da qui con l'integrazioni di finestre di dialogo modeless ed i Menù potrete implementare tool per la visualizzazione di modelli ed edito e quant?altro sfruttando le MFC per la costruzione dell'interfaccia utente! Ovviamente dovrete andare avanti con lo studio della programmazione
3D con
DirectX !
Biagio Iannuzzi
Staff Tecnico
#->Vis-ingeniI<->DeveLOPmenT<-#
http://www.vis-ingenii.com ">
http://www.vis-ingenii.com