V dnešní lekci se konečně dostaneme k dlouho slibovaným transformacím objektů. Dále rozšíříme projekt Display o třídu X3DObject, která zapouzdří vše, co jsme minule použili pro náš rotující trávník.
Na začátku jsme do našeho projektu přidali třídu CMesh, která zapouzdřuje objekt načtený ze souboru. Nevýhodou bylo, že jsme nemohli transformovat každý vytvořený objekt samostatně, zkrátka jsme měli pouze jednu transformační matici pro všechny objekty ve scéně. Dnes do této třídy přidáme právě transformační matici, takže každý objekt bude transformován zvlášť. Tuto matici nastavíme před každým vykreslením toho daného objektu jako světovou. Všechny vertexy objektu se tedy budou transformovat podle této matice. Dále přidáme sadu členských funkcí, pomocí kterých budeme tuto matici upravovat - transformovat a tím vytvářet a skládat základní transformace na objekt. O transformacích jsme mluvili v jedné z prvních lekcích. Máme tedy tři základní transformace, pomocí kterých vytvoříme libovolný pohyb objektu. Jsou to:
1) pohyb posuvný - translace
2) pohyb otáčivý - rotace
3) změna měřítka
Ke všem těmto transformacím existují funkce z knihovny D3DX, které vytvářejí požadované matice. Výhodou matic je, že jdou skládat a můžeme tak složit více efektů dohromady. I k tomu nám pomůže pár funkcí z knihovny D3DX.
Přejděme tedy rovnou k příkladu.
Do třídy XMesh přidáme atribut D3DXMATRIX:
D3DXMATRIX *m_lpTransMat;
Dále vytvoříme sadu členských funkcí pro transformaci objektu:
void Translate(float fX, float
fY, float fZ);
void RotateX(float fAngle);
void RotateY(float fAngle);
void RotateZ(float fAngle);
void ScaleXYZ(float fFactor);
void ScaleX(float fFactor);
void ScaleY(float fFactor);
void ScaleZ(float fFactor);
Objekt m_lpTransMat musíme v konstruktoru vytvořit a v destruktoru zničit:
XMesh::XMesh(void)
{
m_lpMesh = NULL;
m_lpDevice = NULL;
m_bVisible = FALSE;
m_lpTransMat = new
D3DXMATRIX;
D3DXMatrixIdentity(m_lpTransMat);
}
XMesh::~XMesh(void)
{
Release();
m_lpDevice = NULL;
SAFE_DELETE_ARRAY(m_pMaterials);
SAFE_DELETE_ARRAY(m_pTextures);
SAFE_DELETE(m_lpTransMat);
}
Nakonec nesmíme zapomenout nastavit matici těsně před vykreslením:
int XMesh::Draw()
{
if(m_lpDevice && m_bVisible && m_lpMesh)
{
// Set transformation matrix before rendering
if(m_lpTransMat)
{
m_lpDevice->SetTransform(D3DTS_WORLD, m_lpTransMat);
}
if(m_meshType == CUSTOM)
{
for(int i =
0; i < (int)m_dwNumMat; i++)
{
m_lpDevice->SetMaterial(&m_pMaterials[i]);
m_lpDevice->SetTexture(0, m_pTextures[i].GetTexture());
m_lpMesh->DrawSubset(i);
}
}
else
{
// set
default material
D3DMATERIAL8
mtrl;
ZeroMemory(
&mtrl, sizeof(D3DMATERIAL8) );
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
m_lpDevice->SetMaterial(&mtrl);
m_lpDevice->SetTexture(0, NULL);
m_lpMesh->DrawSubset(0);
}
return 0;
}
return -1;
}
A implementujeme transformační metody, které jen modifikují transformační matici:
void XMesh::Translate(float fX,
float fY, float fZ)
{
D3DXMATRIX matTemp;
// Create
translation matrix
D3DXMatrixTranslation(&matTemp, fX, fY, fZ);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::RotateX(float fAngle)
{
D3DXMATRIX matTemp;
// create rotation
matrix
D3DXMatrixRotationX(&matTemp, fAngle);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::RotateY(float fAngle)
{
D3DXMATRIX matTemp;
// create rotation
matrix
D3DXMatrixRotationY(&matTemp, fAngle);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::RotateZ(float fAngle)
{
D3DXMATRIX matTemp;
// create rotation
matrix
D3DXMatrixRotationZ(&matTemp, fAngle);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::ScaleXYZ(float fFactor)
{
D3DXMATRIX matTemp;
// Create scaling
matrix
D3DXMatrixScaling(&matTemp, fFactor, fFactor, fFactor);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::ScaleX(float fFactor)
{
D3DXMATRIX matTemp;
// Create scaling
matrix
D3DXMatrixScaling(&matTemp, fFactor, 1.0f, 1.0f);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::ScaleY(float fFactor)
{
D3DXMATRIX matTemp;
// Create scaling
matrix
D3DXMatrixScaling(&matTemp, 1.0f, fFactor, 1.0f);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
void XMesh::ScaleZ(float fFactor)
{
D3DXMATRIX matTemp;
// Create scaling
matrix
D3DXMatrixScaling(&matTemp, 1.0f, 1.0f, fFactor);
// modify
transformation matrix of the object
D3DXMatrixMultiply(m_lpTransMat, m_lpTransMat, &matTemp);
}
Na začátku je matice jednotková, to znamená, že neprovádí žádnou transformaci. Ještě trochu upravíme projekt Tester, abychom vyzkoušeli všechny nové metody. Nyní už nebudeme potřebovat transformační matici g_matWorld1. Tuto matici zastupují postupně všechny matice jednotlivých objektů. Takže místo úpravy této matice v funkci UpdateFrame() budeme upravovat matice objektů podle potřeby:
D3DXMATRIX matRot;
float fFactor = cmnGetTime(TIMER_GETELAPSEDTIME1);
if(fFactor != 0) {
g_Torus.RotateY(0.8f * fFactor);
g_Cylinder.RotateX(1.0f * fFactor);
g_Box.RotateZ(3.0f * fFactor);
g_Teapot.RotateZ(-0.1f * fFactor);
g_Airplane.RotateZ(1.8f * fFactor);
g_Tiger.RotateZ(5.8f * fFactor);
g_Sphere.RotateY(6.0f * fFactor);
}
Navíc můžeme každý objekt uvést do počátečního stavu v inicializační funkci:
// preddefinovane objekty
g_Sphere.CreateSphere(2.0f, 48, 48, g_theDisplay.GetDevice());
g_Sphere.Translate(4.0f, 0.0f, -30.0f);
g_Sphere.ScaleXYZ(0.1f);
g_Box.CreateBox(1.0f, 1.0f, 6.0f,
g_theDisplay.GetDevice());
g_Cylinder.CreateCylinder(1.5f, 2.5f, 4.0f, 24, 8, g_theDisplay.GetDevice());
g_Torus.CreateTorus(0.5f, 2.0f, 64, 64, g_theDisplay.GetDevice());
g_Teapot.CreateTeapot(g_theDisplay.GetDevice());
g_Teapot.RotateX(D3DX_PI/2);
g_Teapot.ScaleXYZ(0.5f);
g_Teapot.Translate(4.5f, 0.0f, 0.0f);
// model tygra
g_Tiger.LoadMeshFromFile("tiger.x", g_theDisplay.GetDevice(),
g_theDisplay.GetResourceManager());
g_Tiger.RotateX(D3DX_PI/2);
g_Tiger.ScaleY(0.5f);
g_Tiger.Translate(0.0f, 0.0f, 4.0f);
g_Airplane.LoadMeshFromFile("airplane
2.x", g_theDisplay.GetDevice(), g_theDisplay.GetResourceManager());
g_Airplane.ScaleXYZ(0.1f);
g_Airplane.Translate(6.0f, 0.0f, 0.0f);
g_Airplane.RotateX(D3DX_PI/2);
Když jsme minule vytvářeli rotující čtverec, mohli jsme všechny jeho atributy zapouzdřit do nějaké třídy. V této lekci tuto třídu vytvoříme a nazveme jí X3DObject. Bude obsahovat všechny atributy jako náš čtverec a krom toho ještě několik doplňujících položek jako je transformační matice atd. Jak se tento objekt liší od XMesh? XMesh nahráváme většinou ze souboru .x. Naopak ve třídě X3DObject budeme pracovat s vlastními vertex a index buffery, takže tak můžeme vytvořit například povrch země apod. Tato třída ale bude mít velice podobné metody, jen bude vykreslovat data z jiného zdroje. Deklaraci třídy zapíšeme takto:
class
DISPLAY_API X3DObject
{
// is
object visible?
BOOL m_bVisible;
// set
trans matrix before render?
BOOL m_bApplyTransformations;
// vertex
buffer
XVertexBuffer m_VB;
// index
buffer
XIndexBuffer m_IB;
//
texture
XTexture
m_Texture;
//
transformation matrix
D3DXMATRIX *m_lpTransMat;
//
pointer to device
LPDIRECT3DDEVICE8 m_lpDevice;
public:
//
standard methods
int Init(
LPDIRECT3DDEVICE8 lpDevice,
LPCSTR szTexture, int iMipMap, D3DFORMAT formatTexture ,
DWORD dwVBSize,
DWORD dwIBSize,
DWORD dwFlags);
int Draw( D3DPRIMITIVETYPE Type,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount);
int Restore();
void Release();
XVertexBuffer* GetVB() { return &m_VB; }
XIndexBuffer* GetIB() { return &m_IB; }
XTexture* GetTexture() { return &m_Texture; }
LPDIRECT3DDEVICE8 GetDevice() { return m_lpDevice; }
BOOL IsVisible() { return m_bVisible; }
void Visible(BOOL bVis = TRUE) { m_bVisible = bVis; }
void EnableTransformation(BOOL bEnable = TRUE) {m_bApplyTransformations
= bEnable;}
BOOL IsTransformationEnabled() { return m_bApplyTransformations;}
//
transformations methods
void
Translate(float fX, float fY, float fZ);
void RotateX(float fAngle);
void RotateY(float fAngle);
void RotateZ(float fAngle);
void ScaleXYZ(float fFactor);
void ScaleX(float fFactor);
void ScaleY(float fFactor);
void ScaleZ(float fFactor);
public:
X3DObject(void);
~X3DObject(void);
};
Na první pohled se zdá být třída složitá, ale je to velice jednoduché. Ve skutečnosti jen zjednodušujeme implementaci čtverce z minulé lekce. Všimněte si stejných atributů. Navíc jsme přidali transformační matici a "vypínač" vlastní transformace, abychom mohli rotaci vypnout. Samozřejmě si objekt pamatuje také ukazatel zařízení.
Dále nadefinujeme příznaky metody Init(), které nastavíme, pokud chceme vytvořit VB a IB s vlastností WRITE ONLY, která určí, že dané buffery jsou určeny pouze pro zápis a jsou umístěny v hůře přístupné paměti pro uživatele, ale lépe přístupné pro hardware:
#define VB_WRITEONLY
0x00000001
#define IB_WRITEONLY 0x00000002
A nyní už jen nadefinujeme jednotlivé metody. Zde
není nic nepochopitelného. Transformace je naprosto totožná jako v
případě třídy XMesh, jen se provádí pokud
je zapnuta atributem m_bApplyTransformations,
takže tyto metody zcela vynechám. Budeme tedy implementovat jen metody
Init(), Release(),
Draw(), Restore(),
konstruktor a destruktor.
Začneme konstruktorem a destruktorem:
X3DObject::X3DObject(void)
{
m_lpDevice = NULL;
m_lpTransMat = new D3DXMATRIX;
D3DXMatrixIdentity(m_lpTransMat);
}
X3DObject::~X3DObject(void)
{
SAFE_DELETE(m_lpTransMat);
}
Zde vytvoříme objekt transformační matice a vynulujeme pointer na zařízení, podle kterého poznáme, že objekt ještě nebyl inicializován.
Metoda Init():
int X3DObject::Init(LPDIRECT3DDEVICE8
lpDevice,
LPCSTR szTexture, int iMipMap, D3DFORMAT formatTexture,
DWORD dwVBSize,
DWORD dwIBSize,
DWORD dwFlags)
{
DWORD dwUsage = 0;
int iRet;
m_lpDevice = lpDevice;
if(m_lpDevice)
{
if(dwVBSize != 0)
{
if(VB_WRITEONLY
& dwFlags)
{
dwUsage |= D3DUSAGE_WRITEONLY;
}
iRet = m_VB.Create(dwVBSize,
dwUsage, VERTEXFORMAT, D3DPOOL_DEFAULT, m_lpDevice);
if(iRet != 0)
{
TRACE("Cannot create VB for 3DObject.");
}
}
if(dwIBSize != 0)
{
dwUsage = 0;
if(IB_WRITEONLY
& dwFlags)
{
dwUsage |= D3DUSAGE_WRITEONLY;
}
iRet = m_IB.Create(dwIBSize,
dwUsage, D3DFMT_INDEX16, D3DPOOL_DEFAULT, m_lpDevice);
if(iRet != 0)
{
TRACE("Cannot create IB for 3DObject.");
}
}
if(szTexture)
{
iRet = m_Texture.LoadTextureFromFileEx(szTexture,
iMipMap, formatTexture, lpDevice);
if(iRet != 0)
{
TRACE("Cannot create texure.");
}
}
m_bVisible = TRUE;
m_bApplyTransformations = TRUE;
return S_OK;
}
return S_FALSE;
}
Zde musíme vytvořit všechny dílčí objekty: VB, IB a texturu. Jako parametry posíláme ukazatel na zařízení, jméno textury, počet mipmap a formát textury, dále velikost VB a IB a nakonec parametr příznaků pro další vlastnosti, zatím tedy jen pro parametry WRITE ONLY.
Metoda pro vykreslení Draw():
int X3DObject::Draw(D3DPRIMITIVETYPE
Type,
UINT MinIndex,
UINT NumVertices,
UINT StartIndex,
UINT PrimitiveCount)
{
if(m_lpDevice)
{
if(m_lpTransMat)
{
m_lpDevice->SetTransform(D3DTS_WORLD,
m_lpTransMat);
}
if(m_bVisible)
{
m_lpDevice->SetVertexShader(VERTEXFORMAT);
m_lpDevice->SetStreamSource(0,
m_VB.GetBuffer(), sizeof(VERTEX));
m_lpDevice->SetIndices(m_IB.GetBuffer(),
0);
m_lpDevice->SetTexture(0,
m_Texture.GetTexture());
m_lpDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST,
0, 4, 0, 2);
}
// set indentity matrix
if(m_lpTransMat)
{
D3DXMATRIX
mat;
D3DXMatrixIdentity(&mat);
m_lpDevice->SetTransform(D3DTS_WORLD,
&mat);
}
return S_OK;
}
return S_FALSE;
}
Zde nastavíme transformaci a pokud je objekt viditelný vykreslíme ho úplně stejně jako v minulé lekci. Všechny parametry metody DrawIndexedPrimitive() jsou zároveň parametry samotné metody Draw(). Na konci je třeba nastavit jednotkovou matici pro objekty, které nenastavují vlastní matici.
Nakonec doděláme metody Release() a Restore(), kde jen voláme členské funkce pod-objektů VB, IB a textury:
int X3DObject::Restore()
{
if(m_lpDevice)
{
m_Texture.Restore();
m_VB.Restore();
m_IB.Restore();
// after that, caller must refill buffers
return S_OK;
}
return S_FALSE;
}
void X3DObject::Release()
{
if(m_lpDevice)
{
m_Texture.Release();
m_VB.Release();
m_IB.Release();
}
}
Ve všech metodách kontrolujeme ukazatel na zařízení, tak poznáme, že byla volána metoda Init().
Nyní můžeme rotující čtverec vytvořit pomocí třídy X3DObject. Zdrojový kód najdete v příkladu v sekci Downloads.
Těším se příště nashledanou.